kappanhang/pkt7.go
Birk da Yooper 7656d4335b Added flag to set alternate controller address
Added flag to show CI-V packet contents to support debugging

At start-up show virtual soundcard info by both name & filesystem location
Updated to always present status lines at bottom of the terminal
Removed milliseconds from time shown on status lines
Reformatted status line showing packet activity to be easier to follow
Adjusted default status update frequency to 150ms to be easier to observe

Restored commented out code for working with single VFO

Added multiple functions to handle BCD <-> human readable values to improve easy of maintenance
Added function to prepare CI-V packets, abstracting this step so that each function no longer
  needs to create entire packet. This makes the code much easier to follow and maintaint.
  This will also greatly ease effort needed for extending radio features supported

Added/adjusted various comments

Updated some variable names to be less terse to facilitate maintenance
  eg "incTS" to "incTuningStep"

Some adjustments to improve readability/ease maintenance in favor of unnecessary optimizations
  EG - using formatted prints instead of prints using concatenations
       using strings.Repeat instead of loop to build space buffers
       abstracted control char strings to variables

Hooks for future functionality

Removed some commented out code blocks
   "Trust the repo, Luke!"
2024-02-24 20:53:49 -05:00

185 lines
5.1 KiB
Go

package main
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"time"
)
// This is sent every 100ms by the server, but as this seems to be only a ping-line packet,
// it is also fine to send it in a longer interval.
const pkt7SendInterval = 3 * time.Second
const pkt7TimeoutDuration = 3 * time.Second
type pkt7Type struct {
sendSeq uint16
innerSendSeq uint16
// lastConfirmedSeq uint16
sendTicker *time.Ticker
timeoutTimer *time.Timer
latency time.Duration
lastSendAt time.Time
periodicStopNeededChan chan bool
periodicStopFinishedChan chan bool
}
var controlStreamLatency time.Duration
func (p *pkt7Type) isPkt7(r []byte) bool {
// Note that the first byte can be 0x15 or 0x00, so we ignore that.
return len(r) == 21 && bytes.Equal(r[1:6], []byte{0x00, 0x00, 0x00, 0x07, 0x00})
}
func (p *pkt7Type) handle(s *streamCommon, r []byte) error {
gotSeq := binary.LittleEndian.Uint16(r[6:8])
if r[16] == 0x00 { // This is a pkt7 request from the radio.
// Replying to the radio.
// Example request from radio:
// 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x1c, 0x0e, 0xe4, 0x35, 0xdd,
// 0x72, 0xbe, 0xd9, 0xf2, 0x63, 0x00, 0x57, 0x2b, 0x12, 0x00
//
// Example answer from PC:
// 0x15, 0x00, 0x00, 0x00, 0x07, 0x00, 0x1c, 0x0e, 0xbe, 0xd9, 0xf2,
// 0x63, 0xe4, 0x35, 0xdd, 0x72, 0x01, 0x57, 0x2b, 0x12, 0x00
// Only replying if the auth is already done.
if p.sendTicker != nil {
if err := p.sendReply(s, r[17:21], gotSeq); err != nil {
return err
}
}
} else { // This is a pkt7 reply to our request.
if p.sendTicker != nil { // Auth is already done?
if p.timeoutTimer != nil {
p.timeoutTimer.Stop()
p.timeoutTimer.Reset(pkt7TimeoutDuration)
}
if s.name == "control" { // Only measure latency on the control stream.
// Only measure latency after the timeout has been initialized, so the auth is already done.
p.latency += time.Since(p.lastSendAt)
p.latency /= 2
statusLog.reportRTTLatency(p.latency)
controlStreamLatency = p.latency
}
}
// expectedSeq := p.lastConfirmedSeq + 1
// if expectedSeq != gotSeq && gotSeq != p.lastConfirmedSeq {
// var missingPkts int
// if gotSeq > expectedSeq {
// missingPkts = int(gotSeq) - int(expectedSeq)
// } else {
// missingPkts = int(gotSeq) + 65536 - int(expectedSeq)
// }
// log.Error(s.name+"/lost ", missingPkts, " packets")
// }
// p.lastConfirmedSeq = gotSeq
}
return nil
}
func (p *pkt7Type) sendDo(s *streamCommon, replyID []byte, seq uint16) error {
// Example request from PC: 0x15, 0x00, 0x00, 0x00, 0x07, 0x00, 0x09, 0x00, 0xbe, 0xd9, 0xf2, 0x63, 0xe4, 0x35, 0xdd, 0x72, 0x00, 0x78, 0x40, 0xf6, 0x02
// Example reply from radio: 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x09, 0x00, 0xe4, 0x35, 0xdd, 0x72, 0xbe, 0xd9, 0xf2, 0x63, 0x01, 0x78, 0x40, 0xf6, 0x02
var replyFlag byte
if replyID == nil {
replyID = make([]byte, 4)
var randID [1]byte
if _, err := rand.Read(randID[:]); err != nil {
return err
}
replyID[0] = randID[0]
replyID[1] = byte(p.innerSendSeq)
replyID[2] = byte(p.innerSendSeq >> 8)
replyID[3] = 0x06
p.innerSendSeq++
} else {
replyFlag = 0x01
}
d := []byte{0x15, 0x00, 0x00, 0x00, 0x07, 0x00, byte(seq), byte(seq >> 8),
byte(s.localSID >> 24), byte(s.localSID >> 16), byte(s.localSID >> 8), byte(s.localSID),
byte(s.remoteSID >> 24), byte(s.remoteSID >> 16), byte(s.remoteSID >> 8), byte(s.remoteSID),
replyFlag, replyID[0], replyID[1], replyID[2], replyID[3]}
if err := s.send(d); err != nil {
return err
}
return nil
}
func (p *pkt7Type) send(s *streamCommon) error {
if err := p.sendDo(s, nil, p.sendSeq); err != nil {
return err
}
p.lastSendAt = time.Now()
p.sendSeq++
return nil
}
func (p *pkt7Type) sendReply(s *streamCommon, replyID []byte, seq uint16) error {
return p.sendDo(s, replyID, seq)
}
func (p *pkt7Type) loop(s *streamCommon) {
for {
if p.timeoutTimer != nil {
select {
case <-p.timeoutTimer.C:
reportError(errors.New(s.name + "/ping timeout"))
case <-p.sendTicker.C:
if err := p.send(s); err != nil {
reportError(err)
}
case <-p.periodicStopNeededChan:
p.periodicStopFinishedChan <- true
return
}
} else {
select {
case <-p.sendTicker.C:
if err := p.send(s); err != nil {
reportError(err)
}
case <-p.periodicStopNeededChan:
p.periodicStopFinishedChan <- true
return
}
}
}
}
func (p *pkt7Type) startPeriodicSend(s *streamCommon, firstSeqNo uint16, checkPingTimeout bool) {
p.sendSeq = firstSeqNo
p.innerSendSeq = 0x8304
// p.lastConfirmedSeq = p.sendSeq - 1
p.sendTicker = time.NewTicker(pkt7SendInterval)
if checkPingTimeout {
p.timeoutTimer = time.NewTimer(pkt7TimeoutDuration)
}
p.periodicStopNeededChan = make(chan bool)
p.periodicStopFinishedChan = make(chan bool)
go p.loop(s)
}
func (p *pkt7Type) stopPeriodicSend() {
if p.sendTicker == nil { // Periodic send has not started?
return
}
p.periodicStopNeededChan <- true
<-p.periodicStopFinishedChan
if p.timeoutTimer != nil {
p.timeoutTimer.Stop()
}
p.sendTicker.Stop()
}