From 58a0d17e47a5c166ba9d8e3752b2f0f6e14d0c75 Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Sun, 6 Oct 2024 21:31:23 +0300 Subject: [PATCH] Add: status page screen change support --- api/pages/statuspage.go | 213 ++++++++++++++++++++++++++-------------- components/text.go | 14 ++- main.go | 4 +- 3 files changed, 155 insertions(+), 76 deletions(-) diff --git a/api/pages/statuspage.go b/api/pages/statuspage.go index 70f600e..3d51de6 100644 --- a/api/pages/statuspage.go +++ b/api/pages/statuspage.go @@ -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 } diff --git a/components/text.go b/components/text.go index d07e08c..f091958 100644 --- a/components/text.go +++ b/components/text.go @@ -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) { diff --git a/main.go b/main.go index 3b900bb..c0955c2 100644 --- a/main.go +++ b/main.go @@ -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