package pages import ( "context" "fmt" "math" "sync" "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" ) // Debug variant: // line0 - time // time1 - tempreture, speed // time2 - latitude // time3 - longitude const ( timeLayout = "01.09.06 15:04:05" ) // Some layout constants 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 // Layout values signalX int serviceX int // 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) } // 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 return &statusPage{ drawer: d, line0: line0, line1: line1, line2: line2, line3: line3, signalX: signalX, serviceX: serviceX, }, nil } 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) // Setup threads p.ctx, p.cancel = context.WithCancel(context.Background()) go p.timeUpdateLoop() } 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() defer p.st.mutex.Unlock() p.st.gpsData = newData // 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) } func (p *statusPage) SetMpu(newData mpu.Data) { p.st.mutex.Lock() defer p.st.mutex.Unlock() p.st.mpuData = newData } // Only safely updates local values func (p *statusPage) setRssi(newData int) { p.st.mutex.Lock() defer p.st.mutex.Unlock() p.st.rssi = newData } func (p *statusPage) setService(svc string) { p.st.mutex.Lock() defer p.st.mutex.Unlock() p.st.service = svc } 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 } 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)) } 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)) } } } 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 }