feat(cat): three transports for Kenwood/Elecraft, chosen not inferred
USB, RS-232-to-Ethernet, and the radio's own network protocol are three different things, so they are three entries in one dropdown — and the transport is now a stored setting rather than something deduced from which field happened to be filled in. The old rule (a host wins whenever it is not empty) is invisible from the settings page: someone who typed a host months ago and later set a COM port had a radio that never answered and nothing on screen to say why. The third entry is named and refused, with the reason. It is a session with its own framing and login, not the CAT byte stream over a socket, so it has to be written per radio and against one — and a K4 owner reading this list should be told that, not left wondering whether the empty address field was the problem. The upgrade reads an older install from what it has: a configured host means the bridge, which is what the previous code used it for. Pinned by a test, including the case that matters after the fact — choosing USB with a stale host still stored gives the COM port.
This commit is contained in:
@@ -132,6 +132,13 @@ const (
|
||||
keyCATYaesuPort = "cat.yaesu.port" // Yaesu CAT serial port (e.g. COM4)
|
||||
keyCATYaesuBaud = "cat.yaesu.baud" // Yaesu CAT baud (FTDX10/101 default 38400)
|
||||
keyCATKenwoodHost = "cat.kenwood.host" // Kenwood CAT over a network serial bridge (ser2net), "host:port"
|
||||
// keyCATKenwoodLink says HOW the radio is reached: "usb" (a COM port),
|
||||
// "bridge" (the same CAT bytes over TCP — ser2net, an Ethernet-serial
|
||||
// adapter), or "native" (the radio's own network protocol). Kept apart from
|
||||
// the address because the three are different transports, and inferring the
|
||||
// choice from whether a field happened to be filled in is how an operator
|
||||
// ends up with a host typed in and a radio that never answers.
|
||||
keyCATKenwoodLink = "cat.kenwood.link"
|
||||
keyCATKenwoodPort = "cat.kenwood.port" // Kenwood CAT serial port (TS-590/890/2000, Elecraft)
|
||||
keyCATKenwoodBaud = "cat.kenwood.baud" // Kenwood CAT baud (TS-590 default 9600, TS-890 115200)
|
||||
// One key PER BACKEND, deliberately not shared. A Xiegu fix that reached
|
||||
@@ -457,6 +464,9 @@ type CATSettings struct {
|
||||
KenwoodHost string `json:"kenwood_host"` // "host:port" of a serial-over-network bridge (ser2net, Ethernet-serial
|
||||
// adapter). NOT the radio’s own RJ45, which speaks Kenwood’s KNS/ARCP.
|
||||
KenwoodPort string `json:"kenwood_port"` // Kenwood CAT serial port (TS-590/890/2000, Elecraft)
|
||||
// KenwoodLink: "usb" | "bridge" | "native". Empty means usb, unless a host
|
||||
// was already configured by an older build — see GetCATSettings.
|
||||
KenwoodLink string `json:"kenwood_link"`
|
||||
KenwoodBaud int `json:"kenwood_baud"` // Kenwood CAT baud (TS-590 default 9600)
|
||||
// What a DATA/digital mode (FT8, PSK…) sets on the rig: "usb" (default), "data"
|
||||
// (MD6 — Elecraft K3/K4 DATA mode) or "keep" (leave the rig's mode untouched,
|
||||
@@ -8044,7 +8054,7 @@ func (a *App) GetCATSettings() (CATSettings, error) {
|
||||
if a.settings == nil {
|
||||
return CATSettings{Backend: "omnirig", OmniRigNum: 1, PollMs: 250}, fmt.Errorf("db not initialized")
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx, keyCATEnabled, keyCATBackend, keyCATOmniRigNum, keyCATOmniRigVFO, keyCATFlexHost, keyCATFlexPort, keyCATFlexSpots, keyCATFlexDVKDax, keyCATFlexDecodeSpots, keyCATFlexDecodeSecs, keyCATXieguPort, keyCATXieguBaud, keyCATXieguAddr, keyCATXieguPTTLine, keyCATYaesuPort, keyCATYaesuBaud, keyCATKenwoodPort, keyCATKenwoodBaud, keyCATKenwoodHost, keyCATYaesuLowLines, keyCATKenwoodLowLines, keyCATKenwoodDataMode, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPttHotkeyEnabled, keyCATPttHotkey, keyCATPttHotkeyToggle, keyCATPollMs, keyCATDelayMs, keyCATOffsetOn, keyCATOffsetHz, keyCATDigitalDefault, keyCATShareEnabled, keyCATSharePort, keyCATShareProto, keyCATShareTCIPort)
|
||||
m, err := a.settings.GetMany(a.ctx, keyCATEnabled, keyCATBackend, keyCATOmniRigNum, keyCATOmniRigVFO, keyCATFlexHost, keyCATFlexPort, keyCATFlexSpots, keyCATFlexDVKDax, keyCATFlexDecodeSpots, keyCATFlexDecodeSecs, keyCATXieguPort, keyCATXieguBaud, keyCATXieguAddr, keyCATXieguPTTLine, keyCATYaesuPort, keyCATYaesuBaud, keyCATKenwoodPort, keyCATKenwoodBaud, keyCATKenwoodHost, keyCATKenwoodLink, keyCATYaesuLowLines, keyCATKenwoodLowLines, keyCATKenwoodDataMode, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPttHotkeyEnabled, keyCATPttHotkey, keyCATPttHotkeyToggle, keyCATPollMs, keyCATDelayMs, keyCATOffsetOn, keyCATOffsetHz, keyCATDigitalDefault, keyCATShareEnabled, keyCATSharePort, keyCATShareProto, keyCATShareTCIPort)
|
||||
if err != nil {
|
||||
return CATSettings{}, err
|
||||
}
|
||||
@@ -8065,6 +8075,9 @@ func (a *App) GetCATSettings() (CATSettings, error) {
|
||||
YaesuBaud: 38400,
|
||||
KenwoodPort: m[keyCATKenwoodPort],
|
||||
KenwoodHost: m[keyCATKenwoodHost],
|
||||
// An install that predates the setting is read from what it has: a host
|
||||
// filled in meant the bridge, since that is what the old code preferred.
|
||||
KenwoodLink: kenwoodLinkOr(m[keyCATKenwoodLink], m[keyCATKenwoodHost]),
|
||||
KenwoodBaud: 9600,
|
||||
YaesuLowLines: m[keyCATYaesuLowLines] == "1",
|
||||
KenwoodLowLines: m[keyCATKenwoodLowLines] == "1",
|
||||
@@ -8264,6 +8277,7 @@ func (a *App) SaveCATSettings(s CATSettings) error {
|
||||
keyCATYaesuBaud: strconv.Itoa(s.YaesuBaud),
|
||||
keyCATKenwoodPort: strings.TrimSpace(s.KenwoodPort),
|
||||
keyCATKenwoodHost: strings.TrimSpace(s.KenwoodHost),
|
||||
keyCATKenwoodLink: kenwoodLinkOr(s.KenwoodLink, s.KenwoodHost),
|
||||
keyCATKenwoodBaud: strconv.Itoa(s.KenwoodBaud),
|
||||
keyCATYaesuLowLines: b01(s.YaesuLowLines),
|
||||
keyCATKenwoodLowLines: b01(s.KenwoodLowLines),
|
||||
@@ -15199,37 +15213,38 @@ func (a *App) reloadCAT() {
|
||||
yz := cat.NewYaesu(s.YaesuPort, s.YaesuBaud, s.DigitalDefault)
|
||||
yz.SetLowerLines(s.YaesuLowLines)
|
||||
a.cat.Start(yz)
|
||||
case "kenwood":
|
||||
case "kenwood", "elecraft":
|
||||
// Native Kenwood CAT — TS-590/890/990/2000 and everything that speaks the
|
||||
// same dialect (Elecraft K3/K4, and the "Kenwood" setting on other rigs).
|
||||
// One IF; frame carries frequency, mode, VFO and split, so the poll costs a
|
||||
// single round trip where OmniRig needed a rig file to describe each one.
|
||||
// A network address wins over the COM port when both are filled: it is the
|
||||
// more deliberate setting, and silently preferring the wire would leave an
|
||||
// operator staring at a host they typed and a radio that never answers.
|
||||
if h := strings.TrimSpace(s.KenwoodHost); h != "" {
|
||||
kw := cat.NewKenwoodTCP(h, s.DigitalDefault)
|
||||
// same dialect, the Elecraft K3/K4 included: the K3 emulates the Kenwood
|
||||
// command set, so one transport serves both and the Elecraft flag only
|
||||
// turns on its specifics (digital modes → DATA A via MD6+DT0).
|
||||
elecraft := s.Backend == "elecraft"
|
||||
switch kenwoodLinkOr(s.KenwoodLink, s.KenwoodHost) {
|
||||
case kenwoodLinkNative:
|
||||
// The radio's OWN network protocol — a session, its own framing, its
|
||||
// own authentication. Nothing here speaks it: it is not the CAT byte
|
||||
// stream with a socket in front, and pretending otherwise would open a
|
||||
// connection that answers nothing and blame the radio for it.
|
||||
applog.Printf("cat: %s over the radio's own network protocol is not implemented — use USB, or an RS-232-to-Ethernet bridge", s.Backend)
|
||||
a.cat.Stop()
|
||||
// Said on screen as well as in the log: a CAT panel that simply
|
||||
// stays disconnected sends the operator hunting a cable.
|
||||
if a.ctx != nil {
|
||||
wruntime.EventsEmit(a.ctx, "cat:state", cat.RigState{
|
||||
Error: "this radio's own network protocol is not supported yet — use USB, or an RS-232-to-Ethernet bridge",
|
||||
})
|
||||
}
|
||||
return
|
||||
case kenwoodLinkBridge:
|
||||
kw := cat.NewKenwoodTCP(strings.TrimSpace(s.KenwoodHost), s.DigitalDefault)
|
||||
kw.SetDataMode(s.KenwoodDataMode)
|
||||
kw.SetElecraft(elecraft)
|
||||
a.cat.Start(kw)
|
||||
} else {
|
||||
default:
|
||||
kw := cat.NewKenwood(s.KenwoodPort, s.KenwoodBaud, s.DigitalDefault)
|
||||
kw.SetLowerLines(s.KenwoodLowLines)
|
||||
kw.SetDataMode(s.KenwoodDataMode)
|
||||
a.cat.Start(kw)
|
||||
}
|
||||
case "elecraft":
|
||||
// Elecraft K3/K4: the Kenwood-dialect client with the Elecraft specifics on
|
||||
// (digital modes → DATA A via MD6+DT0). Reuses the Kenwood port/baud/host
|
||||
// settings — the K3 emulates the Kenwood command set, so a separate transport
|
||||
// would be a near-total duplicate.
|
||||
if h := strings.TrimSpace(s.KenwoodHost); h != "" {
|
||||
kw := cat.NewKenwoodTCP(h, s.DigitalDefault)
|
||||
kw.SetElecraft(true)
|
||||
a.cat.Start(kw)
|
||||
} else {
|
||||
kw := cat.NewKenwood(s.KenwoodPort, s.KenwoodBaud, s.DigitalDefault)
|
||||
kw.SetLowerLines(s.KenwoodLowLines)
|
||||
kw.SetElecraft(true)
|
||||
kw.SetElecraft(elecraft)
|
||||
a.cat.Start(kw)
|
||||
}
|
||||
case "icom":
|
||||
|
||||
Reference in New Issue
Block a user