Added 6+4m and 10G and up frequency detection patterns

This commit is contained in:
Marc Froehlich
2026-08-10 02:42:29 +02:00
parent 852f76b05a
commit caaeebd00c
2 changed files with 192 additions and 88 deletions
@@ -101,102 +101,143 @@ public class Utils4KST {
}
/**
* Normalizes a chatmembers frequency-string for cluster usage<br/>
* <b>returns a frequency String in KHz like = "144300" or "144300.0" to match DXC protocol needs</b>
* Converts a frequency detected in a chat message into the kHz representation
* used by the DX Cluster protocol.
*
* @param optionalPrefix: if there is a value like ".300", it have to be decided, wich ".300": 144.300, 432.300, 1296.300 .... prefix means for example "144."
* <p>Complete frequencies with two to five MHz digits are supported, for
* example 50.200, 144.205, 1296.338, 10368.100 and 24048.100. An optional
* second fractional group is retained as sub-kHz precision, for example
* 144.205.2 becomes 144205.2 kHz.</p>
*
* <p>Relative values such as .205 or 205 use the configured fallback-band
* prefix. The method only performs this fallback conversion after the general
* message parser has already accepted the value as a frequency.</p>
*
* @param qrgString frequency as detected by the chat parser
* @param optionalPrefix fallback MHz prefix for relative frequencies
* @return frequency in kHz for a DX Cluster spot, or an empty string if the
* value cannot be converted safely
*/
public static String normalizeFrequencyString(String qrgString, SimpleStringProperty optionalPrefix) {
// final String PTRN_QRG_CAT2 = "(([0-9]{3,4}[\\.|,| ]?[0-9]{3})([\\.|,][\\d]{1,2})?)|(([a-zA-Z][0-4]{1}[\\d]{2}\\b)([\\.|,][\\d]{1,2}\\b)?)|((\\b[0-4]{1}[\\d]{2}\\b)([\\.|,][\\d]{1,2}\\b)?)";
try {
qrgString = qrgString.replace(" ","");
} catch (Exception e) {
System.out.println("UTILS: QRG NULL, nothing to convert");
// e.printStackTrace();
public static String normalizeFrequencyString(
String qrgString,
SimpleStringProperty optionalPrefix
) {
if (qrgString == null || qrgString.isBlank()) {
return "";
}
/*
* A comma is accepted as a decimal separator. Spaces are removed so older
* stored formats such as "432 088" remain usable.
*/
String normalizedValue = qrgString
.trim()
.replace(" ", "")
.replace(',', '.');
final String PTRN_QRG_CAT2_wholeQRGMHz4Digits = "(([0-9]{4}[\\.|,| ]?[0-9]{3})([\\.|,][\\d]{1,2})?)"; //1296.300.3 etc
final String PTRN_QRG_CAT2_wholeQRGMHz3Digits = "(([0-9]{3}[\\.|,| ]?[0-9]{3})([\\.][\\d]{1,2})?)"; //144.300.3 etc
final String PTRN_QRG_CAT2_QRGwithoutPrefix = "((\\b[0-4]{1}[\\d]{2}\\b)([\\.|,][\\d]{1,2}\\b)?)"; //144.300.3 etc
/*
* Complete frequency with an explicit separator:
*
* 50.200
* 144.205
* 1296.338
* 10368.100
* 144.205.2
*/
Matcher completeFrequencyMatcher = Pattern.compile(
"^(\\d{2,5})\\.(\\d{1,3})(?:\\.(\\d{1,2}))?$"
).matcher(normalizedValue);
String stringAggregation = "";
if (testPattern(qrgString, PTRN_QRG_CAT2_wholeQRGMHz4Digits)) {//case 1296.200 or 1296.200.2 etc.
stringAggregation = qrgString;
stringAggregation = stringAggregation.replace(".","");
stringAggregation = stringAggregation.replace(",","");
stringAggregation = stringAggregation.replace(" ", "");
if (stringAggregation.length() == 8) {
String stringAggregationNew = stringAggregation.substring(0, stringAggregation.length()-1) + "." + stringAggregation.substring(stringAggregation.length()-1, stringAggregation.length());
stringAggregation = stringAggregationNew + ".0";
return stringAggregation;
} else if (stringAggregation.length() == 9) {
String stringAggregationNew = stringAggregation.substring(0, stringAggregation.length()-2) + "." + stringAggregation.substring(stringAggregation.length()-2, stringAggregation.length());
stringAggregation = stringAggregationNew;
return stringAggregation;
}
} else
if (testPattern(qrgString, PTRN_QRG_CAT2_wholeQRGMHz3Digits)) { //case 144.300 or 144.300.2
stringAggregation = qrgString;
stringAggregation = stringAggregation.replace(".","");
stringAggregation = stringAggregation.replace(",","");
stringAggregation = stringAggregation.replace(" ", "");
if (stringAggregation.length() == 6) {
stringAggregation = stringAggregation + ".0";
return stringAggregation;
}
if (stringAggregation.length() == 7) {
String stringAggregationNew = stringAggregation.substring(0, stringAggregation.length()-1) + "." + stringAggregation.substring(stringAggregation.length()-1, stringAggregation.length());
stringAggregation = stringAggregationNew + ".0";
return stringAggregation;
} else if (stringAggregation.length() == 8) {
String stringAggregationNew = stringAggregation.substring(0, stringAggregation.length()-2) + "." + stringAggregation.substring(stringAggregation.length()-2, stringAggregation.length());
stringAggregation = stringAggregationNew;
return stringAggregation;
}
}
else
if (testPattern(qrgString, PTRN_QRG_CAT2_QRGwithoutPrefix)) { //case ".050 or .300 or something like that"
stringAggregation = qrgString;
stringAggregation = stringAggregation.replace(".", "");
stringAggregation = stringAggregation.replace(",", "");
stringAggregation = stringAggregation.replace(" ", "");
if (stringAggregation.length() == 3) { // like 050 or 300
String stringAggregationNew = optionalPrefix.getValue() + stringAggregation;
stringAggregation = stringAggregationNew + ".0";
return stringAggregation;
} else if (stringAggregation.length() == 4) { //like 050.2 --> 0502
stringAggregation = optionalPrefix.getValue() + stringAggregation;
String stringAggregationNew = stringAggregation.substring(0, stringAggregation.length() - 1) + "." + stringAggregation.substring(stringAggregation.length() - 1, stringAggregation.length());
stringAggregation = stringAggregationNew;
return stringAggregation;
} else if (stringAggregation.length() == 5) { //like 050.20 --> 05020
stringAggregation = optionalPrefix.getValue() + stringAggregation;
String stringAggregationNew = stringAggregation.substring(0, stringAggregation.length() - 2) + "." + stringAggregation.substring(stringAggregation.length() - 2, stringAggregation.length());
stringAggregation = stringAggregationNew;
return stringAggregation;
}
if (completeFrequencyMatcher.matches()) {
return formatDxClusterFrequency(
completeFrequencyMatcher.group(1),
completeFrequencyMatcher.group(2),
completeFrequencyMatcher.group(3)
);
}
return stringAggregation; //if nothing else helps
/*
* Compact complete frequency retained for compatibility:
*
* 144205
* 1296338
* 10368100
* 432088.2
*/
Matcher compactFrequencyMatcher = Pattern.compile(
"^(\\d{2,5})(\\d{3})(?:\\.(\\d{1,2}))?$"
).matcher(normalizedValue);
if (compactFrequencyMatcher.matches()) {
return formatDxClusterFrequency(
compactFrequencyMatcher.group(1),
compactFrequencyMatcher.group(2),
compactFrequencyMatcher.group(3)
);
}
/*
* Relative frequency. Values above 499 are deliberately rejected, matching
* the previous behaviour and preventing a report such as 599 from becoming
* a plausible-looking cluster frequency.
*/
Matcher relativeFrequencyMatcher = Pattern.compile(
"^\\.?([0-4]\\d{2})(?:\\.(\\d{1,2}))?$"
).matcher(normalizedValue);
if (!relativeFrequencyMatcher.matches()) {
return "";
}
String fallbackPrefix = optionalPrefix == null
? null
: optionalPrefix.getValue();
if (fallbackPrefix == null) {
return "";
}
fallbackPrefix = fallbackPrefix.trim();
if (!fallbackPrefix.matches("\\d{2,5}")) {
return "";
}
return formatDxClusterFrequency(
fallbackPrefix,
relativeFrequencyMatcher.group(1),
relativeFrequencyMatcher.group(2)
);
}
/**
* Formats an MHz part, a fractional MHz part and optional sub-kHz digits as a
* DX Cluster frequency in kHz.
*
* <p>The fractional MHz part is padded on the right because 144.2 means
* 144.200 MHz, while 144.21 means 144.210 MHz.</p>
*
* @param mhzPart complete MHz part
* @param fractionalMhzPart one to three digits following the first separator
* @param subKhzPart optional digits following a second separator
* @return DX Cluster frequency in kHz
*/
private static String formatDxClusterFrequency(
String mhzPart,
String fractionalMhzPart,
String subKhzPart
) {
String paddedFraction =
(fractionalMhzPart + "000").substring(0, 3);
String subKhz = subKhzPart == null
? "0"
: subKhzPart;
return mhzPart
+ paddedFraction
+ "."
+ subKhz;
}
}
@@ -0,0 +1,63 @@
package kst4contest.test;
import javafx.beans.property.SimpleStringProperty;
import kst4contest.controller.Utils4KST;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class Utils4KSTFrequencyTest {
@Test
void convertsCompleteFrequenciesAcrossAllSupportedPrefixLengths() {
assertEquals("50200.0", convert("50.200", "144"));
assertEquals("70250.0", convert("70,250", "144"));
assertEquals("144205.0", convert("144.205", "432"));
assertEquals("432088.0", convert("432.088", "144"));
assertEquals("1296338.0", convert("1296.338", "144"));
assertEquals("10368100.0", convert("10368.100", "144"));
assertEquals("24048100.0", convert("24048.100", "144"));
}
@Test
void retainsSubKhzPrecision() {
assertEquals("144205.2", convert("144.205.2", "432"));
assertEquals("1296338.25", convert("1296.338.25", "144"));
}
@Test
void convertsCompactLegacyFormats() {
assertEquals("432088.0", convert("432088", "144"));
assertEquals("10368100.0", convert("10368100", "144"));
assertEquals("432088.2", convert("432088.2", "144"));
assertEquals("432088.0", convert("432 088", "144"));
}
@Test
void appliesFallbackOnlyToRelativeFrequencies() {
assertEquals("432205.0", convert(".205", "432"));
assertEquals("144205.0", convert("205", "144"));
assertEquals("10368300.0", convert(".300", "10368"));
}
@Test
void rejectsMissingOrUnsupportedValues() {
assertEquals("", convert(null, "144"));
assertEquals("", convert("", "144"));
assertEquals("", convert("not-a-frequency", "144"));
assertEquals("", convert("599", "144"));
assertEquals("", convert(".205", null));
assertEquals("", convert(".205", "invalid"));
}
private String convert(String frequency, String fallbackPrefix) {
SimpleStringProperty fallback = fallbackPrefix == null
? null
: new SimpleStringProperty(fallbackPrefix);
return Utils4KST.normalizeFrequencyString(
frequency,
fallback
);
}
}