refactor(cat): one DTR/RTS setting per backend, none shared

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.
This commit is contained in:
2026-08-01 20:35:57 +02:00
parent acf73c73c7
commit 296a4a55c0
3 changed files with 66 additions and 54 deletions
+21 -11
View File
@@ -120,7 +120,12 @@ const (
keyCATKenwoodHost = "cat.kenwood.host" // Kenwood CAT over a network serial bridge (ser2net), "host:port" 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) 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) 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) // 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) keyCATIcomPort = "cat.icom.port" // Icom USB CI-V serial port (e.g. COM5)
keyCATIcomBaud = "cat.icom.baud" // Icom CI-V baud (default 115200) keyCATIcomBaud = "cat.icom.baud" // Icom CI-V baud (default 115200)
keyCATIcomAddr = "cat.icom.addr" // Icom CI-V address, decimal (IC-7610 = 152 / 0x98) keyCATIcomAddr = "cat.icom.addr" // Icom CI-V address, decimal (IC-7610 = 152 / 0x98)
@@ -379,10 +384,11 @@ type CATSettings struct {
// adapter). NOT the radios own RJ45, which speaks Kenwoods KNS/ARCP. // adapter). NOT the radios own RJ45, which speaks Kenwoods KNS/ARCP.
KenwoodPort string `json:"kenwood_port"` // Kenwood CAT serial port (TS-590/890/2000, Elecraft) 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) 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 // interfaces that read either line as PTT. Off by default: lowering them
// stops some USB-serial interfaces transmitting at all. // stops some USB-serial interfaces transmitting at all.
LowerLines bool `json:"lower_lines"` 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) 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) IcomBaud int `json:"icom_baud"` // Icom CI-V baud (default 115200)
IcomAddr int `json:"icom_addr"` // Icom CI-V address, decimal (IC-7610 = 152) IcomAddr int `json:"icom_addr"` // Icom CI-V address, decimal (IC-7610 = 152)
@@ -6824,7 +6830,7 @@ func (a *App) GetCATSettings() (CATSettings, error) {
if a.settings == nil { if a.settings == nil {
return CATSettings{Backend: "omnirig", OmniRigNum: 1, PollMs: 250}, fmt.Errorf("db not initialized") 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 { if err != nil {
return CATSettings{}, err return CATSettings{}, err
} }
@@ -6845,7 +6851,8 @@ func (a *App) GetCATSettings() (CATSettings, error) {
KenwoodPort: m[keyCATKenwoodPort], KenwoodPort: m[keyCATKenwoodPort],
KenwoodHost: m[keyCATKenwoodHost], KenwoodHost: m[keyCATKenwoodHost],
KenwoodBaud: 9600, KenwoodBaud: 9600,
LowerLines: m[keyCATLowerLines] == "1", YaesuLowLines: m[keyCATYaesuLowLines] == "1",
KenwoodLowLines: m[keyCATKenwoodLowLines] == "1",
IcomPort: m[keyCATIcomPort], IcomPort: m[keyCATIcomPort],
IcomBaud: 115200, IcomBaud: 115200,
IcomAddr: 0x98, // IC-7610 default IcomAddr: 0x98, // IC-7610 default
@@ -6981,9 +6988,11 @@ func (a *App) SaveCATSettings(s CATSettings) error {
if s.DigitalDefault == "" { if s.DigitalDefault == "" {
s.DigitalDefault = "FT8" s.DigitalDefault = "FT8"
} }
lowerLines := "0" b01 := func(v bool) string {
if s.LowerLines { if v {
lowerLines = "1" return "1"
}
return "0"
} }
shareEnabled := "0" shareEnabled := "0"
if s.ShareEnabled { if s.ShareEnabled {
@@ -7008,7 +7017,8 @@ func (a *App) SaveCATSettings(s CATSettings) error {
keyCATKenwoodPort: strings.TrimSpace(s.KenwoodPort), keyCATKenwoodPort: strings.TrimSpace(s.KenwoodPort),
keyCATKenwoodHost: strings.TrimSpace(s.KenwoodHost), keyCATKenwoodHost: strings.TrimSpace(s.KenwoodHost),
keyCATKenwoodBaud: strconv.Itoa(s.KenwoodBaud), keyCATKenwoodBaud: strconv.Itoa(s.KenwoodBaud),
keyCATLowerLines: lowerLines, keyCATYaesuLowLines: b01(s.YaesuLowLines),
keyCATKenwoodLowLines: b01(s.KenwoodLowLines),
keyCATIcomPort: strings.TrimSpace(s.IcomPort), keyCATIcomPort: strings.TrimSpace(s.IcomPort),
keyCATIcomBaud: strconv.Itoa(s.IcomBaud), keyCATIcomBaud: strconv.Itoa(s.IcomBaud),
keyCATIcomAddr: strconv.Itoa(s.IcomAddr), 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 // (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. // and B on another); talking to the radio directly removes it.
yz := cat.NewYaesu(s.YaesuPort, s.YaesuBaud, s.DigitalDefault) yz := cat.NewYaesu(s.YaesuPort, s.YaesuBaud, s.DigitalDefault)
yz.SetLowerLines(s.LowerLines) yz.SetLowerLines(s.YaesuLowLines)
a.cat.Start(yz) a.cat.Start(yz)
case "kenwood": case "kenwood":
// Native Kenwood CAT — TS-590/890/990/2000 and everything that speaks the // 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)) a.cat.Start(cat.NewKenwoodTCP(h, s.DigitalDefault))
} else { } else {
kw := cat.NewKenwood(s.KenwoodPort, s.KenwoodBaud, s.DigitalDefault) kw := cat.NewKenwood(s.KenwoodPort, s.KenwoodBaud, s.DigitalDefault)
kw.SetLowerLines(s.LowerLines) kw.SetLowerLines(s.KenwoodLowLines)
a.cat.Start(kw) a.cat.Start(kw)
} }
case "icom": case "icom":
+3 -3
View File
@@ -1136,7 +1136,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
const [modeDraft, setModeDraft] = useState(''); const [modeDraft, setModeDraft] = useState('');
const [catCfg, setCatCfg] = useState<CATSettings>({ const [catCfg, setCatCfg] = useState<CATSettings>({
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, 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, 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, tci_host: '', tci_port: 40001, tci_spots: false, poll_ms: 250, delay_ms: 0,
digital_default: 'FT8', share_enabled: false, share_port: 4532, digital_default: 'FT8', share_enabled: false, share_port: 4532,
@@ -2582,7 +2582,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="flex items-center gap-2 text-xs cursor-pointer"> <label className="flex items-center gap-2 text-xs cursor-pointer">
<Checkbox checked={!!catCfg.lower_lines} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, lower_lines: !!c }))} /> <Checkbox checked={!!catCfg.yaesu_low_lines} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, yaesu_low_lines: !!c }))} />
{t('cat.lowerLines')} {t('cat.lowerLines')}
</label> </label>
<span className="text-xs text-muted-foreground">{t('cat.lowerLinesHint')}</span> <span className="text-xs text-muted-foreground">{t('cat.lowerLinesHint')}</span>
@@ -2636,7 +2636,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="flex items-center gap-2 text-xs cursor-pointer"> <label className="flex items-center gap-2 text-xs cursor-pointer">
<Checkbox checked={!!catCfg.lower_lines} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, lower_lines: !!c }))} /> <Checkbox checked={!!catCfg.kenwood_low_lines} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, kenwood_low_lines: !!c }))} />
{t('cat.lowerLines')} {t('cat.lowerLines')}
</label> </label>
<span className="text-xs text-muted-foreground">{t('cat.lowerLinesHint')}</span> <span className="text-xs text-muted-foreground">{t('cat.lowerLinesHint')}</span>
+4 -2
View File
@@ -1924,7 +1924,8 @@ export namespace main {
kenwood_host: string; kenwood_host: string;
kenwood_port: string; kenwood_port: string;
kenwood_baud: number; kenwood_baud: number;
lower_lines: boolean; yaesu_low_lines: boolean;
kenwood_low_lines: boolean;
icom_port: string; icom_port: string;
icom_baud: number; icom_baud: number;
icom_addr: number; icom_addr: number;
@@ -1965,7 +1966,8 @@ export namespace main {
this.kenwood_host = source["kenwood_host"]; this.kenwood_host = source["kenwood_host"];
this.kenwood_port = source["kenwood_port"]; this.kenwood_port = source["kenwood_port"];
this.kenwood_baud = source["kenwood_baud"]; 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_port = source["icom_port"];
this.icom_baud = source["icom_baud"]; this.icom_baud = source["icom_baud"];
this.icom_addr = source["icom_addr"]; this.icom_addr = source["icom_addr"];