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:
@@ -91,6 +91,15 @@ type Client struct {
|
||||
statusMu sync.RWMutex
|
||||
status Status
|
||||
lastRaw string // last raw status payload logged (log only on change)
|
||||
// offUntil suppresses status frames that were already in flight when the amp
|
||||
// was switched off. 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 resurrected an amplifier the operator had just
|
||||
// switched off, and the console only caught up when they pressed OFF twice.
|
||||
//
|
||||
// Time-bounded rather than a latch: an amp switched back on at its own front
|
||||
// panel must reappear on its own.
|
||||
offUntil time.Time
|
||||
|
||||
stop chan struct{}
|
||||
running bool
|
||||
@@ -177,6 +186,12 @@ func (c *Client) PowerOn() error {
|
||||
if c.GetStatus().Connected {
|
||||
return nil
|
||||
}
|
||||
// Switching on cancels the suppression window: the operator wants frames
|
||||
// believed again, and waiting out a timer they cannot see would read as the
|
||||
// power-on having failed.
|
||||
c.statusMu.Lock()
|
||||
c.offUntil = time.Time{}
|
||||
c.statusMu.Unlock()
|
||||
// The poll goroutine may still be inside a blocking read on the old handle;
|
||||
// Windows keeps the port "busy" until that read times out (ioTimeout). Retry
|
||||
// the open for a little longer than that.
|
||||
@@ -226,6 +241,14 @@ func (c *Client) PowerOn() error {
|
||||
// the poll loop keeps reopening the port with both lines high and that has
|
||||
// never woken it — only the deliberate low→high sequence in PowerOn does.
|
||||
func (c *Client) PowerOff() error {
|
||||
// Close the window BEFORE the key goes out, so a frame already travelling up
|
||||
// the wire cannot land after setErr and undo it. Three seconds covers a poll
|
||||
// interval with room to spare; after that a real answer means the amp is
|
||||
// genuinely alive again, which is what happens if it is switched back on at
|
||||
// its own front panel.
|
||||
c.statusMu.Lock()
|
||||
c.offUntil = time.Now().Add(3 * time.Second)
|
||||
c.statusMu.Unlock()
|
||||
err := c.sendCmd(cmdOff)
|
||||
applog.Printf("spe: power OFF — SWITCH OFF key sent (err=%v)", err)
|
||||
// Drop the link and mark the amp offline at once, exactly as a fresh start
|
||||
@@ -482,6 +505,9 @@ func (c *Client) decodeCSV(payload string) {
|
||||
c.lastRaw = payload
|
||||
applog.Printf("spe: status raw=%q fields=%d", payload, len(f))
|
||||
}
|
||||
if time.Now().Before(c.offUntil) {
|
||||
return // a frame from before the switch-off — the amp is on its way down
|
||||
}
|
||||
c.status.Connected = true
|
||||
c.status.LastError = ""
|
||||
// The real frame carries a leading empty field (it starts with a comma), so the
|
||||
|
||||
Reference in New Issue
Block a user