fix: Antenna Genius drew both ports on the same antenna

Reported on an 8x2: port A correctly on the 80 m vertical, then a few seconds
later both A and B shown on the same beam.

The display preferred the TX antenna, falling back to RX. On an 8x2 only ONE port
can hold the transmit antenna, so the switch reports the same txant on both — and
every keepalive poll redrew port A as whatever port B transmits through. The RX
antenna is the per-port selection, and the only thing Activate sets, so it is
what a port shows; TX stays the fallback for a port reporting no RX antenna.

A port change is now logged with the raw line — but only on CHANGE, since the
device pushes state every few seconds and logging each one would bury the rest.
Without any trace, "port A jumped to the wrong antenna" cannot be checked.

Two tests pin it: rx wins over a disagreeing tx, and a message about one port
never moves the other — which is the shape that was actually on screen.
This commit is contained in:
2026-07-29 12:46:48 +02:00
parent 08f3e54afb
commit 0cc806722e
3 changed files with 109 additions and 14 deletions
+34 -14
View File
@@ -66,11 +66,12 @@ type Client struct {
authTries int
ready atomic.Bool // init commands sent → keepalive may run
statusMu sync.RWMutex
status Status
antennas map[int]string // index → name (rebuilt into status.Antennas)
antBands map[int]int // index → band bitmask (which bands the antenna covers)
antRawN int // one-shot: how many raw antenna lines we've logged
statusMu sync.RWMutex
status Status
antennas map[int]string // index → name (rebuilt into status.Antennas)
antBands map[int]int // index → band bitmask (which bands the antenna covers)
antRawN int // one-shot: how many raw antenna lines we've logged
lastShown map[int]int // port id → antenna last reported, so only CHANGES are logged
stop chan struct{}
running bool
@@ -81,13 +82,14 @@ func New(host string, port int, password string) *Client {
port = defaultPort
}
return &Client{
host: host,
port: port,
password: strings.TrimSpace(password),
stop: make(chan struct{}),
antennas: map[int]string{},
antBands: map[int]int{},
status: Status{Host: host},
host: host,
port: port,
password: strings.TrimSpace(password),
stop: make(chan struct{}),
antennas: map[int]string{},
antBands: map[int]int{},
lastShown: map[int]int{},
status: Status{Host: host},
}
}
@@ -398,9 +400,27 @@ func (c *Client) parsePort(msg string) {
}
tx := kvInt(msg, "txant")
rx := kvInt(msg, "rxant")
active := tx
// The RX antenna is what identifies a port's selection, and it is the only
// thing Activate sets. Preferring the TX antenna made both ports converge on
// the same row a few seconds after a change: on an 8x2 only ONE port can hold
// the transmit antenna, so the device reports the same txant on both, and a
// port A genuinely on the 80 m vertical was redrawn as the beam that port B
// transmits through.
//
// TX remains the fallback for a port that reports no RX antenna at all.
active := rx
if active == 0 {
active = rx
active = tx
}
// Logged only when the shown antenna CHANGES: the device pushes port state
// every few seconds, and logging each one would bury everything else — but
// without any trace, "port A jumped to the wrong antenna" is unfalsifiable.
c.statusMu.Lock()
prev, seen := c.lastShown[id]
c.lastShown[id] = active
c.statusMu.Unlock()
if !seen || prev != active {
applog.Printf("antgenius: port %d → antenna %d (rxant=%d txant=%d) raw=%q", id, active, rx, tx, msg)
}
txOn := kvInt(msg, "tx") != 0 // the standalone "tx=0|1" transmit flag
c.setStatus(func(s *Status) {