From 37301efe606906e390d91c5938aa8dc9dfc8be7e Mon Sep 17 00:00:00 2001 From: Kevin Wallace Date: Sat, 25 Apr 2026 06:13:06 -0700 Subject: wait for cvend at startup, only redraw on state changes --- cmd/orca/display.go | 108 +++++++++++++++++++++++++++++++++++ cmd/orca/main.go | 159 +++++++++++++++++++++++----------------------------- ipp/ipp.go | 12 ++-- 3 files changed, 184 insertions(+), 95 deletions(-) create mode 100644 cmd/orca/display.go diff --git a/cmd/orca/display.go b/cmd/orca/display.go new file mode 100644 index 0000000..5bd4f87 --- /dev/null +++ b/cmd/orca/display.go @@ -0,0 +1,108 @@ +package main + +import ( + "embed" + "image" + "image/color" + "image/draw" + _ "image/png" + "io" + "io/fs" + "os" + + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/font/sfnt" + "golang.org/x/image/math/fixed" +) + +// openSFNT opens the specified font file +// It keeps the file open indefinitely for lazy loading when the font is used. +// It never attempts to close the file and is meant only for loading fonts permanently at init time. +func openSFNT(fs fs.FS, path string) *sfnt.Font { + f, err := fs.Open(path) + if err != nil { + panic(err) + } + font, err := sfnt.ParseReaderAt(f.(io.ReaderAt)) + if err != nil { + panic(err) + } + return font +} + +func openImage(fs fs.FS, path string) image.Image { + f, err := fs.Open(path) + if err != nil { + panic(err) + } + img, _, err := image.Decode(f) + if err != nil { + panic(err) + } + return img +} + +//go:embed *.ttf *.png +var embedFS embed.FS +var qtFonts = os.DirFS("/usr/lib/fonts") + +var ( + dejaVuSans = openSFNT(qtFonts, "DejaVuSans.ttf") + notoSans_Regular = openSFNT(embedFS, "NotoSans-Regular.ttf") + notoSans_Bold = openSFNT(embedFS, "NotoSans-Bold.ttf") +) + +var ( + orcaImg = openImage(embedFS, "orca.png") + belowImg = openImage(embedFS, "below.png") +) + +type justify int + +const ( + _ justify = iota + justifyLeft + justifyCenter + justifyRight +) + +func drawText(img draw.Image, sfnt *sfnt.Font, size float64, just justify, c color.Color, dot fixed.Point26_6, text string) { + face, err := opentype.NewFace(sfnt, &opentype.FaceOptions{ + Size: size, + DPI: 72, + Hinting: font.HintingNone, + }) + if err != nil { + panic(err) + } + defer face.Close() + drawer := font.Drawer{ + Dst: img, + Src: &image.Uniform{c}, + Face: face, + Dot: dot, + } + _, adv := drawer.BoundString(text) + var offset fixed.Int26_6 + switch just { + case justifyCenter: + offset = adv / 2 + case justifyRight: + offset = adv + } + drawer.Dot.X -= offset + drawer.DrawString(text) +} + +func drawDisplay(img draw.Image, s displayState) { + draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{34, 31, 32, 255}}, image.Point{}, draw.Src) + drawText(img, dejaVuSans, 20, justifyLeft, color.White, fixed.P(50, 25), s.now.Format("Jan 02, 2006")) + drawText(img, dejaVuSans, 20, justifyRight, color.White, fixed.P(750, 25), s.now.Format("3:04 pm")) + drawText(img, notoSans_Regular, 16, justifyCenter, color.White, fixed.P(400, 25), "V1234567-D98765") + draw.Draw(img, orcaImg.Bounds().Add(image.Point{400 - orcaImg.Bounds().Dx()/2, 75}), orcaImg, image.Point{}, draw.Over) + if s.cvend.ready { + drawText(img, notoSans_Bold, 48, justifyCenter, color.White, fixed.P(400, 330), "Tap below") + draw.Draw(img, belowImg.Bounds().Add(image.Point{400 - belowImg.Bounds().Dx()/2, 380}), belowImg, image.Point{}, draw.Over) + } +} diff --git a/cmd/orca/main.go b/cmd/orca/main.go index 79c5eab..f840d22 100644 --- a/cmd/orca/main.go +++ b/cmd/orca/main.go @@ -1,125 +1,104 @@ package main import ( - "embed" "flag" "image" - "image/color" - "image/draw" - _ "image/png" - "io" - "io/fs" "log" "os" "time" - "golang.org/x/image/font" - "golang.org/x/image/font/opentype" - "golang.org/x/image/font/sfnt" - "golang.org/x/image/math/fixed" + "pm3.dev/cvend" "pm3.dev/fb" + "pm3.dev/ipp" ) -// openSFNT opens the specified font file -// It keeps the file open indefinitely for lazy loading when the font is used. -// It never attempts to close the file and is meant only for loading fonts permanently at init time. -func openSFNT(fs fs.FS, path string) *sfnt.Font { - f, err := fs.Open(path) - if err != nil { - panic(err) - } - font, err := sfnt.ParseReaderAt(f.(io.ReaderAt)) - if err != nil { - panic(err) - } - return font +type cvendState struct { + ready bool } -func openImage(fs fs.FS, path string) image.Image { - f, err := fs.Open(path) - if err != nil { - panic(err) - } - img, _, err := image.Decode(f) - if err != nil { - panic(err) - } - return img +type displayState struct { + now time.Time + cvend cvendState } -//go:embed *.ttf *.png -var embedFS embed.FS -var qtFonts = os.DirFS("/usr/lib/fonts") +func main() { + flag.Parse() + log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) + log.Println(os.Args[0], "start") -var ( - dejaVuSans = openSFNT(qtFonts, "DejaVuSans.ttf") - notoSans_Regular = openSFNT(embedFS, "NotoSans-Regular.ttf") - notoSans_Bold = openSFNT(embedFS, "NotoSans-Bold.ttf") -) + displayStateCh := make(chan displayState) + go displayTask(displayStateCh) -var ( - orcaImg = openImage(embedFS, "orca.png") - belowImg = openImage(embedFS, "below.png") -) + displayState := displayState{ + now: time.Now(), + } + displayStateCh <- displayState -type justify int + cvendStateCh := make(chan cvendState) + go cvendTask(cvendStateCh) -const ( - _ justify = iota - justifyLeft - justifyCenter - justifyRight -) + tickerCh := wallTicker(time.Minute).C -func drawText(img draw.Image, sfnt *sfnt.Font, size float64, just justify, c color.Color, dot fixed.Point26_6, text string) { - face, err := opentype.NewFace(sfnt, &opentype.FaceOptions{ - Size: size, - DPI: 72, - Hinting: font.HintingNone, - }) - if err != nil { - panic(err) - } - defer face.Close() - drawer := font.Drawer{ - Dst: img, - Src: &image.Uniform{c}, - Face: face, - Dot: dot, - } - _, adv := drawer.BoundString(text) - var offset fixed.Int26_6 - switch just { - case justifyCenter: - offset = adv / 2 - case justifyRight: - offset = adv + for { + select { + case now := <-tickerCh: + displayState.now = now + case cvendState := <-cvendStateCh: + displayState.cvend = cvendState + } + displayStateCh <- displayState } - drawer.Dot.X -= offset - drawer.DrawString(text) } -func main() { - flag.Parse() - log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) - log.Println(os.Args[0], "start") +// wallTicker returns a ticker that fires whenever time.Now().Truncate(d) changes +func wallTicker(d time.Duration) *time.Ticker { + first := time.Until(time.Now().Add(d).Truncate(d)) + ticker := time.NewTicker(first) + go func() { + time.Sleep(first) + ticker.Reset(d) + }() + return ticker +} +func displayTask(stateCh <-chan displayState) { fb, err := fb.Open() if err != nil { log.Fatalf("open fb: %v", err) } defer fb.Close() - for { - now := time.Now() + for state := range stateCh { + start := time.Now() img := image.NewRGBA(fb.Bounds()) - draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{34, 31, 32, 255}}, image.Point{}, draw.Src) - drawText(img, dejaVuSans, 20, justifyLeft, color.White, fixed.P(50, 25), now.Format("Jan 02, 2006")) - drawText(img, dejaVuSans, 20, justifyRight, color.White, fixed.P(750, 25), now.Format("3:04 pm")) - drawText(img, notoSans_Regular, 16, justifyCenter, color.White, fixed.P(400, 25), "V1234567-D98765") - draw.Draw(img, orcaImg.Bounds().Add(image.Point{400 - orcaImg.Bounds().Dx()/2, 75}), orcaImg, image.Point{}, draw.Over) - drawText(img, notoSans_Bold, 48, justifyCenter, color.White, fixed.P(400, 330), "Tap below") - draw.Draw(img, belowImg.Bounds().Add(image.Point{400 - belowImg.Bounds().Dx()/2, 380}), belowImg, image.Point{}, draw.Over) + drawDisplay(img, state) fb.WriteRGBA(img) + dur := time.Now().Sub(start) + log.Printf("display update %s %+v", dur, state) + } + +} + +func cvendTask(out chan<- cvendState) { + var state cvendState + var cv ipp.Session + var err error + cv, err = cvend.OpenIPP(func(msgType byte, msgData []byte) { + switch msgType { + case 0x05: // StatusReply + if !state.ready { + state.ready = true + out <- state + } + default: + cvend.LogIPP(msgType, msgData) + } + }) + if err != nil { + panic(err) + } + for { + cv.SendIPP(0x04, nil) // Status + time.Sleep(1 * time.Second) } } diff --git a/ipp/ipp.go b/ipp/ipp.go index 391b094..89036b0 100644 --- a/ipp/ipp.go +++ b/ipp/ipp.go @@ -149,11 +149,11 @@ func (s *session) handleMsg(bs []byte) (nRead int) { return } } - var ellipsis string - if ippLen > 0 { - ellipsis = "..." - } if *Trace { + var ellipsis string + if ippLen > 0 { + ellipsis = "..." + } log.Printf("%s < %s%s%s", s.path, hex.EncodeToString(bs[:7]), ellipsis, hex.EncodeToString(bs[7+ippLen:expectedLen])) } if s.handler != nil { @@ -166,7 +166,9 @@ func (s *session) Close() error { s.mu.Lock() defer s.mu.Unlock() s.closed = true - return unix.Close(s.fd) + fd := s.fd + s.fd = -1 + return unix.Close(fd) } func (s *session) Write(bs []byte) (int, error) { -- cgit v1.2.3