mirror of
https://github.com/nonoo/kappanhang.git
synced 2025-12-06 08:02:00 +01:00
440 lines
8.6 KiB
Go
440 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/mattn/go-isatty"
|
|
)
|
|
|
|
type statusLogData struct {
|
|
line1 string
|
|
line2 string
|
|
line3 string
|
|
|
|
stateStr string
|
|
frequency uint
|
|
mode string
|
|
dataMode string
|
|
filter string
|
|
preamp string
|
|
vd string
|
|
txPower string
|
|
rfGain string
|
|
s string
|
|
ovf bool
|
|
ts string
|
|
|
|
startTime time.Time
|
|
rttStr string
|
|
|
|
audioMonOn bool
|
|
audioRecOn bool
|
|
audioStateStr string
|
|
}
|
|
|
|
type statusLogStruct struct {
|
|
ticker *time.Ticker
|
|
stopChan chan bool
|
|
stopFinishedChan chan bool
|
|
mutex sync.Mutex
|
|
|
|
preGenerated struct {
|
|
retransmitsColor *color.Color
|
|
lostColor *color.Color
|
|
|
|
stateStr struct {
|
|
unknown string
|
|
rx string
|
|
tx string
|
|
tune string
|
|
}
|
|
audioStateStr struct {
|
|
off string
|
|
monOn string
|
|
rec string
|
|
}
|
|
|
|
ovf string
|
|
}
|
|
|
|
data *statusLogData
|
|
}
|
|
|
|
var statusLog statusLogStruct
|
|
|
|
func (s *statusLogStruct) reportRTTLatency(l time.Duration) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.rttStr = fmt.Sprint(l.Milliseconds())
|
|
}
|
|
|
|
func (s *statusLogStruct) updateAudioStateStr() {
|
|
if s.data.audioRecOn {
|
|
s.data.audioStateStr = s.preGenerated.audioStateStr.rec
|
|
} else if s.data.audioMonOn {
|
|
s.data.audioStateStr = s.preGenerated.audioStateStr.monOn
|
|
} else {
|
|
s.data.audioStateStr = s.preGenerated.audioStateStr.off
|
|
}
|
|
}
|
|
|
|
func (s *statusLogStruct) reportAudioMon(enabled bool) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.audioMonOn = enabled
|
|
s.updateAudioStateStr()
|
|
}
|
|
|
|
func (s *statusLogStruct) reportAudioRec(enabled bool) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.audioRecOn = enabled
|
|
s.updateAudioStateStr()
|
|
}
|
|
|
|
func (s *statusLogStruct) reportFrequency(f uint) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.frequency = f
|
|
}
|
|
|
|
func (s *statusLogStruct) reportMode(mode, filter string) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.mode = mode
|
|
s.data.filter = filter
|
|
}
|
|
|
|
func (s *statusLogStruct) reportDataMode(dataMode, filter string) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.dataMode = dataMode
|
|
if dataMode != "" {
|
|
s.data.filter = filter
|
|
}
|
|
}
|
|
|
|
func (s *statusLogStruct) reportPreamp(preamp int) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.preamp = fmt.Sprint("PAMP", preamp)
|
|
}
|
|
|
|
func (s *statusLogStruct) reportVd(voltage float64) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.vd = fmt.Sprintf("%.1fV", voltage)
|
|
}
|
|
|
|
func (s *statusLogStruct) reportS(sValue string) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.s = sValue
|
|
}
|
|
|
|
func (s *statusLogStruct) reportOVF(ovf bool) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.ovf = ovf
|
|
}
|
|
|
|
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()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
if tune {
|
|
s.data.stateStr = s.preGenerated.stateStr.tune
|
|
} else if ptt {
|
|
s.data.stateStr = s.preGenerated.stateStr.tx
|
|
} else {
|
|
s.data.stateStr = s.preGenerated.stateStr.rx
|
|
}
|
|
}
|
|
|
|
func (s *statusLogStruct) reportTxPower(percent int) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.txPower = fmt.Sprint(percent, "%")
|
|
}
|
|
|
|
func (s *statusLogStruct) reportRFGain(percent int) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.rfGain = fmt.Sprint(percent, "%")
|
|
}
|
|
|
|
func (s *statusLogStruct) clearInternal() {
|
|
fmt.Printf("%c[2K", 27)
|
|
}
|
|
|
|
func (s *statusLogStruct) print() {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.isRealtimeInternal() {
|
|
s.clearInternal()
|
|
fmt.Println(s.data.line1)
|
|
s.clearInternal()
|
|
fmt.Println(s.data.line2)
|
|
s.clearInternal()
|
|
fmt.Printf(s.data.line3+"%c[1A%c[1A", 27, 27)
|
|
} else {
|
|
log.PrintStatusLog(s.data.line3)
|
|
}
|
|
}
|
|
|
|
func (s *statusLogStruct) padLeft(str string, length int) string {
|
|
if !s.isRealtimeInternal() {
|
|
return str
|
|
}
|
|
|
|
for len(str) < length {
|
|
str = " " + str
|
|
}
|
|
return str
|
|
}
|
|
func (s *statusLogStruct) update() {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
var ovfStr string
|
|
if s.data.ovf {
|
|
ovfStr = " " + s.preGenerated.ovf
|
|
}
|
|
var rfGainStr string
|
|
if s.data.rfGain != "" {
|
|
rfGainStr = " rfg " + s.data.rfGain
|
|
}
|
|
s.data.line1 = fmt.Sprint(s.data.s, ovfStr, rfGainStr)
|
|
|
|
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 filterStr string
|
|
if s.data.filter != "" {
|
|
filterStr = " " + s.data.filter
|
|
}
|
|
var preampStr string
|
|
if s.data.preamp != "" {
|
|
preampStr = " " + s.data.preamp
|
|
}
|
|
var vdStr string
|
|
if s.data.vd != "" {
|
|
vdStr = " " + s.data.vd
|
|
}
|
|
var txPowerStr string
|
|
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),
|
|
tsStr, modeStr, filterStr, preampStr, vdStr, txPowerStr, " audio ", s.data.audioStateStr)
|
|
|
|
up, down, lost, retransmits := netstat.get()
|
|
lostStr := "0"
|
|
if lost > 0 {
|
|
lostStr = s.preGenerated.lostColor.Sprint(" ", lost, " ")
|
|
}
|
|
retransmitsStr := "0"
|
|
if retransmits > 0 {
|
|
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")
|
|
|
|
if s.isRealtimeInternal() {
|
|
t := time.Now().Format("2006-01-02T15:04:05.000Z0700")
|
|
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)
|
|
}
|
|
}
|
|
|
|
func (s *statusLogStruct) loop() {
|
|
for {
|
|
select {
|
|
case <-s.ticker.C:
|
|
s.update()
|
|
s.print()
|
|
case <-s.stopChan:
|
|
s.stopFinishedChan <- true
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *statusLogStruct) isRealtimeInternal() bool {
|
|
return statusLogInterval < time.Second
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (s *statusLogStruct) startPeriodicPrint() {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
s.initIfNeeded()
|
|
|
|
s.data = &statusLogData{
|
|
stateStr: s.preGenerated.stateStr.unknown,
|
|
startTime: time.Now(),
|
|
rttStr: "?",
|
|
audioStateStr: s.preGenerated.audioStateStr.off,
|
|
}
|
|
|
|
s.stopChan = make(chan bool)
|
|
s.stopFinishedChan = make(chan bool)
|
|
s.ticker = time.NewTicker(statusLogInterval)
|
|
go s.loop()
|
|
}
|
|
|
|
func (s *statusLogStruct) stopPeriodicPrint() {
|
|
if !s.isActive() {
|
|
return
|
|
}
|
|
s.ticker.Stop()
|
|
s.ticker = nil
|
|
|
|
s.stopChan <- true
|
|
<-s.stopFinishedChan
|
|
|
|
if s.isRealtimeInternal() {
|
|
s.clearInternal()
|
|
fmt.Println()
|
|
s.clearInternal()
|
|
fmt.Println()
|
|
s.clearInternal()
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
func (s *statusLogStruct) initIfNeeded() {
|
|
if s.data != nil { // Already initialized?
|
|
return
|
|
}
|
|
|
|
if !isatty.IsTerminal(os.Stdout.Fd()) && statusLogInterval < time.Second {
|
|
statusLogInterval = time.Second
|
|
}
|
|
|
|
c := color.New(color.FgHiWhite)
|
|
c.Add(color.BgWhite)
|
|
s.preGenerated.stateStr.unknown = c.Sprint(" ?? ")
|
|
s.preGenerated.audioStateStr.off = c.Sprint(" OFF ")
|
|
|
|
c = color.New(color.FgHiWhite)
|
|
c.Add(color.BgGreen)
|
|
s.preGenerated.stateStr.rx = c.Sprint(" RX ")
|
|
s.preGenerated.audioStateStr.monOn = c.Sprint(" MON ")
|
|
|
|
c = color.New(color.FgHiWhite, color.BlinkRapid)
|
|
c.Add(color.BgRed)
|
|
s.preGenerated.stateStr.tx = c.Sprint(" TX ")
|
|
s.preGenerated.stateStr.tune = c.Sprint(" TUNE ")
|
|
s.preGenerated.audioStateStr.rec = c.Sprint(" REC ")
|
|
|
|
c = color.New(color.FgHiWhite)
|
|
c.Add(color.BgRed)
|
|
s.preGenerated.ovf = c.Sprint(" OVF ")
|
|
|
|
s.preGenerated.retransmitsColor = color.New(color.FgHiWhite)
|
|
s.preGenerated.retransmitsColor.Add(color.BgYellow)
|
|
s.preGenerated.lostColor = color.New(color.FgHiWhite)
|
|
s.preGenerated.lostColor.Add(color.BgRed)
|
|
}
|