corrected string formatting on qrgs bigger than 10 GHz in wintest parser

This commit is contained in:
Marc Froehlich
2026-08-16 01:12:23 +02:00
parent f014b4697b
commit 88cb55520d
5 changed files with 184 additions and 71 deletions
+17 -1
View File
@@ -10,13 +10,15 @@ Die veröffentlichten Stable-Versionen und ihre Programmpakete stehen unter [Git
## v1.42 Nightly / in Entwicklung
> Stand dieses Abschnitts: 10. August 2026.
> Stand dieses Abschnitts: 14. August 2026.
> v1.42 ist noch kein veröffentlichtes Stable-Release. Bis zur Freigabe können weitere Änderungen hinzukommen.
v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen, Worked-Status, NOT-QRV-Markierungen, Rufzeichensuffixe und Frequenzen werden dadurch konsistenter in der Benutzerliste, der Stationskarte, der Prioritätsberechnung und den externen Schnittstellen verwendet.
### Neu
- **Sichtbarer ON4KST-Verbindungsstatus:** Ein kompakter `LINK`-Indicator im Hauptfenster zeigt den tatsächlichen Zustand der ON4KST-Verbindung an. Grün bedeutet vollständig angemeldet und synchronisiert, Gelb kennzeichnet Verbindungsaufbau und Synchronisation, Rot eine unterbrochene Verbindung, einen Fehler oder die Wartezeit vor dem nächsten Verbindungsversuch.
- **Gemeinsame Herleitung verfügbarer Bänder:** Ein zentraler `BandOpportunityResolver` wertet aktuelle QRGs, Bandangaben im Namensfeld, aktive Rufzeichenvarianten, Worked-Informationen und NOT-QRV-Markierungen gemeinsam aus. Benutzerliste, **New bands**, Band-Upgrade-Hinweis, Priority Score, Stationskarte und automatische Bandauswahl verwenden damit dieselbe Grundlage.
- **Erweiterte Bandanzeige:** Die Bandspalten unterscheiden jetzt:
@@ -41,6 +43,10 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
### Geändert
- **Sessionbezogene ON4KST-Verbindungssteuerung:** Socket, Reader, Writer, Messagebus und Warteschlangen gehören jetzt zu einer eindeutig identifizierten Verbindungssession. Veraltete Threads einer abgelösten Verbindung können dadurch keine Daten mehr verarbeiten oder die neue Verbindung schließen. `ONLINE` wird erst nach bestätigtem Login und vollständig empfangenen Benutzerlisten gemeldet. Verbindungsaufbau, Login und Synchronisation besitzen feste Zeitlimits; Heartbeats, ausbleibende Eingangsdaten, EOF sowie Lese- und Schreibfehler werden überwacht und lösen bei Bedarf einen kontrollierten Neuaufbau mit Backoff aus.
- **ON4KST-Protokollbefehle abgesichert:** Ausgehende Befehle werden zentral aufgebaut und auf gültige Kategorien, Locatoren und unerlaubte Frame-Trennzeichen geprüft. Da ON4KST pro TCP-Session nur einen Locator verwaltet, wird für beide Chat-Kategorien der Hauptlocator verwendet und eine abweichende zweite Konfiguration protokolliert, statt widersprüchliche Befehle an den Server zu senden.
- **QRG-Erkennung präzisiert:** Vollständige und relative Frequenzangaben werden weiterhin erkannt. Nackte dreistellige Zahlen gelten nur noch bei erkennbarem Frequenzkontext als QRG. Signalrapporte, Bandangaben und andere Zahlen erzeugen dadurch seltener falsche Frequenzen.
- **Stationsbezogener Frequenzkontext:** Bei relativen QRGs verwendet KST4Contest zuerst einen höchstens 30 Minuten alten Bandkontext derselben Station. Erst wenn dieser fehlt, wird das global konfigurierte Fallback-Band verwendet.
@@ -79,6 +85,16 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
### Behoben
- **Zuverlässige Benutzerliste beim Login:** Ungültige oder unvollständige `UA0`-Teilnehmerdatensätze werden einzeln verworfen und protokolliert, ohne die Verarbeitung der alphabetisch folgenden Teilnehmer abzubrechen. Die gültigen Einträge werden zunächst pro Kategorie gesammelt und erst mit dem ersten zugehörigen `UE`-Abschlussframe vollständig veröffentlicht.
- **Benutzerliste verschwindet nach dem Login:** ON4KST kann nach Namens-, Status- oder anderen Live-Änderungen weitere `UE`-Frames für dieselbe Kategorie senden. Wiederholte Abschlussframes werden jetzt erkannt und ignoriert, damit eine bereits gefüllte Benutzerliste nicht durch eine leere Momentaufnahme ersetzt wird.
- **Fehlgeschlagener Erstaufbau und Verbindungsverlust:** Wenn beim Programmstart keine Verbindung zum Server hergestellt werden kann, läuft KST4Contest nicht mehr in eine Endlos- oder Busy-Wait-Schleife. Die Oberfläche bleibt bedienbar und weitere Versuche erfolgen mit begrenztem Backoff. Auch ein vom Server geschlossener oder über längere Zeit stummer Socket wird zuverlässig erkannt.
- **Messagebus-Protokollierung:** Bereits korrekt verarbeitete ON4KST-Frames werden nicht mehr zusätzlich als `Critical, detected unhandled Chatmessage` gemeldet. Nur tatsächlich unbekannte Telegramme erreichen noch diesen Logzweig.
- **Passwort im Fehlerlog:** Das ON4KST-Passwort wird beim Verbindungsaufbau nicht mehr im Klartext in die Konsole oder Logdatei geschrieben.
- **Langzeitfehler der Stationsauswahl:** Die vom Message-Thread verwalteten Chatmember wurden von der JavaFX-Ansicht entkoppelt. Gleichzeitige Änderungen der Daten und Tabellenansicht führen dadurch nicht mehr nach längerer Laufzeit zu fehlerhaften Auswahlmodellen oder Concurrent-Modification-Problemen.
- **Keine Phantom-Chatmember durch UM3:** Historische oder zusätzliche Servermeldungen erzeugen keine Benutzerlisteneinträge für Stationen, die nicht tatsächlich im Chat angemeldet sind.
+15 -1
View File
@@ -10,13 +10,15 @@ Published Stable versions and their application packages are available under [Gi
## v1.42 Nightly / in development
> Status of this section: 10 August 2026.
> Status of this section: 14 August 2026.
> v1.42 is not a published Stable release yet. Further changes may be added before release.
v1.42 brings several previously separate calculations together. Band information, Worked status, NOT-QRV marks, callsign suffixes and frequencies are now used more consistently by the user list, station map, priority calculation and external interfaces.
### Added
- **Visible ON4KST connection state:** A compact `LINK` indicator in the main window displays the actual state of the ON4KST connection. Green means fully authenticated and synchronised, yellow indicates connection setup or synchronisation, and red indicates a lost connection, an error or the delay before the next connection attempt.
- **Shared band-opportunity calculation:** A central `BandOpportunityResolver` evaluates recent QRGs, band designators in station names, active callsign variants, Worked information and NOT-QRV marks. The user list, **New bands**, band-upgrade hint, Priority Score, station map and automatic band selection now use the same basis.
- **Extended band status display:** The band columns now distinguish:
@@ -41,6 +43,10 @@ v1.42 brings several previously separate calculations together. Band information
### Changed
- **Session-based ON4KST connection lifecycle:** Each socket, reader, writer, message bus and queue now belongs to an explicitly identified connection session. Delayed threads from an obsolete connection can therefore no longer process data or close its replacement. `ONLINE` is reported only after the login has been accepted and all requested user lists have been received. Connection setup, login and synchronisation use bounded timeouts, while heartbeats, missing inbound traffic, EOF and read or write failures trigger controlled reconnect attempts with backoff where appropriate.
- **Validated ON4KST protocol commands:** Outgoing frames are built centrally and checked for valid categories, locators and prohibited frame delimiters. Because ON4KST maintains one locator per TCP session, the main locator is used for both chat categories and a conflicting secondary configuration is logged instead of sending contradictory commands to the server.
- **More precise QRG recognition:** Complete and relative frequency references continue to be recognised. Bare three-digit numbers are treated as QRGs only when a frequency context is available. Signal reports, band designators and unrelated numbers therefore produce fewer false frequencies.
- **Station-specific frequency context:** For relative QRGs, KST4Contest first uses a band context for the same station which is no more than 30 minutes old. The globally configured fallback band is used only when this context is unavailable.
@@ -79,6 +85,14 @@ v1.42 brings several previously separate calculations together. Band information
### Fixed
- **Reliable initial user list:** Invalid or incomplete `UA0` member records are rejected and logged individually without preventing alphabetically following members from being processed. Valid entries are staged per category and published as one complete snapshot when the first corresponding `UE` end marker is received.
- **User list disappearing after login:** ON4KST may send additional `UE` frames for the same category after name, state or other live updates. Repeated end markers are now detected and ignored so that an already populated user list cannot be replaced by an empty snapshot.
- **Failed initial connection and lost sockets:** An unavailable server during startup no longer sends KST4Contest into an endless or busy-wait loop. The user interface remains responsive and further attempts use bounded reconnect backoff. Sockets closed by the server, or connections without inbound traffic for an excessive period, are also detected reliably.
- **Message-bus diagnostics:** Correctly processed ON4KST frames are no longer reported additionally as `Critical, detected unhandled Chatmessage`. Only genuinely unknown frames reach the fallback diagnostic branch.
- **Long-running station-selection failure:** Chat members managed by the message thread have been decoupled from the JavaFX view. Simultaneous data and table updates therefore no longer cause broken selection models or concurrent-modification problems after longer runtimes.
- **No phantom chat members from UM3:** Historical or additional server messages no longer create user-list entries for stations which are not actually logged into the chat.
@@ -3549,7 +3549,7 @@ private ObservableList<String>
/**
* Reloads the worked/not-QRV state from the internal database and applies the
* result both to the active chatmember list and to the database table list in the
* settings dialog. UI-bound list modifications are executed on the JavaFX thread.
* settings dialog. UI-bound list modifications are executed on the JavaFX thread..
*/
public void refreshWorkedStateAndDatabaseListFromDatabase() {
@@ -15,7 +15,6 @@ import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
@@ -165,18 +164,79 @@ public class ReadUDPByWintestThread extends Thread {
}
/**
* Parse Win-Test STATUS packets and update own QRG from WT station.
* Formats a frequency from a Win-Test STATUS packet for use as MYQRG.
*
* Parsing model (tokenized with quotes preserved):
* parts[0] = "STATUS"
* parts[1] = station name (example: "STN1")
* parts[5] = val2 (used to derive mode: 1 => SSB, else CW)
* parts[7] = frequency in 0.1 kHz units (example: 1443210 => 144321.0)
* <p>Win-Test transmits the frequency in units of 0.1 kHz. KST4Contest
* displays frequencies as {@code MHz.kHz.10Hz}, for example
* {@code 144.300.00} or {@code 10368.100.00}. The MHz part may contain
* between one and five digits. Deriving its length from the complete value
* avoids separate and incomplete handling for individual bands.</p>
*
* @param frequencyIn100Hz frequency received from Win-Test in units of
* 0.1 kHz, equivalent to 100 Hz
* @return frequency formatted for MYQRG
* @throws IllegalArgumentException if the supplied frequency is not positive
* or too small to be formatted
* @throws ArithmeticException if the supplied value exceeds the supported
* numeric range
*/
private String helper_formatWinTestFrequency(long frequencyIn100Hz) {
if (frequencyIn100Hz <= 0) {
throw new IllegalArgumentException(
"Win-Test frequency must be greater than zero"
);
}
/*
* Multiplication by ten creates a digit sequence whose final five
* digits represent kHz and 10-Hz groups:
*
* 1443210 -> 14432100 -> 144.321.00
* 103681000 -> 1036810000 -> 10368.100.00
*/
long frequencyIn10Hz = Math.multiplyExact(
frequencyIn100Hz,
10L
);
String frequencyDigits = Long.toString(frequencyIn10Hz);
if (frequencyDigits.length() < 6) {
throw new IllegalArgumentException(
"Win-Test frequency is too small: " + frequencyIn100Hz
);
}
int mhzEndIndex = frequencyDigits.length() - 5;
int khzEndIndex = frequencyDigits.length() - 2;
return frequencyDigits.substring(0, mhzEndIndex)
+ "."
+ frequencyDigits.substring(mhzEndIndex, khzEndIndex)
+ "."
+ frequencyDigits.substring(khzEndIndex);
}
/**
* Parses a Win-Test STATUS packet and optionally updates MYQRG.
*
* <p>The packet is tokenised while preserving quoted station names.
* The configured station-name filter is applied before any frequency is
* processed. An empty filter accepts STATUS packets from every Win-Test
* station.</p>
*
* <p>The main frequency is read from token 7. If pass-frequency use is
* enabled and token 11 contains a valid frequency, the pass frequency is
* used instead. A missing or invalid pass frequency deliberately falls
* back to the main frequency.</p>
*
* @param msg complete Win-Test STATUS packet
*/
private void parseStatus(String msg) {
try {
ArrayList<String> parts = new ArrayList<>();
Matcher matcher = STATUS_TOKEN_PATTERN.matcher(msg);
while (matcher.find()) {
if (matcher.group(1) != null) {
parts.add(matcher.group(1));
@@ -186,86 +246,106 @@ public class ReadUDPByWintestThread extends Thread {
}
if (parts.size() < 8) {
System.out.println("[WinTest] STATUS too short: " + msg);
System.out.println(
"[WinTest] STATUS too short: " + msg
);
return;
}
String stn = parts.get(1);
String stationFilter = client.getChatPreferences().getLogsynch_wintestNetworkStationNameOfWintestClient1();
if (stationFilter != null && !stationFilter.isBlank() && !stn.equalsIgnoreCase(stationFilter)) {
String stationName = parts.get(1);
String stationFilter = client
.getChatPreferences()
.getLogsynch_wintestNetworkStationNameOfWintestClient1();
if (stationFilter != null
&& !stationFilter.isBlank()
&& !stationName.equalsIgnoreCase(stationFilter)) {
return;
}
String val2 = parts.get(5);
String freqRaw = parts.get(7);
double freqFloat = Integer.parseInt(freqRaw) / 10.0;
String modeValue = parts.get(5);
long mainFrequencyRaw = Long.parseLong(parts.get(7));
double mainFrequencyKHz = mainFrequencyRaw / 10.0;
String mode;
if ("1".equals(val2)) {
mode = freqFloat > 10000.0 ? "usb" : "lsb";
if ("1".equals(modeValue)) {
mode = mainFrequencyKHz > 10000.0 ? "usb" : "lsb";
} else {
mode = "cw";
}
// Format as MMM.KKK.HH display format (e.g. 144.300.00) consistent with UCX thread
// freqFloat is in kHz (e.g. 144300.0), convert to Hz-string for formatting
long freqHzTimes100 = Math.round(freqFloat * 100.0); // e.g. 14430000
String hzStr = String.valueOf(freqHzTimes100);
String formattedQRG;
if (hzStr.length() == 8) {
// 144MHz range: 14430000 -> 144.300.00
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 3), hzStr.substring(3, 6), hzStr.substring(6, 8));
} else if (hzStr.length() == 9) {
// 1296MHz range: 129600000 -> 1296.000.00
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 4), hzStr.substring(4, 7), hzStr.substring(7, 9));
} else if (hzStr.length() == 7) {
// 70MHz range: 7010000 -> 70.100.00
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 2), hzStr.substring(2, 5), hzStr.substring(5, 7));
} else if (hzStr.length() == 6) {
// 50MHz range: 5030000 but 6 digits: 503000 -> 5.030.00
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 1), hzStr.substring(1, 4), hzStr.substring(4, 6));
} else {
formattedQRG = String.format(Locale.US, "%.1f", freqFloat); // fallback
}
// Parse pass frequency from parts[11] if available (WT STATUS format)
String formattedPassQRG = null;
String formattedMainQrg =
helper_formatWinTestFrequency(mainFrequencyRaw);
/*
* Token 11 may contain the pass frequency, depending on the
* Win-Test STATUS packet. Small numeric flag values must not be
* interpreted as frequencies.
*/
String formattedPassQrg = null;
if (parts.size() > 11) {
try {
String passFreqRaw = parts.get(11);
double passFreqFloat = Integer.parseInt(passFreqRaw) / 10.0;
if (passFreqFloat > 100) { // Must be a valid radio frequency (> 100 kHz), protects against parsing boolean flag tokens
long passFreqHzTimes100 = Math.round(passFreqFloat * 100.0);
String passHzStr = String.valueOf(passFreqHzTimes100);
if (passHzStr.length() == 8) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 3), passHzStr.substring(3, 6), passHzStr.substring(6, 8));
} else if (passHzStr.length() == 9) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 4), passHzStr.substring(4, 7), passHzStr.substring(7, 9));
} else if (passHzStr.length() == 7) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 2), passHzStr.substring(2, 5), passHzStr.substring(5, 7));
} else if (passHzStr.length() == 6) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 1), passHzStr.substring(1, 4), passHzStr.substring(4, 6));
} else {
formattedPassQRG = String.format(Locale.US, "%.1f", passFreqFloat);
}
long passFrequencyRaw =
Long.parseLong(parts.get(11));
double passFrequencyKHz =
passFrequencyRaw / 10.0;
if (passFrequencyKHz > 100.0) {
formattedPassQrg =
helper_formatWinTestFrequency(
passFrequencyRaw
);
}
} catch (Exception ignored) {
// parts[11] not a valid frequency, leave formattedPassQRG as null
} catch (NumberFormatException
| IllegalArgumentException
| ArithmeticException ignored) {
/*
* Token 11 does not contain a usable frequency.
* The main frequency remains the safe fallback.
*/
}
}
if (this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()) {
final String qrgToSet = (this.client.getChatPreferences().isLogsynch_wintestUsePassQrg() && formattedPassQRG != null)
? formattedPassQRG
: formattedQRG;
// JavaFX StringProperty must be updated on the FX Application Thread
Platform.runLater(() -> this.client.getChatPreferences().getMYQRGFirstCat().set(qrgToSet));
boolean usePassQrg = client
.getChatPreferences()
.isLogsynch_wintestUsePassQrg();
final String qrgToSet =
usePassQrg && formattedPassQrg != null
? formattedPassQrg
: formattedMainQrg;
if (client
.getChatPreferences()
.isLogsynch_wintestQrgSyncEnabled()) {
Platform.runLater(
() -> client
.getChatPreferences()
.getMYQRGFirstCat()
.set(qrgToSet)
);
}
System.out.println("[WinTest STATUS] stn=" + stn + ", mode=" + mode + ", qrg=" + formattedQRG
+ (formattedPassQRG != null ? ", passQrg=" + formattedPassQRG : "")
+ ", syncActive=" + this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled());
} catch (Exception e) {
System.out.println("[WinTest] STATUS parsing error: " + e.getMessage());
System.out.println(
"[WinTest STATUS] stn=" + stationName
+ ", mode=" + mode
+ ", qrg=" + formattedMainQrg
+ (formattedPassQrg != null
? ", passQrg=" + formattedPassQrg
: "")
+ ", selectedQrg=" + qrgToSet
+ ", syncActive="
+ client
.getChatPreferences()
.isLogsynch_wintestQrgSyncEnabled()
);
} catch (Exception exception) {
System.out.println(
"[WinTest] STATUS parsing error: "
+ exception.getMessage()
);
}
}
+4 -1
View File
@@ -2386,4 +2386,7 @@ PA2RU;René;JO32LT;StringProperty [value: null]; wkd true; wkd144 false; wkd432f
SM6VTZ;Chris .135;JO58UJ;StringProperty [value: 144.135]; wkd true; wkd144 false; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
SM7EYW;Torleif 432,205;JO65NK;StringProperty [value: 144.205]; wkd true; wkd144 false; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
DJ8MS;Tor_70cm;JO54UC;StringProperty [value: null]; wkd true; wkd144 false; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
DK0MM;Jens/Alex;JN49IU;StringProperty [value: 432.305]; wkd true; wkd144 false; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
DK0MM;Jens/Alex;JN49IU;StringProperty [value: 432.305]; wkd true; wkd144 false; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
SM6VTZ;Chris .135;JO58UJ;StringProperty [value: 144.135]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
SM6VTZ;Chris .135;JO58UJ;StringProperty [value: 432.135]; wkd true; wkd144 true; wkd432true; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz
LA0BY;Stefan @ hilltop;JO59IX;StringProperty [value: 144.062]; wkd true; wkd144 true; wkd432false; wkd1240false; wkd2300false; wkd3400false; wkd5600false; wkd10Gfalse ; 2: 144/432 MHz