13 Commits

42 changed files with 2134 additions and 17722 deletions

258
.github/latex-manual/manual-template.tex vendored Normal file
View File

@@ -0,0 +1,258 @@
%% ============================================================
%% KST4Contest pandoc LaTeX manual template
%% PDF engine: XeLaTeX
%% Usage: pandoc --template=manual-template.tex --pdf-engine=xelatex
%% ============================================================
\documentclass[11pt,a4paper]{article}
%% ─── Font / encoding ──────────────────────────────────────────────────────
\usepackage{fontspec}
% Latin Modern handles all Western European characters (umlauts etc.)
\defaultfontfeatures{Ligatures=TeX,Scale=MatchLowercase}
%% ─── Language ─────────────────────────────────────────────────────────────
\usepackage{polyglossia}
$if(polyglossia-lang)$
\setmainlanguage{$polyglossia-lang$}
$else$
\setmainlanguage{english}
$endif$
%% ─── Page layout ──────────────────────────────────────────────────────────
\usepackage[a4paper, top=2.5cm, bottom=2.5cm, left=2.5cm, right=2.5cm]{geometry}
%% ─── Text decorations (strikethrough via ~~...~~ in Markdown → \st{}) ────
\usepackage{soul}
%% ─── Colors ───────────────────────────────────────────────────────────────
\usepackage[dvipsnames,svgnames,x11names]{xcolor}
\definecolor{brand-green}{RGB}{7,166,54}
\definecolor{link-blue}{RGB}{0,86,163}
\definecolor{code-bg}{RGB}{245,247,250}
\definecolor{code-border}{RGB}{180,200,225}
\definecolor{blockquote-line}{RGB}{7,166,54}
%% ─── Hyperlinks ───────────────────────────────────────────────────────────
\usepackage{hyperref}
\hypersetup{
colorlinks = true,
linkcolor = link-blue,
urlcolor = link-blue,
filecolor = link-blue,
citecolor = link-blue,
pdftitle = {$title$},
pdfauthor = {DO5AMF (Marc Fröhlich), DN9APW (Philipp Wagner)},
pdfsubject = {KST4Contest User Manual},
pdfkeywords = {KST4Contest, pratiKST, VHF, Contest, Ham Radio},
bookmarks = true,
bookmarksnumbered = true,
bookmarksopen = true,
bookmarksopenlevel = 2,
pdfpagemode = UseOutlines,
}
%% ─── Graphics ─────────────────────────────────────────────────────────────
\usepackage{graphicx}
\graphicspath{{./}{./github_docs/}}
\makeatletter
\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi}
\def\maxheight{\ifdim\Gin@nat@height>0.65\textheight 0.65\textheight\else\Gin@nat@height\fi}
\makeatother
\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio}
%% ─── Tables ───────────────────────────────────────────────────────────────
\usepackage{longtable}
\usepackage{booktabs}
\usepackage{array}
\usepackage{calc}
\usepackage{multirow}
\setlength{\tabcolsep}{8pt}
\renewcommand{\arraystretch}{1.35}
% Pandoc pipe-table helpers
\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}}
\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}}
\newcolumntype{R}[1]{>{\raggedleft\arraybackslash}p{#1}}
%% ─── Lists ────────────────────────────────────────────────────────────────
\providecommand{\tightlist}{%
\setlength{\itemsep}{2pt}\setlength{\parskip}{0pt}}
%% ─── Code blocks (--listings flag) ───────────────────────────────────
\usepackage{listings}
\lstset{
basicstyle = \ttfamily\small,
backgroundcolor = \color{code-bg},
frame = single,
framesep = 4pt,
rulecolor = \color{code-border},
breaklines = true,
breakatwhitespace= false,
showstringspaces = false,
extendedchars = true,
xleftmargin = 6pt,
xrightmargin = 6pt,
aboveskip = 8pt,
belowskip = 8pt,
literate = {}{{\ensuremath{\rightarrow}}}1
{}{{\ensuremath{\leftarrow}}}1
{}{{\ensuremath{\leftrightarrow}}}1,
}
%% ─── Blockquotes ──────────────────────────────────────────────────────────
\usepackage{mdframed}
\newmdenv[
topline = false,
rightline = false,
bottomline = false,
leftline = true,
linewidth = 3pt,
linecolor = blockquote-line,
backgroundcolor = code-bg,
leftmargin = 0pt,
rightmargin = 0pt,
innerleftmargin = 12pt,
innerrightmargin = 8pt,
innertopmargin = 6pt,
innerbottommargin= 6pt,
skipabove = 8pt,
skipbelow = 8pt,
]{blockquotebox}
\renewenvironment{quote}
{\begin{blockquotebox}\small\itshape}
{\end{blockquotebox}}
%% ─── Section styling ──────────────────────────────────────────────────────
\usepackage{titlesec}
\titleformat{\section}
{\Large\bfseries\color{brand-green}}
{}
{0em}
{}
[\color{brand-green}\titlerule]
\titlespacing{\section}{0pt}{20pt}{10pt}
\titleformat{\subsection}
{\large\bfseries\color{brand-green}}
{}
{0em}
{}
\titlespacing{\subsection}{0pt}{14pt}{6pt}
\titleformat{\subsubsection}
{\normalsize\bfseries}
{}
{0em}
{}
\titlespacing{\subsubsection}{0pt}{10pt}{4pt}
% Level 4 (####): displayed as a named block heading in dark-grey
\titleformat{\paragraph}
{\normalsize\bfseries\color{brand-green}}
{}
{0em}
{}
\titlespacing{\paragraph}{0pt}{8pt}{2pt}
% Level 5 (#####): slightly smaller, italic, lighter grey
\titleformat{\subparagraph}
{\small\bfseries\itshape\color{brand-green!85!black}}
{}
{0em}
{}
\titlespacing{\subparagraph}{0pt}{6pt}{1pt}
% Reserve two additional section levels for future use (###### and deeper).
% Pandoc currently maps up to \subparagraph for standard Markdown headings.
\titleclass{\subsubsubsection}{straight}[\subparagraph]
\newcounter{subsubsubsection}[subparagraph]
\renewcommand\thesubsubsubsection{\thesubparagraph.\arabic{subsubsubsection}}
\titleformat{\subsubsubsection}
{\small\bfseries\color{brand-green!75!black}}
{}
{0em}
{}
\titlespacing{\subsubsubsection}{0pt}{5pt}{1pt}
\titleclass{\subsubsubsubsection}{straight}[\subsubsubsection]
\newcounter{subsubsubsubsection}[subsubsubsection]
\renewcommand\thesubsubsubsubsection{\thesubsubsubsection.\arabic{subsubsubsubsection}}
\titleformat{\subsubsubsubsection}
{\small\itshape\color{brand-green!65!black}}
{}
{0em}
{}
\titlespacing{\subsubsubsubsection}{0pt}{4pt}{1pt}
\setcounter{secnumdepth}{6}
\setcounter{tocdepth}{6}
%% ─── Header / Footer ──────────────────────────────────────────────────────
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[L]{\small\color{brand-green}\textbf{KST4Contest}}
\fancyhead[R]{\small\color{brand-green}$if(version)$$version$$endif$}
\fancyfoot[L]{\small\color{gray}DO5AMF \textbar\ DN9APW}
\fancyfoot[C]{\small\color{gray}\thepage}
\fancyfoot[R]{\small\color{gray}$title$}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.3pt}
\renewcommand{\headrule}{\color{brand-green}\hrule width\headwidth height\headrulewidth}
%% ─── Paragraph spacing ────────────────────────────────────────────────────
\usepackage{parskip}
\setlength{\parskip}{6pt}
\setlength{\parindent}{0pt}
%% ─── TOC styling ──────────────────────────────────────────────────────────
\usepackage{tocloft}
\renewcommand{\cfttoctitlefont}{\Large\bfseries\color{brand-green}}
\renewcommand{\cftsecfont}{\bfseries\color{brand-green}}
\renewcommand{\cftsecpagefont}{\bfseries\color{brand-green}}
\renewcommand{\cftsubsecfont}{\color{brand-green}}
\renewcommand{\cftsubsecpagefont}{\color{brand-green}}
\renewcommand{\cftsubsubsecfont}{\color{brand-green!85!black}}
\renewcommand{\cftsubsubsecpagefont}{\color{brand-green!85!black}}
\renewcommand{\cftparafont}{\color{brand-green!75!black}}
\renewcommand{\cftparapagefont}{\color{brand-green!75!black}}
\renewcommand{\cftsubparafont}{\color{brand-green!65!black}}
\renewcommand{\cftsubparapagefont}{\color{brand-green!65!black}}
\setlength{\cftbeforesecskip}{4pt}
%% ─── Misc ─────────────────────────────────────────────────────────────────
\usepackage{amsmath}
\usepackage{microtype}
% Pandoc helper macros
\newcommand{\passthrough}[1]{#1}
%% ══════════════════════════════════════════════════════════════════════════
\begin{document}
%% ─── Title page ───────────────────────────────────────────────────────────
\begin{titlepage}
\pagecolor{brand-green}
\centering
\vspace*{3.5cm}
{\fontsize{52}{62}\selectfont\bfseries\color{white}KST4Contest}\\[0.4cm]
{\fontsize{22}{28}\selectfont\color{white!75!brand-green}pratiKST (ON4KST Chat Client)}\\[2.8cm]
\color{white!40!brand-green}\rule{10cm}{0.6pt}\\[1.8cm]
{\LARGE\bfseries\color{white}$title$}\\[1cm]
$if(version)${\large\color{white!80!brand-green}Version:\space$version$}\\[0.6cm]$endif$
\vfill
{\large\color{white}DO5AMF · Marc Fröhlich · DM5M · DN9APW · Philipp Wagner}\\[0.4cm]
{\color{white!70!brand-green}\today}\\[2cm]
\end{titlepage}
\pagecolor{white}
\newpage
%% ─── Table of Contents ────────────────────────────────────────────────────
\tableofcontents
\newpage
%% ─── Main content ─────────────────────────────────────────────────────────
$body$
\end{document}

View File

@@ -0,0 +1,156 @@
--[[
strip-wiki-links.lua pandoc Lua filter for KST4Contest documentation
-----------------------------------------------------------------------
1. Removes language-switch blockquotes (GitHub Wiki navigation) that
are not relevant in the printed PDF manual.
2. Converts internal GitHub-wiki-style links to in-document anchors
so links jump within the generated PDF.
3. Replaces flag emoji and other symbols that XeLaTeX cannot render with
plain-text equivalents.
--]]
local PAGE_ANCHOR_MAP = {
["de-Home"] = "kst4contest-wiki",
["de-Installation"] = "installation",
["de-Konfiguration"] = "konfiguration",
["de-Funktionen"] = "funktionen",
["de-Benutzeroberflaeche"] = "benutzeroberflache",
["de-Makros-und-Variablen"] = "makros-und-variablen",
["de-Log-Synchronisation"] = "log-synchronisation",
["de-AirScout-Integration"] = "airscout-integration",
["de-DX-Cluster-Server"] = "integrierter-dx-cluster-server",
["de-Changelog"] = "changelog",
["en-Home"] = "kst4contest-wiki",
["en-Installation"] = "installation",
["en-Configuration"] = "configuration",
["en-Features"] = "features",
["en-User-Interface"] = "user-interface",
["en-Macros-and-Variables"] = "macros-and-variables",
["en-Log-Sync"] = "log-synchronisation",
["en-AirScout-Integration"] = "airscout-integration",
["en-DX-Cluster-Server"] = "built-in-dx-cluster-server",
["en-Changelog"] = "changelog",
["Installation"] = "installation",
["Konfiguration"] = "konfiguration",
["Funktionen"] = "funktionen",
["Benutzeroberflaeche"] = "benutzeroberflache",
["Makros-und-Variablen"] = "makros-und-variablen",
["Log-Synchronisation"] = "log-synchronisation",
["AirScout-Integration"] = "airscout-integration",
["DX-Cluster-Server"] = "integrierter-dx-cluster-server",
["Changelog"] = "changelog",
["Configuration"] = "configuration",
["Features"] = "features",
["User-Interface"] = "user-interface",
["Macros-and-Variables"] = "macros-and-variables",
["Log-Sync"] = "log-synchronisation",
}
local function normalize_anchor(text)
local s = text:lower()
s = s:gsub("%%20", "-")
s = s:gsub("ä", "a"):gsub("ö", "o"):gsub("ü", "u"):gsub("ß", "ss")
s = s:gsub("[^%w%s%-_]", "")
s = s:gsub("[_%s]+", "-")
s = s:gsub("%-+", "-")
s = s:gsub("^%-", ""):gsub("%-$", "")
return s
end
local function normalize_page_key(page)
local key = page:gsub("^%./", ""):gsub("^/", "")
key = key:gsub("^github_docs/", "")
key = key:gsub("%.md$", "")
return key
end
local function resolve_page_anchor(page)
local key = normalize_page_key(page)
return PAGE_ANCHOR_MAP[key] or normalize_anchor(key)
end
local function convert_url_token(token)
local url, trailing = token:match("^(https?://%S-)([%.%,%;%:%!%?]?)$")
if not url then
return nil
end
local link = pandoc.Link({pandoc.Str(url)}, url)
if trailing ~= "" then
return {link, pandoc.Str(trailing)}
end
return link
end
-- Map of emoji / special Unicode sequences → plain-text replacements.
-- Add more entries here as needed.
local EMOJI_MAP = {
-- Flag sequences
["\xF0\x9F\x87\xAC\xF0\x9F\x87\xA7"] = "[EN]", -- 🇬🇧
["\xF0\x9F\x87\xA9\xF0\x9F\x87\xAA"] = "[DE]", -- 🇩🇪
-- Status symbols
["\xE2\x9C\x85"] = "[OK]", -- ✅
["\xE2\x9D\x8C"] = "[--]", -- ❌
-- Misc symbols used in tables / text
["\xF0\x9F\x94\xB4"] = "[red]", -- 🔴
["\xF0\x9F\x9F\xA1"] = "[yellow]", -- 🟡
["\xF0\x9F\x9F\xA2"] = "[green]", -- 🟢
}
--- Replace emoji in a plain string.
local function replace_emoji(text)
for pattern, replacement in pairs(EMOJI_MAP) do
text = text:gsub(pattern, replacement)
end
return text
end
--- Filter: remove language-switch blockquotes from PDF output.
-- These blockquotes appear in every wiki page for GitHub navigation
-- but are not needed in the printed manual.
function BlockQuote(el)
local text = pandoc.utils.stringify(el)
if text:find("Du liest gerade die deutsche Version") or
text:find("You are reading the English version") then
return {}
end
return el
end
--- Filter: convert internal wiki links to in-PDF anchor links.
function Link(el)
local target = el.target
-- Keep external URLs unchanged.
if target:match("^https?://") or target:match("^mailto:") then
return el
end
if target:match("^#") then
local fragment = target:gsub("^#", "")
return pandoc.Link(el.content, "#" .. normalize_anchor(fragment), el.title, el.attr)
end
local page, fragment = target:match("^([^#]+)#(.+)$")
if page and fragment then
return pandoc.Link(el.content, "#" .. normalize_anchor(fragment), el.title, el.attr)
end
return pandoc.Link(el.content, "#" .. resolve_page_anchor(target), el.title, el.attr)
end
--- Filter: replace emoji sequences in plain Str elements.
function Str(el)
local linkified = convert_url_token(el.text)
if linkified then
return linkified
end
local replaced = replace_emoji(el.text)
if replaced ~= el.text then
return pandoc.Str(replaced)
end
return el
end

107
.github/workflows/docs-pdf.yml vendored Normal file
View File

@@ -0,0 +1,107 @@
name: Build Documentation PDF
# Runs when documentation changes are pushed to main, or on manual trigger.
# Also triggered as a dependency from the tagged-release workflow.
on:
push:
branches:
- main
paths:
- github_docs/**
- .github/latex-manual/**
- .github/workflows/docs-pdf.yml
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
build-docs-pdf:
name: Build Documentation PDF
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Resolve version string
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION="${{ github.ref_name }}"
else
VERSION="$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')-${GITHUB_SHA::7}"
fi
echo "DOC_VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Install pandoc and LaTeX toolchain
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
pandoc \
texlive-xetex \
texlive-fonts-recommended \
texlive-latex-extra \
texlive-plain-generic
- name: Build English PDF manual
run: |
mkdir -p dist
pandoc \
--from=markdown-yaml_metadata_block \
--template=.github/latex-manual/manual-template.tex \
--pdf-engine=xelatex \
--lua-filter=.github/latex-manual/strip-wiki-links.lua \
--resource-path=.:github_docs \
--listings \
--toc \
--toc-depth=6 \
-V title="User Manual" \
-V polyglossia-lang=english \
-V version="${DOC_VERSION}" \
-o dist/KST4Contest-${DOC_VERSION}-manual-en.pdf \
github_docs/en-Home.md \
github_docs/en-Installation.md \
github_docs/en-Configuration.md \
github_docs/en-Features.md \
github_docs/en-User-Interface.md \
github_docs/en-Macros-and-Variables.md \
github_docs/en-Log-Sync.md \
github_docs/en-AirScout-Integration.md \
github_docs/en-DX-Cluster-Server.md \
github_docs/en-Changelog.md
- name: Build German PDF manual
run: |
pandoc \
--from=markdown-yaml_metadata_block \
--template=.github/latex-manual/manual-template.tex \
--pdf-engine=xelatex \
--lua-filter=.github/latex-manual/strip-wiki-links.lua \
--resource-path=.:github_docs \
--listings \
--toc \
--toc-depth=6 \
-V title="Benutzerhandbuch" \
-V polyglossia-lang=german \
-V version="${DOC_VERSION}" \
-o dist/KST4Contest-${DOC_VERSION}-manual-de.pdf \
github_docs/de-Home.md \
github_docs/de-Installation.md \
github_docs/de-Konfiguration.md \
github_docs/de-Funktionen.md \
github_docs/de-Benutzeroberflaeche.md \
github_docs/de-Makros-und-Variablen.md \
github_docs/de-Log-Synchronisation.md \
github_docs/de-AirScout-Integration.md \
github_docs/de-DX-Cluster-Server.md \
github_docs/de-Changelog.md
- name: Upload PDF artifacts
uses: actions/upload-artifact@v4.3.4
with:
name: docs-pdf
path: dist/KST4Contest-*-manual-*.pdf
retention-days: 30

View File

@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- test-mac
schedule: schedule:
- cron: "20 2 * * *" - cron: "20 2 * * *"
workflow_dispatch: workflow_dispatch:
@@ -152,3 +153,69 @@ jobs:
name: linux-appimage name: linux-appimage
path: dist/praktiKST-*-linux-x86_64.AppImage path: dist/praktiKST-*-linux-x86_64.AppImage
retention-days: 14 retention-days: 14
build-macos-dmg:
name: Build macOS DMG (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, macos-15-intel]
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Resolve nightly version info
run: |
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
SHORT_SHA="${GITHUB_SHA::7}"
ARCH=$(uname -m)
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV"
echo "ASSET_BASENAME=praktiKST-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
echo "ARCH=$ARCH" >> "$GITHUB_ENV"
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build macOS DMG with jpackage
run: |
mkdir -p dist
jpackage \
--type dmg \
--name praktiKST \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
env:
MACOSX_DEPLOYMENT_TARGET: "13.0"
- name: Rename DMG artifact
run: |
DMG=$(ls dist/*.dmg | head -n 1)
if [ -z "$DMG" ]; then
echo "No DMG produced by jpackage" && exit 1
fi
mv "$DMG" "dist/${ASSET_BASENAME}-macos-${ARCH}.dmg"
- name: Upload macOS artifact
uses: actions/upload-artifact@v4.3.4
with:
name: macos-dmg-${{ matrix.os }}
path: dist/praktiKST-*-macos-*.dmg
retention-days: 14

View File

@@ -134,26 +134,174 @@ jobs:
name: linux-appimage name: linux-appimage
path: dist/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage path: dist/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage
build-macos-dmg:
name: Build macOS DMG (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, macos-15-intel]
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build macOS DMG with jpackage
run: |
mkdir -p dist
jpackage \
--type dmg \
--name praktiKST \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
env:
MACOSX_DEPLOYMENT_TARGET: "13.0"
- name: Rename DMG artifact
run: |
ARCH=$(uname -m)
DMG=$(ls dist/*.dmg | head -n 1)
if [ -z "$DMG" ]; then
echo "No DMG produced by jpackage" && exit 1
fi
mv "$DMG" "dist/praktiKST-${{ github.ref_name }}-macos-${ARCH}.dmg"
- name: Upload macOS artifact
uses: actions/upload-artifact@v4.3.4
with:
name: macos-dmg-${{ matrix.os }}
path: dist/praktiKST-${{ github.ref_name }}-macos-*.dmg
build-docs-pdf:
name: Build Documentation PDF
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Install pandoc and LaTeX toolchain
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
pandoc \
texlive-xetex \
texlive-fonts-recommended \
texlive-latex-extra \
texlive-plain-generic
- name: Build English PDF manual
run: |
mkdir -p dist
pandoc \
--from=markdown-yaml_metadata_block \
--template=.github/latex-manual/manual-template.tex \
--pdf-engine=xelatex \
--lua-filter=.github/latex-manual/strip-wiki-links.lua \
--resource-path=.:github_docs \
--listings \
--toc \
--toc-depth=3 \
-V title="User Manual" \
-V polyglossia-lang=english \
-V version="${{ github.ref_name }}" \
-o dist/KST4Contest-${{ github.ref_name }}-manual-en.pdf \
github_docs/en-Home.md \
github_docs/en-Installation.md \
github_docs/en-Configuration.md \
github_docs/en-Features.md \
github_docs/en-User-Interface.md \
github_docs/en-Macros-and-Variables.md \
github_docs/en-Log-Sync.md \
github_docs/en-AirScout-Integration.md \
github_docs/en-DX-Cluster-Server.md \
github_docs/en-Changelog.md
- name: Build German PDF manual
run: |
pandoc \
--from=markdown-yaml_metadata_block \
--template=.github/latex-manual/manual-template.tex \
--pdf-engine=xelatex \
--lua-filter=.github/latex-manual/strip-wiki-links.lua \
--resource-path=.:github_docs \
--listings \
--toc \
--toc-depth=3 \
-V title="Benutzerhandbuch" \
-V polyglossia-lang=german \
-V version="${{ github.ref_name }}" \
-o dist/KST4Contest-${{ github.ref_name }}-manual-de.pdf \
github_docs/de-Home.md \
github_docs/de-Installation.md \
github_docs/de-Konfiguration.md \
github_docs/de-Funktionen.md \
github_docs/de-Benutzeroberflaeche.md \
github_docs/de-Makros-und-Variablen.md \
github_docs/de-Log-Synchronisation.md \
github_docs/de-AirScout-Integration.md \
github_docs/de-DX-Cluster-Server.md \
github_docs/de-Changelog.md
- name: Upload PDF artifacts
uses: actions/upload-artifact@v4.3.4
with:
name: docs-pdf
path: dist/KST4Contest-${{ github.ref_name }}-manual-*.pdf
release-tag: release-tag:
name: Publish Tagged Release name: Publish Tagged Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- build-windows-zip - build-windows-zip
- build-linux-appimage - build-linux-appimage
- build-macos-dmg
- build-docs-pdf
steps: steps:
- name: Download Windows artifact - name: Download Windows artifact
uses: actions/download-artifact@v4.1.1 uses: actions/download-artifact@v4.1.3
with: with:
name: windows-zip name: windows-zip
path: release-assets/windows path: release-assets/windows
- name: Download Linux artifact - name: Download Linux artifact
uses: actions/download-artifact@v4.1.1 uses: actions/download-artifact@v4.1.3
with: with:
name: linux-appimage name: linux-appimage
path: release-assets/linux path: release-assets/linux
- name: Download macOS artifacts
uses: actions/download-artifact@v4.1.3
with:
pattern: macos-dmg-*
merge-multiple: true
path: release-assets/macos
- name: Download PDF manuals
uses: actions/download-artifact@v4.1.3
with:
name: docs-pdf
path: release-assets/docs
- name: Create tagged release - name: Create tagged release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
@@ -165,4 +313,9 @@ jobs:
replacesArtifacts: false replacesArtifacts: false
makeLatest: ${{ !startsWith(github.ref_name, 'beta-') }} makeLatest: ${{ !startsWith(github.ref_name, 'beta-') }}
generateReleaseNotes: true generateReleaseNotes: true
artifacts: release-assets/windows/praktiKST-${{ github.ref_name }}-windows-x64.zip,release-assets/linux/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage artifacts: >-
release-assets/windows/praktiKST-${{ github.ref_name }}-windows-x64.zip,
release-assets/linux/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage,
release-assets/macos/praktiKST-${{ github.ref_name }}-macos-*.dmg,
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-en.pdf,
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-de.pdf

3
.gitignore vendored
View File

@@ -30,5 +30,8 @@ target/
#builds #builds
build/ build/
#pdf output directory
dist/
#zip files for local backups #zip files for local backups
*.zip *.zip

View File

@@ -1,2 +1,38 @@
# kst4contest # KST4Contest
java based on4kst chatclient
KST4Contest (also known as pratiKST) is a Java-based chat client for ON4KST, focused on VHF/UHF/SHF contest operation.
## Documentation
The full user documentation is maintained in the project wiki:
- https://github.com/praktimarc/kst4contest/wiki
Direct entry points:
- German start page: https://github.com/praktimarc/kst4contest/wiki/de-Home
- English start page: https://github.com/praktimarc/kst4contest/wiki/en-Home
## Build
Compile locally with Maven Wrapper:
```bash
./mvnw -B -DskipTests compile
```
## Notes
- Source code is under `src/`.
- Documentation markdown pages for wiki/PDF are under `github_docs/`.
## Status of the latest CI:
Wiki Publishing:
[![Publish wiki](https://github.com/praktimarc/kst4contest/actions/workflows/github-wiki.yml/badge.svg)](https://github.com/praktimarc/kst4contest/actions/workflows/github-wiki.yml)
[![Docs PDF](https://github.com/praktimarc/kst4contest/actions/workflows/docs-pdf.yml/badge.svg)](https://github.com/praktimarc/kst4contest/actions/workflows/docs-pdf.yml)
Builds:
[![Nightly Runtime Artifacts](https://github.com/praktimarc/kst4contest/actions/workflows/nightly-artifacts.yml/badge.svg)](https://github.com/praktimarc/kst4contest/actions/workflows/nightly-artifacts.yml)

View File

@@ -1,2 +0,0 @@
dr2x
oe3cin

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,6 @@ Entwickelt von **DO5AMF (Marc Fröhlich)**, Operator bei DM5M.
## Schnellinfo / Quick Info ## Schnellinfo / Quick Info
- **Download**: https://do5amf.funkerportal.de/ - **Download**: https://github.com/praktimarc/kst4contest/releases
- **GitHub**: https://github.com/praktimarc/kst4contest - **GitHub**: https://github.com/praktimarc/kst4contest
- **Kontakt / Contact**: praktimarc+kst4contest@gmail.com - **Kontakt / Contact**: praktimarc+kst4contest@gmail.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@@ -32,6 +32,10 @@ Für diesen Dienst ist ein Account erforderlich. Bitte eine Spende für Thomas i
1. AirScout starten. 1. AirScout starten.
2. In den AirScout-Einstellungen den OV3T-Feed-Account eintragen (Benutzername, Passwort, URL). 2. In den AirScout-Einstellungen den OV3T-Feed-Account eintragen (Benutzername, Passwort, URL).
![AirscoutSchritt1](as_plane_feed_1.png)
![AirscoutSchritt2](as_plane_feed_2.png)
3. Verbindung testen. 3. Verbindung testen.
### Schritt 2: UDP-Kommunikation für KST4Contest aktivieren ### Schritt 2: UDP-Kommunikation für KST4Contest aktivieren
@@ -47,6 +51,10 @@ In den KST4Contest-Preferences → **AirScout Settings**:
- AirScout-Kommunikation aktivieren - AirScout-Kommunikation aktivieren
- IP und Port auf Standardwerte lassen (sofern nicht geändert) - IP und Port auf Standardwerte lassen (sofern nicht geändert)
![AirscoutSchritt3](as_plane_feed_3.png){ width=85% }
--- ---
## Kommunikation zwischen KST4Contest und AirScout (ab v1.263) ## Kommunikation zwischen KST4Contest und AirScout (ab v1.263)

View File

@@ -6,6 +6,85 @@ Versionsverlauf von KST4Contest / PraktiKST.
--- ---
letzter Changelog bitte aus GitHub entnehmen. Der bisherige Changelog
## v1.40 (2026-02-16)
**Großes Feature-Release: Score-System, AP-Timeline, Win-Test, PSTRotator**
**Neu:**
- **Chatmember Score-System**: Jeder Chatmember erhält automatisch eine Prioritätsbewertung anhand von Antennenrichtung, Aktivitätszeit, Nachrichtenanzahl, aktiven Bändern, Frequenzen, Sked-Richtung und anderen Faktoren. Die Top-Kandidaten werden in einer eigenen Liste hervorgehoben.
- **AP-Timeline**: Für jeden möglichen AP-Ankunftsminuten-Slot werden bis zu 4 hochbewertete Stationen angezeigt, die erreichbar wären. Bevorzugt werden APs mit dem höchsten Potenzial, nicht die schnellste Ankunft. Stationen, auf die die eigene Antenne nicht zeigt, werden transparent dargestellt.
- **Win-Test-Unterstützung** (ab v1.31 als Beta, jetzt vollständig konfigurierbar): Log-Synchronisation, Frequenzauswertung und **Sked-Übergabe via UDP** vollständig integriert. In den Preferences aktivier-/deaktivierbar.
- **PSTRotator-Interface** (ab v1.31 als Beta, jetzt vollständig konfigurierbar): Aktualisierung der Rotatorposition direkt aus KST4Contest. In den Preferences aktivier-/deaktivierbar.
- **QSO-Sniffer**: Nachrichten von konfigurierbaren Rufzeichen-Listen werden automatisch in das PM-Fenster weitergeleitet.
- **Band-Alert bei gearbeiteten Stationen**: Wenn eine Station geloggt wird, erscheint ein Hinweis, wenn diese Station ein weiteres Band aktiv hat, auf dem man selbst ebenfalls QRV ist.
- **Sked-Erinnerungs-ALERT**: Pro Chatmember kann ein Sked-Alarm mit automatischen Nachrichten in konfigurierbaren Intervallen (2+1 / 5+2+1 / 10+5+2+1 Minuten vor dem Sked) eingerichtet werden, plus akustische und optische Benachrichtigung.
- **Chat-Historie beim Start laden**: Beim Verbindungsaufbau wird die Serverhistorie geladen, um aktive Chatmember und letzte Nachrichten sofort sichtbar zu machen.
- **Skedfail-Button**: Im FurtherInfo-Panel kann ein Sked-Misserfolg für einen Chatmember markiert werden, was dessen Score senkt.
**Geändert:**
- AP-Notizen in DX-Cluster-Spots integriert.
- Scrolling der Chatmember-Tabelle folgt automatisch der aktuellen Nachrichtenauswahl.
- Generic Auto-Antwort und QRG-Auto-Antwort senden max. einmal pro 45 Sekunden pro Rufzeichen (verhindert Spam-Schleifen).
- Speicherbare Einstellungen erweitert: ServerDNS/Port, PSTRotator-Interface, Win-Test-Interface, Callsign-Sniffer, Dark-Mode-Standard.
- Datum in der Chat-Tabelle entfernt (nur Uhrzeit verbleibt spart Platz).
**Behoben:**
- Benutzerliste wird jetzt bei jedem Neu-Login automatisch sortiert.
- Posonpill-Nachrichten beenden jetzt nur genau eine Client-Instanz (nicht alle und nicht wtKST).
- wtKST: Absturz bei KST4Contest-Trennung behoben.
- Mehrere Probleme mit Rufzeichen-Suffixen wie `/p`, `-2` etc. behoben.
- `QTFDefault` wurde nicht korrekt gespeichert → behoben.
- AirScout-Watchlist (ASWATCHLIST) wurde nicht korrekt aktualisiert → behoben.
- Dark Mode: QRG-Felder wurden nicht vollständig angezeigt → behoben.
- Versionsnummer-Anzeige korrigiert.
---
## v1.31 (2025-12-13)
**Win-Test + PSTRotator Beta, QSO-Sniffer, DNS-Hotfix**
**Neu:**
- **Win-Test-Unterstützung** (Beta, noch nicht deaktivierbar): Log-Synchronisation und Frequenzauswertung.
- **PSTRotator-Unterstützung** (Beta, noch nicht deaktivierbar).
- **QSO-Sniffer**: Nachrichten von konfigurierbaren Rufzeichen werden ins PM-Fenster weitergeleitet.
**Geändert:**
- **DNS-Server geändert**: Von `www.on4kst.info` auf `www.on4kst.org` (Hotfix). Der DNS-Server ist ab sofort in den Preferences änderbar.
**Behoben:**
- Endlosschleife im Fehlerfall friert den Client ein → behoben.
---
## v1.266 (2025-10-03)
**AirScout-Fix für Rufzeichen mit Suffix**
**Behoben:**
- AirScout-Interface funktionierte nicht, wenn das Login-Rufzeichen einen Suffix enthielt (z. B. `9A1W-2`). AirScout kann mit diesem Format nicht umgehen es wird jetzt nur noch das Basis-Rufzeichen ohne Suffix an AirScout übergeben.
*(Fehler gemeldet und getestet von 9A2HM / Kreso herzlichen Dank!)*
---
## v1.265 (2025-09-28)
**Richtungs-Buttons bleiben aktiviert eingefärbt**
**Behoben:**
- Richtungs-Buttons (N / NE / E usw.) behalten jetzt ihre Farbe, wenn sie aktiviert sind, sodass der Aktivierungsstatus auf einen Blick erkennbar ist.
---
## v1.264 (2025-08-02)
**Simplelogfile: Rufzeichen-Erkennung verbessert**
**Behoben:**
- Rufzeichen wie `S53CC`, `S51A` usw. wurden in der SimpleLogFile-Auswertung nicht als gearbeitet markiert → Erkennungsmuster verbessert.
*(Fehler gemeldet von Boris, S53CC danke!)*
---
## v1.263 (2025-06-08) ## v1.263 (2025-06-08)
**AirScout-Kommunikation und Login-Name** **AirScout-Kommunikation und Login-Name**
@@ -151,6 +230,6 @@ Erste öffentlich veröffentlichte Version. Grundfunktionen:
## Geplante Features ## Geplante Features
- `MYQTF`-Variable (eigene Antennenrichtung als Text) - `MYQTF`-Variable (eigene Antennenrichtung als Text)
- Lebensdauer für den Worked-Status (automatisches Zurücksetzen) - ~~Lebensdauer für den Worked-Status (automatisches Zurücksetzen)~~ ✅ **Umgesetzt in v1.40** (3-Tage-Lebensdauer, kein manuelles Zurücksetzen mehr nötig)
- Filterung des „Cluster & QSO der anderen"-Fensters auf eigenes QTF - Filterung des „Cluster & QSO der anderen"-Fensters auf eigenes QTF
- Weitere Topografie-basierte Berechnungen für die Richtungswarnung - Weitere Topografie-basierte Berechnungen für die Richtungswarnung

View File

@@ -135,22 +135,86 @@ Für ausgewählte Stationen in der Benutzerliste gibt es direkte Buttons, um das
--- ---
## Sked-Erinnerungen (Sked Reminder Service) ## Sked-Erinnerungen mit ALERT (ab v1.40)
Für vereinbarte Skeds können automatische Erinnerungs-PMs konfiguriert werden, die X Minuten vor dem vereinbarten Zeitpunkt gesendet werden. Die Erinnerungen werden aus dem FurtherInfo-Panel heraus aktiviert. Für jeden Chatmember kann ein Sked-Erinnerungsdienst mit automatischen Nachrichten aktiviert werden. Konfigurierbare Intervallmuster:
- **2+1 Minuten**: Nachrichten bei 2 min und 1 min vor dem Sked.
- **5+2+1 Minuten**: Nachrichten bei 5, 2 und 1 min vor dem Sked.
- **10+5+2+1 Minuten**: Nachrichten bei 10, 5, 2 und 1 min vor dem Sked.
Zusätzlich zu den Nachrichten an die Gegenstation gibt es eine **akustische und optische Benachrichtigung** für den eigenen Operator, sodass kein Sked vergessen wird.
Aktivierung: FurtherInfo-Panel der entsprechenden Station.
--- ---
## Prioritätsliste / Score-Service ## QSO-Sniffer (ab v1.31)
KST4Contest berechnet automatisch eine **Prioritätsliste** der interessantesten Gesprächspartner, basierend auf: Der QSO-Sniffer überwacht den Chat auf Nachrichten von einer konfigurierbaren Rufzeichen-Liste und leitet diese automatisch in das **PM-Fenster** weiter. So gehen keine relevanten Nachrichten im allgemeinen Chat-Rauschen unter.
- Richtungserkennung Konfiguration: [Konfiguration Sniffer-Einstellungen](de-Konfiguration#sniffer-einstellungen-ab-v131)
---
## Win-Test-Integration (ab v1.31, vollständig ab v1.40)
KST4Contest unterstützt [Win-Test](https://www.win-test.com/) vollständig als Logprogramm:
- **Log-Synchronisation**: Gearbeitete Stationen werden automatisch aus Win-Test übernommen und in der Benutzerliste markiert.
- **Frequenz-Auswertung**: Die aktuelle TRX-Frequenz wird aus Win-Test-UDP-Paketen ausgewertet und befüllt die `MYQRG`-Variable.
- **Sked-Übergabe (SKED Push via UDP)**: Vereinbarte Skeds aus KST4Contest können direkt an Win-Test übertragen werden, sodass das Rufzeichen der Gegenstation im Win-Test-Sked-Fenster erscheint.
Details zur Konfiguration: [Konfiguration Win-Test-Netzwerk-Listener](de-Konfiguration#win-test-netzwerk-listener)
---
## PSTRotator-Interface (ab v1.31, vollständig ab v1.40)
KST4Contest kann die Antennenrichtung direkt über **PSTRotator** steuern. Wenn in der Benutzerliste eine Station ausgewählt wird, kann der Rotator automatisch auf den QTF zur ausgewählten Station gedreht werden.
Konfiguration: [Konfiguration PSTRotator-Einstellungen](de-Konfiguration#pstrotator-einstellungen-ab-v131)
---
## Band-Alert bei neuen QSOs (ab v1.40)
Wenn eine Station geloggt wird, prüft KST4Contest automatisch, ob diese Station im Chat weitere aktive Bänder angezeigt hat, auf denen man selbst ebenfalls QRV ist. Falls ja, erscheint ein **Hinweis-Alert**, damit keine Multi-Band-Möglichkeit übersehen wird.
---
## Worked-Tag-Lebensdauer (ab v1.40)
Gearbeitete Stationen werden nach **3 Tagen** automatisch aus der Datenbank entfernt. Ein manuelles Zurücksetzen der Worked-Datenbank vor jedem Contest ist damit nicht mehr zwingend notwendig die Datenbank hält sich selbst aktuell.
---
## Chatmember Score-System / Prioritätsliste (ab v1.40)
KST4Contest berechnet automatisch eine **Prioritätsbewertung** für jeden aktiven Chatmember. Der Score setzt sich zusammen aus:
- Antennenrichtung der Gegenstation (zeigt sie auf mich?)
- QRB (Entfernung) - QRB (Entfernung)
- Aktivitätszeit und Nachrichtenanzahl
- Aktive Bänder und Frequenzen
- AP-Verfügbarkeit (AirScout) - AP-Verfügbarkeit (AirScout)
- Worked-Status - Sked-Richtung
- Sked-Erfolgsrate und Skedfail-Markierungen
Die Top-Kandidaten werden in einer eigenen Liste angezeigt und helfen, im Contest-Stress die wichtigsten Stationen nicht zu übersehen. Die Top-Kandidaten werden in einer eigenen Prioritätsliste hervorgehoben und helfen, im Contest-Stress die wichtigsten Stationen nicht zu übersehen.
Stationen, bei denen ein Sked gescheitert ist, können über den **Skedfail-Button** im FurtherInfo-Panel markiert werden das senkt ihren Score vorübergehend.
---
## AP-Timeline (ab v1.40)
Eine visuelle Zeitleiste zeigt für jeden möglichen AP-Ankunftsminuten-Slot bis zu 4 hochbewertete Stationen, die per Aircraft Scatter erreichbar wären. Priorisierungskriterien:
- Bevorzugt werden APs mit dem **höchsten Reflexionspotenzial** (nicht unbedingt die schnellste Ankunft).
- Stationen, auf die die eigene Antenne nicht zeigt, werden **transparent** dargestellt.
So kann der Contest-Operator auf einem Blick sehen, welche Stationen wann und über welche Flugzeuge erreichbar sein werden.
--- ---

View File

@@ -42,7 +42,7 @@ Der ON4KST-Chat ist der De-facto-Standard für Skeds auf den 144-MHz-und-höher-
- **E-Mail**: praktimarc+kst4contest@gmail.com *(nur für kst4contest-Themen)* - **E-Mail**: praktimarc+kst4contest@gmail.com *(nur für kst4contest-Themen)*
- **GitHub**: https://github.com/praktimarc/kst4contest - **GitHub**: https://github.com/praktimarc/kst4contest
- **Download**: https://do5amf.funkerportal.de/ - **Download**: https://github.com/praktimarc/kst4contest/releases/latest
--- ---

View File

@@ -4,9 +4,7 @@
## Voraussetzungen ## Voraussetzungen
### Java Es wird eine Mindestauflösung von 1200px mal 720px empfohlen
KST4Contest ist eine Java-Anwendung. Es wird eine aktuelle **Java Runtime Environment (JRE)** benötigt. Die empfohlene Version ist Java 17 oder höher.
### ON4KST-Account ### ON4KST-Account
@@ -34,21 +32,42 @@ Bei starkem Chat-Verkehr (56 Nachrichten pro Sekunde im Contest) gehen öffen
## Download ## Download
### Windows
Die aktuelle Version kann als ZIP-Datei heruntergeladen werden: Die aktuelle Version kann als ZIP-Datei heruntergeladen werden:
**https://do5amf.funkerportal.de/** **https://github.com/praktimarc/kst4contest/releases/latest**
Der Dateiname hat das Format `praktiKST-v<Versionsnummer>-windows-x64.zip `.
### Linux
Die aktuelle Version kann als AppImage heruntergeladen werden:
**https://github.com/praktimarc/kst4contest/releases/latest**
Der Dateiname hat das Format `praktiKST-v<Versionsnummer>-linux-x86_64.AppImage`.
Der Dateiname hat das Format `kst4Contest_v<Versionsnummer>.zip`.
--- ---
## Installation ## Installation
### Windows
1. ZIP-Datei herunterladen. 1. ZIP-Datei herunterladen.
2. ZIP-Datei in einen gewünschten Ordner entpacken. 2. ZIP-Datei in einen gewünschten Ordner entpacken.
3. `praktiKST.exe` (Windows) bzw. das entsprechende Start-Skript ausführen. 3. `praktiKST.exe` ausführen.
Die Einstellungen werden unter `%USERPROFILE%\.praktikst\preferences.xml` (Windows) gespeichert. Die Einstellungen werden unter `%USERPROFILE%\.praktikst\preferences.xml` gespeichert.
### Linux
1. AppImage herunterladen.
2. AppImage in gewünschten Ordner entpacken.
3. AppImage ausführbar machen (geht im Terminal mit `chmod +x praktiKST-v<Versionsnummer>-linux-x86_64.AppImage`)
4. AppImage ausführen.
Die Einstellungen werden unter `~/.praktikst/preferences.xml` gespeichert.
--- ---
@@ -59,8 +78,12 @@ KST4Contest enthält einen **automatischen Update-Hinweis-Dienst**: Sobald eine
- einem Changelog, - einem Changelog,
- dem Download-Link zur neuen Version. - dem Download-Link zur neuen Version.
![Beispiel Update Fenster](update_window.png)
### Update-Prozess ### Update-Prozess
#### Windows
Derzeit gibt es nur einen Weg zum Aktualisieren: Derzeit gibt es nur einen Weg zum Aktualisieren:
1. Den alten Ordner löschen. 1. Den alten Ordner löschen.
@@ -68,6 +91,14 @@ Derzeit gibt es nur einen Weg zum Aktualisieren:
Die Einstellungsdatei (`preferences.xml`) bleibt erhalten, da sie im Benutzerordner gespeichert ist nicht im Programmordner. Die Einstellungsdatei (`preferences.xml`) bleibt erhalten, da sie im Benutzerordner gespeichert ist nicht im Programmordner.
#### Linux
Derzeit folgendermaßen:
1. neues AppImage herunterladen
2. neues AppImage ausführbar makieren
3. (optional) altes AppImage löschen.
--- ---
## Bekannte Probleme beim Start ## Bekannte Probleme beim Start

View File

@@ -4,12 +4,21 @@
Nach dem ersten Start öffnet sich das **Einstellungsfenster** dieses ist der zentrale Ausgangspunkt für alle Konfigurationen. Es empfiehlt sich, das Einstellungsfenster während des Betriebs geöffnet zu lassen (z. B. um den Beacon schnell ein- und auszuschalten). Nach dem ersten Start öffnet sich das **Einstellungsfenster** dieses ist der zentrale Ausgangspunkt für alle Konfigurationen. Es empfiehlt sich, das Einstellungsfenster während des Betriebs geöffnet zu lassen (z. B. um den Beacon schnell ein- und auszuschalten).
> **Wichtig**: Nach jeder Änderung unbedingt **„Save Settings"** klicken! Die Einstellungen werden in `~/.praktikst/preferences.xml` gespeichert. Ab v1.21 werden auch Fenstergrößen und Divider-Positionen beim Speichern gesichert. > **Wichtig**: Nach jeder Änderung unbedingt **„Save Settings"** klicken! Die Einstellungen werden unter Linux in `~/.praktikst/preferences.xml` und unter Windows in `%USERPROFILE%\.praktikst\preferences.xml` (bzw. `C:\Users\<Benutzername>\.praktikst\preferences.xml`) gespeichert. Ab v1.21 werden auch Fenstergrößen und Divider-Positionen beim Speichern gesichert.
--- ---
## Station Settings (Stationseinstellungen) ## Station Settings (Stationseinstellungen)
![Stationseinstellungen](client_settings_window_station.png)
### Login und Chat-Kategorien
Hier werden die Zugangsdaten für den ON4KST-Chat eingetragen (Rufzeichen und Passwort).
Zudem wird die **primäre Chat-Kategorie** (z. B. IARU Region 1 VHF/Microwave) ausgewählt.
Mit der Option für einen **zweiten Chat** (Multi-Channel-Login) kann man sich gleichzeitig in eine weitere Kategorie (z. B. UHF/SHF) einloggen. Beide Chats werden dann parallel überwacht. Hier kann optional auch ein abweichender Login-Name für den zweiten Chat vergeben werden (nützlich für Opposite Station Multi-Callsign Logging).
### Rufzeichen und Locator ### Rufzeichen und Locator
Eigenes Rufzeichen und Maidenhead-Locator (6-stellig, z. B. `JN49IJ`) eintragen. Diese Werte werden für Distanz- und Richtungsberechnungen benötigt. Eigenes Rufzeichen und Maidenhead-Locator (6-stellig, z. B. `JN49IJ`) eintragen. Diese Werte werden für Distanz- und Richtungsberechnungen benötigt.
@@ -30,9 +39,20 @@ Maximale Entfernung (in km), für die Richtungs-Warnungen ausgelöst werden soll
--- ---
## Server-Einstellungen (ab v1.31)
Der Chat-Server-DNS und -Port sind in den Preferences konfigurierbar:
- **Server-DNS**: Standard `www.on4kst.org` (ab v1.31 geändert von `www.on4kst.info`).
- **Port**: Standardport des ON4KST-Servers.
Eine Änderung ist nur notwendig, wenn der Server umzieht oder ein alternativer Endpunkt genutzt wird.
---
## Log-Sync-Einstellungen ## Log-Sync-Einstellungen
Zwei Methoden stehen zur Verfügung, um gearbeitete Stationen automatisch zu markieren. Details: [Log-Synchronisation](de-Log-Synchronisation). Drei Methoden stehen zur Verfügung, um gearbeitete Stationen automatisch zu markieren. Details: [Log-Synchronisation](de-Log-Synchronisation).
### Universal File Based Callsign Interpreter (Simplelogfile) ### Universal File Based Callsign Interpreter (Simplelogfile)
@@ -40,7 +60,11 @@ Interpretiert beliebige Log-Dateien per Regex nach Rufzeichen-Mustern. Keine Ban
### Netzwerk-Listener für QSO-UDP-Broadcast ### Netzwerk-Listener für QSO-UDP-Broadcast
**Empfohlene Methode.** KST4Contest hört auf UDP-Pakete, die das Logprogramm beim Speichern eines QSOs an die Broadcast-Adresse sendet. Die Stationen werden mit Bandinformation markiert. UDP-Port: Standard **12060**. **Empfohlene Methode.** KST4Contest hört auf UDP-Pakete, die das Logprogramm beim Speichern eines QSOs an die Broadcast-Adresse sendet. Die Stationen werden mit Bandinformation markiert. UDP-Port: Standard **12060**. (Wird z. B. von UCXLog, N1MM+, QARTest, DXLog.net genutzt).
### Win-Test Network-Listener (Zusätzlicher UDP-Listener)
Dedizierter Netzwerk-Erkenner für Win-Test. KST4Contest empfängt und verarbeitet Win-Test-spezifische UDP-Pakete (inkl. Sked-Übergabe) auf dem dafür konfigurierten Port.
--- ---
@@ -109,14 +133,55 @@ Neuer Einstellungsbereich mit folgenden Optionen:
--- ---
## Win-Test-Netzwerk-Listener (ab v1.31)
Dedizierter Empfänger für Win-Test-spezifische UDP-Pakete. Ermöglicht:
- **Log-Synchronisation**: Gearbeitete Stationen werden aus Win-Test übernommen und in der Benutzerliste markiert.
- **Frequenz-Auswertung**: Die aktuelle TRX-Frequenz aus Win-Test befüllt die `MYQRG`-Variable.
- **Sked-Übergabe (SKED Push)**: Skeds aus KST4Contest werden via UDP direkt an Win-Test übergeben. Der UDP-Broadcast-Standardport von Win-Test (9871) wird verwendet.
Einstellungen:
- **Aktivieren/Deaktivieren**: Checkbox in den Preferences (ab v1.40).
- **Port**: Konfigurierbarer UDP-Port für den Win-Test-Listener.
- **Sked-UDP-Adresse und Port**: Zieladresse und Port für die SKED-Übergabe an Win-Test.
> **Hinweis**: Der Win-Test-Listener ist ein **zusätzlicher** Listener der Standard-QSO-UDP-Broadcast-Listener auf Port 12060 bleibt davon unabhängig.
---
## PSTRotator-Einstellungen (ab v1.31)
KST4Contest kann die Antennenrichtung über PSTRotator steuern.
Einstellungen:
- **Aktivieren/Deaktivieren**: Checkbox in den Preferences (ab v1.40).
- **IP-Adresse**: IP-Adresse des PSTRotator-Rechners (Standard: `127.0.0.1` bei Betrieb auf demselben PC).
- **Port**: Kommunikationsport von PSTRotator.
> **Hinweis**: Nach einem Klick auf den Richtungs-Button wartet KST4Contest kurz auf die Rotatorantwort. Bei langsamen Rotoren (z. B. SPID) kann es zu einer kleinen Verzögerung kommen.
---
## Sniffer-Einstellungen (ab v1.31)
Der QSO-Sniffer filtert Chat-Nachrichten von konfigurierbaren Rufzeichen und leitet sie ins PM-Fenster weiter.
Einstellungen:
- **Rufzeichen-Liste**: Kommagetrennte Liste von Rufzeichen, deren Nachrichten immer in das PM-Fenster weitergeleitet werden sollen.
Anwendungsfall: Wichtige Stationen (z. B. DX-Peditionen oder feste Verbündete im Contest) im Auge behalten, ohne den Haupt-Chat ständig zu beobachten.
---
## Worked Station Database Settings (Gearbeitete-Stationen-Datenbank) ## Worked Station Database Settings (Gearbeitete-Stationen-Datenbank)
Vor jedem Contest die interne Worked-Datenbank zurücksetzen! Enthält: Die interne Worked-Datenbank enthält:
- Worked-Status aller Stationen (pro Band) - Worked-Status aller Stationen (pro Band)
- NOT-QRV-Tags (seit v1.2) - NOT-QRV-Tags (seit v1.2)
Schaltfläche **„Reinitialize"** unter der Tabelle verwenden. Eine geplante Funktion ist eine automatische Ablaufzeit für den Worked-Status. **Ab v1.40**: Einträge haben eine automatische Lebensdauer von **3 Tagen** ein manuelles Zurücksetzen vor jedem Contest ist nicht mehr zwingend notwendig. Für ein vollständiges Reset kann trotzdem die Schaltfläche **„Reinitialize"** verwendet werden.
--- ---
@@ -130,6 +195,6 @@ Umschaltbar über das Menü: **Window → Use Dark Mode**. Die Farben können ü
Nach **jeder** Änderung **„Save Settings"** klicken! Ohne Speichern gehen alle Änderungen beim nächsten Start verloren. Nach **jeder** Änderung **„Save Settings"** klicken! Ohne Speichern gehen alle Änderungen beim nächsten Start verloren.
- Speicherort: `~/.praktikst/preferences.xml` - Speicherort: unter Linux `~/.praktikst/preferences.xml` und unter Windows `%USERPROFILE%\.praktikst\preferences.xml` (bzw. `C:\Users\<Benutzername>\.praktikst\preferences.xml`)
- Ab v1.21: Fenstergrößen und Divider-Positionen werden ebenfalls gespeichert. - Ab v1.21: Fenstergrößen und Divider-Positionen werden ebenfalls gespeichert.
- Bei Problemen: Konfigurationsdatei löschen → KST4Contest erstellt eine neue mit Standardwerten. - Bei Problemen: Konfigurationsdatei löschen → KST4Contest erstellt eine neue mit Standardwerten.

View File

@@ -6,6 +6,8 @@ KST4Contest markiert gearbeitete Stationen automatisch in der Chat-Benutzerliste
--- ---
![Log-Synchronisation Einstellungsfenster](client_settings_window_logsync.png)
## Methode 1: Universal File Based Callsign Interpreter (Simplelogfile) ## Methode 1: Universal File Based Callsign Interpreter (Simplelogfile)
KST4Contest liest eine Log-Datei und sucht mittels regulärem Ausdruck nach Rufzeichen-Mustern. Dabei werden auch binäre Logdateien unterstützt unlesbarer Binärinhalt wird einfach ignoriert. KST4Contest liest eine Log-Datei und sucht mittels regulärem Ausdruck nach Rufzeichen-Mustern. Dabei werden auch binäre Logdateien unterstützt unlesbarer Binärinhalt wird einfach ignoriert.
@@ -33,6 +35,8 @@ Das Logprogramm sendet beim Speichern eines QSOs ein UDP-Paket an die Broadcast-
### UCXLog (DL7UCX) ### UCXLog (DL7UCX)
![UCXLog Konfiguration](ucxlog_logsync.png)
UCXLog sendet QSO-UDP-Pakete und Transceiver-Frequenzpakete. UCXLog sendet QSO-UDP-Pakete und Transceiver-Frequenzpakete.
**Einstellungen in UCXLog:** **Einstellungen in UCXLog:**
@@ -46,6 +50,8 @@ Hinweis für Multi-Setup (2 Computer, 2 Radios, eine KST4Contest-Instanz): Beide
### QARTest (IK3QAR) ### QARTest (IK3QAR)
![QARTest Konfiguration](qartest_logsync.png)
**Besonderheit**: QARTest kann das **vollständige Log** an KST4Contest senden (Schaltfläche „Invia log completo" in den QARTest-Einstellungen). Damit werden auch QSOs erfasst, die vor dem Start von KST4Contest geloggt wurden. **Besonderheit**: QARTest kann das **vollständige Log** an KST4Contest senden (Schaltfläche „Invia log completo" in den QARTest-Einstellungen). Damit werden auch QSOs erfasst, die vor dem Start von KST4Contest geloggt wurden.
**Einstellungen in QARTest:** **Einstellungen in QARTest:**
@@ -68,14 +74,33 @@ Für den integrierten DX-Cluster-Server: N1MM+ als DX-Cluster-Client konfigurier
### DXLog.net ### DXLog.net
![DXLog.net Konfiguration](dxlog_net_logsync.png)
**Einstellungen in DXLog.net:** **Einstellungen in DXLog.net:**
- UDP-Broadcast aktivieren - UDP-Broadcast aktivieren
- IP des KST4Contest-Computers eintragen (grün markierte Felder) - IP des KST4Contest-Computers eintragen (grün markierte Felder)
- Port: 12060 - Port: 12060
### WinTest ### Win-Test
WinTest wird ebenfalls unterstützt. KST4Contest empfängt WinTest-UDP-Pakete über einen dedizierten Listener. Die Konfiguration erfolgt analog zu den anderen Programmen. Win-Test wird mit einem dedizierten UDP-Netzwerk-Listener unterstützt, der das native Win-Test Netzwerkprotokoll versteht.
**Vorteile der Win-Test Integration:**
- Automatische QSO-Synchronisation zur Markierung gearbeiteter Stationen.
- **Sked-Übergabe (ADDSKED):** Über den Button "Create sked" im Stationsinfo-Panel wird nicht nur in KST4Contest ein Sked angelegt, sondern dieses auch *direkt per UDP an das Win-Test Netzwerk als ADDSKED-Paket gesendet*.
- Es kann zwischen den Sked-Modi "AUTO", "SSB" oder "CW" gewählt werden.
**Notwendige Einstellungen in KST4Contest:**
- `UDP-Port for Win-Test listener` (Standard: 9871).
- `Receive Win-Test network based UDP log messages` aktivieren.
- `Win-Test sked transmission (push via ADDSKED to Win-Test network)` aktivieren.
- `KST station name in Win-Test network (src of SKED packets)`: Legt fest, unter welchem Stationsnamen KST4Contest im WT-Netzwerk auftritt (z.B. "KST").
- `Win-Test station name filter`: Wenn hier ein Name eingetragen wird (z.B. "STN1"), werden nur QSOs von dieser bestimmten Win-Test Instanz verarbeitet. Leer lassen, um alle zu akzeptieren.
- `Win-Test network broadcast address`: Wird idR automatisch erkannt und ist erforderlich, um die Sked-Pakete ins Netzwerk zu senden.
**Einstellungen in Win-Test:**
- Das Netzwerk in Win-Test muss aktiv sein.
- Win-Test muss so konfiguriert sein, dass es seine Broadcasts an den entsprechenden Port (Standard 9871) sendet bzw. empfängt.
--- ---
@@ -83,6 +108,8 @@ WinTest wird ebenfalls unterstützt. KST4Contest empfängt WinTest-UDP-Pakete ü
Neben der QSO-Synchronisation übertragen UCXLog und andere Programme auch die **aktuelle Transceiverfrequenz** via UDP. KST4Contest verarbeitet diese Information und stellt sie als Variable `MYQRG` bereit. Neben der QSO-Synchronisation übertragen UCXLog und andere Programme auch die **aktuelle Transceiverfrequenz** via UDP. KST4Contest verarbeitet diese Information und stellt sie als Variable `MYQRG` bereit.
![FrequenzButtons](qrg_buttons.png)
**Ergebnis**: Die eigene QRG muss im Chat nie mehr manuell eingegeben werden ein Klick auf den MYQRG-Button oder die Verwendung der Variable im Beacon genügt. **Ergebnis**: Die eigene QRG muss im Chat nie mehr manuell eingegeben werden ein Klick auf den MYQRG-Button oder die Verwendung der Variable im Beacon genügt.
> **Hinweis für Multi-Setup**: Bei zwei Logprogrammen an zwei Computern sollte nur **eines** die Frequenzpakete senden. KST4Contest kann nicht zwischen den Quellen unterscheiden und verarbeitet alle eingehenden Pakete. > **Hinweis für Multi-Setup**: Bei zwei Logprogrammen an zwei Computern sollte nur **eines** die Frequenzpakete senden. KST4Contest kann nicht zwischen den Quellen unterscheiden und verarbeitet alle eingehenden Pakete.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -32,6 +32,10 @@ An account is required for this service. Please consider donating to Thomas
1. Start AirScout. 1. Start AirScout.
2. Enter your OV3T feed account details (username, password, URL) in the AirScout settings. 2. Enter your OV3T feed account details (username, password, URL) in the AirScout settings.
![AirscoutStep1](as_plane_feed_1.png)
![AirscoutStep2](as_plane_feed_2.png)
3. Test the connection. 3. Test the connection.
### Step 2: Enable UDP Communication for KST4Contest ### Step 2: Enable UDP Communication for KST4Contest
@@ -47,6 +51,10 @@ In KST4Contest Preferences → **AirScout Settings**:
- Enable AirScout communication - Enable AirScout communication
- Leave IP and port at their default values (unless changed) - Leave IP and port at their default values (unless changed)
![AirscoutStep3](as_plane_feed_3.png){ width=85% }
--- ---
## Communication Between KST4Contest and AirScout (from v1.263) ## Communication Between KST4Contest and AirScout (from v1.263)

View File

@@ -6,6 +6,85 @@ Version history of KST4Contest / PraktiKST.
--- ---
For the latest changelog, please refer to GitHub. The previous changelog is below.
## v1.40 (2026-02-16)
**Major Feature Release: Score System, AP Timeline, Win-Test, PSTRotator**
**New:**
- **Chatmember Score System**: Every chat member is automatically scored based on antenna direction, activity time, message count, active bands, frequencies, sked direction (degrees), and other factors. Top candidates are highlighted in a dedicated list.
- **AP Timeline**: For each minute of possible aircraft arrival, up to 4 highly-scored stations are shown that should be workable. Aircraft with the highest potential are preferred over the fastest arrival. Chat members whose antenna is not pointing towards you are shown transparently.
- **Win-Test Support** (Beta since v1.31, now fully configurable): Log synchronisation, frequency parsing and **sked handover via UDP** fully integrated. Can be enabled/disabled in Preferences.
- **PSTRotator Interface** (Beta since v1.31, now fully configurable): Rotator position updates directly from KST4Contest. Can be enabled/disabled in Preferences.
- **QSO Sniffer**: Messages from configurable callsign lists are automatically forwarded to the PM window.
- **Band Alert for logged stations**: When a station is logged, a hint appears if that station has another active band that you are also QRV on.
- **Sked Reminder ALERT**: A sked alarm with automatic messages in configurable intervals (2+1 / 5+2+1 / 10+5+2+1 minutes before the sked) can be set up for each chat member, plus acoustic and visual notification.
- **Load chat history on startup**: Chat server history is loaded on connect to immediately see active members and recent messages.
- **Skedfail button**: In the FurtherInfo panel, a sked failure can be marked for a chat member, which lowers their priority score.
**Changed:**
- AP notes added to internal DX cluster spots.
- Chat member table scrolling follows the current message selection automatically.
- Generic auto-reply and QRG auto-reply now fire a maximum of once every 45 seconds per callsign (prevents spam and message ping-pong).
- New saveable settings: ServerDNS/Port, PSTRotator interface, Win-Test interface, callsign sniffer, Dark Mode on by default.
- Date column removed from chat table (time only saves space).
**Fixed:**
- User list now automatically sorted on every new member sign-on.
- Posonpill messages now terminate exactly one client instance (no longer affects all instances or wtKST).
- wtKST: crash on KST4Contest disconnection fixed.
- Multiple issues with callsign suffixes like `/p`, `-2`, etc. fixed throughout.
- `QTFDefault` was not saved correctly → fixed.
- AirScout watchlist (ASWATCHLIST) was not being updated → fixed.
- Dark Mode: QRG fields not displayed at full size → fixed.
- Version number display corrected.
---
## v1.31 (2025-12-13)
**Win-Test + PSTRotator Beta, QSO Sniffer, DNS Hotfix**
**New:**
- **Win-Test support** (Beta, not yet deactivatable): Log synchronisation and frequency parsing.
- **PSTRotator support** (Beta, not yet deactivatable).
- **QSO Sniffer**: Messages from configurable callsigns are forwarded to the PM window.
**Changed:**
- **DNS server changed**: From `www.on4kst.info` to `www.on4kst.org` (hotfix). The DNS server is now configurable in Preferences.
**Fixed:**
- Endless loop in error case freezes the client → fixed.
---
## v1.266 (2025-10-03)
**AirScout Fix for Callsigns with Suffix**
**Fixed:**
- AirScout interface did not work when the login callsign contained a suffix (e.g. `9A1W-2`). AirScout cannot handle this format only the base callsign without suffix is now passed to AirScout.
*(Bug reported and tested by 9A2HM / Kreso many thanks!)*
---
## v1.265 (2025-09-28)
**Direction Buttons Stay Coloured When Active**
**Fixed:**
- Direction buttons (N / NE / E etc.) now keep their highlight colour when activated, making the active state immediately visible.
---
## v1.264 (2025-08-02)
**Simplelogfile: Improved Callsign Recognition**
**Fixed:**
- Callsigns like `S53CC`, `S51A`, etc. were not being marked as worked in the SimpleLogFile interpreter → recognition pattern improved.
*(Bug reported by Boris, S53CC thank you!)*
---
## v1.263 (2025-06-08) ## v1.263 (2025-06-08)
**AirScout Communication and Login Name** **AirScout Communication and Login Name**
@@ -151,6 +230,6 @@ First publicly released version. Core features:
## Planned Features ## Planned Features
- `MYQTF` variable (own antenna direction as text) - `MYQTF` variable (own antenna direction as text)
- Lifetime for worked status (automatic reset) - ~~Lifetime for worked status (automatic reset)~~ ✅ **Implemented in v1.40** (3-day lifetime, no manual reset needed anymore)
- Filtering the "Cluster & QSO of others" window to own QTF - Filtering the "Cluster & QSO of others" window to own QTF
- Further topography-based calculations for direction warnings - Further topography-based calculations for direction warnings

View File

@@ -4,49 +4,73 @@
After the first start, the **settings window** opens this is the central starting point for all configuration. It is recommended to keep the settings window open during operation (e.g. to quickly toggle the beacon on and off). After the first start, the **settings window** opens this is the central starting point for all configuration. It is recommended to keep the settings window open during operation (e.g. to quickly toggle the beacon on and off).
> **Important**: Always click **"Save Settings"** after any change! Settings are stored in `~/.praktikst/preferences.xml`. From v1.21 onwards, window sizes and divider positions are also saved when you click Save. > **Important**: Always click **"Save Settings"** after any change! Settings are stored in `~/.praktikst/preferences.xml` on Linux and in `%USERPROFILE%\.praktikst\preferences.xml` (or `C:\Users\<Username>\.praktikst\preferences.xml`) on Windows. From v1.21 onwards, window sizes and divider positions are also saved when you click Save.
--- ---
## Station Settings ## Station Settings
![Station Settings](client_settings_window_station.png)
### Login and Chat Categories
Enter your ON4KST chat credentials here (callsign and password).
Also, select the **primary chat category** (e.g., IARU Region 1 VHF/Microwave).
With the option for a **second chat** (Multi-Channel Login), you can log in to another category simultaneously (e.g., UHF/SHF). Both chats will then be monitored in parallel. You can optionally specify a different login name for the second chat (useful for Opposite Station Multi-Callsign Logging).
### Callsign and Locator ### Callsign and Locator
Enter your callsign and Maidenhead locator (6 characters, e.g. `JN49IJ`). These values are used for distance and direction calculations. Enter your own callsign and Maidenhead locator (6 characters, e.g., `JN49IJ`). These values are needed for distance and direction calculations.
### Active Bands ### Active Bands
Use the **"my station uses band"** checkboxes to select which bands you are active on. Only selected bands will show buttons and table rows in the user interface. A restart is required after changing these settings. Use the **"my station uses band"** checkboxes to select the active bands. Buttons and table rows will only appear in the user interface for selected bands. The software must be restarted after making changes.
### Antenna Beamwidth ### Antenna Beamwidth
Enter a realistic value for your antenna's beamwidth (in degrees). This value is used for the [Sked Direction Highlighting](Features#sked-direction-highlighting). A test value of 50° has proven useful; DM5M uses Quads with 69°. Enter a realistic value for your antenna's beamwidth (in degrees). This value is used for the [Sked Direction Highlighting](Features#sked-direction-highlighting). A test value of 50° has proven effective; DM5M uses quads with 69°.
> **Do not** enter fantasy values the direction calculations will become meaningless. > **Do not** enter fantasy values the direction calculations will become useless.
### Default Maximum QRB ### Default Maximum QRB
Maximum distance (in km) for which direction warnings should be triggered. A realistic value for DM5M is 900 km. Stations beyond this distance are ignored for highlighting purposes. Maximum distance (in km) for which direction warnings should be triggered. A realistic value for DM5M is 900 km. Stations farther away are ignored for highlighting purposes.
---
## Server Settings (from v1.31)
The chat server DNS and port are configurable in the Preferences:
- **Server DNS**: Default `www.on4kst.org` (changed from `www.on4kst.info` in v1.31 hotfix).
- **Port**: Default port of the ON4KST server.
A change is only needed if the server moves or an alternative endpoint is used.
--- ---
## Log Sync Settings ## Log Sync Settings
Two methods are available for automatically marking worked stations. Details: [Log Synchronisation](en-Log-Sync). Three methods are available for automatically marking worked stations. Details: [Log Synchronisation](en-Log-Sync).
### Universal File Based Callsign Interpreter (Simplelogfile) ### Universal File Based Callsign Interpreter (Simplelogfile)
Interprets any log file using regex to find callsign patterns. No band information available. Suitable as a fallback or for unsupported logging programs. Interprets any log file using regex for callsign patterns. No band information is available. Suitable as a fallback or for log programs that are not directly supported.
### Network Listener for Logger's QSO UDP Broadcast ### Network Listener for Logger's QSO UDP Broadcast
**Recommended method.** KST4Contest listens for UDP packets sent by the logging software when saving a QSO. Stations are marked including band information. UDP port: default **12060**. **Recommended method.** KST4Contest listens for UDP packets sent by the logging software to the broadcast address when a QSO is saved. Stations are marked with band information. UDP port: default **12060**. (Used by UCXLog, N1MM+, QARTest, DXLog.net, etc.).
### Win-Test Network Listener (Additional UDP Listener)
A dedicated network listener for Win-Test. KST4Contest receives and processes Win-Test-specific UDP packets (including sked handovers) on the configured port.
--- ---
## TRX Sync Settings ## TRX Sync Settings
Receives the current transceiver frequency from the logging software via UDP. Makes the `MYQRG` variable available automatically. Useful for: Receives the current transceiver frequency from the logging software via UDP. This enables the automatic population of the `MYQRG` variable. Useful for:
- Quickly inserting your own QRG into chat messages. - Quickly inserting your own QRG into chat messages.
- Automatic CQ beacon with current frequency. - Automatic CQ beacon with current frequency.
@@ -67,13 +91,13 @@ Three notification types are available:
1. **Simple sounds**: TADA sound for incoming messages, tick for sked direction detection, etc. 1. **Simple sounds**: TADA sound for incoming messages, tick for sked direction detection, etc.
2. **CW announcement**: The callsign of a station sending a private message is output as a CW signal. 2. **CW announcement**: The callsign of a station sending a private message is output as a CW signal.
3. **Phonetic announcement**: The callsign is spoken phonetically. 3. **Phonetic announcement**: The callsign is pronounced phonetically.
--- ---
## Shortcut Settings ## Shortcut Settings
Configure quick-access buttons that appear directly in the main window. Clicking a button inserts the configured text into the send field. All [variables](Macros-and-Variables#variables) can be used. Configuration of quick-access buttons that appear directly in the main window. Clicking a button inserts the configured text into the send field. All [variables](Macros-and-Variables#variables) can be used.
--- ---
@@ -93,9 +117,9 @@ If a callsign is selected in the user list, the snippet is addressed as a direct
## Beacon Settings ## Beacon Settings
Configure an automatic interval message in the public chat channel. Recommended: use the `MYQRG` variable in the text so the current frequency is always up to date. Interval and text are freely configurable. Configuration of an automatic interval beacon in the public chat channel. Recommended: use the `MYQRG` variable in the text so the current frequency is always up to date. Interval and text are freely configurable.
> **Tip**: Enable the beacon while calling CQ and quickly disable it in the settings window when not calling. > **Tip**: Enable the beacon when calling CQ and quickly disable it in the settings window when not calling.
--- ---
@@ -104,32 +128,73 @@ Configure an automatic interval message in the public chat channel. Recommended:
New settings section with the following options: New settings section with the following options:
- **Auto-reply to all incoming messages**: Configurable automatic reply to private messages. - **Auto-reply to all incoming messages**: Configurable automatic reply to private messages.
- **Auto-reply with CQ QRG**: When someone asks for your frequency, KST4Contest automatically replies with the `MYQRG` variable content. - **Auto-reply with own CQ QRG**: When someone asks for your QRG, KST4Contest automatically replies with the content of the `MYQRG` variable.
- **Default filter for the userinfo window**: Pre-configured message filter for the station info panel *(for Gianluca :-) )*. - **Default filter for the userinfo window**: Pre-configured message filter for the station info window *(for Gianluca :-) )*.
---
## Win-Test Network Listener (from v1.31)
A dedicated listener for Win-Test-specific UDP packets. Enables:
- **Log synchronisation**: Worked stations are retrieved from Win-Test and marked in the user list.
- **Frequency parsing**: The current TRX frequency from Win-Test populates the `MYQRG` variable.
- **Sked handover (SKED push)**: Skeds from KST4Contest are passed directly to Win-Test via UDP. Win-Test's default UDP broadcast port (9871) is used.
Settings:
- **Enable/Disable**: Checkbox in Preferences (from v1.40).
- **Port**: Configurable UDP port for the Win-Test listener.
- **Sked UDP address and port**: Target address and port for SKED handover to Win-Test.
> **Note**: The Win-Test listener is an **additional** listener the standard QSO UDP broadcast listener on port 12060 remains independent.
---
## PSTRotator Settings (from v1.31)
KST4Contest can control antenna direction via PSTRotator.
Settings:
- **Enable/Disable**: Checkbox in Preferences (from v1.40).
- **IP address**: IP address of the PSTRotator computer (default: `127.0.0.1` when running on the same PC).
- **Port**: Communication port of PSTRotator.
> **Note**: After clicking a direction button, KST4Contest waits briefly for the rotator response. With slow rotors (e.g. SPID) there may be a small delay.
---
## Sniffer Settings (from v1.31)
The QSO sniffer filters chat messages from configurable callsigns and forwards them to the PM window.
Settings:
- **Callsign list**: Comma-separated list of callsigns whose messages are always forwarded to the PM window.
Use case: Keep track of important stations (e.g. DX expeditions or trusted contest allies) without constantly monitoring the main chat.
--- ---
## Worked Station Database Settings ## Worked Station Database Settings
Reset the internal worked database before each contest! Contains: The internal worked database contains:
- Worked status for all stations (per band) - Worked status of all stations (per band)
- NOT-QRV tags (since v1.2) - NOT-QRV tags (since v1.2)
Use the **"Reinitialize"** button below the table. A planned feature is an automatic expiry time for the worked status. **From v1.40**: Entries have an automatic lifetime of **3 days** manually resetting before each contest is no longer strictly necessary. For a full reset, the **"Reinitialize"** button is still available.
--- ---
## Dark Mode (from v1.26) ## Dark Mode (from v1.26)
Toggle via the menu: **Window → Use Dark Mode**. Colours can be individually customised via CSS. Toggle via the menu: **Window → Use Dark Mode**. The colors can be individually customized via CSS.
--- ---
## Saving Settings ## Saving Settings
Click **"Save Settings"** after **every** change! Without saving, all changes are lost on next start. Click **"Save Settings"** after **every** change! Without saving, all changes will be lost on the next start.
- Storage location: `~/.praktikst/preferences.xml` - Storage location: `~/.praktikst/preferences.xml` on Linux and `%USERPROFILE%\.praktikst\preferences.xml` (or `C:\Users\<Username>\.praktikst\preferences.xml`) on Windows
- From v1.21: Window sizes and divider positions are also saved. - From v1.21: Window sizes and divider positions are also saved.
- If you encounter problems: delete the configuration file → KST4Contest creates a new one with default values. - If you encounter problems: delete the configuration file → KST4Contest will create a new one with default values.

View File

@@ -135,22 +135,86 @@ For selected stations in the user list, there are direct buttons to open the **Q
--- ---
## Sked Reminders (Sked Reminder Service) ## Sked Reminders with ALERT (from v1.40)
For agreed skeds, automatic reminder PMs can be configured, sent X minutes before the agreed time. Reminders are activated from the FurtherInfo panel. A sked reminder service with automatic messages can be activated for each chat member. Configurable interval patterns:
- **2+1 minutes**: Messages at 2 min and 1 min before the sked.
- **5+2+1 minutes**: Messages at 5, 2 and 1 min before the sked.
- **10+5+2+1 minutes**: Messages at 10, 5, 2 and 1 min before the sked.
In addition to the automated messages to the remote station, there is an **acoustic and visual notification** for your own operator so no sked is ever missed.
Activate from the FurtherInfo panel of the corresponding station.
--- ---
## Priority List / Score Service ## QSO Sniffer (from v1.31)
KST4Contest automatically calculates a **priority list** of the most interesting contacts, based on: The QSO sniffer monitors the chat for messages from a configurable callsign list and automatically forwards them to the **PM window**. This prevents relevant messages from being lost in the general chat traffic.
- Direction detection Configuration: [Configuration Sniffer Settings](en-Configuration#sniffer-settings-from-v131)
---
## Win-Test Integration (from v1.31, fully configurable from v1.40)
KST4Contest fully supports [Win-Test](https://www.win-test.com/) as a logging programme:
- **Log synchronisation**: Worked stations are automatically retrieved from Win-Test and marked in the user list.
- **Frequency parsing**: The current TRX frequency is read from Win-Test UDP packets and populates the `MYQRG` variable.
- **Sked handover (SKED push via UDP)**: Agreed skeds from KST4Contest can be pushed directly to Win-Test, so the remote callsign appears in Win-Test's sked window.
Details: [Configuration Win-Test Network Listener](en-Configuration#win-test-network-listener)
---
## PSTRotator Interface (from v1.31, fully configurable from v1.40)
KST4Contest can control antenna direction directly via **PSTRotator**. When a station is selected in the user list, the rotator can automatically be turned to the QTF of the selected station.
Configuration: [Configuration PSTRotator Settings](en-Configuration#pstrotator-settings-from-v131)
---
## Band Alert for New QSOs (from v1.40)
When a station is logged, KST4Contest automatically checks whether that station has shown any other active bands in the chat that you are also QRV on. If so, a **hint alert** appears so no multi-band opportunity is missed.
---
## Worked Tag Lifetime (from v1.40)
Worked stations are automatically removed from the database after **3 days**. Manually resetting the worked database before each contest is therefore no longer strictly necessary the database keeps itself up to date.
---
## Chatmember Score System / Priority List (from v1.40)
KST4Contest automatically calculates a **priority score** for each active chat member. The score is derived from:
- Antenna direction of the remote station (is it pointing towards me?)
- QRB (distance) - QRB (distance)
- Activity time and message count
- Active bands and frequencies
- AP availability (AirScout) - AP availability (AirScout)
- Worked status - Sked direction (degrees)
- Sked success rate and skedfail markings
The top candidates are shown in a separate list, helping you not to miss the most important stations during contest stress. The top candidates are highlighted in a dedicated priority list, helping you not to miss the most important contacts during contest stress.
Stations with a failed sked can be marked using the **Skedfail button** in the FurtherInfo panel this temporarily lowers their score.
---
## AP Timeline (from v1.40)
A visual timeline shows up to 4 highly-scored stations per minute slot that should be workable via aircraft scatter. Prioritisation criteria:
- **Highest reflection potential** is preferred (not necessarily the fastest arrival).
- Stations towards which your antenna is not pointing are shown **transparently**.
This gives the contest operator a quick overview of which stations will be reachable via which aircraft and at what time.
--- ---

View File

@@ -12,14 +12,14 @@ Developed by **DO5AMF (Marc Fröhlich)**, operator at DM5M.
| Page | Contents | | Page | Contents |
|---|---| |---|---|
| [Installation](en-Installation) | Download, Java requirements, updates | | [Installation](en-Installation) | Download, Java requirements, update |
| [Configuration](en-Configuration) | All settings in detail | | [Configuration](en-Configuration) | All settings in detail |
| [Log Synchronisation](en-Log-Sync) | UCXLog, N1MM+, QARTest, DXLog.net, WinTest | | [Log Synchronisation](en-Log-Sync) | UCXLog, N1MM+, QARTest, DXLog.net, WinTest |
| [AirScout Integration](en-AirScout-Integration) | Aircraft scatter detection | | [AirScout Integration](en-AirScout-Integration) | Aircraft scatter detection |
| [DX Cluster Server](en-DX-Cluster-Server) | Built-in DX cluster for your logging software | | [DX Cluster Server](en-DX-Cluster-Server) | Built-in DX cluster for your logging software |
| [Features](en-Features) | All features at a glance | | [Features](en-Features) | All features at a glance |
| [Macros and Variables](en-Macros-and-Variables) | Text snippets, shortcuts, variables | | [Macros and Variables](en-Macros-and-Variables) | Text snippets, shortcuts, variables |
| [User Interface](en-User-Interface) | UI explained and how to operate it | | [User Interface](en-User-Interface) | UI explanation and operation |
| [Changelog](en-Changelog) | Version history | | [Changelog](en-Changelog) | Version history |
--- ---
@@ -42,7 +42,7 @@ The ON4KST Chat is the de-facto standard for skeds on the 144 MHz and higher ban
- **Email**: praktimarc+kst4contest@gmail.com *(for kst4contest topics only)* - **Email**: praktimarc+kst4contest@gmail.com *(for kst4contest topics only)*
- **GitHub**: https://github.com/praktimarc/kst4contest - **GitHub**: https://github.com/praktimarc/kst4contest
- **Download**: https://do5amf.funkerportal.de/ - **Download**: https://github.com/praktimarc/kst4contest/releases/latest
--- ---

View File

@@ -4,23 +4,21 @@
## Prerequisites ## Prerequisites
### Java An resolution of 1200px by 720px is recommended
KST4Contest is a Java application. A current **Java Runtime Environment (JRE)** is required. The recommended version is Java 17 or higher.
### ON4KST Account ### ON4KST Account
To use the chat client, you need a registered account with the ON4KST chat service: To use the chat, a registered account with the ON4KST chat service is required:
- Register at: http://www.on4kst.info/chat/register.php - Register at: http://www.on4kst.info/chat/register.php
### Behavioural Etiquette ### Chat Etiquette
The official language in the ON4KST Chat is **English**. Please use English even when communicating with stations from your own country. Common HAM abbreviations (agn, dir, pse, rrr, tnx, 73 …) are widely used and understood. The official language in the ON4KST Chat is **English**. Please use English even when communicating with stations from your own country. Common HAM abbreviations (agn, dir, pse, rrr, tnx, 73 …) are widely used and understood.
### Sending Personal Messages ### Personal Messages
To send a private message to another station, always use this format: To send a private message to another station, always use the following format:
``` ```
/CQ CALLSIGN message text /CQ CALLSIGN message text
@@ -28,45 +26,78 @@ To send a private message to another station, always use this format:
Example: `/CQ DL5ASG pse sked 144.205?` Example: `/CQ DL5ASG pse sked 144.205?`
During contest operation (56 messages per second in the public channel), public messages directed at a specific callsign are easily missed. KST4Contest also catches such messages if they are accidentally posted publicly (see [Features PM Catching](Features#pm-catching)). During heavy chat traffic (56 messages per second in a contest), public messages directed at a specific callsign are easily missed. However, KST4Contest also catches such messages if they are accidentally posted publicly (see [Features PM Catching](Features#catching-personal-messages)).
--- ---
## Download ## Download
### Windows
The latest version can be downloaded as a ZIP file: The latest version can be downloaded as a ZIP file:
**https://do5amf.funkerportal.de/** **https://github.com/praktimarc/kst4contest/releases/latest**
The filename has the format `praktiKST-v<version_number>-windows-x64.zip`.
### Linux
The latest version can be downloaded as an AppImage:
**https://github.com/praktimarc/kst4contest/releases/latest**
The filename has the format `praktiKST-v<version_number>-linux-x86_64.AppImage`.
The filename follows the pattern `kst4Contest_v<version>.zip`.
--- ---
## Installation ## Installation
1. Download the ZIP file. ### Windows
2. Unzip into a folder of your choice.
3. Run `praktiKST.exe` (Windows) or the corresponding start script.
Settings are stored at `%USERPROFILE%\.praktikst\preferences.xml` (Windows). 1. Download the ZIP file.
2. Unzip the ZIP file into a folder of your choice.
3. Run `praktiKST.exe`.
Settings are stored at `%USERPROFILE%\.praktikst\preferences.xml`.
### Linux
1. Download the AppImage.
2. Unzip the AppImage into a folder of your choice.
3. Make the AppImage executable (in the terminal with `chmod +x praktiKST-v<version_number>-linux-x86_64.AppImage`)
4. Run the AppImage.
Settings are stored at `~/.praktikst/preferences.xml`.
--- ---
## Updating ## Updating
KST4Contest includes an **automatic update notification service**: when a new version is available, a window will appear at startup showing: KST4Contest includes an **automatic update notification service**: as soon as a new version is available, a window will appear at startup with:
- A notification that a new version is available - information that a new version is available,
- A changelog - a changelog,
- The download link for the latest package - the download link for the new version.
![Example Update Window](update_window.png)
### Update Process ### Update Process
Currently the only way to update is: #### Windows
Currently, there is only one way to update:
1. Delete the old folder. 1. Delete the old folder.
2. Unzip the new package. 2. Unzip the new ZIP file.
The settings file (`preferences.xml`) is preserved because it is stored in the user folder, not the program folder.
#### Linux
Currently as follows:
1. Download the new AppImage
2. Mark the new AppImage as executable
3. (optional) Delete the old AppImage.
Your settings file (`preferences.xml`) is preserved since it is stored in your user folder, not the program folder.
--- ---
@@ -74,10 +105,10 @@ Your settings file (`preferences.xml`) is preserved since it is stored in your u
### Norton 360 ### Norton 360
Norton 360 flags `praktiKST.exe` as dangerous (false positive). You need to add an exception: Norton 360 classifies `praktiKST.exe` as dangerous (false positive). An exception must be created for the file:
1. Open Norton 360. 1. Open Norton 360.
2. Security → History → Find the relevant event. 2. Security → History → Find the corresponding event.
3. Select "Restore & Add Exception". 3. Select "Restore & Add Exception".
*(Reported by PE0WGA, Franz van Velzen thank you!)* *(Reported by PE0WGA, Franz van Velzen thank you!)*

View File

@@ -6,6 +6,8 @@ KST4Contest automatically marks worked stations in the chat user list. Two basic
--- ---
![Log Sync Settings Window](client_settings_window_logsync.png)
## Method 1: Universal File Based Callsign Interpreter (Simplelogfile) ## Method 1: Universal File Based Callsign Interpreter (Simplelogfile)
KST4Contest reads a log file and searches for callsign patterns using a regular expression. Binary log files are also supported unreadable binary content is simply ignored. KST4Contest reads a log file and searches for callsign patterns using a regular expression. Binary log files are also supported unreadable binary content is simply ignored.
@@ -33,6 +35,8 @@ When saving a QSO, the logging software sends a UDP packet to the broadcast addr
### UCXLog (DL7UCX) ### UCXLog (DL7UCX)
![UCXLog Configuration](ucxlog_logsync.png)
UCXLog sends QSO UDP packets and transceiver frequency packets. UCXLog sends QSO UDP packets and transceiver frequency packets.
**Settings in UCXLog:** **Settings in UCXLog:**
@@ -46,6 +50,8 @@ Note for multi-setup (2 computers, 2 radios, one KST4Contest instance): Both log
### QARTest (IK3QAR) ### QARTest (IK3QAR)
![QARTest Configuration](qartest_logsync.png)
**Special feature**: QARTest can send the **complete log** to KST4Contest (button "Invia log completo" in the QARTest settings). This means QSOs logged before KST4Contest was started are also captured. **Special feature**: QARTest can send the **complete log** to KST4Contest (button "Invia log completo" in the QARTest settings). This means QSOs logged before KST4Contest was started are also captured.
**Settings in QARTest:** **Settings in QARTest:**
@@ -68,14 +74,33 @@ For the built-in DX cluster server: configure N1MM+ as a DX cluster client (serv
### DXLog.net ### DXLog.net
![DXLog.net Configuration](dxlog_net_logsync.png)
**Settings in DXLog.net:** **Settings in DXLog.net:**
- Enable UDP broadcast - Enable UDP broadcast
- Enter the IP of the KST4Contest computer (green-highlighted fields) - Enter the IP of the KST4Contest computer (green-highlighted fields)
- Port: 12060 - Port: 12060
### WinTest ### Win-Test
WinTest is also supported. KST4Contest receives WinTest UDP packets via a dedicated listener. Configuration is analogous to the other programs. Win-Test is supported with a dedicated UDP network listener that understands the native Win-Test network protocol.
**Advantages of Win-Test Integration:**
- Automatic QSO synchronization to mark worked stations.
- **Sked Handover (ADDSKED):** Using the "Create sked" button in the station info panel not only creates a sked in KST4Contest but also *sends it directly via UDP to the Win-Test network as an ADDSKED packet*.
- You can choose between "AUTO", "SSB", or "CW" sked modes.
**Required Settings in KST4Contest:**
- `UDP-Port for Win-Test listener` (Default: 9871).
- Enable `Receive Win-Test network based UDP log messages`.
- Enable `Win-Test sked transmission (push via ADDSKED to Win-Test network)`.
- `KST station name in Win-Test network (src of SKED packets)`: Defines the station name KST4Contest uses in the WT network (e.g., "KST").
- `Win-Test station name filter`: If a name is entered here (e.g., "STN1"), only QSOs from that specific Win-Test instance will be processed. Leave empty to accept all.
- `Win-Test network broadcast address`: Is usually detected automatically and is required to send sked packets to the network.
**Settings in Win-Test:**
- The network in Win-Test must be active.
- Win-Test must be configured to send/receive its broadcasts on the corresponding port (default 9871).
--- ---
@@ -83,6 +108,8 @@ WinTest is also supported. KST4Contest receives WinTest UDP packets via a dedica
In addition to QSO synchronisation, UCXLog and other programs also transmit the **current transceiver frequency** via UDP. KST4Contest processes this information and makes it available as the `MYQRG` variable. In addition to QSO synchronisation, UCXLog and other programs also transmit the **current transceiver frequency** via UDP. KST4Contest processes this information and makes it available as the `MYQRG` variable.
![Frequency Buttons](qrg_buttons.png)
**Result**: Your own QRG never needs to be typed manually in the chat clicking the MYQRG button or using the variable in the beacon is sufficient. **Result**: Your own QRG never needs to be typed manually in the chat clicking the MYQRG button or using the variable in the beacon is sufficient.
> **Note for multi-setup**: With two logging programs on two computers, only **one** should send frequency packets. KST4Contest cannot distinguish between sources and processes all incoming packets. > **Note for multi-setup**: With two logging programs on two computers, only **one** should send frequency packets. KST4Contest cannot distinguish between sources and processes all incoming packets.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
github_docs/qrg_buttons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

528
src/kstsimulator.py Normal file
View File

@@ -0,0 +1,528 @@
import socket
import threading
import time
import random
import traceback
from datetime import datetime, timedelta
# =====================================
# KST-Server-Simulator / DO5AMF
# Usage: change configuration below and
# run. Enter 127.0.0.1 : 23001 as a
# target in KST4Contest or another
# KST chat client.
# =====================================
# ==========================================
# KONFIGURATION
# ==========================================
PORT = 23001
HOST = '127.0.0.1'
MSG_TO_USER_INTERVAL = 300.0
LOGIN_LOGOUT_INTERVAL = 60.0
KEEP_ALIVE_INTERVAL = 10.0
CLIENT_WARMUP_TIME = 5.0
PROB_INACTIVE = 0.10
PROB_REACTIVE = 0.20
# QSY Wahrscheinlichkeit (Wie oft wechselt ein User seine Frequenz?)
# 0.05 = 5% Chance pro Nachricht, dass er die Frequenz ändert. Sonst bleibt er stabil.
PROB_QSY = 0.05
BANDS_VHF = { "2m": (144.150, 144.400), "70cm": (432.100, 432.300) }
BANDS_UHF = { "23cm": (1296.100, 1296.300), "3cm": (10368.100, 10368.250) }
CHANNELS_SETUP = {
"2": {
"NAME": "144/432 MHz",
"NUM_USERS": 777,
"BANDS": BANDS_VHF,
"RATES": {"PUBLIC": 0.5, "DIRECTED": 3.0},
"PERMANENT": [
{"call": "DK5EW", "name": "Erwin", "loc": "JN47NX"},
{"call": "DL1TEST", "name": "TestOp", "loc": "JO50XX"}
]
},
"3": {
"NAME": "Microwave",
"NUM_USERS": 333,
"BANDS": BANDS_UHF,
"RATES": {"PUBLIC": 0.2, "DIRECTED": 0.5},
"PERMANENT": [
{"call": "ON4KST", "name": "Alain", "loc": "JO20HI"},
{"call": "G4CBW", "name": "MwTest", "loc": "IO83AA"}
]
}
}
COUNTRY_MAPPING = {
"DL": ["JO", "JN"], "DA": ["JO", "JN"], "DF": ["JO", "JN"], "DJ": ["JO", "JN"], "DK": ["JO", "JN"], "DO": ["JO", "JN"],
"F": ["JN", "IN", "JO"], "G": ["IO", "JO"], "M": ["IO", "JO"], "2E": ["IO", "JO"],
"PA": ["JO"], "ON": ["JO"], "OZ": ["JO"], "SM": ["JO", "JP"], "LA": ["JO", "JP"],
"OH": ["KP"], "SP": ["JO", "KO"], "OK": ["JO", "JN"], "OM": ["JN", "KN"],
"HA": ["JN", "KN"], "S5": ["JN"], "9A": ["JN"], "HB9": ["JN"], "OE": ["JN"],
"I": ["JN", "JM"], "IK": ["JN", "JM"], "IU": ["JN", "JM"], "EA": ["IN", "IM"],
"CT": ["IM"], "EI": ["IO"], "GM": ["IO"], "GW": ["IO"], "YO": ["KN"],
"YU": ["KN"], "LZ": ["KN"], "SV": ["KM", "KN"], "UR": ["KO", "KN"],
"LY": ["KO"], "YL": ["KO"], "ES": ["KO"]
}
NAMES = ["Hans", "Peter", "Jo", "Alain", "Mike", "Sven", "Ole", "Jean", "Bob", "Tom", "Giovanni", "Mario", "Frank", "Steve", "Dave"]
MSG_TEMPLATES_WITH_FREQ = [
"QSY {freq}", "PSE QSY {freq}", "Calling CQ on {freq}", "I am QRV on {freq}",
"Listening on {freq}", "Can you try {freq}?", "Signals strong on {freq}",
"Scattering on {freq}", "Please go to {freq}", "Running test on {freq}",
"Any takers for {freq}?", "Back to {freq}", "QRG {freq}?", "Aircraft scatter {freq}"
]
MSG_TEMPLATES_TEXT_ONLY = [
"TNX for QSO", "73 all", "Anyone for sked?", "Good conditions",
"Nothing heard", "Rain scatter?", "Waiting for moonrise", "CQ Contest",
"QRZ?", "My locator is {loc}", "Band is open"
]
REPLY_TEMPLATES = [
"Hello {user}, 599 here", "Rgr {user}, tnx for report", "Yes {user}, QSY?",
"Sorry {user}, no copy", "Pse wait 5 min {user}", "Ok {user}, 73",
"Locator is {loc}", "Go to {freq} please", "Rgr {user}, gl"
]
# ==========================================
# CLIENT WRAPPER
# ==========================================
class ConnectedClient:
def __init__(self, sock, addr):
self.sock = sock
self.addr = addr
self.call = f"GUEST_{random.randint(1000,9999)}"
self.channels = {"2"}
self.login_time = time.time()
self.lock = threading.Lock()
def send_safe(self, data_str):
if not data_str: return True
with self.lock:
try:
self.sock.sendall(data_str.encode('latin-1', errors='replace'))
return True
except:
return False
def close(self):
try: self.sock.close()
except: pass
# ==========================================
# LOGIK KLASSEN
# ==========================================
class MessageFactory:
@staticmethod
def get_stable_frequency(user, band_name, min_f, max_f):
"""Liefert eine stabile Frequenz für diesen User auf diesem Band"""
# Wenn noch keine Frequenz da ist ODER Zufall zuschlägt (QSY)
if band_name not in user['freqs'] or random.random() < PROB_QSY:
freq_val = round(random.uniform(min_f, max_f), 3)
user['freqs'][band_name] = f"{freq_val:.3f}"
return user['freqs'][band_name]
@staticmethod
def get_chat_message(bands_config, user):
try:
# Entscheidung: Text mit Frequenz oder ohne?
if random.random() < 0.7:
# Wähle zufälliges Band aus den verfügbaren
band_name = random.choice(list(bands_config.keys()))
min_f, max_f = bands_config[band_name]
# Hole STABILE Frequenz für diesen User
freq_str = MessageFactory.get_stable_frequency(user, band_name, min_f, max_f)
return random.choice(MSG_TEMPLATES_WITH_FREQ).format(freq=freq_str)
else:
return random.choice(MSG_TEMPLATES_TEXT_ONLY).format(loc=user['loc'])
except: return "TNX 73"
@staticmethod
def get_reply_msg(bands, target_call, my_loc):
try:
tmpl = random.choice(REPLY_TEMPLATES)
freq_str = "QSY?"
# Bei Replies simulieren wir oft nur "QSY?" ohne konkrete Frequenz,
# oder nutzen eine zufällige, da der Kontext fehlt.
if "{freq}" in tmpl and bands:
band_name = random.choice(list(bands.keys()))
min_f, max_f = bands[band_name]
freq_str = f"{round(random.uniform(min_f, max_f), 3):.3f}"
return tmpl.format(user=target_call, loc=my_loc, freq=freq_str)
except: return "TNX 73"
class UserFactory:
registry = {}
@classmethod
def get_or_create_user(cls, channel_id, current_channel_users):
# 1. Reuse existing
candidates = [u for call, u in cls.registry.items() if call not in current_channel_users]
if candidates and random.random() < 0.5:
return random.choice(candidates)
# 2. Create new
return cls._create_new_unique_user(channel_id, current_channel_users)
@classmethod
def _create_new_unique_user(cls, channel_id, current_channel_users):
while True:
prefix = random.choice(list(COUNTRY_MAPPING.keys()))
num = random.randint(0, 9)
suffix = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=random.randint(1,3)))
call = f"{prefix}{num}{suffix}"
if call in current_channel_users: continue
if call in cls.registry: return cls.registry[call]
valid_grids = COUNTRY_MAPPING[prefix]
grid_prefix = random.choice(valid_grids)
sq_num = f"{random.randint(0,99):02d}"
sub = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=2))
loc = f"{grid_prefix}{sq_num}{sub}"
name = random.choice(NAMES)
rand = random.random()
if rand < PROB_INACTIVE: role = "INACTIVE"
elif rand < (PROB_INACTIVE + PROB_REACTIVE): role = "REACTIVE"
else: role = "ACTIVE"
# Neu V31: Frequenz-Gedächtnis
user_data = {
"call": call,
"name": name,
"loc": loc,
"role": role,
"freqs": {} # Speicher für { '2m': '144.300' }
}
cls.registry[call] = user_data
return user_data
@classmethod
def register_permanent(cls, user_data):
# Sicherstellen, dass auch Permanent User Freq-Memory haben
if "freqs" not in user_data:
user_data["freqs"] = {}
cls.registry[user_data['call']] = user_data
# ==========================================
# CHANNEL INSTANCE
# ==========================================
class ChannelInstance:
def __init__(self, cid, config, server):
self.id = cid
self.config = config
self.server = server
self.users_pool = []
self.online_users = {}
self.history_chat = []
self.last_pub = time.time()
self.last_dir = time.time()
self.last_me = time.time()
self.last_login = time.time()
self.rate_pub = 1.0 / config["RATES"]["PUBLIC"]
self.rate_dir = 1.0 / config["RATES"]["DIRECTED"]
self._init_data()
def _init_data(self):
print(f"[*] Init Channel {self.id} ({self.config['NAME']})...")
for u in self.config["PERMANENT"]:
u_full = u.copy()
u_full["role"] = "ACTIVE"
UserFactory.register_permanent(u_full)
self.online_users[u['call']] = u_full
for _ in range(self.config["NUM_USERS"]):
new_u = UserFactory.get_or_create_user(self.id, self.online_users.keys())
self.users_pool.append(new_u)
fill = int(self.config["NUM_USERS"] * 0.9)
for i in range(fill):
u = self.users_pool[i]
if u['call'] not in self.online_users:
self.online_users[u['call']] = u
print(f"[*] Channel {self.id} ready: {len(self.online_users)} Users.")
self._prefill_history()
def _prefill_history(self):
actives = [u for u in self.online_users.values() if u['role'] == "ACTIVE"]
if not actives: return
start = datetime.now() - timedelta(minutes=15)
for i in range(30):
msg_time = start + timedelta(seconds=i*30)
ts = str(int(msg_time.timestamp()))
sender = random.choice(actives)
if i % 2 == 0:
text = MessageFactory.get_chat_message(self.config["BANDS"], sender)
frame = f"CH|{self.id}|{ts}|{sender['call']}|{sender['name']}|0|{text}|0|\r\n"
else:
target = random.choice(list(self.online_users.values()))
text = MessageFactory.get_reply_msg(self.config["BANDS"], target['call'], sender['loc'])
frame = f"CH|{self.id}|{ts}|{sender['call']}|{sender['name']}|0|{text}|{target['call']}|\r\n"
self.history_chat.append(frame)
def tick(self, now):
actives = [u for u in self.online_users.values() if u['role'] == "ACTIVE"]
if not actives: return
# PUBLIC
if now - self.last_pub > self.rate_pub:
self.last_pub = now
u = random.choice(actives)
# V31: Nutzt jetzt get_chat_message, das das Freq-Memory abfragt
text = MessageFactory.get_chat_message(self.config["BANDS"], u)
ts = str(int(now))
frame = f"CH|{self.id}|{ts}|{u['call']}|{u['name']}|0|{text}|0|\r\n"
self._add_hist(frame)
self.server.broadcast_to_channel(self.id, frame)
# DIRECTED
if now - self.last_dir > self.rate_dir:
self.last_dir = now
if len(actives) > 5:
u1 = random.choice(actives)
u2 = random.choice(list(self.online_users.values()))
if u1 != u2:
if random.random() < 0.5:
# Auch hier Frequenzstabilität beachten
text = MessageFactory.get_chat_message(self.config["BANDS"], u1)
else:
text = MessageFactory.get_reply_msg(self.config["BANDS"], u2['call'], u1['loc'])
ts = str(int(now))
frame = f"CH|{self.id}|{ts}|{u1['call']}|{u1['name']}|0|{text}|{u2['call']}|\r\n"
self.server.broadcast_to_channel(self.id, frame)
if u2['role'] != "INACTIVE":
threading.Thread(target=self._schedule_reply, args=(u2['call'], u1['call']), daemon=True).start()
# MSG TO YOU
if now - self.last_me > MSG_TO_USER_INTERVAL:
self.last_me = now
target_client = self.server.get_random_subscriber(self.id)
if target_client and actives:
if not target_client.call.startswith("GUEST"):
sender = random.choice(actives)
text = MessageFactory.get_chat_message(self.config["BANDS"], sender)
print(f"[SIM Ch{self.id}] MSG TO YOU ({target_client.call})")
self.process_msg(sender['call'], sender['name'], text, target_client.call)
# LOGIN/LOGOUT
if now - self.last_login > LOGIN_LOGOUT_INTERVAL:
self.last_login = now
if random.choice(['IN', 'OUT']) == 'OUT' and len(self.online_users) > 20:
cands = [c for c in self.online_users if c not in [p['call'] for p in self.config["PERMANENT"]]]
if cands:
l = random.choice(cands)
del self.online_users[l]
self.server.broadcast_to_channel(self.id, f"UR6|{self.id}|{l}|\r\n")
else:
candidates = [u for u in self.users_pool if u['call'] not in self.online_users]
if candidates:
n = random.choice(candidates)
self.online_users[n['call']] = n
self.server.broadcast_to_channel(self.id, f"UA5|{self.id}|{n['call']}|{n['name']}|{n['loc']}|2|\r\n")
def process_msg(self, sender, name, text, target):
ts = str(int(time.time()))
frame = f"CH|{self.id}|{ts}|{sender}|{name}|0|{text}|{target}|\r\n"
if target == "0": self._add_hist(frame)
self.server.broadcast_to_channel(self.id, frame)
if target in self.online_users:
threading.Thread(target=self._schedule_reply, args=(target, sender), daemon=True).start()
def _schedule_reply(self, sim_sender, real_target):
if sim_sender not in self.online_users: return
u = self.online_users[sim_sender]
if u['role'] == "INACTIVE": return
time.sleep(random.uniform(2.0, 5.0))
if sim_sender in self.online_users:
text = MessageFactory.get_reply_msg(self.config["BANDS"], real_target, u['loc'])
ts = str(int(time.time()))
if self.server.is_real_user(real_target):
print(f"[REPLY Ch{self.id}] {sim_sender} -> {real_target}")
frame = f"CH|{self.id}|{ts}|{sim_sender}|{u['name']}|0|{text}|{real_target}|\r\n"
self.server.broadcast_to_channel(self.id, frame)
def _add_hist(self, frame):
self.history_chat.append(frame)
if len(self.history_chat) > 50: self.history_chat.pop(0)
def get_full_init_blob(self):
blob = ""
for u in self.online_users.values():
blob += f"UA0|{self.id}|{u['call']}|{u['name']}|{u['loc']}|0|\r\n"
for h in self.history_chat: blob += h
blob += f"UE|{self.id}|{len(self.online_users)}|\r\n"
return blob.encode('latin-1', errors='replace')
# ==========================================
# SERVER
# ==========================================
class KSTServerV31:
def __init__(self):
self.lock = threading.Lock()
self.running = True
self.clients = {}
self.channels = {}
for cid, cfg in CHANNELS_SETUP.items():
self.channels[cid] = ChannelInstance(cid, cfg, self)
def start(self):
threading.Thread(target=self._sim_loop, daemon=True).start()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
s.bind((HOST, PORT))
s.listen(5)
s.settimeout(1.0)
print(f"[*] ON4KST V31 (Stable Frequencies) running on {HOST}:{PORT}")
while self.running:
try:
sock, addr = s.accept()
print(f"[*] CONNECT: {addr}")
threading.Thread(target=self._handle_client, args=(sock,), daemon=True).start()
except socket.timeout: continue
except OSError: break
except KeyboardInterrupt:
print("\n[!] Stop.")
finally:
self.running = False
try: s.close()
except: pass
def _handle_client(self, sock):
client_obj = ConnectedClient(sock, None)
with self.lock:
self.clients[sock] = client_obj
buffer = ""
try:
while self.running:
try: data = sock.recv(2048)
except: break
if not data: break
buffer += data.decode('latin-1', errors='replace')
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
line = line.strip()
if not line: continue
parts = line.split('|')
cmd = parts[0]
if cmd == 'LOGIN' or cmd == 'LOGINC':
if len(parts) > 1:
client_obj.call = parts[1].strip().upper()
print(f"[LOGIN] {client_obj.call} (Ch 2)")
client_obj.send_safe(f"LOGSTAT|100|2|PySimV31|KEY|Conf|3|\r\n")
if cmd == 'LOGIN':
self._send_channel_init(client_obj, "2")
elif cmd == 'SDONE':
self._send_channel_init(client_obj, "2")
elif cmd.startswith('ACHAT'):
if len(parts) >= 2:
new_chan = parts[1]
if new_chan in self.channels:
client_obj.channels.add(new_chan)
print(f"[ACHAT] {client_obj.call} -> Ch {new_chan}")
self._send_channel_init(client_obj, new_chan)
elif cmd == 'MSG':
if len(parts) >= 4:
cid = parts[1]
target = parts[2]
text = parts[3]
if text.lower().startswith("/cq"):
spl = text.split(' ', 2)
if len(spl) >= 3:
target = spl[1]; text = spl[2]
if cid in self.channels:
self.channels[cid].process_msg(client_obj.call, "Me", text, target)
elif cmd == 'CK': pass
except Exception as e:
print(f"[!] Err: {e}")
finally:
with self.lock:
if sock in self.clients: del self.clients[sock]
client_obj.close()
def _send_channel_init(self, client_obj, cid):
if cid in self.channels:
full_blob = self.channels[cid].get_full_init_blob()
client_obj.send_safe(full_blob.decode('latin-1'))
def broadcast_to_channel(self, cid, frame):
now = time.time()
with self.lock:
targets = list(self.clients.values())
for c in targets:
if cid in c.channels:
if now - c.login_time > CLIENT_WARMUP_TIME:
c.send_safe(frame)
def get_random_subscriber(self, cid):
with self.lock:
subs = [c for c in self.clients.values() if cid in c.channels and not c.call.startswith("GUEST")]
return random.choice(subs) if subs else None
def is_real_user(self, call):
with self.lock:
for c in self.clients.values():
if c.call.upper() == call.upper() and not c.call.startswith("GUEST"):
return True
return False
def _sim_loop(self):
print("[*] Sim Loop running...")
last_ka = time.time()
while self.running:
now = time.time()
time.sleep(0.02)
for c in self.channels.values():
c.tick(now)
if now - last_ka > KEEP_ALIVE_INTERVAL:
last_ka = now
self.broadcast_global("CK|\r\n")
def broadcast_global(self, frame):
with self.lock:
targets = list(self.clients.values())
for c in targets:
c.send_safe(frame)
if __name__ == '__main__':
KSTServerV31().start()

View File

@@ -23,6 +23,7 @@ import kst4contest.locatorUtils.DirectionUtils;
import kst4contest.logic.PriorityCalculator; import kst4contest.logic.PriorityCalculator;
import kst4contest.model.*; import kst4contest.model.*;
import kst4contest.test.MockKstServer; import kst4contest.test.MockKstServer;
import kst4contest.utils.BoundedDequeObservableList;
import kst4contest.utils.PlayAudioUtils; import kst4contest.utils.PlayAudioUtils;
import kst4contest.view.Kst4ContestApplication; import kst4contest.view.Kst4ContestApplication;
@@ -1007,7 +1008,8 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// ******All abstract types below here are used by the messageprocessor! // ******All abstract types below here are used by the messageprocessor!
// *************** // ***************
private ObservableList<ChatMessage> lst_globalChatMessageList = FXCollections.observableArrayList(); //All chatmessages will be put in there, later create filtered message lists private static final int MAX_CHAT_MESSAGES = 10000;
private final BoundedDequeObservableList<ChatMessage> lst_globalChatMessageList = new BoundedDequeObservableList<>(MAX_CHAT_MESSAGES); //All chatmessages will be put in there, later create filtered message lists
// private ObservableList<ChatMessage> lst_toAllMessageList = FXCollections.observableArrayList(); // directed to all // private ObservableList<ChatMessage> lst_toAllMessageList = FXCollections.observableArrayList(); // directed to all
// (beacon) // (beacon)
private FilteredList<ChatMessage> lst_toAllMessageList = new FilteredList<>(lst_globalChatMessageList); // directed to all private FilteredList<ChatMessage> lst_toAllMessageList = new FilteredList<>(lst_globalChatMessageList); // directed to all
@@ -1152,13 +1154,14 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
this.lst_selectedCallSignInfofilteredMessageList = lst_selectedCallSignInfofilteredMessageList; this.lst_selectedCallSignInfofilteredMessageList = lst_selectedCallSignInfofilteredMessageList;
} }
public void addChatMessage(ChatMessage message) {
lst_globalChatMessageList.addFirst(message);
}
public ObservableList<ChatMessage> getLst_globalChatMessageList() { public ObservableList<ChatMessage> getLst_globalChatMessageList() {
return lst_globalChatMessageList; return lst_globalChatMessageList;
} }
public void setLst_globalChatMessageList(ObservableList<ChatMessage> lst_globalChatMessageList) {
this.lst_globalChatMessageList = lst_globalChatMessageList;
}
public String getHostname() { public String getHostname() {
return hostname; return hostname;

View File

@@ -772,7 +772,7 @@ public class MessageBusManagementThread extends Thread {
dummy.setCallSign("ALL"); dummy.setCallSign("ALL");
newMessageArrived.setReceiver(dummy); newMessageArrived.setReceiver(dummy);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); // sdtout to all message-List this.client.addChatMessage(newMessageArrived); // sdtout to all message-List
} else { } else {
//message is directed to another chatmember, process as such! //message is directed to another chatmember, process as such!
@@ -817,7 +817,7 @@ public class MessageBusManagementThread extends Thread {
if (newMessageArrived.getReceiver().getCallSign() if (newMessageArrived.getReceiver().getCallSign()
.equals(this.client.getChatPreferences().getStn_loginCallSign())) { .equals(this.client.getChatPreferences().getStn_loginCallSign())) {
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); this.client.addChatMessage(newMessageArrived);
if (this.client.getChatPreferences().isNotify_playSimpleSounds()) { if (this.client.getChatPreferences().isNotify_playSimpleSounds()) {
this.client.getPlayAudioUtils().playNoiseLauncher('P'); this.client.getPlayAudioUtils().playNoiseLauncher('P');
@@ -960,7 +960,7 @@ public class MessageBusManagementThread extends Thread {
String originalMessage = newMessageArrived.getMessageText(); String originalMessage = newMessageArrived.getMessageText();
newMessageArrived newMessageArrived
.setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage); .setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage);
this.client.getLst_globalChatMessageList().add(0,newMessageArrived); this.client.addChatMessage(newMessageArrived);
// if you sent the message to another station, it will be sorted in to // if you sent the message to another station, it will be sorted in to
// the "to me message list" with modified messagetext, added rxers callsign // the "to me message list" with modified messagetext, added rxers callsign
@@ -1024,7 +1024,7 @@ public class MessageBusManagementThread extends Thread {
newMessageArrived.getSender().setInAngleAndRange(false); newMessageArrived.getSender().setInAngleAndRange(false);
} }
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); this.client.addChatMessage(newMessageArrived);
// System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign()); // System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign());
} }
} catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) { } catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) {
@@ -1364,7 +1364,7 @@ public class MessageBusManagementThread extends Thread {
dummy.setCallSign("ALL"); dummy.setCallSign("ALL");
newMessageArrived.setReceiver(dummy); newMessageArrived.setReceiver(dummy);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); // sdtout to all message-List this.client.addChatMessage(newMessageArrived); // sdtout to all message-List
} else { } else {
//message is directed to another chatmember, process as such! //message is directed to another chatmember, process as such!
@@ -1408,7 +1408,7 @@ public class MessageBusManagementThread extends Thread {
if (newMessageArrived.getReceiver().getCallSign() if (newMessageArrived.getReceiver().getCallSign()
.equals(this.client.getChatPreferences().getStn_loginCallSign())) { .equals(this.client.getChatPreferences().getStn_loginCallSign())) {
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); this.client.addChatMessage(newMessageArrived);
System.out.println("Historic message directed to me: " + newMessageArrived.getReceiver().getCallSign() + "."); System.out.println("Historic message directed to me: " + newMessageArrived.getReceiver().getCallSign() + ".");
@@ -1421,7 +1421,7 @@ public class MessageBusManagementThread extends Thread {
String originalMessage = newMessageArrived.getMessageText(); String originalMessage = newMessageArrived.getMessageText();
newMessageArrived newMessageArrived
.setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage); .setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage);
this.client.getLst_globalChatMessageList().add(0,newMessageArrived); this.client.addChatMessage(newMessageArrived);
// if you sent the message to another station, it will be sorted in to // if you sent the message to another station, it will be sorted in to
// the "to me message list" with modified messagetext, added rxers callsign // the "to me message list" with modified messagetext, added rxers callsign
@@ -1441,7 +1441,7 @@ public class MessageBusManagementThread extends Thread {
newMessageArrived.getSender().setInAngleAndRange(false); newMessageArrived.getSender().setInAngleAndRange(false);
} }
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); this.client.addChatMessage(newMessageArrived);
// System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign()); // System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign());
} }
} catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) { } catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) {
@@ -1514,7 +1514,7 @@ public class MessageBusManagementThread extends Thread {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
client.getLst_globalChatMessageList().add(pwErrorMsg); client.addChatMessage(pwErrorMsg);
// client.getLst_toMeMessageList().add(pwErrorMsg); // client.getLst_toMeMessageList().add(pwErrorMsg);
// client.getLst_toAllMessageList().add(pwErrorMsg); // client.getLst_toAllMessageList().add(pwErrorMsg);
} }

View File

@@ -0,0 +1,168 @@
package kst4contest.utils;
import javafx.collections.ObservableListBase;
import java.util.Arrays;
/**
* A bounded ObservableList backed by a circular buffer (ring buffer).
* <p>
* Provides O(1) {@link #addFirst} and {@link #addLast} as well as O(1)
* random access via {@link #get}. When the list reaches {@code maxCapacity},
* adding a new element at the front automatically evicts the oldest element
* at the back — and vice versa.
* <p>
* This is a drop-in replacement for {@code FXCollections.observableArrayList()}
* wherever elements are prepended frequently, e.g. chat message lists.
*/
public class BoundedDequeObservableList<E> extends ObservableListBase<E> {
private final int maxCapacity;
private final Object[] elements;
private int head = 0;
private int size = 0;
public BoundedDequeObservableList(int maxCapacity) {
if (maxCapacity <= 0) throw new IllegalArgumentException("maxCapacity must be > 0");
this.maxCapacity = maxCapacity;
this.elements = new Object[maxCapacity];
}
// ── read access ──────────────────────────────────────────────────────────
@Override
public int size() {
return size;
}
@Override
@SuppressWarnings("unchecked")
public E get(int index) {
checkIndex(index);
return (E) elements[physicalIndex(index)];
}
// ── O(1) deque operations ─────────────────────────────────────────────────
/**
* Inserts {@code element} at index 0 (newest-first order).
* If the list is already at capacity the oldest element (last index) is
* removed first — both changes are reported as a single compound change.
*/
public void addFirst(E element) {
beginChange();
if (size == maxCapacity) {
// evict last element
int lastPhysical = physicalIndex(size - 1);
@SuppressWarnings("unchecked")
E evicted = (E) elements[lastPhysical];
elements[lastPhysical] = null;
size--;
nextRemove(size, evicted); // index after decrement == old last index
}
head = (head - 1 + maxCapacity) % maxCapacity;
elements[head] = element;
size++;
nextAdd(0, 1);
endChange();
}
/**
* Appends {@code element} at the last index (oldest-first order).
* If the list is already at capacity the newest element (index 0) is
* removed first.
*/
public void addLast(E element) {
beginChange();
if (size == maxCapacity) {
// evict first element
@SuppressWarnings("unchecked")
E evicted = (E) elements[head];
elements[head] = null;
head = (head + 1) % maxCapacity;
size--;
nextRemove(0, evicted);
}
elements[physicalIndex(size)] = element;
size++;
nextAdd(size - 1, size);
endChange();
}
// ── standard List mutation (O(n) — use addFirst/addLast for hot path) ─────
@Override
public void add(int index, E element) {
if (index == 0) {
addFirst(element);
return;
}
if (index == size) {
addLast(element);
return;
}
checkIndexForAdd(index);
beginChange();
if (size == maxCapacity) {
int lastPhysical = physicalIndex(size - 1);
@SuppressWarnings("unchecked")
E evicted = (E) elements[lastPhysical];
elements[lastPhysical] = null;
size--;
nextRemove(size, evicted);
}
// shift elements [index .. size-1] one position towards the end
for (int i = size; i > index; i--) {
elements[physicalIndex(i)] = elements[physicalIndex(i - 1)];
}
elements[physicalIndex(index)] = element;
size++;
nextAdd(index, index + 1);
endChange();
}
@Override
public E remove(int index) {
checkIndex(index);
beginChange();
@SuppressWarnings("unchecked")
E removed = (E) elements[physicalIndex(index)];
// shift elements [index+1 .. size-1] one position towards the front
for (int i = index; i < size - 1; i++) {
elements[physicalIndex(i)] = elements[physicalIndex(i + 1)];
}
elements[physicalIndex(size - 1)] = null;
size--;
nextRemove(index, removed);
endChange();
return removed;
}
@Override
public E set(int index, E element) {
checkIndex(index);
beginChange();
@SuppressWarnings("unchecked")
E old = (E) elements[physicalIndex(index)];
elements[physicalIndex(index)] = element;
nextSet(index, old);
endChange();
return old;
}
// ── helpers ───────────────────────────────────────────────────────────────
private int physicalIndex(int virtualIndex) {
return (head + virtualIndex) % maxCapacity;
}
private void checkIndex(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
private void checkIndexForAdd(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}

View File

@@ -5382,7 +5382,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
FlowPane chatMemberTableFilterQRBHBox = new FlowPane(); FlowPane chatMemberTableFilterQRBHBox = new FlowPane();
chatMemberTableFilterQRBHBox.setAlignment(Pos.CENTER_LEFT); chatMemberTableFilterQRBHBox.setAlignment(Pos.CENTER_LEFT);
chatMemberTableFilterQRBHBox.setHgap(2); chatMemberTableFilterQRBHBox.setHgap(2);
chatMemberTableFilterQRBHBox.setPrefWidth(210); chatMemberTableFilterQRBHBox.setPrefWidth(225);
TextField chatMemberTableFilterMaxQrbTF = new TextField(chatcontroller.getChatPreferences().getStn_maxQRBDefault() + ""); TextField chatMemberTableFilterMaxQrbTF = new TextField(chatcontroller.getChatPreferences().getStn_maxQRBDefault() + "");
chatMemberTableFilterMaxQrbTF.setFocusTraversable(false); chatMemberTableFilterMaxQrbTF.setFocusTraversable(false);
@@ -5431,7 +5431,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
// HBox chatMemberTableFilterQTFHBox = new HBox(); // HBox chatMemberTableFilterQTFHBox = new HBox();
FlowPane chatMemberTableFilterQTFHBox = new FlowPane(); FlowPane chatMemberTableFilterQTFHBox = new FlowPane();
chatMemberTableFilterQTFHBox.setAlignment(Pos.CENTER_LEFT); chatMemberTableFilterQTFHBox.setAlignment(Pos.CENTER_LEFT);
chatMemberTableFilterQTFHBox.setPrefWidth(490); chatMemberTableFilterQTFHBox.setPrefWidth(525);
chatMemberTableFilterQTFHBox.setHgap(2); chatMemberTableFilterQTFHBox.setHgap(2);
CheckBox chatMemberTableFilterQtfEnableChkbx = new CheckBox("Show only QTF:"); CheckBox chatMemberTableFilterQtfEnableChkbx = new CheckBox("Show only QTF:");
@@ -6212,7 +6212,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
* *
****************************************************************************/ ****************************************************************************/
settingsStage = new Stage(); settingsStage = new Stage();
settingsStage.setTitle("Change Client seetings"); settingsStage.setTitle("Change Client Settings");
BorderPane optionsPanel = new BorderPane(); BorderPane optionsPanel = new BorderPane();

View File

@@ -1,5 +1,7 @@
module praktiKST { module praktiKST {
requires javafx.controls; requires javafx.controls;
requires javafx.fxml;
requires javafx.web;
requires jdk.xml.dom; requires jdk.xml.dom;
requires java.sql; requires java.sql;
requires javafx.media; requires javafx.media;

File diff suppressed because it is too large Load Diff