diff --git a/README.md b/README.md index 665be78..ea95db1 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,12 @@ kappanhang displays a "realtime" status bar (when the audio/serial connection is up) with the following info: - First status bar line: + - `S meter`: periodically refreshed S meter value + +- Second status bar line: - `state`: RX/TX/TUNE depending on the PTT status - `freq`: operating frequency in MHz + - `ts`: tuning step - `mode`: LSB/USB/FM etc. - `filter`: active filter (FIL1, FIL2 etc.) - `preamp`: PAMP0 means the preamp is off @@ -94,9 +98,8 @@ is up) with the following info: - `txpwr`: current transmit power setting in percent - `audio`: current status of the audio monitor (see the *Hotkeys* section in this README for more information about this feature) - - `S meter`: periodically refreshed S meter value -- Second status bar line: +- Third status bar line: - `up`: how long the audio/serial connection is active - `rtt`: roundtrip communication latency with the server - `up/down`: currently used upload/download bandwidth (only considering UDP @@ -104,8 +107,8 @@ is up) with the following info: - `retx`: audio/serial retransmit request count to/from the server - `lost`: lost audio/serial packet count from the server -Data for the first status bar line is acquired by monitoring CiV traffic in -the serial stream. +Data for the first 2 status bar lines are acquired by monitoring CiV traffic +in the serial stream. S value is queried periodically. `retx` and `lost` are displayed in a 1 minute window, which means they will be reset to 0 if they don't increase for 1 minute. A `retx` value other than 0 @@ -116,7 +119,7 @@ audio and serial communication disruptions. If status bar interval (can be changed with the `-i` command line argument) is equal to or above 1 second, then the realtime status bar will be -disabled and the contents of the second line of the status bar will be written +disabled and the contents of the last line of the status bar will be written as new console log lines. This is also the case if a Unix/VT100 terminal is not available. @@ -135,12 +138,8 @@ Some basic CAT control hotkeys are also supported: - `t`: toggles the tune process - `+`: increases TX power - `-`: decreases TX power -- `<`, `>`: decreases, increases frequency 1Hz -- `,`, `.`: decreases, increases frequency 10Hz -- `:`, `"`: decreases, increases frequency 100Hz -- `;`, `'`: decreases, increases frequency 1kHz -- `{`, `}`: decreases, increases frequency 10kHz -- `[`, `]`: decreases, increases frequency 100kHz +- `[`, `]`: decreases, increases frequency +- `{`, `}`: decreases, increases tuning step - `n`, `m`: cycles through operating modes - `d`, `f`: cycles through filters - `D`: toggles data mode diff --git a/civcontrol.go b/civcontrol.go index 8a45d73..b88cdf5 100644 --- a/civcontrol.go +++ b/civcontrol.go @@ -79,6 +79,8 @@ type civControlStruct struct { bandIdx int bandChanging bool preamp int + tsValue byte + ts uint } } @@ -100,6 +102,8 @@ func (s *civControlStruct) decode(d []byte) { s.decodeFreq(payload) case 0x04: s.decodeMode(payload) + case 0x10: + s.decodeTS(payload) case 0x1a: s.decodeDataMode(payload) case 0x14: @@ -174,6 +178,46 @@ func (s *civControlStruct) decodeMode(d []byte) { _ = s.getDataMode() } +func (s *civControlStruct) decodeTS(d []byte) { + if len(d) < 1 { + return + } + + 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) +} + func (s *civControlStruct) decodeDataMode(d []byte) { if len(d) < 3 || d[0] != 0x06 { return @@ -304,12 +348,12 @@ func (s *civControlStruct) getDigit(v uint, n int) byte { return byte(uint(f) % 10) } -func (s *civControlStruct) incFreq(v uint) error { - return s.setFreq(s.state.freq + v) +func (s *civControlStruct) incFreq() error { + return s.setFreq(s.state.freq + s.state.ts) } -func (s *civControlStruct) decFreq(v uint) error { - return s.setFreq(s.state.freq - v) +func (s *civControlStruct) decFreq() error { + return s.setFreq(s.state.freq - s.state.ts) } func (s *civControlStruct) setFreq(f uint) error { @@ -448,6 +492,26 @@ func (s *civControlStruct) togglePreamp() error { return s.st.send([]byte{254, 254, civAddress, 224, 0x16, 0x02, b, 253}) } +func (s *civControlStruct) incTS() error { + var b byte + if s.state.tsValue == 13 { + b = 0 + } else { + b = s.state.tsValue + 1 + } + return s.st.send([]byte{254, 254, civAddress, 224, 0x10, b, 253}) +} + +func (s *civControlStruct) decTS() error { + var b byte + if s.state.tsValue == 0 { + b = 13 + } else { + b = s.state.tsValue - 1 + } + return s.st.send([]byte{254, 254, civAddress, 224, 0x10, b, 253}) +} + func (s *civControlStruct) getFreq() error { return s.st.send([]byte{254, 254, civAddress, 224, 3, 253}) } @@ -475,6 +539,10 @@ func (s *civControlStruct) getS() error { return s.st.send([]byte{254, 254, civAddress, 224, 0x15, 0x02, 253}) } +func (s *civControlStruct) getTS() error { + return s.st.send([]byte{254, 254, civAddress, 224, 0x10, 253}) +} + func (s *civControlStruct) loop() { for { select { @@ -517,6 +585,9 @@ func (s *civControlStruct) init(st *serialStream) error { if err := s.getS(); err != nil { return err } + if err := s.getTS(); err != nil { + return err + } s.deinitNeeded = make(chan bool) s.deinitFinished = make(chan bool) diff --git a/hotkeys.go b/hotkeys.go index f9717ff..c2b86a0 100644 --- a/hotkeys.go +++ b/hotkeys.go @@ -24,78 +24,30 @@ func handleHotkey(k byte) { log.Error("can't decrease power: ", err) } } - case '>': - if civControl != nil { - if err := civControl.incFreq(1); err != nil { - log.Error("can't increase freq: ", err) - } - } - case '<': - if civControl != nil { - if err := civControl.decFreq(1); err != nil { - log.Error("can't decrease freq: ", err) - } - } - case '.': - if civControl != nil { - if err := civControl.incFreq(10); err != nil { - log.Error("can't increase freq: ", err) - } - } - case ',': - if civControl != nil { - if err := civControl.decFreq(10); err != nil { - log.Error("can't decrease freq: ", err) - } - } - case '"': - if civControl != nil { - if err := civControl.incFreq(100); err != nil { - log.Error("can't increase freq: ", err) - } - } - case ':': - if civControl != nil { - if err := civControl.decFreq(100); err != nil { - log.Error("can't decrease freq: ", err) - } - } - case '\'': - if civControl != nil { - if err := civControl.incFreq(1000); err != nil { - log.Error("can't increase freq: ", err) - } - } - case ';': - if civControl != nil { - if err := civControl.decFreq(1000); err != nil { - log.Error("can't decrease freq: ", err) - } - } - case '}': - if civControl != nil { - if err := civControl.incFreq(10000); err != nil { - log.Error("can't increase freq: ", err) - } - } - case '{': - if civControl != nil { - if err := civControl.decFreq(10000); err != nil { - log.Error("can't decrease freq: ", err) - } - } case ']': if civControl != nil { - if err := civControl.incFreq(100000); err != nil { + if err := civControl.incFreq(); err != nil { log.Error("can't increase freq: ", err) } } case '[': if civControl != nil { - if err := civControl.decFreq(100000); err != nil { + if err := civControl.decFreq(); err != nil { log.Error("can't decrease freq: ", err) } } + case '}': + if civControl != nil { + if err := civControl.incTS(); err != nil { + log.Error("can't increase ts: ", err) + } + } + case '{': + if civControl != nil { + if err := civControl.decTS(); err != nil { + log.Error("can't decrease ts: ", err) + } + } case 'm': if civControl != nil { if err := civControl.incOperatingMode(); err != nil { diff --git a/statuslog.go b/statuslog.go index e864766..585eb4e 100644 --- a/statuslog.go +++ b/statuslog.go @@ -15,15 +15,16 @@ type statusLogData struct { line2 string line3 string - stateStr string - frequency uint - mode string - dataMode string - filter string - preamp string - vd string - txPowerStr string - s string + stateStr string + frequency uint + mode string + dataMode string + filter string + preamp string + vd string + txPower string + s string + ts string startTime time.Time rttStr string @@ -167,6 +168,27 @@ func (s *statusLogStruct) reportS(sValue string) { s.data.s = sValue } +func (s *statusLogStruct) reportTS(ts uint) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.data == nil { + return + } + s.data.ts = "TS" + if ts >= 1000 { + if ts%1000 == 0 { + s.data.ts += fmt.Sprintf("%.0fk", float64(ts)/1000) + } else if ts%100 == 0 { + s.data.ts += fmt.Sprintf("%.1fk", float64(ts)/1000) + } else { + s.data.ts += fmt.Sprintf("%.2fk", float64(ts)/1000) + } + } else { + s.data.ts += fmt.Sprint(ts) + } +} + func (s *statusLogStruct) reportPTT(ptt, tune bool) { s.mutex.Lock() defer s.mutex.Unlock() @@ -190,7 +212,7 @@ func (s *statusLogStruct) reportTxPower(percent int) { if s.data == nil { return } - s.data.txPowerStr = fmt.Sprint(percent, "%") + s.data.txPower = fmt.Sprint(percent, "%") } func (s *statusLogStruct) clearInternal() { @@ -229,6 +251,10 @@ func (s *statusLogStruct) update() { s.data.line1 = fmt.Sprint(s.data.s) + var tsStr string + if s.data.ts != "" { + tsStr = " " + s.data.ts + } var modeStr string if s.data.mode != "" { modeStr = " " + s.data.mode + s.data.dataMode @@ -246,11 +272,11 @@ func (s *statusLogStruct) update() { vdStr = " " + s.data.vd } var txPowerStr string - if s.data.txPowerStr != "" { - txPowerStr = " txpwr " + s.data.txPowerStr + if s.data.txPower != "" { + txPowerStr = " txpwr " + s.data.txPower } s.data.line2 = fmt.Sprint(s.data.stateStr, " ", fmt.Sprintf("%.6f", float64(s.data.frequency)/1000000), - modeStr, filterStr, preampStr, vdStr, txPowerStr, " audio ", s.data.audioStateStr) + tsStr, modeStr, filterStr, preampStr, vdStr, txPowerStr, " audio ", s.data.audioStateStr) up, down, lost, retransmits := netstat.get() lostStr := "0"