From 296a4a55c049b0d9665780c6ec46ebf88df53fa9 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 1 Aug 2026 20:35:57 +0200 Subject: [PATCH] refactor(cat): one DTR/RTS setting per backend, none shared MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0.22.7/0.22.8 breakage came from a Xiegu-reported fault being fixed inside the Yaesu and Kenwood backends. The lesson is structural: nothing a backend does may be steered by another backend's report or another backend's setting. The lower-lines checkbox introduced for 0.22.9 was still one shared key applied to both Yaesu and Kenwood — the same reflex in miniature. It is now cat.yaesu.low_dtr_rts and cat.kenwood.low_dtr_rts, each checkbox in its backend's own settings block, each wired only to its backend. The shared key never shipped, so nothing needs migrating. --- app.go | 108 ++++++++++++---------- frontend/src/components/SettingsModal.tsx | 6 +- frontend/wailsjs/go/models.ts | 6 +- 3 files changed, 66 insertions(+), 54 deletions(-) diff --git a/app.go b/app.go index c1924dc..b6fe4d8 100644 --- a/app.go +++ b/app.go @@ -107,30 +107,35 @@ const ( keyCATFlexDecodeSpots = "cat.flex.decode_spots" // push WSJT-X decodes (heard stations) to the panadapter keyCATFlexDecodeSecs = "cat.flex.decode_secs" // decode spot display duration (seconds) before auto-removal keyCATPollMs = "cat.poll_ms" - keyCATDelayMs = "cat.delay_ms" // pause between commands - keyCATDigitalDefault = "cat.digital_default" // mode to use when CAT reports DATA - keyCATShareEnabled = "cat.share.enabled" // expose CAT to other programs (Hamlib NET rigctl) - keyCATSharePort = "cat.share.port" // TCP port for that server (rigctld default 4532) - keyCATXieguPort = "cat.xiegu.port" // Xiegu CI-V serial port (G90/X6100…) - keyCATXieguBaud = "cat.xiegu.baud" // Xiegu CI-V baud (G90 default 19200) - keyCATXieguAddr = "cat.xiegu.addr" // Xiegu CI-V address (factory 0x70) - keyCATXieguPTTLine = "cat.xiegu.ptt_line" // "" | rts | dtr — G90 does not key over CI-V - 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" - 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) - keyCATLowerLines = "cat.serial.low_dtr_rts" // deassert DTR/RTS on connect (interfaces that read them as PTT) - keyCATIcomPort = "cat.icom.port" // Icom USB CI-V serial port (e.g. COM5) - keyCATIcomBaud = "cat.icom.baud" // Icom CI-V baud (default 115200) - keyCATIcomAddr = "cat.icom.addr" // Icom CI-V address, decimal (IC-7610 = 152 / 0x98) - keyCATIcomNetHost = "cat.icom.net.host" // Icom network remote: rig IP/hostname - keyCATIcomNetUser = "cat.icom.net.user" // Icom network: Network User1 ID - keyCATIcomNetPass = "cat.icom.net.pass" // Icom network: Network User1 password - keyCATIcomNetAudio = "cat.icom.net.audio" // Icom network: stream RX audio on 50003 (experimental) - keyCATTCIHost = "cat.tci.host" // TCI host (Expert Electronics SunSDR / ExpertSDR2) - keyCATTCIPort = "cat.tci.port" // TCI WebSocket port (default 40001) - keyCATTCISpots = "cat.tci.spots" // push cluster spots to the TCI panorama + keyCATDelayMs = "cat.delay_ms" // pause between commands + keyCATDigitalDefault = "cat.digital_default" // mode to use when CAT reports DATA + keyCATShareEnabled = "cat.share.enabled" // expose CAT to other programs (Hamlib NET rigctl) + keyCATSharePort = "cat.share.port" // TCP port for that server (rigctld default 4532) + keyCATXieguPort = "cat.xiegu.port" // Xiegu CI-V serial port (G90/X6100…) + keyCATXieguBaud = "cat.xiegu.baud" // Xiegu CI-V baud (G90 default 19200) + keyCATXieguAddr = "cat.xiegu.addr" // Xiegu CI-V address (factory 0x70) + keyCATXieguPTTLine = "cat.xiegu.ptt_line" // "" | rts | dtr — G90 does not key over CI-V + 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" + 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 + // into the Yaesu and Kenwood backends is what broke them in 0.22.7/0.22.8; + // the rule since is that nothing a backend does may be steered by another + // backend's setting. + keyCATYaesuLowLines = "cat.yaesu.low_dtr_rts" // deassert DTR/RTS on connect + keyCATKenwoodLowLines = "cat.kenwood.low_dtr_rts" // deassert DTR/RTS on connect + keyCATIcomPort = "cat.icom.port" // Icom USB CI-V serial port (e.g. COM5) + keyCATIcomBaud = "cat.icom.baud" // Icom CI-V baud (default 115200) + keyCATIcomAddr = "cat.icom.addr" // Icom CI-V address, decimal (IC-7610 = 152 / 0x98) + keyCATIcomNetHost = "cat.icom.net.host" // Icom network remote: rig IP/hostname + keyCATIcomNetUser = "cat.icom.net.user" // Icom network: Network User1 ID + keyCATIcomNetPass = "cat.icom.net.pass" // Icom network: Network User1 password + keyCATIcomNetAudio = "cat.icom.net.audio" // Icom network: stream RX audio on 50003 (experimental) + keyCATTCIHost = "cat.tci.host" // TCI host (Expert Electronics SunSDR / ExpertSDR2) + keyCATTCIPort = "cat.tci.port" // TCI WebSocket port (default 40001) + keyCATTCISpots = "cat.tci.spots" // push cluster spots to the TCI panorama // Audio (Digital Voice Keyer + QSO recorder). Machine-local hardware, so // global (not per-profile) like CAT/rotator. Device fields store the @@ -379,25 +384,26 @@ type CATSettings struct { // 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) KenwoodBaud int `json:"kenwood_baud"` // Kenwood CAT baud (TS-590 default 9600) - // LowerLines deasserts DTR and RTS after opening a serial CAT port, for + // Per-backend: deassert DTR and RTS after opening the CAT port, for // interfaces that read either line as PTT. Off by default: lowering them // stops some USB-serial interfaces transmitting at all. - LowerLines bool `json:"lower_lines"` - IcomPort string `json:"icom_port"` // Icom USB CI-V serial port (e.g. COM5) - IcomBaud int `json:"icom_baud"` // Icom CI-V baud (default 115200) - IcomAddr int `json:"icom_addr"` // Icom CI-V address, decimal (IC-7610 = 152) - IcomNetHost string `json:"icom_net_host"` // Icom network remote: rig IP/hostname (built-in LAN server) - IcomNetUser string `json:"icom_net_user"` // Icom network Network User1 ID - IcomNetPass string `json:"icom_net_pass"` // Icom network Network User1 password - IcomNetAudio bool `json:"icom_net_audio"` // Icom network: stream RX audio (50003) — experimental, needs on-rig verification - TCIHost string `json:"tci_host"` // TCI host (Expert Electronics SunSDR) - TCIPort int `json:"tci_port"` // TCI WebSocket port (default 40001) - TCISpots bool `json:"tci_spots"` // push cluster spots to the TCI panorama - PollMs int `json:"poll_ms"` // poll interval in ms (default 250) - DelayMs int `json:"delay_ms"` // pause between commands (default 0) - DigitalDefault string `json:"digital_default"` // when CAT says DATA, surface this mode (FT8/FT4/RTTY/…) - ShareEnabled bool `json:"share_enabled"` // serve CAT to other programs (Hamlib NET rigctl) - SharePort int `json:"share_port"` // TCP port for it (default 4532) + YaesuLowLines bool `json:"yaesu_low_lines"` + KenwoodLowLines bool `json:"kenwood_low_lines"` + IcomPort string `json:"icom_port"` // Icom USB CI-V serial port (e.g. COM5) + IcomBaud int `json:"icom_baud"` // Icom CI-V baud (default 115200) + IcomAddr int `json:"icom_addr"` // Icom CI-V address, decimal (IC-7610 = 152) + IcomNetHost string `json:"icom_net_host"` // Icom network remote: rig IP/hostname (built-in LAN server) + IcomNetUser string `json:"icom_net_user"` // Icom network Network User1 ID + IcomNetPass string `json:"icom_net_pass"` // Icom network Network User1 password + IcomNetAudio bool `json:"icom_net_audio"` // Icom network: stream RX audio (50003) — experimental, needs on-rig verification + TCIHost string `json:"tci_host"` // TCI host (Expert Electronics SunSDR) + TCIPort int `json:"tci_port"` // TCI WebSocket port (default 40001) + TCISpots bool `json:"tci_spots"` // push cluster spots to the TCI panorama + PollMs int `json:"poll_ms"` // poll interval in ms (default 250) + DelayMs int `json:"delay_ms"` // pause between commands (default 0) + DigitalDefault string `json:"digital_default"` // when CAT says DATA, surface this mode (FT8/FT4/RTTY/…) + ShareEnabled bool `json:"share_enabled"` // serve CAT to other programs (Hamlib NET rigctl) + SharePort int `json:"share_port"` // TCP port for it (default 4532) } // ModePreset is a mode entry with default RST values to auto-populate @@ -6824,7 +6830,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, keyCATFlexDecodeSpots, keyCATFlexDecodeSecs, keyCATXieguPort, keyCATXieguBaud, keyCATXieguAddr, keyCATXieguPTTLine, keyCATYaesuPort, keyCATYaesuBaud, keyCATKenwoodPort, keyCATKenwoodBaud, keyCATKenwoodHost, keyCATLowerLines, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPollMs, keyCATDelayMs, keyCATDigitalDefault, keyCATShareEnabled, keyCATSharePort) + m, err := a.settings.GetMany(a.ctx, keyCATEnabled, keyCATBackend, keyCATOmniRigNum, keyCATOmniRigVFO, keyCATFlexHost, keyCATFlexPort, keyCATFlexSpots, keyCATFlexDecodeSpots, keyCATFlexDecodeSecs, keyCATXieguPort, keyCATXieguBaud, keyCATXieguAddr, keyCATXieguPTTLine, keyCATYaesuPort, keyCATYaesuBaud, keyCATKenwoodPort, keyCATKenwoodBaud, keyCATKenwoodHost, keyCATYaesuLowLines, keyCATKenwoodLowLines, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPollMs, keyCATDelayMs, keyCATDigitalDefault, keyCATShareEnabled, keyCATSharePort) if err != nil { return CATSettings{}, err } @@ -6845,7 +6851,8 @@ func (a *App) GetCATSettings() (CATSettings, error) { KenwoodPort: m[keyCATKenwoodPort], KenwoodHost: m[keyCATKenwoodHost], KenwoodBaud: 9600, - LowerLines: m[keyCATLowerLines] == "1", + YaesuLowLines: m[keyCATYaesuLowLines] == "1", + KenwoodLowLines: m[keyCATKenwoodLowLines] == "1", IcomPort: m[keyCATIcomPort], IcomBaud: 115200, IcomAddr: 0x98, // IC-7610 default @@ -6981,9 +6988,11 @@ func (a *App) SaveCATSettings(s CATSettings) error { if s.DigitalDefault == "" { s.DigitalDefault = "FT8" } - lowerLines := "0" - if s.LowerLines { - lowerLines = "1" + b01 := func(v bool) string { + if v { + return "1" + } + return "0" } shareEnabled := "0" if s.ShareEnabled { @@ -7008,7 +7017,8 @@ func (a *App) SaveCATSettings(s CATSettings) error { keyCATKenwoodPort: strings.TrimSpace(s.KenwoodPort), keyCATKenwoodHost: strings.TrimSpace(s.KenwoodHost), keyCATKenwoodBaud: strconv.Itoa(s.KenwoodBaud), - keyCATLowerLines: lowerLines, + keyCATYaesuLowLines: b01(s.YaesuLowLines), + keyCATKenwoodLowLines: b01(s.KenwoodLowLines), keyCATIcomPort: strings.TrimSpace(s.IcomPort), keyCATIcomBaud: strconv.Itoa(s.IcomBaud), keyCATIcomAddr: strconv.Itoa(s.IcomAddr), @@ -12839,7 +12849,7 @@ func (a *App) reloadCAT() { // (a rig file that hides the VFO, a Freq property meaning A on one model // and B on another); talking to the radio directly removes it. yz := cat.NewYaesu(s.YaesuPort, s.YaesuBaud, s.DigitalDefault) - yz.SetLowerLines(s.LowerLines) + yz.SetLowerLines(s.YaesuLowLines) a.cat.Start(yz) case "kenwood": // Native Kenwood CAT — TS-590/890/990/2000 and everything that speaks the @@ -12853,7 +12863,7 @@ func (a *App) reloadCAT() { a.cat.Start(cat.NewKenwoodTCP(h, s.DigitalDefault)) } else { kw := cat.NewKenwood(s.KenwoodPort, s.KenwoodBaud, s.DigitalDefault) - kw.SetLowerLines(s.LowerLines) + kw.SetLowerLines(s.KenwoodLowLines) a.cat.Start(kw) } case "icom": diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 85f9aae..b8d0491 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -1136,7 +1136,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan const [modeDraft, setModeDraft] = useState(''); const [catCfg, setCatCfg] = useState({ enabled: false, backend: 'omnirig', omnirig_rig: 1, omnirig_vfo: '', flex_host: '', flex_port: 4992, flex_spots: false, flex_decode_spots: false, flex_decode_secs: 120, - yaesu_port: '', yaesu_baud: 38400, lower_lines: false, kenwood_port: '', kenwood_baud: 9600, kenwood_host: '', xiegu_port: '', xiegu_baud: 19200, xiegu_addr: 0x70, xiegu_ptt_line: '', + yaesu_port: '', yaesu_baud: 38400, yaesu_low_lines: false, kenwood_low_lines: false, kenwood_port: '', kenwood_baud: 9600, kenwood_host: '', xiegu_port: '', xiegu_baud: 19200, xiegu_addr: 0x70, xiegu_ptt_line: '', icom_port: '', icom_baud: 115200, icom_addr: 0x98, icom_net_host: '', icom_net_user: '', icom_net_pass: '', icom_net_audio: false, tci_host: '', tci_port: 40001, tci_spots: false, poll_ms: 250, delay_ms: 0, digital_default: 'FT8', share_enabled: false, share_port: 4532, @@ -2582,7 +2582,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
{t('cat.lowerLinesHint')} @@ -2636,7 +2636,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
{t('cat.lowerLinesHint')} diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 8887d2c..7eee750 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1924,7 +1924,8 @@ export namespace main { kenwood_host: string; kenwood_port: string; kenwood_baud: number; - lower_lines: boolean; + yaesu_low_lines: boolean; + kenwood_low_lines: boolean; icom_port: string; icom_baud: number; icom_addr: number; @@ -1965,7 +1966,8 @@ export namespace main { this.kenwood_host = source["kenwood_host"]; this.kenwood_port = source["kenwood_port"]; this.kenwood_baud = source["kenwood_baud"]; - this.lower_lines = source["lower_lines"]; + this.yaesu_low_lines = source["yaesu_low_lines"]; + this.kenwood_low_lines = source["kenwood_low_lines"]; this.icom_port = source["icom_port"]; this.icom_baud = source["icom_baud"]; this.icom_addr = source["icom_addr"];