feat(cluster): feed the locator store from PSK Reporter, filtered at the broker
The store shipped without its main source. Locators came only from this station's own WSJT-X decodes, which is what the whole MQTT discussion was about. PSK Reporter now feeds it through a new OnGrid callback, fired before any geographic filtering: what the store wants is "which square is this callsign in", and that is true whoever happened to hear the report. The subscription filters on the RECEIVER's square, a level the v2 topic exposes. Measured on the live feed: the four opening bands unfiltered are 83 messages a second, of which roughly one in a hundred survived the NearKm test that already existed here — the rest was received, TLS-decrypted, JSON-parsed and discarded. One ring of squares is 0.2 to 1.2 a second. By square rather than by DXCC, which was the obvious alternative: one country measured 1.2 messages a second (OH) against 72.5 (K) on a single band, because a DXCC can be a continent. By square the same measurement is 0.2 to 1.2, so the load follows distance — what the feed is actually about — and is the same for every operator. Grid chasing subscribes with the "+" band wildcard, so one subscription per square covers every band instead of one per band per square. The store gains a source column (decode | mqtt), migrated in place on an existing file.
This commit is contained in:
@@ -1392,13 +1392,12 @@ func (a *App) startup(ctx context.Context) {
|
||||
go a.sendTelemetryHeartbeat()
|
||||
go a.liveStatusLoop() // multi-op: heartbeat current activity to shared MySQL
|
||||
go a.chatLoop() // multi-op: poll the shared chat + heartbeat presence
|
||||
// PSK Reporter, when the opening watch is on. After the operator's grid is
|
||||
// known: without it there is no distance to measure and the feed stays down.
|
||||
a.startBandOpenFeed()
|
||||
// Locator store. After settings are scoped, so the option is read from the
|
||||
// right profile, and before the cluster starts serving statuses so the first
|
||||
// spots already carry their locators.
|
||||
// Locator store BEFORE the feed: the feed asks whether it exists to decide
|
||||
// which bands to subscribe to.
|
||||
a.startGridCache()
|
||||
// PSK Reporter. After the operator's grid is known: without it there is no
|
||||
// distance to measure and no receiver squares to filter on, so it stays down.
|
||||
a.startBandOpenFeed()
|
||||
// One-time tidy-up of a field nothing used to record. Background, once.
|
||||
a.backfillDistancesOnce()
|
||||
|
||||
@@ -11966,7 +11965,7 @@ func (a *App) consumeUDPEvents() {
|
||||
// Remember the grid before anything else: a CQ is the one message that
|
||||
// carries it, and the station may never send another.
|
||||
if ev.DecodeGrid != "" {
|
||||
a.rememberDecodeGrid(ev.DecodeCall, ev.DecodeGrid)
|
||||
a.rememberDecodeGrid(ev.DecodeCall, ev.DecodeGrid, gridcache.SourceDecode)
|
||||
}
|
||||
// A WSJT-X decode (heard station). Render it on the FlexRadio
|
||||
// panadapter when the option is on; green + SNR comment, auto-expiring
|
||||
@@ -17046,10 +17045,33 @@ const decodeGridsCap = 100000
|
||||
// GetChaseNewGrids reports whether learnt locators are kept across restarts.
|
||||
func (a *App) GetChaseNewGrids() bool { return a.settingOr(keyChaseNewGrids, "") == "1" }
|
||||
|
||||
// GridCacheStatus is the live count under the option. A store that is on but
|
||||
// has learnt nothing looks exactly like a broken one until a number moves.
|
||||
type GridCacheStatus struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Known int `json:"known"` // locators in memory, stored + learnt this session
|
||||
Pending int `json:"pending"` // waiting for the next batch write
|
||||
}
|
||||
|
||||
// GetGridCacheStatus reports what the locator store holds.
|
||||
func (a *App) GetGridCacheStatus() GridCacheStatus {
|
||||
out := GridCacheStatus{Enabled: a.gridStore != nil}
|
||||
a.decodeGridsMu.RLock()
|
||||
out.Known = len(a.decodeGrids) + len(a.decodeGridsOld)
|
||||
a.decodeGridsMu.RUnlock()
|
||||
if a.gridStore != nil {
|
||||
out.Pending = a.gridStore.Pending()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SetChaseNewGrids turns grid chasing on or off and applies it immediately.
|
||||
func (a *App) SetChaseNewGrids(on bool) error {
|
||||
a.setSetting(keyChaseNewGrids, boolStr(on))
|
||||
a.startGridCache()
|
||||
// Resubscribe: with grid chasing the feed covers every band, without it only
|
||||
// the opening bands — and with neither consumer it comes down entirely.
|
||||
a.startBandOpenFeed()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17108,7 +17130,7 @@ func (a *App) startGridCache() {
|
||||
// previous one and a fresh map takes over. Nothing is scanned, nothing is
|
||||
// timestamped, and the write stays a single map assignment — which matters
|
||||
// because this runs once per decode.
|
||||
func (a *App) rememberDecodeGrid(call, grid string) {
|
||||
func (a *App) rememberDecodeGrid(call, grid, source string) {
|
||||
call = strings.ToUpper(strings.TrimSpace(call))
|
||||
grid = strings.TrimSpace(grid)
|
||||
if call == "" || grid == "" {
|
||||
@@ -17140,7 +17162,7 @@ func (a *App) rememberDecodeGrid(call, grid string) {
|
||||
a.decodeGridsMu.Unlock()
|
||||
|
||||
if changed && store != nil {
|
||||
store.Put(call, grid)
|
||||
store.Put(call, grid, source)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user