diff --git a/Makefile b/Makefile index a969545..3720062 100644 --- a/Makefile +++ b/Makefile @@ -17,5 +17,4 @@ get: go get . update: - go env go get -u \ No newline at end of file diff --git a/api/api.go b/api/api.go index d496f59..1743415 100644 --- a/api/api.go +++ b/api/api.go @@ -20,10 +20,8 @@ type displayApi struct { curPage pages.Page - initPage pages.InitPage - // statusPage pages.StatusPage - - st status + initPage pages.InitPage + statusPage pages.StatusPage } // Work process: @@ -33,7 +31,9 @@ type displayApi struct { // Status page - gps, time, rssi, service, ... type Display interface { + // Put them to different functions because of different returns SwitchToInitPage() pages.InitPageContent + SwitchToStatusPage() pages.StatusPageContent io.Closer } @@ -53,10 +53,16 @@ func New() (Display, error) { if err != nil { return nil, fmt.Errorf("new init page: %w", err) } + // Status page + statusPage, err := pages.NewStatusPage(d) + if err != nil { + return nil, fmt.Errorf("new init page: %w", err) + } return &displayApi{ - d: d, - initPage: initPage, + d: d, + initPage: initPage, + statusPage: statusPage, }, nil } @@ -75,6 +81,11 @@ func (d *displayApi) SwitchToInitPage() pages.InitPageContent { return d.initPage } +func (d *displayApi) SwitchToStatusPage() pages.StatusPageContent { + d.switchToPage(d.statusPage) + return d.statusPage +} + func (d *displayApi) Close() error { // !!! TMP DEBUG BAD CODE d.d.Clear() diff --git a/api/pages/pages.go b/api/pages/pages.go index cc7d898..ec2a356 100644 --- a/api/pages/pages.go +++ b/api/pages/pages.go @@ -3,6 +3,7 @@ package pages import "io" type Page interface { + // Warning: now threre is no protection from double activation/diactivation Activate() Diactivate() diff --git a/api/pages/status.go b/api/pages/status.go new file mode 100644 index 0000000..a2bee99 --- /dev/null +++ b/api/pages/status.go @@ -0,0 +1,29 @@ +package pages + +import ( + "sync" + + "gitea.unprism.ru/KRBL/mpu/mpu" + "gitea.unprism.ru/KRBL/sim-modem/api/modem/gps" +) + +// Some supplement types and constants + +// This struct contains status of the system that will be on the display +type systemStatus struct { + mutex sync.Mutex + + // Status data + gpsData gps.Data + mpuData mpu.Data + + rssi int // Received signal strength indicator (check gitea.unprism.ru/KRBL/sim-modem/api/modem/utils/signal.go) + service string // Internet service name, could be: "NO SERVICE", "GSM", "WCDMA", "LTE", "TDS" +} + +type SystemStatusSetter interface { + SetGps(newData gps.Data) + SetMpu(newData mpu.Data) + SetRssi(newData int) + SetService(newData string) +} diff --git a/api/pages/statuspage.go b/api/pages/statuspage.go new file mode 100644 index 0000000..2905c9d --- /dev/null +++ b/api/pages/statuspage.go @@ -0,0 +1,195 @@ +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" +) + +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 + + // 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) + } + + return &statusPage{ + drawer: d, + + line0: line0, + line1: line1, + line2: line2, + line3: line3, + }, 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() + + // 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°%02.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) + p.line3.SetStr(logStr) +} + +func (p *statusPage) SetMpu(newData mpu.Data) { + p.st.mutex.Lock() + defer p.st.mutex.Unlock() + + p.st.mpuData = newData +} + +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) { + p.st.mutex.Lock() + defer p.st.mutex.Unlock() + + p.st.service = newData +} + +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 +} diff --git a/api/status.go b/api/status.go deleted file mode 100644 index 0d92dbc..0000000 --- a/api/status.go +++ /dev/null @@ -1,67 +0,0 @@ -package api - -import ( - "sync" - "time" - - "gitea.unprism.ru/KRBL/mpu/mpu" - "gitea.unprism.ru/KRBL/sim-modem/api/modem/gps" -) - -// Some supplement types and constants - -// This struct contains status of the system that will be on the display -type status struct { - mutex sync.Mutex - - // Status data - time time.Time - gpsData gps.Data - mpuData mpu.Data - - rssi int // Received signal strength indicator (check gitea.unprism.ru/KRBL/sim-modem/api/modem/utils/signal.go) - service string // Internet service name, could be: "NO SERVICE", "GSM", "WCDMA", "LTE", "TDS" -} - -type StatusUpdater interface { - UpdateTime(newTime time.Time) - UpdateGps(newData gps.Data) - UpdateMpu(newData mpu.Data) - UpdateRssi(newData int) - UpdateService(newData string) -} - -func (st *status) UpdateTime(newTime time.Time) { - st.mutex.Lock() - defer st.mutex.Unlock() - - st.time = newTime -} - -func (st *status) UpdateGps(newData gps.Data) { - st.mutex.Lock() - defer st.mutex.Unlock() - - st.gpsData = newData -} - -func (st *status) UpdateMpu(newData mpu.Data) { - st.mutex.Lock() - defer st.mutex.Unlock() - - st.mpuData = newData -} - -func (st *status) UpdateRssi(newData int) { - st.mutex.Lock() - defer st.mutex.Unlock() - - st.rssi = newData -} - -func (st *status) UpdateService(newData string) { - st.mutex.Lock() - defer st.mutex.Unlock() - - st.service = newData -} diff --git a/drawer/font.go b/drawer/font.go index fddc389..4564faf 100644 --- a/drawer/font.go +++ b/drawer/font.go @@ -74,7 +74,7 @@ var stdFont = []byte{ 0x61, 0x11, 0x9, 0x7, 0x0, // 7 0x36, 0x49, 0x49, 0x36, 0x0, // 8 0x6, 0x49, 0x49, 0x3e, 0x0, // 9 - 0x50, 0x0, 0x0, 0x0, 0x0, // : + 0x00, 0x0, 0x22, 0x0, 0x0, // : 0x80, 0x50, 0x0, 0x0, 0x0, // ; 0x10, 0x28, 0x44, 0x0, 0x0, // < 0x14, 0x14, 0x14, 0x0, 0x0, // = @@ -192,7 +192,7 @@ var stdFont = []byte{ 0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty 0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty 0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty - 0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty + 0x03, 0x03, 0x00, 0x00, 0x00, // ° 0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty 0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty 0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty diff --git a/go.mod b/go.mod index a171708..2bf6cc0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.5 require ( gitea.unprism.ru/KRBL/mpu v0.1.0 - gitea.unprism.ru/KRBL/sim-modem v0.1.7 + gitea.unprism.ru/KRBL/sim-modem v0.1.8 github.com/stianeikeland/go-rpio/v4 v4.6.0 golang.org/x/text v0.17.0 periph.io/x/conn/v3 v3.7.1 diff --git a/main.go b/main.go index 7cea45b..ebb6af1 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,13 @@ import ( "gitea.unprism.ru/yotia/display-test/drawer" "gitea.unprism.ru/KRBL/sim-modem/api/modem" + "gitea.unprism.ru/KRBL/sim-modem/api/modem/gps" ) const ( displayUpdateTimeout = 10 * time.Millisecond ) -type _ = api.StatusUpdater - func main() { log.Println("CGSG forever!!!") @@ -184,13 +183,15 @@ func mainE(ctx context.Context) error { defer a.Close() logger.Println("Inited") + // !!! Init example + + // Setup init page ip := a.SwitchToInitPage() ip.SetTitle("KRBL") - ip.GetProgressBar().SetProgress(0) - time.Sleep(300 * time.Millisecond) - pb := progress.NewHandler(ip.GetProgressBar(), 100) + pb := progress.NewHandler(ip.GetProgressBar(), 10) - for range 100 { + // Simulate init process + for range 10 { select { case <-ctx.Done(): return nil @@ -198,7 +199,21 @@ func mainE(ctx context.Context) error { } pb.Checkpoint() } - time.Sleep(400 * time.Millisecond) + time.Sleep(200 * time.Millisecond) + + // !!! Status page simulating + + // Switch + sp := a.SwitchToStatusPage() + sp.SetGps(gps.Data{ + Latitude: 3113.330650, + LatitudeIndicator: "N", + Longitude: 12121.262554, + LongitudeIndicator: "E", + }) + + // Just wait now + <-ctx.Done() return nil }