From 04958609963110d88e12373cd461d8c27c29605a Mon Sep 17 00:00:00 2001 From: Andrey Egorov Date: Sun, 6 Oct 2024 20:20:22 +0300 Subject: [PATCH] Add: image, signal and service status output --- api/pages/statuspage.go | 114 +++++++++++++++++++++++++-- drawer/drawer.go | 2 + drawer/image.go | 167 ++++++++++++++++++++++++++++++++++++++++ main.go | 40 +++++++--- 4 files changed, 309 insertions(+), 14 deletions(-) create mode 100644 drawer/image.go diff --git a/api/pages/statuspage.go b/api/pages/statuspage.go index 2905c9d..70f600e 100644 --- a/api/pages/statuspage.go +++ b/api/pages/statuspage.go @@ -23,6 +23,8 @@ const ( timeLayout = "01.09.06 15:04:05" ) +// Some layout constants + type statusPage struct { drawer drawer.Drawer // Drawer with dysplay @@ -36,6 +38,9 @@ type statusPage struct { line1 components.Text line2 components.Text line3 components.Text + // Layout values + signalX int + serviceX int // Threads sync ctx context.Context @@ -78,6 +83,10 @@ func NewStatusPage(d drawer.Drawer) (StatusPage, error) { 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, @@ -85,6 +94,9 @@ func NewStatusPage(d drawer.Drawer) (StatusPage, error) { line1: line1, line2: line2, line3: line3, + + signalX: signalX, + serviceX: serviceX, }, nil } @@ -100,6 +112,11 @@ func (p *statusPage) Activate() { 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() @@ -130,9 +147,9 @@ func (p *statusPage) SetGps(newData gps.Data) { // Latitude and longitude output format: // DD° MM.MMM' N // DDD° MM.MMM' W - latStr := fmt.Sprintf(" %02d°%02.3f' %s", int(p.st.gpsData.Latitude)/100, math.Mod(p.st.gpsData.Latitude, 100), p.st.gpsData.LatitudeIndicator) + 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°%02.3f' %s", int(p.st.gpsData.Longitude)/100, math.Mod(p.st.gpsData.Longitude, 100), p.st.gpsData.LongitudeIndicator) + 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) } @@ -143,18 +160,105 @@ func (p *statusPage) SetMpu(newData mpu.Data) { p.st.mpuData = newData } -func (p *statusPage) SetRssi(newData int) { +// 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(newData string) { +func (p *statusPage) setService(svc string) { p.st.mutex.Lock() defer p.st.mutex.Unlock() - p.st.service = newData + 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() { diff --git a/drawer/drawer.go b/drawer/drawer.go index 53521a0..caef6c0 100644 --- a/drawer/drawer.go +++ b/drawer/drawer.go @@ -25,6 +25,8 @@ type Drawer interface { PutBar(x0, y0, x1, y1 int, color byte) FillBar(x0, y0, x1, y1 int, color byte) + + CopyImg(x0, y0 int, img Image) } func New(dev display.Display) Drawer { diff --git a/drawer/image.go b/drawer/image.go new file mode 100644 index 0000000..6b050f8 --- /dev/null +++ b/drawer/image.go @@ -0,0 +1,167 @@ +package drawer + +// Simple glyphs like signal or service status that are too simple to be whole image + +const ( + SignalStatusGlyphI = 0 // first of 5 + ServiceStatusGlyphI = 5 // first of 5 +) + +type Image struct { + W int + H int + Bits []byte +} + +var CommonGlyphs = []Image{ + { // Signal 0/0 + W: 7, + H: 7, + Bits: []byte{ + 1, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 0, 1, 0, 1, + 0, 0, 1, 0, 0, 0, 1, + 1, 0, 1, 0, 1, 0, 1, + }, + }, + { // Signal 1/4 + W: 7, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, + }, + }, + { // Signal 2/4 + W: 7, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, + 1, 0, 1, 0, 0, 0, 0, + }, + }, + { // Signal 3/4 + W: 7, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 1, 0, 0, + 1, 0, 1, 0, 1, 0, 0, + }, + }, + { // Signal 4/4 + W: 7, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 1, 0, 1, + 0, 0, 1, 0, 1, 0, 1, + 0, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, + }, + }, + { // Service 0/4 + W: 9, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, + }, + }, + { // Service 1/4 + W: 9, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, + }, + }, + { // Service 2/4 + W: 9, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, + }, + }, + { // Service 3/4 + W: 9, + H: 7, + Bits: []byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, + }, + }, + { // Service 4/4 + W: 9, + H: 7, + Bits: []byte{ + 0, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, + }, + }, +} + +func (d *drawer) CopyImg(x0, y0 int, img Image) { + d.dev.LockImg() + defer d.dev.UnlockImg() + + // Coords in img coord space + + minX := IntMax(x0, 0) - x0 + minY := IntMax(y0, 0) - y0 + maxX := IntMin(x0+img.W, d.img.Rect.Dx()) - x0 + maxY := IntMin(y0+img.H, d.img.Rect.Dy()) - y0 + + w := maxX - minX + if w <= 0 { + return + } + for yi := minY; yi < maxY; yi++ { + copy(d.img.Pix[(y0+yi)*d.img.Stride+x0:], img.Bits[yi*img.W:yi*img.W+w]) + } +} diff --git a/main.go b/main.go index ebb6af1..3b900bb 100644 --- a/main.go +++ b/main.go @@ -176,6 +176,8 @@ func mainE(ctx context.Context) error { // return err // } + logger.Println("") + a, err := api.New() if err != nil { return fmt.Errorf("new api: %w", err) @@ -204,16 +206,36 @@ func mainE(ctx context.Context) error { // !!! Status page simulating // Switch + + services := []string{"NO SIGNAL", "GSM", "WCDMA", "LTE", "TDS"} sp := a.SwitchToStatusPage() - sp.SetGps(gps.Data{ - Latitude: 3113.330650, - LatitudeIndicator: "N", - Longitude: 12121.262554, - LongitudeIndicator: "E", - }) - // Just wait now - <-ctx.Done() + for { + select { + case <-ctx.Done(): + return nil + case <-time.After(1000 * time.Millisecond): + // Random coords + lat := float64(rand.Int()%10000) + float64(rand.Int()%1000000)/1000000 + log := float64(rand.Int()%100000) + float64(rand.Int()%1000000)/1000000 + latI := "N" + if rand.Int()%2 == 0 { + latI = "S" + } + logI := "E" + if rand.Int()%2 == 0 { + logI = "W" + } + sp.SetGps(gps.Data{ + Latitude: lat, + LatitudeIndicator: latI, + Longitude: log, + LongitudeIndicator: logI, + }) - return nil + // Random rssi + sp.SetRssi(rand.Int() % 31) + sp.SetService(services[rand.Int()%len(services)]) + } + } }