fix: make beacons session-only, slower and QSY-aware (#171)

Forgotten beacons kept filling the chat while nobody was at the station.

- The two enable switches are session state now: they start off, are
  never read from preferences.xml and are always stored as "false", so
  neither a stored switch nor an older release reading a newer file can
  start a beacon. The in-code default was "on" and started a beacon
  whenever the file was unreadable or had no <beaconCQ> block.
- The minimum interval rises from 1 to 5 minutes; the bundled template
  ships 10 instead of 1. Smaller stored values are raised when the
  timer is scheduled.
- BeaconFrequencyGuard switches a beacon off once a logger-reported
  transceiver frequency lies more than 2 kHz from the one reported when
  the beacon was switched on. UCXLog RadioInfo and Win-Test STATUS feed
  it wherever they already set MYQRG/SECONDQRG. Without SO2R the single
  radio stands for both categories, with SO2R each radio for its own.

Manual (DE/EN), changelog, website and PROJECT_CONTEXT updated.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJ1tUJVmWChD9V5Pg7uH8X
This commit is contained in:
Claude committed 2026-11-11 09:53:27 +00:00
1 parent e5c057378f
commit 86289609e8
25 files changed
+600 -39

No files matched your search

@@ -473,7 +473,7 @@ profiles.changeAfterSwitch=Die Änderung wirkt erst nach einem erneuten Wechsel
profiles.alreadyActive=Dieses Profil ist bereits aktiv.
profiles.selectFirst=Zuerst ein Profil in der Tabelle wählen.
beacon.invalidMessage=Der Bakentext ist ungültig: {0}
beacon.intervalTooShort=Einen ganzzahligen Bakenabstand von mindestens {0} Minute eingeben.
beacon.intervalTooShort=Einen ganzzahligen Bakenabstand von mindestens {0} Minuten eingeben.
notification.invalidPort=„{0}" ist kein gültiger TCP-Port. Einen Wert von 1 bis 65535 eingeben.
notification.invalidSpotter=„{0}" ist kein gültiges Spotter-Rufzeichen.
notification.invalidCallsign=Bitte ein gültiges Rufzeichen eingeben.
@@ -482,7 +482,7 @@ profiles.changeAfterSwitch=The change takes effect after switching to this profi
profiles.alreadyActive=This profile is already active.
profiles.selectFirst=Select a profile in the table first.
beacon.invalidMessage=The beacon message is invalid: {0}
beacon.intervalTooShort=Enter a whole beacon interval of at least {0} minute.
beacon.intervalTooShort=Enter a whole beacon interval of at least {0} minutes.
notification.invalidPort="{0}" is not a valid TCP port. Enter a value from 1 to 65535.
notification.invalidSpotter="{0}" is not a valid spotter callsign.
notification.invalidCallsign=Please enter a valid callsign.
@@ -180,6 +180,20 @@ class BeaconTabStateTest(@TempDir private val tempDir: Path) {
"restarting the timer per rejected keystroke would disturb the cooldown")
}
/** Issue #171: one minute was allowed and was what the template shipped. */
@Test
fun `an interval below five minutes is refused and five is accepted`() {
val s = Session()
s.prefs.setBcn_beaconIntervalInMinutesMainCat(9)
val state = s.state()
assertNotNull(state.commitBeaconInterval("4"))
assertEquals(9, state.beaconIntervalMinutes)
assertNull(state.commitBeaconInterval("5"))
assertEquals(5, state.beaconIntervalMinutes)
}
@Test
fun `an interval that is not a whole number is refused`() {
val s = Session()
@@ -73,7 +73,7 @@ class RemainingTextsFollowTheLanguageTest {
assertEquals("Wertung", score.title(), "the column title stayed in the old language")
assertEquals(
"Einen ganzzahligen Bakenabstand von mindestens 1 Minute eingeben.",
"Einen ganzzahligen Bakenabstand von mindestens 5 Minuten eingeben.",
beacon.commitBeaconInterval("0"),
"the refusal from the tab state stayed in the old language",
)