kappanhang/statuslog.go

586 lines
11 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
ptt bool
tune bool
frequency uint
subFrequency uint
mode string
dataMode string
filter string
subMode string
subDataMode string
subFilter string
preamp string
agc string
vd string
txPower string
rfGain string
sql string
nr string
nrEnabled bool
s string
ovf bool
swr string
ts string
split string
splitMode splitMode
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 {
rxColor *color.Color
retransmitsColor *color.Color
lostColor *color.Color
splitColor *color.Color
stateStr struct {
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) reportSubFrequency(f uint) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.subFrequency = f
}
func (s *statusLogStruct) reportMode(mode string, dataMode bool, filter string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.mode = mode
if dataMode {
s.data.dataMode = "-D"
} else {
s.data.dataMode = ""
}
s.data.filter = filter
}
func (s *statusLogStruct) reportSubMode(mode string, dataMode bool, filter string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.subMode = mode
if dataMode {
s.data.subDataMode = "-D"
} else {
s.data.subDataMode = ""
}
s.data.subFilter = 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) reportAGC(agc string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.agc = "AGC" + agc
}
func (s *statusLogStruct) reportNREnabled(enabled bool) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.nrEnabled = enabled
}
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) reportSWR(swr float64) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.swr = fmt.Sprintf("%.1f", swr)
}
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
}
s.data.tune = tune
s.data.ptt = ptt
}
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) reportSQL(percent int) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.sql = fmt.Sprint(percent, "%")
}
func (s *statusLogStruct) reportNR(percent int) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.nr = fmt.Sprint(percent, "%")
}
func (s *statusLogStruct) reportSplit(mode splitMode, split string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.splitMode = mode
if split == "" {
s.data.split = ""
} else {
s.data.split = s.preGenerated.splitColor.Sprint(split)
}
}
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) padRight(str string, length int) string {
if !s.isRealtimeInternal() {
return str
}
for len(str) < length {
str += " "
}
return str
}
func (s *statusLogStruct) update() {
s.mutex.Lock()
defer s.mutex.Unlock()
var filterStr 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 {
nrStr += s.data.nr
} else {
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 {
stateStr = s.preGenerated.stateStr.tx
} else {
var ovfStr string
if s.data.ovf {
ovfStr = s.preGenerated.ovf
}
if len(s.data.s) <= 2 {
stateStr = s.preGenerated.rxColor.Sprint(" " + s.padRight(s.data.s, 4) + " ")
} else {
stateStr = s.preGenerated.rxColor.Sprint(" " + 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 {
splitStr += fmt.Sprintf("/%.6f/%s%s/%s", float64(s.data.subFrequency)/1000000,
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
}
s.data.line2 = fmt.Sprint(stateStr, " ", fmt.Sprintf("%.6f", float64(s.data.frequency)/1000000),
tsStr, modeStr, splitStr, vdStr, txPowerStr, swrStr)
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 keyboard.initialized
}
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{
s: "S0",
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 quietLog || (!isatty.IsTerminal(os.Stdout.Fd()) && statusLogInterval < time.Second) {
statusLogInterval = time.Second
} else {
keyboard.init()
}
c := color.New(color.FgHiWhite)
c.Add(color.BgWhite)
s.preGenerated.audioStateStr.off = c.Sprint(" MON ")
s.preGenerated.rxColor = color.New(color.FgHiWhite)
s.preGenerated.rxColor.Add(color.BgGreen)
s.preGenerated.audioStateStr.monOn = s.preGenerated.rxColor.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)
s.preGenerated.splitColor = color.New(color.FgHiMagenta)
}