kappanhang/statuslog.go
2020-11-05 16:14:38 +01:00

534 lines
10 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
mode string
dataMode string
filter 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
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
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) 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) 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) 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 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, 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 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{
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 !isatty.IsTerminal(os.Stdout.Fd()) && statusLogInterval < time.Second {
statusLogInterval = time.Second
}
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)
}