feat: added RIT and XIT for Icom
This commit is contained in:
@@ -382,6 +382,10 @@ type IcomTXState struct {
|
||||
SMeter int `json:"s_meter"` // 0-100 (raw 0-255; S9≈120)
|
||||
PowerMeter int `json:"power_meter"` // 0-100 (TX Po)
|
||||
SWRMeter int `json:"swr_meter"` // 0-100 (TX SWR)
|
||||
// RIT / ΔTX (XIT).
|
||||
RITHz int `json:"rit_hz"` // RIT/XIT offset, signed Hz
|
||||
RITOn bool `json:"rit_on"`
|
||||
XITOn bool `json:"xit_on"`
|
||||
// Set controls.
|
||||
RFPower int `json:"rf_power"` // 0-100 (TX output)
|
||||
MicGain int `json:"mic_gain"` // 0-100
|
||||
@@ -422,6 +426,9 @@ type IcomController interface {
|
||||
SetScope(bool) error // enable/disable the spectrum-scope waveform stream
|
||||
SetScopeMode(bool) error // true = fixed span, false = center-on-VFO
|
||||
ScopeData() ScopeSweep // latest assembled sweep (empty until enabled)
|
||||
SetRIT(int) error // RIT/ΔTX offset in signed Hz
|
||||
SetRITOn(bool) error // RIT on/off
|
||||
SetXITOn(bool) error // ΔTX (XIT) on/off
|
||||
}
|
||||
|
||||
// ScopeSweep is one complete spectrum-scope sweep reassembled from the Icom's
|
||||
|
||||
@@ -44,6 +44,11 @@ const (
|
||||
CmdSwitch = 0x16 // on/off + multi-state DSP settings (sub + 1 byte)
|
||||
CmdATU = 0x1C // sub 0x01 = antenna tuner (0x00 off, 0x01 through, 0x02 tune)
|
||||
CmdScope = 0x27 // spectrum-scope waveform stream (sub 0x00 = data, 0x11 = on/off)
|
||||
CmdRIT = 0x21 // RIT/ΔTX: sub 0x00 offset freq, 0x01 RIT on/off, 0x02 ΔTX(XIT) on/off
|
||||
|
||||
SubRITFreq = 0x00 // RIT/ΔTX offset: 2 BCD bytes (LE, 0-9999) + sign byte (00 +, 01 -)
|
||||
SubRITOn = 0x01 // RIT on/off (00/01)
|
||||
SubXITOn = 0x02 // ΔTX (XIT) on/off (00/01)
|
||||
|
||||
SubDataMode = 0x06
|
||||
SubPTT = 0x00
|
||||
@@ -154,6 +159,38 @@ func BCDToLevel(b []byte) int {
|
||||
return int(b[0])*100 + int(b[1]>>4)*10 + int(b[1]&0x0F)
|
||||
}
|
||||
|
||||
// RITToBCD encodes a RIT/ΔTX offset (Hz, −9999..9999) as the 3 bytes CI-V
|
||||
// command 0x21 0x00 uses: 2 little-endian BCD bytes of the magnitude followed by
|
||||
// a sign byte (0x00 positive, 0x01 negative).
|
||||
func RITToBCD(hz int) []byte {
|
||||
neg := hz < 0
|
||||
if neg {
|
||||
hz = -hz
|
||||
}
|
||||
if hz > 9999 {
|
||||
hz = 9999
|
||||
}
|
||||
lo := byte(hz%10 | (hz/10%10)<<4)
|
||||
hi := byte(hz/100%10 | (hz/1000%10)<<4)
|
||||
sign := byte(0)
|
||||
if neg {
|
||||
sign = 1
|
||||
}
|
||||
return []byte{lo, hi, sign}
|
||||
}
|
||||
|
||||
// BCDToRIT decodes the 3 offset bytes of a 0x21 0x00 response back to signed Hz.
|
||||
func BCDToRIT(b []byte) int {
|
||||
if len(b) < 3 {
|
||||
return 0
|
||||
}
|
||||
v := int(b[0]&0x0F) + int(b[0]>>4)*10 + int(b[1]&0x0F)*100 + int(b[1]>>4)*1000
|
||||
if b[2] != 0 {
|
||||
return -v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ByteToBCD / BCDToByte handle a single packed-BCD byte (used by the
|
||||
// attenuator, where the value is dB: 0x00, 0x06, 0x12, 0x18…).
|
||||
func ByteToBCD(v int) byte {
|
||||
|
||||
@@ -277,6 +277,12 @@ func (b *IcomSerial) SetPTT(on bool) error {
|
||||
// ── helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
func (b *IcomSerial) write(payload ...byte) error {
|
||||
// Not connected (rig off / port dropped): fail cleanly instead of
|
||||
// dereferencing a nil port — a Set* dispatched while disconnected (e.g.
|
||||
// clicking Scope ON with the radio off) would otherwise panic the app.
|
||||
if b.port == nil {
|
||||
return fmt.Errorf("icom: not connected")
|
||||
}
|
||||
// Drop any stale/unsolicited frames buffered from before this command so
|
||||
// recv() only sees the reply to THIS request (avoids a previous command's ack
|
||||
// or an unsolicited dial-turn update being mistaken for our response).
|
||||
@@ -514,6 +520,68 @@ func (b *IcomSerial) SetScopeMode(fixed bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRIT sets the RIT/ΔTX offset (signed Hz, ±9999).
|
||||
func (b *IcomSerial) SetRIT(hz int) error {
|
||||
if err := b.exec(append([]byte{civ.CmdRIT, civ.SubRITFreq}, civ.RITToBCD(hz)...)...); err != nil {
|
||||
return err
|
||||
}
|
||||
if hz < -9999 {
|
||||
hz = -9999
|
||||
}
|
||||
if hz > 9999 {
|
||||
hz = 9999
|
||||
}
|
||||
b.setCache(func(s *IcomTXState) { s.RITHz = hz })
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *IcomSerial) SetRITOn(on bool) error {
|
||||
if err := b.exec(civ.CmdRIT, civ.SubRITOn, boolByte(on)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.setCache(func(s *IcomTXState) { s.RITOn = on })
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *IcomSerial) SetXITOn(on bool) error {
|
||||
if err := b.exec(civ.CmdRIT, civ.SubXITOn, boolByte(on)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.setCache(func(s *IcomTXState) { s.XITOn = on })
|
||||
return nil
|
||||
}
|
||||
|
||||
// readRIT reads the offset + RIT/ΔTX on-off flags into st (best-effort).
|
||||
func (b *IcomSerial) readRIT(st *IcomTXState) {
|
||||
if err := b.write(civ.CmdRIT, civ.SubRITFreq); err == nil {
|
||||
if f, err := b.recv(icomDSPTimeout, func(d civ.Decoded) bool {
|
||||
return d.Cmd == civ.CmdRIT && len(d.Data) >= 4 && d.Data[0] == civ.SubRITFreq
|
||||
}); err == nil {
|
||||
st.RITHz = civ.BCDToRIT(f.Data[1:4])
|
||||
}
|
||||
}
|
||||
if v, ok := b.readSwitchSub(civ.CmdRIT, civ.SubRITOn); ok {
|
||||
st.RITOn = v != 0
|
||||
}
|
||||
if v, ok := b.readSwitchSub(civ.CmdRIT, civ.SubXITOn); ok {
|
||||
st.XITOn = v != 0
|
||||
}
|
||||
}
|
||||
|
||||
// readSwitchSub reads a 1-byte on/off value for cmd+sub (generalises readSwitch).
|
||||
func (b *IcomSerial) readSwitchSub(cmd, sub byte) (byte, bool) {
|
||||
if err := b.write(cmd, sub); err != nil {
|
||||
return 0, false
|
||||
}
|
||||
f, err := b.recv(icomDSPTimeout, func(d civ.Decoded) bool {
|
||||
return d.Cmd == cmd && len(d.Data) >= 2 && d.Data[0] == sub
|
||||
})
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return f.Data[1], true
|
||||
}
|
||||
|
||||
// ScopeData returns a copy of the latest reassembled sweep as a number array.
|
||||
func (b *IcomSerial) ScopeData() ScopeSweep {
|
||||
b.scopeMu.Lock()
|
||||
@@ -743,6 +811,7 @@ func (b *IcomSerial) readDSP() {
|
||||
if _, f, ok := b.readModeFilter(); ok {
|
||||
st.Filter = int(f)
|
||||
}
|
||||
b.readRIT(&st)
|
||||
|
||||
b.dspMu.Lock()
|
||||
b.dsp = st
|
||||
|
||||
Reference in New Issue
Block a user