Compare commits
11 Commits
c6af2620e0
...
master
Author | SHA1 | Date | |
---|---|---|---|
58a0d17e47 | |||
0495860996 | |||
0976b5c8d0 | |||
a4fda8303b | |||
bad142a565 | |||
f29bbf8345 | |||
eff00d2351 | |||
5d67d667d7 | |||
30359a0a22 | |||
8889e959ee | |||
7e41196dce |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ out/
|
|||||||
go.sum
|
go.sum
|
||||||
.git/
|
.git/
|
||||||
*.swp
|
*.swp
|
||||||
|
.vscode
|
4
Makefile
4
Makefile
@ -9,7 +9,9 @@ build:
|
|||||||
@go build -o out/out main.go
|
@go build -o out/out main.go
|
||||||
|
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
export GOPRIVATE=gitea.unprism.ru/KRBL/*
|
export GOPRIVATE=gitea.unprism.ru/KRBL/*\
|
||||||
|
# In my case even setting up "url.git@gitea.unprism.ru:8022/.insteadof=https://gitea.unprism.ru/" did not help and git continued to use https instead of ssh
|
||||||
|
# So just go to config(location is listed in error message) and change the URL yourself
|
||||||
|
|
||||||
get:
|
get:
|
||||||
go get .
|
go get .
|
||||||
|
97
api/api.go
Normal file
97
api/api.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/yotia/display-test/api/pages"
|
||||||
|
"gitea.unprism.ru/yotia/display-test/display"
|
||||||
|
"gitea.unprism.ru/yotia/display-test/drawer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
loopTimeout = 10 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
type displayApi struct {
|
||||||
|
d drawer.Drawer
|
||||||
|
|
||||||
|
curPage pages.Page
|
||||||
|
|
||||||
|
initPage pages.InitPage
|
||||||
|
statusPage pages.StatusPage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work process:
|
||||||
|
// Display has pages that you can switch between. Switch returns page controller.
|
||||||
|
// Every page has values that you can change:
|
||||||
|
// Init page - title, progress bar
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (Display, error) {
|
||||||
|
// Device
|
||||||
|
dev, err := display.New(log.New(log.Writer(), "display", log.LstdFlags), display.MT12232A)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create display: %w", err)
|
||||||
|
}
|
||||||
|
// Drawer
|
||||||
|
d := drawer.New(dev)
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
|
||||||
|
// Init page
|
||||||
|
initPage, err := pages.NewInitPage(d)
|
||||||
|
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,
|
||||||
|
statusPage: statusPage,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayApi) switchToPage(p pages.Page) {
|
||||||
|
if d.curPage != nil {
|
||||||
|
d.curPage.Diactivate()
|
||||||
|
}
|
||||||
|
if p != nil {
|
||||||
|
p.Activate()
|
||||||
|
}
|
||||||
|
d.curPage = p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayApi) SwitchToInitPage() pages.InitPageContent {
|
||||||
|
d.switchToPage(d.initPage)
|
||||||
|
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()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
// !!!
|
||||||
|
|
||||||
|
d.switchToPage(nil) // To deactivate cur page
|
||||||
|
return d.d.GetDisplay().Close()
|
||||||
|
}
|
77
api/pages/initpage.go
Normal file
77
api/pages/initpage.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/yotia/display-test/components"
|
||||||
|
"gitea.unprism.ru/yotia/display-test/drawer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type initPage struct {
|
||||||
|
drawer drawer.Drawer // Drawer with dysplay
|
||||||
|
|
||||||
|
title components.Text
|
||||||
|
bar components.ProgressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only functions that control content of page
|
||||||
|
type InitPageContent interface {
|
||||||
|
SetTitle(title string)
|
||||||
|
GetProgressBar() components.ProgressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitPage interface {
|
||||||
|
Page
|
||||||
|
InitPageContent
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInitPage(d drawer.Drawer) (InitPage, error) {
|
||||||
|
if err := d.GetDisplay().IsReady(); err != nil {
|
||||||
|
return nil, fmt.Errorf("display is ready: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
title, err := components.NewText(d, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create title: %w", err)
|
||||||
|
}
|
||||||
|
pb, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH) // Bottom
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create progress bar: %w", err)
|
||||||
|
}
|
||||||
|
return &initPage{
|
||||||
|
drawer: d,
|
||||||
|
title: title,
|
||||||
|
bar: pb,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *initPage) Activate() {
|
||||||
|
// Draw
|
||||||
|
p.drawer.Clear()
|
||||||
|
p.title.Draw()
|
||||||
|
p.bar.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *initPage) Diactivate() {
|
||||||
|
// Do not clear because next page will have to clear whole screen any way
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *initPage) SetTitle(title string) {
|
||||||
|
p.title.SetPos((p.drawer.W()-len(title)*(drawer.FontCharW+drawer.CharGap))/2, drawer.FontCharH) // Text in center
|
||||||
|
p.title.SetStr(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *initPage) GetProgressBar() components.ProgressBar {
|
||||||
|
return p.bar
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *initPage) Close() (outErr error) {
|
||||||
|
// TODO Not the best way...
|
||||||
|
if err := p.title.Close(); err != nil {
|
||||||
|
outErr = fmt.Errorf("title close: %w:", err)
|
||||||
|
}
|
||||||
|
if err := p.bar.Close(); err != nil {
|
||||||
|
outErr = fmt.Errorf("progress bar close: %w:", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
11
api/pages/pages.go
Normal file
11
api/pages/pages.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type Page interface {
|
||||||
|
// Warning: now threre is no protection from double activation/diactivation
|
||||||
|
Activate()
|
||||||
|
Diactivate()
|
||||||
|
|
||||||
|
io.Closer
|
||||||
|
}
|
29
api/pages/status.go
Normal file
29
api/pages/status.go
Normal file
@ -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)
|
||||||
|
}
|
370
api/pages/statuspage.go
Normal file
370
api/pages/statuspage.go
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
screenN = 2
|
||||||
|
screenChangeTimeout = 6 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
// Current screen(index) (default - 0)
|
||||||
|
curScreen atomic.Int32
|
||||||
|
|
||||||
|
// 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.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() {
|
||||||
|
// 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()
|
||||||
|
p.st.gpsData = newData
|
||||||
|
p.st.mutex.Unlock()
|
||||||
|
|
||||||
|
if p.curScreen.Load() == 0 { // Draw only on first screen
|
||||||
|
p.drawCoords(&newData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *statusPage) SetMpu(newData mpu.Data) {
|
||||||
|
p.st.mutex.Lock()
|
||||||
|
p.st.mpuData = newData
|
||||||
|
p.st.mutex.Unlock()
|
||||||
|
|
||||||
|
if p.curScreen.Load() == 1 { // Draw only on second screen
|
||||||
|
p.drawMpu(&newData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *statusPage) SetRssi(rssi int) {
|
||||||
|
// Save data
|
||||||
|
p.st.mutex.Lock()
|
||||||
|
p.st.rssi = rssi
|
||||||
|
p.st.mutex.Unlock()
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
p.drawRssi(rssi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *statusPage) SetService(svc string) {
|
||||||
|
// Save data
|
||||||
|
p.st.mutex.Lock()
|
||||||
|
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 {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update time in top line
|
||||||
|
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) screenChangeLoop() {
|
||||||
|
p.wg.Add(1)
|
||||||
|
|
||||||
|
// 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...")
|
||||||
|
}
|
||||||
|
}
|
62
api/progress/handler.go
Normal file
62
api/progress/handler.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package progress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/yotia/display-test/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
bar components.ProgressBar
|
||||||
|
checkpointN int // Number of check points
|
||||||
|
curP int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles progress bar update
|
||||||
|
// You set the amount of
|
||||||
|
type Handler interface {
|
||||||
|
Checkpoint() // Increments current progress
|
||||||
|
// SetProgress(n int) // Do not want to make it public because there is no places it is needed yet
|
||||||
|
Finish() // Set progress to end
|
||||||
|
GetBar() components.ProgressBar
|
||||||
|
Reset()
|
||||||
|
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(bar components.ProgressBar, checkpointN int) Handler {
|
||||||
|
return &handler{
|
||||||
|
bar: bar,
|
||||||
|
checkpointN: checkpointN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetBar() components.ProgressBar {
|
||||||
|
return h.bar
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Checkpoint() {
|
||||||
|
h.curP = min(h.curP+1, h.checkpointN)
|
||||||
|
h.bar.SetProgress(float64(h.curP) / float64(h.checkpointN))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) SetProgress(n int) {
|
||||||
|
h.curP = min(max(n, 0), h.checkpointN)
|
||||||
|
h.bar.SetProgress(float64(h.curP) / float64(h.checkpointN))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Finish() {
|
||||||
|
h.curP = h.checkpointN
|
||||||
|
h.bar.SetProgress(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Reset() {
|
||||||
|
h.curP = 0
|
||||||
|
h.bar.SetProgress(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Close() error {
|
||||||
|
h.curP = 0
|
||||||
|
h.checkpointN = 0
|
||||||
|
return h.bar.Close()
|
||||||
|
}
|
57
components/components.go
Normal file
57
components/components.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// Some general types
|
||||||
|
|
||||||
|
// Rectangle and its' suplements
|
||||||
|
type rect struct {
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
w int
|
||||||
|
h int
|
||||||
|
}
|
||||||
|
|
||||||
|
type RectGetter interface {
|
||||||
|
GetPos() (int, int)
|
||||||
|
GetSize() (int, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RectSetter interface {
|
||||||
|
SetPos(x, y int)
|
||||||
|
SetSize(w, h int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask
|
||||||
|
|
||||||
|
// Implementation dependent !!!
|
||||||
|
// Now for mt12232
|
||||||
|
type mask struct {
|
||||||
|
bits uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mask) Update(r rect) {
|
||||||
|
m.bits = 0
|
||||||
|
y0 := min(3, max(0, int(r.y/8)))
|
||||||
|
y1 := min(3, max(0, int((r.y+r.h-1)/8)))
|
||||||
|
|
||||||
|
x0 := min(1, max(0, int(r.x/61)))
|
||||||
|
x1 := min(1, max(0, int((r.x+r.w-1)/61)))
|
||||||
|
|
||||||
|
//log.Println(x0, y0, x1, y1)
|
||||||
|
for y := y0; y <= y1; y++ {
|
||||||
|
for x := x0; x <= x1; x++ {
|
||||||
|
m.bits |= (1 << (x*4 + y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component - all components should implement this interface
|
||||||
|
|
||||||
|
type Component interface {
|
||||||
|
RectGetter
|
||||||
|
|
||||||
|
Draw() // Draw and flush
|
||||||
|
|
||||||
|
io.Closer
|
||||||
|
}
|
93
components/progressbar.go
Normal file
93
components/progressbar.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/yotia/display-test/drawer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type progressBar struct {
|
||||||
|
drawer drawer.Drawer // Drawer with dysplay
|
||||||
|
mask mask // Flush mask
|
||||||
|
progress float64
|
||||||
|
|
||||||
|
rect rect // Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgressBar interface {
|
||||||
|
Component
|
||||||
|
|
||||||
|
SetProgress(coef float64) // Value from 0 to 1
|
||||||
|
Clear() // Clear space of bar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creation function
|
||||||
|
// Now the only way to chage shape is to recreate component
|
||||||
|
func NewProgressBar(drawer drawer.Drawer, x, y, w, h int) (ProgressBar, error) {
|
||||||
|
if err := drawer.GetDisplay().IsReady(); err != nil {
|
||||||
|
return nil, fmt.Errorf("display is ready: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for correct sizes
|
||||||
|
if w < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid width: below 4")
|
||||||
|
}
|
||||||
|
if h < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid height: below 4")
|
||||||
|
}
|
||||||
|
pb := progressBar{
|
||||||
|
drawer: drawer,
|
||||||
|
rect: rect{x, y, w, h},
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.mask.Update(pb.rect)
|
||||||
|
// pb.Draw()
|
||||||
|
|
||||||
|
return &pb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *progressBar) flush() {
|
||||||
|
pb.drawer.GetDisplay().FlushByMask(pb.mask.bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *progressBar) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *progressBar) Clear() {
|
||||||
|
pb.drawer.FillBar(pb.rect.x, pb.rect.y, pb.rect.x+pb.rect.w, pb.rect.y+pb.rect.h, 0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw whole bar
|
||||||
|
func (pb *progressBar) Draw() {
|
||||||
|
// Shape:
|
||||||
|
// Bounds - 1px
|
||||||
|
// Padding - 1px
|
||||||
|
// Progress - the rest space
|
||||||
|
pb.Clear()
|
||||||
|
pb.drawer.PutBar(pb.rect.x, pb.rect.y, pb.rect.x+pb.rect.w, pb.rect.y+pb.rect.h, 0xFF)
|
||||||
|
pb.drawBar()
|
||||||
|
pb.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw only bar(without frames)
|
||||||
|
func (pb *progressBar) drawBar() {
|
||||||
|
w := int(float64(pb.rect.w-4) * pb.progress)
|
||||||
|
|
||||||
|
pb.drawer.FillBar(pb.rect.x+2, pb.rect.y+2, pb.rect.x+2+w, pb.rect.y+pb.rect.h-2, 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *progressBar) SetProgress(coef float64) {
|
||||||
|
pb.progress = max(0, min(1, coef))
|
||||||
|
|
||||||
|
pb.drawBar()
|
||||||
|
pb.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *progressBar) GetPos() (int, int) {
|
||||||
|
return pb.rect.x, pb.rect.y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *progressBar) GetSize() (int, int) {
|
||||||
|
return pb.rect.w, pb.rect.h
|
||||||
|
}
|
93
components/text.go
Normal file
93
components/text.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/yotia/display-test/drawer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type text struct {
|
||||||
|
drawer drawer.Drawer // Drawer with dysplay
|
||||||
|
mask mask // Flush mask
|
||||||
|
|
||||||
|
str string
|
||||||
|
|
||||||
|
rect rect // Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
type Text interface {
|
||||||
|
Component
|
||||||
|
RectSetter
|
||||||
|
|
||||||
|
SetStr(newStr string) // Update string and redraw
|
||||||
|
Clear() // Clear space of bar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creation function
|
||||||
|
// Now the only way to chage shape is to recreate component
|
||||||
|
func NewText(d drawer.Drawer, x, y int) (Text, error) {
|
||||||
|
if err := d.GetDisplay().IsReady(); err != nil {
|
||||||
|
return nil, fmt.Errorf("display is ready: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := text{
|
||||||
|
drawer: d,
|
||||||
|
rect: rect{x, y, 0, drawer.FontCharH},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.mask.Update(t.rect)
|
||||||
|
//t.Draw()
|
||||||
|
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) Clear() {
|
||||||
|
t.drawer.FillBar(t.rect.x, t.rect.y, t.rect.x+t.rect.w, t.rect.y+t.rect.h, 0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) Draw() {
|
||||||
|
t.drawer.PutText(t.rect.x, t.rect.y, t.str)
|
||||||
|
t.drawer.GetDisplay().FlushByMask(t.mask.bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) updateW() {
|
||||||
|
t.rect.w = len(t.str)*(drawer.FontCharW+drawer.CharGap) - drawer.CharGap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) SetStr(str string) {
|
||||||
|
t.str = str
|
||||||
|
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) {
|
||||||
|
return t.rect.x, t.rect.y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) GetSize() (int, int) {
|
||||||
|
return t.rect.w, t.rect.h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) SetPos(x, y int) {
|
||||||
|
t.rect.x = x
|
||||||
|
t.rect.y = y
|
||||||
|
t.mask.Update(t.rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *text) SetSize(w, h int) {
|
||||||
|
// Empty
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package display
|
package display
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
@ -8,9 +9,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Display interface {
|
type Display interface {
|
||||||
Flush(img *image.Gray) error
|
IsReady() error // Nil if it is ready, error with some info otherwise
|
||||||
|
|
||||||
|
Flush(crystal, page byte)
|
||||||
|
FlushByMask(mask uint32)
|
||||||
|
GetFlushMaskBit(crystal, page byte) uint32
|
||||||
|
|
||||||
|
// Image functions
|
||||||
|
GetImg() *image.Gray
|
||||||
|
LockImg()
|
||||||
|
UnlockImg()
|
||||||
GetBounds() image.Rectangle
|
GetBounds() image.Rectangle
|
||||||
|
|
||||||
|
Test(ctx context.Context) error // DEBUG ONLY
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +31,7 @@ type DisplayModel int
|
|||||||
const (
|
const (
|
||||||
SSD1306 DisplayModel = iota
|
SSD1306 DisplayModel = iota
|
||||||
MT12864A
|
MT12864A
|
||||||
|
MT12232A
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(logger *log.Logger, model DisplayModel) (Display, error) {
|
func New(logger *log.Logger, model DisplayModel) (Display, error) {
|
||||||
@ -27,6 +40,8 @@ func New(logger *log.Logger, model DisplayModel) (Display, error) {
|
|||||||
return newSsd1306(logger)
|
return newSsd1306(logger)
|
||||||
case MT12864A:
|
case MT12864A:
|
||||||
return newMt12864a(logger)
|
return newMt12864a(logger)
|
||||||
|
case MT12232A:
|
||||||
|
return newMt12232a(logger)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("invalid display model")
|
return nil, fmt.Errorf("invalid display model")
|
||||||
}
|
}
|
||||||
|
316
display/mt-12232a.go
Normal file
316
display/mt-12232a.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
package display
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/yotia/display-test/pkg/mt12232a"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = rand.Int() // for import existance
|
||||||
|
|
||||||
|
const (
|
||||||
|
mt12232aW = 122
|
||||||
|
mt12232aH = 32
|
||||||
|
flushChanCap = 24 // Flush channel capacity
|
||||||
|
|
||||||
|
flushUpdateTimeout = 5 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
type displayMt12232a struct {
|
||||||
|
logger *log.Logger
|
||||||
|
|
||||||
|
// Some state flags
|
||||||
|
isTurnedOn bool
|
||||||
|
|
||||||
|
// Image
|
||||||
|
img *image.Gray
|
||||||
|
imgMutex sync.Mutex
|
||||||
|
|
||||||
|
// GPIO pins
|
||||||
|
dev mt12232a.Device
|
||||||
|
|
||||||
|
// Flush goroutine
|
||||||
|
flushCancel context.CancelFunc
|
||||||
|
pagesFlushFlags atomic.Uint32 // (!use only 8 bits) Every bit corresponds to page/crystal with this number
|
||||||
|
flushDone chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMt12232a(logger *log.Logger) (Display, error) {
|
||||||
|
dev, err := mt12232a.New(log.New(logger.Writer(), "display-mt12864 : ", log.LstdFlags))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("mt12864 create: %w", err)
|
||||||
|
}
|
||||||
|
// Allocate bits
|
||||||
|
bits := make([][]byte, 32)
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
bits[i] = make([]byte, 122)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
// Setup submit goroutine
|
||||||
|
d := displayMt12232a{
|
||||||
|
logger: logger,
|
||||||
|
dev: dev,
|
||||||
|
flushCancel: cancel,
|
||||||
|
img: image.NewGray(image.Rect(0, 0, mt12232aW, mt12232aH)),
|
||||||
|
flushDone: make(chan struct{}), // For waiting flush to finish before exiting
|
||||||
|
}
|
||||||
|
go d.flushLoop(ctx)
|
||||||
|
|
||||||
|
// Temp debug draw
|
||||||
|
//if st0, st1 := d.dev.ReadStatus(0), d.dev.ReadStatus(1); false { //st0&0x20 == 0 && st1&0x20 == 0 {
|
||||||
|
// d.logger.Println("Display is already on")
|
||||||
|
//} else {
|
||||||
|
d.status("Setup start")
|
||||||
|
if err := d.dev.Reset(); err != nil {
|
||||||
|
return nil, fmt.Errorf("reset: %w", err)
|
||||||
|
}
|
||||||
|
d.status("After reset")
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
if st0, st1 := d.dev.ReadStatus(0), d.dev.ReadStatus(1); st0 == 0 && st1 == 0 {
|
||||||
|
return nil, fmt.Errorf("No status response from dysplay")
|
||||||
|
}
|
||||||
|
if err := d.powerOn(); err != nil {
|
||||||
|
return nil, fmt.Errorf("power on: %w", err)
|
||||||
|
}
|
||||||
|
d.status("Setup end")
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) status(label string) {
|
||||||
|
//d.logger.Printf("STATUS %s -- L: %08b R: %08b\n", label, d.dev.ReadStatus(0)&0xFF, d.dev.ReadStatus(1)&0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) powerOn() error {
|
||||||
|
// Reset
|
||||||
|
if err := d.dev.WriteCode(0, 0xE2); err != nil {
|
||||||
|
return fmt.Errorf("reset: %w", err)
|
||||||
|
}
|
||||||
|
if err := d.dev.WriteCode(1, 0xE2); err != nil {
|
||||||
|
return fmt.Errorf("reset: %w", err)
|
||||||
|
}
|
||||||
|
// ReadModifyWrite off
|
||||||
|
if err := d.dev.WriteCode(0, 0xEE); err != nil {
|
||||||
|
return fmt.Errorf("RMW off: %w", err)
|
||||||
|
}
|
||||||
|
if err := d.dev.WriteCode(1, 0xEE); err != nil {
|
||||||
|
return fmt.Errorf("RMW off: %w", err)
|
||||||
|
}
|
||||||
|
// Turn on common mode
|
||||||
|
if err := d.dev.WriteCode(0, 0xA4); err != nil {
|
||||||
|
return fmt.Errorf("turn on common mode: %w", err)
|
||||||
|
}
|
||||||
|
if err := d.dev.WriteCode(1, 0xA4); err != nil {
|
||||||
|
return fmt.Errorf("turn on common mode: %w", err)
|
||||||
|
}
|
||||||
|
// Multiplex 1/32
|
||||||
|
if err := d.dev.WriteCode(0, 0xA9); err != nil {
|
||||||
|
return fmt.Errorf("multiplex 1/32: %w", err)
|
||||||
|
}
|
||||||
|
if err := d.dev.WriteCode(1, 0xA9); err != nil {
|
||||||
|
return fmt.Errorf("multiplex 1/32: %w", err)
|
||||||
|
}
|
||||||
|
// Top line to 0
|
||||||
|
if err := d.dev.WriteCode(0, 0xC0); err != nil {
|
||||||
|
return fmt.Errorf("top line to 0: %w", err)
|
||||||
|
}
|
||||||
|
if err := d.dev.WriteCode(1, 0xC0); err != nil {
|
||||||
|
return fmt.Errorf("top line to 0: %w", err)
|
||||||
|
}
|
||||||
|
// Invert scan RAM
|
||||||
|
if err := d.dev.WriteCode(0, 0xA1); err != nil {
|
||||||
|
return fmt.Errorf("inver scan RAM: %w", err)
|
||||||
|
}
|
||||||
|
if err := d.dev.WriteCode(1, 0xA0); err != nil {
|
||||||
|
return fmt.Errorf("inver scan RAM: %w", err)
|
||||||
|
}
|
||||||
|
// Display on
|
||||||
|
if err := d.dev.WriteCode(0, 0xAF); err != nil {
|
||||||
|
return fmt.Errorf("display on: %w", err)
|
||||||
|
}
|
||||||
|
if err := d.dev.WriteCode(1, 0xAF); err != nil {
|
||||||
|
return fmt.Errorf("display on: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// Check that crystals are turned on
|
||||||
|
|
||||||
|
// The same but with error
|
||||||
|
if d.dev.ReadStatus(0)&0x20 != 0 {
|
||||||
|
return fmt.Errorf("Left cristal is off")
|
||||||
|
}
|
||||||
|
if d.dev.ReadStatus(1)&0x20 != 0 {
|
||||||
|
return fmt.Errorf("Right cristal is off")
|
||||||
|
}
|
||||||
|
d.isTurnedOn = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) IsReady() error {
|
||||||
|
if !d.isTurnedOn {
|
||||||
|
return fmt.Errorf("display is turned off")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) Test(ctx context.Context) error {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for p := 0; p < 4; p++ {
|
||||||
|
d.dev.WriteCode(0, byte(p)|0xB8)
|
||||||
|
d.dev.WriteCode(0, 0x13)
|
||||||
|
for c := 0; c < 61; c++ {
|
||||||
|
d.dev.WriteData(0, byte(rand.Int()))
|
||||||
|
}
|
||||||
|
d.dev.WriteCode(1, byte(p)|0xB8)
|
||||||
|
d.dev.WriteCode(1, 0x00)
|
||||||
|
for c := 0; c < 61; c++ {
|
||||||
|
d.dev.WriteData(1, byte(rand.Int()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := time.Now()
|
||||||
|
d.logger.Println(end.Sub(start))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) GetBounds() image.Rectangle {
|
||||||
|
return image.Rect(0, 0, 122, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) FlushFull(img *image.Gray) error {
|
||||||
|
st := time.Now()
|
||||||
|
|
||||||
|
for p := byte(0); p < 4; p++ {
|
||||||
|
d.dev.WriteCode(0, (3-p)|0xB8)
|
||||||
|
d.dev.WriteCode(0, 0x13)
|
||||||
|
for c := 0; c < 61; c++ {
|
||||||
|
d.dev.WriteDatas(0, []byte{
|
||||||
|
img.Pix[int(p<<3+7)*122+c],
|
||||||
|
img.Pix[int(p<<3+6)*122+c],
|
||||||
|
img.Pix[int(p<<3+5)*122+c],
|
||||||
|
img.Pix[int(p<<3+4)*122+c],
|
||||||
|
img.Pix[int(p<<3+3)*122+c],
|
||||||
|
img.Pix[int(p<<3+2)*122+c],
|
||||||
|
img.Pix[int(p<<3+1)*122+c],
|
||||||
|
img.Pix[int(p<<3+0)*122+c],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
d.dev.WriteCode(1, (3-p)|0xB8)
|
||||||
|
d.dev.WriteCode(1, 0x00)
|
||||||
|
for c := 61; c < 122; c++ {
|
||||||
|
d.dev.WriteDatas(1, []byte{
|
||||||
|
img.Pix[int(p<<3+7)*122+c],
|
||||||
|
img.Pix[int(p<<3+6)*122+c],
|
||||||
|
img.Pix[int(p<<3+5)*122+c],
|
||||||
|
img.Pix[int(p<<3+4)*122+c],
|
||||||
|
img.Pix[int(p<<3+3)*122+c],
|
||||||
|
img.Pix[int(p<<3+2)*122+c],
|
||||||
|
img.Pix[int(p<<3+1)*122+c],
|
||||||
|
img.Pix[int(p<<3+0)*122+c],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.logger.Println("Flush time:", time.Since(st))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) Flush(crystal, page byte) {
|
||||||
|
// !!! TODO Need to update GO to 1.23 to use .Or !!!
|
||||||
|
bit := uint32(1 << (crystal*4 + page))
|
||||||
|
d.pagesFlushFlags.Store(d.pagesFlushFlags.Load() | bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) FlushByMask(mask uint32) {
|
||||||
|
// !!! TODO Need to update GO to 1.23 to use .Or !!!
|
||||||
|
d.pagesFlushFlags.Store(d.pagesFlushFlags.Load() | mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) GetFlushMaskBit(crystal, page byte) uint32 {
|
||||||
|
return uint32(1 << (crystal*4 + page))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) Close() error {
|
||||||
|
if d.flushCancel != nil {
|
||||||
|
d.flushCancel()
|
||||||
|
d.flushCancel = nil
|
||||||
|
<-d.flushDone
|
||||||
|
}
|
||||||
|
d.isTurnedOn = false
|
||||||
|
return d.dev.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) flushLoop(ctx context.Context) {
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
close(d.flushDone)
|
||||||
|
return
|
||||||
|
case <-time.After(flushUpdateTimeout):
|
||||||
|
forUpdate := d.pagesFlushFlags.Swap(0)
|
||||||
|
checkBit := uint32(1)
|
||||||
|
//st := time.Now()
|
||||||
|
d.LockImg()
|
||||||
|
for p := byte(0); p < 4; p++ {
|
||||||
|
if forUpdate&(checkBit) != 0 {
|
||||||
|
d.dev.WriteCode(0, (3-p)|0xB8)
|
||||||
|
d.dev.WriteCode(0, 0x13)
|
||||||
|
for c := 0; c < 61; c++ {
|
||||||
|
d.dev.WriteDatas(0, []byte{
|
||||||
|
d.img.Pix[int(p<<3+7)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+6)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+5)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+4)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+3)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+2)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+1)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+0)*122+c],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkBit = checkBit << 1
|
||||||
|
}
|
||||||
|
//d.logger.Printf("%08b - %s\n", forUpdate, time.Since(st))
|
||||||
|
for p := byte(0); p < 4; p++ {
|
||||||
|
if forUpdate&(checkBit) != 0 {
|
||||||
|
d.dev.WriteCode(1, (3-p)|0xB8)
|
||||||
|
d.dev.WriteCode(1, 0x00)
|
||||||
|
for c := 61; c < 122; c++ {
|
||||||
|
d.dev.WriteDatas(1, []byte{
|
||||||
|
d.img.Pix[int(p<<3+7)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+6)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+5)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+4)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+3)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+2)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+1)*122+c],
|
||||||
|
d.img.Pix[int(p<<3+0)*122+c],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkBit = checkBit << 1
|
||||||
|
}
|
||||||
|
d.UnlockImg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12232a) GetImg() *image.Gray {
|
||||||
|
return d.img
|
||||||
|
}
|
||||||
|
func (d *displayMt12232a) LockImg() {
|
||||||
|
d.imgMutex.Lock()
|
||||||
|
}
|
||||||
|
func (d *displayMt12232a) UnlockImg() {
|
||||||
|
d.imgMutex.Unlock()
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package display
|
package display
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"log"
|
"log"
|
||||||
@ -36,7 +37,12 @@ func newMt12864a(logger *log.Logger) (Display, error) {
|
|||||||
d.test()
|
d.test()
|
||||||
d.logger.Println("Draw finished")
|
d.logger.Println("Draw finished")
|
||||||
|
|
||||||
return &d, nil
|
//return &d, nil
|
||||||
|
return nil, fmt.Errorf("IMPLEMENTATION COMMENTED")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12864a) Test(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *displayMt12864a) test() {
|
func (d *displayMt12864a) test() {
|
||||||
@ -137,10 +143,22 @@ func (d *displayMt12864a) GetBounds() image.Rectangle {
|
|||||||
return image.Rectangle{}
|
return image.Rectangle{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *displayMt12864a) Flush(img *image.Gray) error {
|
func (d *displayMt12864a) Flush(crystal, page byte) {
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12864a) FlushByMask(mask uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *displayMt12864a) Close() error {
|
func (d *displayMt12864a) Close() error {
|
||||||
return rpio.Close()
|
return rpio.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *displayMt12864a) GetImg() *image.Gray {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (d *displayMt12864a) LockImg() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (d *displayMt12864a) UnlockImg() {
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package display
|
package display
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"log"
|
"log"
|
||||||
@ -41,19 +42,29 @@ func newSsd1306(logger *log.Logger) (Display, error) {
|
|||||||
return nil, fmt.Errorf("create i2c: %w", err)
|
return nil, fmt.Errorf("create i2c: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &displaySsd1306{
|
_ = displaySsd1306{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
bus: bus,
|
bus: bus,
|
||||||
dev: dev,
|
dev: dev,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("IMPLEMENTATION COMMENTED")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *displaySsd1306) GetBounds() image.Rectangle {
|
func (d *displaySsd1306) GetBounds() image.Rectangle {
|
||||||
return d.dev.Bounds()
|
return d.dev.Bounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *displaySsd1306) Flush(img *image.Gray) error {
|
func (d *displaySsd1306) Test(ctx context.Context) error {
|
||||||
return d.dev.Draw(img.Bounds(), d.img, image.Point{})
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displaySsd1306) Flush(crystal, page byte) {
|
||||||
|
//return d.dev.Draw(img.Bounds(), d.img, image.Point{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *displaySsd1306) FlushByMask(mask uint32) {
|
||||||
|
//return d.dev.Draw(img.Bounds(), d.img, image.Point{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *displaySsd1306) Close() error {
|
func (d *displaySsd1306) Close() error {
|
||||||
@ -62,3 +73,13 @@ func (d *displaySsd1306) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *displaySsd1306) GetImg() *image.Gray {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (d *displaySsd1306) LockImg() {
|
||||||
|
|
||||||
|
}
|
||||||
|
func (d *displaySsd1306) UnlockImg() {
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -2,9 +2,9 @@ package drawer
|
|||||||
|
|
||||||
import "math"
|
import "math"
|
||||||
|
|
||||||
func (d *drawer) FillBar(sx, sy, ex, ey, color int) {
|
func (d *drawer) FillBar(sx, sy, ex, ey int, color byte) {
|
||||||
d.Lock()
|
d.dev.LockImg()
|
||||||
defer d.Unlock()
|
defer d.dev.UnlockImg()
|
||||||
|
|
||||||
bounds := d.img.Bounds()
|
bounds := d.img.Bounds()
|
||||||
// Crop
|
// Crop
|
||||||
@ -18,14 +18,14 @@ func (d *drawer) FillBar(sx, sy, ex, ey, color int) {
|
|||||||
addr := lineaddr
|
addr := lineaddr
|
||||||
le := addr + w
|
le := addr + w
|
||||||
for ; addr < le; addr++ {
|
for ; addr < le; addr++ {
|
||||||
d.img.Pix[addr] = 255
|
d.img.Pix[addr] = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
|
func (d *drawer) PutBar(sx, sy, ex, ey int, color byte) {
|
||||||
d.Lock()
|
d.dev.LockImg()
|
||||||
defer d.Unlock()
|
defer d.dev.UnlockImg()
|
||||||
|
|
||||||
bounds := d.img.Bounds()
|
bounds := d.img.Bounds()
|
||||||
// Crop
|
// Crop
|
||||||
@ -36,7 +36,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
|
|||||||
x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
|
x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
|
||||||
|
|
||||||
for addr := sy*bounds.Dx() + x0; addr < sy*bounds.Dx()+x1; addr++ {
|
for addr := sy*bounds.Dx() + x0; addr < sy*bounds.Dx()+x1; addr++ {
|
||||||
d.img.Pix[addr] = 255
|
d.img.Pix[addr] = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bottom
|
// Bottom
|
||||||
@ -45,7 +45,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
|
|||||||
x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
|
x1 := int(math.Min(float64(ex), float64(bounds.Dx())))
|
||||||
|
|
||||||
for addr := (ey-1)*bounds.Dx() + x0; addr < (ey-1)*bounds.Dx()+x1; addr++ {
|
for addr := (ey-1)*bounds.Dx() + x0; addr < (ey-1)*bounds.Dx()+x1; addr++ {
|
||||||
d.img.Pix[addr] = 255
|
d.img.Pix[addr] = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Left
|
// Left
|
||||||
@ -54,7 +54,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
|
|||||||
y1 := int(math.Min(float64(ey), float64(bounds.Dy())))
|
y1 := int(math.Min(float64(ey), float64(bounds.Dy())))
|
||||||
|
|
||||||
for addr := y0*bounds.Dx() + sx; addr < y1*bounds.Dx()+sx; addr += bounds.Dx() {
|
for addr := y0*bounds.Dx() + sx; addr < y1*bounds.Dx()+sx; addr += bounds.Dx() {
|
||||||
d.img.Pix[addr] = 255
|
d.img.Pix[addr] = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Right
|
// Right
|
||||||
@ -63,7 +63,7 @@ func (d *drawer) PutBar(sx, sy, ex, ey, color int) {
|
|||||||
y1 := int(math.Min(float64(ey), float64(bounds.Dy())))
|
y1 := int(math.Min(float64(ey), float64(bounds.Dy())))
|
||||||
|
|
||||||
for addr := y0*bounds.Dx() + (ex - 1); addr < y1*bounds.Dx()+(ex-1); addr += bounds.Dx() {
|
for addr := y0*bounds.Dx() + (ex - 1); addr < y1*bounds.Dx()+(ex-1); addr += bounds.Dx() {
|
||||||
d.img.Pix[addr] = 255
|
d.img.Pix[addr] = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,76 +2,72 @@ package drawer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"gitea.unprism.ru/yotia/display-test/display"
|
"gitea.unprism.ru/yotia/display-test/display"
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/font/inconsolata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type drawer struct {
|
type drawer struct {
|
||||||
font font.Face
|
dev display.Display
|
||||||
|
|
||||||
dev display.Display
|
img *image.Gray
|
||||||
img *image.Gray
|
|
||||||
imgMutex sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Drawer interface {
|
type Drawer interface {
|
||||||
GetFont() font.Face
|
GetDisplay() display.Display
|
||||||
|
|
||||||
// Lowlevel image
|
W() int
|
||||||
GetImg() *image.Gray
|
H() int
|
||||||
Lock()
|
|
||||||
Unlock()
|
|
||||||
|
|
||||||
Clear()
|
Clear()
|
||||||
Flush() error
|
Flush()
|
||||||
|
|
||||||
PutText(x, y int, text string)
|
PutText(x, y int, text string)
|
||||||
|
|
||||||
PutBar(x0, y0, x1, y1, color int)
|
PutBar(x0, y0, x1, y1 int, color byte)
|
||||||
FillBar(x0, y0, x1, y1, color int)
|
FillBar(x0, y0, x1, y1 int, color byte)
|
||||||
|
|
||||||
|
CopyImg(x0, y0 int, img Image)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(dev display.Display) Drawer {
|
func New(dev display.Display) Drawer {
|
||||||
return &drawer{
|
d := &drawer{
|
||||||
font: inconsolata.Regular8x16,
|
|
||||||
|
|
||||||
dev: dev,
|
dev: dev,
|
||||||
img: image.NewGray(dev.GetBounds()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.img = d.dev.GetImg()
|
||||||
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawer) GetImg() *image.Gray {
|
func (d *drawer) GetDisplay() display.Display {
|
||||||
return d.img
|
return d.dev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawer) GetFont() font.Face {
|
func (d *drawer) W() int {
|
||||||
return d.font
|
return d.img.Rect.Dx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawer) Lock() {
|
func (d *drawer) H() int {
|
||||||
d.imgMutex.Lock()
|
return d.img.Rect.Dy()
|
||||||
}
|
|
||||||
|
|
||||||
func (d *drawer) Unlock() {
|
|
||||||
d.imgMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawer) Clear() {
|
func (d *drawer) Clear() {
|
||||||
d.Lock()
|
d.dev.LockImg()
|
||||||
defer d.Unlock()
|
defer d.dev.UnlockImg()
|
||||||
|
|
||||||
for i := range d.img.Pix {
|
for i := range d.img.Pix {
|
||||||
d.img.Pix[i] = 0
|
d.img.Pix[i] = 0
|
||||||
}
|
}
|
||||||
|
d.dev.FlushByMask(0xFF)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawer) Flush() error {
|
func (d *drawer) Flush() {
|
||||||
d.Lock()
|
// Flush all pages
|
||||||
defer d.Unlock()
|
d.dev.Flush(0, 0)
|
||||||
|
d.dev.Flush(0, 1)
|
||||||
return d.dev.Flush(d.img)
|
d.dev.Flush(0, 2)
|
||||||
|
d.dev.Flush(0, 3)
|
||||||
|
d.dev.Flush(1, 0)
|
||||||
|
d.dev.Flush(1, 1)
|
||||||
|
d.dev.Flush(1, 2)
|
||||||
|
d.dev.Flush(1, 3)
|
||||||
}
|
}
|
||||||
|
354
drawer/font.go
Normal file
354
drawer/font.go
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
package drawer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding/charmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CharGap = 1
|
||||||
|
LineGap = 1
|
||||||
|
FontCharW = 5
|
||||||
|
FontCharH = 7
|
||||||
|
LineH = FontCharH + LineGap
|
||||||
|
)
|
||||||
|
|
||||||
|
// Standard ASCII 5x7 font
|
||||||
|
var stdFont = []byte{
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, // Empty Cell
|
||||||
|
0x3e, 0x5b, 0x4f, 0x5b, 0x3e, // Sad Smiley
|
||||||
|
0x3e, 0x6b, 0x4f, 0x6b, 0x3e, // Happy Smiley
|
||||||
|
0x1c, 0x3e, 0x7c, 0x3e, 0x1c, // Heart
|
||||||
|
0x18, 0x3c, 0x7e, 0x3c, 0x18, // Diamond
|
||||||
|
0x1c, 0x57, 0x7d, 0x57, 0x1c, // Clubs
|
||||||
|
0x1c, 0x5e, 0x7f, 0x5e, 0x1c, // Spades
|
||||||
|
0x18, 0x3c, 0x18, 0x0, 0x0, // Bullet Point
|
||||||
|
0xff, 0xe7, 0xc3, 0xe7, 0xff, // Rev Bullet Point
|
||||||
|
0x18, 0x24, 0x18, 0x0, 0x0, // Hollow Bullet Point
|
||||||
|
0xff, 0xe7, 0xdb, 0xe7, 0xff, // Rev Hollow BP
|
||||||
|
0x30, 0x48, 0x3a, 0x6, 0xe, // Male
|
||||||
|
0x26, 0x29, 0x79, 0x29, 0x26, // Female
|
||||||
|
0x40, 0x7f, 0x5, 0x5, 0x7, // Music Note 1
|
||||||
|
0x40, 0x7f, 0x5, 0x25, 0x3f, // Music Note 2
|
||||||
|
0x5a, 0x3c, 0xe7, 0x3c, 0x5a, // Snowflake
|
||||||
|
0x7f, 0x3e, 0x1c, 0x1c, 0x8, // Right Pointer
|
||||||
|
0x8, 0x1c, 0x1c, 0x3e, 0x7f, // Left Pointer
|
||||||
|
0x14, 0x22, 0x7f, 0x22, 0x14, // UpDown Arrows
|
||||||
|
0x5f, 0x5f, 0x0, 0x5f, 0x5f, // Double Exclamation
|
||||||
|
0x6, 0x9, 0x7f, 0x1, 0x7f, // Paragraph Mark
|
||||||
|
0x66, 0x89, 0x95, 0x6a, 0x0, // Section Mark
|
||||||
|
0x60, 0x60, 0x60, 0x60, 0x60, // Double Underline
|
||||||
|
0x94, 0xa2, 0xff, 0xa2, 0x94, // UpDown Underlined
|
||||||
|
0x8, 0x4, 0x7e, 0x4, 0x8, // Up Arrow
|
||||||
|
0x10, 0x20, 0x7e, 0x20, 0x10, // Down Arrow
|
||||||
|
0x8, 0x8, 0x2a, 0x1c, 0x8, // Right Arrow
|
||||||
|
0x8, 0x1c, 0x2a, 0x8, 0x8, // Left Arrow
|
||||||
|
0x1e, 0x10, 0x10, 0x10, 0x10, // Angled
|
||||||
|
0xc, 0x1e, 0xc, 0x1e, 0xc, // Squashed #
|
||||||
|
0x30, 0x38, 0x3e, 0x38, 0x30, // Up Pointer
|
||||||
|
0x6, 0xe, 0x3e, 0xe, 0x6, // Down Pointer
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, // Space
|
||||||
|
0x5f, 0x0, 0x0, 0x0, 0x0, // !
|
||||||
|
0x3, 0x0, 0x3, 0x0, 0x0, // "
|
||||||
|
0x14, 0x3e, 0x14, 0x3e, 0x14, // #
|
||||||
|
0x24, 0x6a, 0x2b, 0x12, 0x0, // $
|
||||||
|
0x63, 0x13, 0x8, 0x64, 0x63, // %
|
||||||
|
0x36, 0x49, 0x56, 0x20, 0x50, // &
|
||||||
|
0x3, 0x0, 0x0, 0x0, 0x0, // '
|
||||||
|
0x1c, 0x22, 0x41, 0x0, 0x0, // (
|
||||||
|
0x41, 0x22, 0x1c, 0x0, 0x0, // )
|
||||||
|
0x28, 0x18, 0xe, 0x18, 0x28, // *
|
||||||
|
0x8, 0x8, 0x3e, 0x8, 0x8, // +
|
||||||
|
0xb0, 0x70, 0x0, 0x0, 0x0, // ,
|
||||||
|
0x8, 0x8, 0x8, 0x8, 0x0, // -
|
||||||
|
0x60, 0x60, 0x0, 0x0, 0x0, // .
|
||||||
|
0x60, 0x18, 0x6, 0x1, 0x0, // /
|
||||||
|
0x3e, 0x41, 0x41, 0x3e, 0x0, // 0
|
||||||
|
0x42, 0x7f, 0x40, 0x0, 0x0, // 1
|
||||||
|
0x62, 0x51, 0x49, 0x46, 0x0, // 2
|
||||||
|
0x22, 0x41, 0x49, 0x36, 0x0, // 3
|
||||||
|
0x18, 0x14, 0x12, 0x7f, 0x0, // 4
|
||||||
|
0x27, 0x45, 0x45, 0x39, 0x0, // 5
|
||||||
|
0x3e, 0x49, 0x49, 0x30, 0x0, // 6
|
||||||
|
0x61, 0x11, 0x9, 0x7, 0x0, // 7
|
||||||
|
0x36, 0x49, 0x49, 0x36, 0x0, // 8
|
||||||
|
0x6, 0x49, 0x49, 0x3e, 0x0, // 9
|
||||||
|
0x00, 0x0, 0x22, 0x0, 0x0, // :
|
||||||
|
0x80, 0x50, 0x0, 0x0, 0x0, // ;
|
||||||
|
0x10, 0x28, 0x44, 0x0, 0x0, // <
|
||||||
|
0x14, 0x14, 0x14, 0x0, 0x0, // =
|
||||||
|
0x44, 0x28, 0x10, 0x0, 0x0, // >
|
||||||
|
0x2, 0x59, 0x9, 0x6, 0x0, // ?
|
||||||
|
0x3e, 0x49, 0x55, 0x5d, 0xe, // @
|
||||||
|
0x7e, 0x11, 0x11, 0x7e, 0x0, // A
|
||||||
|
0x7f, 0x49, 0x49, 0x36, 0x0, // B
|
||||||
|
0x3e, 0x41, 0x41, 0x22, 0x0, // C
|
||||||
|
0x7f, 0x41, 0x41, 0x3e, 0x0, // D
|
||||||
|
0x7f, 0x49, 0x49, 0x41, 0x0, // E
|
||||||
|
0x7f, 0x9, 0x9, 0x1, 0x0, // F
|
||||||
|
0x3e, 0x41, 0x49, 0x7a, 0x0, // G
|
||||||
|
0x7f, 0x8, 0x8, 0x7f, 0x0, // H
|
||||||
|
0x41, 0x7f, 0x41, 0x0, 0x0, // I
|
||||||
|
0x30, 0x40, 0x41, 0x3f, 0x0, // J
|
||||||
|
0x7f, 0x8, 0x14, 0x63, 0x0, // K
|
||||||
|
0x7f, 0x40, 0x40, 0x40, 0x0, // L
|
||||||
|
0x7f, 0x2, 0xc, 0x2, 0x7f, // M
|
||||||
|
0x7f, 0x4, 0x8, 0x10, 0x7f, // N
|
||||||
|
0x3e, 0x41, 0x41, 0x3e, 0x0, // O
|
||||||
|
0x7f, 0x9, 0x9, 0x6, 0x0, // P
|
||||||
|
0x3e, 0x41, 0x41, 0xbe, 0x0, // Q
|
||||||
|
0x7f, 0x9, 0x9, 0x76, 0x0, // R
|
||||||
|
0x46, 0x49, 0x49, 0x32, 0x0, // S
|
||||||
|
0x1, 0x1, 0x7f, 0x1, 0x1, // T
|
||||||
|
0x3f, 0x40, 0x40, 0x3f, 0x0, // U
|
||||||
|
0xf, 0x30, 0x40, 0x30, 0xf, // V
|
||||||
|
0x3f, 0x40, 0x38, 0x40, 0x3f, // W
|
||||||
|
0x63, 0x14, 0x8, 0x14, 0x63, // X
|
||||||
|
0x7, 0x8, 0x70, 0x8, 0x7, // Y
|
||||||
|
0x61, 0x51, 0x49, 0x47, 0x0, // Z
|
||||||
|
0x7f, 0x41, 0x0, 0x0, 0x0, // [
|
||||||
|
0x1, 0x6, 0x18, 0x60, 0x0, //
|
||||||
|
0x41, 0x7f, 0x0, 0x0, 0x0, // ]
|
||||||
|
0x2, 0x1, 0x2, 0x0, 0x0, // ^
|
||||||
|
0x40, 0x40, 0x40, 0x40, 0x0, // _
|
||||||
|
0x1, 0x2, 0x0, 0x0, 0x0, // `
|
||||||
|
0x20, 0x54, 0x54, 0x78, 0x0, // a
|
||||||
|
0x7f, 0x44, 0x44, 0x38, 0x0, // b
|
||||||
|
0x38, 0x44, 0x44, 0x28, 0x0, // c
|
||||||
|
0x38, 0x44, 0x44, 0x7f, 0x0, // d
|
||||||
|
0x38, 0x54, 0x54, 0x18, 0x0, // e
|
||||||
|
0x4, 0x7e, 0x5, 0x0, 0x0, // f
|
||||||
|
0x98, 0xa4, 0xa4, 0x78, 0x0, // g
|
||||||
|
0x7f, 0x4, 0x4, 0x78, 0x0, // h
|
||||||
|
0x44, 0x7d, 0x40, 0x0, 0x0, // i
|
||||||
|
0x40, 0x80, 0x84, 0x7d, 0x0, // j
|
||||||
|
0x7f, 0x10, 0x28, 0x44, 0x0, // k
|
||||||
|
0x41, 0x7f, 0x40, 0x0, 0x0, // l
|
||||||
|
0x7c, 0x4, 0x7c, 0x4, 0x78, // m
|
||||||
|
0x7c, 0x4, 0x4, 0x78, 0x0, // n
|
||||||
|
0x38, 0x44, 0x44, 0x38, 0x0, // o
|
||||||
|
0xfc, 0x24, 0x24, 0x18, 0x0, // p
|
||||||
|
0x18, 0x24, 0x24, 0xfc, 0x0, // q
|
||||||
|
0x7c, 0x8, 0x4, 0x4, 0x0, // r
|
||||||
|
0x48, 0x54, 0x54, 0x24, 0x0, // s
|
||||||
|
0x4, 0x3f, 0x44, 0x0, 0x0, // t
|
||||||
|
0x3c, 0x40, 0x40, 0x7c, 0x0, // u
|
||||||
|
0x1c, 0x20, 0x40, 0x20, 0x1c, // v
|
||||||
|
0x3c, 0x40, 0x3c, 0x40, 0x3c, // w
|
||||||
|
0x44, 0x28, 0x10, 0x28, 0x44, // x
|
||||||
|
0x9c, 0xa0, 0xa0, 0x7c, 0x0, // y
|
||||||
|
0x64, 0x54, 0x4c, 0x0, 0x0, // z
|
||||||
|
0x8, 0x36, 0x41, 0x0, 0x0, // {
|
||||||
|
0x7f, 0x0, 0x0, 0x0, 0x0, // |
|
||||||
|
0x41, 0x36, 0x8, 0x0, 0x0, // }
|
||||||
|
0x8, 0x4, 0x8, 0x4, 0x0, // ~
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, // Hollow Up Arrow
|
||||||
|
0x7f, 0x55, 0x49, 0x55, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
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
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7f, 0x41, 0x41, 0x41, 0x7f, // Empty
|
||||||
|
0x7e, 0x11, 0x11, 0x7e, 0x0, // А
|
||||||
|
0x7f, 0x49, 0x49, 0x31, 0x0, // Б
|
||||||
|
0x7f, 0x49, 0x49, 0x36, 0x0, // В
|
||||||
|
0x7f, 0x1, 0x1, 0x1, 0x0, // Г
|
||||||
|
0xc0, 0x7e, 0x41, 0x7f, 0xc0, // Д
|
||||||
|
0x7f, 0x49, 0x49, 0x41, 0x0, // Е
|
||||||
|
0x77, 0x8, 0x7f, 0x8, 0x77, // Ж
|
||||||
|
0x22, 0x41, 0x49, 0x36, 0x0, // З
|
||||||
|
0x7f, 0x20, 0x10, 0x7f, 0x0, // И
|
||||||
|
0x7f, 0x20, 0x11, 0x7d, 0x0, // Й
|
||||||
|
0x7f, 0x8, 0x14, 0x63, 0x0, // К
|
||||||
|
0x40, 0x3c, 0x2, 0x7f, 0x0, // Л
|
||||||
|
0x7f, 0x2, 0xc, 0x2, 0x7f, // М
|
||||||
|
0x7f, 0x8, 0x8, 0x7f, 0x0, // Н
|
||||||
|
0x3e, 0x41, 0x41, 0x3e, 0x0, // О
|
||||||
|
0x7f, 0x1, 0x1, 0x7f, 0x0, // П
|
||||||
|
0x7f, 0x9, 0x9, 0x6, 0x0, // Р
|
||||||
|
0x3e, 0x41, 0x41, 0x22, 0x0, // С
|
||||||
|
0x1, 0x1, 0x7f, 0x1, 0x1, // Т
|
||||||
|
0x47, 0x48, 0x48, 0x3f, 0x0, // У
|
||||||
|
0xe, 0x11, 0x7f, 0x11, 0xe, // Ф
|
||||||
|
0x63, 0x14, 0x8, 0x14, 0x63, // Х
|
||||||
|
0x7f, 0x40, 0x40, 0x7f, 0xc0, // Ц
|
||||||
|
0xf, 0x8, 0x8, 0x7f, 0x0, // Ч
|
||||||
|
0x7f, 0x40, 0x7c, 0x40, 0x7f, // Ш
|
||||||
|
0x7f, 0x40, 0x7c, 0x40, 0xff, // Щ
|
||||||
|
0x1, 0x7f, 0x48, 0x48, 0x30, // Ъ
|
||||||
|
0x7f, 0x48, 0x30, 0x0, 0x7f, // Ы
|
||||||
|
0x7f, 0x48, 0x48, 0x30, 0x0, // Ь
|
||||||
|
0x41, 0x49, 0x49, 0x3e, 0x0, // Э
|
||||||
|
0x7f, 0x8, 0x3e, 0x41, 0x3e, // Ю
|
||||||
|
0x46, 0x29, 0x19, 0x7f, 0x0, // Я
|
||||||
|
0x20, 0x54, 0x54, 0x78, 0x0, // а
|
||||||
|
0x3c, 0x4a, 0x4a, 0x31, 0x0, // б
|
||||||
|
0x7e, 0x59, 0x56, 0x20, 0x0, // в
|
||||||
|
0x7c, 0x4, 0x4, 0x4, 0x0, // г
|
||||||
|
0xc0, 0x78, 0x44, 0x7c, 0xc0, // д
|
||||||
|
0x38, 0x54, 0x54, 0x18, 0x0, // е
|
||||||
|
0x6c, 0x10, 0x7c, 0x10, 0x6c, // ж
|
||||||
|
0x48, 0x84, 0x94, 0x68, 0x0, // з
|
||||||
|
0x7c, 0x20, 0x10, 0x7c, 0x0, // и
|
||||||
|
0x7c, 0x21, 0x11, 0x7c, 0x0, // й
|
||||||
|
0x7c, 0x10, 0x28, 0x44, 0x0, // к
|
||||||
|
0x40, 0x38, 0x4, 0x7c, 0x0, // л
|
||||||
|
0x7c, 0x8, 0x10, 0x8, 0x7c, // м
|
||||||
|
0x7c, 0x10, 0x10, 0x7c, 0x0, // н
|
||||||
|
0x38, 0x44, 0x44, 0x38, 0x0, // о
|
||||||
|
0x7c, 0x4, 0x4, 0x7c, 0x0, // п
|
||||||
|
0xfc, 0x24, 0x24, 0x18, 0x0, // р
|
||||||
|
0x38, 0x44, 0x44, 0x28, 0x0, // с
|
||||||
|
0x4, 0x7c, 0x4, 0x0, 0x0, // т
|
||||||
|
0x9c, 0xa0, 0xa0, 0x7c, 0x0, // у
|
||||||
|
0x18, 0x24, 0xfc, 0x24, 0x18, // ф
|
||||||
|
0x44, 0x28, 0x10, 0x28, 0x44, // х
|
||||||
|
0x7c, 0x40, 0x40, 0x7c, 0xc0, // ц
|
||||||
|
0x1c, 0x10, 0x10, 0x7c, 0x0, // ч
|
||||||
|
0x7c, 0x40, 0x70, 0x40, 0x7c, // ш
|
||||||
|
0x7c, 0x40, 0x70, 0x40, 0xfc, // щ
|
||||||
|
0x4, 0x7c, 0x48, 0x48, 0x30, // ъ
|
||||||
|
0x7c, 0x48, 0x30, 0x0, 0x7c, // ы
|
||||||
|
0x7c, 0x48, 0x48, 0x30, 0x0, // ь
|
||||||
|
0x44, 0x54, 0x54, 0x38, 0x0, // э
|
||||||
|
0x7c, 0x10, 0x38, 0x44, 0x38, // ю
|
||||||
|
0x48, 0x34, 0x14, 0x7c, 0x0, // я
|
||||||
|
}
|
||||||
|
|
||||||
|
func IntMin(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func IntMax(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *drawer) putChar(x0, y0 int, c byte) {
|
||||||
|
if int(c) > len(stdFont)/5 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//if x0+7 < 0 || y0+5 < 0 || y0 >= d.img.Rect.Dy() || x0 >= d.img.Rect.Dx() {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
minX := IntMax(x0, 0) - x0
|
||||||
|
minY := IntMax(y0, 0) - y0
|
||||||
|
|
||||||
|
maxY := IntMin(y0+7, d.img.Rect.Dy()) - y0
|
||||||
|
maxX := IntMin(x0+5, d.img.Rect.Dx()) - x0
|
||||||
|
|
||||||
|
for yi := minY; yi < maxY; yi++ {
|
||||||
|
lineaddr := (y0+yi)*d.img.Stride + x0
|
||||||
|
for xi := minX; xi < maxX; xi++ {
|
||||||
|
d.img.Pix[lineaddr+xi] = ((stdFont[int(c)*5+xi] >> yi) & 1) * 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStr(str string) []byte {
|
||||||
|
out, err := charmap.Windows1251.NewEncoder().Bytes([]byte(str))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ENCODE ERROR: ", err.Error())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *drawer) PutText(x0, y0 int, text string) {
|
||||||
|
d.dev.LockImg()
|
||||||
|
defer d.dev.UnlockImg()
|
||||||
|
|
||||||
|
asciiStr := convertStr(text)
|
||||||
|
//asciiStr := []byte(text)
|
||||||
|
|
||||||
|
// Create font drawer
|
||||||
|
x := x0
|
||||||
|
for _, c := range asciiStr {
|
||||||
|
d.putChar(x, y0, c)
|
||||||
|
x += CharGap + FontCharW
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate update mask
|
||||||
|
mask := uint32(0)
|
||||||
|
|
||||||
|
// Implementation dependent !!!
|
||||||
|
// Now for mt12232
|
||||||
|
my0 := min(3, max(0, int((y0)/8)))
|
||||||
|
my1 := min(3, max(0, int((y0+FontCharH-1)/8)))
|
||||||
|
|
||||||
|
mx0 := min(1, max(0, int(x0/61)))
|
||||||
|
mx1 := min(1, max(0, int((x0+FontCharW*len(text)-1)/61)))
|
||||||
|
|
||||||
|
//log.Println(x0, y0, x1, y1)
|
||||||
|
for y := my0; y <= my1; y++ {
|
||||||
|
for x := mx0; x <= mx1; x++ {
|
||||||
|
mask |= (1 << (x*4 + y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.dev.FlushByMask(mask)
|
||||||
|
}
|
167
drawer/image.go
Normal file
167
drawer/image.go
Normal file
@ -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])
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
package drawer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *drawer) PutText(x, y int, text string) {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
|
|
||||||
// Create font drawer
|
|
||||||
col := color.Gray{255}
|
|
||||||
point := fixed.Point26_6{X: fixed.I(x), Y: fixed.I(y)}
|
|
||||||
|
|
||||||
drawer := &font.Drawer{
|
|
||||||
Dst: d.img,
|
|
||||||
Src: image.NewUniform(col),
|
|
||||||
Face: d.font,
|
|
||||||
Dot: point,
|
|
||||||
}
|
|
||||||
drawer.DrawString(text)
|
|
||||||
}
|
|
6
go.mod
6
go.mod
@ -3,9 +3,10 @@ module gitea.unprism.ru/yotia/display-test
|
|||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.unprism.ru/KRBL/sim-modem v0.1.7
|
gitea.unprism.ru/KRBL/mpu v0.1.0
|
||||||
|
gitea.unprism.ru/KRBL/sim-modem v0.1.8
|
||||||
github.com/stianeikeland/go-rpio/v4 v4.6.0
|
github.com/stianeikeland/go-rpio/v4 v4.6.0
|
||||||
golang.org/x/image v0.19.0
|
golang.org/x/text v0.17.0
|
||||||
periph.io/x/conn/v3 v3.7.1
|
periph.io/x/conn/v3 v3.7.1
|
||||||
periph.io/x/devices/v3 v3.7.1
|
periph.io/x/devices/v3 v3.7.1
|
||||||
periph.io/x/host/v3 v3.8.2
|
periph.io/x/host/v3 v3.8.2
|
||||||
@ -14,5 +15,6 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/creack/goselect v0.1.2 // indirect
|
github.com/creack/goselect v0.1.2 // indirect
|
||||||
go.bug.st/serial v1.6.2 // indirect
|
go.bug.st/serial v1.6.2 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
golang.org/x/sys v0.24.0 // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
)
|
)
|
||||||
|
272
main.go
272
main.go
@ -4,15 +4,20 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/yotia/display-test/api"
|
||||||
|
"gitea.unprism.ru/yotia/display-test/api/progress"
|
||||||
|
"gitea.unprism.ru/yotia/display-test/components"
|
||||||
"gitea.unprism.ru/yotia/display-test/display"
|
"gitea.unprism.ru/yotia/display-test/display"
|
||||||
"gitea.unprism.ru/yotia/display-test/drawer"
|
"gitea.unprism.ru/yotia/display-test/drawer"
|
||||||
|
|
||||||
"gitea.unprism.ru/KRBL/sim-modem/api/modem"
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem"
|
||||||
|
"gitea.unprism.ru/KRBL/sim-modem/api/modem/gps"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,138 +47,195 @@ var d drawer.Drawer
|
|||||||
|
|
||||||
var m modem.Modem
|
var m modem.Modem
|
||||||
|
|
||||||
func DrawProgress(ctx context.Context, timeout time.Duration) error {
|
|
||||||
deadline := time.Now().Add(timeout)
|
|
||||||
|
|
||||||
// Draw outline
|
|
||||||
x := 0
|
|
||||||
y := d.GetFont().Metrics().Height.Ceil() + 4
|
|
||||||
w := d.GetImg().Rect.Dx()
|
|
||||||
h := d.GetFont().Metrics().Height.Ceil() - 4
|
|
||||||
|
|
||||||
d.PutBar(x, y, x+w, y+h, 255)
|
|
||||||
gap := 3
|
|
||||||
|
|
||||||
// Draw body
|
|
||||||
x += gap
|
|
||||||
y += gap
|
|
||||||
w -= 2 * gap
|
|
||||||
h -= 2 * gap
|
|
||||||
lastX := x
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
case <-time.After(displayUpdateTimeout):
|
|
||||||
value := 1 - float64(time.Until(deadline).Milliseconds())/float64(timeout.Milliseconds())
|
|
||||||
newX := x + int(value*float64(w))
|
|
||||||
// Draw
|
|
||||||
d.FillBar(lastX, y, newX, y+h, 255)
|
|
||||||
lastX = newX
|
|
||||||
if value >= 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := d.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("display flush: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init(ctx context.Context) error {
|
func Init(ctx context.Context) error {
|
||||||
// Display init
|
// Display init
|
||||||
var err error
|
var err error
|
||||||
dev, err = display.New(log.New(log.Writer(), "display", log.LstdFlags), display.SSD1306)
|
dev, err = display.New(log.New(log.Writer(), "display", log.LstdFlags), display.MT12232A)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("new display: %w", err)
|
return fmt.Errorf("new display: %w", err)
|
||||||
}
|
}
|
||||||
logger.Println("Display inited")
|
logger.Println("Display inited")
|
||||||
|
|
||||||
|
// Create drawer
|
||||||
d := drawer.New(dev)
|
d := drawer.New(dev)
|
||||||
|
|
||||||
d.Clear()
|
d.Clear()
|
||||||
d.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Initing modem...")
|
//d.FillBar(0, 0, 10, 10, 1)
|
||||||
|
d.PutText(2, 0, "12345678901234567890")
|
||||||
|
d.PutText(2, drawer.LineH, "WWWWWWWWWWWWWWWWWWWWWWW")
|
||||||
|
d.PutText(1, drawer.LineH*2, "Progress bar")
|
||||||
d.Flush()
|
d.Flush()
|
||||||
|
|
||||||
// Modem init
|
time.Sleep(time.Second)
|
||||||
mInitCtx, mInitCtxCancel := context.WithCancel(ctx)
|
|
||||||
go DrawProgress(mInitCtx, 13*time.Second)
|
|
||||||
defer mInitCtxCancel()
|
|
||||||
|
|
||||||
m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
|
pbc, err := components.NewProgressBar(d, 0, drawer.LineH*3, d.W(), drawer.LineH)
|
||||||
if err := m.Init(); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("modem init: %w", err)
|
return fmt.Errorf("new progress bar: %w", err)
|
||||||
}
|
}
|
||||||
logger.Println("Display inited")
|
|
||||||
mInitCtxCancel()
|
|
||||||
|
|
||||||
d.Clear()
|
pb := progress.NewHandler(pbc, 10)
|
||||||
d.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Modem inited.")
|
|
||||||
d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, "Starting test...")
|
|
||||||
d.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func Close() {
|
|
||||||
m.Close()
|
|
||||||
if d != nil {
|
|
||||||
d.Clear()
|
|
||||||
d.Flush()
|
|
||||||
}
|
|
||||||
dev.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func MainLoop(ctx context.Context) error {
|
for range 10 {
|
||||||
mainLoopTimeout := time.Second
|
|
||||||
|
|
||||||
frameCounter := 0
|
|
||||||
deadline := time.Now().Add(100 * time.Millisecond)
|
|
||||||
for {
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
case <-time.After(time.Until(deadline)):
|
case <-time.After(time.Duration(rand.Int()%1000) * time.Millisecond):
|
||||||
deadline = time.Now().Add(mainLoopTimeout)
|
|
||||||
|
|
||||||
st, _ := m.Gps().GetStatus()
|
|
||||||
data := m.GetData()
|
|
||||||
|
|
||||||
coordsStr := fmt.Sprintf("%03d°%02.0f'%s %03d°%02.0f'%s",
|
|
||||||
int(data.Latitude), (data.Latitude-float64(int(data.Latitude)))*100, data.LatitudeIndicator,
|
|
||||||
int(data.Longitude), (data.Longitude-float64(int(data.Longitude)))*100, data.LongitudeIndicator)
|
|
||||||
var str1 string
|
|
||||||
var str2 string
|
|
||||||
|
|
||||||
if frameCounter%4 < 2 {
|
|
||||||
str2 = fmt.Sprintf("Sat. count: %d", st.FoundSatelitesCount)
|
|
||||||
} else {
|
|
||||||
str2 = coordsStr
|
|
||||||
}
|
|
||||||
str1 = fmt.Sprintf("%s", time.Now().Format("15:04:05"))
|
|
||||||
|
|
||||||
d.Clear()
|
|
||||||
d.PutText(0, d.GetFont().Metrics().Height.Ceil(), str1)
|
|
||||||
d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, str2)
|
|
||||||
d.Flush()
|
|
||||||
frameCounter++
|
|
||||||
}
|
}
|
||||||
|
pb.Checkpoint()
|
||||||
}
|
}
|
||||||
|
time.Sleep(400 * time.Millisecond)
|
||||||
|
|
||||||
|
pb.GetBar().Clear()
|
||||||
|
pb.Close()
|
||||||
|
|
||||||
|
d.FillBar(0, drawer.LineH*2, d.W(), drawer.LineH*3, 0)
|
||||||
|
d.PutText(1, drawer.LineH*2, "Done")
|
||||||
|
d.Flush()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
//d.PutText(0, 0, "можно 4 строки впихнуть")
|
||||||
|
//d.PutText(0, drawer.FontCharH+drawer.LineGap, "каждая буква 5x7")
|
||||||
|
//d.PutText(0, (drawer.FontCharH+drawer.LineGap)*2, "+ русский через жопу")
|
||||||
|
//d.PutText(0, (drawer.FontCharH+drawer.LineGap)*3, "KRBL forever!!")
|
||||||
|
|
||||||
|
// Modem init
|
||||||
|
//mInitCtx, mInitCtxCancel := context.WithCancel(ctx)
|
||||||
|
//go DrawProgress(mInitCtx, 13*time.Second)
|
||||||
|
//defer mInitCtxCancel()
|
||||||
|
//
|
||||||
|
//m = modem.New(log.New(logger.Writer(), "modem : ", log.LstdFlags))
|
||||||
|
//if err := m.Init(); err != nil {
|
||||||
|
// return fmt.Errorf("modem init: %w", err)
|
||||||
|
//}
|
||||||
|
//logger.Println("Display inited")
|
||||||
|
//mInitCtxCancel()
|
||||||
|
//
|
||||||
|
//d.Clear()
|
||||||
|
//d.PutText(0, d.GetFont().Metrics().Height.Ceil(), "Modem inited.")
|
||||||
|
//d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, "Starting test...")
|
||||||
|
//d.Flush()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
func Close() {
|
||||||
|
//m.Close()
|
||||||
|
//if d != nil {
|
||||||
|
// d.Clear()
|
||||||
|
// d.Flush()
|
||||||
|
//}
|
||||||
|
dev.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
//func MainLoop(ctx context.Context) error {
|
||||||
|
// mainLoopTimeout := time.Second
|
||||||
|
//
|
||||||
|
// frameCounter := 0
|
||||||
|
// deadline := time.Now().Add(100 * time.Millisecond)
|
||||||
|
// for {
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return nil
|
||||||
|
// case <-time.After(time.Until(deadline)):
|
||||||
|
// deadline = time.Now().Add(mainLoopTimeout)
|
||||||
|
//
|
||||||
|
// st, _ := m.Gps().GetStatus()
|
||||||
|
// data := m.GetData()
|
||||||
|
//
|
||||||
|
// coordsStr := fmt.Sprintf("%03d°%02.0f'%s %03d°%02.0f'%s",
|
||||||
|
// int(data.Latitude), (data.Latitude-float64(int(data.Latitude)))*100, data.LatitudeIndicator,
|
||||||
|
// int(data.Longitude), (data.Longitude-float64(int(data.Longitude)))*100, data.LongitudeIndicator)
|
||||||
|
// var str1 string
|
||||||
|
// var str2 string
|
||||||
|
//
|
||||||
|
// if frameCounter%4 < 2 {
|
||||||
|
// str2 = fmt.Sprintf("Sat. count: %d", st.FoundSatelitesCount)
|
||||||
|
// } else {
|
||||||
|
// str2 = coordsStr
|
||||||
|
// }
|
||||||
|
// str1 = fmt.Sprintf("%s", time.Now().Format("15:04:05"))
|
||||||
|
//
|
||||||
|
// d.Clear()
|
||||||
|
// d.PutText(0, d.GetFont().Metrics().Height.Ceil(), str1)
|
||||||
|
// d.PutText(0, d.GetFont().Metrics().Height.Ceil()*2, str2)
|
||||||
|
// d.Flush()
|
||||||
|
// frameCounter++
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
func mainE(ctx context.Context) error {
|
func mainE(ctx context.Context) error {
|
||||||
logger = log.New(os.Stdout, "main : ", log.LstdFlags)
|
logger = log.New(os.Stdout, "main : ", log.LstdFlags)
|
||||||
//
|
//
|
||||||
disp, err := display.New(logger, display.MT12864A)
|
//if err := Init(ctx); err != nil {
|
||||||
if err != nil {
|
// return err
|
||||||
return err
|
//}
|
||||||
}
|
////if err := dev.Test(ctx); err != nil {
|
||||||
time.Sleep(2 * time.Second)
|
//// return err
|
||||||
defer disp.Close()
|
////}
|
||||||
// if err := Init(ctx); err != nil {
|
//defer Close()
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// defer Close()
|
|
||||||
// if err := MainLoop(ctx); err != nil {
|
// if err := MainLoop(ctx); err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
return nil
|
|
||||||
|
logger.Println("")
|
||||||
|
|
||||||
|
a, err := api.New()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("new api: %w", err)
|
||||||
|
}
|
||||||
|
defer a.Close()
|
||||||
|
logger.Println("Inited")
|
||||||
|
|
||||||
|
// !!! Init example
|
||||||
|
|
||||||
|
// Setup init page
|
||||||
|
ip := a.SwitchToInitPage()
|
||||||
|
ip.SetTitle("KRBL")
|
||||||
|
pb := progress.NewHandler(ip.GetProgressBar(), 10)
|
||||||
|
|
||||||
|
// Simulate init process
|
||||||
|
for range 10 {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
case <-time.After(60 * time.Millisecond):
|
||||||
|
}
|
||||||
|
pb.Checkpoint()
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// !!! Status page simulating
|
||||||
|
|
||||||
|
// Switch
|
||||||
|
|
||||||
|
services := []string{"NO SIGNAL", "GSM", "WCDMA", "LTE", "TDS"}
|
||||||
|
sp := a.SwitchToStatusPage()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Random rssi
|
||||||
|
sp.SetRssi(rand.Int() % 31)
|
||||||
|
sp.SetService(services[rand.Int()%len(services)])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,10 @@ import (
|
|||||||
"github.com/stianeikeland/go-rpio/v4"
|
"github.com/stianeikeland/go-rpio/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
waitReadyTimeout = time.Microsecond
|
||||||
|
)
|
||||||
|
|
||||||
type mt12232a struct {
|
type mt12232a struct {
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
|
||||||
@ -20,16 +24,13 @@ type mt12232a struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Device interface {
|
type Device interface {
|
||||||
PowerOn() error
|
Reset() error
|
||||||
SetPixel(x, y byte, c bool) error
|
|
||||||
|
|
||||||
WriteCodeL(c byte) error
|
WriteCode(cs rpio.State, c byte) error
|
||||||
WriteCodeR(c byte) error
|
WriteData(cs rpio.State, b byte) error
|
||||||
WriteDataL(b byte) error
|
WriteDatas(cs rpio.State, b []byte) error
|
||||||
WriteDataR(b byte) error
|
|
||||||
|
|
||||||
ReadDataL() (byte, error)
|
ReadData(cs rpio.State) (byte, error)
|
||||||
ReadDataR() (byte, error)
|
|
||||||
ReadStatus(cs rpio.State) byte
|
ReadStatus(cs rpio.State) byte
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
@ -42,138 +43,88 @@ func New(logger *log.Logger) (Device, error) {
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
||||||
dev: parallel8bit.New(logger, parallel8bit.DevicePins{
|
dev: parallel8bit.New(logger, parallel8bit.DevicePins{
|
||||||
PinA0: rpio.Pin(18),
|
PinA0: rpio.Pin(1), // 19
|
||||||
PinRW: rpio.Pin(17),
|
PinRW: rpio.Pin(13),
|
||||||
PinE: rpio.Pin(27),
|
PinE: rpio.Pin(12),
|
||||||
|
|
||||||
PinDB0: rpio.Pin(22),
|
PinDB0: rpio.Pin(22),
|
||||||
PinDB1: rpio.Pin(10),
|
PinDB1: rpio.Pin(10),
|
||||||
PinDB2: rpio.Pin(9), // From 21
|
PinDB2: rpio.Pin(9),
|
||||||
PinDB3: rpio.Pin(11),
|
PinDB3: rpio.Pin(11),
|
||||||
PinDB4: rpio.Pin(12),
|
PinDB4: rpio.Pin(21),
|
||||||
PinDB5: rpio.Pin(16), // From 32
|
PinDB5: rpio.Pin(20),
|
||||||
PinDB6: rpio.Pin(20), // From 31
|
PinDB6: rpio.Pin(26),
|
||||||
PinDB7: rpio.Pin(13), // From 33
|
PinDB7: rpio.Pin(16),
|
||||||
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
pinCS: rpio.Pin(19),
|
pinCS: rpio.Pin(17),
|
||||||
pinRES: rpio.Pin(21),
|
pinRES: rpio.Pin(27),
|
||||||
}
|
}
|
||||||
d.pinCS.Output()
|
d.pinCS.Output()
|
||||||
d.pinRES.Output()
|
d.pinRES.Output()
|
||||||
d.dev.Reset()
|
d.dev.Reset()
|
||||||
|
d.pinCS.Low()
|
||||||
|
d.pinRES.High()
|
||||||
|
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mt12232a) Close() error {
|
func (d *mt12232a) Close() error {
|
||||||
|
d.pinCS.Low()
|
||||||
|
//d.pinRES.Low() // TMP
|
||||||
|
d.dev.Pins().PinA0.Low()
|
||||||
|
d.dev.Pins().PinE.Low()
|
||||||
|
d.dev.Pins().PinRW.Low()
|
||||||
|
d.dev.Pins().PinDB0.Low()
|
||||||
|
d.dev.Pins().PinDB1.Low()
|
||||||
|
d.dev.Pins().PinDB2.Low()
|
||||||
|
d.dev.Pins().PinDB3.Low()
|
||||||
|
d.dev.Pins().PinDB4.Low()
|
||||||
|
d.dev.Pins().PinDB5.Low()
|
||||||
|
d.dev.Pins().PinDB6.Low()
|
||||||
|
d.dev.Pins().PinDB7.Low()
|
||||||
|
|
||||||
return rpio.Close()
|
return rpio.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mt12232a) SetPixel(x, y byte, c bool) error {
|
func (d *mt12232a) status() {
|
||||||
// var c8 byte
|
d.logger.Printf("ST L: %08b R: %08b\n", d.ReadStatus(0)&0xFF, d.ReadStatus(1)&0xFF)
|
||||||
// var mask byte
|
|
||||||
|
|
||||||
// Check bounds
|
|
||||||
if x > 127 || y > 63 {
|
|
||||||
return fmt.Errorf("positions out of bounds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if x < 64 { // Left crystal
|
|
||||||
d.WriteCodeL(0xB8 | (y >> 3)) // Set page
|
|
||||||
d.WriteCodeL(0x40 | x) // Set addr
|
|
||||||
// c8=ReadDataL(); // ?? ok
|
|
||||||
// c8=ReadDataL(); // Read byte
|
|
||||||
// m8=1<<(y&0x07);//Вычислить маску нужного бита в байте
|
|
||||||
// if (c==1) //Зажигать точку?
|
|
||||||
// c8|=m8//Установить нужный бит в байте
|
|
||||||
// else //Или гасить точку?
|
|
||||||
// c8&=~m8;//Сбросить нужный бит в байте
|
|
||||||
// WriteCodeL(0x40|x);//Снова установить адрес нужного байта
|
|
||||||
d.WriteDataL(0x34) // Write byte
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mt12232a) PowerOn() error {
|
func (d *mt12232a) Reset() error {
|
||||||
d.logger.Println(d.ReadStatus(0)) // Should be 0
|
|
||||||
d.logger.Println("Reset")
|
|
||||||
d.pinRES.Low()
|
d.pinRES.Low()
|
||||||
time.Sleep(time.Microsecond)
|
|
||||||
d.logger.Println(d.ReadStatus(0)) // Should be 48 (power off and reset)
|
|
||||||
d.pinRES.High()
|
|
||||||
time.Sleep(10 * time.Microsecond)
|
time.Sleep(10 * time.Microsecond)
|
||||||
d.logger.Println(d.ReadStatus(0)) // Should be 32 (power off)
|
d.pinRES.High()
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
// Module is reset and should be turned off
|
|
||||||
if d.ReadStatus(0) == 0 || d.ReadStatus(1) == 0 {
|
|
||||||
return fmt.Errorf("no response from display(or it is possible that it is turned on but...)")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.logger.Println("Power on")
|
|
||||||
d.WriteCodeL(0xE2) // Reset
|
|
||||||
d.WriteCodeR(0xE2) // Reset
|
|
||||||
d.WriteCodeL(0xEE) // ReadModifyWrite off
|
|
||||||
d.WriteCodeR(0xEE) // ReadModifyWrite off
|
|
||||||
d.WriteCodeL(0xA4) // Turn on common mode
|
|
||||||
d.WriteCodeR(0xA4) // Turn on common mode
|
|
||||||
d.WriteCodeL(0xA9) // Multiplex 1/32
|
|
||||||
d.WriteCodeR(0xA9) // Multiplex 1/32
|
|
||||||
d.WriteCodeL(0xC0) // Top line to 0
|
|
||||||
d.WriteCodeR(0xC0) // Top line to 0
|
|
||||||
d.WriteCodeL(0xA1) // Invert scan RAM
|
|
||||||
d.WriteCodeR(0xA0) // NonInvert scan RAM
|
|
||||||
|
|
||||||
d.logger.Println("Display on")
|
|
||||||
d.WriteCodeL(0xAF) // Display on
|
|
||||||
d.WriteCodeR(0xAF) // Display on
|
|
||||||
|
|
||||||
// Check that crystals are turned on
|
|
||||||
if (d.ReadStatus(0) & (1 << 5)) != 0 {
|
|
||||||
return fmt.Errorf("Left cristal is still off")
|
|
||||||
}
|
|
||||||
if (d.ReadStatus(1) & (1 << 5)) != 0 {
|
|
||||||
return fmt.Errorf("Right cristal is still off")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write codes
|
// Write codes
|
||||||
|
|
||||||
func (d *mt12232a) WriteCodeL(c byte) error {
|
func (d *mt12232a) WriteCode(cs rpio.State, c byte) error {
|
||||||
return d.writeByte(c, 0, 0)
|
return d.writeByte(c, 0, cs)
|
||||||
}
|
|
||||||
|
|
||||||
func (d *mt12232a) WriteCodeR(c byte) error {
|
|
||||||
return d.writeByte(c, 0, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write data as byte
|
// Write data as byte
|
||||||
|
|
||||||
func (d *mt12232a) WriteDataL(b byte) error {
|
func (d *mt12232a) WriteData(cs rpio.State, b byte) error {
|
||||||
return d.writeByte(b, 1, 0)
|
return d.writeByte(b, 1, cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mt12232a) WriteDataR(b byte) error {
|
func (d *mt12232a) WriteDatas(cs rpio.State, b []byte) error {
|
||||||
return d.writeByte(b, 1, 1)
|
return d.writeBytes(b, 1, cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
|
|
||||||
func (d *mt12232a) ReadDataL() (byte, error) {
|
func (d *mt12232a) ReadData(cs rpio.State) (byte, error) {
|
||||||
return d.readByte(1, 0)
|
return d.readByte(1, cs)
|
||||||
}
|
|
||||||
|
|
||||||
func (d *mt12232a) ReadDataR() (byte, error) {
|
|
||||||
return d.readByte(1, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Low level functions
|
// Low level functions
|
||||||
|
|
||||||
func (d *mt12232a) writeByte(b byte, cd, cs rpio.State) error {
|
func (d *mt12232a) writeByte(b byte, cd, cs rpio.State) error {
|
||||||
// d.logger.Println("Write byte", b, cd, l, r)
|
|
||||||
if err := d.waitReady(cs); err != nil {
|
if err := d.waitReady(cs); err != nil {
|
||||||
return fmt.Errorf("wait ready: %w", err)
|
return fmt.Errorf("wait ready: %w", err)
|
||||||
}
|
}
|
||||||
@ -183,6 +134,16 @@ func (d *mt12232a) writeByte(b byte, cd, cs rpio.State) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *mt12232a) writeBytes(b []byte, cd, cs rpio.State) error {
|
||||||
|
if err := d.waitReady(cs); err != nil {
|
||||||
|
return fmt.Errorf("wait ready: %w", err)
|
||||||
|
}
|
||||||
|
d.pinCS.Write(cs) // Select cristals
|
||||||
|
|
||||||
|
d.dev.WriteBytes(b, cd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *mt12232a) readByte(cd, cs rpio.State) (byte, error) {
|
func (d *mt12232a) readByte(cd, cs rpio.State) (byte, error) {
|
||||||
// Setup
|
// Setup
|
||||||
if err := d.waitReady(cs); err != nil {
|
if err := d.waitReady(cs); err != nil {
|
||||||
@ -195,11 +156,13 @@ func (d *mt12232a) readByte(cd, cs rpio.State) (byte, error) {
|
|||||||
|
|
||||||
// Wait, checking status byte
|
// Wait, checking status byte
|
||||||
func (d *mt12232a) waitReady(cs rpio.State) error {
|
func (d *mt12232a) waitReady(cs rpio.State) error {
|
||||||
d.pinCS.Write(cs) // Select cristals
|
// d.pinCS.Write(cs) // Select cristals
|
||||||
return d.dev.WaitReady()
|
// return d.dev.WaitReady()
|
||||||
|
time.Sleep(waitReadyTimeout)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mt12232a) ReadStatus(cs rpio.State) byte {
|
func (d *mt12232a) ReadStatus(cs rpio.State) byte {
|
||||||
d.pinCS.Write(cs) // Select cristals
|
d.pinCS.Write(cs) // Select cristals
|
||||||
return d.dev.ReadByte(1)
|
return d.dev.ReadByte(0)
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
adressWriteTimeout = 140 * time.Nanosecond
|
adressWriteTimeout = 50 * time.Nanosecond // 40
|
||||||
dataStrobeTimeout = 250 * time.Nanosecond // (Data transfer)
|
dataStrobeTimeout = 200 * time.Nanosecond // (Data transfer) 160
|
||||||
|
dataReadTimeout = 400 * time.Nanosecond // 300
|
||||||
|
gapTimeout = 2000 * time.Nanosecond // TIM // 2000
|
||||||
maxWaitCycles = 100
|
maxWaitCycles = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,8 +20,11 @@ const (
|
|||||||
type Device interface {
|
type Device interface {
|
||||||
Reset()
|
Reset()
|
||||||
WriteByte(b byte, cd rpio.State)
|
WriteByte(b byte, cd rpio.State)
|
||||||
|
WriteBytes(b []byte, cd rpio.State)
|
||||||
ReadByte(cd rpio.State) byte
|
ReadByte(cd rpio.State) byte
|
||||||
|
|
||||||
|
Pins() DevicePins
|
||||||
|
|
||||||
WaitReady() error
|
WaitReady() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,14 +41,12 @@ type DevicePins struct {
|
|||||||
PinDB5 rpio.Pin
|
PinDB5 rpio.Pin
|
||||||
PinDB6 rpio.Pin
|
PinDB6 rpio.Pin
|
||||||
PinDB7 rpio.Pin
|
PinDB7 rpio.Pin
|
||||||
PinE1 rpio.Pin
|
|
||||||
PinE2 rpio.Pin
|
|
||||||
PinRES rpio.Pin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type device struct {
|
type device struct {
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
|
||||||
|
isBusOutput bool
|
||||||
DevicePins
|
DevicePins
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,17 +57,29 @@ func New(logger *log.Logger, pins DevicePins) Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *device) Pins() DevicePins {
|
||||||
|
return d.DevicePins
|
||||||
|
}
|
||||||
|
|
||||||
func (d *device) Reset() {
|
func (d *device) Reset() {
|
||||||
d.PinA0.Output()
|
d.PinA0.Output()
|
||||||
d.PinRW.Output()
|
d.PinRW.Output()
|
||||||
d.PinE.Output()
|
d.PinE.Output()
|
||||||
d.PinRES.Output()
|
|
||||||
d.busOutput()
|
d.busOutput()
|
||||||
d.PinE.Low()
|
d.PinA0.Low()
|
||||||
|
d.PinRW.Low()
|
||||||
|
d.PinE.High()
|
||||||
|
d.PinDB0.Low()
|
||||||
|
d.PinDB1.Low()
|
||||||
|
d.PinDB2.Low()
|
||||||
|
d.PinDB3.Low()
|
||||||
|
d.PinDB4.Low()
|
||||||
|
d.PinDB5.Low()
|
||||||
|
d.PinDB6.Low()
|
||||||
|
d.PinDB7.Low()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *device) WriteByte(b byte, cd rpio.State) {
|
func (d *device) WriteByte(b byte, cd rpio.State) {
|
||||||
// d.logger.Println("Write byte", b, cd, l, r)
|
|
||||||
d.busOutput()
|
d.busOutput()
|
||||||
d.PinRW.Low() // We write
|
d.PinRW.Low() // We write
|
||||||
d.PinA0.Write(cd)
|
d.PinA0.Write(cd)
|
||||||
@ -79,25 +94,50 @@ func (d *device) WriteByte(b byte, cd rpio.State) {
|
|||||||
d.PinDB6.Write(rpio.State((b >> 6) & 1))
|
d.PinDB6.Write(rpio.State((b >> 6) & 1))
|
||||||
d.PinDB7.Write(rpio.State((b >> 7) & 1))
|
d.PinDB7.Write(rpio.State((b >> 7) & 1))
|
||||||
|
|
||||||
// Strobe start
|
time.Sleep(adressWriteTimeout)
|
||||||
d.PinE.High()
|
|
||||||
time.Sleep(dataStrobeTimeout)
|
|
||||||
|
|
||||||
// Strobe end
|
d.PinE.Low() // Strobe start
|
||||||
d.PinE.Low()
|
time.Sleep(dataStrobeTimeout)
|
||||||
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout)
|
d.PinE.High() // Strobe end
|
||||||
|
|
||||||
|
time.Sleep(gapTimeout - dataStrobeTimeout - adressWriteTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *device) WriteBytes(b []byte, cd rpio.State) {
|
||||||
|
d.busOutput()
|
||||||
|
d.PinRW.Low() // We write
|
||||||
|
d.PinA0.Write(cd)
|
||||||
|
|
||||||
|
// Write bus
|
||||||
|
d.PinDB0.Write(rpio.State(b[0]))
|
||||||
|
d.PinDB1.Write(rpio.State(b[1]))
|
||||||
|
d.PinDB2.Write(rpio.State(b[2]))
|
||||||
|
d.PinDB3.Write(rpio.State(b[3]))
|
||||||
|
d.PinDB4.Write(rpio.State(b[4]))
|
||||||
|
d.PinDB5.Write(rpio.State(b[5]))
|
||||||
|
d.PinDB6.Write(rpio.State(b[6]))
|
||||||
|
d.PinDB7.Write(rpio.State(b[7]))
|
||||||
|
|
||||||
|
time.Sleep(adressWriteTimeout)
|
||||||
|
|
||||||
|
d.PinE.Low() // Strobe start
|
||||||
|
time.Sleep(dataStrobeTimeout)
|
||||||
|
d.PinE.High() // Strobe end
|
||||||
|
|
||||||
|
time.Sleep(gapTimeout - dataStrobeTimeout - adressWriteTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *device) ReadByte(cd rpio.State) byte {
|
func (d *device) ReadByte(cd rpio.State) byte {
|
||||||
// Setup
|
// Setup
|
||||||
var b byte
|
var b byte
|
||||||
d.busOutput()
|
|
||||||
d.PinRW.High() // We write
|
|
||||||
d.PinA0.Write(cd)
|
d.PinA0.Write(cd)
|
||||||
|
d.PinRW.High() // We read
|
||||||
|
d.busInput()
|
||||||
|
|
||||||
// Strobe start
|
// Strobe start
|
||||||
d.PinE.High()
|
time.Sleep(adressWriteTimeout)
|
||||||
time.Sleep(dataStrobeTimeout)
|
d.PinE.Low()
|
||||||
|
time.Sleep(dataReadTimeout)
|
||||||
|
|
||||||
// Read
|
// Read
|
||||||
b = uint8(d.PinDB0.Read()) |
|
b = uint8(d.PinDB0.Read()) |
|
||||||
@ -110,44 +150,47 @@ func (d *device) ReadByte(cd rpio.State) byte {
|
|||||||
(uint8(d.PinDB7.Read()) << 7)
|
(uint8(d.PinDB7.Read()) << 7)
|
||||||
|
|
||||||
// Strobe end
|
// Strobe end
|
||||||
d.PinE.Low()
|
d.PinE.High()
|
||||||
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout)
|
time.Sleep(gapTimeout - dataReadTimeout - adressWriteTimeout)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *device) WaitReady() error {
|
func (d *device) WaitReady() error {
|
||||||
d.busInput() // Set bus to input
|
d.busInput() // Set bus to input
|
||||||
d.PinRW.High() // We read
|
|
||||||
d.PinA0.Low() // Status
|
d.PinA0.Low() // Status
|
||||||
|
d.PinRW.High() // We read
|
||||||
time.Sleep(adressWriteTimeout)
|
time.Sleep(adressWriteTimeout)
|
||||||
|
|
||||||
// Strobe start
|
// Strobe start
|
||||||
d.PinE.High()
|
d.PinE.Low()
|
||||||
time.Sleep(dataStrobeTimeout)
|
time.Sleep(dataReadTimeout)
|
||||||
|
|
||||||
// Wait status flag drop
|
// Wait status flag drop
|
||||||
ok := false
|
ok := false
|
||||||
d.busInput() // Set bus to input
|
|
||||||
for counter := 0; counter < maxWaitCycles; counter++ {
|
for counter := 0; counter < maxWaitCycles; counter++ {
|
||||||
if d.PinDB7.Read() != rpio.High {
|
if d.PinDB7.Read() != rpio.High {
|
||||||
//d.logger.Printf("BUS:%d%d%d%d%d%d%d%d\n", d.pinDB0.Read(), d.pinDB1.Read(), d.pinDB2.Read(), d.pinDB3.Read(), d.pinDB4.Read(), d.pinDB5.Read(), d.pinDB6.Read(), d.pinDB7.Read())
|
//d.logger.Printf("BUS:%d%d%d%d%d%d%d%d\n", d.PinDB0.Read(), d.PinDB1.Read(), d.PinDB2.Read(), d.PinDB3.Read(), d.PinDB4.Read(), d.PinDB5.Read(), d.PinDB6.Read(), d.PinDB7.Read())
|
||||||
ok = true
|
ok = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
fmt.Print(".")
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("busy timeout")
|
return fmt.Errorf("busy timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strobe end
|
// Strobe end
|
||||||
d.PinE.Low()
|
d.PinE.High()
|
||||||
time.Sleep(time.Millisecond - dataStrobeTimeout - adressWriteTimeout)
|
time.Sleep(gapTimeout - dataReadTimeout - adressWriteTimeout)
|
||||||
// d.logger.Println("Ready")
|
// d.logger.Println("Ready")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set bus pins to output
|
// Set bus Pins to output
|
||||||
func (d *device) busOutput() {
|
func (d *device) busOutput() {
|
||||||
|
if d.isBusOutput {
|
||||||
|
return
|
||||||
|
}
|
||||||
d.PinDB0.Output()
|
d.PinDB0.Output()
|
||||||
d.PinDB1.Output()
|
d.PinDB1.Output()
|
||||||
d.PinDB2.Output()
|
d.PinDB2.Output()
|
||||||
@ -156,9 +199,13 @@ func (d *device) busOutput() {
|
|||||||
d.PinDB5.Output()
|
d.PinDB5.Output()
|
||||||
d.PinDB6.Output()
|
d.PinDB6.Output()
|
||||||
d.PinDB7.Output()
|
d.PinDB7.Output()
|
||||||
|
d.isBusOutput = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *device) busInput() {
|
func (d *device) busInput() {
|
||||||
|
if !d.isBusOutput {
|
||||||
|
return
|
||||||
|
}
|
||||||
d.PinDB0.Input()
|
d.PinDB0.Input()
|
||||||
d.PinDB1.Input()
|
d.PinDB1.Input()
|
||||||
d.PinDB2.Input()
|
d.PinDB2.Input()
|
||||||
@ -167,4 +214,5 @@ func (d *device) busInput() {
|
|||||||
d.PinDB5.Input()
|
d.PinDB5.Input()
|
||||||
d.PinDB6.Input()
|
d.PinDB6.Input()
|
||||||
d.PinDB7.Input()
|
d.PinDB7.Input()
|
||||||
|
d.isBusOutput = false
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user