display transport

This commit is contained in:
Gabriel Augendre 2024-09-15 10:46:07 +02:00
parent 8be7b20062
commit 452ac199d0
8 changed files with 199 additions and 93 deletions

View file

@ -45,48 +45,9 @@ func New() (*EPD, error) {
partFlag: 1, partFlag: 1,
} }
if err := epd.resetPin.Out(gpio.Low); err != nil {
return nil, fmt.Errorf("setting reset pin to low: %w", err)
}
if err := epd.dcPin.Out(gpio.Low); err != nil {
return nil, fmt.Errorf("setting dc pin to low: %w", err)
}
if err := epd.csPin.Out(gpio.Low); err != nil {
return nil, fmt.Errorf("setting cs pin to low: %w", err)
}
if err := epd.pwrPin.Out(gpio.High); err != nil {
return nil, fmt.Errorf("setting pwr pin to low: %w", err)
}
var err error
if epd.spiReg, err = spireg.Open("0"); err != nil {
return nil, fmt.Errorf("opening SPI: %w", err)
}
c, err := epd.spiReg.Connect(4*physic.MegaHertz, spi.Mode0, 8)
if err != nil {
return nil, fmt.Errorf("connecting to SPI: %w", err)
}
epd.spi = c
return epd, nil return epd, nil
} }
func (e *EPD) turnOff() error {
log.Println("turning off...")
if err := e.spiReg.Close(); err != nil {
return fmt.Errorf("closing SPI: %w", err)
}
e.resetPin.Out(gpio.Low)
e.dcPin.Out(gpio.Low)
e.pwrPin.Out(gpio.Low)
return nil
}
func (e *EPD) reset() { func (e *EPD) reset() {
e.resetPin.Out(gpio.High) e.resetPin.Out(gpio.High)
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
@ -97,7 +58,6 @@ func (e *EPD) reset() {
} }
func (e *EPD) sendCommand(cmd byte) { func (e *EPD) sendCommand(cmd byte) {
log.Printf("sending command 0x%02X\n", cmd)
e.dcPin.Out(gpio.Low) e.dcPin.Out(gpio.Low)
e.csPin.Out(gpio.Low) e.csPin.Out(gpio.Low)
if _, err := e.spiWrite([]byte{cmd}); err != nil { if _, err := e.spiWrite([]byte{cmd}); err != nil {
@ -116,7 +76,6 @@ func (e *EPD) sendData(data byte) {
} }
func (e *EPD) sendDataSlice(data []byte) { func (e *EPD) sendDataSlice(data []byte) {
log.Printf("sending data slice %v\n", len(data))
e.dcPin.Out(gpio.High) e.dcPin.Out(gpio.High)
toSend := len(data) toSend := len(data)
const maxSize = 4096 const maxSize = 4096
@ -164,8 +123,44 @@ func (e *EPD) readBusy() {
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
} }
func (e *EPD) Init() { func (e *EPD) turnOn() error {
log.Println("turning on")
if err := e.resetPin.Out(gpio.Low); err != nil {
return fmt.Errorf("setting reset pin to low: %w", err)
}
if err := e.dcPin.Out(gpio.Low); err != nil {
return fmt.Errorf("setting dc pin to low: %w", err)
}
if err := e.csPin.Out(gpio.Low); err != nil {
return fmt.Errorf("setting cs pin to low: %w", err)
}
if err := e.pwrPin.Out(gpio.High); err != nil {
return fmt.Errorf("setting pwr pin to low: %w", err)
}
var err error
if e.spiReg, err = spireg.Open("0"); err != nil {
return fmt.Errorf("opening SPI: %w", err)
}
c, err := e.spiReg.Connect(4*physic.MegaHertz, spi.Mode0, 8)
if err != nil {
return fmt.Errorf("connecting to SPI: %w", err)
}
e.spi = c
return nil
}
func (e *EPD) Init() error {
log.Println("initializing EPD") log.Println("initializing EPD")
if err := e.turnOn(); err != nil {
return fmt.Errorf("turning on: %w", err)
}
e.reset() e.reset()
e.sendCommand(0x01) e.sendCommand(0x01)
@ -192,10 +187,17 @@ func (e *EPD) Init() {
e.sendCommand(0x60) e.sendCommand(0x60)
e.sendData(0x22) e.sendData(0x22)
return nil
} }
func (e *EPD) InitFast() { func (e *EPD) InitFast() error {
log.Println("initializing Fast EPD") log.Println("initializing Fast EPD")
if err := e.turnOn(); err != nil {
return fmt.Errorf("turning on: %w", err)
}
e.reset() e.reset()
e.sendCommand(0x00) e.sendCommand(0x00)
@ -216,13 +218,13 @@ func (e *EPD) InitFast() {
e.sendCommand(0x50) e.sendCommand(0x50)
e.sendDataSlice([]byte{0x11, 0x07}) e.sendDataSlice([]byte{0x11, 0x07})
return nil
} }
func (e *EPD) Clear() { func (e *EPD) Clear() {
log.Println("clearing epd") log.Println("clearing epd")
e.Fill(White) e.Fill(White)
//e.Refresh()
} }
func (e *EPD) Refresh() { func (e *EPD) Refresh() {
@ -233,7 +235,7 @@ func (e *EPD) Refresh() {
} }
func (e *EPD) Sleep() error { func (e *EPD) Sleep() error {
log.Println("sleeping...") log.Println("sleeping display...")
e.sendCommand(0x02) e.sendCommand(0x02)
e.readBusy() e.readBusy()
@ -248,6 +250,19 @@ func (e *EPD) Sleep() error {
return nil return nil
} }
func (e *EPD) turnOff() error {
log.Println("turning off...")
if err := e.spiReg.Close(); err != nil {
return fmt.Errorf("closing SPI: %w", err)
}
e.resetPin.Out(gpio.Low)
e.dcPin.Out(gpio.Low)
e.pwrPin.Out(gpio.Low)
return nil
}
type Color int type Color int
const ( const (
@ -270,7 +285,6 @@ func (e *EPD) Fill(c Color) {
} }
func (e *EPD) Send(black image.Image, red image.Image) { func (e *EPD) Send(black image.Image, red image.Image) {
log.Println("drawing...")
if black != nil { if black != nil {
log.Println("sending black") log.Println("sending black")
e.sendCommand(0x10) // write bw data e.sendCommand(0x10) // write bw data

7
go.mod
View file

@ -11,4 +11,9 @@ require (
golang.org/x/image v0.20.0 golang.org/x/image v0.20.0
) )
require github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 require (
github.com/carlmjohnson/requests v0.24.2
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
)
require golang.org/x/net v0.27.0 // indirect

4
go.sum
View file

@ -1,3 +1,5 @@
github.com/carlmjohnson/requests v0.24.2 h1:JDakhAmTIKL/qL/1P7Kkc2INGBJIkIFP6xUeUmPzLso=
github.com/carlmjohnson/requests v0.24.2/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
@ -8,6 +10,8 @@ github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e h1:ZAvbj5hI/G/EbAYAcj4y
github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY= github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
periph.io/x/conn/v3 v3.7.0 h1:f1EXLn4pkf7AEWwkol2gilCNZ0ElY+bxS4WE2PQXfrA= periph.io/x/conn/v3 v3.7.0 h1:f1EXLn4pkf7AEWwkol2gilCNZ0ElY+bxS4WE2PQXfrA=
periph.io/x/conn/v3 v3.7.0/go.mod h1:ypY7UVxgDbP9PJGwFSVelRRagxyXYfttVh7hJZUHEhg= periph.io/x/conn/v3 v3.7.0/go.mod h1:ypY7UVxgDbP9PJGwFSVelRRagxyXYfttVh7hJZUHEhg=
periph.io/x/host/v3 v3.8.2 h1:ayKUDzgUCN0g8+/xM9GTkWaOBhSLVcVHGTfjAOi8OsQ= periph.io/x/host/v3 v3.8.2 h1:ayKUDzgUCN0g8+/xM9GTkWaOBhSLVcVHGTfjAOi8OsQ=

34
img.go
View file

@ -1,36 +1,38 @@
package main package main
import ( import (
"context"
"fmt"
"github.com/Crocmagnon/display-epaper/epd" "github.com/Crocmagnon/display-epaper/epd"
"github.com/Crocmagnon/display-epaper/transports"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg" "github.com/llgcode/draw2d/draw2dimg"
"image" "image"
"image/color" "image/color"
) )
func getBlack() (*image.RGBA, error) { func getBlack(ctx context.Context, transportsClient *transports.Client) (*image.RGBA, error) {
passages, err := transportsClient.GetTCLPassages(ctx, 290)
if err != nil {
return nil, fmt.Errorf("getting passages: %w", err)
}
img := newWhite() img := newWhite()
gc := draw2dimg.NewGraphicContext(img) gc := draw2dimg.NewGraphicContext(img)
gc.SetFillRule(draw2d.FillRuleWinding)
gc.SetFillColor(color.RGBA{0, 0, 0, 255}) gc.SetFillColor(color.RGBA{0, 0, 0, 255})
gc.SetStrokeColor(color.RGBA{0, 0, 0, 255}) gc.SetStrokeColor(color.RGBA{0, 0, 0, 255})
gc.SetLineWidth(2)
text(gc, "Hello, world", 18, 110, 50) for i, passage := range passages.Passages {
x := float64(10 + i*100)
return img, nil text(gc, passage.Ligne, 15, x, 20)
for j, delay := range passage.Delays {
y := float64(20 + (j+1)*30)
text(gc, delay, 15, x, y)
}
} }
func getRed() (*image.RGBA, error) {
img := newBlack()
gc := draw2dimg.NewGraphicContext(img)
gc.SetFillColor(color.RGBA{0, 0, 0, 255})
gc.SetStrokeColor(color.RGBA{255, 255, 255, 255})
gc.SetLineWidth(2)
rect(gc, 10, 10, 50, 50)
rect(gc, 60, 10, 100, 50)
return img, nil return img, nil
} }
@ -38,6 +40,8 @@ func getRed() (*image.RGBA, error) {
func text(gc *draw2dimg.GraphicContext, s string, size, x, y float64) { func text(gc *draw2dimg.GraphicContext, s string, size, x, y float64) {
gc.SetFontData(draw2d.FontData{Name: fontName}) gc.SetFontData(draw2d.FontData{Name: fontName})
gc.SetFontSize(size) gc.SetFontSize(size)
gc.FillStringAt(s, x, y)
gc.SetLineWidth(2)
gc.StrokeStringAt(s, x, y) gc.StrokeStringAt(s, x, y)
} }

11
main.go
View file

@ -1,11 +1,14 @@
package main package main
import ( import (
"context"
"github.com/Crocmagnon/display-epaper/transports"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
_ "golang.org/x/image/bmp" _ "golang.org/x/image/bmp"
"golang.org/x/image/font/gofont/goregular" "golang.org/x/image/font/gofont/goregular"
"log" "log"
"os"
) )
const fontName = "default" const fontName = "default"
@ -13,6 +16,8 @@ const fontName = "default"
func main() { func main() {
log.Println("starting...") log.Println("starting...")
ctx := context.Background()
font, err := truetype.Parse(goregular.TTF) font, err := truetype.Parse(goregular.TTF)
if err != nil { if err != nil {
log.Fatalf("loading font: %v\n", err) log.Fatalf("loading font: %v\n", err)
@ -21,7 +26,11 @@ func main() {
fontCache.Store(draw2d.FontData{Name: fontName}, font) fontCache.Store(draw2d.FontData{Name: fontName}, font)
draw2d.SetFontCache(fontCache) draw2d.SetFontCache(fontCache)
if err := run(); err != nil { transportsClient := transports.New(nil, transports.Config{
Authorization: os.Getenv("TRANSPORTS_AUTHORIZATION"),
})
if err := run(ctx, transportsClient); err != nil {
log.Fatal("error: ", err) log.Fatal("error: ", err)
} }

View file

@ -1,23 +1,19 @@
package main package main
import ( import (
"context"
"github.com/Crocmagnon/display-epaper/transports"
"github.com/llgcode/draw2d/draw2dimg" "github.com/llgcode/draw2d/draw2dimg"
"log" "log"
) )
func run() error { func run(ctx context.Context, transportsClient *transports.Client) error {
img, err := getBlack() img, err := getBlack(ctx, transportsClient)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
draw2dimg.SaveToPngFile("out/black.png", img) draw2dimg.SaveToPngFile("out/black.png", img)
img, err = getRed()
if err != nil {
log.Fatal(err)
}
draw2dimg.SaveToPngFile("out/red.png", img)
log.Println("done") log.Println("done")
return nil return nil

View file

@ -1,13 +1,16 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"github.com/Crocmagnon/display-epaper/epd" "github.com/Crocmagnon/display-epaper/epd"
"github.com/Crocmagnon/display-epaper/transports"
"log" "log"
"periph.io/x/host/v3" "periph.io/x/host/v3"
"time"
) )
func run() error { func run(ctx context.Context, transportsClient *transports.Client) error {
_, err := host.Init() _, err := host.Init()
if err != nil { if err != nil {
return fmt.Errorf("initializing host: %w", err) return fmt.Errorf("initializing host: %w", err)
@ -18,30 +21,45 @@ func run() error {
return fmt.Errorf("initializing epd: %w", err) return fmt.Errorf("initializing epd: %w", err)
} }
display.Init() for {
//display.InitFast() select {
display.Clear() case <-ctx.Done():
display.Refresh() log.Println("stopping because of context")
return ctx.Err()
default:
}
black, err := getBlack() err = loop(ctx, display, transportsClient)
if err != nil {
log.Printf("error looping: %v\n", err)
}
log.Println("time.Sleep(30s)")
time.Sleep(30 * time.Second)
}
}
func loop(ctx context.Context, display *epd.EPD, transportsClient *transports.Client) error {
defer func() {
if err := display.Sleep(); err != nil {
log.Printf("error sleeping: %v\n", err)
}
}()
err := display.Init()
if err != nil {
return fmt.Errorf("initializing display: %w", err)
}
display.Clear()
black, err := getBlack(ctx, transportsClient)
if err != nil { if err != nil {
return fmt.Errorf("getting black: %w", err) return fmt.Errorf("getting black: %w", err)
} }
red, err := getRed() display.Send(black, nil)
if err != nil { display.Refresh()
return fmt.Errorf("getting red: %w", err)
}
display.Send(black, red)
log.Println("sleeping...")
if err := display.Sleep(); err != nil {
return fmt.Errorf("sleeping: %w", err)
}
log.Println("done")
return nil return nil
} }

56
transports/transports.go Normal file
View file

@ -0,0 +1,56 @@
package transports
import (
"context"
"fmt"
"github.com/carlmjohnson/requests"
"net/http"
)
type Stop struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Passage struct {
Ligne string `json:"ligne"`
Delays []string `json:"delais"`
Destination Stop `json:"destination"`
}
type Passages struct {
Passages []Passage `json:"passages"`
Stop Stop `json:"stop"`
}
type Config struct {
Authorization string
}
type Client struct {
client *http.Client
config Config
}
func New(httpClient *http.Client, config Config) *Client {
if httpClient == nil {
httpClient = &http.Client{}
}
return &Client{
client: httpClient,
config: config,
}
}
func (c *Client) GetTCLPassages(ctx context.Context, stop int) (res *Passages, err error) {
err = requests.URL("https://tcl.augendre.info").
Pathf("/stop/%v", stop).
Header("Authorization", c.config.Authorization).
Client(c.client).
ToJSON(&res).
Fetch(ctx)
if err != nil {
return nil, fmt.Errorf("calling api: %w", err)
}
return res, nil
}