2024-09-30 18:56:45 +00:00
|
|
|
package pages
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"sync"
|
2024-10-06 18:31:23 +00:00
|
|
|
"sync/atomic"
|
2024-09-30 18:56:45 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"gitea.unprism.ru/KRBL/mpu/mpu"
|
|
|
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
|
|
|
|
"gitea.unprism.ru/yotia/display-test/components"
|
|
|
|
"gitea.unprism.ru/yotia/display-test/drawer"
|
|
|
|
)
|
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
/*
|
|
|
|
Status page has status line at the top and three lines with primary data below.
|
|
|
|
Status line shows time and signal and service icon at the right corner.
|
|
|
|
|
|
|
|
Main space can show several screens:
|
|
|
|
1) GPS
|
|
|
|
GPS coordinates
|
|
|
|
2) System state
|
|
|
|
temepature
|
|
|
|
speed
|
|
|
|
may be amount of cameras
|
|
|
|
|
|
|
|
Other pages can show for ex important logs.
|
|
|
|
*/
|
2024-09-30 18:56:45 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
timeLayout = "01.09.06 15:04:05"
|
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
screenN = 2
|
|
|
|
screenChangeTimeout = 6 * time.Second
|
|
|
|
)
|
2024-10-06 17:20:22 +00:00
|
|
|
|
2024-09-30 18:56:45 +00:00
|
|
|
type statusPage struct {
|
|
|
|
drawer drawer.Drawer // Drawer with dysplay
|
|
|
|
|
|
|
|
// Status data
|
|
|
|
st systemStatus
|
|
|
|
|
|
|
|
timeShift time.Duration
|
|
|
|
|
|
|
|
// Visual components
|
|
|
|
line0 components.Text
|
|
|
|
line1 components.Text
|
|
|
|
line2 components.Text
|
|
|
|
line3 components.Text
|
2024-10-06 17:20:22 +00:00
|
|
|
// Layout values
|
|
|
|
signalX int
|
|
|
|
serviceX int
|
2024-10-06 18:31:23 +00:00
|
|
|
// Current screen(index) (default - 0)
|
|
|
|
curScreen atomic.Int32
|
2024-09-30 18:56:45 +00:00
|
|
|
|
|
|
|
// Threads sync
|
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
wg sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only functions that control content of page
|
|
|
|
type StatusPageContent interface {
|
|
|
|
SystemStatusSetter
|
|
|
|
SetTimeShift(shift time.Duration)
|
|
|
|
}
|
|
|
|
|
|
|
|
type StatusPage interface {
|
|
|
|
Page
|
|
|
|
StatusPageContent
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewStatusPage(d drawer.Drawer) (StatusPage, error) {
|
|
|
|
// Check display
|
|
|
|
if err := d.GetDisplay().IsReady(); err != nil {
|
|
|
|
return nil, fmt.Errorf("display is ready: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create visual components
|
|
|
|
line0, err := components.NewText(d, 0, drawer.LineH*0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("create line0: %w", err)
|
|
|
|
}
|
|
|
|
line1, err := components.NewText(d, 0, drawer.LineH*1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("create line1: %w", err)
|
|
|
|
}
|
|
|
|
line2, err := components.NewText(d, 0, drawer.LineH*2)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("create line2: %w", err)
|
|
|
|
}
|
|
|
|
line3, err := components.NewText(d, 0, drawer.LineH*3)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("create line3: %w", err)
|
|
|
|
}
|
|
|
|
|
2024-10-06 17:20:22 +00:00
|
|
|
// Calculate signal and service glyphs' pos
|
|
|
|
signalX := d.W() - drawer.CommonGlyphs[drawer.SignalStatusGlyphI].W - drawer.CommonGlyphs[drawer.ServiceStatusGlyphI].W - drawer.CharGap
|
|
|
|
serviceX := d.W() - drawer.CommonGlyphs[drawer.ServiceStatusGlyphI].W
|
|
|
|
|
2024-09-30 18:56:45 +00:00
|
|
|
return &statusPage{
|
|
|
|
drawer: d,
|
|
|
|
|
|
|
|
line0: line0,
|
|
|
|
line1: line1,
|
|
|
|
line2: line2,
|
|
|
|
line3: line3,
|
2024-10-06 17:20:22 +00:00
|
|
|
|
|
|
|
signalX: signalX,
|
|
|
|
serviceX: serviceX,
|
2024-09-30 18:56:45 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) Activate() {
|
|
|
|
// Draw
|
|
|
|
p.drawer.Clear()
|
2024-10-06 18:31:23 +00:00
|
|
|
|
2024-10-06 17:20:22 +00:00
|
|
|
p.SetRssi(0)
|
2024-10-06 18:31:23 +00:00
|
|
|
p.SetService("NO SERVICE")
|
2024-10-06 17:20:22 +00:00
|
|
|
|
2024-09-30 18:56:45 +00:00
|
|
|
// Setup threads
|
|
|
|
p.ctx, p.cancel = context.WithCancel(context.Background())
|
|
|
|
go p.timeUpdateLoop()
|
2024-10-06 18:31:23 +00:00
|
|
|
go p.screenChangeLoop()
|
2024-09-30 18:56:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) Diactivate() {
|
|
|
|
// Stop all support threads
|
|
|
|
p.cancel()
|
|
|
|
p.wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) SetTimeShift(shift time.Duration) {
|
|
|
|
p.st.mutex.Lock()
|
|
|
|
defer p.st.mutex.Unlock()
|
|
|
|
|
|
|
|
p.timeShift = shift
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) SetGps(newData gps.Data) {
|
|
|
|
p.st.mutex.Lock()
|
|
|
|
p.st.gpsData = newData
|
2024-10-06 18:31:23 +00:00
|
|
|
p.st.mutex.Unlock()
|
2024-09-30 18:56:45 +00:00
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
if p.curScreen.Load() == 0 { // Draw only on first screen
|
|
|
|
p.drawCoords(&newData)
|
|
|
|
}
|
2024-09-30 18:56:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) SetMpu(newData mpu.Data) {
|
|
|
|
p.st.mutex.Lock()
|
|
|
|
p.st.mpuData = newData
|
2024-10-06 18:31:23 +00:00
|
|
|
p.st.mutex.Unlock()
|
|
|
|
|
|
|
|
if p.curScreen.Load() == 1 { // Draw only on second screen
|
|
|
|
p.drawMpu(&newData)
|
|
|
|
}
|
2024-09-30 18:56:45 +00:00
|
|
|
}
|
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
func (p *statusPage) SetRssi(rssi int) {
|
|
|
|
// Save data
|
2024-09-30 18:56:45 +00:00
|
|
|
p.st.mutex.Lock()
|
2024-10-06 18:31:23 +00:00
|
|
|
p.st.rssi = rssi
|
|
|
|
p.st.mutex.Unlock()
|
2024-09-30 18:56:45 +00:00
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
// Draw
|
|
|
|
p.drawRssi(rssi)
|
2024-09-30 18:56:45 +00:00
|
|
|
}
|
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
func (p *statusPage) SetService(svc string) {
|
|
|
|
// Save data
|
2024-09-30 18:56:45 +00:00
|
|
|
p.st.mutex.Lock()
|
2024-10-06 17:20:22 +00:00
|
|
|
p.st.service = svc
|
2024-10-06 18:31:23 +00:00
|
|
|
p.st.mutex.Unlock()
|
|
|
|
|
|
|
|
// Draw
|
|
|
|
p.drawService(svc)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) Close() (outErr error) {
|
|
|
|
|
|
|
|
// TODO Not the best way...
|
|
|
|
if err := p.line0.Close(); err != nil {
|
|
|
|
outErr = fmt.Errorf("line 0 close: %w:", err)
|
|
|
|
}
|
|
|
|
if err := p.line1.Close(); err != nil {
|
|
|
|
outErr = fmt.Errorf("line 1 close: %w:", err)
|
|
|
|
}
|
|
|
|
if err := p.line2.Close(); err != nil {
|
|
|
|
outErr = fmt.Errorf("line 2 close: %w:", err)
|
|
|
|
}
|
|
|
|
if err := p.line3.Close(); err != nil {
|
|
|
|
outErr = fmt.Errorf("line 3 close: %w:", err)
|
|
|
|
}
|
|
|
|
return
|
2024-10-06 17:20:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getSignalStrength(rssi int) int {
|
|
|
|
// Signal strength clasification (from google):
|
|
|
|
// 0 - no signal -110 to ...
|
|
|
|
// 1 - poor signal -90 to -110
|
|
|
|
// 2 - fair signal -70 to -90
|
|
|
|
// 3 - good signal -50 to -70
|
|
|
|
// 4 - excelent signal ... to -50
|
|
|
|
|
|
|
|
// Description from "sim-modem/api/modem/utils/signal.go"
|
|
|
|
// 0 -113 dBm or less
|
|
|
|
// 1 -111 dBm
|
|
|
|
// 2...30 -109... -53 dBm
|
|
|
|
// 31 -51 dBm or greater
|
|
|
|
// 99 not known or not detectable
|
|
|
|
// 100 -116 dBm or less
|
|
|
|
// 101 -115 dBm
|
|
|
|
// 102…191 -114... -26dBm
|
|
|
|
// 191 -25 dBm or greater
|
|
|
|
// 199 not known or not detectable
|
|
|
|
// 100…199 expand to TDSCDMA, indicate RSCPreceived
|
|
|
|
|
|
|
|
// Cut certain cases
|
|
|
|
switch rssi {
|
|
|
|
case 99, 199: // not known or not detectable
|
|
|
|
return 0
|
|
|
|
case 0, 1, 100, 101: // no signal
|
|
|
|
return 0
|
|
|
|
case 31, 191: // Excelent
|
|
|
|
return 4
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ranged values
|
|
|
|
if rssi >= 2 && rssi <= 30 {
|
|
|
|
// it is in -109...-53 dBm
|
|
|
|
// Simplified interpolation from 2..30 to -109...-53 and then to 0...4
|
|
|
|
return int((float64(rssi)*2-3)/20) + 1
|
|
|
|
}
|
|
|
|
if rssi >= 102 && rssi <= 191 {
|
|
|
|
// it is in -114...-26 dBm
|
|
|
|
// Simplified interpolation from 2..30 to -114...-26 and then to 0...4
|
|
|
|
return min(int((float64(rssi-102)+15)/20), 4)
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0 // Invalid value
|
|
|
|
}
|
|
|
|
|
|
|
|
func getServiceStrength(service string) int {
|
|
|
|
// Service clasification by speed:
|
|
|
|
// 0 - no service - "NO SERVICE"
|
|
|
|
// 1 - 1G - do not use
|
|
|
|
// 2 - 2G - "GSM"
|
|
|
|
// 3 - 3G - "WCDMA"
|
|
|
|
// 4 - 4G - "LTE", "TDS"
|
|
|
|
|
|
|
|
switch service {
|
|
|
|
case "NO SERVICE":
|
|
|
|
return 0
|
|
|
|
case "GSM":
|
|
|
|
return 2
|
|
|
|
case "WCDMA":
|
|
|
|
return 3
|
|
|
|
case "LTE", "TDS":
|
|
|
|
return 4
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0 // Invalid value
|
|
|
|
}
|
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
// Update time in top line
|
2024-09-30 18:56:45 +00:00
|
|
|
func (p *statusPage) timeUpdateLoop() {
|
|
|
|
p.wg.Add(1)
|
|
|
|
|
|
|
|
// Because ticker do not send signal immediately
|
|
|
|
p.line0.SetStr(time.Now().Add(p.timeShift).Format(timeLayout))
|
|
|
|
|
|
|
|
ticker := time.NewTicker(time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-p.ctx.Done():
|
|
|
|
ticker.Stop()
|
|
|
|
p.wg.Done()
|
|
|
|
return
|
|
|
|
case now := <-ticker.C:
|
|
|
|
p.line0.SetStr(now.Add(p.timeShift).Format(timeLayout))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
func (p *statusPage) screenChangeLoop() {
|
|
|
|
p.wg.Add(1)
|
2024-09-30 18:56:45 +00:00
|
|
|
|
2024-10-06 18:31:23 +00:00
|
|
|
// Because ticker do not send signal immediately
|
|
|
|
ticker := time.NewTicker(screenChangeTimeout)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-p.ctx.Done():
|
|
|
|
ticker.Stop()
|
|
|
|
p.wg.Done()
|
|
|
|
return
|
|
|
|
case <-ticker.C:
|
|
|
|
p.changeScreen()
|
|
|
|
}
|
2024-09-30 18:56:45 +00:00
|
|
|
}
|
2024-10-06 18:31:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) changeScreen() {
|
|
|
|
scr := p.curScreen.Load()
|
|
|
|
scr = (scr + 1) % screenN
|
|
|
|
p.curScreen.Store(scr)
|
|
|
|
p.drawScreen(int(scr))
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////// Draw functions /////////////////////
|
|
|
|
|
|
|
|
// Draw functions get draw data as arguments because otherwise they have to get it using mutex
|
|
|
|
// Moreover they will lock it second time
|
|
|
|
|
|
|
|
func (p *statusPage) drawRssi(rssi int) {
|
|
|
|
ss := getSignalStrength(rssi) // Signal strength in [0; 4] range
|
|
|
|
p.drawer.CopyImg(p.signalX, 0, drawer.CommonGlyphs[drawer.SignalStatusGlyphI+ss])
|
|
|
|
p.drawer.GetDisplay().FlushByMask(p.drawer.GetDisplay().GetFlushMaskBit(1, 0))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) drawService(svc string) {
|
|
|
|
ss := getServiceStrength(svc) // Service strength in [0; 4] range
|
|
|
|
p.drawer.CopyImg(p.serviceX, 0, drawer.CommonGlyphs[drawer.ServiceStatusGlyphI+ss])
|
|
|
|
p.drawer.GetDisplay().FlushByMask(p.drawer.GetDisplay().GetFlushMaskBit(1, 0))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) drawCoords(data *gps.Data) {
|
|
|
|
// Langitude, longitude store format: ddmm.mmmmmm, dddmm.mmmmmm
|
|
|
|
// Latitude and longitude layout:
|
|
|
|
// DD° MM.MMM' N
|
|
|
|
// DDD° MM.MMM' W
|
|
|
|
latStr := fmt.Sprintf(" %02d°%06.3f' %s", int(p.st.gpsData.Latitude)/100, math.Mod(p.st.gpsData.Latitude, 100), p.st.gpsData.LatitudeIndicator)
|
|
|
|
p.line2.SetStr(latStr)
|
|
|
|
logStr := fmt.Sprintf(" %03d°%06.3f' %s", int(p.st.gpsData.Longitude)/100, math.Mod(p.st.gpsData.Longitude, 100), p.st.gpsData.LongitudeIndicator)
|
|
|
|
p.line3.SetStr(logStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *statusPage) drawMpu(data *mpu.Data) {
|
|
|
|
// Layout:
|
|
|
|
// temp ttt.ttC
|
|
|
|
|
|
|
|
tempStr := fmt.Sprintf("temperature %06.3fC", data.TempData)
|
|
|
|
p.line2.SetStr(tempStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw main part depends on what current screen is active
|
|
|
|
func (p *statusPage) drawScreen(screen int) {
|
|
|
|
// Clear
|
|
|
|
//p.drawer.FillBar(0, drawer.LineH, p.drawer.W(), p.drawer.H()-drawer.LineH, 0)
|
|
|
|
//p.drawer.GetDisplay().FlushByMask(0b01110111)
|
|
|
|
p.line1.SetStr("")
|
|
|
|
p.line2.SetStr("")
|
|
|
|
p.line3.SetStr("")
|
|
|
|
switch screen {
|
|
|
|
case 0:
|
|
|
|
p.st.mutex.Lock()
|
|
|
|
gpsData := &p.st.gpsData
|
|
|
|
p.st.mutex.Unlock()
|
|
|
|
p.drawCoords(gpsData)
|
|
|
|
case 1:
|
|
|
|
p.st.mutex.Lock()
|
|
|
|
mpuData := &p.st.mpuData
|
|
|
|
p.st.mutex.Unlock()
|
|
|
|
p.drawMpu(mpuData)
|
|
|
|
p.line1.SetStr("47 cameras connected")
|
|
|
|
p.line3.SetStr("some other info...")
|
2024-09-30 18:56:45 +00:00
|
|
|
}
|
|
|
|
}
|