mirror of
https://github.com/nonoo/kappanhang.git
synced 2026-02-01 05:14:37 +01:00
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!"
This commit is contained in:
parent
66f2369efc
commit
7656d4335b
46
args.go
46
args.go
|
|
@ -10,19 +10,23 @@ import (
|
|||
"github.com/pborman/getopt"
|
||||
)
|
||||
|
||||
var verboseLog bool
|
||||
var quietLog bool
|
||||
var connectAddress string
|
||||
var username string
|
||||
var password string
|
||||
var civAddress byte
|
||||
var serialTCPPort uint16
|
||||
var enableSerialDevice bool
|
||||
var rigctldPort uint16
|
||||
var runCmd string
|
||||
var runCmdOnSerialPortCreated string
|
||||
var statusLogInterval time.Duration
|
||||
var setDataModeOnTx bool
|
||||
var (
|
||||
verboseLog bool
|
||||
quietLog bool
|
||||
connectAddress string
|
||||
username string
|
||||
password string
|
||||
civAddress byte
|
||||
controllerAddress byte
|
||||
serialTCPPort uint16
|
||||
enableSerialDevice bool
|
||||
rigctldPort uint16
|
||||
runCmd string
|
||||
runCmdOnSerialPortCreated string
|
||||
statusLogInterval time.Duration
|
||||
setDataModeOnTx bool
|
||||
debugPackets bool
|
||||
)
|
||||
|
||||
func parseArgs() {
|
||||
h := getopt.BoolLong("help", 'h', "display help")
|
||||
|
|
@ -31,14 +35,16 @@ func parseArgs() {
|
|||
a := getopt.StringLong("address", 'a', "IC-705", "Connect to address")
|
||||
u := getopt.StringLong("username", 'u', "beer", "Username")
|
||||
p := getopt.StringLong("password", 'p', "beerbeer", "Password")
|
||||
c := getopt.StringLong("civ-address", 'c', "0xa4", "CI-V address")
|
||||
c := getopt.StringLong("civ-address", 'c', "0xa4", "CI-V address for radio")
|
||||
t := getopt.Uint16Long("serial-tcp-port", 't', 4531, "Expose radio's serial port on this TCP port")
|
||||
s := getopt.BoolLong("enable-serial-device", 's', "Expose radio's serial port as a virtual serial port")
|
||||
r := getopt.Uint16Long("rigctld-port", 'r', 4532, "Use this TCP port for the internal rigctld")
|
||||
e := getopt.StringLong("exec", 'e', "", "Exec cmd when connected")
|
||||
o := getopt.StringLong("exec-serial", 'o', "socat /tmp/kappanhang-IC-705.pty /tmp/vmware.pty", "Exec cmd when virtual serial port is created, set to - to disable")
|
||||
i := getopt.Uint16Long("log-interval", 'i', 100, "Status bar/log interval in milliseconds")
|
||||
i := getopt.Uint16Long("log-interval", 'i', 150, "Status bar/log interval in milliseconds")
|
||||
d := getopt.BoolLong("set-data-tx", 'd', "Automatically enable data mode on TX")
|
||||
dp := getopt.BoolLong("debug-packets", 'D', "Show CI-V packets for debugging")
|
||||
ca := getopt.StringLong("controller-address", 'z', "0xe0", "Controller address")
|
||||
|
||||
getopt.Parse()
|
||||
|
||||
|
|
@ -63,6 +69,15 @@ func parseArgs() {
|
|||
}
|
||||
civAddress = byte(civAddressInt)
|
||||
|
||||
*ca = strings.Replace(*ca, "0x", "", -1)
|
||||
*ca = strings.Replace(*ca, "0X", "", -1)
|
||||
controllerAddressInt, err := strconv.ParseInt(*ca, 16, 64)
|
||||
if err != nil {
|
||||
fmt.Println("invalid CI-V address for controller: can't parse", *ca)
|
||||
os.Exit(1)
|
||||
}
|
||||
controllerAddress = byte(controllerAddressInt)
|
||||
|
||||
serialTCPPort = *t
|
||||
enableSerialDevice = *s
|
||||
rigctldPort = *r
|
||||
|
|
@ -70,4 +85,5 @@ func parseArgs() {
|
|||
runCmdOnSerialPortCreated = *o
|
||||
statusLogInterval = time.Duration(*i) * time.Millisecond
|
||||
setDataModeOnTx = *d
|
||||
debugPackets = *dp
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package main
|
||||
|
|
@ -392,6 +393,10 @@ func (a *audioStruct) loop() {
|
|||
|
||||
// We only init the audio once, with the first device name we acquire, so apps using the virtual sound card
|
||||
// won't have issues with the interface going down while the app is running.
|
||||
//
|
||||
// ad8im note: but this seems like it leaves stale virtuals around indefinitely in some cases
|
||||
//
|
||||
// so it may be desirable to enable force cleanup and recreate via flags
|
||||
func (a *audioStruct) initIfNeeded(devName string) error {
|
||||
a.devName = devName
|
||||
bufferSizeInBits := (audioSampleRate * audioSampleBytes * 8) / 1000 * pulseAudioBufferLength.Milliseconds()
|
||||
|
|
@ -446,7 +451,10 @@ func (a *audioStruct) initIfNeeded(devName string) error {
|
|||
}
|
||||
|
||||
if a.virtualSoundcardStream.playBuf == nil {
|
||||
log.Print("opened device " + a.virtualSoundcardStream.source.Name)
|
||||
//origina line// log.Print("opened device '" + a.virtualSoundcardStream.source.Name + "'")
|
||||
log.Print("opened virtual sound card device")
|
||||
log.Print(" name - source:'" + a.virtualSoundcardStream.source.Name + "' sink:'" + a.virtualSoundcardStream.sink.Name + "'")
|
||||
log.Print(" location - source:'" + a.virtualSoundcardStream.source.Filename + "' sink:'" + a.virtualSoundcardStream.sink.Filename + "'")
|
||||
|
||||
a.play = make(chan []byte)
|
||||
a.rec = make(chan []byte)
|
||||
|
|
|
|||
|
|
@ -88,25 +88,8 @@ func (s *audioStream) handleRxSeqBufEntry(e seqBufEntry) {
|
|||
audio.play <- e.data
|
||||
}
|
||||
|
||||
// var drop int
|
||||
|
||||
func (s *audioStream) handleAudioPacket(r []byte) error {
|
||||
gotSeq := binary.LittleEndian.Uint16(r[6:8])
|
||||
|
||||
// if drop == 0 && time.Now().UnixNano()%10 == 0 {
|
||||
// log.Print("drop start - ", gotSeq)
|
||||
// drop = 1
|
||||
// return nil
|
||||
// } else if drop > 0 {
|
||||
// drop++
|
||||
// if drop >= int(time.Now().UnixNano()%10) {
|
||||
// log.Print("drop stop - ", gotSeq)
|
||||
// drop = 0
|
||||
// } else {
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
|
||||
if s.timeoutTimer != nil {
|
||||
s.timeoutTimer.Stop()
|
||||
s.timeoutTimer.Reset(audioTimeoutDuration)
|
||||
|
|
|
|||
728
civcontrol.go
728
civcontrol.go
File diff suppressed because it is too large
Load diff
1
go.mod
1
go.mod
|
|
@ -11,4 +11,5 @@ require (
|
|||
github.com/pborman/getopt v1.1.0
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.16.0
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529
|
||||
)
|
||||
|
|
|
|||
3
go.sum
3
go.sum
|
|
@ -34,11 +34,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
|
|
@ -46,6 +44,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
|
|||
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
|
||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
|
|
|
|||
11
hotkeys.go
11
hotkeys.go
|
|
@ -4,12 +4,15 @@ import "fmt"
|
|||
|
||||
func handleHotkey(k byte) {
|
||||
switch k {
|
||||
case 'c':
|
||||
// provide a way to clear the screen since sometimes the stack of errors gets to be rather distracting
|
||||
fmt.Printf("%v", termDetail.eraseScreen)
|
||||
case 'l':
|
||||
audio.togglePlaybackToDefaultSoundcard()
|
||||
case ' ':
|
||||
audio.toggleRecFromDefaultSoundcard()
|
||||
case 't':
|
||||
if err := civControl.toggleTune(); err != nil {
|
||||
if err := civControl.toggleAntennaTuner(); err != nil {
|
||||
log.Error("can't toggle tune: ", err)
|
||||
}
|
||||
case '+':
|
||||
|
|
@ -137,11 +140,11 @@ func handleHotkey(k byte) {
|
|||
log.Error("can't decrease freq: ", err)
|
||||
}
|
||||
case '}':
|
||||
if err := civControl.incTS(); err != nil {
|
||||
if err := civControl.incTuningStep(); err != nil {
|
||||
log.Error("can't increase ts: ", err)
|
||||
}
|
||||
case '{':
|
||||
if err := civControl.decTS(); err != nil {
|
||||
if err := civControl.decTuningStep(); err != nil {
|
||||
log.Error("can't decrease ts: ", err)
|
||||
}
|
||||
case 'm':
|
||||
|
|
@ -191,7 +194,7 @@ func handleHotkey(k byte) {
|
|||
case '\n':
|
||||
if statusLog.isRealtime() {
|
||||
statusLog.mutex.Lock()
|
||||
statusLog.clearInternal()
|
||||
statusLog.clearStatusLine()
|
||||
fmt.Println()
|
||||
statusLog.mutex.Unlock()
|
||||
statusLog.print()
|
||||
|
|
|
|||
8
log.go
8
log.go
|
|
@ -29,7 +29,7 @@ func (l *logger) GetCallerFileName(withLine bool) string {
|
|||
func (l *logger) Print(a ...interface{}) {
|
||||
if statusLog.isRealtime() {
|
||||
statusLog.mutex.Lock()
|
||||
statusLog.clearInternal()
|
||||
statusLog.clearStatusLine()
|
||||
defer func() {
|
||||
statusLog.mutex.Unlock()
|
||||
statusLog.print()
|
||||
|
|
@ -45,7 +45,7 @@ func (l *logger) PrintStatusLog(a ...interface{}) {
|
|||
func (l *logger) Debug(a ...interface{}) {
|
||||
if statusLog.isRealtime() {
|
||||
statusLog.mutex.Lock()
|
||||
statusLog.clearInternal()
|
||||
statusLog.clearStatusLine()
|
||||
defer func() {
|
||||
statusLog.mutex.Unlock()
|
||||
statusLog.print()
|
||||
|
|
@ -57,7 +57,7 @@ func (l *logger) Debug(a ...interface{}) {
|
|||
func (l *logger) Error(a ...interface{}) {
|
||||
if statusLog.isRealtime() {
|
||||
statusLog.mutex.Lock()
|
||||
statusLog.clearInternal()
|
||||
statusLog.clearStatusLine()
|
||||
defer func() {
|
||||
statusLog.mutex.Unlock()
|
||||
statusLog.print()
|
||||
|
|
@ -69,7 +69,7 @@ func (l *logger) Error(a ...interface{}) {
|
|||
func (l *logger) ErrorC(a ...interface{}) {
|
||||
if statusLog.isRealtime() {
|
||||
statusLog.mutex.Lock()
|
||||
statusLog.clearInternal()
|
||||
statusLog.clearStatusLine()
|
||||
defer func() {
|
||||
statusLog.mutex.Unlock()
|
||||
statusLog.print()
|
||||
|
|
|
|||
10
main.go
10
main.go
|
|
@ -24,7 +24,10 @@ func getAboutStr() string {
|
|||
} else {
|
||||
v = "(devel)"
|
||||
}
|
||||
return "kappanhang " + v + " by Norbert Varga HA2NON and Akos Marton ES1AKOS https://github.com/nonoo/kappanhang"
|
||||
about_msg := "orignal kappanhang " + v + " by Norbert Varga HA2NON and Akos Marton ES1AKOS https://github.com/nonoo/kappanhang"
|
||||
about_msg += "\n\tthis version produced by AD8IM and can be fuond at http://github.com/AD8IM/kappanhang"
|
||||
|
||||
return about_msg
|
||||
}
|
||||
|
||||
func wait(d time.Duration, osSignal chan os.Signal) (shouldExit bool) {
|
||||
|
|
@ -54,7 +57,6 @@ func runControlStream(osSignal chan os.Signal) (requireWait, shouldExit bool, ex
|
|||
}
|
||||
|
||||
ctrl := &controlStream{}
|
||||
|
||||
if err := ctrl.init(); err != nil {
|
||||
log.Error(err)
|
||||
ctrl.deinit()
|
||||
|
|
@ -65,8 +67,8 @@ func runControlStream(osSignal chan os.Signal) (requireWait, shouldExit bool, ex
|
|||
}
|
||||
|
||||
select {
|
||||
// Need to wait before reinit because the IC-705 will disconnect our audio stream eventually if we relogin
|
||||
// in a too short interval without a deauth...
|
||||
// Need to wait before reinit because the IC-705 will disconnect our audio stream eventually
|
||||
// if we relogin in a too short interval without a deauth...
|
||||
case requireWait = <-gotErrChan:
|
||||
ctrl.deinit()
|
||||
return
|
||||
|
|
|
|||
16
pkt7.go
16
pkt7.go
|
|
@ -30,16 +30,24 @@ type pkt7Type struct {
|
|||
var controlStreamLatency time.Duration
|
||||
|
||||
func (p *pkt7Type) isPkt7(r []byte) bool {
|
||||
return len(r) == 21 && bytes.Equal(r[1:6], []byte{0x00, 0x00, 0x00, 0x07, 0x00}) // Note that the first byte can be 0x15 or 0x00, so we ignore that.
|
||||
// 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
|
||||
if p.sendTicker != nil { // Only replying if the auth is already done.
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type serialStream struct {
|
|||
deinitFinishedChan chan bool
|
||||
}
|
||||
|
||||
// load the data into a full packet and send it
|
||||
func (s *serialStream) send(d []byte) error {
|
||||
l := byte(len(d))
|
||||
p := append([]byte{0x15 + l, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
|
@ -92,6 +93,9 @@ func (s *serialStream) handleRxSeqBufEntry(e seqBufEntry) {
|
|||
|
||||
e.data = e.data[21:]
|
||||
|
||||
// decode the received CI-V data packet
|
||||
// if it fails return directly to the main polling loop, without sending it on to the serial &/or network channels
|
||||
|
||||
if !civControl.decode(e.data) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
138
statuslog.go
138
statuslog.go
|
|
@ -3,11 +3,13 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/mattn/go-isatty"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
type statusLogData struct {
|
||||
|
|
@ -76,7 +78,34 @@ type statusLogStruct struct {
|
|||
data *statusLogData
|
||||
}
|
||||
|
||||
type termAspects struct {
|
||||
cols int
|
||||
rows int
|
||||
cursorLeft string
|
||||
cursorRight string
|
||||
cursorUp string
|
||||
cursorDown string
|
||||
eraseLine string
|
||||
eraseScreen string
|
||||
}
|
||||
|
||||
var statusLog statusLogStruct
|
||||
var termDetail = termAspects{
|
||||
cols: 0,
|
||||
rows: 0,
|
||||
cursorUp: fmt.Sprintf("%c[1A", 0x1b),
|
||||
cursorDown: fmt.Sprintf("%c[1B", 0x1b),
|
||||
cursorRight: fmt.Sprintf("%c[1C", 0x1b),
|
||||
cursorLeft: fmt.Sprintf("%c[1D", 0x1b),
|
||||
eraseLine: fmt.Sprintf("%c[2K", 0x1b),
|
||||
eraseScreen: fmt.Sprintf("%c[2J", 0x1b),
|
||||
}
|
||||
|
||||
var upArrow = "\u21d1"
|
||||
var downArrow = "\u21d3"
|
||||
|
||||
// var roundTripArrow = "\u2b6f\u200a" // widdershin circle w/arrow
|
||||
var roundTripArrow = "\u2b8c\u200a" // out and back arrow
|
||||
|
||||
func (s *statusLogStruct) reportRTTLatency(l time.Duration) {
|
||||
s.mutex.Lock()
|
||||
|
|
@ -242,14 +271,14 @@ func (s *statusLogStruct) reportSWR(swr float64) {
|
|||
s.data.swr = fmt.Sprintf("%.1f", swr)
|
||||
}
|
||||
|
||||
func (s *statusLogStruct) reportTS(ts uint) {
|
||||
func (s *statusLogStruct) reportTuningStep(ts uint) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.data == nil {
|
||||
return
|
||||
}
|
||||
s.data.ts = "TS"
|
||||
s.data.ts = "TS: "
|
||||
if ts >= 1000 {
|
||||
if ts%1000 == 0 {
|
||||
s.data.ts += fmt.Sprintf("%.0fk", float64(ts)/1000)
|
||||
|
|
@ -329,8 +358,8 @@ func (s *statusLogStruct) reportSplit(mode splitMode, split string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *statusLogStruct) clearInternal() {
|
||||
fmt.Printf("%c[2K", 27)
|
||||
func (s *statusLogStruct) clearStatusLine() {
|
||||
fmt.Print(termDetail.eraseLine)
|
||||
}
|
||||
|
||||
func (s *statusLogStruct) print() {
|
||||
|
|
@ -338,12 +367,12 @@ func (s *statusLogStruct) print() {
|
|||
defer s.mutex.Unlock()
|
||||
|
||||
if s.isRealtimeInternal() {
|
||||
s.clearInternal()
|
||||
s.clearStatusLine()
|
||||
fmt.Println(s.data.line1)
|
||||
s.clearInternal()
|
||||
s.clearStatusLine()
|
||||
fmt.Println(s.data.line2)
|
||||
s.clearInternal()
|
||||
fmt.Printf(s.data.line3+"%c[1A%c[1A", 27, 27)
|
||||
s.clearStatusLine()
|
||||
fmt.Printf(s.data.line3+"%v%v", termDetail.cursorUp, termDetail.cursorUp)
|
||||
} else {
|
||||
log.PrintStatusLog(s.data.line3)
|
||||
}
|
||||
|
|
@ -353,9 +382,8 @@ func (s *statusLogStruct) padLeft(str string, length int) string {
|
|||
if !s.isRealtimeInternal() {
|
||||
return str
|
||||
}
|
||||
|
||||
for len(str) < length {
|
||||
str = " " + str
|
||||
if length-len(str) > 0 {
|
||||
str = strings.Repeat(" ", length-len(str)) + str
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
|
@ -364,9 +392,8 @@ func (s *statusLogStruct) padRight(str string, length int) string {
|
|||
if !s.isRealtimeInternal() {
|
||||
return str
|
||||
}
|
||||
|
||||
for len(str) < length {
|
||||
str += " "
|
||||
if length-len(str) > 0 {
|
||||
str += strings.Repeat(" ", length-len(str))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
|
@ -375,19 +402,34 @@ func (s *statusLogStruct) update() {
|
|||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
var filterStr string
|
||||
var (
|
||||
filterStr string
|
||||
preampStr string
|
||||
agcStr string
|
||||
nrStr string
|
||||
rfGainStr string
|
||||
sqlStr string
|
||||
stateStr string
|
||||
tsStr string
|
||||
modeStr string
|
||||
vdStr string
|
||||
txPowerStr string
|
||||
splitStr string
|
||||
swrStr string
|
||||
)
|
||||
|
||||
if s.data.filter != "" {
|
||||
filterStr = " " + s.data.filter
|
||||
}
|
||||
var preampStr string
|
||||
|
||||
if s.data.preamp != "" {
|
||||
preampStr = " " + s.data.preamp
|
||||
}
|
||||
var agcStr string
|
||||
|
||||
if s.data.agc != "" {
|
||||
agcStr = " " + s.data.agc
|
||||
}
|
||||
var nrStr string
|
||||
|
||||
if s.data.nr != "" {
|
||||
nrStr = " NR"
|
||||
if s.data.nrEnabled {
|
||||
|
|
@ -396,17 +438,16 @@ func (s *statusLogStruct) update() {
|
|||
nrStr += "-"
|
||||
}
|
||||
}
|
||||
var rfGainStr string
|
||||
|
||||
if s.data.rfGain != "" {
|
||||
rfGainStr = " rfg " + s.data.rfGain
|
||||
}
|
||||
var sqlStr string
|
||||
|
||||
if s.data.sql != "" {
|
||||
sqlStr = " sql " + s.data.sql
|
||||
}
|
||||
s.data.line1 = fmt.Sprint(s.data.audioStateStr, filterStr, preampStr, agcStr, nrStr, rfGainStr, sqlStr)
|
||||
|
||||
var stateStr string
|
||||
if s.data.tune {
|
||||
stateStr = s.preGenerated.stateStr.tune
|
||||
} else if s.data.ptt {
|
||||
|
|
@ -417,29 +458,29 @@ func (s *statusLogStruct) update() {
|
|||
ovfStr = s.preGenerated.ovf
|
||||
}
|
||||
if len(s.data.s) <= 2 {
|
||||
stateStr = s.preGenerated.rxColor.Sprint(" " + s.padRight(s.data.s, 4) + " ")
|
||||
stateStr = s.preGenerated.rxColor.Sprintf(" %v ", s.padRight(s.data.s, 4))
|
||||
} else {
|
||||
stateStr = s.preGenerated.rxColor.Sprint(" " + s.padRight(s.data.s, 5) + " ")
|
||||
stateStr = s.preGenerated.rxColor.Sprintf(" %v ", s.padRight(s.data.s, 5))
|
||||
}
|
||||
stateStr += ovfStr
|
||||
}
|
||||
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
|
||||
}
|
||||
var vdStr string
|
||||
|
||||
if s.data.vd != "" {
|
||||
vdStr = " " + s.data.vd
|
||||
}
|
||||
var txPowerStr string
|
||||
|
||||
if s.data.txPower != "" {
|
||||
txPowerStr = " txpwr " + s.data.txPower
|
||||
}
|
||||
var splitStr string
|
||||
|
||||
if s.data.split != "" {
|
||||
splitStr = " " + s.data.split
|
||||
if s.data.splitMode == splitModeOn {
|
||||
|
|
@ -447,7 +488,7 @@ func (s *statusLogStruct) update() {
|
|||
s.data.subMode, s.data.subDataMode, s.data.subFilter)
|
||||
}
|
||||
}
|
||||
var swrStr string
|
||||
|
||||
if (s.data.tune || s.data.ptt) && s.data.swr != "" {
|
||||
swrStr = " SWR" + s.data.swr
|
||||
}
|
||||
|
|
@ -464,13 +505,17 @@ func (s *statusLogStruct) update() {
|
|||
retransmitsStr = s.preGenerated.retransmitsColor.Sprint(" ", retransmits, " ")
|
||||
}
|
||||
|
||||
s.data.line3 = fmt.Sprint("up ", s.padLeft(fmt.Sprint(time.Since(s.data.startTime).Round(time.Second)), 6),
|
||||
" rtt ", s.padLeft(s.data.rttStr, 3), "ms up ",
|
||||
s.padLeft(netstat.formatByteCount(up), 8), "/s down ",
|
||||
s.padLeft(netstat.formatByteCount(down), 8), "/s retx ", retransmitsStr, "/1m lost ", lostStr, "/1m\r")
|
||||
s.data.line3 = fmt.Sprint(
|
||||
" [", s.padLeft(netstat.formatByteCount(up), 8), "/s "+upArrow+"] ",
|
||||
" [", s.padLeft(netstat.formatByteCount(down), 8), "/s "+downArrow+"] ",
|
||||
" [", s.padLeft(s.data.rttStr, 3), "ms "+roundTripArrow+"] ",
|
||||
" re-Tx ", retransmitsStr, "/1m lost ", lostStr, "/1m",
|
||||
" - uptime: ", s.padLeft(fmt.Sprint(time.Since(s.data.startTime).Round(time.Second)), 6),
|
||||
"\r")
|
||||
|
||||
if s.isRealtimeInternal() {
|
||||
t := time.Now().Format("2006-01-02T15:04:05.000Z0700")
|
||||
//t := time.Now().Format("2006-01-02T15:04:05.000Z0700") // this is visually busy with no real benefit
|
||||
t := time.Now().Format("2006-01-02T15:04:05 Z0700")
|
||||
s.data.line1 = fmt.Sprint(t, " ", s.data.line1)
|
||||
s.data.line2 = fmt.Sprint(t, " ", s.data.line2)
|
||||
s.data.line3 = fmt.Sprint(t, " ", s.data.line3)
|
||||
|
|
@ -497,14 +542,12 @@ func (s *statusLogStruct) isRealtimeInternal() bool {
|
|||
func (s *statusLogStruct) isRealtime() bool {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
return s.ticker != nil && s.isRealtimeInternal()
|
||||
}
|
||||
|
||||
func (s *statusLogStruct) isActive() bool {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
return s.ticker != nil
|
||||
}
|
||||
|
||||
|
|
@ -527,6 +570,7 @@ func (s *statusLogStruct) startPeriodicPrint() {
|
|||
go s.loop()
|
||||
}
|
||||
|
||||
// stop the update timer and clear the status rows... but not any error/info that may have been printed
|
||||
func (s *statusLogStruct) stopPeriodicPrint() {
|
||||
if !s.isActive() {
|
||||
return
|
||||
|
|
@ -538,12 +582,11 @@ func (s *statusLogStruct) stopPeriodicPrint() {
|
|||
<-s.stopFinishedChan
|
||||
|
||||
if s.isRealtimeInternal() {
|
||||
s.clearInternal()
|
||||
fmt.Println()
|
||||
s.clearInternal()
|
||||
fmt.Println()
|
||||
s.clearInternal()
|
||||
fmt.Println()
|
||||
statusRows := 3 // AD8IM NOTE: I intend to adjust this in the future to be dynamic, eg more rows when terminal is narrow
|
||||
for i := 0; i < statusRows; i++ {
|
||||
s.clearStatusLine()
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -558,6 +601,17 @@ func (s *statusLogStruct) initIfNeeded() {
|
|||
keyboard.init()
|
||||
}
|
||||
|
||||
cols, rows, err := terminal.GetSize(int(os.Stdout.Fd()))
|
||||
if err == nil {
|
||||
termDetail.cols = cols
|
||||
termDetail.rows = rows
|
||||
}
|
||||
|
||||
// consider doing this with a nice looking start up screen too
|
||||
// what'd be kinda useful would be a nice map of the hotkeys
|
||||
vertWhitespace := strings.Repeat(termDetail.cursorDown, rows-10)
|
||||
fmt.Printf("%v%v", termDetail.eraseScreen, vertWhitespace)
|
||||
|
||||
c := color.New(color.FgHiWhite)
|
||||
c.Add(color.BgWhite)
|
||||
s.preGenerated.audioStateStr.off = c.Sprint(" MON ")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import "time"
|
|||
|
||||
// This value is sent to the transceiver and - according to my observations - it will use
|
||||
// this as it's RX buf length. Note that if it is set to larger than 500-600ms then audio TX
|
||||
// won't work (small radio memory?)
|
||||
// won't work (small radio memory?) - HA2NON
|
||||
const txSeqBufLength = 300 * time.Millisecond
|
||||
|
||||
type txSeqBufEntry struct {
|
||||
|
|
@ -27,8 +27,15 @@ func (s *txSeqBufStruct) add(seq seqNum, p []byte) {
|
|||
}
|
||||
|
||||
func (s *txSeqBufStruct) purgeOldEntries() {
|
||||
// previous comment:
|
||||
// We keep much more entries than the specified length of the TX seqbuf, so we can serve
|
||||
// any requests coming from the server.
|
||||
//
|
||||
// NOTE: need to wade deeper through the entire code base and see if the following is a more useful comment, or we need a better "why" here:
|
||||
//
|
||||
// Prune the oldest item in the txSeqBuf if it's older than ten time the txSeqBuf length... so "about 1 second"
|
||||
// this enables serving any requests from the server, even if older than the max... up to a point
|
||||
|
||||
for len(s.entries) > 0 && time.Since(s.entries[0].addedAt) > txSeqBufLength*10 {
|
||||
s.entries = s.entries[1:]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue