fix: log the grid the station sent, and show the SPE going off first press
Grid: RI0FA transmitted QN35 all evening and was logged with its licence holder's home square. WSJT-X's logged ADIF usually carries no GRIDSQUARE, so the lookup was the only source left — and QRZ/HamQTH describe where an operator LIVES, which for an expedition is the wrong side of the planet. The square heard on the air is now applied before the lookup runs, so refineGrid has something to defend. It still upgrades a 4-character square to a 6-character one from the same field, and still refuses a finer square from a different field. SPE: decodeCSV marks the client connected on every frame it parses, and the poll goroutine can be mid-read while PowerOff runs. The frame from a second ago landed after PowerOff had marked the amp offline and put it straight back, which is why OFF had to be pressed twice. Frames are ignored for three seconds after the off key goes out — the window is opened before the key is sent, so nothing already travelling can beat it. Time bounded rather than latched: an amp switched back on at its own front panel has to reappear without being told.
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package spe
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// decodeCSV marks the client connected on EVERY frame it parses, and the poll
|
||||
// goroutine can be mid-read while PowerOff runs. So the frame from a second ago
|
||||
// landed after PowerOff had marked the amp offline and put it straight back —
|
||||
// the console kept showing the amplifier on until the operator pressed OFF a
|
||||
// second time.
|
||||
func TestFrameInFlightDoesNotResurrectASwitchedOffAmp(t *testing.T) {
|
||||
c := &Client{}
|
||||
const frame = ",13K,O,R,A,1,05,1b,0r,M,0000, 0.00, 0.00, 1.3, 0.0, 26,000,000,N,N,"
|
||||
|
||||
// Normal running: a frame marks it connected and reads OPERATE.
|
||||
c.decodeCSV(frame)
|
||||
if !c.GetStatus().Connected || !c.GetStatus().Operate {
|
||||
t.Fatal("a status frame should mark the amp connected")
|
||||
}
|
||||
|
||||
// The operator presses OFF. This is what PowerOff does, in order.
|
||||
c.statusMu.Lock()
|
||||
c.offUntil = time.Now().Add(3 * time.Second)
|
||||
c.statusMu.Unlock()
|
||||
c.setErr(errSwitchedOff{})
|
||||
|
||||
// A frame that was already on the wire arrives now.
|
||||
c.decodeCSV(frame)
|
||||
|
||||
if c.GetStatus().Connected {
|
||||
t.Error("a frame from before the switch-off put the amp back on — this is the double-click on OFF")
|
||||
}
|
||||
}
|
||||
|
||||
// An amp switched back on at its own front panel must reappear without OpsLog
|
||||
// being told, so the suppression has to expire rather than latch.
|
||||
func TestSuppressionExpires(t *testing.T) {
|
||||
c := &Client{}
|
||||
c.statusMu.Lock()
|
||||
c.offUntil = time.Now().Add(-time.Second) // window already past
|
||||
c.statusMu.Unlock()
|
||||
c.setErr(errSwitchedOff{})
|
||||
|
||||
c.decodeCSV(",13K,O,R,A,1,05,1b,0r,M,0000, 0.00, 0.00, 1.3, 0.0, 26,000,000,N,N,")
|
||||
if !c.GetStatus().Connected {
|
||||
t.Error("an amp answering after the window must come back on its own")
|
||||
}
|
||||
}
|
||||
|
||||
type errSwitchedOff struct{}
|
||||
|
||||
func (errSwitchedOff) Error() string { return "switched off" }
|
||||
Reference in New Issue
Block a user