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
+45 -13
View File
@@ -12,18 +12,38 @@ import (
"go.bug.st/serial"
)
// IcomSerial controls an Icom transceiver over its USB/serial CI-V port (local
// control). It speaks the shared civ protocol, so when the network backend
// (icomnet) is added it will reuse the same encode/decode — only the transport
// changes. Implements Backend; all methods run on the Manager's CAT goroutine,
// so the port is accessed single-threaded (no locking needed).
// civTransport is everything IcomSerial needs from its underlying link. It's
// satisfied directly by a go.bug.st serial.Port (USB, local control) and, for
// remote control, by the icomnet UDP stream which presents the tunnelled CI-V
// byte stream as plain Read/Write (there SetDTR/SetRTS/SetReadTimeout are
// no-ops). Abstracting it here means the whole IcomController surface — DSP,
// scope, RIT, CW — is reused unchanged over the network; only `open` differs.
type civTransport interface {
Read(p []byte) (int, error)
Write(p []byte) (int, error)
Close() error
SetReadTimeout(time.Duration) error
SetDTR(bool) error
SetRTS(bool) error
}
// IcomSerial controls an Icom transceiver over the shared civ protocol. The
// transport is pluggable via `open`: NewIcomSerial opens a USB/serial port;
// NewIcomNet (later) returns one configured with a network transport. Implements
// Backend; all methods run on the Manager's CAT goroutine, so the port is
// accessed single-threaded (no locking needed).
type IcomSerial struct {
portName string
baud int
rigAddr byte // rig's CI-V address (IC-7610 default 0x98)
digital string // mode to command for DATA (FT8/RTTY/…)
port serial.Port
// open dials the underlying link (serial port or network stream). Set by the
// constructor; called by Connect. Blocks until connected (the network opener
// performs the full UDP handshake + login before returning).
open func() (civTransport, error)
port civTransport
model string
// I/O routing. A single reader goroutine owns port.Read and dispatches every
@@ -82,7 +102,7 @@ func NewIcomSerial(portName string, baud, civAddr int, digitalDefault string) *I
if digitalDefault == "" {
digitalDefault = "FT8"
}
return &IcomSerial{
b := &IcomSerial{
portName: portName,
baud: baud,
rigAddr: byte(civAddr),
@@ -90,20 +110,32 @@ func NewIcomSerial(portName string, baud, civAddr int, digitalDefault string) *I
model: "Icom",
scopeFixed: true, // rigs default to a fixed-span scope
}
// USB/serial transport opener.
b.open = func() (civTransport, error) {
if b.portName == "" {
return nil, fmt.Errorf("no serial port configured")
}
p, err := serial.Open(b.portName, &serial.Mode{BaudRate: b.baud})
if err != nil {
return nil, fmt.Errorf("open %s @ %d baud: %w", b.portName, b.baud, err)
}
return p, nil
}
return b
}
func (b *IcomSerial) Name() string { return "icom" }
func (b *IcomSerial) Connect() error {
if b.portName == "" {
return fmt.Errorf("no serial port configured")
if b.open == nil {
return fmt.Errorf("no transport configured")
}
port, err := serial.Open(b.portName, &serial.Mode{BaudRate: b.baud})
port, err := b.open()
if err != nil {
return fmt.Errorf("open %s @ %d baud: %w", b.portName, b.baud, err)
return err
}
// Short read timeout so recv() polls in a tight loop without blocking the
// CAT goroutine when the rig is silent.
// CAT goroutine when the rig is silent. (No-op on the network transport.)
_ = port.SetReadTimeout(60 * time.Millisecond)
// Deassert DTR/RTS. Icom USB rigs (IC-7610, IC-7300…) let "USB SEND" and
// "USB Keying (CW)" be mapped to the RTS or DTR line: if the port opens with
@@ -312,7 +344,7 @@ func (b *IcomSerial) recv(timeout time.Duration, match func(civ.Decoded) bool) (
// frames and routes each: our own echoes are dropped, spectrum-scope frames
// (0x27) go to specCh, everything else (control replies, acks, unsolicited
// transceive updates) goes to respCh. It exits when the port is closed.
func (b *IcomSerial) reader(port serial.Port, done chan struct{}) {
func (b *IcomSerial) reader(port civTransport, done chan struct{}) {
defer close(done)
tmp := make([]byte, 512)
var rx []byte