fix: solve issue with Antenna Genius for remote operations

This commit is contained in:
2026-07-05 20:52:37 +02:00
parent 4f32012930
commit fafa0c22ab
8 changed files with 297 additions and 35 deletions
+38 -7
View File
@@ -17,6 +17,8 @@ import (
"strings"
"sync"
"time"
"hamlog/internal/applog"
)
const (
@@ -47,8 +49,9 @@ type Status struct {
}
type Client struct {
host string
port int
host string
port int
password string // remote-access password; sent as "login <pw>" when the device requires AUTH
mu sync.Mutex // guards conn + writes
conn net.Conn
@@ -61,13 +64,14 @@ type Client struct {
running bool
}
func New(host string, port int) *Client {
func New(host string, port int, password string) *Client {
if port <= 0 || port > 65535 {
port = defaultPort
}
return &Client{
host: host,
port: port,
password: strings.TrimSpace(password),
stop: make(chan struct{}),
antennas: map[int]string{},
status: Status{Host: host},
@@ -146,13 +150,27 @@ func (c *Client) runLoop() {
c.conn = conn
c.mu.Unlock()
c.setStatus(func(s *Status) { s.Connected = true; s.LastError = ""; s.Host = c.host })
applog.Printf("antgenius: TCP connected %s → %s", conn.LocalAddr(), conn.RemoteAddr())
// When reached remotely the device announces "V… AG AUTH" and refuses every
// command (R1|FF) until the client logs in. Sent FIRST so it's authenticated
// before the subscribe/get commands (the device processes the stream in
// order). On the LAN there's no AUTH and the password is blank → skipped.
if c.password != "" {
if err := c.send("login " + c.password); err != nil {
applog.Printf("antgenius: login send failed: %v", err)
} else {
applog.Printf("antgenius: sent login (password %d chars)", len(c.password))
}
}
// Subscribe to live updates and pull the initial state. Command set and
// order mirror a known-working Node-RED v4 client (WA9WUD).
_ = c.send("antenna list")
_ = c.send("sub port all")
_ = c.send("port get 1")
_ = c.send("port get 2")
for _, cmd := range []string{"antenna list", "sub port all", "port get 1", "port get 2"} {
if err := c.send(cmd); err != nil {
applog.Printf("antgenius: init send %q failed: %v", cmd, err)
}
}
done := make(chan struct{})
go c.keepalive(conn, done)
@@ -198,14 +216,27 @@ func (c *Client) keepalive(conn net.Conn, done chan struct{}) {
func (c *Client) readLoop(conn net.Conn) error {
r := bufio.NewReader(conn)
var sb strings.Builder
start := time.Now()
lines, bytesRx := 0, 0
for {
_ = conn.SetReadDeadline(time.Now().Add(readIdleTimeout))
b, err := r.ReadByte()
if err != nil {
// Log where the link died: 0 lines after connect points at the device
// refusing/closing (e.g. a client-slot limit or source-IP rejection —
// common when reaching it remotely); an EOF after the banner/replies
// points at something later in the exchange.
applog.Printf("antgenius: read ended after %s — %d line(s), %d byte(s): %v",
time.Since(start).Round(time.Millisecond), lines, bytesRx, err)
return err
}
bytesRx++
if b == '\r' || b == '\n' {
if sb.Len() > 0 {
lines++
if lines <= 10 {
applog.Printf("antgenius: rx[%d] %q", lines, sb.String())
}
c.handleLine(sb.String())
sb.Reset()
}
+4 -4
View File
@@ -3,7 +3,7 @@ package antgenius
import "testing"
func TestHandleAntennaList(t *testing.T) {
c := New("x", 9007)
c := New("x", 9007, "")
// Names may contain spaces — must be captured up to " tx=".
c.handleLine("R3|0|antenna 1 name=UB VL2.3 tx=ffff rx=ffff inband=0000")
c.handleLine("R3|0|antenna 2 name=DX Commander tx=00ff rx=00ff inband=0000")
@@ -20,7 +20,7 @@ func TestHandleAntennaList(t *testing.T) {
}
func TestAntennaUnderscoreAndPlaceholder(t *testing.T) {
c := New("x", 9007)
c := New("x", 9007, "")
c.handleLine("R3|0|antenna 1 name=Hex_Beam tx=ffff rx=ffff inband=0000") // underscore → space
c.handleLine("R3|0|antenna 4 name=Antenna_4 tx=0000 rx=0000 inband=0000") // default → filtered
c.handleLine("R3|0|antenna 5 name=antenna 5 tx=0000 rx=0000 inband=0000") // default → filtered
@@ -31,7 +31,7 @@ func TestAntennaUnderscoreAndPlaceholder(t *testing.T) {
}
func TestHandlePortStatus(t *testing.T) {
c := New("x", 9007)
c := New("x", 9007, "")
// Async push after "sub port all": active antenna is txant (fallback rxant).
c.handleLine("S0|port 1 source=MANUAL band=6 rxant=2 txant=2 inband=0 inhibit=0")
c.handleLine("S0|port 2 source=AUTO band=0 rxant=0 txant=0 inband=0 inhibit=0")
@@ -50,7 +50,7 @@ func TestHandlePortStatus(t *testing.T) {
}
func TestPortTxFallbackToRx(t *testing.T) {
c := New("x", 9007)
c := New("x", 9007, "")
c.handleLine("S0|port 1 source=MANUAL band=6 rxant=3 txant=0 inband=0 inhibit=0")
if st := c.GetStatus(); st.PortA != 3 {
t.Errorf("PortA = %d, want 3 (rx fallback when tx=0)", st.PortA)