package main import ( "fmt" "math" "sync" "time" ) const civAddress = 0xa4 const statusPollInterval = time.Second const commandRetryTimeout = 500 * time.Millisecond const pttTimeout = 3 * time.Minute const tuneTimeout = 30 * time.Second // Commands reference: https://www.icomeurope.com/wp-content/uploads/2020/08/IC-705_ENG_CI-V_1_20200721.pdf type civOperatingMode struct { name string code byte } var civOperatingModes = []civOperatingMode{ {name: "LSB", code: 0x00}, {name: "USB", code: 0x01}, {name: "AM", code: 0x02}, {name: "CW", code: 0x03}, {name: "RTTY", code: 0x04}, {name: "FM", code: 0x05}, {name: "WFM", code: 0x06}, {name: "CW-R", code: 0x07}, {name: "RTTY-R", code: 0x08}, {name: "DV", code: 0x17}, } type civFilter struct { name string code byte } var civFilters = []civFilter{ {name: "FIL1", code: 0x01}, {name: "FIL2", code: 0x02}, {name: "FIL3", code: 0x03}, } type civBand struct { freqFrom uint freqTo uint freq uint } var civBands = []civBand{ {freqFrom: 1800000, freqTo: 1999999}, // 1.9 {freqFrom: 3400000, freqTo: 4099999}, // 3.5 {freqFrom: 6900000, freqTo: 7499999}, // 7 {freqFrom: 9900000, freqTo: 10499999}, // 10 {freqFrom: 13900000, freqTo: 14499999}, // 14 {freqFrom: 17900000, freqTo: 18499999}, // 18 {freqFrom: 20900000, freqTo: 21499999}, // 21 {freqFrom: 24400000, freqTo: 25099999}, // 24 {freqFrom: 28000000, freqTo: 29999999}, // 28 {freqFrom: 50000000, freqTo: 54000000}, // 50 {freqFrom: 74800000, freqTo: 107999999}, // WFM {freqFrom: 108000000, freqTo: 136999999}, // AIR {freqFrom: 144000000, freqTo: 148000000}, // 144 {freqFrom: 420000000, freqTo: 450000000}, // 430 {freqFrom: 0, freqTo: 0}, // GENE } type splitMode int const ( splitModeOff = iota splitModeOn splitModeDUPMinus splitModeDUPPlus ) type civCmd struct { pending bool sentAt time.Time name string cmd []byte } type civControlStruct struct { st *serialStream deinitNeeded chan bool deinitFinished chan bool resetSReadTimer chan bool newPendingCmdAdded chan bool state struct { mutex sync.Mutex pendingCmds []*civCmd getFreq civCmd getMode civCmd getDataMode civCmd getPwr civCmd getS civCmd getOVF civCmd getSWR civCmd getTransmitStatus civCmd getPreamp civCmd getAGC civCmd getTuneStatus civCmd getVd civCmd getTS civCmd getRFGain civCmd getSQL civCmd getNR civCmd getNREnabled civCmd getSplit civCmd lastSReceivedAt time.Time lastOVFReceivedAt time.Time lastSWRReceivedAt time.Time setPwr civCmd setRFGain civCmd setSQL civCmd setNR civCmd setFreq civCmd setMode civCmd setPTT civCmd setTune civCmd setDataMode civCmd setPreamp civCmd setAGC civCmd setNREnabled civCmd setTS civCmd setVFO civCmd setSplit civCmd pttTimeoutTimer *time.Timer tuneTimeoutTimer *time.Timer freq uint ptt bool tune bool pwrPercent int rfGainPercent int sqlPercent int nrPercent int nrEnabled bool operatingModeIdx int filterIdx int dataMode bool bandIdx int bandChanging bool preamp int agc int tsValue byte ts uint vfoBActive bool splitMode splitMode } } var civControl *civControlStruct // Returns false if the message should not be forwarded to the serial port TCP server or the virtual serial port. func (s *civControlStruct) decode(d []byte) bool { if len(d) < 6 || d[0] != 0xfe || d[1] != 0xfe || d[len(d)-1] != 0xfd { return true } payload := d[5 : len(d)-1] s.state.mutex.Lock() defer s.state.mutex.Unlock() switch d[4] { case 0x00: return s.decodeFreq(payload) case 0x01: return s.decodeMode(payload) case 0x03: return s.decodeFreq(payload) case 0x04: return s.decodeMode(payload) case 0x05: return s.decodeFreq(payload) case 0x06: return s.decodeMode(payload) case 0x07: return s.decodeVFO(payload) case 0x0f: return s.decodeSplit(payload) case 0x10: return s.decodeTS(payload) case 0x1a: return s.decodeDataModeAndOVF(payload) case 0x14: return s.decodePowerRFGainSQLNRPwr(payload) case 0x1c: return s.decodeTransmitStatus(payload) case 0x15: return s.decodeVdSWRS(payload) case 0x16: return s.decodePreampAGCNREnabled(payload) } return true } func (s *civControlStruct) decodeFreq(d []byte) bool { if len(d) < 2 { return !s.state.getFreq.pending && !s.state.setFreq.pending } var f uint var pos int for _, v := range d { s1 := v & 0x0f s2 := v >> 4 f += uint(s1) * uint(math.Pow(10, float64(pos))) pos++ f += uint(s2) * uint(math.Pow(10, float64(pos))) pos++ } s.state.freq = f statusLog.reportFrequency(s.state.freq) s.state.bandIdx = len(civBands) - 1 // Set the band idx to GENE by default. for i := range civBands { if s.state.freq >= civBands[i].freqFrom && s.state.freq <= civBands[i].freqTo { s.state.bandIdx = i civBands[s.state.bandIdx].freq = s.state.freq break } } if s.state.getFreq.pending { s.removePendingCmd(&s.state.getFreq) return false } if s.state.setFreq.pending { s.removePendingCmd(&s.state.setFreq) return false } return true } func (s *civControlStruct) decodeFilterValueToFilterIdx(v byte) int { for i := range civFilters { if civFilters[i].code == v { return i } } return -1 } func (s *civControlStruct) decodeMode(d []byte) bool { if len(d) < 1 { return !s.state.getMode.pending && !s.state.setMode.pending } var mode string for i := range civOperatingModes { if civOperatingModes[i].code == d[0] { s.state.operatingModeIdx = i mode = civOperatingModes[i].name break } } var filter string if len(d) > 1 { s.state.filterIdx = s.decodeFilterValueToFilterIdx(d[1]) filter = civFilters[s.state.filterIdx].name } statusLog.reportMode(mode, filter) if s.state.getMode.pending { s.removePendingCmd(&s.state.getMode) return false } if s.state.setMode.pending { s.removePendingCmd(&s.state.setMode) return false } return true } func (s *civControlStruct) decodeVFO(d []byte) bool { if len(d) < 1 { return !s.state.setVFO.pending } if d[0] == 1 { s.state.vfoBActive = true log.Print("active vfo: B") } else { s.state.vfoBActive = false log.Print("active vfo: A") } if s.state.setVFO.pending { // The radio does not send the VFO's frequency automatically. _ = s.getFreq() s.removePendingCmd(&s.state.setVFO) return false } return true } func (s *civControlStruct) decodeSplit(d []byte) bool { if len(d) < 1 { return !s.state.getSplit.pending && !s.state.setSplit.pending } var str string switch d[0] { default: s.state.splitMode = splitModeOff case 0x01: s.state.splitMode = splitModeOn str = "SPLIT" case 0x11: s.state.splitMode = splitModeDUPMinus str = "DUP-" case 0x12: s.state.splitMode = splitModeDUPPlus str = "DUP+" } statusLog.reportSplit(str) if s.state.getSplit.pending { s.removePendingCmd(&s.state.getSplit) return false } if s.state.setSplit.pending { s.removePendingCmd(&s.state.setSplit) return false } return true } func (s *civControlStruct) decodeTS(d []byte) bool { if len(d) < 1 { return !s.state.getTS.pending && !s.state.setTS.pending } s.state.tsValue = d[0] switch s.state.tsValue { default: s.state.ts = 1 case 1: s.state.ts = 100 case 2: s.state.ts = 500 case 3: s.state.ts = 1000 case 4: s.state.ts = 5000 case 5: s.state.ts = 6250 case 6: s.state.ts = 8330 case 7: s.state.ts = 9000 case 8: s.state.ts = 10000 case 9: s.state.ts = 12500 case 10: s.state.ts = 20000 case 11: s.state.ts = 25000 case 12: s.state.ts = 50000 case 13: s.state.ts = 100000 } statusLog.reportTS(s.state.ts) if s.state.getTS.pending { s.removePendingCmd(&s.state.getTS) return false } if s.state.setTS.pending { s.removePendingCmd(&s.state.setTS) return false } return true } func (s *civControlStruct) decodeDataModeAndOVF(d []byte) bool { switch d[0] { case 0x06: if len(d) < 3 { return !s.state.getDataMode.pending && !s.state.setDataMode.pending } var dataMode string var filter string if d[1] == 1 { dataMode = "-D" s.state.dataMode = true s.state.filterIdx = s.decodeFilterValueToFilterIdx(d[2]) filter = civFilters[s.state.filterIdx].name } else { s.state.dataMode = false } statusLog.reportDataMode(dataMode, filter) if s.state.getDataMode.pending { s.removePendingCmd(&s.state.getDataMode) return false } if s.state.setDataMode.pending { s.removePendingCmd(&s.state.setDataMode) return false } case 0x09: if len(d) < 2 { return !s.state.getOVF.pending } if d[1] != 0 { statusLog.reportOVF(true) } else { statusLog.reportOVF(false) } s.state.lastOVFReceivedAt = time.Now() if s.state.getOVF.pending { s.removePendingCmd(&s.state.getOVF) return false } } return true } func (s *civControlStruct) decodePowerRFGainSQLNRPwr(d []byte) bool { switch d[0] { case 0x02: if len(d) < 3 { return !s.state.getRFGain.pending && !s.state.setRFGain.pending } hex := uint16(d[1])<<8 | uint16(d[2]) s.state.rfGainPercent = int(math.Round((float64(hex) / 0x0255) * 100)) statusLog.reportRFGain(s.state.rfGainPercent) if s.state.getRFGain.pending { s.removePendingCmd(&s.state.getRFGain) return false } if s.state.setRFGain.pending { s.removePendingCmd(&s.state.setRFGain) return false } case 0x03: if len(d) < 3 { return !s.state.getSQL.pending && !s.state.setSQL.pending } hex := uint16(d[1])<<8 | uint16(d[2]) s.state.sqlPercent = int(math.Round((float64(hex) / 0x0255) * 100)) statusLog.reportSQL(s.state.sqlPercent) if s.state.getSQL.pending { s.removePendingCmd(&s.state.getSQL) return false } if s.state.setSQL.pending { s.removePendingCmd(&s.state.setSQL) return false } case 0x06: if len(d) < 3 { return !s.state.getNR.pending && !s.state.setNR.pending } hex := uint16(d[1])<<8 | uint16(d[2]) s.state.nrPercent = int(math.Round((float64(hex) / 0x0255) * 100)) statusLog.reportNR(s.state.nrPercent) if s.state.getNR.pending { s.removePendingCmd(&s.state.getNR) return false } if s.state.setNR.pending { s.removePendingCmd(&s.state.setNR) return false } case 0x0a: if len(d) < 3 { return !s.state.getPwr.pending && !s.state.setPwr.pending } hex := uint16(d[1])<<8 | uint16(d[2]) s.state.pwrPercent = int(math.Round((float64(hex) / 0x0255) * 100)) statusLog.reportTxPower(s.state.pwrPercent) if s.state.getPwr.pending { s.removePendingCmd(&s.state.getPwr) return false } if s.state.setPwr.pending { s.removePendingCmd(&s.state.setPwr) return false } } return true } func (s *civControlStruct) decodeTransmitStatus(d []byte) bool { if len(d) < 2 { return !s.state.getTuneStatus.pending && !s.state.getTransmitStatus.pending && !s.state.setPTT.pending } switch d[0] { case 0: if d[1] == 1 { s.state.ptt = true } else { if s.state.ptt { // PTT released? s.state.ptt = false s.state.pttTimeoutTimer.Stop() _ = s.getVd() } } statusLog.reportPTT(s.state.ptt, s.state.tune) if s.state.setPTT.pending { s.removePendingCmd(&s.state.setPTT) return false } case 1: if d[1] == 2 { s.state.tune = true // The transceiver does not send the tune state after it's finished. time.AfterFunc(time.Second, func() { _ = s.getTransmitStatus() }) } else { if s.state.tune { // Tune finished? s.state.tune = false s.state.tuneTimeoutTimer.Stop() _ = s.getVd() } } statusLog.reportPTT(s.state.ptt, s.state.tune) if s.state.setTune.pending { s.removePendingCmd(&s.state.setTune) return false } } if s.state.getTuneStatus.pending { s.removePendingCmd(&s.state.getTuneStatus) return false } if s.state.getTransmitStatus.pending { s.removePendingCmd(&s.state.getTransmitStatus) return false } return true } func (s *civControlStruct) decodeVdSWRS(d []byte) bool { switch d[0] { case 0x02: if len(d) < 3 { return !s.state.getS.pending } sValue := (int(math.Round(((float64(int(d[1])<<8) + float64(d[2])) / 0x0241) * 18))) sStr := "S" if sValue <= 9 { sStr += fmt.Sprint(sValue) } else { sStr += "9+" switch sValue { case 10: sStr += "10" case 11: sStr += "20" case 12: sStr += "30" case 13: sStr += "40" case 14: sStr += "40" case 15: sStr += "40" case 16: sStr += "40" case 17: sStr += "50" case 18: sStr += "50" default: sStr += "60" } } s.state.lastSReceivedAt = time.Now() statusLog.reportS(sStr) if s.state.getS.pending { s.removePendingCmd(&s.state.getS) return false } case 0x12: if len(d) < 3 { return !s.state.getSWR.pending } s.state.lastSWRReceivedAt = time.Now() statusLog.reportSWR(((float64(int(d[1])<<8)+float64(d[2]))/0x0120)*2 + 1) if s.state.getSWR.pending { s.removePendingCmd(&s.state.getSWR) return false } case 0x15: if len(d) < 3 { return !s.state.getVd.pending } statusLog.reportVd(((float64(int(d[1])<<8) + float64(d[2])) / 0x0241) * 16) if s.state.getVd.pending { s.removePendingCmd(&s.state.getVd) return false } } return true } func (s *civControlStruct) decodePreampAGCNREnabled(d []byte) bool { switch d[0] { case 0x02: if len(d) < 2 { return !s.state.getPreamp.pending && !s.state.setPreamp.pending } s.state.preamp = int(d[1]) statusLog.reportPreamp(s.state.preamp) if s.state.getPreamp.pending { s.removePendingCmd(&s.state.getPreamp) return false } if s.state.setPreamp.pending { s.removePendingCmd(&s.state.setPreamp) return false } case 0x12: if len(d) < 2 { return !s.state.getAGC.pending && !s.state.setAGC.pending } s.state.agc = int(d[1]) var agc string switch s.state.agc { case 1: agc = "F" case 2: agc = "M" case 3: agc = "S" } statusLog.reportAGC(agc) if s.state.getAGC.pending { s.removePendingCmd(&s.state.getAGC) return false } if s.state.setAGC.pending { s.removePendingCmd(&s.state.setAGC) return false } case 0x40: if len(d) < 2 { return !s.state.getNREnabled.pending && !s.state.setNREnabled.pending } if d[1] == 1 { s.state.nrEnabled = true } else { s.state.nrEnabled = false } statusLog.reportNREnabled(s.state.nrEnabled) if s.state.getNREnabled.pending { s.removePendingCmd(&s.state.getNREnabled) return false } if s.state.setNREnabled.pending { s.removePendingCmd(&s.state.setNREnabled) return false } } return true } func (s *civControlStruct) initCmd(cmd *civCmd, name string, data []byte) { *cmd = civCmd{} cmd.name = name cmd.cmd = data } func (s *civControlStruct) getPendingCmdIndex(cmd *civCmd) int { for i := range s.state.pendingCmds { if cmd == s.state.pendingCmds[i] { return i } } return -1 } func (s *civControlStruct) removePendingCmd(cmd *civCmd) { cmd.pending = false index := s.getPendingCmdIndex(cmd) if index < 0 { return } s.state.pendingCmds[index] = s.state.pendingCmds[len(s.state.pendingCmds)-1] s.state.pendingCmds[len(s.state.pendingCmds)-1] = nil s.state.pendingCmds = s.state.pendingCmds[:len(s.state.pendingCmds)-1] } func (s *civControlStruct) sendCmd(cmd *civCmd) error { cmd.pending = true cmd.sentAt = time.Now() if s.getPendingCmdIndex(cmd) < 0 { s.state.pendingCmds = append(s.state.pendingCmds, cmd) select { case s.newPendingCmdAdded <- true: default: } } return s.st.send(cmd.cmd) } func (s *civControlStruct) setPwr(percent int) error { v := uint16(0x0255 * (float64(percent) / 100)) s.initCmd(&s.state.setPwr, "setPwr", []byte{254, 254, civAddress, 224, 0x14, 0x0a, byte(v >> 8), byte(v & 0xff), 253}) return s.sendCmd(&s.state.setPwr) } func (s *civControlStruct) incPwr() error { if s.state.pwrPercent < 100 { return s.setPwr(s.state.pwrPercent + 1) } return nil } func (s *civControlStruct) decPwr() error { if s.state.pwrPercent > 0 { return s.setPwr(s.state.pwrPercent - 1) } return nil } func (s *civControlStruct) setRFGain(percent int) error { v := uint16(0x0255 * (float64(percent) / 100)) s.initCmd(&s.state.setRFGain, "setRFGain", []byte{254, 254, civAddress, 224, 0x14, 0x02, byte(v >> 8), byte(v & 0xff), 253}) return s.sendCmd(&s.state.setRFGain) } func (s *civControlStruct) incRFGain() error { if s.state.rfGainPercent < 100 { return s.setRFGain(s.state.rfGainPercent + 1) } return nil } func (s *civControlStruct) decRFGain() error { if s.state.rfGainPercent > 0 { return s.setRFGain(s.state.rfGainPercent - 1) } return nil } func (s *civControlStruct) setSQL(percent int) error { v := uint16(0x0255 * (float64(percent) / 100)) s.initCmd(&s.state.setSQL, "setSQL", []byte{254, 254, civAddress, 224, 0x14, 0x03, byte(v >> 8), byte(v & 0xff), 253}) return s.sendCmd(&s.state.setSQL) } func (s *civControlStruct) incSQL() error { if s.state.sqlPercent < 100 { return s.setSQL(s.state.sqlPercent + 1) } return nil } func (s *civControlStruct) decSQL() error { if s.state.sqlPercent > 0 { return s.setSQL(s.state.sqlPercent - 1) } return nil } func (s *civControlStruct) setNR(percent int) error { if !s.state.nrEnabled { if err := s.toggleNR(); err != nil { return err } } v := uint16(0x0255 * (float64(percent) / 100)) s.initCmd(&s.state.setNR, "setNR", []byte{254, 254, civAddress, 224, 0x14, 0x06, byte(v >> 8), byte(v & 0xff), 253}) return s.sendCmd(&s.state.setNR) } func (s *civControlStruct) incNR() error { if s.state.nrPercent < 100 { return s.setNR(s.state.nrPercent + 1) } return nil } func (s *civControlStruct) decNR() error { if s.state.nrPercent > 0 { return s.setNR(s.state.nrPercent - 1) } return nil } func (s *civControlStruct) getDigit(v uint, n int) byte { f := float64(v) for n > 0 { f /= 10 n-- } return byte(uint(f) % 10) } func (s *civControlStruct) incFreq() error { return s.setFreq(s.state.freq + s.state.ts) } func (s *civControlStruct) decFreq() error { return s.setFreq(s.state.freq - s.state.ts) } func (s *civControlStruct) setFreq(f uint) error { var b [5]byte v0 := s.getDigit(f, 9) v1 := s.getDigit(f, 8) b[4] = v0<<4 | v1 v0 = s.getDigit(f, 7) v1 = s.getDigit(f, 6) b[3] = v0<<4 | v1 v0 = s.getDigit(f, 5) v1 = s.getDigit(f, 4) b[2] = v0<<4 | v1 v0 = s.getDigit(f, 3) v1 = s.getDigit(f, 2) b[1] = v0<<4 | v1 v0 = s.getDigit(f, 1) v1 = s.getDigit(f, 0) b[0] = v0<<4 | v1 s.initCmd(&s.state.setFreq, "setFreq", []byte{254, 254, civAddress, 224, 5, b[0], b[1], b[2], b[3], b[4], 253}) return s.sendCmd(&s.state.setFreq) } func (s *civControlStruct) incOperatingMode() error { s.state.operatingModeIdx++ if s.state.operatingModeIdx >= len(civOperatingModes) { s.state.operatingModeIdx = 0 } return civControl.setOperatingModeAndFilter(civOperatingModes[s.state.operatingModeIdx].code, civFilters[s.state.filterIdx].code) } func (s *civControlStruct) decOperatingMode() error { s.state.operatingModeIdx-- if s.state.operatingModeIdx < 0 { s.state.operatingModeIdx = len(civOperatingModes) - 1 } return civControl.setOperatingModeAndFilter(civOperatingModes[s.state.operatingModeIdx].code, civFilters[s.state.filterIdx].code) } func (s *civControlStruct) incFilter() error { s.state.filterIdx++ if s.state.filterIdx >= len(civFilters) { s.state.filterIdx = 0 } return civControl.setOperatingModeAndFilter(civOperatingModes[s.state.operatingModeIdx].code, civFilters[s.state.filterIdx].code) } func (s *civControlStruct) decFilter() error { s.state.filterIdx-- if s.state.filterIdx < 0 { s.state.filterIdx = len(civFilters) - 1 } return civControl.setOperatingModeAndFilter(civOperatingModes[s.state.operatingModeIdx].code, civFilters[s.state.filterIdx].code) } func (s *civControlStruct) setOperatingModeAndFilter(modeCode, filterCode byte) error { s.initCmd(&s.state.setMode, "setMode", []byte{254, 254, civAddress, 224, 0x06, modeCode, filterCode, 253}) if err := s.sendCmd(&s.state.setMode); err != nil { return err } return s.getMode() } func (s *civControlStruct) setPTT(enable bool) error { var b byte if enable { b = 1 s.state.pttTimeoutTimer = time.AfterFunc(pttTimeout, func() { _ = s.setPTT(false) }) } s.initCmd(&s.state.setPTT, "setPTT", []byte{254, 254, civAddress, 224, 0x1c, 0, b, 253}) return s.sendCmd(&s.state.setPTT) } func (s *civControlStruct) setTune(enable bool) error { if s.state.ptt { return nil } var b byte if enable { b = 2 s.state.tuneTimeoutTimer = time.AfterFunc(tuneTimeout, func() { _ = s.setTune(false) }) } else { b = 1 } s.initCmd(&s.state.setTune, "setTune", []byte{254, 254, civAddress, 224, 0x1c, 1, b, 253}) return s.sendCmd(&s.state.setTune) } func (s *civControlStruct) toggleTune() error { return s.setTune(!s.state.tune) } func (s *civControlStruct) setDataMode(enable bool) error { var b byte var f byte if enable { b = 1 f = 1 } else { b = 0 f = 0 } s.initCmd(&s.state.setDataMode, "setDataMode", []byte{254, 254, civAddress, 224, 0x1a, 0x06, b, f, 253}) return s.sendCmd(&s.state.setDataMode) } func (s *civControlStruct) toggleDataMode() error { return s.setDataMode(!s.state.dataMode) } func (s *civControlStruct) incBand() error { s.state.bandChanging = true i := s.state.bandIdx + 1 if i >= len(civBands) { i = 0 } f := civBands[i].freq if f == 0 { f = (civBands[i].freqFrom + civBands[i].freqTo) / 2 } return s.setFreq(f) } func (s *civControlStruct) decBand() error { s.state.bandChanging = true i := s.state.bandIdx - 1 if i < 0 { i = len(civBands) - 1 } f := civBands[i].freq if f == 0 { f = civBands[i].freqFrom } return s.setFreq(f) } func (s *civControlStruct) togglePreamp() error { b := byte(s.state.preamp + 1) if b > 2 { b = 0 } s.initCmd(&s.state.setPreamp, "setPreamp", []byte{254, 254, civAddress, 224, 0x16, 0x02, b, 253}) return s.sendCmd(&s.state.setPreamp) } func (s *civControlStruct) toggleAGC() error { b := byte(s.state.agc + 1) if b > 3 { b = 1 } s.initCmd(&s.state.setAGC, "setAGC", []byte{254, 254, civAddress, 224, 0x16, 0x12, b, 253}) return s.sendCmd(&s.state.setAGC) } func (s *civControlStruct) toggleNR() error { var b byte if !s.state.nrEnabled { b = 1 } s.initCmd(&s.state.setNREnabled, "setNREnabled", []byte{254, 254, civAddress, 224, 0x16, 0x40, b, 253}) return s.sendCmd(&s.state.setNREnabled) } func (s *civControlStruct) setTS(b byte) error { s.initCmd(&s.state.setTS, "setTS", []byte{254, 254, civAddress, 224, 0x10, b, 253}) return s.sendCmd(&s.state.setTS) } func (s *civControlStruct) incTS() error { var b byte if s.state.tsValue == 13 { b = 0 } else { b = s.state.tsValue + 1 } return s.setTS(b) } func (s *civControlStruct) decTS() error { var b byte if s.state.tsValue == 0 { b = 13 } else { b = s.state.tsValue - 1 } return s.setTS(b) } func (s *civControlStruct) setVFO(nr byte) error { s.initCmd(&s.state.setVFO, "setVFO", []byte{254, 254, civAddress, 224, 0x07, nr, 253}) return s.sendCmd(&s.state.setVFO) } func (s *civControlStruct) toggleVFO() error { var b byte if !s.state.vfoBActive { b = 1 } return s.setVFO(b) } func (s *civControlStruct) setSplit(mode splitMode) error { var b byte switch mode { default: b = 0x00 case splitModeOn: b = 0x01 case splitModeDUPMinus: b = 0x11 case splitModeDUPPlus: b = 0x12 } s.initCmd(&s.state.setSplit, "setSplit", []byte{254, 254, civAddress, 224, 0x0f, b, 253}) return s.sendCmd(&s.state.setSplit) } func (s *civControlStruct) toggleSplit() error { var mode splitMode switch s.state.splitMode { case splitModeOff: mode = splitModeOn case splitModeOn: mode = splitModeDUPMinus case splitModeDUPMinus: mode = splitModeDUPPlus default: mode = splitModeOff } return s.setSplit(mode) } func (s *civControlStruct) getFreq() error { s.initCmd(&s.state.getFreq, "getFreq", []byte{254, 254, civAddress, 224, 3, 253}) return s.sendCmd(&s.state.getFreq) } func (s *civControlStruct) getMode() error { s.initCmd(&s.state.getMode, "getMode", []byte{254, 254, civAddress, 224, 4, 253}) return s.sendCmd(&s.state.getMode) } func (s *civControlStruct) getDataMode() error { s.initCmd(&s.state.getDataMode, "getDataMode", []byte{254, 254, civAddress, 224, 0x1a, 0x06, 253}) return s.sendCmd(&s.state.getDataMode) } func (s *civControlStruct) getPwr() error { s.initCmd(&s.state.getPwr, "getPwr", []byte{254, 254, civAddress, 224, 0x14, 0x0a, 253}) return s.sendCmd(&s.state.getPwr) } func (s *civControlStruct) getTransmitStatus() error { s.initCmd(&s.state.getTransmitStatus, "getTransmitStatus", []byte{254, 254, civAddress, 224, 0x1c, 0, 253}) if err := s.sendCmd(&s.state.getTransmitStatus); err != nil { return err } s.initCmd(&s.state.getTuneStatus, "getTuneStatus", []byte{254, 254, civAddress, 224, 0x1c, 1, 253}) return s.sendCmd(&s.state.getTuneStatus) } func (s *civControlStruct) getPreamp() error { s.initCmd(&s.state.getPreamp, "getPreamp", []byte{254, 254, civAddress, 224, 0x16, 0x02, 253}) return s.sendCmd(&s.state.getPreamp) } func (s *civControlStruct) getAGC() error { s.initCmd(&s.state.getAGC, "getAGC", []byte{254, 254, civAddress, 224, 0x16, 0x12, 253}) return s.sendCmd(&s.state.getAGC) } func (s *civControlStruct) getVd() error { s.initCmd(&s.state.getVd, "getVd", []byte{254, 254, civAddress, 224, 0x15, 0x15, 253}) return s.sendCmd(&s.state.getVd) } func (s *civControlStruct) getS() error { s.initCmd(&s.state.getS, "getS", []byte{254, 254, civAddress, 224, 0x15, 0x02, 253}) return s.sendCmd(&s.state.getS) } func (s *civControlStruct) getOVF() error { s.initCmd(&s.state.getOVF, "getOVF", []byte{254, 254, civAddress, 224, 0x1a, 0x09, 253}) return s.sendCmd(&s.state.getOVF) } func (s *civControlStruct) getSWR() error { s.initCmd(&s.state.getSWR, "getSWR", []byte{254, 254, civAddress, 224, 0x15, 0x12, 253}) return s.sendCmd(&s.state.getSWR) } func (s *civControlStruct) getTS() error { s.initCmd(&s.state.getTS, "getTS", []byte{254, 254, civAddress, 224, 0x10, 253}) return s.sendCmd(&s.state.getTS) } func (s *civControlStruct) getRFGain() error { s.initCmd(&s.state.getRFGain, "getRFGain", []byte{254, 254, civAddress, 224, 0x14, 0x02, 253}) return s.sendCmd(&s.state.getRFGain) } func (s *civControlStruct) getSQL() error { s.initCmd(&s.state.getSQL, "getSQL", []byte{254, 254, civAddress, 224, 0x14, 0x03, 253}) return s.sendCmd(&s.state.getSQL) } func (s *civControlStruct) getNR() error { s.initCmd(&s.state.getNR, "getNR", []byte{254, 254, civAddress, 224, 0x14, 0x06, 253}) return s.sendCmd(&s.state.getNR) } func (s *civControlStruct) getNREnabled() error { s.initCmd(&s.state.getNREnabled, "getNREnabled", []byte{254, 254, civAddress, 224, 0x16, 0x40, 253}) return s.sendCmd(&s.state.getNREnabled) } func (s *civControlStruct) getSplit() error { s.initCmd(&s.state.getSplit, "getSplit", []byte{254, 254, civAddress, 224, 0x0f, 253}) return s.sendCmd(&s.state.getSplit) } func (s *civControlStruct) loop() { for { s.state.mutex.Lock() nextPendingCmdTimeout := time.Hour for i := range s.state.pendingCmds { diff := time.Since(s.state.pendingCmds[i].sentAt) if diff >= commandRetryTimeout { nextPendingCmdTimeout = 0 break } if diff < nextPendingCmdTimeout { nextPendingCmdTimeout = diff } } s.state.mutex.Unlock() select { case <-s.deinitNeeded: s.deinitFinished <- true return case <-time.After(statusPollInterval): if !s.state.getS.pending && time.Since(s.state.lastSReceivedAt) >= statusPollInterval { _ = s.getS() } if !s.state.getOVF.pending && time.Since(s.state.lastOVFReceivedAt) >= statusPollInterval { _ = s.getOVF() } if !s.state.getSWR.pending && time.Since(s.state.lastSWRReceivedAt) >= statusPollInterval && (s.state.ptt || s.state.tune) { _ = s.getSWR() } case <-s.resetSReadTimer: case <-s.newPendingCmdAdded: case <-time.After(nextPendingCmdTimeout): s.state.mutex.Lock() for _, cmd := range s.state.pendingCmds { if time.Since(cmd.sentAt) >= commandRetryTimeout { log.Debug("retrying cmd send ", cmd.name) _ = s.sendCmd(cmd) } } s.state.mutex.Unlock() } } } func (s *civControlStruct) init(st *serialStream) error { s.st = st if err := s.getFreq(); err != nil { return err } if err := s.getMode(); err != nil { return err } if err := s.getDataMode(); err != nil { return err } if err := s.getPwr(); err != nil { return err } if err := s.getTransmitStatus(); err != nil { return err } if err := s.getPreamp(); err != nil { return err } if err := s.getAGC(); err != nil { return err } if err := s.getVd(); err != nil { return err } if err := s.getS(); err != nil { return err } if err := s.getOVF(); err != nil { return err } if err := s.getSWR(); err != nil { return err } if err := s.getTS(); err != nil { return err } if err := s.getRFGain(); err != nil { return err } if err := s.getSQL(); err != nil { return err } if err := s.getNR(); err != nil { return err } if err := s.getNREnabled(); err != nil { return err } if err := s.getSplit(); err != nil { return err } s.deinitNeeded = make(chan bool) s.deinitFinished = make(chan bool) s.resetSReadTimer = make(chan bool) s.newPendingCmdAdded = make(chan bool) go s.loop() return nil } func (s *civControlStruct) deinit() { if s.deinitNeeded != nil { s.deinitNeeded <- true <-s.deinitFinished } s.deinitNeeded = nil }