Author SHA1 Message Date
Marc Froehlich b00e4c1e3d fixed worked-flag in the qso-of-the-other-table 2026-08-07 23:43:04 +02:00
Marc Froehlich b3ca684f04 updated website for sked and timeline features 2026-08-07 23:38:13 +02:00
Marc Froehlich 1de9673d12 uploaded 2 manual pictures for sked and timeline 2026-08-07 23:34:57 +02:00
Marc Froehlich c119f28b30 Uploaded sked, timeline, priority candidates and wintest connection manuals 2026-08-07 23:13:40 +02:00
Marc Froehlich 6a7f07c62e fix(wintest): replace unreliable band-limited AUTO sked mode detection with explicit SSB/CW selection and show exact KST callsigns in the timeline and fixed (#50 ensured Filter-Reset) 2026-08-07 22:56:24 +02:00
Marc Froehlich 4f574ebec6 fix(wintest): add band-aware sked QRG resolution, preserve portable callsigns, strip KST suffixes and correct ADDSKED timestamps 2026-08-07 22:43:17 +02:00
Marc Froehlich 7ce31e110b fix(wintest): add band-aware sked QRG resolution, preserve portable callsigns, strip KST suffixes and correct ADDSKED timestamps 2026-08-07 22:28:51 +02:00
Marc Froehlich 92804a622a Corrected typo error in MessagebusmanagementThread. Solves (#48) 2026-08-07 22:08:19 +02:00
Marc Froehlich 8bd5d8877d Add persistent map analysis toggle and improve compact layout for smaller screens. Solves (#69) 2026-08-06 01:52:59 +02:00
Marc Froehlich 97a93f726b Added en-manual for priority score and preferences + website feature text changed 2026-08-06 00:53:17 +02:00
Marc Froehlich c682ecf3f9 Changed website manual navigation 2026-08-06 00:46:00 +02:00
Marc Froehlich 259d0a4916 added priority score manual 2026-08-06 00:42:44 +02:00
Marc Froehlich 6d65bc365c fixed scoreservice. Not-QRV stations and such which dont provide needed bands now never could appear at the prirotity list 2026-08-06 00:26:29 +02:00
Marc Froehlich 084923366f fix(chat): grouped calculation of priority score for equal raw callsigns with different suffixes (also fixes #73) 2026-08-06 00:18:36 +02:00
Marc Froehlich eb38268be5 fix(chat): separate active members by full callsign and category 2026-08-06 00:01:29 +02:00
Marc Froehlich 36d2bd512d updated english manuals for changes from 1.40 due to 1.42 2026-08-05 23:20:11 +02:00
Marc Froehlich ffe7343671 inserted screenshots for manual pages for wkd status infos, band settings, functions and settings 2026-08-05 22:58:34 +02:00
30 changed files with 2541 additions and 666 deletions
+42 -3
View File
@@ -33,7 +33,7 @@ Die zentrale Tabelle aller aktuell aktiven Chat-Nutzer. Spalten (je nach Konfigu
| QTF | Richtung in Grad | | QTF | Richtung in Grad |
| QRG | Zuletzt aus einer Chat-Nachricht erkannte Frequenz | | QRG | Zuletzt aus einer Chat-Nachricht erkannte Frequenz |
| Tropo | Ergebnis der bandbezogenen Tropo- beziehungsweise Streckenbewertung | | Tropo | Ergebnis der bandbezogenen Tropo- beziehungsweise Streckenbewertung |
| Score | Aktueller Prioritätswert | | Score | Aktueller, numerisch sortierbarer Prioritätswert des normalisierten Basisrufzeichens |
| Act | Minuten seit der letzten Aktivität | | Act | Minuten seit der letzten Aktivität |
| AP | AirScout-Flugzeugdaten, sofern aktiviert | | AP | AirScout-Flugzeugdaten, sofern aktiviert |
| worked | Bandbezogener Worked-, Bandmöglichkeits- und Großfeldstatus sowie `wkdany` | | worked | Bandbezogener Worked-, Bandmöglichkeits- und Großfeldstatus sowie `wkdany` |
@@ -128,13 +128,52 @@ Die Änderung wirkt sofort auf die Spalte **NOT QRV @**, die Bandmöglichkeiten
![Bandbezogene NOT-QRV-Markierungen im Further-Info-Bereich](not_qrv_controls.png) ![Bandbezogene NOT-QRV-Markierungen im Further-Info-Bereich](not_qrv_controls.png)
Hier können auch **Sked-Erinnerungen / Wecker** für beide Skkedpartner aktiviert werden. Im selben Bereich wird der aktuelle **Priority score** der ausgewählten Station angezeigt.
Mit **Sked fail** lässt sich ein fehlgeschlagener Versuch markieren. Der Score des normalisierten Basisrufzeichens wird dadurch stark reduziert. **Reset fail** entfernt diese Markierung wieder. Die Markierung gilt für alle aktiven Suffix- und Kategorievarianten der Station und bleibt innerhalb der laufenden Programmsitzung erhalten.
Darunter befinden sich die Bedienelemente zum Anlegen eines Skeds:
| Bedienelement | Bedeutung |
|---|---|
| **Sked in** | Zeit bis zum Sked |
| **Band** | vereinbartes Band aus den eigenen aktivierten Bändern |
| **Mode** | `SSB` oder `CW` für eine mögliche Win-Test-Übergabe |
| **Create sked** | internen Sked anlegen |
| **Remind-PM in** | automatische Reminder-PMs aktivieren |
| **2+1**, **5+2+1**, **10+5+2+1** | Zeitpunkte der Reminder-PMs vor dem Termin |
![Sked-Steuerung im Further-Info-Bereich](sked_controls.png)
Das vorgeschlagene Band wird aus aktuellen QRG- und Namensinformationen der Station hergeleitet. Vor dem Anlegen kann es ausdrücklich geändert werden. Die Mode-Auswahl betrifft nur die Übergabe an Win-Test; der interne Sked und die Reminder-PMs funktionieren unabhängig davon.
**Create sked** legt den Termin immer zuerst in KST4Contest an. Ist der Win-Test-Netzwerk-Listener aktiv, wird anschließend zusätzlich eine Übergabe an Win-Test versucht. Kann keine zum ausgewählten Band passende QRG ermittelt werden oder ist Win-Test nicht erreichbar, bleiben der interne Sked, seine Priorisierung und gegebenenfalls angelegte Reminder erhalten.
Die vollständige Herleitung und die Grenzen der Funktion sind unter [Skeds und Sked-Erinnerungen](de-Funktionen#skeds-und-sked-erinnerungen) beschrieben.
--- ---
## Prioritätsliste ## Prioritätsliste
Zeigt die vom Score-Service berechneten Top-Kandidaten. Aktualisiert sich automatisch im Hintergrund basierend auf Richtung, Entfernung und AP-Verfügbarkeit. Die kompakte Prioritätsleiste befindet sich rechts zwischen Benutzerliste und Further-Info-Bereich. Sie zeigt die beiden derzeit höchstbewerteten Kandidaten unmittelbar im Hauptfenster:
```text
Priority: 1 RUFZEICHEN SCORE 2 RUFZEICHEN SCORE more
```
Ein Klick auf einen der beiden Kandidaten wählt den dazugehörigen aktiven Chatmember aus. Dabei werden das vollständige Rufzeichen einschließlich Suffix und die zugehörige Chat-Kategorie verwendet.
Die Schaltfläche **more** öffnet ein separates Fenster mit bis zu 15 Kandidaten. Die Liste ist nach absteigendem Score sortiert. Ein Doppelklick wählt den betreffenden Kandidaten aus und schließt das Fenster.
![Priority Score, kompakte Kandidatenliste und Further-Info-Steuerung](priority_score_overview.png)
Stationen mit einem Score von `0` werden nicht in die Prioritätsliste aufgenommen. In der Benutzerliste bleiben sie sichtbar, sodass der Ausschluss nachvollzogen und beispielsweise durch eine geänderte NOT-QRV-Markierung korrigiert werden kann.
Der Score wird für das normalisierte Basisrufzeichen berechnet. Mehrere aktive Varianten wie `9A0BB-2` und `9A0BB-70` können daher in der Benutzerliste denselben Wert anzeigen. Die Chatmember bleiben trotzdem getrennte Nachrichtenziele.
Neue Nachrichten, AirScout-Daten, Skeds und Statusänderungen lösen eine Neuberechnung aus. Zusätzlich erfolgt eine regelmäßige Aktualisierung im Hintergrund. Eine kurzzeitig noch nicht angepasste Reihenfolge ist deshalb kein Fehler.
Herleitung und Grenzen: [Prioritätsscore und Prioritätsliste](de-Funktionen#prioritätsscore-und-prioritätsliste-ab-v140).
--- ---
+237 -33
View File
@@ -313,19 +313,86 @@ Für ausgewählte Stationen in der Benutzerliste gibt es direkte Buttons, um das
--- ---
## Sked-Erinnerungen mit ALERT (ab v1.40) ## Skeds und Sked-Erinnerungen
Für jeden Chatmember kann ein Sked-Erinnerungsdienst mit automatischen Nachrichten aktiviert werden. Konfigurierbare Intervallmuster: > Verfügbar ab v1.40; Band-, Rufzeichen- und Win-Test-Behandlung erweitert in Nightly / v1.42.
- **2+1 Minuten**: Nachrichten bei 2 min und 1 min vor dem Sked. Ein Sked ist mehr als eine Erinnerung an eine Uhrzeit. Er muss während des laufenden Contestbetriebs rechtzeitig sichtbar werden, die vereinbarte Station priorisieren und sofern gewünscht die Gegenstation noch einmal an den Termin erinnern.
- **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. KST4Contest behandelt deshalb drei voneinander unabhängige Aufgaben:
Aktivierung: FurtherInfo-Panel der entsprechenden Station. 1. Der Sked wird intern gespeichert und in die Prioritätsberechnung einbezogen.
2. Der Termin erscheint in der AP- und Sked-Timeline.
3. Optional werden vor dem Termin automatische Privatnachrichten gesendet.
Ist der Win-Test-Netzwerk-Listener aktiviert, versucht KST4Contest zusätzlich, den Sked an Win-Test zu übergeben. Ein Problem bei dieser Übergabe löscht oder verhindert den internen Sked nicht.
### Sked anlegen
Zuerst die gewünschte Station in der Benutzerliste auswählen. Die Bedienelemente befinden sich anschließend unten im Bereich **Further Info**.
| Bedienelement | Funktion |
|---|---|
| **Sked in** | Legt fest, in wie vielen Minuten der Sked stattfinden soll. Verfügbar sind 2 bis 15 sowie 20 Minuten. |
| **Band** | Wählt das Band des Skeds. Angeboten werden die unter **Station → my station uses …** aktivierten eigenen Bänder. |
| **Mode** | Legt den an Win-Test zu übertragenden Mode fest. Verfügbar sind `SSB` und `CW`. Die Auswahl hat keinen Einfluss auf den internen Sked oder die Reminder-PMs. |
| **Create sked** | Legt den internen Sked an und versucht bei aktiviertem Win-Test-Netzwerk-Listener zusätzlich die Übergabe an Win-Test. |
| **Remind-PM in** | Aktiviert die automatischen Privatnachrichten vor dem Termin. |
| **2+1**, **5+2+1**, **10+5+2+1** | Legt fest, wie viele Minuten vor dem Sked die Reminder-PMs gesendet werden. |
![Sked-Steuerung im Further-Info-Bereich](sked_controls.png)
KST4Contest versucht, ein sinnvolles Band vorzuwählen. Dafür werden nacheinander folgende Informationen verwendet:
1. eine höchstens 30 Minuten alte QRG der ausgewählten Station auf einem eigenen aktivierten Band,
2. eine eindeutige Bandangabe im Namensfeld der Station und
3. das erste aktivierte eigene Band.
Aktive Rufzeichenvarianten desselben Basisrufzeichens werden bei der Suche nach einer aktuellen Bandinformation gemeinsam betrachtet. Eine manuelle NOT-QRV-Markierung wird bei der automatischen Vorauswahl berücksichtigt. Das Band kann trotzdem ausdrücklich geändert werden, wenn der Operator bewusst eine andere Vereinbarung getroffen hat.
### Auswirkung auf den Priority Score
Ein eingetragener Sked erhöht den Score des normalisierten Basisrufzeichens:
| Zeitraum | Sked-Anteil am Score |
|---|---:|
| mehr als 15 Minuten vor dem Termin | `+40` |
| 15 bis 3 Minuten vor dem Termin | kontinuierlicher Anstieg von `+300` bis in Richtung `+1200` |
| weniger als 3 Minuten vor bis 1 Minute nach dem Termin | `+5000` |
| später als 1 Minute nach dem Termin | kein Sked-Boost mehr |
Die starke Gewichtung unmittelbar vor dem Termin ist beabsichtigt. Ein vereinbarter Sked soll dann nicht durch eine gerade sehr aktive, aber nicht fest eingeplante Station aus der Prioritätsliste verdrängt werden.
Der Score wird für das Basisrufzeichen berechnet. Ein Sked mit `DN9APW-2` beeinflusst daher auch den gemeinsamen Score weiterer aktiver Varianten von `DN9APW`. Das konkrete Nachrichtenziel bleibt trotzdem `DN9APW-2` in der beim Anlegen ausgewählten Chat-Kategorie.
Fünf Minuten nach dem Termin wird der Sked aus der internen Liste entfernt.
### Reminder-PMs
Reminder-PMs werden nur angelegt, wenn **Remind-PM in** aktiviert ist. Je nach ausgewähltem Muster sendet KST4Contest beispielsweise zwei und eine Minute vor dem Sked folgende Privatnachricht:
```text
[KST4C Autoreminder] sked in 2 min
```
Die Nachricht geht an das vollständige sichtbare KST-Rufzeichen und in die Chat-Kategorie, in der der Sked angelegt wurde. Ein Sked für `DN9APW-2` wird daher nicht versehentlich an `DN9APW`, `DN9APW-70` oder eine gleichnamige Station in einer anderen Kategorie gesendet.
Beim tatsächlichen Reminder zeigt KST4Contest zusätzlich den optischen **SKED**-Hinweis an. Ist die einfache Soundausgabe aktiviert, wird außerdem ein Hinweiston abgespielt. Das bloße Aktivieren des Reminders löst noch kein Blinken aus.
Wird für dasselbe vollständige Rufzeichen ein neuer Satz Reminder aktiviert, ersetzt dieser die zuvor geplanten Reminder dieses Rufzeichens.
### Speicherung und Grenzen
Skeds und Reminder-Zeitpläne werden nur im Arbeitsspeicher geführt. Nach einem Neustart von KST4Contest müssen noch benötigte Termine erneut angelegt werden.
Die automatische Bandvorauswahl ist eine Herleitung aus vorhandenen Chatinformationen. Sie beweist nicht, dass die Station noch auf der zuletzt genannten QRG arbeitet. Band, Uhrzeit und Mode sollten deshalb vor **Create sked** kontrolliert werden.
Bedienung: [Stationsinfo-Panel](de-Benutzeroberflaeche#stationsinfo-panel-further-info)
Darstellung: [AP- und Sked-Timeline](#ap-und-sked-timeline)
Win-Test-Übergabe: [Log-Synchronisation Win-Test](de-Log-Synchronisation#win-test)
---
## QSO-Monitoring (ab v1.31) ## QSO-Monitoring (ab v1.31)
@@ -337,17 +404,23 @@ Konfiguration: [Konfiguration Sniffer-Einstellungen](de-Konfiguration#sniffe
--- ---
## Win-Test-Integration (ab v1.31, vollständig ab v1.40) ## Win-Test-Integration
KST4Contest unterstützt [Win-Test](https://www.win-test.com/) vollständig als Logprogramm: KST4Contest verwendet für Win-Test einen eigenen Listener für das native Win-Test-Netzwerkprotokoll. Darüber werden drei voneinander getrennte Funktionen bereitgestellt:
- **Log-Synchronisation**: Gearbeitete Stationen werden automatisch aus Win-Test übernommen und in der Benutzerliste markiert. - neue QSOs einschließlich Band- und gegebenenfalls Locatorinformation übernehmen,
- **Frequenz-Auswertung**: Die aktuelle TRX-Frequenz wird aus Win-Test-UDP-Paketen ausgewertet und befüllt die `MYQRG`-Variable. - die aktuelle QRG aus Win-Test-STATUS-Paketen auswerten und
- **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. - intern angelegte Skeds als `ADDSKED` an das Win-Test-Netzwerk übergeben.
Bei der Sked-Übergabe wird die QRG nicht durch eine feste Standardfrequenz ersetzt. KST4Contest sendet nur dann einen Win-Test-Sked, wenn eine zum ausgewählten Band passende QRG ermittelt werden konnte. Der interne Sked, die Timeline und die Reminder-PMs funktionieren unabhängig davon weiter.
Ein sichtbarer KST-Suffix wie `-2`, `-70` oder `-144` bleibt innerhalb von KST4Contest erhalten, wird für das Win-Test-Logrufzeichen jedoch entfernt. Portable Bestandteile wie `/P`, `/M` oder ein Länderpräfix bleiben bestehen.
Einrichtung und genaue Datenbehandlung: [Log-Synchronisation Win-Test](de-Log-Synchronisation#win-test)
Einstellungen: [Win-Test-Netzwerk-Listener](de-Konfiguration#win-test-netzwerk-listener-ab-v131)
Details zur Konfiguration: [Konfiguration Win-Test-Netzwerk-Listener](de-Konfiguration#win-test-netzwerk-listener)
---
## PSTRotator-Interface (ab v1.31, vollständig ab v1.40) ## PSTRotator-Interface (ab v1.31, vollständig ab v1.40)
@@ -372,40 +445,171 @@ Konfiguration: [Band-Upgrade-Hinweis nach einem Logeintrag](de-Konfiguration#ban
Worked-, NOT-QRV- und Großfelddaten laufen nach drei Tagen automatisch ab. Einzelheiten und manueller Reset: [Worked Station Database Settings](de-Konfiguration#worked-station-database-settings-gearbeitete-stationen-datenbank). Worked-, NOT-QRV- und Großfelddaten laufen nach drei Tagen automatisch ab. Einzelheiten und manueller Reset: [Worked Station Database Settings](de-Konfiguration#worked-station-database-settings-gearbeitete-stationen-datenbank).
--- ---
## Chatmember Score-System / Prioritätsliste (ab v1.40) ## Prioritätsscore und Prioritätsliste (ab v1.40)
KST4Contest berechnet automatisch eine **Prioritätsbewertung** für jeden aktiven Chatmember. Der Score setzt sich zusammen aus: ### Warum wird überhaupt ein Score benötigt?
- Antennenrichtung der Gegenstation (zeigt sie auf mich?) Eine klassische Chat-Benutzerliste zeigt zunächst nur, welche Stationen gerade eingeloggt sind. Im Contestbetrieb reicht diese Information nicht aus. Der Operator muss zusätzlich abschätzen, welche Station noch nicht gearbeitet wurde, auf welchem Band ein QSO möglich sein könnte, wohin die Antenne zeigt, ob ein passendes Flugzeug verfügbar ist und ob ein vereinbarter Sked unmittelbar bevorsteht.
- QRB (Entfernung)
- Aktivitätszeit und Nachrichtenanzahl
- Aktive Bänder und Frequenzen
- AP-Verfügbarkeit (AirScout)
- Sked-Richtung
- Sked-Erfolgsrate und Skedfail-Markierungen
Die Top-Kandidaten werden in einer eigenen Prioritätsliste hervorgehoben und helfen, im Contest-Stress die wichtigsten Stationen nicht zu übersehen. Bei einer kurzen Liste lässt sich das noch im Kopf erledigen. Mit zunehmender Contestdauer, mehreren Bändern und zwei gleichzeitig verwendeten Chat-Kategorien wird daraus jedoch eine ständig wiederholte Entscheidung.
Stationen, bei denen ein Sked gescheitert ist, können über den **Skedfail-Button** im FurtherInfo-Panel markiert werden das senkt ihren Score vorübergehend. KST4Contest führt die bereits vorhandenen Informationen deshalb in einem Prioritätsscore zusammen. Der Score beantwortet nicht die Frage, ob ein QSO sicher möglich ist. Er hilft bei der praktisch wichtigeren Frage:
> Welche der aktuell sichtbaren Stationen sollte ich mir als Nächstes ansehen?
### Wann wird eine Station ausgeschlossen?
Vor der eigentlichen Gewichtung prüft KST4Contest, ob überhaupt eine bekannte Bandmöglichkeit besteht. Dafür werden alle aktiven Chat-Einträge desselben normalisierten Basisrufzeichens gemeinsam ausgewertet.
Berücksichtigt werden:
1. die in den Stationseinstellungen aktivierten eigenen Bänder,
2. höchstens 30 Minuten alte QRG-Erkennungen der Gegenstation,
3. eindeutige Bandangaben im Namensfeld ihrer aktiven Chat-Einträge,
4. die pro Band gespeicherten Worked-Markierungen und
5. manuell gesetzte NOT-QRV-Tags.
NOT QRV hat dabei Vorrang vor automatisch erkannten Frequenzen oder Bandangaben.
Sind Bänder der Gegenstation bekannt, aber keines davon ist lokal aktiviert und noch verfügbar, erhält die Station einen Score von `0`. Dasselbe gilt, wenn alle gemeinsam möglichen Bänder bereits gearbeitet wurden.
Fehlen dagegen sämtliche Bandinformationen, wird die Station nicht allein deshalb ausgeschlossen. Eine unbekannte Bandmöglichkeit ist nicht dasselbe wie eine nachweislich unmögliche Bandmöglichkeit. Erst wenn alle eigenen aktivierten Bänder für die Station manuell als NOT QRV markiert wurden, ist auch in diesem Fall keine aktuelle Contestmöglichkeit mehr vorhanden.
Stationen mit einem Score von `0` bleiben in der Benutzerliste sichtbar, erscheinen aber nicht in der Prioritätsliste.
### Welche Informationen erhöhen oder verringern den Score?
Der Score entsteht aus mehreren voneinander unabhängigen Hinweisen. Ein einzelnes Kriterium entscheidet daher normalerweise nicht über den endgültigen Listenplatz.
| Faktor | Wirkung auf die Priorisierung |
|---|---|
| Worked-Status | Ein noch auf keinem unterstützten Band gearbeitetes Rufzeichen erhält eine höhere Ausgangspriorität. Bereits gearbeitete Stationen werden niedriger bewertet, bleiben bei offenen Bandmöglichkeiten aber Kandidaten. |
| Verfügbare Bänder | Mehrere gemeinsam nutzbare und noch nicht gearbeitete Bänder erhöhen die Priorität. Optional kann für Band-Upgrades ein zusätzlicher Boost aktiviert werden. |
| Entfernung | Entfernungen unter 200 km werden niedriger gewichtet. Der Bereich zwischen 200 km und dem konfigurierten maximalen QRB wird bevorzugt. Stationen jenseits des maximalen QRB werden deutlich herabgestuft. Fehlt der QRB, entfällt dieser Faktor. |
| Antennenrichtung | Liegt der QTF zur Gegenstation innerhalb der Hälfte des konfigurierten Antennen-Öffnungswinkels um den aktuellen eigenen QTF, steigt der Score. Je näher die Richtungen zusammenliegen, desto stärker wirkt der Hinweis. |
| AirScout | Mindestens ein aktuell erreichbares Flugzeug erhöht den Score. Eine erwartete AP-Gelegenheit in null, einer oder zwei Minuten wird zusätzlich zeitlich gewichtet. |
| Aktuelle Chat-Aktivität | Eine Nachricht innerhalb der letzten Minute wirkt stärker als eine Nachricht innerhalb der letzten drei Minuten. Mehrere eingehende Zeilen innerhalb des Aktivitätsfensters erhöhen den Score zusätzlich. |
| Positive Signale | Erkannte Angaben wie `QRV`, `READY`, `RGR`, `OK`, `TNX` oder vergleichbare konfigurierte Textmuster werden für einige Minuten als positiver Hinweis berücksichtigt. |
| Antwortverhalten | Reagiert eine Station nach einer eigenen `/cq`-Nachricht schnell mit einer weiteren sichtbaren Chat-Zeile, wirkt sich die gemittelte Reaktionszeit positiv aus. Bleibt eine solche Zeile aus, entsteht nach dem konfigurierten Timeout eine negative Bewertung. |
| Skeds | Ein eingetragener Sked erhöht die Priorität zunächst leicht. Innerhalb der letzten 15 Minuten vor dem Termin steigt der Einfluss kontinuierlich an. Zwischen drei Minuten vor und einer Minute nach dem Termin erhält der Sked eine sehr hohe Priorität. |
| Fehlgeschlagener Versuch | **Sked fail** reduziert den Score der Station stark, bis die Markierung mit **Reset fail** zurückgesetzt oder KST4Contest neu gestartet wird. |
Das standardmäßige Aktivitätsfenster für die Anzahl eingehender Nachrichten beträgt 180 Sekunden. Eine aktuelle Nachricht innerhalb der letzten 60 Sekunden wird noch einmal gesondert bewertet. Der standardmäßige No-Reply-Timeout beträgt 13 Minuten.
Beim Antwortverhalten kann KST4Contest nicht sicher feststellen, ob eine nachfolgende öffentliche oder private Nachricht tatsächlich die Antwort auf die eigene Anfrage war. Jede anschließend empfangene Zeile derselben Station beendet deshalb den laufenden Antwortzeitversuch. Der Wert ist eine praktische Näherung und keine statistisch belastbare Antwortquote.
### Was bedeutet ein eingetragener Sked?
Ein Sked ist eine zeitlich vereinbarte Arbeitsaufgabe. Deshalb übersteuert ein unmittelbar bevorstehender Sked die meisten normalen Aktivitäts- und Entfernungshinweise. Ohne diese Gewichtung könnte eine gerade sehr aktive Station einen vereinbarten Termin aus der Prioritätsliste verdrängen.
Der starke Sked-Boost ist absichtlich auf den Zeitraum von drei Minuten vor bis eine Minute nach dem eingetragenen Termin begrenzt. Ein weiter in der Zukunft liegender Sked bleibt sichtbar, soll den laufenden Betrieb aber noch nicht dominieren.
Die Bewertung wird für das normalisierte Basisrufzeichen vorgenommen. Ein Sked für eine aktive Variante wie `9A0BB-23` beeinflusst daher den gemeinsamen Score der zu `9A0BB` gehörenden Chat-Einträge.
### Wie werden mehrere SSIDs und Chat-Kategorien behandelt?
Aktive Rufzeichen wie `9A0BB-2`, `9A0BB-70`, `9A0BB-23` und `9A0BB-13` bleiben getrennte Chatmember. Dadurch können Nachrichten weiterhin an das vollständige Rufzeichen und die richtige Chat-Kategorie adressiert werden.
Worked-, Band-, NOT-QRV- und Score-Informationen beziehen sich dagegen auf das gemeinsame Basisrufzeichen `9A0BB`. Der Score wird deshalb einmal berechnet und auf alle aktiven Varianten übertragen. Die Benutzerliste kann mehrere getrennte Zeilen mit demselben Score enthalten; in der Prioritätsliste erscheint das Basisrufzeichen nur einmal.
Als konkretes Nachrichtenziel verwendet KST4Contest den zuletzt passenden aktiven Login in der zuletzt verwendeten Chat-Kategorie. Beim Anklicken eines Kandidaten wird anschließend das vollständige Rufzeichen einschließlich Suffix und Kategorie ausgewählt.
### Aktualisierung und Anzeige
Änderungen durch neue Nachrichten, AirScout-Daten, Skeds, Worked-Informationen oder manuelle NOT-QRV- und Sked-fail-Markierungen fordern unmittelbar eine Neuberechnung an. Zusätzlich wird der Score regelmäßig im Hintergrund aktualisiert, weil Aktivitäts-, AP- und Sked-Informationen auch ohne neues Ereignis altern.
Eine kurze Verzögerung von einigen Sekunden zwischen einem Ereignis und der sichtbaren neuen Reihenfolge ist daher normal.
Die Benutzeroberfläche zeigt den Score an drei Stellen:
- als numerisch sortierbare Spalte **Score** in der Benutzerliste,
- für die ausgewählte Station im Bereich **Further Info** und
- als kompakte Liste der beiden derzeit höchstbewerteten Kandidaten mit einem zusätzlichen Fenster für bis zu 15 Kandidaten.
Bedienung: [Prioritätsliste in der Benutzeroberfläche](de-Benutzeroberflaeche#prioritätsliste).
### Was sagt der Score nicht aus?
Der Zahlenwert ist weder eine Erfolgswahrscheinlichkeit noch eine Signalprognose. Ein doppelt so hoher Score bedeutet nicht, dass ein QSO doppelt so wahrscheinlich ist.
Die Berechnung kennt unter anderem nicht:
- die tatsächliche Antennenrichtung der Gegenstation,
- deren momentane Betriebssituation,
- lokale Störungen,
- kurzfristige Ausbreitungsänderungen,
- Geländeabschattungen außerhalb der jeweils angebundenen Funktionen oder
- die Frage, ob eine im Chat aktive Station tatsächlich gerade am Funkgerät sitzt.
Auch bekannte Eingabedaten können veraltet oder missverständlich sein. Eine erkannte Frequenz belegt beispielsweise nur, dass diese QRG kürzlich im Zusammenhang mit der Station aufgetreten ist.
Im Klartext: Der Score ersetzt nicht die Entscheidung des Operators. Er sorgt dafür, dass die dafür bereits vorhandenen Informationen nicht bei jedem Kandidaten erneut im Kopf zusammengesucht werden müssen.
Zugehörige Einstellungen:
- [Aktivierte Bänder](de-Konfiguration#aktivierte-bänder)
- [Antennen-Öffnungswinkel](de-Konfiguration#antennen-öffnungswinkel-antenna-beamwidth)
- [Standard-Maximum-QRB](de-Konfiguration#standard-maximum-qrb)
- [AirScout-Einstellungen](de-Konfiguration#airscout-einstellungen)
- [Band-Upgrade-Hinweis und Priority Boost](de-Konfiguration#band-upgrade-hinweis-nach-einem-logeintrag)
--- ---
## AP-Timeline (ab v1.40) ## AP- und Sked-Timeline
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: Die Timeline stellt bevorstehende Aircraft-Scatter-Gelegenheiten und eingetragene Skeds für die nächsten 30 Minuten gemeinsam dar. Sie beantwortet damit zwei Fragen auf einen Blick:
- Bevorzugt werden APs mit dem **höchsten Reflexionspotenzial** (nicht unbedingt die schnellste Ankunft). - Wann entsteht voraussichtlich eine interessante AP-Gelegenheit?
- Stationen, auf die die eigene Antenne nicht zeigt, werden **transparent** dargestellt. - Welcher bereits vereinbarte Sked nähert sich unabhängig davon?
So kann der Contest-Operator auf einem Blick sehen, welche Stationen wann und über welche Flugzeuge erreichbar sein werden. Weiter in der Zukunft liegende Ereignisse erscheinen rechts. Mit ablaufender Zeit wandern sie nach links in Richtung des aktuellen Zeitpunkts.
--- ![AP-Kandidaten und Skeds in der Timeline](sked_timeline.png)
### AP-Kandidaten
AP-Kandidaten erscheinen in den oberen Spuren. Pro Ankunftsminute können bis zu vier ausgewählte Kandidaten dargestellt werden. Die Auswahl berücksichtigt den Priority Score und das von AirScout gemeldete Reflexionspotenzial.
Die Farbe des AP-Symbols kennzeichnet das Reflexionspotenzial:
| Farbe | Reflexionspotenzial |
|---|---:|
| Magenta | mindestens 95 % |
| Rot | mindestens 75 % |
| Gelb | mindestens 50 % |
| Blau | unter 50 % |
Die Farbe ist keine QSO-Wahrscheinlichkeit. Sie gibt den von AirScout übernommenen Wert für die berechnete Reflexionsgeometrie wieder.
Ein Klick auf einen AP-Kandidaten wählt den dazugehörigen aktiven Chatmember einschließlich Rufzeichensuffix und Chat-Kategorie aus. Dadurch kann unmittelbar eine passende Nachricht vorbereitet werden.
### Skeds
Skeds erscheinen als Rauten in der unteren Spur. Die Beschriftung verwendet das vollständige KST-Rufzeichen, beispielsweise `SKED: DN9APW-2`. Dadurch bleibt erkennbar, welcher konkrete Login für den Termin ausgewählt wurde.
Der Tooltip eines Skeds zeigt mindestens:
- das vollständige KST-Rufzeichen,
- das vereinbarte Band und
- den QTF zur Gegenstation.
Sind passende AirScout-Daten vorhanden, werden zusätzlich die aktuelle AP-Erreichbarkeit und die nächste berechnete AP-Gelegenheit angezeigt.
### Berücksichtigung der Antennenrichtung
Liegt der QTF eines Ereignisses deutlich außerhalb der aktuellen Antennenrichtung, wird dessen Symbol transparenter dargestellt. Die Beschriftung bleibt lesbar. Liegt das Ziel nahe der Mitte des konfigurierten Antennenbereichs, wird das Symbol zusätzlich hervorgehoben.
Diese Darstellung verändert weder den Sked noch den Priority Score. Sie ist eine optische Hilfe, um Kandidaten in der aktuellen Antennenrichtung schneller zu erkennen.
Die Timeline ist eine Vorschau. AirScout-Daten können sich ändern, und ein eingetragener Sked garantiert weder eine freie Frequenz noch eine tatsächlich vorhandene Ausbreitungsverbindung.
## Intervall-Beacon ## Intervall-Beacon
KST4Contest kann wiederkehrende CQ-Nachrichten in den öffentlichen Chat senden. Beide Chat-Kategorien verwenden ein gemeinsames Intervall, besitzen aber jeweils einen eigenen Aktivierungsschalter und Nachrichtentext. Globale Variablen wie `MYQRG`, `SECONDQRG` oder `MYLOCATOR` werden unmittelbar vor jeder Aussendung aktualisiert. KST4Contest kann wiederkehrende CQ-Nachrichten in den öffentlichen Chat senden. Beide Chat-Kategorien verwenden ein gemeinsames Intervall, besitzen aber jeweils einen eigenen Aktivierungsschalter und Nachrichtentext. Globale Variablen wie `MYQRG`, `SECONDQRG` oder `MYLOCATOR` werden unmittelbar vor jeder Aussendung aktualisiert.
Der Beacon ist für längeres CQ-Rufen auf einer festen Frequenz gedacht. Beim Absuchen oder häufigen Wechseln der QRG sollte er ausgeschaltet werden, damit keine inzwischen falsche Frequenz verbreitet wird. Details: [Konfiguration Beacon Settings](de-Konfiguration#beacon-settings-automatischer-beacon). Der Beacon ist für längeres CQ-Rufen auf einer festen Frequenz gedacht. Beim Absuchen oder häufigen Wechseln der QRG sollte er ausgeschaltet werden, damit keine inzwischen falsche Frequenz verbreitet wird. Details: [Konfiguration Beacon Settings](de-Konfiguration#beacon-settings-automatischer-beacon).
--- ---
## Simplelogfile ## Simplelogfile
@@ -418,7 +622,7 @@ Dateibasierte Log-Auswertung per Regex. Details: [Log-Synchronisation](Log-Synch
Ein separates Fenster zeigt den QSO-Fluss zwischen anderen Stationen. Besonders interessant in ruhigeren Nacht-Stunden während des Contests, wenn weniger Verkehr herrscht. Ein separates Fenster zeigt den QSO-Fluss zwischen anderen Stationen. Besonders interessant in ruhigeren Nacht-Stunden während des Contests, wenn weniger Verkehr herrscht.
Dieses Fenster kann miniaturisiert werden, wenn es nicht benötigt wird. Zukünftig geplant: Filterung auf Stationen im ausgewählten QTF. Dieses Fenster kann minimiert werden, wenn es nicht benötigt wird. Zukünftig geplant: Filterung auf Stationen im ausgewählten QTF.
--- ---
+42 -11
View File
@@ -186,7 +186,13 @@ Bleibt mindestens ein gemeinsames, noch nicht gearbeitetes Band übrig, erschein
Die beiden Optionen haben unterschiedliche Aufgaben: Die beiden Optionen haben unterschiedliche Aufgaben:
- **Blink + sound …** aktiviert den Hinweis nach einem passenden Logeintrag. - **Blink + sound …** aktiviert den Hinweis nach einem passenden Logeintrag.
- **Priority boost …** erhöht zusätzlich die Priorität von Stationen mit einer offenen Bandmöglichkeit. Der Boost ist ein Faktor innerhalb der gesamten Prioritätsberechnung und garantiert keinen bestimmten Listenplatz. - **Priority boost …** erhöht zusätzlich den Score von Stationen, die bereits auf mindestens einem Band gearbeitet wurden, aber noch ein weiteres gemeinsames und nicht gearbeitetes Band anbieten.
Der Priority Boost ist nur ein Faktor innerhalb der gesamten Berechnung. Entfernung, Antennenrichtung, aktuelle Aktivität, AirScout-Daten, Skeds und negative Hinweise können den endgültigen Listenplatz weiterhin verändern. Die aktivierte Option garantiert deshalb weder einen bestimmten Score noch einen bestimmten Platz in der Prioritätsliste.
Die übrigen Score-Gewichte besitzen derzeit keine eigenen Bedienelemente. Mehrere vorhandene Einstellungen liefern jedoch Eingangsdaten für die Berechnung, insbesondere die [aktivierten Bänder](#aktivierte-bänder), der [Antennen-Öffnungswinkel](#antennen-öffnungswinkel-antenna-beamwidth), der [Standard-Maximum-QRB](#standard-maximum-qrb) und die [AirScout-Einstellungen](#airscout-einstellungen).
Die vollständige Herleitung ist unter [Prioritätsscore und Prioritätsliste](de-Funktionen#prioritätsscore-und-prioritätsliste-ab-v140) beschrieben.
Der Hinweis setzt eine Log-Synchronisation mit Bandinformation voraus. Der einfache dateibasierte Callsign-Interpreter erkennt lediglich Rufzeichen und liefert deshalb keine sichere Information über das Band des gerade geloggten QSOs. Der Hinweis setzt eine Log-Synchronisation mit Bandinformation voraus. Der einfache dateibasierte Callsign-Interpreter erkennt lediglich Rufzeichen und liefert deshalb keine sichere Information über das Band des gerade geloggten QSOs.
@@ -348,20 +354,45 @@ Weitere Hintergründe: [Automatische Antworten auf Privatnachrichten](de-Funktio
## Win-Test-Netzwerk-Listener (ab v1.31) ## Win-Test-Netzwerk-Listener (ab v1.31)
Dedizierter Empfänger für Win-Test-spezifische UDP-Pakete. Ermöglicht: Der Win-Test-Netzwerk-Listener verarbeitet das native Win-Test-UDP-Protokoll. Er ist vom allgemeinen QSO-UDP-Listener auf Port `12060` unabhängig und übernimmt drei Aufgaben:
- **Log-Synchronisation**: Gearbeitete Stationen werden aus Win-Test übernommen und in der Benutzerliste markiert. - QSOs einschließlich Band- und Locatorinformation auswerten,
- **Frequenz-Auswertung**: Die aktuelle TRX-Frequenz aus Win-Test befüllt die `MYQRG`-Variable. - STATUS-Pakete für die eigene QRG verarbeiten und
- **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. - Skeds an das Win-Test-Netzwerk übergeben.
Einstellungen: ### Einstellungen unter Log sync
- **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. | Einstellung | Funktion |
|---|---|
| **Receive Win-Test network based UDP log messages** | Aktiviert den Win-Test-Netzwerk-Listener. Bei aktiviertem Listener wird nach **Create sked** auch die Sked-Übergabe versucht. |
| **UDP-Port for Win-Test listener** | Port des Win-Test-Netzwerks. Standard ist `9871`. Der Port wird auch für die Sked-Übergabe verwendet. |
| **KST station name in Win-Test network (src of SKED packets)** | Stationsname, unter dem KST4Contest die Sked-Pakete sendet. In einem Netzwerk mit mehreren Clients sollte ein eindeutiger Name verwendet werden. |
| **Win-Test network broadcast address** | Zieladresse für ausgehende Win-Test-Netzwerkpakete. Bei lokalem Netzwerkbetrieb muss hier eine vom Win-Test-Rechner erreichbare Broadcast-Adresse eingetragen sein. |
Die Broadcast-Adresse ist konfigurierbar, weil `255.255.255.255` nicht in jedem Stationsnetz und nicht über jede Netzwerkschnittstelle zuverlässig weitergeleitet wird. Bei mehreren Rechnern kann stattdessen die zum Stationsnetz gehörende gerichtete Broadcast-Adresse erforderlich sein.
### Einstellungen unter TRX sync
| Einstellung | Funktion |
|---|---|
| **Win-Test STATUS QRG Sync** | Übernimmt die aktuelle Frequenz aus Win-Test-STATUS-Paketen als eigene QRG. |
| **Use pass frequency from Win-Test STATUS** | Verwendet die übertragene Pass-Frequenz anstelle der normalen TRX-QRG. |
| **Win-Test station name filter** | Verarbeitet nur STATUS-Pakete der angegebenen Win-Test-Station. Ein leeres Feld akzeptiert alle Stationsnamen. |
Der Stationsfilter ist insbesondere bei mehreren Win-Test-Clients sinnvoll. Ohne Filter kann die zuletzt eingegangene STATUS-Meldung eines anderen Arbeitsplatzes die eigene QRG in KST4Contest überschreiben.
### Sked-Übergabe
Für die Sked-Übergabe gibt es keinen davon getrennten internen Sked-Modus. Ist der Listener aktiviert, versucht **Create sked** zusätzlich zur internen Anlage die Übertragung an Win-Test.
KST4Contest sendet nur dann ein `ADDSKED`-Paket, wenn eine QRG ermittelt wurde, die zum ausdrücklich ausgewählten Band gehört. Kann keine passende QRG gefunden werden, bleibt der interne Sked bestehen und die Win-Test-Übergabe wird ausgelassen.
Die Auswahl `SSB` oder `CW` erfolgt direkt im Further-Info-Bereich beim Anlegen des Skeds. Eine automatische Mode-Ableitung wird nicht verwendet.
Nach Änderungen **Save Settings** verwenden, damit Port, Stationsname, Broadcast-Adresse und TRX-Optionen beim nächsten Programmstart wiederhergestellt werden.
Datenbehandlung und QRG-Auswahl: [Log-Synchronisation Win-Test](de-Log-Synchronisation#win-test)
---
## PSTRotator-Einstellungen (ab v1.31) ## PSTRotator-Einstellungen (ab v1.31)
+64 -23
View File
@@ -86,35 +86,76 @@ Beim Broadcast des vollständigen Logbuchs verwendet DXLog.net `contactreplace`
### Win-Test ### Win-Test
Win-Test wird mit einem dedizierten UDP-Netzwerk-Listener unterstützt, der das native Win-Test Netzwerkprotokoll versteht. Win-Test wird über einen eigenen UDP-Listener für das native Win-Test-Netzwerkprotokoll angebunden. Dieser Listener ist vom allgemeinen QSO-UDP-Listener auf Port `12060` unabhängig.
Bei einem neuen QSO übernimmt KST4Contest das Rufzeichen und löst die native Win-Test-Band-ID auf. Dabei werden auch 50 und 70 MHz verarbeitet. Ist im Paket ein gültiger Locator enthalten, wird zusätzlich das gearbeitete Großfeld für das erkannte Band gespeichert. #### QSO- und Worked-Synchronisation
**Vorteile der Win-Test Integration:** Bei einem neuen QSO übernimmt KST4Contest:
- **Bandbezogene Worked-Daten:** Neue QSOs setzen die Worked-Markierung des von Win-Test gemeldeten Bandes und aktualisieren sofern vorhanden den Großfeldstatus.
- 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 dieser auch *direkt per UDP an das Win-Test Netzwerk als ADDSKED-Paket gesendet* automatisch, sobald der Listener aktiv ist.
- Es kann zwischen den Sked-Modi "AUTO", "SSB" oder "CW" gewählt werden.
- **Automatische QRG-Auflösung für SKEDs:** KST4Contest wählt die Sked-Frequenz intelligent:
1. Hat die Gegenstation in einer Chat-Nachricht ihre QRG genannt, wird diese verwendet.
2. Sonst wird die eigene aktuelle QRG verwendet (aus Win-Test STATUS oder manueller Eingabe).
**Einstellungen im Reiter „Log-Synchronisation":** - das geloggte Rufzeichen,
- `Receive Win-Test network based UDP log messages` aktivieren. - die native Win-Test-Band-ID und
- `UDP-Port for Win-Test listener` (Standard: 9871). - einen gültigen Locator, sofern er im Paket enthalten ist.
- `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 network broadcast address`: Wird i.d.R. automatisch erkannt; erforderlich für das Senden von Sked-Paketen.
**Einstellungen im Reiter „TRX-Synchronisation":** Die Band-IDs für 50 und 70 MHz werden ebenso verarbeitet wie die VHF-, UHF- und SHF-Bänder. Das Rufzeichen wird global und auf dem erkannten Band als gearbeitet markiert. Liegt zusätzlich ein Locator vor, wird dessen vierstelliges Großfeld für dieses Band gespeichert.
- `Win-Test STATUS QRG Sync`: Wenn aktiviert, übernimmt KST4Contest die aktuelle Transceiverfrequenz aus dem Win-Test STATUS-Paket als eigene QRG (MYQRG).
- `Use pass frequency from Win-Test STATUS`: Statt der eigenen TRX-QRG wird die im STATUS-Paket enthaltene Pass-Frequenz als MYQRG verwendet (für Multi-Op-Setups, bei denen mit einer Pass-QRG gearbeitet wird).
- `Win-Test station name filter`: Wird hier ein Name eingetragen (z.B. "STN1"), verarbeitet KST4Contest nur Pakete dieser Win-Test-Instanz. Leer lassen, um alle zu akzeptieren.
**Einstellungen in Win-Test:** Die Daten werden in derselben internen Datenbank abgelegt wie Worked-Informationen aus den übrigen QSO-UDP-Schnittstellen und nach einem Neustart wiederhergestellt.
- 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. #### Skeds an Win-Test übergeben
Mit **Create sked** wird zunächst ein interner KST4Contest-Sked angelegt. Ist der Win-Test-Netzwerk-Listener aktiviert, versucht KST4Contest anschließend automatisch, den Sked als `ADDSKED` an das Win-Test-Netzwerk zu übertragen.
Die QRG wird in folgender Reihenfolge bestimmt:
1. KST4Contest sucht die neueste, höchstens 30 Minuten alte QRG der Gegenstation auf dem ausdrücklich ausgewählten Band. Dabei werden aktive Varianten desselben Basisrufzeichens gemeinsam ausgewertet.
2. Fehlt eine solche QRG, wird die eigene QRG der Chat-Kategorie geprüft, in der der Sked angelegt wurde. Sie wird nur verwendet, wenn sie sich auswerten lässt und tatsächlich zum ausgewählten Band gehört.
3. Kann auf keinem dieser Wege eine passende QRG ermittelt werden, wird kein `ADDSKED` gesendet.
Eine feste Ersatzfrequenz wie `144.300` wird bewusst nicht verwendet. Eine technisch erfolgreiche Übergabe mit falschem Band oder falscher QRG wäre im Contestbetrieb schlechter als eine sichtbar ausgelassene Übergabe.
Der interne Sked bleibt in jedem Fall erhalten. Das gilt auch bei einer ungültigen Broadcast-Adresse, einem Netzwerkfehler oder einem nicht erreichbaren Win-Test-Client.
#### Behandlung von KST-Rufzeichensuffixen
KST-Suffixe kennzeichnen häufig den verwendeten Chat-Login oder ein Band. Sie gehören nicht in jedem Fall zum Logrufzeichen. Für Win-Test entfernt KST4Contest deshalb einen mit `-` abgetrennten KST-Suffix, erhält aber portable und internationale Rufzeichenbestandteile:
| Rufzeichen im KST-Chat | Übergabe an Win-Test |
|---|---|
| `DN9APW-2` | `DN9APW` |
| `9A0BB-70` | `9A0BB` |
| `EA5/G8MBI/P-70` | `EA5/G8MBI/P` |
| `DN9APW-2/P` | `DN9APW/P` |
Innerhalb von KST4Contest bleibt das vollständige Rufzeichen erhalten. Timeline, Reminder-PMs und Chat-Kategorie beziehen sich weiterhin auf den konkret ausgewählten Login.
#### Mode, Zeitpunkt und Notizen
Der Mode wird beim Anlegen des Skeds ausdrücklich als `SSB` oder `CW` gewählt. Eine automatische Ableitung aus der QRG findet nicht statt, weil eine begrenzte Liste angenommener Bandsegmente nicht alle unterstützten VHF-, UHF- und SHF-Bänder zuverlässig abbilden kann.
KST4Contest überträgt den tatsächlichen Sked-Zeitpunkt ohne einen zusätzlichen Minutenversatz. Die Notizen enthalten soweit bekannt Locator und QTF sowie den Hinweis, dass der Sked über KST4Contest angelegt wurde.
Für die Übergabe sendet KST4Contest die Win-Test-Pakete `LOCKSKED`, `ADDSKED` und `UNLOCKSKED`.
![Von KST4Contest an Win-Test übergebener Sked](wintest_sked_handover.png)
#### Einstellungen
Im Reiter **Log sync**:
- `Receive Win-Test network based UDP log messages`
- `UDP-Port for Win-Test listener`, standardmäßig `9871`
- `KST station name in Win-Test network (src of SKED packets)`
- `Win-Test network broadcast address`
Im Reiter **TRX sync**:
- `Win-Test STATUS QRG Sync`
- `Use pass frequency from Win-Test STATUS`
- `Win-Test station name filter`
Das Win-Test-Netzwerk muss in Win-Test aktiviert sein. Bei mehreren Computern muss die Broadcast-Adresse das betreffende lokale Netzwerk erreichen. Der Stationsname sollte die sendende KST4Contest-Instanz innerhalb des Win-Test-Netzwerks eindeutig erkennen lassen.
Ausführliche Beschreibung der Einstellungen: [Win-Test-Netzwerk-Listener](de-Konfiguration#win-test-netzwerk-listener-ab-v131)
---
## TRX-Frequenz-Synchronisation ## TRX-Frequenz-Synchronisation
+133 -14
View File
@@ -25,7 +25,18 @@ Enter your own callsign and Maidenhead locator (6 characters, e.g., `JN49IJ`). T
### Active Bands ### Active Bands
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. The **My station uses ** checkboxes define the bands available in the current local station setup. Supported choices are 50 MHz, 70 MHz, 144 MHz, 432 MHz, 1296 MHz, 2320 MHz, 3400 MHz, 5760 MHz and 10 GHz.
This selection controls more than the visible band columns. It is also used for:
- the per-band Worked and NOT-QRV filters,
- the NOT-QRV controls visible in the **Further Info** panel,
- the `a` and `B+` band-opportunity calculation,
- the **New bands** filter,
- the band-upgrade hint after a log entry, and
- band-specific priority and Reachability functions.
After a change, click **Save Settings** and restart KST4Contest. Band columns and several related controls are created while the user interface is being built and are therefore not added or removed completely during the current session.
### Antenna Beamwidth ### Antenna Beamwidth
@@ -87,12 +98,71 @@ Configuration of the interface to AirScout for aircraft scatter detection. Detai
## Notification Settings ## Notification Settings
![Notifications, DX cluster output and QSO monitoring](client_settings_window_notification.png)
Three notification types are available: 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 pronounced phonetically. 3. **Phonetic announcement**: The callsign is pronounced phonetically.
### Fallback Band for Relative QRG Detection
The **Fallback band for relative QRG detection** dropdown selects the band used when a relative QRG cannot be assigned to a recent station-specific band context.
Only band prefixes supported by the frequency parser are available:
```text
50 MHz
70 MHz
144 MHz
432 MHz
1296 MHz
2320 MHz
3400 MHz
5760 MHz
10368 MHz (10G)
24048 MHz (24G)
```
The dropdown is neither a filter nor an override for complete frequencies. `432.088` is recognised as a frequency in the 432 MHz band regardless of the selection. The fallback is needed for relative values such as `.205`, `,205` or `qrg 205`.
Before using the fallback, KST4Contest checks the sender's recent band context. If a complete frequency has been detected for the same station during the previous 30 minutes, that band takes precedence. A fallback setting of `144 MHz` therefore still turns `.100` into `432.100 MHz` if the station mentioned `432.088` shortly before.
Although the setting is located in the Notification tab, it affects the general QRG parser. It therefore influences the QRG column, detected active bands, priority calculations, band-upgrade hints and other functions which use a known station frequency not only DX cluster spots.
### Band Upgrade Hint after a Log Entry
After receiving a log entry from UCXLog or Win-Test, KST4Contest can check whether the station which has just been worked still offers another common and unworked band.
The check uses the same derivation as the `a` and `B+` display:
1. the bands enabled in the local station settings,
2. QRGs detected for the remote station during the previous 30 minutes,
3. explicit band designators in the name fields of its active chat entries,
4. stored per-band Worked marks, and
5. manually assigned NOT-QRV marks.
Active chat variants of the same normalised callsign are evaluated together. NOT-QRV takes precedence over an automatically detected QRG or band designator.
If at least one common and unworked band remains, the main window displays a blinking **BAND+** hint for approximately twelve seconds. The callsign and remaining bands are included in the button text; the tooltip contains the complete derivation. If general notification sounds are enabled, KST4Contest also plays a short sound.
The two options serve different purposes:
- **Blink + sound …** enables the hint after a matching log entry.
- **Priority boost …** additionally raises the score of stations which have already been worked on at least one band but still offer another common and unworked band.
The Priority Boost is only one factor in the complete calculation. Distance, antenna direction, recent activity, AirScout data, skeds and negative hints may still change the final order. Enabling the option therefore guarantees neither a particular score nor a particular position in the priority list.
The other score weights currently have no separate user-interface controls. Several existing settings nevertheless provide input data for the calculation, particularly the [enabled bands](#enabled-bands), [antenna beamwidth](#antenna-beamwidth), [default maximum QRB](#default-maximum-qrb) and [AirScout settings](#airscout-settings).
The complete calculation is described under [Priority Score and Priority List](en-Features#priority-score-and-priority-list-from-v140).
The hint requires a log-synchronisation source which provides band information. The file-based callsign interpreter sees callsigns only and cannot reliably identify the band of the QSO which has just been logged.
Further explanation: [Band Upgrade Hint after a Log Entry](en-Features#band-upgrade-hint-after-a-log-entry).
--- ---
## Shortcut Settings ## Shortcut Settings
@@ -135,18 +205,44 @@ New settings section with the following options:
## Win-Test Network Listener (from v1.31) ## Win-Test Network Listener (from v1.31)
A dedicated listener for Win-Test-specific UDP packets. Enables: The Win-Test network listener processes the native Win-Test UDP protocol. It is independent of the general QSO UDP listener on port `12060` and has three separate tasks:
- **Log synchronisation**: Worked stations are retrieved from Win-Test and marked in the user list. - processing QSOs including band and locator information,
- **Frequency parsing**: The current TRX frequency from Win-Test populates the `MYQRG` variable. - processing STATUS packets for the local QRG, and
- **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. - handing skeds over to the Win-Test network.
Settings: ### Log sync 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. | Setting | Function |
|---|---|
| **Receive Win-Test network based UDP log messages** | Enables the Win-Test network listener. When the listener is enabled, pressing **Create sked** also attempts the Win-Test handover. |
| **UDP-Port for Win-Test listener** | Port used by the Win-Test network. The default is `9871`. The same port is used for the sked handover. |
| **KST station name in Win-Test network (src of SKED packets)** | Station name used by KST4Contest when sending sked packets. A unique name should be used in a network containing several clients. |
| **Win-Test network broadcast address** | Destination address for outgoing Win-Test network packets. In a local network, the address must be reachable by the Win-Test computer. |
The broadcast address is configurable because `255.255.255.255` is not forwarded reliably through every station network or network interface. In a multi-computer setup, the directed broadcast address belonging to the station network may be required instead.
### TRX sync settings
| Setting | Function |
|---|---|
| **Win-Test STATUS QRG Sync** | Takes the current frequency from Win-Test STATUS packets and uses it as the local QRG. |
| **Use pass frequency from Win-Test STATUS** | Uses the transmitted pass frequency instead of the normal TRX QRG. |
| **Win-Test station name filter** | Only processes STATUS packets from the specified Win-Test station. An empty field accepts every station name. |
The station filter is particularly useful when several Win-Test clients are active. Without a filter, the most recently received STATUS packet from another operating position can overwrite the local QRG in KST4Contest.
### Sked handover
There is no separate internal sked mode for the Win-Test handover. When the listener is enabled, **Create sked** attempts the Win-Test transfer in addition to creating the internal sked.
KST4Contest only sends an `ADDSKED` packet when it can determine a QRG which belongs to the explicitly selected band. If no matching QRG is available, the internal sked remains intact and the Win-Test handover is omitted.
`SSB` or `CW` is selected directly in the Further Info section when the sked is created. No automatic mode inference is used.
Click **Save Settings** after making changes so that the port, station name, broadcast address and TRX options are restored at the next start.
Data handling and QRG selection: [Log Synchronisation Win-Test](en-Log-Sync#win-test)
--- ---
@@ -174,14 +270,37 @@ Use case: Keep track of important stations (e.g. DX expeditions or trusted conte
--- ---
## GUI Settings: Band-Column Hints
Two optional additions to the band columns can be enabled or disabled in the **GUI** tab:
- **Show "o" in band columns …** displays an `o` if the four-character grid square has already been worked on the relevant band. Disabling the option does not delete any database records; it only hides the additional indicator in the band columns. `wkdany` is unaffected.
- **Show "a" in band columns …** distinguishes a completely new callsign from a band opportunity involving a callsign already worked elsewhere. When disabled, both cases are displayed as `B+`. The band-opportunity calculation itself remains unchanged.
Changes are reflected in the current user interface immediately. Click **Save Settings** afterwards if they should persist after the next start.
![GUI settings for band-column hints](client_settings_window_gui.png)
---
## Worked Station Database Settings ## Worked Station Database Settings
The internal worked database contains: The internal SQLite database stores contest-related state independently of the logging application's database:
- Worked status of all stations (per band) - the global Worked status of a callsign,
- NOT-QRV tags (since v1.2) - Worked status per band,
- manually assigned NOT-QRV marks per band, and
- worked four-character grid squares per band.
**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. The normalised callsign, without visible chat brackets or category formatting, is used as the key. This allows active variants of the same callsign to be evaluated consistently.
Worked and NOT-QRV information expires automatically three days after its most recent change. Stored grid squares expire three days after the corresponding log entry. A manual reset before every contest is therefore normally unnecessary.
The **Reset worked, NOT-QRV and grid data...** button removes every Worked mark, NOT-QRV mark and stored worked grid square. A confirmation dialog is displayed first. Known callsign rows remain in the database; only the contest-related state is reset.
A reset is useful when you deliberately want to start with an empty contest state or have imported test data. It is not intended as a daily maintenance step.
Display and derivation: [Worked Callsigns, New Bands and New Grid Squares](en-Features#worked-callsigns-new-bands-and-new-grid-squares).
--- ---
+332 -49
View File
@@ -48,26 +48,92 @@ Recognised formats: `144.205`, `432.088`, `.205` (with configured band assumptio
--- ---
## Worked Marking ## Worked Callsigns, New Bands and New Grid Squares
Worked stations are visually marked in the user list per band. Based on [Log Synchronisation](en-Log-Sync) via UDP or Simplelogfile. KST4Contest distinguishes between three pieces of information which may look similar during a contest but answer different questions:
Reset the database before each contest: [Configuration Worked Station Database Settings](Configuration#worked-station-database-settings). 1. Has this callsign been worked before?
2. Has this callsign been worked on a particular band?
3. Has the four-character Maidenhead grid square already been worked, possibly with a different station?
--- This distinction matters. A callsign already worked on one band may still be useful on another. Conversely, a new callsign may be located in a grid square which is already in the log.
## NOT-QRV Tags (from v1.2) ### Worked information from the log
When a station indicates it is not QRV on a specific band, this can be manually marked: [Log Synchronisation](en-Log-Sync) imports new QSOs from the logging application. The amount of information available depends on the interface being used:
- The file-based Simplelogfile interpreter detects callsigns only. It can therefore set only the global Worked status.
- The QSO UDP interfaces and the Win-Test network listener can also provide the band.
- If the log packet contains a valid locator, KST4Contest additionally stores the worked four-character grid square for that band.
Missing information is not guessed. A QSO without a locator does not create a worked-grid record, and a Simplelogfile match does not create a band-specific Worked mark.
### Meaning of the band columns
The shared **worked** column contains only the bands enabled under **Station → My station uses …**. Its cells deliberately use short status codes because a full description would leave very little room for the actual user list.
| Display | Meaning |
|---|---|
| `X` | The callsign has been worked on this band. |
| `a` | The station offers this band, the band has not been worked yet, and the callsign has not been worked on any band. |
| `B+` | The station offers this band and it has not been worked yet. The callsign has already been worked on another band. If the separate `a` display is disabled, a completely new callsign is also shown as `B+`. |
| `o` | The station's four-character grid square has already been worked on this band, regardless of callsign. |
| empty | No matching information is available for this band. This does not mean that the station is not QRV. |
The `o` is an independent overlay and can therefore be combined with the other codes. Examples include `Xo`, `ao` and `B+o`. A single `o` means that the grid square has been worked on this band, while the displayed callsign has neither a Worked mark nor a current band opportunity.
![Band-specific Worked status and worked grid squares](worked_band_status.png)
### How is a band opportunity derived?
KST4Contest displays `a` or `B+` only if it can derive an open band opportunity. The calculation combines:
1. the bands enabled for the local station,
2. QRGs detected for the remote station during the previous 30 minutes,
3. explicit band designators in the remote station's name field,
4. stored per-band Worked marks, and
5. manually assigned NOT-QRV marks.
Active chat entries with the same normalised callsign are evaluated together. This is particularly relevant when the station appears in several chat categories or with different visible callsign variants. An explicit band designator in the name field remains useful while the corresponding chat entry is active. A band derived from a detected QRG expires after 30 minutes.
The remaining set contains only bands which are enabled locally, known for the remote station and not yet worked. A manual NOT-QRV mark overrides automatically detected evidence. The chat category alone is not sufficient evidence that an individual station is QRV on a particular band.
The global Worked mark does not decide whether a band opportunity exists. It merely selects `a` or `B+` for the display. The actual opportunity calculation uses per-band Worked information.
### Meaning of `wkdany`
The **wkdany** subcolumn combines the global callsign and grid-square status:
| Display | Meaning |
|---|---|
| empty | Neither the callsign nor the four-character grid square has been worked. |
| `x` | The callsign has been worked on at least one band. |
| `o` | The four-character grid square has been worked on at least one band. |
| `xo` | Both the callsign and the grid square have been worked. |
`wkdany` is deliberately band-independent. The lower-case `x` must therefore not be confused with the upper-case `X` in a band column. The global status is used for the overview and the global **wkd** filter; it is not a substitute for per-band Worked information.
### NOT-QRV marks
If a station reports that it is not QRV on a particular band, mark this in the selected station's **Further Info** panel:
1. Select the station in the user list. 1. Select the station in the user list.
2. Right-click → Set NOT-QRV for the appropriate band. 2. Enable the relevant band under **Not QRV**.
3. Use **tag not qrv all** only if the station should not be requested on any supported band.
These tags are stored in the internal database and persist after a KST4Contest restart. Can be reset via the settings. The individual NOT-QRV controls are shown for the bands enabled at the local station. **tag not qrv all**, however, marks every supported band, including bands which are not currently visible in the user interface. The state is stored per band under the normalised callsign and propagated to its active chat variants.
**Benefit**: Prevents repeated sked requests on bands where the station is not active saves time for both sides. ![Per-band NOT-QRV marks in the Further Info panel](not_qrv_controls.png)
--- NOT-QRV is a manual correction and therefore takes precedence over detected QRGs and band designators in the name field. The affected band is no longer offered as `a` or `B+`, is not counted as an opportunity by the **New bands** filter and is excluded by the corresponding band filter.
In plain terms: an automatically detected hint means "probably active on this band". A manual NOT-QRV mark means "do not request this station on this band". The next detected number must not silently reverse that decision.
### Storage and lifetime
Worked, NOT-QRV and worked-grid information is stored in the internal SQLite database and restored on the next start. Entries expire automatically after three days, so a reset before every contest is normally unnecessary.
A manual reset under **Workedstn database** removes all Worked marks, NOT-QRV marks and stored worked grid squares. The known callsign rows remain in the database. See [Worked Station Database Settings](en-Configuration#worked-station-database-settings) for details.
## Direction Filter ## Direction Filter
@@ -83,9 +149,19 @@ Hide stations beyond a maximum distance. The **"Show only QRB [km] <="** button
--- ---
## Worked and NOT-QRV Filter ## Filters for Worked Status, New Bands and New Grid Squares
Toggle buttons (one per band) to hide already-worked stations and/or NOT-QRV-tagged stations. The filter takes effect **immediately** without manually reactivating (live since v1.22). The filters above the user list use the same information as the Worked columns:
- **wkd** hides callsigns which have been worked on at least one band.
- The individual band buttons hide a station if the callsign has already been worked on that band or has manually been marked NOT QRV there.
- **New bands** shows only stations for which at least one locally enabled and unworked band is known. It evaluates recent QRG detections and band designators in the name field; NOT-QRV takes precedence.
- **Only new grids** shows only stations whose four-character grid square has not been worked on any band. Stations without a valid locator do not pass this filter.
- **Grid color** does not filter the list. When enabled, it gives the QRA cell of an already worked grid square a slightly darker background. New grid squares retain the normal table colour.
Several active filters are applied together. A station remains visible only if it satisfies every selected condition. The filters react immediately to new log entries and changed NOT-QRV marks.
Operation and layout of the filter bar: [User Interface Filters](en-User-Interface#filters).
--- ---
@@ -135,19 +211,86 @@ For selected stations in the user list, there are direct buttons to open the **Q
--- ---
## Sked Reminders with ALERT (from v1.40) ## Skeds and Sked Reminders
A sked reminder service with automatic messages can be activated for each chat member. Configurable interval patterns: > Available from v1.40; band, callsign and Win-Test handling extended in Nightly / v1.42.
- **2+1 minutes**: Messages at 2 min and 1 min before the sked. A sked is more than a reminder tied to a particular time. During a contest, it must become visible early enough, move the agreed station up the priority list and if required remind the remote station as well.
- **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. KST4Contest therefore treats three tasks separately:
Activate from the FurtherInfo panel of the corresponding station. 1. The sked is stored internally and included in the priority calculation.
2. The scheduled contact appears in the AP and sked timeline.
3. Automatic private reminder messages can optionally be sent before the agreed time.
When the Win-Test network listener is enabled, KST4Contest also attempts to hand the sked over to Win-Test. A failed handover neither removes nor prevents the internal sked.
### Creating a sked
First select the required station in the user list. The sked controls then appear at the bottom of the **Further Info** section.
| Control | Function |
|---|---|
| **Sked in** | Sets the number of minutes until the sked. Available values are 2 through 15 and 20 minutes. |
| **Band** | Selects the sked band. The dropdown contains the local bands enabled under **Station → my station uses …**. |
| **Mode** | Sets the mode passed to Win-Test. Available values are `SSB` and `CW`. This selection does not affect the internal sked or reminder PMs. |
| **Create sked** | Creates the internal sked and, if the Win-Test network listener is enabled, also attempts the Win-Test handover. |
| **Remind-PM in** | Enables automatic private reminder messages before the sked. |
| **2+1**, **5+2+1**, **10+5+2+1** | Selects how many minutes before the sked the reminder PMs are sent. |
![Sked controls in the Further Info section](sked_controls.png)
KST4Contest attempts to preselect a useful band. It checks the following information in this order:
1. a QRG of the selected station which is no more than 30 minutes old and belongs to a locally enabled band,
2. an unambiguous band designator in the station's name field, and
3. the first locally enabled band.
Active callsign variants belonging to the same base callsign are evaluated together when looking for recent band information. A manual NOT-QRV mark is taken into account by the automatic selection. The operator can still select another band explicitly when a different arrangement has been made.
### Effect on the Priority Score
A stored sked raises the score of the normalised base callsign:
| Time relative to the sked | Contribution to the score |
|---|---:|
| more than 15 minutes before the sked | `+40` |
| 15 to 3 minutes before the sked | continuous increase from `+300` towards `+1200` |
| less than 3 minutes before until 1 minute after the sked | `+5000` |
| more than 1 minute after the sked | no remaining sked boost |
The strong weighting immediately around the scheduled time is intentional. An agreed sked should not disappear from the priority list merely because another station is currently very active but has no fixed appointment.
The score is calculated for the base callsign. A sked created for `DN9APW-2` therefore also affects the shared score of other active `DN9APW` variants. The actual message target nevertheless remains `DN9APW-2` in the chat category selected when the sked was created.
The sked is removed from the internal list five minutes after its scheduled time.
### Reminder PMs
Reminder PMs are only scheduled when **Remind-PM in** is enabled. Depending on the selected pattern, KST4Contest sends a private message such as the following two and one minute before the sked:
```text
[KST4C Autoreminder] sked in 2 min
```
The message is sent to the complete visible KST callsign in the chat category in which the sked was created. A sked for `DN9APW-2` is therefore not accidentally sent to `DN9APW`, `DN9APW-70` or a similarly named station in another category.
When a reminder is actually triggered, KST4Contest also displays the visual **SKED** indication. If simple notification sounds are enabled, a short sound is played as well. Merely arming a reminder does not start the blinking indication.
Creating a new set of reminders for the same complete callsign replaces the previously scheduled reminders for that callsign.
### Storage and limitations
Skeds and reminder schedules are kept in memory only. Any skeds which are still required must be recreated after restarting KST4Contest.
The automatic band selection is derived from available chat information. It cannot prove that the station is still operating on the most recently mentioned QRG. Check the band, time and mode before pressing **Create sked**.
Operation: [Station Info Panel](en-User-Interface#station-info-panel-further-info)
Display: [AP and Sked Timeline](#ap-and-sked-timeline)
Win-Test handover: [Log Synchronisation Win-Test](en-Log-Sync#win-test)
---
## QSO Sniffer (from v1.31) ## QSO Sniffer (from v1.31)
@@ -157,17 +300,22 @@ Configuration: [Configuration Sniffer Settings](en-Configuration#sniffer-set
--- ---
## Win-Test Integration (from v1.31, fully configurable from v1.40) ## Win-Test Integration
KST4Contest fully supports [Win-Test](https://www.win-test.com/) as a logging programme: KST4Contest uses a dedicated listener for the native Win-Test network protocol. It provides three separate functions:
- **Log synchronisation**: Worked stations are automatically retrieved from Win-Test and marked in the user list. - importing new QSOs including band and, where available, locator information,
- **Frequency parsing**: The current TRX frequency is read from Win-Test UDP packets and populates the `MYQRG` variable. - reading the current QRG from Win-Test STATUS packets, and
- **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. - handing internally created skeds over to the Win-Test network as `ADDSKED` packets.
Details: [Configuration Win-Test Network Listener](en-Configuration#win-test-network-listener) The sked handover does not replace a missing QRG with a fixed default frequency. KST4Contest only sends a Win-Test sked when it can determine a QRG which belongs to the selected band. The internal sked, timeline and reminder PMs continue to work independently.
A visible KST suffix such as `-2`, `-70` or `-144` is retained inside KST4Contest but removed from the callsign passed to the Win-Test log. Portable components such as `/P`, `/M` and country prefixes are preserved.
Setup and data handling: [Log Synchronisation Win-Test](en-Log-Sync#win-test)
Settings: [Win-Test Network Listener](en-Configuration#win-test-network-listener-from-v131)
---
## PSTRotator Interface (from v1.31, fully configurable from v1.40) ## PSTRotator Interface (from v1.31, fully configurable from v1.40)
@@ -177,46 +325,181 @@ Configuration: [Configuration PSTRotator Settings](en-Configuration#pstrotat
--- ---
## Band Alert for New QSOs (from v1.40) ## Band Upgrade Hint after a Log Entry
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. When UCXLog or Win-Test reports a new log entry with band information, KST4Contest checks whether the worked station still offers another common band.
The calculation follows the same rules as `a`, `B+` and the **New bands** filter: locally enabled bands, recent QRG detections, band designators in the name field, per-band Worked marks and NOT-QRV marks. Active chat variants of the same normalised callsign are evaluated together.
If at least one common and unworked band remains, a blinking hint appears for approximately twelve seconds. It includes the callsign and the remaining bands, for example `BAND+ DL0ABC 432, 1296`. Its tooltip also lists the enabled, worked and NOT-QRV bands used for the decision. If general notification sounds are enabled, KST4Contest also plays a short sound.
The Simplelogfile interpreter cannot trigger this hint reliably because it provides no band information for the QSO which has just been logged.
Configuration: [Band Upgrade Hint after a Log Entry](en-Configuration#band-upgrade-hint-after-a-log-entry).
Worked, NOT-QRV and worked-grid data expire automatically after three days. See [Worked Station Database Settings](en-Configuration#worked-station-database-settings) for the lifetime and manual reset behaviour.
--- ---
## Worked Tag Lifetime (from v1.40) ## Priority Score and Priority List (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. ### Why is a score needed at all?
A conventional chat user list initially tells the operator only which stations are logged in. That is not enough during a contest. The operator must also consider which stations have not yet been worked, which bands may still be available, where the antenna is pointing, whether a suitable aircraft is approaching and whether an agreed sked is about to begin.
With a short list, much of this can still be handled mentally. As the contest continues, several bands are used and two chat categories are monitored at the same time, the same decision has to be reconstructed over and over again.
KST4Contest therefore combines the available information into a priority score. The score does not answer whether a QSO will definitely be possible. It supports the more practical question:
> Which of the currently visible stations should I examine next?
### When is a station excluded?
Before applying the weighted factors, KST4Contest checks whether a known band opportunity exists. All active chat entries belonging to the same normalised base callsign are evaluated together.
The calculation uses:
1. the bands enabled in the local station settings,
2. QRGs detected for the remote station during the previous 30 minutes,
3. explicit band designators in the name fields of its active chat entries,
4. stored per-band Worked marks, and
5. manually assigned NOT-QRV marks.
NOT QRV takes precedence over automatically detected frequencies and band designators.
If the remote stations bands are known but none of them is both enabled locally and still available, the station receives a score of `0`. The same applies when every common band opportunity has already been worked.
A station is not excluded merely because all band information is missing. An unknown band opportunity is not the same as a known incompatibility. In this case, the station is removed from consideration only if all locally enabled bands have been manually marked NOT QRV for that station.
Stations with a score of `0` remain visible in the user list but are not included in the priority list.
### Which information raises or lowers the score?
The score combines several independent hints. One factor will therefore not normally determine the final position on its own.
| Factor | Effect on priority |
|---|---|
| Worked status | A callsign which has not been worked on any supported band receives a higher initial priority. A station which has already been worked is ranked lower but remains a candidate when another band opportunity is available. |
| Available bands | Several common and unworked bands raise the priority. An additional boost for band-upgrade cases can be enabled separately. |
| Distance | Distances below 200 km are weighted lower. The range between 200 km and the configured maximum QRB is preferred. Stations beyond the maximum QRB are reduced substantially. If the QRB is unavailable, this factor is omitted. |
| Antenna direction | The score rises when the QTF to the station lies within half of the configured antenna beamwidth around the current local QTF. The closer both directions are, the stronger the effect. |
| AirScout | At least one currently reachable aircraft raises the score. An expected AP opportunity in zero, one or two minutes receives an additional time-dependent weighting. |
| Recent chat activity | A message received during the previous minute has a stronger effect than one received during the previous three minutes. Several incoming lines within the activity window raise the score further. |
| Positive signals | Detected terms such as `QRV`, `READY`, `RGR`, `OK`, `TNX` or comparable configured text patterns are treated as a positive hint for several minutes. |
| Reply behaviour | If another visible chat line from the station follows an outgoing `/cq` message quickly, the averaged reaction time raises the score. If no such line arrives before the configured timeout, a negative mark is added. |
| Skeds | A scheduled contact initially adds a small amount of priority. During the final 15 minutes, its influence increases continuously. From three minutes before until one minute after the scheduled time, the sked receives very high priority. |
| Failed attempt | **Sked fail** strongly reduces the stations score until the mark is removed with **Reset fail** or KST4Contest is restarted. |
The default activity window for counting incoming messages is 180 seconds. A message received during the previous 60 seconds is evaluated separately as current activity. The default no-reply timeout is 13 minutes.
For reply behaviour, KST4Contest cannot prove that a later public or private line is actually a reply to the operators request. Any subsequent line received from the same station therefore ends the pending response-time measurement. This is a practical approximation, not a statistically reliable response rate.
### What does a scheduled contact mean for the score?
A sked is a time-dependent operating commitment. An imminent sked must therefore take precedence over most normal activity and distance hints. Without this weighting, a station which happens to be very active in the chat could displace an agreed contact from the priority list.
The strongest sked boost is deliberately limited to the period from three minutes before until one minute after the scheduled time. A sked further in the future remains relevant but should not yet dominate current operation.
The score is calculated for the normalised base callsign. A sked entered for an active variant such as `9A0BB-23` therefore affects the common score of the chat entries belonging to `9A0BB`.
### How are multiple suffixes and chat categories handled?
Active callsigns such as `9A0BB-2`, `9A0BB-70`, `9A0BB-23` and `9A0BB-13` remain separate chat members. Messages can therefore still be addressed to the complete callsign in the correct chat category.
Worked, band, NOT-QRV and score information belongs to the common base callsign `9A0BB`. The score is calculated once and projected to all active variants. The user list may consequently contain several separate rows with the same score, while the priority list contains only one entry for the base station.
KST4Contest uses the most recently suitable active login in the last relevant chat category as the concrete message target. Selecting a priority candidate then resolves the complete callsign, including its suffix and chat category.
### Updating and displaying the score
New messages, AirScout data, skeds, Worked information and manual NOT-QRV or Sked-fail changes request a new calculation immediately. The score is also refreshed periodically because activity, AP and sked information changes with time even when no new event is received.
A delay of a few seconds between an event and the visible new order is therefore normal.
The user interface displays the score in three places:
- the numerically sortable **Score** column in the user list,
- the **Further Info** section for the selected station, and
- the compact list of the two highest-ranked candidates, with a separate window containing up to 15 candidates.
Operation: [Priority List in the User Interface](en-User-Interface#priority-list).
### What does the score not tell you?
The numerical value is neither a success probability nor a signal prediction. A score which is twice as high does not mean that the QSO is twice as likely.
Among other things, the calculation does not know:
- the actual antenna direction of the remote station,
- its current operating situation,
- local interference,
- short-term propagation changes,
- terrain obstruction outside the connected assessment functions, or
- whether a station which is active in the chat is currently sitting at the radio.
Known input data may also be outdated or ambiguous. A detected frequency, for example, proves only that the QRG recently appeared in connection with that station.
In practical terms, the score does not replace the operators decision. It prevents the information already available to KST4Contest from having to be reconstructed mentally for every candidate.
Related settings:
- [Enabled Bands](en-Configuration#enabled-bands)
- [Antenna Beamwidth](en-Configuration#antenna-beamwidth)
- [Default Maximum QRB](en-Configuration#default-maximum-qrb)
- [AirScout Settings](en-Configuration#airscout-settings)
- [Band Upgrade Hint and Priority Boost](en-Configuration#band-upgrade-hint-after-a-log-entry)
--- ---
## Chatmember Score System / Priority List (from v1.40) ## AP and Sked Timeline
KST4Contest automatically calculates a **priority score** for each active chat member. The score is derived from: The timeline combines upcoming aircraft-scatter opportunities and stored skeds for the next 30 minutes. It therefore answers two different questions in the same place:
- Antenna direction of the remote station (is it pointing towards me?) - When is an interesting AP opportunity expected?
- QRB (distance) - Which previously agreed sked is approaching independently of that opportunity?
- Activity time and message count
- Active bands and frequencies
- AP availability (AirScout)
- Sked direction (degrees)
- Sked success rate and skedfail markings
The top candidates are highlighted in a dedicated priority list, helping you not to miss the most important contacts during contest stress. Events further in the future appear on the right. As time passes, they move left towards the current time.
Stations with a failed sked can be marked using the **Skedfail button** in the FurtherInfo panel this temporarily lowers their score. ![AP candidates and skeds in the timeline](sked_timeline.png)
--- ### AP candidates
## AP Timeline (from v1.40) AP candidates appear in the upper lanes. Up to four selected candidates can be displayed for each aircraft arrival minute. The selection takes the Priority Score and the reflection potential reported by AirScout into account.
A visual timeline shows up to 4 highly-scored stations per minute slot that should be workable via aircraft scatter. Prioritisation criteria: The colour of an AP marker represents the reflection potential:
- **Highest reflection potential** is preferred (not necessarily the fastest arrival). | Colour | Reflection potential |
- Stations towards which your antenna is not pointing are shown **transparently**. |---|---:|
| Magenta | at least 95% |
| Red | at least 75% |
| Yellow | at least 50% |
| Blue | below 50% |
The colour is not a QSO probability. It represents the AirScout value for the calculated reflection geometry.
Clicking an AP candidate selects the corresponding active chat member, including its callsign suffix and chat category. A suitable message can then be prepared immediately.
### Skeds
Skeds appear as diamonds in the lower lane. Their labels use the complete KST callsign, for example `SKED: DN9APW-2`. This makes it clear which particular login was selected for the scheduled contact.
A sked tooltip shows at least:
- the complete KST callsign,
- the agreed band, and
- the QTF towards the remote station.
Where suitable AirScout data is available, the tooltip also includes current AP reachability and the next calculated AP opportunity.
### Antenna direction
When the QTF of an event is clearly outside the current antenna direction, its marker becomes more transparent. The label remains readable. A target close to the centre of the configured antenna beam is highlighted.
This visual effect changes neither the sked nor the Priority Score. It is simply a quick way of identifying candidates which fit the current antenna direction.
The timeline is a preview. AirScout data can change, and a stored sked guarantees neither a clear frequency nor an actual propagation path.
This gives the contest operator a quick overview of which stations will be reachable via which aircraft and at what time.
---
## Interval Beacon ## Interval Beacon
+91 -33
View File
@@ -2,7 +2,7 @@
> 🇬🇧 You are reading the English version | 🇩🇪 [Deutsche Version](de-Log-Synchronisation) > 🇬🇧 You are reading the English version | 🇩🇪 [Deutsche Version](de-Log-Synchronisation)
KST4Contest automatically marks worked stations in the chat user list. Two basic methods are available: KST4Contest imports worked stations from the logging application and derives the global Worked status, per-band Worked marks and where a locator is available worked grid squares. Three input paths are available: the file-based Simplelogfile interpreter, the general QSO UDP listener and the dedicated Win-Test network listener.
--- ---
@@ -10,24 +10,25 @@ KST4Contest automatically marks worked stations in the chat user list. Two basic
## 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 it for callsigns using a configurable regular expression. The file is read only and is never modified. Binary log files can also be used; content which cannot be interpreted as text is skipped.
**Advantage**: Works with almost any logging program that writes a file. The advantage is broad compatibility: no dedicated network interface is required from the logging application.
**Disadvantage**: No band information available stations are only marked as "worked", not on which band.
Enter the path to the log file in the Preferences. The file is only read, never modified (read-only). The limitation is equally clear. A callsign match alone provides neither a reliable band nor a locator. The Simplelogfile interpreter can therefore set only the global Worked status. It does not create a per-band `X`, a worked-grid record or a reliable basis for the band-upgrade hint after a log entry.
> **Tip**: The Simplelogfile function can also be used to mark stations that are definitely unreachable (e.g. personal notes). This will be replaced in a later version by a better tagging system. Configure the log-file path and regular expression in the **Log sync** tab. Use one of the network interfaces where possible if band-specific information is required.
--- ---
## Method 2: Network Listener (UDP Broadcast) Recommended # Method 2: Network Listener for QSO UDP Packets Recommended
When saving a QSO, the logging software sends a UDP packet to the broadcast address of the home network. KST4Contest receives this packet and marks the station including **band information** in its internal SQLite database. UCXLog, QARTest, N1MM+ and DXLog.net can transmit a UDP packet when a QSO is saved. KST4Contest receives these packets on port `12060` by default and imports the callsign together with any band and locator information they contain.
> **Important**: KST4Contest must be **running in parallel with the logging software**. QSOs logged while KST4Contest is not running will not be captured except with QARTest (which can send the complete log). If a band is available, the callsign is marked as worked on that band. If the packet also contains a valid locator, KST4Contest stores its four-character grid square for that band. Missing information is not inferred from unrelated fields.
**Default UDP port**: 12060 (matches the default of most logging programs) KST4Contest must be running when the packet is transmitted. Some logging applications can, however, resend an existing log: QARTest provides **Invia log completo**, while DXLog.net sends `contactreplace` packets when broadcasting the complete log. KST4Contest processes both mechanisms.
**Default port:** `12060`
--- ---
@@ -81,34 +82,81 @@ For the built-in DX cluster server: configure N1MM+ as a DX cluster client (serv
- Enter the IP of the KST4Contest computer (green-highlighted fields) - Enter the IP of the KST4Contest computer (green-highlighted fields)
- Port: 12060 - Port: 12060
When broadcasting the complete logbook, DXLog.net uses `contactreplace` instead of `contactinfo`. KST4Contest processes both packet types. Older QSOs can therefore be imported by starting a complete-log broadcast while KST4Contest is running.
### Win-Test ### Win-Test
Win-Test is supported with a dedicated UDP network listener that understands the native Win-Test network protocol. Win-Test is connected through a dedicated UDP listener for the native Win-Test network protocol. This listener is independent of the general QSO UDP listener on port `12060`.
**Advantages of Win-Test Integration:** #### QSO and Worked synchronisation
- 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* automatically, as soon as the listener is active. No separate toggle is needed.
- You can choose between "AUTO", "SSB", or "CW" sked modes.
- **Automatic QRG resolution for SKEDs:** KST4Contest selects the sked frequency intelligently:
1. If the other station mentioned their QRG in a recent chat message, that frequency is used.
2. Otherwise, your own current QRG is used (from Win-Test STATUS or manual entry).
**Settings in the "Log Synchronisation" tab:** For a new QSO, KST4Contest imports:
- Enable `Receive Win-Test network based UDP log messages`.
- `UDP-Port for Win-Test listener` (default: 9871).
- `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 network broadcast address`: Usually detected automatically; required to send sked packets to the network.
**Settings in the "TRX Synchronisation" tab:** - the logged callsign,
- `Win-Test STATUS QRG Sync`: When enabled, KST4Contest takes the current transceiver frequency from the Win-Test STATUS packet and uses it as your own QRG (MYQRG). - the native Win-Test band ID, and
- `Use pass frequency from Win-Test STATUS`: Instead of the main TRX frequency, the pass frequency contained in the STATUS packet is used as MYQRG (useful for multi-op setups that operate with a dedicated pass QRG). - a valid locator where one is included in the packet.
- `Win-Test station name filter`: If a name is entered here (e.g. "STN1"), KST4Contest only processes packets from that specific Win-Test instance. Leave empty to accept all.
Band IDs for 50 and 70 MHz are processed in the same way as the VHF, UHF and SHF bands. The callsign is marked as worked globally and on the detected band. If a locator is also available, its four-character grid square is stored for that band.
The information is written to the same internal database as Worked data received through the other QSO UDP interfaces and is restored after a restart.
#### Handing skeds over to Win-Test
Pressing **Create sked** first creates an internal KST4Contest sked. If the Win-Test network listener is enabled, KST4Contest then automatically attempts to send the sked to the Win-Test network as an `ADDSKED` packet.
The QRG is selected in the following order:
1. KST4Contest looks for the most recent QRG of the remote station on the explicitly selected band. The QRG must be no more than 30 minutes old. Active variants of the same base callsign are evaluated together.
2. If no such QRG is available, KST4Contest checks the local QRG of the chat category in which the sked was created. It is only used if it can be parsed and actually belongs to the selected band.
3. If neither source provides a matching QRG, no `ADDSKED` packet is sent.
A fixed replacement frequency such as `144.300` is deliberately not used. During a contest, a technically successful handover containing the wrong band or QRG is worse than a visibly omitted handover.
The internal sked remains intact in every case. This also applies when the broadcast address is invalid, the network fails or no Win-Test client can be reached.
#### Handling KST callsign suffixes
KST suffixes often identify a particular chat login or band. They are not necessarily part of the log callsign. KST4Contest therefore removes a suffix separated by `-` before handing the callsign over to Win-Test, while preserving portable and international callsign components:
| Callsign in the KST chat | Callsign passed to Win-Test |
|---|---|
| `DN9APW-2` | `DN9APW` |
| `9A0BB-70` | `9A0BB` |
| `EA5/G8MBI/P-70` | `EA5/G8MBI/P` |
| `DN9APW-2/P` | `DN9APW/P` |
The complete callsign remains available inside KST4Contest. The timeline, reminder PMs and chat category continue to refer to the login which was actually selected.
#### Mode, time and notes
The mode is selected explicitly as `SSB` or `CW` when the sked is created. It is not inferred automatically from the QRG because a limited list of assumed band segments cannot represent every supported VHF, UHF and SHF band reliably.
KST4Contest sends the actual scheduled time without adding an extra minute. Where available, the notes include the locator and QTF together with an indication that the sked was created through KST4Contest.
The handover consists of the Win-Test packets `LOCKSKED`, `ADDSKED` and `UNLOCKSKED`.
![Sked handed over from KST4Contest to Win-Test](wintest_sked_handover.png)
#### Settings
In the **Log sync** tab:
- `Receive Win-Test network based UDP log messages`
- `UDP-Port for Win-Test listener`, default `9871`
- `KST station name in Win-Test network (src of SKED packets)`
- `Win-Test network broadcast address`
In the **TRX sync** tab:
- `Win-Test STATUS QRG Sync`
- `Use pass frequency from Win-Test STATUS`
- `Win-Test station name filter`
The Win-Test network must be enabled in Win-Test. When several computers are used, the broadcast address must reach the relevant local network. The station name should identify the sending KST4Contest instance unambiguously within the Win-Test network.
Detailed settings: [Win-Test Network Listener](en-Configuration#win-test-network-listener-from-v131)
**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).
---
## TRX Frequency Synchronisation ## TRX Frequency Synchronisation
@@ -144,6 +192,16 @@ For DM5M-style setups (2 radios, 2 computers, one KST4Contest instance or two se
## Internal Database ## Internal Database
KST4Contest stores worked information in an internal **SQLite database**. This is independent of the logging program's database and is only populated via the UDP broadcast. KST4Contest stores Worked, NOT-QRV and worked-grid information in its own SQLite database. This database is independent of the logging application's database.
Before each new contest: reset the database! → [Configuration Worked Station Database Settings](Configuration#worked-station-database-settings) The input sources provide different levels of detail:
| Source | Global callsign status | Per-band status | Grid square |
|---|---:|---:|---:|
| Simplelogfile | yes | no | no |
| QSO UDP listener | yes | yes, if included in the packet | yes, if both band and locator are available |
| Win-Test network listener | yes | yes | yes, if a locator is available |
The information is restored when KST4Contest starts and updated during operation when new log entries arrive. It expires automatically after three days, so a reset before every contest is normally unnecessary.
A complete manual reset removes Worked marks, NOT-QRV marks and worked grid squares together. See [Worked Station Database Settings](en-Configuration#worked-station-database-settings) for details.
+96 -15
View File
@@ -26,14 +26,29 @@ The central table of all currently active chat users. Columns (depending on conf
| Column | Content | | Column | Content |
|---|---| |---|---|
| Call | Station's callsign | | Callsign | Station callsign |
| Name | Name from the chat name field | | Name | Name and additional information from the chat name field |
| Loc | Maidenhead locator | | QRA | Maidenhead locator |
| QRB | Distance in km | | QRB | Distance in km |
| QTF | Direction in degrees | | QTF | Direction in degrees |
| QRG | Automatically detected frequency | | QRG | Most recent frequency detected in a chat message |
| AP | AirScout aircraft data (when active) | | Tropo | Result of the band-specific tropo or path assessment |
| Band colours | Worked / NOT-QRV status per band | | Score | Current, numerically sortable priority score of the normalised base callsign |
| Act | Minutes since the most recent activity |
| AP | AirScout aircraft data, when enabled |
| worked | Per-band Worked, band-opportunity and grid-square status, plus `wkdany` |
| NOT QRV @ | Bands on which the station has manually been marked not QRV |
| Category | Chat category of this entry |
### Worked, band and grid-square status
The subcolumns under **worked** use compact codes because several enabled bands leave little room for full descriptions. `X` marks a callsign worked on that band. `a` and `B+` identify an offered band which has not yet been worked. An appended `o` means that the four-character grid square has already been worked on this band.
The **wkdany** subcolumn is band-independent: `x` means that the callsign has been worked, `o` means that the grid square has been worked on any band, and `xo` means both.
Each status cell has a tooltip containing the legend and the state derived for that station. For the complete calculation, including NOT-QRV precedence, see [Worked Callsigns, New Bands and New Grid Squares](en-Features#worked-callsigns-new-bands-and-new-grid-squares).
![Band-specific Worked status and worked grid squares](worked_band_status.png)
**Sorting**: Click column headers. QRB sorting is numerical (corrected in v1.22). **Sorting**: Click column headers. QRB sorting is numerical (corrected in v1.22).
@@ -53,26 +68,91 @@ Input field for the current antenna direction. Used for the planned `MYQTF` vari
## Filters ## Filters
The filter bar (from v1.21 as a flowpane for small screens): The filter bar is located above the chat-member table and groups related controls:
- **Show only QTF**: Activate direction filter (N/NE/E/… buttons or degree input) - **Show only QTF** limits the list to a selected antenna direction.
- **Show only QRB [km] <=**: Activate distance filter (toggle button) - **Show only QRB [km] <=** sets a maximum distance.
- **Hide Worked [Band]**: Hide worked stations per band (one toggle per band) - **Find** searches for a callsign.
- **Hide NOT-QRV [Band]**: Hide NOT-QRV-tagged stations per band - **wkd** hides callsigns which have already been worked on at least one band.
- The individual band buttons hide a station if it has already been worked on that band or has been marked NOT QRV there. Only bands enabled for the local station are shown.
- **Only new grids** shows only stations in four-character grid squares which have not been worked on any band.
- **Grid color** is not a filter. It marks the QRA cell of an already worked grid square without hiding stations.
- **New bands** shows stations with at least one detected, locally enabled and unworked band opportunity. NOT-QRV marks take precedence.
- **Reachability**, **Tropo >=0dB** and **AS next 5m** limit the list according to the selected path or AirScout criteria.
The filter bar has no fixed width. QTF, Worked and Reachability controls initially use the available space in their respective rows. When the horizontal divider is moved to the right and the chat-member area becomes narrower, controls wrap only when their actual required width no longer fits.
![Wrapped filter bar in a narrow chat-member view](filter_bar_wrapped.png)
In plain terms: the filters determine the table contents, but no longer enforce the minimum width of the entire right-hand side. The bar remains compact in the normal layout and uses additional height only when the view becomes genuinely narrow. Moving the divider back to the left immediately returns the controls to the available rows.
--- ---
## Station Info Panel (Further Info) ## Station Info Panel (Further Info)
Bottom right: Shows all messages of a selected station (CQ messages and PMs in one panel). A message filter can be pre-configured via the default filter in the Preferences. The lower-right panel combines the messages associated with the selected station. This includes public messages, private messages to the local station and, where visible in the chat, private messages addressed to other stations.
**Sked reminders** can also be activated here. The selected filter controls which of these messages are displayed. Under **Settings → GUI**, the default filter can be set to:
- all messages,
- private messages to the local station,
- private messages to other stations, or
- public messages.
This setting changes the Further Info display only. Messages are neither discarded nor removed from the other message tables, and the filter can be changed at any time for the currently selected station.
The lower part of the panel contains per-band **Not QRV** marks for the selected station. Individual controls are shown for the bands enabled in the local station settings. **tag not qrv all** sets or removes the mark for every supported band, including bands which are not currently visible.
The change immediately affects the **NOT QRV @** column, band opportunities and the corresponding filters. It is stored in the internal database and restored after a restart.
![Per-band NOT-QRV marks in the Further Info panel](not_qrv_controls.png)
The current **Priority score** of the selected station is displayed in the same section.
**Sked fail** marks an unsuccessful attempt and strongly reduces the score of the normalised base callsign. **Reset fail** removes the mark. It applies to all active suffix and category variants of the station and remains active for the current program session.
The controls underneath are used to create a sked:
| Control | Meaning |
|---|---|
| **Sked in** | Time remaining until the sked |
| **Band** | Agreed band selected from the locally enabled bands |
| **Mode** | `SSB` or `CW` for a possible Win-Test handover |
| **Create sked** | Create the internal sked |
| **Remind-PM in** | Enable automatic reminder PMs |
| **2+1**, **5+2+1**, **10+5+2+1** | Times at which reminder PMs are sent before the sked |
![Sked controls in the Further Info section](sked_controls.png)
The proposed band is derived from recent QRG and name information for the station. It can be changed explicitly before creating the sked. The mode selection only affects the Win-Test handover; the internal sked and reminder PMs work independently.
**Create sked** always creates the appointment inside KST4Contest first. If the Win-Test network listener is active, KST4Contest then attempts an additional handover to Win-Test. If no QRG matching the selected band can be found or Win-Test cannot be reached, the internal sked, its priority contribution and any scheduled reminders remain intact.
The complete derivation and limitations are described under [Skeds and Sked Reminders](en-Features#skeds-and-sked-reminders).
--- ---
## Priority List ## Priority List
Shows the top candidates calculated by the Score Service. Updates automatically in the background based on direction, distance and AP availability. The compact priority bar is located on the right-hand side between the user list and the Further Info section. It displays the two currently highest-ranked candidates directly in the main window:
```text
Priority: 1 CALLSIGN SCORE 2 CALLSIGN SCORE more
```
Clicking either candidate selects the corresponding active chat member. The complete callsign, including its suffix and chat category, is used.
The **more** button opens a separate window containing up to 15 candidates. The list is sorted by descending score. Double-clicking an entry selects the candidate and closes the window.
![Priority Score, compact candidate list and Further Info controls](priority_score_overview.png)
Stations with a score of `0` are not included in the priority list. They remain visible in the user list so that the reason for their exclusion can be examined and, for example, an incorrect NOT-QRV mark can be changed.
The score is calculated for the normalised base callsign. Several active variants such as `9A0BB-2` and `9A0BB-70` may therefore display the same value in the user list. They nevertheless remain separate message targets.
New messages, AirScout data, skeds and status changes request a new calculation. A periodic refresh also runs in the background. A briefly outdated order is therefore not an error.
Calculation and limitations: [Priority Score and Priority List](en-Features#priority-score-and-priority-list-from-v140).
--- ---
@@ -100,6 +180,7 @@ If you encounter display problems: delete the configuration file → KST4Contest
## Operating Tips ## Operating Tips
- **Keep the settings window open**: Quick access to enable/disable the beacon. - **Keep the settings window open**: Quick access to enable/disable the beacon.
- **Right-click in the user list**: Opens the snippet menu and further actions (QRZ.com profile, set NOT-QRV tags). - **Right-click in the user list**: Opens the snippet menu and other context actions.
- **Mark a station NOT QRV**: Select the station and use the per-band controls in the **Further Info** panel.
- **Enter from anywhere**: When text is in the send field, Enter sends directly even if the focus is elsewhere. - **Enter from anywhere**: When text is in the send field, Enter sends directly even if the focus is elsewhere.
- **Stop the beacon**: Switch off the beacon while scanning frequencies to avoid flooding the chat with messages. - **Stop the beacon**: Switch off the beacon while scanning frequencies to avoid flooding the chat with messages.
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

@@ -318,7 +318,6 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
} }
public void stopRotator() { public void stopRotator() {
if (rotatorClient != null) { if (rotatorClient != null) {
rotatorClient.stop(); rotatorClient.stop();
@@ -773,24 +772,6 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
private final Map<String, ChatCategory> lastInboundCategoryByCallSignRaw = private final Map<String, ChatCategory> lastInboundCategoryByCallSignRaw =
new java.util.concurrent.ConcurrentHashMap<>(); new java.util.concurrent.ConcurrentHashMap<>();
/** Tracks the last time WE sent a message containing a QRG to a specific callsign (UPPERCASE).
* Compared against knownActiveBands.timestampEpoch to decide whose QRG to use in a SKED. */
private final Map<String, Long> lastSentQRGToCallsign =
new java.util.concurrent.ConcurrentHashMap<>();
/** Call this whenever we send a PM to {@code receiverCallsign} that contains our QRG. */
public void recordOutboundQRG(String receiverCallsign) {
if (receiverCallsign == null) return;
lastSentQRGToCallsign.put(receiverCallsign.trim().toUpperCase(), System.currentTimeMillis());
System.out.println("[ChatController] Recorded outbound QRG to: " + receiverCallsign);
}
/** Returns epoch-ms of when we last sent our QRG to this callsign, or 0 if never. */
public long getLastSentQRGTimestamp(String callsign) {
if (callsign == null) return 0L;
return lastSentQRGToCallsign.getOrDefault(callsign.trim().toUpperCase(), 0L);
}
private final ScoreService scoreService = new ScoreService(this, new PriorityCalculator(), 15); private final ScoreService scoreService = new ScoreService(this, new PriorityCalculator(), 15);
private ScheduledExecutorService scoreScheduler; private ScheduledExecutorService scoreScheduler;
private final StationMetricsService stationMetricsService = new StationMetricsService(); private final StationMetricsService stationMetricsService = new StationMetricsService();
@@ -814,158 +795,420 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
} }
/** /**
* Pushes a sked to Win-Test via UDP broadcast (LOCKSKED / ADDSKED / UNLOCKSKED). * Pushes a sked to Win-Test via UDP broadcast.
* Runs on a background thread to avoid blocking the UI. *
* <p>The internal sked is independent from this handover. If no frequency can
* be resolved safely for the selected band, the internal sked remains active,
* but no misleading Win-Test entry is created.</p>
*/ */
private void pushSkedToWinTest(ContestSked sked) { private void pushSkedToWinTest(ContestSked sked) {
new Thread(() -> { new Thread(() -> {
try { try {
InetAddress broadcastAddr = InetAddress.getByName( Double frequencyKHz = resolveSkedFrequencyKHz(sked);
chatPreferences.getLogsynch_wintestNetworkBroadcastAddress());
int port = chatPreferences.getLogsynch_wintestNetworkPort();
String stationName = chatPreferences.getLogsynch_wintestNetworkStationNameOfKST();
WinTestSkedSender sender = new WinTestSkedSender(stationName, broadcastAddr, port, this); if (frequencyKHz == null) {
reportSkippedWinTestSked(
// Frequency resolution: sked,
// Compare WHO sent a QRG most recently in the PM conversation: "no recent or configured QRG matches "
// - OM sent their QRG last → use OM's Last Known QRG (ChatMember.frequency) + sked.getBand().getDisplayLabel()
// - WE sent our QRG last → use our own Win-Test QRG (MYQRG) );
// Fallback chain if no timestamps exist: OM's Last Known QRG → hardcoded default return;
double freqKHz = -1.0;
final long SKED_FREQ_MAX_AGE_MS = 60 * 60 * 1000L; // 60 minutes
ChatMember targetMember = resolveSkedTargetMember(sked.getTargetCallsign());
// Collect timestamps: when did the OM last mention their QRG? When did WE last send ours?
long omLastQRGTimestamp = 0L;
double omLastQRGMhz = 0.0;
if (targetMember != null && sked.getBand() != null) {
ChatMember.ActiveFrequencyInfo fi = targetMember.getKnownActiveBands().get(sked.getBand());
if (fi != null && fi.frequency > 0
&& (System.currentTimeMillis() - fi.timestampEpoch) <= SKED_FREQ_MAX_AGE_MS) {
omLastQRGTimestamp = fi.timestampEpoch;
omLastQRGMhz = fi.frequency;
}
}
long ourLastQRGTimestamp = getLastSentQRGTimestamp(sked.getTargetCallsign());
// Decision: who was more recent?
if (omLastQRGTimestamp > 0 && omLastQRGTimestamp >= ourLastQRGTimestamp) {
// OM mentioned their QRG MORE RECENTLY (or at same time) → use their QRG
freqKHz = omLastQRGMhz * 1000.0;
System.out.println("[ChatController] SKED freq: OM sent last → "
+ omLastQRGMhz + " MHz → " + freqKHz + " kHz");
} else if (ourLastQRGTimestamp > 0) {
// WE sent our QRG more recently → use our Win-Test QRG
try {
String qrgStr = chatPreferences.getMYQRGFirstCat().get();
if (qrgStr != null && !qrgStr.isBlank()) {
String cleaned = qrgStr.trim().replace(".", "");
double parsed = Double.parseDouble(cleaned) / 100.0;
if (parsed > 50000) {
freqKHz = parsed;
System.out.println("[ChatController] SKED freq: WE sent last → "
+ freqKHz + " kHz (raw: " + qrgStr + ")");
}
}
} catch (NumberFormatException ignored) { }
} }
// Fallback A: OM's Last Known QRG from KST field (if no PM QRG exchange found at all) String winTestCallsign =
if (freqKHz < 0 && targetMember != null) { toWinTestSkedCallsign(
try { sked.getTargetChatCallsign()
String memberQrg = targetMember.getFrequency().get(); );
if (memberQrg != null && !memberQrg.isBlank()) {
double mhz = Double.parseDouble(memberQrg.trim()); if (winTestCallsign == null || winTestCallsign.isBlank()) {
freqKHz = mhz * 1000.0; reportSkippedWinTestSked(
System.out.println("[ChatController] SKED freq: fallback Last Known QRG → " sked,
+ mhz + " MHz → " + freqKHz + " kHz"); "the target callsign could not be converted"
} );
} catch (NumberFormatException ignored) { } return;
} }
// Fallback B: hardcoded default InetAddress broadcastAddress = InetAddress.getByName(
if (freqKHz < 0) { chatPreferences
freqKHz = 144300.0; .getLogsynch_wintestNetworkBroadcastAddress()
} );
int port =
chatPreferences.getLogsynch_wintestNetworkPort();
String stationName =
chatPreferences
.getLogsynch_wintestNetworkStationNameOfKST();
WinTestSkedSender sender = new WinTestSkedSender(
stationName,
broadcastAddress,
port,
this
);
String targetLocator =
resolveSkedTargetLocator(
sked.getTargetCallsign()
);
// Build notes string with target locator/azimuth info like reference: [JO02OB - 279°]
String targetLocator = resolveSkedTargetLocator(sked.getTargetCallsign());
String notes = "sked via KST4Contest"; String notes = "sked via KST4Contest";
if (targetLocator != null && !targetLocator.isBlank() && sked.getTargetAzimuth() > 0) {
notes = String.format("[%s - %.0f°] %s", targetLocator, sked.getTargetAzimuth(), notes); if (targetLocator != null
} else if (targetLocator != null && !targetLocator.isBlank()) { && !targetLocator.isBlank()
notes = String.format("[%s] %s", targetLocator, notes); && sked.getTargetAzimuth() > 0) {
notes = String.format(
"[%s - %.0f°] %s",
targetLocator,
sked.getTargetAzimuth(),
notes
);
} else if (targetLocator != null
&& !targetLocator.isBlank()) {
notes = String.format(
"[%s] %s",
targetLocator,
notes
);
} else if (sked.getTargetAzimuth() > 0) { } else if (sked.getTargetAzimuth() > 0) {
notes = String.format("[%.0f°] %s", sked.getTargetAzimuth(), notes);
notes = String.format(
"[%.0f°] %s",
sked.getTargetAzimuth(),
notes
);
} }
// Determine mode: -1 = auto-detect, 0 = CW, 1 = SSB String configuredMode =
String modeStr = chatPreferences.getLogsynch_wintestSkedMode(); chatPreferences.getLogsynch_wintestSkedMode();
int modeOverride = -1; // AUTO
if ("CW".equalsIgnoreCase(modeStr)) modeOverride = 0;
else if ("SSB".equalsIgnoreCase(modeStr)) modeOverride = 1;
sender.pushSkedToWinTest(sked, freqKHz, notes, modeOverride); /*
} catch (Exception e) { * Win-Test mode IDs:
System.out.println("[ChatController] Error pushing sked to Win-Test: " + e.getMessage()); * 0 = CW
e.printStackTrace(); * 1 = SSB
*
* SSB is the safe fallback for missing or obsolete settings.
* The former AUTO mode was frequency-based and could not produce
* reliable results on every supported VHF, UHF and SHF band.
*/
int winTestMode =
"CW".equalsIgnoreCase(configuredMode)
? 0
: 1;
sender.pushSkedToWinTest(
sked,
winTestCallsign,
frequencyKHz,
notes,
winTestMode
);
} catch (Exception exception) {
String message =
"Error pushing sked to Win-Test: "
+ exception.getMessage();
System.out.println(
"[ChatController] " + message
);
onThreadStatus(
"WT-SkedSend",
new ThreadStateMessage(
"WT-SkedSend",
false,
message,
true
)
);
exception.printStackTrace();
} }
}, "WinTestSkedPush").start(); }, "WinTestSkedPush").start();
} }
private ChatMember resolveSkedTargetMember(String targetCallsignRaw) { /**
if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) { * Resolves the frequency used for the Win-Test sked.
*
* <ol>
* <li>Newest QRG detected for the target station on the selected band</li>
* <li>Own QRG belonging to the target's chat category</li>
* <li>No result: do not send the Win-Test sked</li>
* </ol>
*/
private Double resolveSkedFrequencyKHz(ContestSked sked) {
if (sked == null || sked.getBand() == null) {
return null; return null;
} }
List<ChatMember> matchingMembers = findActiveChatMembersByRawCall(targetCallsignRaw); Double targetFrequencyKHz =
return matchingMembers.isEmpty() ? null : matchingMembers.get(0); resolveRecentTargetFrequencyKHz(sked);
}
private String resolveSkedTargetLocator(String targetCallsignRaw) { if (targetFrequencyKHz != null) {
if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) { System.out.println(
return null; "[ChatController] SKED frequency from target: "
+ targetFrequencyKHz
+ " kHz on "
+ sked.getBand()
);
return targetFrequencyKHz;
} }
for (ChatMember member : findActiveChatMembersByRawCall(targetCallsignRaw)) { String ownQrg =
String locator = member.getQra(); resolveOwnQrgForSkedCategory(
if (locator != null && !locator.isBlank()) { sked.getTargetChatCategory()
return locator.trim().toUpperCase(Locale.ROOT); );
}
Double ownFrequencyKHz =
parseSkedFrequencyKHz(
ownQrg,
sked.getBand()
);
if (ownFrequencyKHz != null) {
System.out.println(
"[ChatController] SKED frequency from own category QRG: "
+ ownFrequencyKHz
+ " kHz on "
+ sked.getBand()
);
return ownFrequencyKHz;
} }
return null; return null;
} }
// private ChatMember resolveSkedTargetMember(String targetCallsignRaw) { /**
// if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) { * Returns the newest QRG detected on the selected band across all active
// return null; * suffix and category variants of the base callsign.
// } */
// private Double resolveRecentTargetFrequencyKHz(ContestSked sked) {
// List<ChatMember> matchingMembers = findActiveChatMembersByRawCall(targetCallsignRaw); List<ChatMember> variants =
// return matchingMembers.isEmpty() ? null : matchingMembers.get(0); findActiveChatMembersByRawCall(
// sked.getTargetCallsign()
// } );
//
// private String resolveSkedTargetLocator(String targetCallsignRaw) { long now = System.currentTimeMillis();
// if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) { long newestTimestamp = Long.MIN_VALUE;
// return null; Double newestFrequencyKHz = null;
// }
// for (ChatMember member : variants) {
// String normalizedTargetCall = normalizeCallRaw(targetCallsignRaw); if (member == null || member.getKnownActiveBands() == null) {
// continue;
// for (ChatMember member : findActiveChatMembersByRawCall(targetCallsignRaw)) { }
// String locator = member.getQra();
// if (locator != null && !locator.isBlank()) { ChatMember.ActiveFrequencyInfo frequencyInfo =
// return locator.trim().toUpperCase(Locale.ROOT); member.getKnownActiveBands().get(
// } sked.getBand()
// } );
//
// return null; if (frequencyInfo == null
// } || frequencyInfo.frequency <= 0.0
|| !sked.getBand().isPlausible(
frequencyInfo.frequency
)) {
continue;
}
long ageMs = now - frequencyInfo.timestampEpoch;
if (ageMs < 0L
|| ageMs > BandOpportunityResolver
.RECENT_DYNAMIC_EVIDENCE_MAX_AGE_MS) {
continue;
}
if (frequencyInfo.timestampEpoch > newestTimestamp) {
newestTimestamp = frequencyInfo.timestampEpoch;
newestFrequencyKHz =
frequencyInfo.frequency * 1000.0;
}
}
return newestFrequencyKHz;
}
/**
* Returns the own QRG belonging to the chat category in which the target
* station was selected.
*/
private String resolveOwnQrgForSkedCategory(
ChatCategory targetCategory) {
if (sameChatCategory(
targetCategory,
getChatCategorySecondChat())) {
return chatPreferences
.getMYQRGSecondCat()
.get();
}
return chatPreferences
.getMYQRGFirstCat()
.get();
}
private boolean sameChatCategory(ChatCategory first,
ChatCategory second) {
return first != null
&& second != null
&& first.getCategoryNumber()
== second.getCategoryNumber();
}
/**
* Parses the QRG formats used by KST4Contest and Win-Test.
*
* <p>Examples: 144.300, 144.300.03, 144300 and 144300.0.</p>
*/
private Double parseSkedFrequencyKHz(String value,
Band expectedBand) {
if (value == null
|| value.isBlank()
|| expectedBand == null) {
return null;
}
String normalized = value
.trim()
.replace(',', '.')
.replaceAll("\\s+", "");
try {
java.util.regex.Matcher groupedFrequency =
java.util.regex.Pattern.compile(
"^(\\d{2,5})\\.(\\d{3})(?:\\.(\\d{1,2}))?$"
).matcher(normalized);
if (groupedFrequency.matches()) {
double frequencyKHz =
Integer.parseInt(groupedFrequency.group(1))
* 1000.0
+ Integer.parseInt(
groupedFrequency.group(2)
);
String subKHzPart =
groupedFrequency.group(3);
if (subKHzPart != null) {
double subKHz =
Integer.parseInt(subKHzPart);
frequencyKHz +=
subKHzPart.length() == 1
? subKHz / 10.0
: subKHz / 100.0;
}
return expectedBand.isPlausible(
frequencyKHz / 1000.0
) ? frequencyKHz : null;
}
double numericValue =
Double.parseDouble(normalized);
if (expectedBand.isPlausible(numericValue)) {
return numericValue * 1000.0;
}
if (expectedBand.isPlausible(
numericValue / 1000.0)) {
return numericValue;
}
} catch (NumberFormatException ignored) {
// Invalid or unsupported QRG format.
}
return null;
}
/**
* Removes KST dash suffixes while retaining ordinary amateur-radio slash
* notation such as /P, /M or a country prefix.
*
* <p>Examples:
* DN9APW-2 -> DN9APW
* EA5/G8MBI/P-70 -> EA5/G8MBI/P
* DN9APW-2/P -> DN9APW/P</p>
*/
private String toWinTestSkedCallsign(String chatCallsign) {
if (chatCallsign == null || chatCallsign.isBlank()) {
return null;
}
String normalized =
chatCallsign.trim().toUpperCase(Locale.ROOT);
return normalized.replaceAll(
"-[^/]*(?=/|$)",
""
);
}
private void reportSkippedWinTestSked(ContestSked sked,
String reason) {
String target =
sked == null
? "unknown station"
: sked.getTargetChatCallsign();
String message =
"Win-Test sked not sent for "
+ target
+ ": "
+ reason
+ ". The internal KST4Contest sked remains active.";
System.out.println(
"[ChatController] " + message
);
onThreadStatus(
"WT-SkedSend",
new ThreadStateMessage(
"WT-SkedSend",
false,
message,
true
)
);
}
private String resolveSkedTargetLocator(
String targetCallsignRaw) {
if (targetCallsignRaw == null
|| targetCallsignRaw.isBlank()) {
return null;
}
for (ChatMember member
: findActiveChatMembersByRawCall(
targetCallsignRaw)) {
String locator = member.getQra();
if (locator != null && !locator.isBlank()) {
return locator
.trim()
.toUpperCase(Locale.ROOT);
}
}
return null;
}
public StationMetricsService getStationMetricsService() { public StationMetricsService getStationMetricsService() {
return stationMetricsService; return stationMetricsService;
@@ -1175,27 +1418,45 @@ private ObservableList<String>
} }
/** /**
* Builds the active-member key. The raw/base callsign alone is not enough * Builds the unique identity key for one active ON4KST login.
* because the same station can be logged into multiple ON4KST categories at *
* the same time. Therefore the category number is part of the key. * <p>The complete callsign must be preserved here. Callsigns such as
* {@code 9A0BB-2}, {@code 9A0BB-70} and {@code 9A0BB-144} represent different
* active chat sessions and must therefore remain separate objects, even when
* they are logged into the same chat category.</p>
*
* <p>The normalized base callsign ({@code callSignRaw}) is deliberately not
* used for this key. It remains the common station key for worked, band and
* NOT-QRV information.</p>
*/ */
private String buildActiveChatMemberKey(String callSign, ChatCategory category) { private String buildActiveChatMemberKey(String callSign, ChatCategory category) {
String normalizedCallsign = ChatMember.normalizeCallSignToBaseCallSign(callSign);
if (normalizedCallsign == null || normalizedCallsign.isBlank()) { if (callSign == null) {
return null; return null;
} }
int categoryNumber = category == null ? -1 : category.getCategoryNumber(); String fullCallSign = callSign.trim().toUpperCase(Locale.ROOT);
return normalizedCallsign.trim().toUpperCase(Locale.ROOT) + "|" + categoryNumber;
if (fullCallSign.isBlank()) {
return null;
}
int categoryNumber =
category == null ? -1 : category.getCategoryNumber();
return fullCallSign + "|" + categoryNumber;
} }
private String buildActiveChatMemberKey(ChatMember member) { private String buildActiveChatMemberKey(ChatMember member) {
if (member == null) { if (member == null) {
return null; return null;
} }
String callSign = member.getCallSignRaw() != null ? member.getCallSignRaw() : member.getCallSign(); return buildActiveChatMemberKey(
return buildActiveChatMemberKey(callSign, member.getChatCategory()); member.getCallSign(),
member.getChatCategory()
);
} }
/** /**
@@ -602,76 +602,129 @@ public class MessageBusManagementThread extends Thread {
} }
/** /**
* Resolves a message sender from the thread-safe active-member model. If a * Resolves a message sender from the thread-safe active-member model.
* CH/CR message arrives before the matching user-enter message, a marked *
* fallback sender is used. The returned sender is never null. * <p>The local login is handled before the active-user lookup because the own
* ChatMember is intentionally not stored in the visible user list. This also
* prevents another active login with the same base callsign from replacing the
* identity of our own message echo.</p>
*/ */
private ChatMember resolveInboundSender(String senderCallSign, ChatCategory category, ChatMessage message) { private ChatMember resolveInboundSender(
String senderCallSign,
ChatCategory category,
ChatMessage message
) {
String myCall =
this.client.getChatPreferences().getStn_loginCallSign();
if (senderCallSign != null
&& myCall != null
&& senderCallSign.equalsIgnoreCase(myCall)) {
ChatMember ownSender = new ChatMember();
ownSender.setCallSign(senderCallSign);
ownSender.setChatCategory(category);
ownSender.setAirPlaneReflectInfo(
new AirPlaneReflectionInfo()
);
return ownSender;
}
ChatMember lookup = new ChatMember(); ChatMember lookup = new ChatMember();
lookup.setCallSign(senderCallSign); lookup.setCallSign(senderCallSign);
lookup.setChatCategory(category); lookup.setChatCategory(category);
ChatMember senderObj = this.client.findActiveChatMember(lookup); ChatMember senderObj =
this.client.findActiveChatMember(lookup);
if (senderObj != null) { if (senderObj != null) {
senderObj.setActivityTimeLastInEpoch(new Utils4KST().time_generateCurrentEpochTime()); senderObj.setActivityTimeLastInEpoch(
new Utils4KST().time_generateCurrentEpochTime()
);
// Remember the last active category so later outgoing replies can be routed correctly. this.client.rememberLastInboundCategory(
this.client.rememberLastInboundCategory(senderObj.getCallSignRaw(), senderObj.getChatCategory()); senderObj.getCallSignRaw(),
senderObj.getChatCategory()
);
// Metrics influence priority scoring; process them after message text is known.
this.client.getStationMetricsService().onInboundMessage( this.client.getStationMetricsService().onInboundMessage(
senderObj.getCallSignRaw(), senderObj.getCallSignRaw(),
System.currentTimeMillis(), System.currentTimeMillis(),
message == null ? null : message.getMessageText(), message == null ? null : message.getMessageText(),
this.client.getChatPreferences(), this.client.getChatPreferences(),
this.client.getChatPreferences().getStn_loginCallSign() myCall
);
this.client.getScoreService().requestRecompute(
"rx-chat-message"
); );
this.client.getScoreService().requestRecompute("rx-chat-message");
return senderObj; return senderObj;
} }
ChatMember fallbackSender = new ChatMember(); ChatMember fallbackSender = new ChatMember();
String myCall = this.client.getChatPreferences().getStn_loginCallSign(); fallbackSender.setCallSign("[n/a]" + senderCallSign);
if (senderCallSign != null && senderCallSign.equalsIgnoreCase(myCall)) {
fallbackSender.setCallSign(myCall);
} else {
fallbackSender.setCallSign("[n/a]" + senderCallSign);
}
fallbackSender.setChatCategory(category); fallbackSender.setChatCategory(category);
fallbackSender.setAirPlaneReflectInfo(new AirPlaneReflectionInfo()); fallbackSender.setAirPlaneReflectInfo(
new AirPlaneReflectionInfo()
);
return fallbackSender; return fallbackSender;
} }
/** /**
* Resolves a message receiver from the active-member model. Unknown receivers * Resolves a message receiver from the thread-safe active-member model.
* are represented as explicit fallback objects instead of null. This keeps PM *
* echo display and historic messages stable even if the target station already * <p>The local login is handled before the active-user lookup. The receiver
* left the chat. * from the ON4KST packet therefore remains authoritative even if another
* station with the same base callsign is logged into the same category.</p>
*/ */
private ChatMember resolveInboundReceiver(String receiverCallSign, ChatCategory category) { private ChatMember resolveInboundReceiver(
if (receiverCallSign == null || receiverCallSign.equals("0")) { String receiverCallSign,
ChatCategory category
) {
if (receiverCallSign == null
|| receiverCallSign.equals("0")) {
return createAllReceiver(); return createAllReceiver();
} }
String myCall =
this.client.getChatPreferences().getStn_loginCallSign();
if (myCall != null
&& receiverCallSign.equalsIgnoreCase(myCall)) {
ChatMember ownReceiver = new ChatMember();
ownReceiver.setCallSign(receiverCallSign);
ownReceiver.setChatCategory(category);
ownReceiver.setAirPlaneReflectInfo(
new AirPlaneReflectionInfo()
);
return ownReceiver;
}
ChatMember lookup = new ChatMember(); ChatMember lookup = new ChatMember();
lookup.setCallSign(receiverCallSign); lookup.setCallSign(receiverCallSign);
lookup.setChatCategory(category); lookup.setChatCategory(category);
ChatMember receiverObj = this.client.findActiveChatMember(lookup); ChatMember receiverObj =
this.client.findActiveChatMember(lookup);
if (receiverObj != null) { if (receiverObj != null) {
return receiverObj; return receiverObj;
} }
ChatMember fallbackReceiver = new ChatMember(); ChatMember fallbackReceiver = new ChatMember();
String myCall = this.client.getChatPreferences().getStn_loginCallSign(); fallbackReceiver.setCallSign(receiverCallSign + "(left)");
if (receiverCallSign.equalsIgnoreCase(myCall)) {
fallbackReceiver.setCallSign(myCall);
} else {
fallbackReceiver.setCallSign(receiverCallSign + "(left)");
}
fallbackReceiver.setChatCategory(category); fallbackReceiver.setChatCategory(category);
fallbackReceiver.setAirPlaneReflectInfo(new AirPlaneReflectionInfo()); fallbackReceiver.setAirPlaneReflectInfo(
new AirPlaneReflectionInfo()
);
return fallbackReceiver; return fallbackReceiver;
} }
@@ -1320,9 +1373,12 @@ public class MessageBusManagementThread extends Thread {
newDXCListSender3.setQra(splittedMessageLine[5]); newDXCListSender3.setQra(splittedMessageLine[5]);
ChatMember newDXCListReceiver3 = new ChatMember(); ChatMember newDXCListReceiver3 = new ChatMember();
// newDXCListReceiver3.setFrequency(splittedMessageLine[4]);
newDXCListReceiver3.setCallSign(splittedMessageLine[4]); newDXCListReceiver3.setCallSign(splittedMessageLine[4]);
newDXCListReceiver3.setQra(splittedMessageLine[5]); /*
* MA format:
* MA|0|epoch|sender|receiver|sender locator|receiver locator|
*/
newDXCListReceiver3.setQra(splittedMessageLine[6]);
dxcMsg3.setSender(newDXCListSender3); dxcMsg3.setSender(newDXCListSender3);
dxcMsg3.setReceiver(newDXCListReceiver3); dxcMsg3.setReceiver(newDXCListReceiver3);
@@ -166,7 +166,15 @@ public final class ScoreService {
scoreByCallRaw.put(callRaw, score); scoreByCallRaw.put(callRaw, score);
preferredCategoryByCallRaw.put(callRaw, representative.getChatCategory()); preferredCategoryByCallRaw.put(callRaw, representative.getChatCategory());
topAll.add(new TopCandidate(callRaw, representative.getCallSign(), representative.getChatCategory(), score)); if (Double.isFinite(score) && score > 0.0) {
topAll.add(new TopCandidate(
callRaw,
representative.getCallSign(),
representative.getChatCategory(),
score
));
}
} }
// 3) Build Top-N // 3) Build Top-N
@@ -227,12 +235,11 @@ public final class ScoreService {
ChatMember chosen = null; ChatMember chosen = null;
if (preferredCat != null) { if (preferredCat != null) {
for (ChatMember v : variants) { chosen = variants.stream()
if (v != null && v.getChatCategory() == preferredCat) { .filter(Objects::nonNull)
chosen = v; .filter(v -> isSameChatCategory(v.getChatCategory(), preferredCat))
break; .max(Comparator.comparingLong(ChatMember::getActivityTimeLastInEpoch))
} .orElse(null);
}
} }
if (chosen == null) { if (chosen == null) {
@@ -247,6 +254,14 @@ public final class ScoreService {
return representative; return representative;
} }
private static boolean isSameChatCategory(ChatCategory left, ChatCategory right) {
return left != null
&& right != null
&& left.getCategoryNumber() == right.getCategoryNumber();
}
/** /**
* Projects the immutable score snapshot back into ChatMember display fields so * Projects the immutable score snapshot back into ChatMember display fields so
* the normal station table can sort/filter by score without knowing the score * the normal station table can sort/filter by score without knowing the score
@@ -2,6 +2,7 @@ package kst4contest.controller;
import kst4contest.logic.SignalDetector; import kst4contest.logic.SignalDetector;
import kst4contest.model.ChatPreferences; import kst4contest.model.ChatPreferences;
import kst4contest.model.ChatMember;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
@@ -21,7 +22,7 @@ import java.util.regex.Pattern;
public final class StationMetricsService { public final class StationMetricsService {
/** /cq <CALL> ... */ /** /cq <CALL> ... */
private static final Pattern OUTBOUND_CQ_PATTERN = Pattern.compile("(?i)^\\s*/cq\\s+([A-Z0-9/]+)\\b.*"); private static final Pattern OUTBOUND_CQ_PATTERN = Pattern.compile("(?i)^\\s*/cq\\s+([A-Z0-9/-]+)\\b.*");
/** Rolling window timestamps for momentum scoring. */ /** Rolling window timestamps for momentum scoring. */
private static final int MAX_STORED_INBOUND_TIMESTAMPS = 32; private static final int MAX_STORED_INBOUND_TIMESTAMPS = 32;
@@ -195,7 +196,7 @@ public final class StationMetricsService {
private static String normalizeCallRaw(String s) { private static String normalizeCallRaw(String s) {
if (s == null) return null; if (s == null) return null;
return s.trim().toUpperCase(); return ChatMember.normalizeCallSignToBaseCallSign(s);
} }
private static final class StationMetrics { private static final class StationMetrics {
@@ -39,26 +39,63 @@ public class WinTestSkedSender {
} }
/** /**
* Pushes a ContestSked into Win-Test by sending the LOCKSKED / ADDSKED / UNLOCKSKED * Pushes a ContestSked into Win-Test by sending the
* sequence via UDP broadcast. * LOCKSKED / ADDSKED / UNLOCKSKED sequence.
* *
* @param sked the sked to push * @param sked sked to push
* @param frequencyKHz current operating frequency in kHz (e.g. 144321.0) * @param targetCallsign callsign prepared for Win-Test
* @param notes free-text notes (e.g. "[JO62QM - 123°] sked via KST") * @param frequencyKHz operating frequency in kHz
* @param notes optional notes
* @param mode Win-Test mode ID: 0 for CW, 1 for SSB
*/ */
public void pushSkedToWinTest(ContestSked sked, double frequencyKHz, String notes, int modeOverride) { public void pushSkedToWinTest(ContestSked sked,
String targetCallsign,
double frequencyKHz,
String notes,
int mode) {
try { try {
sendLockSked(); sendLockSked();
sendAddSked(sked, frequencyKHz, notes, modeOverride);
sendAddSked(
sked,
targetCallsign,
frequencyKHz,
notes,
mode
);
sendUnlockSked(); sendUnlockSked();
reportStatus("Sked pushed to WT: " + sked.getTargetCallsign(), false); reportStatus(
System.out.println("[WinTestSkedSender] Sked pushed: " + sked.getTargetCallsign() "Sked pushed to WT: " + targetCallsign,
+ " at " + frequencyKHz + " kHz, band=" + sked.getBand()); false
} catch (Exception e) { );
reportStatus("ERROR pushing sked: " + e.getMessage(), true);
System.out.println("[WinTestSkedSender] Error pushing sked: " + e.getMessage()); System.out.println(
e.printStackTrace(); "[WinTestSkedSender] Sked pushed: "
+ targetCallsign
+ " at "
+ frequencyKHz
+ " kHz, band="
+ sked.getBand()
+ ", mode="
+ mode
);
} catch (Exception exception) {
reportStatus(
"ERROR pushing sked: "
+ exception.getMessage(),
true
);
System.out.println(
"[WinTestSkedSender] Error pushing sked: "
+ exception.getMessage()
);
exception.printStackTrace();
} }
} }
@@ -86,46 +123,53 @@ public class WinTestSkedSender {
/** /**
* Sends an ADDSKED message with the sked details. * Sends an ADDSKED message with the sked details.
* <p> *
* Win-Test ADDSKED data format (from wtKST): * <p>The wtKST implementation subtracts a reference time of
* <pre> * 1970-01-01 00:01:00 UTC and subsequently adds 60 seconds. Both
* {epoch_seconds} {freq_in_0.1kHz} {bandId} {mode} "{callsign}" "{notes}" * operations cancel each other out. The transmitted value is therefore
* </pre> * an ordinary Unix timestamp and must not receive another offset here.</p>
* <p>
* Win-Test uses a timestamp reference of 1970-01-01 00:01:00 UTC (60s offset from Unix epoch).
* The C# code adds 60 seconds to compensate.
*/ */
private void sendAddSked(ContestSked sked, double frequencyKHz, String notes, int modeOverride) throws Exception { private void sendAddSked(ContestSked sked,
// Win-Test timestamp: epoch seconds with 60s offset String targetCallsign,
long epochSeconds = sked.getSkedTimeEpoch() / 1000; double frequencyKHz,
long wtTimestamp = epochSeconds + 60; String notes,
int mode) throws Exception {
// Frequency in 0.1 kHz units (Win-Test convention): multiply kHz by 10 long wtTimestamp =
long freqTenthKHz = Math.round(frequencyKHz * 10.0); sked.getSkedTimeEpoch() / 1000L;
// Win-Test band ID // Frequency in 0.1 kHz units.
int bandId = toWinTestBandId(sked.getBand()); long frequencyTenthKHz =
Math.round(frequencyKHz * 10.0);
// Mode: -1 = auto-detect from frequency, 0 = CW, 1 = SSB int bandId =
int mode; toWinTestBandId(sked.getBand());
if (modeOverride >= 0) {
mode = modeOverride;
} else {
mode = isInSsbSegment(frequencyKHz) ? 1 : 0;
}
String data = wtTimestamp /*
+ " " + freqTenthKHz * Accept only the mode IDs supported by this UI.
+ " " + bandId * Any unexpected value falls back to SSB.
+ " " + mode */
+ " \"" + sked.getTargetCallsign() + "\"" int winTestMode =
+ " \"" + (notes != null ? notes : "") + "\""; mode == 0
? 0
: 1;
WinTestMessage msg = new WinTestMessage( String data =
wtTimestamp
+ " " + frequencyTenthKHz
+ " " + bandId
+ " " + winTestMode
+ " \"" + targetCallsign + "\""
+ " \"" + (notes != null ? notes : "") + "\"";
WinTestMessage message = new WinTestMessage(
WinTestMessage.MessageType.ADDSKED, WinTestMessage.MessageType.ADDSKED,
stationName, "", stationName,
data); "",
sendUdp(msg); data
);
sendUdp(message);
} }
/** /**
@@ -168,18 +212,6 @@ public class WinTestSkedSender {
}; };
} }
/**
* Very simple SSB segment heuristic.
* A more complete implementation would check actual mode from Win-Test STATUS.
*/
private boolean isInSsbSegment(double frequencyKHz) {
// SSB segments (kHz ranges)
if (frequencyKHz >= 144300 && frequencyKHz <= 144399) return true; // 2m SSB
if (frequencyKHz >= 432200 && frequencyKHz <= 432399) return true; // 70cm SSB
if (frequencyKHz >= 1296200 && frequencyKHz <= 1296399) return true; // 23cm SSB
return false;
}
private void reportStatus(String text, boolean isError) { private void reportStatus(String text, boolean isError) {
if (callback != null) { if (callback != null) {
callback.onThreadStatus(THREAD_NICKNAME, callback.onThreadStatus(THREAD_NICKNAME,
@@ -50,7 +50,7 @@ public class ChatPreferences {
* Reading must stay backwards compatible: missing/unknown tags should fall back to defaults. * Reading must stay backwards compatible: missing/unknown tags should fall back to defaults.
*/ */
// private static final int CONFIG_VERSION = 2; // private static final int CONFIG_VERSION = 2;
public static final int CONFIG_VERSION = 4; public static final int CONFIG_VERSION = 5;
// Prefer writing tag names that mirror variable names (human readable). Keep legacy tags for compatibility. // Prefer writing tag names that mirror variable names (human readable). Keep legacy tags for compatibility.
private static final String TAG_CONFIG_VERSION = "configVersion"; private static final String TAG_CONFIG_VERSION = "configVersion";
@@ -224,7 +224,7 @@ public class ChatPreferences {
boolean logsynch_wintestNetworkListenerEnabled = true; // default true = bisheriges Verhalten boolean logsynch_wintestNetworkListenerEnabled = true; // default true = bisheriges Verhalten
String logsynch_wintestNetworkBroadcastAddress = "255.255.255.255"; // UDP broadcast address for sending to Win-Test String logsynch_wintestNetworkBroadcastAddress = "255.255.255.255"; // UDP broadcast address for sending to Win-Test
boolean logsynch_wintestNetworkSkedPushEnabled = false; // push SKEDs to Win-Test via UDP boolean logsynch_wintestNetworkSkedPushEnabled = false; // push SKEDs to Win-Test via UDP
String logsynch_wintestSkedMode = "SSB"; // CW, SSB or AUTO String logsynch_wintestSkedMode = "SSB"; // Supported values: SSB or CW
boolean logsynch_wintestQrgSyncEnabled = true; // sync QRG from Win-Test STATUS packet boolean logsynch_wintestQrgSyncEnabled = true; // sync QRG from Win-Test STATUS packet
boolean logsynch_wintestUsePassQrg = false; // use pass frequency instead of main QRG from STATUS packet boolean logsynch_wintestUsePassQrg = false; // use pass frequency instead of main QRG from STATUS packet
@@ -339,6 +339,7 @@ public class ChatPreferences {
private double[] GUIstationMapStageSceneSizeHW = new double[] { 1000, 800 }; private double[] GUIstationMapStageSceneSizeHW = new double[] { 1000, 800 };
private double[] GUIstationMapStagePositionXY = new double[] { Double.NaN, Double.NaN }; private double[] GUIstationMapStagePositionXY = new double[] { Double.NaN, Double.NaN };
private boolean GUIstationMapPathAnalysisVisible = true;
/********************************************************************************* /*********************************************************************************
@@ -635,6 +636,14 @@ public class ChatPreferences {
this.GUIstationMapStagePositionXY = GUIstationMapStagePositionXY; this.GUIstationMapStagePositionXY = GUIstationMapStagePositionXY;
} }
public boolean isGUIstationMapPathAnalysisVisible() {
return GUIstationMapPathAnalysisVisible;
}
public void setGUIstationMapPathAnalysisVisible(boolean GUIstationMapPathAnalysisVisible) {
this.GUIstationMapPathAnalysisVisible = GUIstationMapPathAnalysisVisible;
}
public boolean isGuiOptions_defaultFilterNothing() { public boolean isGuiOptions_defaultFilterNothing() {
return guiOptions_defaultFilterNothing; return guiOptions_defaultFilterNothing;
} }
@@ -2048,6 +2057,12 @@ public class ChatPreferences {
); );
guiOptions.appendChild(GUIstationMapStagePositionXY); guiOptions.appendChild(GUIstationMapStagePositionXY);
Element GUIstationMapPathAnalysisVisible = doc.createElement("GUIstationMapPathAnalysisVisible");
GUIstationMapPathAnalysisVisible.setTextContent(
String.valueOf(this.isGUIstationMapPathAnalysisVisible())
);
guiOptions.appendChild(GUIstationMapPathAnalysisVisible);
/**************************************************************************************** /****************************************************************************************
****************************** now write this XML! ************************************* ****************************** now write this XML! *************************************
****************************************************************************************/ ****************************************************************************************/
@@ -2423,8 +2438,11 @@ public class ChatPreferences {
notify_noReplyPenaltyMinutes = noReply; notify_noReplyPenaltyMinutes = noReply;
} }
Integer momentum = getInt(notificationsEl, 666, "notify_momentumWindowSeconds"); Integer momentum = getInt(
if (momentum != null) { notificationsEl,
notify_momentumWindowSeconds,
"notify_momentumWindowSeconds"
); if (momentum != null) {
notify_momentumWindowSeconds = momentum; notify_momentumWindowSeconds = momentum;
} }
@@ -2780,6 +2798,17 @@ public class ChatPreferences {
this.getGUIstationMapStagePositionXY() this.getGUIstationMapStagePositionXY()
); );
/*
* Files written before config version 5 do not contain this value.
* Keep the default true in that case so existing users discover the
* path-analysis feature before choosing to hide it themselves.
*/
this.setGUIstationMapPathAnalysisVisible(getBoolean(
element,
this.isGUIstationMapPathAnalysisVisible(),
"GUIstationMapPathAnalysisVisible"
));
// Splitpane divider positions // Splitpane divider positions
String s1 = getText(element, null, "GUIselectedCallSignSplitPane_dividerposition"); String s1 = getText(element, null, "GUIselectedCallSignSplitPane_dividerposition");
if (s1 != null) { if (s1 != null) {
@@ -3,25 +3,58 @@ package kst4contest.model;
/** /**
* Represents a scheduled event or an AirScout opportunity in the future. * Represents a scheduled event or an AirScout opportunity in the future.
* Used for the Timeline View and Priority Calculation. * Used for the Timeline View and Priority Calculation.
*
* <p>The base callsign remains the grouping key for scoring and worked-state
* handling. The exact KST login and its chat category are stored separately
* because reminders and external logger handover refer to the selected
* ChatMember entity.</p>
*/ */
public class ContestSked { public class ContestSked {
private String targetCallsign; private String targetCallsign;
private double targetAzimuth; // Required for Antenna-Visuals private String targetChatCallsign;
private long skedTimeEpoch; // The peak time (e.g., AP) private ChatCategory targetChatCategory;
private double targetAzimuth;
private long skedTimeEpoch;
private Band band; private Band band;
// Opportunity potential (0..100). -1 means "unknown". // Opportunity potential (0..100). -1 means "unknown".
int opportunityPotentialPercent = -1; int opportunityPotentialPercent = -1;
// Status flags to prevent spamming alarms // Status flags to prevent spamming alarms.
private boolean warning3MinSent = false; private boolean warning3MinSent = false;
private boolean warningNowSent = false; private boolean warningNowSent = false;
public ContestSked(String call, double azimuth, long time, Band b) { /**
this.targetCallsign = call; * Backward-compatible constructor.
*/
public ContestSked(String call, double azimuth, long time, Band band) {
this(call, call, null, azimuth, time, band);
}
/**
* Creates a sked for one exact KST login.
*
* @param callRaw base callsign used for scoring and worked states
* @param chatCallsign exact KST login, including an optional dash suffix
* @param chatCategory category in which the selected login is active
* @param azimuth target azimuth
* @param time sked time in epoch milliseconds
* @param band selected amateur-radio band
*/
public ContestSked(String callRaw,
String chatCallsign,
ChatCategory chatCategory,
double azimuth,
long time,
Band band) {
this.targetCallsign = callRaw;
this.targetChatCallsign = chatCallsign;
this.targetChatCategory = chatCategory;
this.targetAzimuth = azimuth; this.targetAzimuth = azimuth;
this.skedTimeEpoch = time; this.skedTimeEpoch = time;
this.band = b; this.band = band;
} }
/** /**
@@ -32,15 +65,54 @@ public class ContestSked {
return (skedTimeEpoch - System.currentTimeMillis()) / 1000; return (skedTimeEpoch - System.currentTimeMillis()) / 1000;
} }
// Getters and Setters... /**
public String getTargetCallsign() { return targetCallsign; } * Returns the base callsign used for scoring and worked-state grouping.
public double getTargetAzimuth() { return targetAzimuth; } */
public long getSkedTimeEpoch() { return skedTimeEpoch; } public String getTargetCallsign() {
public Band getBand() { return band; } return targetCallsign;
public boolean isWarning3MinSent() { return warning3MinSent; } }
public void setWarning3MinSent(boolean b) { this.warning3MinSent = b; }
public boolean isWarningNowSent() { return warningNowSent; } /**
public void setWarningNowSent(boolean b) { this.warningNowSent = b; } * Returns the exact KST login selected when the sked was created.
*/
public String getTargetChatCallsign() {
if (targetChatCallsign == null || targetChatCallsign.isBlank()) {
return targetCallsign;
}
return targetChatCallsign;
}
public ChatCategory getTargetChatCategory() {
return targetChatCategory;
}
public double getTargetAzimuth() {
return targetAzimuth;
}
public long getSkedTimeEpoch() {
return skedTimeEpoch;
}
public Band getBand() {
return band;
}
public boolean isWarning3MinSent() {
return warning3MinSent;
}
public void setWarning3MinSent(boolean warning3MinSent) {
this.warning3MinSent = warning3MinSent;
}
public boolean isWarningNowSent() {
return warningNowSent;
}
public void setWarningNowSent(boolean warningNowSent) {
this.warningNowSent = warningNowSent;
}
public int getOpportunityPotentialPercent() { public int getOpportunityPotentialPercent() {
return opportunityPotentialPercent; return opportunityPotentialPercent;
@@ -0,0 +1,73 @@
package kst4contest.test;
import kst4contest.model.ChatPreferences;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ChatPreferencesStationMapVisibilityTest {
@TempDir
Path temporaryDirectory;
@Test
void pathAnalysisIsVisibleByDefault() {
ChatPreferences preferences = new ChatPreferences();
assertTrue(preferences.isGUIstationMapPathAnalysisVisible());
}
@Test
void hiddenPathAnalysisStateSurvivesXmlRoundTrip() throws IOException {
Path preferencesFile = temporaryDirectory.resolve("preferences.xml");
ChatPreferences writtenPreferences = new ChatPreferences();
writtenPreferences.setStoreAndRestorePreferencesFileName(
preferencesFile.toString());
writtenPreferences.setGUIstationMapPathAnalysisVisible(false);
writtenPreferences.writePreferencesToXmlFile();
String writtenXml = Files.readString(preferencesFile);
assertTrue(writtenXml.contains(
"<GUIstationMapPathAnalysisVisible>false"
+ "</GUIstationMapPathAnalysisVisible>"));
ChatPreferences restoredPreferences = new ChatPreferences();
restoredPreferences.setStoreAndRestorePreferencesFileName(
preferencesFile.toString());
restoredPreferences.readPreferencesFromXmlFile();
assertFalse(restoredPreferences.isGUIstationMapPathAnalysisVisible());
}
@Test
void legacyXmlWithoutVisibilitySettingKeepsAnalysisDiscoverable()
throws IOException {
Path legacyPreferencesFile =
temporaryDirectory.resolve("legacy-preferences.xml");
Files.writeString(legacyPreferencesFile, """
<?xml version="1.0" encoding="UTF-8"?>
<praktiKST>
<configVersion>4</configVersion>
<guiOptions>
<GUIstationMapStageSceneSizeHW>1000.0;800.0</GUIstationMapStageSceneSizeHW>
</guiOptions>
</praktiKST>
""");
ChatPreferences restoredPreferences = new ChatPreferences();
restoredPreferences.setStoreAndRestorePreferencesFileName(
legacyPreferencesFile.toString());
restoredPreferences.readPreferencesFromXmlFile();
assertTrue(restoredPreferences.isGUIstationMapPathAnalysisVisible());
}
}
@@ -583,6 +583,84 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
}; };
} }
/**
* Chooses a useful initial band for a new sked.
*
* <p>Recent frequency evidence has priority over station-name information.
* Manual NOT-QRV exclusions are respected. The operator can still select
* another locally enabled band from the dropdown.</p>
*/
private Band resolveDefaultSkedBand(ChatMember selectedMember,
EnumSet<Band> enabledBands) {
if (selectedMember == null || enabledBands == null || enabledBands.isEmpty()) {
return null;
}
List<ChatMember> variants =
chatcontroller.findActiveChatMembersByRawCall(
selectedMember.getCallSignRaw()
);
if (variants.isEmpty()) {
variants = List.of(selectedMember);
}
BandOpportunityResolver.Resolution resolution =
BandOpportunityResolver.resolve(
variants,
System.currentTimeMillis()
);
EnumSet<Band> availableBands = resolution.getAvailableBands();
availableBands.retainAll(enabledBands);
Band newestFrequencyBand = null;
long newestTimestamp = Long.MIN_VALUE;
long now = System.currentTimeMillis();
for (ChatMember member : variants) {
if (member == null || member.getKnownActiveBands() == null) {
continue;
}
for (Map.Entry<Band, ChatMember.ActiveFrequencyInfo> entry
: member.getKnownActiveBands().entrySet()) {
Band band = entry.getKey();
ChatMember.ActiveFrequencyInfo info = entry.getValue();
if (band == null
|| info == null
|| !availableBands.contains(band)
|| !band.isPlausible(info.frequency)) {
continue;
}
long ageMs = now - info.timestampEpoch;
if (ageMs < 0L
|| ageMs > BandOpportunityResolver.RECENT_DYNAMIC_EVIDENCE_MAX_AGE_MS) {
continue;
}
if (info.timestampEpoch > newestTimestamp) {
newestTimestamp = info.timestampEpoch;
newestFrequencyBand = band;
}
}
}
if (newestFrequencyBand != null) {
return newestFrequencyBand;
}
if (!availableBands.isEmpty()) {
return availableBands.iterator().next();
}
return enabledBands.iterator().next();
}
/** /**
* This method generates a BoderPane which shows some additional information about a callsign which had been * This method generates a BoderPane which shows some additional information about a callsign which had been
@@ -667,56 +745,158 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
selectedCallSignDownerSiteGridPane.add(priorityRow, 0, 5, 1, 1); selectedCallSignDownerSiteGridPane.add(priorityRow, 0, 5, 1, 1);
ChoiceBox<Integer> cbSkedMinutes = new ChoiceBox<>(FXCollections.observableArrayList(2, 3, 4, 5, 6,7,8,9, 10,11,12,13,14, 15, 20)); ChoiceBox<Integer> cbSkedMinutes = new ChoiceBox<>(
FXCollections.observableArrayList(
2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 20
)
);
cbSkedMinutes.getSelectionModel().select(Integer.valueOf(5)); cbSkedMinutes.getSelectionModel().select(Integer.valueOf(5));
ChoiceBox<String> cbSkedMode = new ChoiceBox<>(FXCollections.observableArrayList("AUTO", "SSB", "CW")); EnumSet<Band> enabledSkedBands =
String configuredSkedMode = this.chatcontroller.getChatPreferences().getLogsynch_wintestSkedMode(); BandOpportunityResolver.getEnabledStationBands(
if (configuredSkedMode == null || configuredSkedMode.isBlank()) { chatcontroller.getChatPreferences()
configuredSkedMode = "AUTO"; );
}
String configuredSkedModeUpper = configuredSkedMode.trim().toUpperCase(java.util.Locale.ROOT);
if (!"AUTO".equals(configuredSkedModeUpper)
&& !"SSB".equals(configuredSkedModeUpper)
&& !"CW".equals(configuredSkedModeUpper)) {
configuredSkedModeUpper = "AUTO";
}
cbSkedMode.setValue(configuredSkedModeUpper);
cbSkedMode.setTooltip(new Tooltip("Mode for Win-Test ADDSKED packets"));
cbSkedMode.setOnAction(e ->
chatcontroller.getChatPreferences().setLogsynch_wintestSkedMode(cbSkedMode.getValue()));
ChoiceBox<String> cbReminderOffsets = new ChoiceBox<>(FXCollections.observableArrayList("2+1", "5+2+1", "10+5+2+1")); ChoiceBox<Band> cbSkedBand = new ChoiceBox<>(
FXCollections.observableArrayList(enabledSkedBands)
);
cbSkedBand.setConverter(new StringConverter<>() {
@Override
public String toString(Band band) {
return band == null ? "" : bandToHumanLabel(band);
}
@Override
public Band fromString(String value) {
return null;
}
});
cbSkedBand.setPrefWidth(75);
cbSkedBand.setTooltip(new Tooltip(
"Band for this sked. The initial value is derived from recent "
+ "QRG information or the station name. Only bands enabled "
+ "for your own station are offered."
));
Band defaultSkedBand =
resolveDefaultSkedBand(
selectedCallSignInfoStageChatMember,
enabledSkedBands
);
if (defaultSkedBand != null) {
cbSkedBand.setValue(defaultSkedBand);
}
ChoiceBox<String> cbSkedMode = new ChoiceBox<>(
FXCollections.observableArrayList("SSB", "CW")
);
String configuredSkedMode =
chatcontroller.getChatPreferences()
.getLogsynch_wintestSkedMode();
if (configuredSkedMode == null
|| (!"SSB".equalsIgnoreCase(configuredSkedMode)
&& !"CW".equalsIgnoreCase(configuredSkedMode))) {
configuredSkedMode = "SSB";
}
cbSkedMode.setValue(
configuredSkedMode.trim().toUpperCase(Locale.ROOT)
);
cbSkedMode.setTooltip(new Tooltip(
"Mode transferred to Win-Test with the ADDSKED packet"
));
cbSkedMode.setOnAction(e ->
chatcontroller.getChatPreferences()
.setLogsynch_wintestSkedMode(
cbSkedMode.getValue()
)
);
ChoiceBox<String> cbReminderOffsets = new ChoiceBox<>(
FXCollections.observableArrayList(
"2+1",
"5+2+1",
"10+5+2+1"
)
);
cbReminderOffsets.getSelectionModel().select("2+1"); cbReminderOffsets.getSelectionModel().select("2+1");
CheckBox chkPmReminders = new CheckBox("Remind-PM in "); CheckBox chkPmReminders = new CheckBox("Remind-PM in ");
Button btnCreateSked = new Button("Create sked"); Button btnCreateSked = new Button("Create sked");
btnCreateSked.setTooltip(new Tooltip("Creates a sked entry and boosts priority (ramp-up).")); btnCreateSked.setTooltip(new Tooltip(
"Creates a sked entry and boosts priority during the approach."
));
btnCreateSked.setOnAction(e -> { btnCreateSked.setOnAction(e -> {
ChatMember sel = chatcontroller.getScoreService().selectedChatMemberProperty().get(); ChatMember selectedMember =
if (sel == null) return; chatcontroller.getScoreService()
.selectedChatMemberProperty()
.get();
if (cbSkedMode.getValue() != null) { if (selectedMember == null) {
chatcontroller.getChatPreferences().setLogsynch_wintestSkedMode(cbSkedMode.getValue()); return;
} }
int minutes = cbSkedMinutes.getValue() == null ? 5 : cbSkedMinutes.getValue(); Band selectedBand = cbSkedBand.getValue();
long skedTime = System.currentTimeMillis() + minutes * 60_000L; if (selectedBand == null) {
showUserInputErrorWindow(
"No sked band is available. Enable at least one band "
+ "under \"My station uses ...\" before creating a sked."
);
return;
}
double az = sel.getQTFdirection() != null ? sel.getQTFdirection() : 0.0; if (cbSkedMode.getValue() != null) {
chatcontroller.getChatPreferences()
.setLogsynch_wintestSkedMode(cbSkedMode.getValue());
}
// band is not strictly required for scoring; keep current category context int minutes =
Band band = Band.B_144; // if you want, replace with a real dropdown later cbSkedMinutes.getValue() == null
ContestSked sked = new ContestSked(sel.getCallSignRaw(), az, skedTime, band); ? 5
: cbSkedMinutes.getValue();
long skedTime =
System.currentTimeMillis() + minutes * 60_000L;
double azimuth =
selectedMember.getQTFdirection() != null
? selectedMember.getQTFdirection()
: 0.0;
ContestSked sked = new ContestSked(
selectedMember.getCallSignRaw(),
selectedMember.getCallSign(),
selectedMember.getChatCategory(),
azimuth,
skedTime,
selectedBand
);
chatcontroller.addSked(sked); chatcontroller.addSked(sked);
chatcontroller.getScoreService().requestRecompute("sked-created"); chatcontroller.getScoreService()
.requestRecompute("sked-created");
if (chkPmReminders.isSelected()) { if (chkPmReminders.isSelected()) {
List<Integer> offsets = parseMinuteOffsets(cbReminderOffsets.getValue()); List<Integer> offsets =
chatcontroller.getSkedReminderService().armReminders(sel.getCallSignRaw(), sel.getChatCategory(), skedTime, offsets); parseMinuteOffsets(cbReminderOffsets.getValue());
chatcontroller.getSkedReminderService().armReminders(
sked.getTargetChatCallsign(),
sked.getTargetChatCategory(),
skedTime,
offsets
);
} }
}); });
@@ -728,6 +908,13 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
); );
skedTimeGroup.setAlignment(Pos.CENTER_LEFT); skedTimeGroup.setAlignment(Pos.CENTER_LEFT);
HBox skedBandGroup = new HBox(
4,
new Label("Band"),
cbSkedBand
);
skedBandGroup.setAlignment(Pos.CENTER_LEFT);
HBox skedModeGroup = new HBox( HBox skedModeGroup = new HBox(
4, 4,
new Label("Mode"), new Label("Mode"),
@@ -750,12 +937,19 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
skedRow.setAlignment(Pos.CENTER_LEFT); skedRow.setAlignment(Pos.CENTER_LEFT);
skedRow.getChildren().addAll( skedRow.getChildren().addAll(
skedTimeGroup, skedTimeGroup,
skedBandGroup,
skedModeGroup, skedModeGroup,
btnCreateSked, btnCreateSked,
skedReminderGroup skedReminderGroup
); );
selectedCallSignDownerSiteGridPane.add(skedRow, 0, 6, 2, 1); selectedCallSignDownerSiteGridPane.add(
skedRow,
0,
6,
2,
1
);
GridPane.setHgrow(skedRow, Priority.ALWAYS); GridPane.setHgrow(skedRow, Priority.ALWAYS);
@@ -2431,24 +2625,32 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
} }
/** /**
* Compares two ChatMember objects by their logical identity. * Compares two ChatMember objects by their active chat identity.
* *
* JavaFX may replace item instances during refreshes, so object identity alone is * <p>JavaFX may replace item instances during list refreshes, so object
* not enough. For selection stability we compare callsign/raw callsign and chat * identity alone is not sufficient. The complete callsign and the chat
* category number. * category identify one active ON4KST login.</p>
*
* <p>The raw/base callsign must not be used here. Otherwise callsigns such as
* {@code 9A0BB-2} and {@code 9A0BB-70} would still be treated as the same
* selection inside one chat category.</p>
*/ */
private boolean isSameLogicalChatMember(ChatMember a, ChatMember b) { private boolean isSameLogicalChatMember(ChatMember a, ChatMember b) {
if (a == b) { if (a == b) {
return true; return true;
} }
if (a == null || b == null) { if (a == null || b == null) {
return false; return false;
} }
String aCall = a.getCallSignRaw() != null ? a.getCallSignRaw() : a.getCallSign(); String aCallSign = a.getCallSign();
String bCall = b.getCallSignRaw() != null ? b.getCallSignRaw() : b.getCallSign(); String bCallSign = b.getCallSign();
if (aCall == null || bCall == null || !aCall.equalsIgnoreCase(bCall)) { if (aCallSign == null
|| bCallSign == null
|| !aCallSign.equalsIgnoreCase(bCallSign)) {
return false; return false;
} }
@@ -2458,11 +2660,13 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (aCategory == bCategory) { if (aCategory == bCategory) {
return true; return true;
} }
if (aCategory == null || bCategory == null) { if (aCategory == null || bCategory == null) {
return false; return false;
} }
return aCategory.getCategoryNumber() == bCategory.getCategoryNumber(); return aCategory.getCategoryNumber()
== bCategory.getCategoryNumber();
} }
/** /**
@@ -3689,12 +3893,12 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
public ObservableValue<String> call(CellDataFeatures<ClusterMessage, String> cellDataFeatures) { public ObservableValue<String> call(CellDataFeatures<ClusterMessage, String> cellDataFeatures) {
SimpleStringProperty locRX = new SimpleStringProperty(); SimpleStringProperty locRX = new SimpleStringProperty();
if (cellDataFeatures.getValue().getSender() != null) { if (cellDataFeatures.getValue().getReceiver() != null) {
locRX.setValue(cellDataFeatures.getValue().getReceiver().getQra()); locRX.setValue(cellDataFeatures.getValue().getReceiver().getQra());
} else { } else {
locRX.setValue("");// TODO: Prevents a bug of not setting all values as a default locRX.setValue("");
} }
return locRX; return locRX;
} }
@@ -3928,7 +4132,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
}); });
TableColumn<ChatMessage, String> workedTXCol = new TableColumn<ChatMessage, String>("wkd TX?"); TableColumn<ChatMessage, String> workedTXCol = new TableColumn<ChatMessage, String>("wkd TX?");
workedRXCol.setCellValueFactory(new Callback<CellDataFeatures<ChatMessage, String>, ObservableValue<String>>() { workedTXCol.setCellValueFactory(new Callback<CellDataFeatures<ChatMessage, String>, ObservableValue<String>>() {
@Override @Override
public ObservableValue<String> call(CellDataFeatures<ChatMessage, String> cellDataFeatures) { public ObservableValue<String> call(CellDataFeatures<ChatMessage, String> cellDataFeatures) {
@@ -4281,32 +4485,24 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
} }
private ChatMember resolveChatMemberForTopCandidate(kst4contest.controller.ScoreService.TopCandidate c) { private ChatMember resolveChatMemberForTopCandidate(
kst4contest.controller.ScoreService.TopCandidate c
) {
String callRaw = c.getCallSignRaw(); if (c == null
ChatCategory preferredCategory = c.getPreferredChatCategory(); || c.getDisplayCallSign() == null
|| c.getPreferredChatCategory() == null) {
// 1) Prefer exact (callRaw + category) match return null;
synchronized (chatcontroller.getLst_chatMemberList()) {
for (ChatMember m : chatcontroller.getLst_chatMemberList()) {
if (m == null) continue;
if (m.getCallSignRaw() == null) continue;
if (!m.getCallSignRaw().equalsIgnoreCase(callRaw)) continue;
if (preferredCategory != null && preferredCategory.equals(m.getChatCategory())) {
return m;
}
}
// 2) Fallback: any variant with the same callsignRaw
for (ChatMember m : chatcontroller.getLst_chatMemberList()) {
if (m == null) continue;
if (m.getCallSignRaw() == null) continue;
if (m.getCallSignRaw().equalsIgnoreCase(callRaw)) return m;
}
} }
return null; /*
* Resolve the concrete active login by full callsign and category. Falling
* back to callSignRaw could select a different suffix in the same category.
*/
return chatcontroller.findActiveChatMember(
c.getDisplayCallSign(),
c.getPreferredChatCategory()
);
} }
@@ -5451,23 +5647,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
btnSkedWarnIndicator.setTooltip(tipSkedWarnIndicator); btnSkedWarnIndicator.setTooltip(tipSkedWarnIndicator);
} }
private void maybeShowSkedWarnIndicator(String key, ThreadStateMessage msg) {
if (msg == null) return;
String text = msg.getRunningInformationTextDescription();
if (text == null || text.isBlank()) text = msg.getRunningInformation();
if (text == null || text.isBlank()) return;
String nick = msg.getThreadNickName() == null ? "" : msg.getThreadNickName().toLowerCase(Locale.ROOT);
String k = key == null ? "" : key.toLowerCase(Locale.ROOT);
String t = text.toLowerCase(Locale.ROOT);
boolean isSkedRelated = k.contains("sked") || nick.contains("sked") || t.contains("reminder");
if (!isSkedRelated) return;
final String finalText = text;
Platform.runLater(() -> showBlinkingSkedWarnIndicator(finalText + " SKED!"));
}
private void showBlinkingSkedWarnIndicator(String text) { private void showBlinkingSkedWarnIndicator(String text) {
// short text for the button; full text in tooltip // short text for the button; full text in tooltip
@@ -6415,8 +6595,28 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
bPaneChatWindow.setTop(flwpne_StatusBar); bPaneChatWindow.setTop(flwpne_StatusBar);
initSkedWarnIndicatorButton(); initSkedWarnIndicatorButton();
chatcontroller.lastUiReminderEventProperty().addListener(
(observable, oldValue, reminderEvent) -> {
if (reminderEvent == null) {
return;
}
String text = "REMINDER: "
+ reminderEvent.getCallSignRaw()
+ " T-"
+ reminderEvent.getMinutesBefore()
+ "m";
Platform.runLater(
() -> showBlinkingSkedWarnIndicator(text)
);
}
);
flwpne_StatusBar.getChildren().add(btnSkedWarnIndicator); flwpne_StatusBar.getChildren().add(btnSkedWarnIndicator);
initBandUpgradeIndicatorButton(); initBandUpgradeIndicatorButton();
flwpne_StatusBar.getChildren().add(btnBandUpgradeIndicator); flwpne_StatusBar.getChildren().add(btnBandUpgradeIndicator);
@@ -7231,6 +7431,17 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
// chatMemberTableFilterQTFAndQRBHbox.set // chatMemberTableFilterQTFAndQRBHbox.set
VBox chatMemberTableFilterVBoxForAllFilters= new VBox(); VBox chatMemberTableFilterVBoxForAllFilters= new VBox();
Button btnResetChatMemberFilters = new Button("Reset filters");
btnResetChatMemberFilters.getStyleClass().clear();
btnResetChatMemberFilters.getStyleClass().addAll(
"button",
"buttonMyQrg1"
);
btnResetChatMemberFilters.setTooltip(
new Tooltip("Disable all station-list filters and show every user")
);
chatMemberTableFilterVBoxForAllFilters.setSpacing(1); chatMemberTableFilterVBoxForAllFilters.setSpacing(1);
chatMemberTableFilterVBoxForAllFilters.setMinWidth(0); chatMemberTableFilterVBoxForAllFilters.setMinWidth(0);
@@ -7414,20 +7625,12 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
"-fx-border-radius: 1;" + "-fx-border-radius: 1;" +
"-fx-border-color: lightgrey;"); "-fx-border-color: lightgrey;");
// chatMemberTableFilterQTFAndQRBHbox.setFillHeight(true); chatMemberTableFilterQTFAndQRBHbox.getChildren().addAll(
// chatMemberTableFilterQTFAndQRBHbox.setAlignment(Pos.CENTER_LEFT); btnResetChatMemberFilters,
// chatMemberTableFilterQTFAndQRBHbox.getChildren().add(chatMemberTableFilterQRBHBox); chatMemberTableFilterQRBHBox
chatMemberTableFilterQTFAndQRBHbox );
.getChildren()
.add(chatMemberTableFilterQRBHBox);
// HBox chatMemberTableFilterQTFHBox = new HBox();
// FlowPane chatMemberTableFilterQTFHBox = new FlowPane();
// chatMemberTableFilterQTFHBox.setAlignment(Pos.CENTER_LEFT);
// chatMemberTableFilterQTFHBox.setPrefWidth(525);
// chatMemberTableFilterQTFHBox.setHgap(2);
FlowPane chatMemberTableFilterQTFHBox = new FlowPane( FlowPane chatMemberTableFilterQTFHBox = new FlowPane(
Orientation.HORIZONTAL, Orientation.HORIZONTAL,
2, 2,
@@ -7669,52 +7872,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
chatMemberTableFilterTextField.setPromptText("Find..."); chatMemberTableFilterTextField.setPromptText("Find...");
chatMemberTableFilterTextField.setFocusTraversable(false); chatMemberTableFilterTextField.setFocusTraversable(false);
// chatMemberTableFilterTextField.focusedProperty().addListener(new ChangeListener<Boolean>() {
// @Override
// public void changed(ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean t1) {
// if (chatMemberTableFilterTextField.focusedProperty().getValue()) {
//
// chatMemberTableFilterTextField.clear();
// } else {
// if (!chatMemberTableFilterTextField.focusedProperty().getValue() && chatMemberTableFilterTextField.textProperty().equals("")) {
//
// chatMemberTableFilterTextField.setText("Find...");
// }
// }
//// System.out.println(chatMemberTableFilterTextField.focusedProperty().getValue());
// }
// });
// chatMemberTableFilterTextField.textProperty().addListener(new ChangeListener<String>() {
//
// Predicate<ChatMember> searchTextPredicate = new Predicate<ChatMember>() {
// @Override
// public boolean test(ChatMember chatMember) {
// if (chatMember.getCallSign().toUpperCase().contains(chatMemberTableFilterTextField.getText().toUpperCase()) ||
// chatMember.getCallSign().toUpperCase().contains(chatMemberTableFilterTextField.getText().toLowerCase())) {
// return true;
// } else
//
// return false;
// }
//
// };
//
// @Override
// public void changed(ObservableValue<? extends String> observableValue, String s, String t1) {
//
// if (chatMemberTableFilterTextField.textProperty().getValue().equals("") && !chatMemberTableFilterTextField.focusedProperty().getValue()) {
// chatMemberTableFilterTextField.setText("Find...");
// chatcontroller.getLst_chatMemberListFilterPredicates().remove(searchTextPredicate);
// }
// else {
// if (!chatcontroller.getLst_chatMemberListFilterPredicates().contains(searchTextPredicate)) {
// chatcontroller.getLst_chatMemberListFilterPredicates().add(searchTextPredicate);
// }
// }
//
// System.out.println("KST4CApp " + chatMemberTableFilterTextField.textProperty().getValue().equals("") + " / " + !chatMemberTableFilterTextField.focusedProperty().getValue());
// }
// });
chatMemberTableFilterTextField.textProperty().addListener(new ChangeListener<String>() { chatMemberTableFilterTextField.textProperty().addListener(new ChangeListener<String>() {
@@ -7755,13 +7912,26 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
} }
}); });
// HBox chatMemberTableFilterWorkedBandFiltersHbx = new HBox();
FlowPane chatMemberTableFilterWorkedBandFiltersHbx = new FlowPane( FlowPane chatMemberTableFilterWorkedBandFiltersHbx = new FlowPane(
Orientation.HORIZONTAL, Orientation.HORIZONTAL,
2, 2,
3 3
); );
HBox chatMemberTableReachabilityBox = new HBox(4);
chatMemberTableReachabilityBox.setAlignment(Pos.CENTER_LEFT);
chatMemberTableReachabilityBox.setMinWidth(0);
chatMemberTableReachabilityBox.setStyle(
"-fx-padding: 1;" +
"-fx-border-style: solid inside;" +
"-fx-border-width: 1;" +
"-fx-border-insets: 1;" +
"-fx-border-radius: 1;" +
"-fx-border-color: lightgrey;"
);
chatMemberTableFilterWorkedBandFiltersHbx.setAlignment(Pos.CENTER_LEFT); chatMemberTableFilterWorkedBandFiltersHbx.setAlignment(Pos.CENTER_LEFT);
chatMemberTableFilterWorkedBandFiltersHbx.setRowValignment(VPos.CENTER); chatMemberTableFilterWorkedBandFiltersHbx.setRowValignment(VPos.CENTER);
chatMemberTableFilterWorkedBandFiltersHbx.setMinWidth(0); chatMemberTableFilterWorkedBandFiltersHbx.setMinWidth(0);
@@ -7813,9 +7983,11 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
} }
}); });
chatMemberTableFilterWorkedBandFiltersHbx.getChildren().add(new Label("Reachability:")); chatMemberTableReachabilityBox.getChildren().addAll(
chatMemberTableFilterWorkedBandFiltersHbx.getChildren().add(cmbReachabilityBand); new Label("Reachability:"),
chatMemberTableFilterWorkedBandFiltersHbx.getChildren().add(btnCalculateSelectedTropo); cmbReachabilityBand,
btnCalculateSelectedTropo
);
/** /**
* In order to work the filters needs the proper band settings, which should be worked * In order to work the filters needs the proper band settings, which should be worked
@@ -8157,8 +8329,44 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
btnTglAsNext5Min btnTglAsNext5Min
); );
// chatMemberTableFilterWorkedBandFilters btnResetChatMemberFilters.setOnAction(event -> {
/*
* Reset the visible state of every station-list filter.
*
* Grid coloring and the selected reachability band are intentionally
* preserved because they are display/calculation settings, not filters.
*/
List.of(
btnTglNewLocator,
btnTglReachableTropo,
btnTglNewBands,
btnTglAsNext5Min,
tglBtnQRBEnable,
btnTglwkd,
btnTglwkd50,
btnTglwkd70,
btnTglwkd144,
btnTglwkd432,
btnTglwkd23,
btnTglwkd13,
btnTglwkd9,
btnTglwkd6,
btnTglwkd3,
btnTglInactive
).forEach(toggleButton -> toggleButton.setSelected(false));
tglGrpQTF.selectToggle(null);
chatMemberTableFilterQtfEnableChkbx.setSelected(false);
chatMemberTableFilterTextField.clear();
/*
* Programmatically changing a ToggleButton does not invoke its action
* handler. Clear the predicate list explicitly so no stale predicate
* can remain active.
*/
chatcontroller.getLst_chatMemberListFilterPredicates().clear();
});
chatMemberTableFilterTextFieldBox.getChildren().addAll(chatMemberTableFilterTextField); chatMemberTableFilterTextFieldBox.getChildren().addAll(chatMemberTableFilterTextField);
@@ -8174,6 +8382,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
chatMemberTableFilterTextFieldAndWorkedBandsHbx.getChildren().addAll( chatMemberTableFilterTextFieldAndWorkedBandsHbx.getChildren().addAll(
chatMemberTableFilterTextFieldBox, chatMemberTableFilterTextFieldBox,
chatMemberTableReachabilityBox,
chatMemberTableFilterWorkedBandFiltersHbx chatMemberTableFilterWorkedBandFiltersHbx
); );
@@ -11359,15 +11568,19 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
vbxButtons.getChildren().addAll(btnOptionspnlConnect, btn_preferences_saveAsDefault, btnOptionsPnlApply, vbxButtons.getChildren().addAll(btnOptionspnlConnect, btn_preferences_saveAsDefault, btnOptionsPnlApply,
btnOptionspnlDisconnect, btnOptionspnlDisconnectOnly); btnOptionspnlDisconnect, btnOptionspnlDisconnectOnly);
AnchorPane anchorPaneOkAndSave = new AnchorPane(); // AnchorPane anchorPaneOkAndSave = new AnchorPane();
AnchorPane.setRightAnchor(vbxButtons, 10d); // AnchorPane.setRightAnchor(vbxButtons, 10d);
AnchorPane.setBottomAnchor(vbxButtons, 10d); // AnchorPane.setBottomAnchor(vbxButtons, 10d);
//
// anchorPaneOkAndSave.getChildren().addAll(vbxButtons);
anchorPaneOkAndSave.getChildren().addAll(vbxButtons); // optionsPanel.setBottom(anchorPaneOkAndSave);
optionsPanel.setBottom(anchorPaneOkAndSave);
// optionsPanel.setAlignment(vbxButtons, Pos.CENTER);; // optionsPanel.setAlignment(vbxButtons, Pos.CENTER);;
vbxButtons.setAlignment(Pos.CENTER_LEFT);
optionsPanel.setBottom(vbxButtons);
// VBox vBox = new VBox(tabPaneOptions); // VBox vBox = new VBox(tabPaneOptions);
settingsScene = new Scene(optionsPanel, chatcontroller.getChatPreferences().getGUIsettingsStageSceneSizeHW()[0], chatcontroller.getChatPreferences().getGUIsettingsStageSceneSizeHW()[1]); settingsScene = new Scene(optionsPanel, chatcontroller.getChatPreferences().getGUIsettingsStageSceneSizeHW()[0], chatcontroller.getChatPreferences().getGUIsettingsStageSceneSizeHW()[1]);
settingsScene.getStylesheets().add(ApplicationConstants.STYLECSSFILE_DEFAULT_DAYLIGHT); settingsScene.getStylesheets().add(ApplicationConstants.STYLECSSFILE_DEFAULT_DAYLIGHT);
@@ -11378,13 +11591,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
settingsStage.show(); settingsStage.show();
chatcontroller.lastUiReminderEventProperty().addListener((obs, oldVal, ev) -> {
if (ev == null) return;
String text = "REMINDER: " + ev.getCallSignRaw() + " T-" + ev.getMinutesBefore() + "m";
Platform.runLater(() -> showBlinkingSkedWarnIndicator(text));
});
//initialize the timeline //initialize the timeline
Platform.runLater(this::updateTimelineVisuals); Platform.runLater(this::updateTimelineVisuals);
@@ -11645,10 +11852,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
updateStatusButton(key, threadStateMessage); updateStatusButton(key, threadStateMessage);
}); });
maybeShowSkedWarnIndicator(key, threadStateMessage);
maybeShowBandUpgradeIndicator(key, threadStateMessage); maybeShowBandUpgradeIndicator(key, threadStateMessage);
//if we receive a threadstatemessage for sked warning, enable the sked warning //if we receive a threadstatemessage for sked warning, enable the sked warning
@@ -208,7 +208,12 @@ public class TimelineView extends Pane {
diamond.setFill(colorForPotential(sked.getOpportunityPotentialPercent())); diamond.setFill(colorForPotential(sked.getOpportunityPotentialPercent()));
String baseToolTipFallBack = sked.getTargetCallsign() + " (" + sked.getBand() + ")\nAz: " + sked.getTargetAzimuth(); String baseToolTipFallBack =
sked.getTargetChatCallsign()
+ " ("
+ sked.getBand()
+ ")\nAz: "
+ sked.getTargetAzimuth();
if (skedTooltipExtraTextProvider != null) { if (skedTooltipExtraTextProvider != null) {
String extra = skedTooltipExtraTextProvider.apply(sked); String extra = skedTooltipExtraTextProvider.apply(sked);
@@ -220,7 +225,9 @@ public class TimelineView extends Pane {
Tooltip t = new Tooltip(baseToolTipFallBack); Tooltip t = new Tooltip(baseToolTipFallBack);
Tooltip.install(diamond, t); Tooltip.install(diamond, t);
Label lbl = new Label("SKED: " + sked.getTargetCallsign()); Label lbl = new Label(
"SKED: " + sked.getTargetChatCallsign()
);
// lbl.setFont(new Font(9)); // lbl.setFont(new Font(9));
// lbl.setTextFill(Color.WHITE); // lbl.setTextFill(Color.WHITE);
lbl.setLayoutY(14); lbl.setLayoutY(14);
@@ -28,6 +28,8 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.ColumnConstraints;
import javafx.geometry.Pos;
import javafx.scene.layout.HBox;
/** /**
* Standalone station map window. * Standalone station map window.
@@ -49,6 +51,9 @@ public final class StationMapView {
*/ */
private static final boolean MAP_DEBUG_LOGGING = false; private static final boolean MAP_DEBUG_LOGGING = false;
private static final double MINIMUM_HEIGHT_WITH_PATH_ANALYSIS = 650.0;
private static final double MINIMUM_HEIGHT_WITHOUT_PATH_ANALYSIS = 420.0;
private final PathProfileChart detailPathProfileChart = new PathProfileChart(); private final PathProfileChart detailPathProfileChart = new PathProfileChart();
private final Label detailPathModeValue = new Label("-"); private final Label detailPathModeValue = new Label("-");
@@ -76,6 +81,12 @@ public final class StationMapView {
private VBox detailPane; private VBox detailPane;
private final Label statusLabel = new Label("Station map not initialized yet."); private final Label statusLabel = new Label("Station map not initialized yet.");
private final Label pathAnalysisHiddenHintLabel = new Label("Path analysis is hidden.");
private final Button pathAnalysisVisibilityButton = new Button();
private final Tooltip pathAnalysisVisibilityTooltip = new Tooltip();
private final Label detailCallsignValue = new Label("-"); private final Label detailCallsignValue = new Label("-");
private final Label detailLocatorValue = new Label("-"); private final Label detailLocatorValue = new Label("-");
private final Label detailQrbValue = new Label("-"); private final Label detailQrbValue = new Label("-");
@@ -138,8 +149,12 @@ public final class StationMapView {
private PathAnalysisResult lastPathAnalysisResult = PathAnalysisResult.waitingForSelection(""); private PathAnalysisResult lastPathAnalysisResult = PathAnalysisResult.waitingForSelection("");
private VBox mapAndProfilePane; private VBox mapAndProfilePane;
private VBox profileSection;
private VBox pathAnalysisSection;
private ScrollPane detailScrollPane; private ScrollPane detailScrollPane;
private final Label detailPathLosValue = new Label("-"); private final Label detailPathLosValue = new Label("-");
private final Label detailPathWorstClearanceValue = new Label("-"); private final Label detailPathWorstClearanceValue = new Label("-");
@@ -287,6 +302,14 @@ public final class StationMapView {
} }
}); });
pathAnalysisVisibilityButton.setMinWidth(Region.USE_PREF_SIZE);
pathAnalysisVisibilityButton.setTooltip(pathAnalysisVisibilityTooltip);
pathAnalysisVisibilityButton.setOnAction(event ->
setPathAnalysisVisible(!profileSection.isVisible(), true));
pathAnalysisHiddenHintLabel.setMinWidth(Region.USE_PREF_SIZE);
pathAnalysisHiddenHintLabel.setStyle("-fx-font-style: italic; -fx-opacity: 0.85;");
webView.setFocusTraversable(true); webView.setFocusTraversable(true);
webView.setPickOnBounds(true); webView.setPickOnBounds(true);
@@ -323,7 +346,8 @@ public final class StationMapView {
webView.widthProperty().addListener((obs, oldValue, newValue) -> requestMapInvalidateSize()); webView.widthProperty().addListener((obs, oldValue, newValue) -> requestMapInvalidateSize());
webView.heightProperty().addListener((obs, oldValue, newValue) -> requestMapInvalidateSize()); webView.heightProperty().addListener((obs, oldValue, newValue) -> requestMapInvalidateSize());
VBox profileSection = createProfileSection(); profileSection = createProfileSection();
mapAndProfilePane = new VBox(6, webView, profileSection); mapAndProfilePane = new VBox(6, webView, profileSection);
mapAndProfilePane.setPadding(new Insets(0)); mapAndProfilePane.setPadding(new Insets(0));
@@ -349,10 +373,9 @@ public final class StationMapView {
mapAndProfilePane.widthProperty().subtract(20) mapAndProfilePane.widthProperty().subtract(20)
); );
detailPane = new VBox(10, pathAnalysisSection = createPathAnalysisSection();
createSelectedStationSection(), detailPane = new VBox(10, createSelectedStationSection(), pathAnalysisSection);
createPathAnalysisSection()
);
detailPane.setPadding(new Insets(10)); detailPane.setPadding(new Insets(10));
detailPane.setMinWidth(0); detailPane.setMinWidth(0);
@@ -364,8 +387,14 @@ public final class StationMapView {
detailScrollPane.setFitToWidth(true); detailScrollPane.setFitToWidth(true);
detailScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); detailScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
detailScrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); detailScrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
detailScrollPane.setMinWidth(300); /*
* Allow the details pane to be reduced far enough to leave more room for the
* map. At its minimum width, a callsign with up to ten characters remains
* readable.
*/
detailScrollPane.setMinWidth(210);
detailScrollPane.setPrefWidth(350); detailScrollPane.setPrefWidth(350);
detailScrollPane.setMaxWidth(Double.MAX_VALUE); detailScrollPane.setMaxWidth(Double.MAX_VALUE);
detailScrollPane.setFitToWidth(true); detailScrollPane.setFitToWidth(true);
@@ -378,8 +407,7 @@ public final class StationMapView {
SplitPane.setResizableWithParent(detailScrollPane, true); SplitPane.setResizableWithParent(detailScrollPane, true);
rootPane = new BorderPane(); rootPane = new BorderPane();
rootPane.setTop(statusLabel); rootPane.setTop(createMapHeader());
BorderPane.setMargin(statusLabel, new Insets(8));
rootPane.setCenter(mainSplitPane); rootPane.setCenter(mainSplitPane);
double[] size = chatPreferences.getGUIstationMapStageSceneSizeHW(); double[] size = chatPreferences.getGUIstationMapStageSceneSizeHW();
@@ -390,7 +418,12 @@ public final class StationMapView {
scene = new Scene(rootPane, initialWidth, initialHeight); scene = new Scene(rootPane, initialWidth, initialHeight);
stage.setMinWidth(900); stage.setMinWidth(900);
stage.setMinHeight(650); stage.setMinHeight(resolveMinimumStationMapHeight(
chatPreferences.isGUIstationMapPathAnalysisVisible()));
stage.setScene(scene);
setPathAnalysisVisible(chatPreferences.isGUIstationMapPathAnalysisVisible(), false);
applyThemeFromPreferences();
stage.setScene(scene); stage.setScene(scene);
applyThemeFromPreferences(); applyThemeFromPreferences();
@@ -446,6 +479,91 @@ public final class StationMapView {
} }
/**
* Creates an always-visible header for the map status and analysis controls.
*
* Keeping the control outside the sections that it hides is important: users
* must always have an obvious way to restore a previously hidden analysis.
*/
private HBox createMapHeader() {
// The status may be shortened before the show/hide control is ever clipped.
statusLabel.setMinWidth(0);
statusLabel.setMaxWidth(Double.MAX_VALUE);
statusLabel.setTextOverrun(OverrunStyle.ELLIPSIS);
HBox header = new HBox(
10,
statusLabel,
pathAnalysisHiddenHintLabel,
pathAnalysisVisibilityButton
);
header.setAlignment(Pos.CENTER_LEFT);
header.setPadding(new Insets(8));
HBox.setHgrow(statusLabel, Priority.ALWAYS);
return header;
}
/**
* Shows or hides both parts of the path analysis as one logical feature.
*
* Both visible and managed must be changed. A node that is merely invisible
* would still reserve layout space and the map would not grow into that area.
* The current analysis result remains attached to the controls and is
* immediately available again when the user restores the sections.
*
* @param visible true to show the profile and detailed analysis
* @param persist true when the change was explicitly requested by the user
*/
private void setPathAnalysisVisible(boolean visible, boolean persist) {
profileSection.setVisible(visible);
profileSection.setManaged(visible);
pathAnalysisSection.setVisible(visible);
pathAnalysisSection.setManaged(visible);
pathAnalysisHiddenHintLabel.setVisible(!visible);
pathAnalysisHiddenHintLabel.setManaged(!visible);
pathAnalysisVisibilityButton.setText(
visible ? "Hide path analysis" : "Show path analysis");
pathAnalysisVisibilityButton.setAccessibleText(
visible ? "Hide path analysis" : "Show path analysis");
pathAnalysisVisibilityButton.setStyle(
visible ? "" : "-fx-font-weight: bold;");
pathAnalysisVisibilityTooltip.setText(visible
? "Hide the path profile and detailed path analysis. You can show them again at any time."
: "Show the path profile and detailed path analysis.");
pathAnalysisVisibilityButton.setAccessibleHelp(
pathAnalysisVisibilityTooltip.getText());
if (!visible) {
/*
* Do not leave a profile hover marker on the map after its chart was
* hidden.
*/
showProfileHoverPointOnMap(null);
}
stage.setMinHeight(resolveMinimumStationMapHeight(visible));
if (persist) {
chatPreferences.setGUIstationMapPathAnalysisVisible(visible);
}
if (rootPane != null) {
rootPane.requestLayout();
Platform.runLater(this::requestMapInvalidateSize);
}
}
private double resolveMinimumStationMapHeight(boolean pathAnalysisVisible) {
return pathAnalysisVisible
? MINIMUM_HEIGHT_WITH_PATH_ANALYSIS
: MINIMUM_HEIGHT_WITHOUT_PATH_ANALYSIS;
}
private VBox createSelectedStationSection() { private VBox createSelectedStationSection() {
GridPane detailGrid = new GridPane(); GridPane detailGrid = new GridPane();
detailGrid.setHgap(8); detailGrid.setHgap(8);
@@ -494,13 +612,18 @@ public final class StationMapView {
gridPane.getColumnConstraints().clear(); gridPane.getColumnConstraints().clear();
ColumnConstraints labelColumn = new ColumnConstraints(); ColumnConstraints labelColumn = new ColumnConstraints();
labelColumn.setMinWidth(105); labelColumn.setMinWidth(70);
labelColumn.setPrefWidth(115); labelColumn.setPrefWidth(115);
labelColumn.setMaxWidth(130); labelColumn.setMaxWidth(130);
labelColumn.setHgrow(Priority.NEVER); labelColumn.setHgrow(Priority.NEVER);
ColumnConstraints valueColumn = new ColumnConstraints(); ColumnConstraints valueColumn = new ColumnConstraints();
valueColumn.setMinWidth(180);
/*
* Reserve enough space for a callsign with up to ten characters, while still
* allowing the details pane to become considerably narrower.
*/
valueColumn.setMinWidth(85);
valueColumn.setHgrow(Priority.ALWAYS); valueColumn.setHgrow(Priority.ALWAYS);
gridPane.getColumnConstraints().addAll(labelColumn, valueColumn); gridPane.getColumnConstraints().addAll(labelColumn, valueColumn);
@@ -1530,8 +1653,11 @@ public final class StationMapView {
return 768.0; return 768.0;
} }
double minimumHeight = resolveMinimumStationMapHeight(
chatPreferences.isGUIstationMapPathAnalysisVisible());
// Avoid restoring very large old test sizes after the layout changed. // Avoid restoring very large old test sizes after the layout changed.
if (storedSize[1] < 650.0 || storedSize[1] > 1100.0) { if (storedSize[1] < minimumHeight || storedSize[1] > 1100.0) {
return 768.0; return 768.0;
} }
+4 -1
View File
@@ -2379,4 +2379,7 @@ OZ1DLD/P;Bent;JO45SK;StringProperty [value: 144.285]; wkd true; wkd144 true; wkd
SM6VTZ;Chris 2/70/23/3;JO58UJ;StringProperty [value: 144.135]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz SM6VTZ;Chris 2/70/23/3;JO58UJ;StringProperty [value: 144.135]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
OZ7KJ;Skive Club;JO46ML;StringProperty [value: 144.225]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz OZ7KJ;Skive Club;JO46ML;StringProperty [value: 144.225]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
OV3T;Thomas;JO46CM;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz OV3T;Thomas;JO46CM;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
OZ6TY;Henning;JO55XE;StringProperty [value: 144.196]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz OZ6TY;Henning;JO55XE;StringProperty [value: 144.196]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
DF7KF;Dithmar;JO30FK;StringProperty [value: null]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
DH1NFJ;Jochen;JO50QL;StringProperty [value: null]; wkd true; wkd144 false; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 3: Microwave
PA2RU;René;JO32LT;StringProperty [value: null]; wkd true; wkd144 false; wkd432false; wkd1240true; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
+10 -8
View File
@@ -97,15 +97,17 @@ function rewriteManualLinks(content, lang) {
return `[${label}](/manual/${linkLang}/${slug.toLowerCase()}/)`; return `[${label}](/manual/${linkLang}/${slug.toLowerCase()}/)`;
}) })
// Markdown-Links auf de-/en-Dateien ohne .md // Markdown links to German or English manual pages.
.replace(/\]\((en|de)-([^)#]+)\)/g, (match, linkLang, slug) => { // The source may contain an optional ".md" suffix and an optional anchor.
return `](/manual/${linkLang}/${slug.toLowerCase()}/)`; // Both forms must be converted to the corresponding website manual URL.
}) .replace(
/\]\((en|de)-([^#)]+?)(?:\.md)?(?:#([^)]+))?\)/g,
(match, linkLang, slug, anchor) => {
const fragment = anchor ? `#${anchor}` : "";
// Markdown-Links auf de-/en-Dateien mit .md return `](/manual/${linkLang}/${slug.toLowerCase()}/${fragment})`;
.replace(/\]\((en|de)-([^)#]+)\.md\)/g, (match, linkLang, slug) => { }
return `](/manual/${linkLang}/${slug.toLowerCase()}/)`; );
});
} }
module.exports = function (eleventyConfig) { module.exports = function (eleventyConfig) {
+42 -8
View File
@@ -1,25 +1,59 @@
--- ---
title: Log Synchronization title: Log Synchronisation
icon: 🔄 icon: 🔄
category: Logger Integration category: Logger Integration
since: "1.31" since: "1.31"
summary: Import worked stations and current frequencies from supported contest loggers so filters and band information follow the log. summary: Import callsign, band, locator and QRG information from supported contest loggers at the level provided by each interface.
description: KST4Contest receives worked-station and, where supported, frequency data from UCXLog, N1MM+, QARTest, DXLog.net and Win-Test through file and UDP interfaces. description: KST4Contest connects chat activity with current log state through file-based evaluation, general QSO UDP packets and the native Win-Test network protocol.
tagsList: tagsList:
- Win-Test - Win-Test
- UCXLog - UCXLog
- N1MM+
- QARTest
- DXLog.net
- contest logger - contest logger
related: related:
- priority-score - priority-score
- dual-chat - dual-chat
- sked-reminder
--- ---
## Connect chat and logging ## Why connect the logger?
Contest operation is faster when chat information and log state are connected. The chat may still show a station as an interesting candidate after the QSO has already been logged. Without synchronisation, Worked filters, band columns and priority calculations would continue to use an outdated contest state.
KST4Contest can use logger information to improve workflow awareness. KST4Contest therefore imports the information provided by the logging application and applies it to the active chat entries of the corresponding base callsign.
## Worked and band awareness ## Three interfaces with different levels of detail
Log synchronization helps the client understand which stations and bands are relevant. The available information depends on the interface:
| Interface | Callsign | Band | Locator |
|---|---:|---:|---:|
| Simplelogfile interpreter | yes | no | no |
| General QSO UDP listener | yes | when included | when included |
| Win-Test network listener | yes | yes | when included |
The Simplelogfile interpreter is broadly compatible but can only identify worked callsigns. A callsign match in a file does not provide enough information to infer a reliable band or grid square.
The general UDP listener processes packets from UCXLog, N1MM+, QARTest and DXLog.net. Where the packet contains band and locator data, KST4Contest also updates the per-band Worked state and worked grid square.
![Log synchronisation settings](/manual/assets/client_settings_window_logsync.png)
## Native Win-Test integration
Win-Test uses a separate listener for its native network protocol. KST4Contest resolves the Win-Test band ID, including 50 and 70 MHz, and stores the resulting Worked information in the same internal database.
STATUS packets can also update the local QRG. In multi-operator networks, a station-name filter prevents STATUS packets from another operating position from replacing the frequency of the intended radio.
Win-Test can additionally receive skeds created in KST4Contest. The handover only takes place when a QRG matching the selected band can be determined. No fixed fallback frequency is inserted merely to make the packet technically valid.
> The band-aware sked handover and explicit `SSB`/`CW` selection are included in Nightly / v1.42.
## Stored state and limitations
Worked, NOT-QRV and worked-grid information is stored in the internal SQLite database and restored after a restart. Contest-related records expire automatically after three days.
KST4Contest can only use the fields supplied by the selected interface. Missing band or locator data is not reconstructed from guesswork. This makes the result less complete in some cases, but also avoids turning an incomplete log packet into incorrect Worked information.
[Read the complete log synchronisation setup in the manual.](/manual/en/log-sync/)
+36 -7
View File
@@ -3,8 +3,8 @@ title: Priority Score System
icon: 🎯 icon: 🎯
category: Contest Workflow category: Contest Workflow
since: "1.40" since: "1.40"
summary: Rank active stations from known context such as direction, activity, band, QRG, AP and sked information. summary: Rank active base callsigns using known band, direction, activity, AirScout, reply and sked information.
description: The Priority Score System combines available contest information to populate a separate list of currently relevant candidates. description: KST4Contest excludes stations with no known remaining band opportunity and orders the other active candidates using the context already available to the client.
tagsList: tagsList:
- ON4KST - ON4KST
- VHF - VHF
@@ -18,12 +18,41 @@ related:
- sked-reminder - sked-reminder
--- ---
## Why it matters ## The problem behind the score
During active VHF, UHF and microwave contests, relevant stations can disappear quickly in busy ON4KST chat traffic. An ON4KST user list shows who is logged in. During a contest, the more useful question is which station should be examined next.
The Priority Score System helps operators focus on stations that are more likely to be useful during contest operation. Answering that question manually means repeatedly combining Worked status, possible bands, distance, antenna direction, recent activity, AP opportunities and scheduled contacts. This remains manageable with a short list. It becomes less reliable after several hours of contest operation, especially when multiple bands and chat categories are involved.
## Built for contest decisions ## How candidates are selected
KST4Contest is not just a chat viewer. It evaluates contest-relevant context and helps turn chat activity into better operating decisions. KST4Contest first checks whether a known common and unworked band opportunity remains. Locally enabled bands, recent QRG detections, band designators in station names, Worked information and manual NOT-QRV marks are evaluated together.
A known incompatibility or an already completed set of possible bands produces a score of `0` and removes the station from the priority list. Missing band information alone does not. Unknown is not the same as impossible.
The remaining stations are ranked using several independent hints, including:
- distance and the configured maximum QRB,
- the current antenna direction and beamwidth,
- recent chat activity and reaction behaviour,
- available AirScout aircraft,
- open band opportunities, and
- the timing of scheduled contacts.
An imminent sked receives a deliberately strong boost. Failed attempts and unanswered calls reduce the score.
## One station, several chat logins
Suffixes and chat categories remain separate message targets. Worked, band, NOT-QRV and score information is shared through the normalised base callsign.
This prevents a station using several band-specific logins from occupying several positions in the priority list while still allowing KST4Contest to address the correct complete callsign in the correct category.
![Priority Score, compact candidate list and Further Info controls](/manual/assets/priority_score_overview.png)
## A decision aid, not a propagation forecast
The score is a relative operating priority. It is not a success probability, a signal estimate or a guarantee that the remote station is ready for a QSO.
Its value depends on the information available to KST4Contest, and that information may be incomplete, outdated or ambiguous. The final decision remains with the operator.
[Read the complete calculation and its limitations in the manual.](/manual/en/features/#priority-score-and-priority-list-from-v140)
+53 -7
View File
@@ -3,24 +3,70 @@ title: Sked Reminder
icon: 🔔 icon: 🔔
category: Sked Management category: Sked Management
since: "1.40" since: "1.40"
summary: Store planned contacts and issue configurable chat messages plus local alerts before the agreed time. summary: Create a timed contact, raise its priority, show it on the timeline and optionally send reminder PMs before the agreed time.
description: Sked Reminder keeps scheduled contacts visible and provides automatic advance messages together with acoustic and visual operator alerts. description: KST4Contest keeps scheduled contacts in the active workflow, increases their priority as the agreed time approaches and can remind both operators.
tagsList: tagsList:
- sked - sked
- ON4KST - ON4KST
- contest reminder - contest reminder
- Win-Test
related: related:
- priority-score - priority-score
- airscout - airscout
- timeline - timeline
--- ---
## Never lose important skeds ## Why store a sked inside the chat client?
During active contests, it is easy to miss a planned contact while handling chat traffic, logging and band changes. A contact agreed for five or ten minutes later has to compete with incoming messages, logging, antenna changes and other stations asking for attention. Remembering the time is only part of the problem. The station must also become visible again when the appointment approaches.
Sked Reminder keeps scheduled contacts visible. KST4Contest therefore treats a sked as an active operating task rather than a simple alarm.
## Built for real contest pressure ## What happens when a sked is created?
The workflow supports time-critical operation where missing a few minutes can mean missing a QSO. Select the station, the remaining time and one of the locally enabled bands. The mode can be set to `SSB` or `CW` for a possible Win-Test handover.
After pressing **Create sked**, KST4Contest:
1. stores the sked internally,
2. raises the station's Priority Score as the scheduled time approaches,
3. adds the contact to the AP and sked timeline, and
4. optionally schedules private reminder messages.
![Sked controls in the Further Info section](/manual/assets/sked_controls.png)
The internal sked does not depend on Win-Test. If no logger is connected or the network handover fails, the sked remains available in KST4Contest.
## Reminding the remote station
Reminder PMs are optional. The available patterns are:
- two and one minute before the sked,
- five, two and one minute before the sked, or
- ten, five, two and one minute before the sked.
Messages are sent to the complete KST callsign in the selected chat category. A band-specific login such as `CALLSIGN-70` therefore remains a separate message target instead of being silently reduced to the base callsign.
The local operator receives a visual **SKED** indication and, if simple notification sounds are enabled, an acoustic reminder.
## Win-Test handover
When the Win-Test network listener is enabled, KST4Contest also attempts to send the sked to Win-Test.
The frequency is not guessed. KST4Contest first looks for a recent QRG of the remote station on the selected band. If none is available, it checks whether the local QRG of the selected chat category belongs to that band. Without a matching frequency, the Win-Test handover is omitted while the internal sked remains intact.
KST-specific suffixes such as `-2`, `-70` or `-144` are removed from the callsign passed to the log. Portable components such as `/P` and `/M` are preserved.
> Band-aware QRG validation, explicit `SSB`/`CW` selection and the corrected handling of KST suffixes are included in Nightly / v1.42.
![Sked handed over from KST4Contest to Win-Test](/manual/assets/wintest_sked_handover.png)
## What the reminder cannot guarantee
Skeds and reminder schedules are stored in memory. They must be recreated after restarting KST4Contest.
The proposed band is derived from recent chat and station-name information. This is useful context, not proof that the station is still operating on the same QRG. Check the band, time and mode before creating the sked.
In plain terms: the function makes a scheduled contact considerably harder to overlook. It cannot prevent every missed sked.
[Read the complete sked handling and its limitations in the manual.](/manual/en/features/#skeds-and-sked-reminders)
+38 -9
View File
@@ -1,25 +1,54 @@
--- ---
title: Timeline View title: AP and Sked Timeline
icon: ⏱️ icon: ⏱️
category: Contest Awareness category: Contest Awareness
since: "1.40" since: "1.40"
summary: Show AP-based priority candidates and scheduled contacts on a shared timeline. summary: Show upcoming aircraft-scatter candidates and scheduled contacts together on a 30-minute timeline.
description: The Timeline View places upcoming aircraft scatter candidates and planned skeds into a common time-based overview. description: The timeline relates AirScout opportunities, priority candidates, antenna direction and internal skeds to their expected time.
tagsList: tagsList:
- timeline - timeline
- AP windows - AP windows
- airplane scatter - aircraft scatter
- sked
related: related:
- airscout - airscout
- priority-score - priority-score
- sked-reminder
--- ---
## Timing matters ## The useful station may only be useful for a minute
Many VHF/UHF/SHF opportunities are short-lived. Aircraft-scatter opportunities are time-dependent. A candidate which matters in two minutes may be irrelevant now, while an agreed sked must remain visible even when no aircraft is currently available.
The Timeline View helps operators see upcoming timing windows instead of keeping everything in mind manually. The timeline places both kinds of event into the same 30-minute view.
## Designed for fast awareness Events further in the future appear on the right. As their time approaches, they move left towards the present.
The timeline supports quick decisions during busy contest operation. ![AP candidates and skeds in the timeline](/manual/assets/sked_timeline.png)
## AP candidates and scheduled contacts remain distinct
AP candidates appear in the upper lanes. Up to four selected candidates can be shown for each arrival minute. Their colours represent the reflection potential reported by AirScout:
- magenta from 95%,
- red from 75%,
- yellow from 50%, and
- blue below 50%.
Skeds appear as diamonds in the lower lane. Their labels use the complete selected KST callsign so that band-specific or otherwise suffixed logins remain identifiable.
> Complete KST callsigns in sked labels are included in Nightly / v1.42.
## Antenna direction remains visible
A marker becomes more transparent when its QTF is clearly outside the current antenna direction. The callsign remains readable. Targets near the centre of the configured antenna beam receive an additional visual highlight.
Clicking an AP candidate selects the corresponding active chat member, including the callsign suffix and chat category.
## A timing aid, not a contact forecast
The timeline shows when an event is expected to matter. It does not guarantee that the frequency is clear, that the remote station is ready or that the calculated aircraft path will produce a workable signal.
AirScout data can change. So can the actual operating situation.
[Read the complete timeline behaviour in the manual.](/manual/en/features/#ap-and-sked-timeline)