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" "fmt"
"math" "math"
"sync" "sync"
"sync/atomic"
"time" "time"
"gitea.unprism.ru/KRBL/mpu/mpu" "gitea.unprism.ru/KRBL/mpu/mpu"
@ -13,17 +14,27 @@ import (
"gitea.unprism.ru/yotia/display-test/drawer" "gitea.unprism.ru/yotia/display-test/drawer"
) )
// Debug variant: /*
// line0 - time Status page has status line at the top and three lines with primary data below.
// time1 - tempreture, speed Status line shows time and signal and service icon at the right corner.
// time2 - latitude
// time3 - longitude 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 ( const (
timeLayout = "01.09.06 15:04:05" timeLayout = "01.09.06 15:04:05"
)
// Some layout constants screenN = 2
screenChangeTimeout = 6 * time.Second
)
type statusPage struct { type statusPage struct {
drawer drawer.Drawer // Drawer with dysplay drawer drawer.Drawer // Drawer with dysplay
@ -41,6 +52,8 @@ type statusPage struct {
// Layout values // Layout values
signalX int signalX int
serviceX int serviceX int
// Current screen(index) (default - 0)
curScreen atomic.Int32
// Threads sync // Threads sync
ctx context.Context ctx context.Context
@ -103,23 +116,14 @@ func NewStatusPage(d drawer.Drawer) (StatusPage, error) {
func (p *statusPage) Activate() { func (p *statusPage) Activate() {
// Draw // Draw
p.drawer.Clear() 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.SetRssi(0)
p.SetService("NO SERVICE")
// Setup threads // Setup threads
p.ctx, p.cancel = context.WithCancel(context.Background()) p.ctx, p.cancel = context.WithCancel(context.Background())
go p.timeUpdateLoop() go p.timeUpdateLoop()
go p.screenChangeLoop()
} }
func (p *statusPage) Diactivate() { func (p *statusPage) Diactivate() {
@ -137,42 +141,60 @@ func (p *statusPage) SetTimeShift(shift time.Duration) {
func (p *statusPage) SetGps(newData gps.Data) { func (p *statusPage) SetGps(newData gps.Data) {
p.st.mutex.Lock() p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.st.gpsData = newData p.st.gpsData = newData
p.st.mutex.Unlock()
// Format and update coords if p.curScreen.Load() == 0 { // Draw only on first screen
p.drawCoords(&newData)
// 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)
} }
func (p *statusPage) SetMpu(newData mpu.Data) { func (p *statusPage) SetMpu(newData mpu.Data) {
p.st.mutex.Lock() p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.st.mpuData = newData 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(rssi int) {
func (p *statusPage) setRssi(newData int) { // Save data
p.st.mutex.Lock() 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() p.st.mutex.Lock()
defer p.st.mutex.Unlock()
p.st.service = svc 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 { func getSignalStrength(rssi int) int {
@ -243,24 +265,7 @@ func getServiceStrength(service string) int {
return 0 // Invalid value return 0 // Invalid value
} }
func (p *statusPage) SetRssi(rssi int) { // Update time in top line
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))
}
func (p *statusPage) timeUpdateLoop() { func (p *statusPage) timeUpdateLoop() {
p.wg.Add(1) 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... // Because ticker do not send signal immediately
if err := p.line0.Close(); err != nil { ticker := time.NewTicker(screenChangeTimeout)
outErr = fmt.Errorf("line 0 close: %w:", err) for {
} select {
if err := p.line1.Close(); err != nil { case <-p.ctx.Done():
outErr = fmt.Errorf("line 1 close: %w:", err) ticker.Stop()
} p.wg.Done()
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 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...")
}
} }

View File

@ -50,7 +50,6 @@ func (t *text) Clear() {
} }
func (t *text) Draw() { 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.PutText(t.rect.x, t.rect.y, t.str)
t.drawer.GetDisplay().FlushByMask(t.mask.bits) t.drawer.GetDisplay().FlushByMask(t.mask.bits)
} }
@ -61,9 +60,18 @@ func (t *text) updateW() {
func (t *text) SetStr(str string) { func (t *text) SetStr(str string) {
t.str = str t.str = str
t.updateW() 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.mask.Update(t.rect)
}
t.Draw() t.Draw()
t.rect.w = newW
t.mask.Update(t.rect)
} }
func (t *text) GetPos() (int, int) { func (t *text) GetPos() (int, int) {

View File

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