kappanhang/statuslog.go

534 lines
10 KiB
Go
Raw Normal View History

2020-10-28 18:03:35 +01:00
package main
import (
"fmt"
"os"
2020-10-28 18:03:35 +01:00
"sync"
"time"
"github.com/fatih/color"
"github.com/mattn/go-isatty"
2020-10-28 18:03:35 +01:00
)
2020-10-29 15:11:51 +01:00
type statusLogData struct {
line1 string
line2 string
line3 string
2020-11-05 08:42:12 +01:00
ptt bool
tune bool
frequency uint
mode string
dataMode string
filter string
preamp string
2020-11-05 08:19:16 +01:00
agc string
vd string
txPower string
2020-11-04 09:41:36 +01:00
rfGain string
sql string
2020-11-04 10:01:07 +01:00
nr string
nrEnabled bool
s string
ovf bool
2020-11-04 10:41:12 +01:00
swr string
ts string
2020-10-28 18:03:35 +01:00
startTime time.Time
rttStr string
audioMonOn bool
audioRecOn bool
audioStateStr string
2020-10-29 15:11:51 +01:00
}
type statusLogStruct struct {
ticker *time.Ticker
stopChan chan bool
stopFinishedChan chan bool
mutex sync.Mutex
2020-10-29 15:16:14 +01:00
preGenerated struct {
2020-11-05 08:42:12 +01:00
rxColor *color.Color
retransmitsColor *color.Color
lostColor *color.Color
2020-10-29 15:16:14 +01:00
stateStr struct {
2020-11-05 08:42:12 +01:00
tx string
tune string
2020-10-29 15:16:14 +01:00
}
audioStateStr struct {
off string
monOn string
rec string
}
ovf string
2020-10-29 15:11:51 +01:00
}
data *statusLogData
2020-10-28 18:03:35 +01:00
}
var statusLog statusLogStruct
func (s *statusLogStruct) reportRTTLatency(l time.Duration) {
s.mutex.Lock()
defer s.mutex.Unlock()
2020-10-29 15:11:51 +01:00
if s.data == nil {
return
}
2020-10-29 17:38:51 +01:00
s.data.rttStr = fmt.Sprint(l.Milliseconds())
2020-10-28 18:03:35 +01:00
}
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()
}
2020-11-01 12:46:37 +01:00
func (s *statusLogStruct) reportFrequency(f uint) {
s.mutex.Lock()
defer s.mutex.Unlock()
2020-10-29 15:11:51 +01:00
if s.data == nil {
return
}
s.data.frequency = f
}
func (s *statusLogStruct) reportMode(mode, filter string) {
s.mutex.Lock()
defer s.mutex.Unlock()
2020-10-29 15:11:51 +01:00
if s.data == nil {
return
}
s.data.mode = mode
s.data.filter = filter
}
2020-10-30 22:58:31 +01:00
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
}
}
2020-11-03 16:19:37 +01:00
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)
}
2020-11-05 08:19:16 +01:00
func (s *statusLogStruct) reportAGC(agc string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.agc = "AGC" + agc
}
2020-11-04 10:01:07 +01:00
func (s *statusLogStruct) reportNREnabled(enabled bool) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
s.data.nrEnabled = enabled
}
2020-11-03 16:35:23 +01:00
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)
}
2020-11-03 22:06:51 +01:00
func (s *statusLogStruct) reportS(sValue string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.data == nil {
return
}
2020-11-03 22:06:51 +01:00
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
}
2020-11-04 10:41:12 +01:00
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()
2020-10-29 15:11:51 +01:00
if s.data == nil {
return
}
2020-11-05 08:42:12 +01:00
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, "%")
}
2020-11-04 09:41:36 +01:00
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, "%")
}
2020-11-04 10:01:07 +01:00
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)
}
2020-10-28 18:03:35 +01:00
func (s *statusLogStruct) print() {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.isRealtimeInternal() {
s.clearInternal()
2020-10-29 15:11:51 +01:00
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)
2020-10-28 18:03:35 +01:00
} else {
log.PrintStatusLog(s.data.line3)
2020-10-28 18:03:35 +01:00
}
}
2020-10-29 20:45:56 +01:00
func (s *statusLogStruct) padLeft(str string, length int) string {
2020-10-29 22:16:20 +01:00
if !s.isRealtimeInternal() {
return str
}
2020-10-29 20:45:56 +01:00
for len(str) < length {
str = " " + str
}
return str
}
2020-11-04 21:36:33 +01:00
func (s *statusLogStruct) padRight(str string, length int) string {
if !s.isRealtimeInternal() {
return str
}
for len(str) < length {
str += " "
}
return str
}
2020-10-28 18:03:35 +01:00
func (s *statusLogStruct) update() {
s.mutex.Lock()
defer s.mutex.Unlock()
2020-11-05 08:42:12 +01:00
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
}
2020-11-04 09:41:36 +01:00
var rfGainStr string
if s.data.rfGain != "" {
rfGainStr = " rfg " + s.data.rfGain
}
var sqlStr string
if s.data.sql != "" {
sqlStr = " sql " + s.data.sql
}
2020-11-04 10:01:07 +01:00
var nrStr string
if s.data.nr != "" {
nrStr = " nr "
if s.data.nrEnabled {
nrStr += s.data.nr
} else {
nrStr += "-"
}
}
2020-11-05 08:42:12 +01:00
s.data.line1 = fmt.Sprint(s.data.audioStateStr, filterStr, preampStr, agcStr, rfGainStr, sqlStr, nrStr)
2020-11-05 08:42:12 +01:00
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
}
2020-11-05 10:41:27 +01:00
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
2020-11-05 08:42:12 +01:00
}
var tsStr string
if s.data.ts != "" {
tsStr = " " + s.data.ts
}
var modeStr string
2020-10-29 15:11:51 +01:00
if s.data.mode != "" {
2020-10-30 22:58:31 +01:00
modeStr = " " + s.data.mode + s.data.dataMode
}
2020-11-03 16:35:23 +01:00
var vdStr string
if s.data.vd != "" {
vdStr = " " + s.data.vd
}
var txPowerStr string
if s.data.txPower != "" {
txPowerStr = " txpwr " + s.data.txPower
}
2020-11-04 10:41:12 +01:00
var swrStr string
if s.data.swr != "" {
swrStr = " swr " + s.data.swr
}
2020-11-05 08:42:12 +01:00
s.data.line2 = fmt.Sprint(stateStr, " ", fmt.Sprintf("%.6f", float64(s.data.frequency)/1000000),
tsStr, modeStr, vdStr, txPowerStr, swrStr)
2020-10-28 18:03:35 +01:00
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),
2020-10-29 20:45:56 +01:00
" 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")
2020-10-28 18:03:35 +01:00
if s.isRealtimeInternal() {
t := time.Now().Format("2006-01-02T15:04:05.000Z0700")
2020-10-29 15:11:51 +01:00
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)
2020-10-28 18:03:35 +01:00
}
}
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()
2020-10-29 15:11:51 +01:00
s.initIfNeeded()
2020-10-29 15:11:51 +01:00
s.data = &statusLogData{
2020-11-05 08:42:12 +01:00
s: "S0",
startTime: time.Now(),
rttStr: "?",
audioStateStr: s.preGenerated.audioStateStr.off,
2020-10-29 15:11:51 +01:00
}
2020-10-28 18:03:35 +01:00
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
2020-10-29 15:21:46 +01:00
if s.isRealtimeInternal() {
s.clearInternal()
fmt.Println()
s.clearInternal()
fmt.Println()
s.clearInternal()
2020-10-29 15:21:46 +01:00
fmt.Println()
}
2020-10-28 18:03:35 +01:00
}
2020-10-29 15:11:51 +01:00
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)
2020-11-05 08:42:12 +01:00
s.preGenerated.audioStateStr.off = c.Sprint(" MON ")
2020-11-05 08:42:12 +01:00
s.preGenerated.rxColor = color.New(color.FgHiWhite)
s.preGenerated.rxColor.Add(color.BgGreen)
s.preGenerated.audioStateStr.monOn = s.preGenerated.rxColor.Sprint(" MON ")
2020-10-29 15:11:51 +01:00
c = color.New(color.FgHiWhite, color.BlinkRapid)
c.Add(color.BgRed)
2020-11-05 08:42:12 +01:00
s.preGenerated.stateStr.tx = c.Sprint(" TX ")
s.preGenerated.stateStr.tune = c.Sprint(" TUNE ")
s.preGenerated.audioStateStr.rec = c.Sprint(" REC ")
2020-10-29 15:11:51 +01:00
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)
2020-10-29 15:11:51 +01:00
}