feat: native Xiegu CAT backend (G90 / X6100 / X6200 / X5105)
Xiegu speaks CI-V with a reduced command set: frames, BCD encoding, addressing and the opcodes for frequency, mode, PTT and split are Icom's, so this reuses internal/cat/civ wholesale instead of re-deriving a codec. It is a SEPARATE backend rather than the Icom one at address 0x70, because what the two rigs do NOT share is the deciding part. The Icom backend reads the spectrum scope, the DSP block, data mode via 1A 06 and the model id via 19 — none of which a Xiegu implements. Pointed at a G90 it would poll every cycle for answers that never come, and spend its silence tolerance on commands the radio was never going to support. Two consequences of the rig's smaller mode table are handled explicitly rather than left to fail: there is no data mode, so a digital QSO is set to plain sideband (what the operator does on the radio anyway) instead of being refused; and the split TX frequency is NOT reported, because reading the unselected VFO needs 0x25, which the Xiegu table does not list — a split flag carrying a wrong TX frequency is worse than the flag alone, since the frequency is what gets logged. The published command table has rows that slipped during typesetting (0x07 and 0x0F share a block). Where it contradicts itself the Icom meaning is used, the rest of the table matching Icom exactly, and every unexpected reply is logged raw so a first on-air run settles it. NOT yet verified on a radio.
This commit is contained in:
@@ -111,6 +111,9 @@ const (
|
||||
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)
|
||||
keyCATYaesuPort = "cat.yaesu.port" // Yaesu CAT serial port (e.g. COM4)
|
||||
keyCATYaesuBaud = "cat.yaesu.baud" // Yaesu CAT baud (FTDX10/101 default 38400)
|
||||
keyCATIcomPort = "cat.icom.port" // Icom USB CI-V serial port (e.g. COM5)
|
||||
@@ -347,6 +350,9 @@ type CATSettings struct {
|
||||
FlexSpots bool `json:"flex_spots"` // push cluster spots to the panadapter
|
||||
FlexDecodeSpots bool `json:"flex_decode_spots"` // push WSJT-X decodes (heard stations) to the panadapter
|
||||
FlexDecodeSecs int `json:"flex_decode_secs"` // decode spot display duration (s) before removal (default 120)
|
||||
XieguPort string `json:"xiegu_port"` // Xiegu CI-V serial port (G90/X6100…)
|
||||
XieguBaud int `json:"xiegu_baud"` // Xiegu CI-V baud (G90 default 19200)
|
||||
XieguAddr int `json:"xiegu_addr"` // Xiegu CI-V address (factory 0x70)
|
||||
YaesuPort string `json:"yaesu_port"` // Yaesu CAT serial port (e.g. COM4)
|
||||
YaesuBaud int `json:"yaesu_baud"` // Yaesu CAT baud (FTDX10/101 default 38400)
|
||||
IcomPort string `json:"icom_port"` // Icom USB CI-V serial port (e.g. COM5)
|
||||
@@ -6582,7 +6588,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, keyCATYaesuPort, keyCATYaesuBaud, 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, keyCATYaesuPort, keyCATYaesuBaud, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPollMs, keyCATDelayMs, keyCATDigitalDefault, keyCATShareEnabled, keyCATSharePort)
|
||||
if err != nil {
|
||||
return CATSettings{}, err
|
||||
}
|
||||
@@ -6595,6 +6601,9 @@ func (a *App) GetCATSettings() (CATSettings, error) {
|
||||
FlexSpots: m[keyCATFlexSpots] == "1",
|
||||
FlexDecodeSpots: m[keyCATFlexDecodeSpots] == "1",
|
||||
FlexDecodeSecs: 120,
|
||||
XieguPort: m[keyCATXieguPort],
|
||||
XieguBaud: 19200,
|
||||
XieguAddr: cat.XieguDefaultAddr,
|
||||
YaesuPort: m[keyCATYaesuPort],
|
||||
YaesuBaud: 38400,
|
||||
IcomPort: m[keyCATIcomPort],
|
||||
@@ -6625,6 +6634,12 @@ func (a *App) GetCATSettings() (CATSettings, error) {
|
||||
if n, _ := strconv.Atoi(m[keyCATSharePort]); n > 0 && n <= 65535 {
|
||||
out.SharePort = n
|
||||
}
|
||||
if n, _ := strconv.Atoi(m[keyCATXieguBaud]); n > 0 {
|
||||
out.XieguBaud = n
|
||||
}
|
||||
if n, _ := strconv.Atoi(m[keyCATXieguAddr]); n > 0 && n <= 0xFF {
|
||||
out.XieguAddr = n
|
||||
}
|
||||
if n, _ := strconv.Atoi(m[keyCATYaesuBaud]); n > 0 {
|
||||
out.YaesuBaud = n
|
||||
}
|
||||
@@ -6672,6 +6687,12 @@ func (a *App) SaveCATSettings(s CATSettings) error {
|
||||
if s.SharePort <= 0 || s.SharePort > 65535 {
|
||||
s.SharePort = 4532
|
||||
}
|
||||
if s.XieguBaud <= 0 {
|
||||
s.XieguBaud = 19200
|
||||
}
|
||||
if s.XieguAddr <= 0 || s.XieguAddr > 0xFF {
|
||||
s.XieguAddr = cat.XieguDefaultAddr
|
||||
}
|
||||
if s.YaesuBaud <= 0 {
|
||||
s.YaesuBaud = 38400
|
||||
}
|
||||
@@ -6727,6 +6748,9 @@ func (a *App) SaveCATSettings(s CATSettings) error {
|
||||
keyCATFlexSpots: flexSpots,
|
||||
keyCATFlexDecodeSpots: flexDecodeSpots,
|
||||
keyCATFlexDecodeSecs: strconv.Itoa(s.FlexDecodeSecs),
|
||||
keyCATXieguPort: strings.TrimSpace(s.XieguPort),
|
||||
keyCATXieguBaud: strconv.Itoa(s.XieguBaud),
|
||||
keyCATXieguAddr: strconv.Itoa(s.XieguAddr),
|
||||
keyCATYaesuPort: strings.TrimSpace(s.YaesuPort),
|
||||
keyCATYaesuBaud: strconv.Itoa(s.YaesuBaud),
|
||||
keyCATIcomPort: strings.TrimSpace(s.IcomPort),
|
||||
@@ -12104,6 +12128,11 @@ func (a *App) reloadCAT() {
|
||||
}
|
||||
}
|
||||
a.cat.Start(fb)
|
||||
case "xiegu":
|
||||
// Xiegu G90/X6100/X6200/X5105 — CI-V, but a REDUCED command set: no scope,
|
||||
// no DSP block, no data mode. Its own backend rather than the Icom one at
|
||||
// another address, so we never poll a G90 for answers it cannot give.
|
||||
a.cat.Start(cat.NewXiegu(s.XieguPort, s.XieguBaud, s.XieguAddr, s.DigitalDefault))
|
||||
case "yaesu":
|
||||
// Native Yaesu CAT over the rig's USB/serial port — no OmniRig. Every
|
||||
// Yaesu fault reported so far came from OmniRig's interpretation layer
|
||||
|
||||
Reference in New Issue
Block a user