From e4a5d42b8518e0eb8c1dbe1dca35d70b1969ea3e Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 5 Sep 2026 21:45:51 +0200 Subject: [PATCH] feat(app): auto-call, PSK Reporter analysis and the decode band change The Wails boundary for the two features above, plus the pieces that belong to neither: - decodes are cut into periods PER RECEIVER, and judged 0.8 s after the last one arrives rather than a slot and four seconds after the period they belong to. That stamp is the START of the slot, thirteen seconds before its decodes exist, so every answer left four seconds into the next slot and the decoder began its call late. - WSJT-X colouring can grey out a station already worked on this band AND in this mode. Its own switch, off by default: the other three verdicts pick out a handful of decodes, this one can match most of a period on a full log. - the watch list's contest auto-add takes a list of callsigns as well as a pattern. A pattern collects a fleet that shares a string; it cannot collect the station taking part under a callsign that says nothing about the event. --- app.go | 25 +++++++++++++++++++++++-- frontend/wailsjs/go/main/App.d.ts | 8 ++++++++ frontend/wailsjs/go/main/App.js | 16 ++++++++++++++++ frontend/wailsjs/go/models.ts | 10 ++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index ba89f2d..8aa8b6f 100644 --- a/app.go +++ b/app.go @@ -753,6 +753,9 @@ type App struct { // is consulted once per message. chaseBandsMu sync.RWMutex chaseBands map[string]bool + // The Chase new panel's OWN band filter, as the set switched off. Read per + // message like the list above, so an atomic rather than a settings lookup. + chaseNewBandsOff atomic.Value // map[string]bool // pskr is the PSK Reporter MQTT feed, up only while the opening watch is on. // It is the source that makes VHF detection work at all: the cluster and RBN // carry a handful of 6 m spots where PSK Reporter carries hundreds. @@ -773,8 +776,12 @@ type App struct { // FT8 window and an FT4 one do not even share a period length — and one // buffer for both cut every slot into fragments, each judged as a period of // its own. The engine then counted a missed period several times a slot. - acPeriod map[string]string - acAt map[string]time.Time + acPeriod map[string]string + acAt map[string]time.Time + // acFed is when the last decode of the open period ARRIVED, wall clock. The + // period's own timestamp cannot answer "has it gone quiet" — it is the start + // of the slot, thirteen seconds before its decodes exist. + acFed map[string]time.Time acTR map[string]int acBuf map[string][]acDecode acTX autocall.TXState @@ -782,6 +789,10 @@ type App struct { acLastJudge time.Time // acTXPeriod is the last transmit period already counted — see autoCallNoteTX. acTXPeriod string + // What the decodes panel is SHOWING, and whether it is publishing at all. + // The panel owns the filters; this is its answer, not a second copy of them. + acVisible map[string]bool + acVisibleOn bool // Self-spot throttle: when and on what frequency we last announced ourselves. // Held in memory only — a restart legitimately re-announces the station. selfSpotMu sync.Mutex @@ -1653,6 +1664,7 @@ func (a *App) startup(ctx context.Context) { a.startGridCache() a.chaseNew = newChaseNewStore() a.refreshChaseNew() + a.refreshChaseNewBands() // 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() @@ -20578,6 +20590,15 @@ func (a *App) SaveChaseSettings(cs ChaseSettings) error { a.clusterStatusMu.Lock() a.clusterStatusIdx = nil a.clusterStatusMu.Unlock() + // Chase new judges each decode AS IT ARRIVES and keeps only what was new at + // that moment, so its list is a set of verdicts reached under the old rule — + // and nothing in it would ever be re-judged. Switching to "new only" left + // rows listed that are merely unconfirmed; switching the other way could not + // bring back what had already been refused. Emptied, it refills from the + // live feed within a period or two, under the rule now in force. + if a.chaseNew != nil { + a.chaseNew.clear() + } // The frontend caches resolved verdicts and only asks about unknown keys — // without this it kept judging half the screen under the OLD rules until a // restart, which read as the new mode simply not working. diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index a667b07..82b4831 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -454,6 +454,8 @@ export function GetChangelog():Promise>; export function GetChaseNew():Promise; +export function GetChaseNewBands():Promise>; + export function GetChaseNewGrids():Promise; export function GetChaseNewSpots():Promise>; @@ -654,6 +656,8 @@ export function GetYaesuState():Promise; export function GridSquares(arg1:string):Promise>; +export function HaltAutoCall():Promise; + export function HaltDecodeTx(arg1:string,arg2:boolean):Promise; export function HasBuiltinReferences(arg1:string):Promise; @@ -1158,6 +1162,8 @@ export function SetAutoCall(arg1:boolean):Promise; export function SetAutoCallOnly(arg1:string):Promise; +export function SetAutoCallVisible(arg1:Array,arg2:boolean):Promise; + export function SetCATFrequency(arg1:number):Promise; export function SetCATMode(arg1:string):Promise; @@ -1168,6 +1174,8 @@ export function SetCWDecoderPitch(arg1:number):Promise; export function SetChaseNew(arg1:boolean):Promise; +export function SetChaseNewBands(arg1:Array):Promise; + export function SetChaseNewGrids(arg1:boolean):Promise; export function SetClublogCtyEnabled(arg1:boolean):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 74e5f7e..4f4713b 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -842,6 +842,10 @@ export function GetChaseNew() { return window['go']['main']['App']['GetChaseNew'](); } +export function GetChaseNewBands() { + return window['go']['main']['App']['GetChaseNewBands'](); +} + export function GetChaseNewGrids() { return window['go']['main']['App']['GetChaseNewGrids'](); } @@ -1242,6 +1246,10 @@ export function GridSquares(arg1) { return window['go']['main']['App']['GridSquares'](arg1); } +export function HaltAutoCall() { + return window['go']['main']['App']['HaltAutoCall'](); +} + export function HaltDecodeTx(arg1, arg2) { return window['go']['main']['App']['HaltDecodeTx'](arg1, arg2); } @@ -2250,6 +2258,10 @@ export function SetAutoCallOnly(arg1) { return window['go']['main']['App']['SetAutoCallOnly'](arg1); } +export function SetAutoCallVisible(arg1, arg2) { + return window['go']['main']['App']['SetAutoCallVisible'](arg1, arg2); +} + export function SetCATFrequency(arg1) { return window['go']['main']['App']['SetCATFrequency'](arg1); } @@ -2270,6 +2282,10 @@ export function SetChaseNew(arg1) { return window['go']['main']['App']['SetChaseNew'](arg1); } +export function SetChaseNewBands(arg1) { + return window['go']['main']['App']['SetChaseNewBands'](arg1); +} + export function SetChaseNewGrids(arg1) { return window['go']['main']['App']['SetChaseNewGrids'](arg1); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 8a202b4..53685a0 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1962,6 +1962,8 @@ export namespace main { misses: number; max_rounds: number; rest_min: number; + on_screen_only: boolean; + trace: boolean; static createFrom(source: any = {}) { return new AutoCallSettings(source); @@ -1976,6 +1978,8 @@ export namespace main { this.misses = source["misses"]; this.max_rounds = source["max_rounds"]; this.rest_min = source["rest_min"]; + this.on_screen_only = source["on_screen_only"]; + this.trace = source["trace"]; } } export class AutoCallStatus { @@ -1987,6 +1991,7 @@ export namespace main { misses: number; max_miss: number; stopped: boolean; + greylisted: number; reason: string; static createFrom(source: any = {}) { @@ -2003,6 +2008,7 @@ export namespace main { this.misses = source["misses"]; this.max_miss = source["max_miss"]; this.stopped = source["stopped"]; + this.greylisted = source["greylisted"]; this.reason = source["reason"]; } } @@ -4962,6 +4968,8 @@ export namespace pskr { last_err?: string; broker: string; bands: string[]; + near_km: number; + squares: number; static createFrom(source: any = {}) { return new Status(source); @@ -4975,6 +4983,8 @@ export namespace pskr { this.last_err = source["last_err"]; this.broker = source["broker"]; this.bands = source["bands"]; + this.near_km = source["near_km"]; + this.squares = source["squares"]; } convertValues(a: any, classs: any, asMap: boolean = false): any {