Add: status page screen change support

This commit is contained in:
Andrey Egorov 2024-10-06 21:31:23 +03:00
parent 0495860996
commit 58a0d17e47
3 changed files with 155 additions and 76 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt"
"math"
"sync"
"sync/atomic"
"time"
"gitea.unprism.ru/KRBL/mpu/mpu"
@ -13,17 +14,27 @@ import (
"gitea.unprism.ru/yotia/display-test/drawer"
)
// Debug variant:
// line0 - time
// time1 - tempreture, speed
// time2 - latitude
// time3 - longitude
/*
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.
*/
const (
timeLayout = "01.09.06 15:04:05"
)
// Some layout constants
screenN = 2
screenChangeTimeout = 6 * time.Second
)
type statusPage struct {
drawer drawer.Drawer // Drawer with dysplay
@ -41,6 +52,8 @@ type statusPage struct {
// Layout values
signalX int
serviceX int
// Current screen(index) (default - 0)
curScreen atomic.Int32
// Threads sync
ctx context.Context
@ -103,23 +116,14 @@ func NewStatusPage(d drawer.Drawer) (StatusPage, error) {
func (p *statusPage) Activate() {
// Draw
p.drawer.Clear()
//p.line0.SetStr("DEBUG line0")
//p.line1.SetStr("DEBUG line1")
//p.line2.SetStr("DEBUG line2")
//p.line3.SetStr("DEBUG line3")
p.line0.Draw()
p.line1.Draw()
p.line2.Draw()
p.line3.Draw()
// At the right-top corner there are signal then service status glyphs
// Layout:
// gap | signal | gap | service
p.SetRssi(0)
p.SetService("NO SERVICE")
// Setup threads
p.ctx, p.cancel = context.WithCancel(context.Background())
go p.timeUpdateLoop()
go p.screenChangeLoop()
}
func (p *statusPage) Diactivate() {
@ -137,42 +141,60 @@ func (p *statusPage) SetTimeShift(shift time.Duration) {
func (p *statusPage) SetGps(newData gps.Data) {
p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.st.gpsData = newData
p.st.mutex.Unlock()
// Format and update coords
// Langitude, longitude store format: ddmm.mmmmmm, dddmm.mmmmmm
// Latitude and longitude output format:
// 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)
if p.curScreen.Load() == 0 { // Draw only on first screen
p.drawCoords(&newData)
}
}
func (p *statusPage) SetMpu(newData mpu.Data) {
p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.st.mpuData = newData
p.st.mutex.Unlock()
if p.curScreen.Load() == 1 { // Draw only on second screen
p.drawMpu(&newData)
}
}
// Only safely updates local values
func (p *statusPage) setRssi(newData int) {
func (p *statusPage) SetRssi(rssi int) {
// Save data
p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.st.rssi = rssi
p.st.mutex.Unlock()
p.st.rssi = newData
// Draw
p.drawRssi(rssi)
}
func (p *statusPage) setService(svc string) {
func (p *statusPage) SetService(svc string) {
// Save data
p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.st.service = svc
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
}
func getSignalStrength(rssi int) int {
@ -243,24 +265,7 @@ func getServiceStrength(service string) int {
return 0 // Invalid value
}
func (p *statusPage) SetRssi(rssi int) {
p.setRssi(rssi) // Save data (with short lock)
// Update screen image
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) SetService(svc string) {
p.setService(svc) // Save data (with short lock)
// Update screen image
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))
}
// Update time in top line
func (p *statusPage) timeUpdateLoop() {
p.wg.Add(1)
@ -280,20 +285,86 @@ func (p *statusPage) timeUpdateLoop() {
}
}
func (p *statusPage) Close() (outErr error) {
func (p *statusPage) screenChangeLoop() {
p.wg.Add(1)
// TODO Not the best way...
if err := p.line0.Close(); err != nil {
outErr = fmt.Errorf("line 0 close: %w:", err)
// 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()
}
}
}
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...")
}
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
}

View File

@ -50,7 +50,6 @@ func (t *text) Clear() {
}
func (t *text) Draw() {
t.Clear() // Assume that is draw is invoked string has been changed
t.drawer.PutText(t.rect.x, t.rect.y, t.str)
t.drawer.GetDisplay().FlushByMask(t.mask.bits)
}
@ -61,9 +60,18 @@ func (t *text) updateW() {
func (t *text) SetStr(str string) {
t.str = str
t.updateW()
t.mask.Update(t.rect)
newW := len(t.str)*(drawer.FontCharW+drawer.CharGap) - drawer.CharGap
if t.rect.w-newW > 0 {
// Need to clear some space
t.Clear()
t.drawer.FillBar(t.rect.x+newW, t.rect.y, t.rect.x+t.rect.w, t.rect.y+t.rect.h, 0)
} else {
t.rect.w = newW
t.mask.Update(t.rect)
}
t.Draw()
t.rect.w = newW
t.mask.Update(t.rect)
}
func (t *text) GetPos() (int, int) {

View File

@ -197,7 +197,7 @@ func mainE(ctx context.Context) error {
select {
case <-ctx.Done():
return nil
case <-time.After(20 * time.Millisecond):
case <-time.After(60 * time.Millisecond):
}
pb.Checkpoint()
}
@ -214,7 +214,7 @@ func mainE(ctx context.Context) error {
select {
case <-ctx.Done():
return nil
case <-time.After(1000 * time.Millisecond):
case <-time.After(1700 * time.Millisecond):
// Random coords
lat := float64(rand.Int()%10000) + float64(rand.Int()%1000000)/1000000
log := float64(rand.Int()%100000) + float64(rand.Int()%1000000)/1000000