improve logging & add cache to tcl & velov

This commit is contained in:
Gabriel Augendre 2024-10-21 23:14:55 +02:00
parent afc766f45d
commit f9c3153db1
8 changed files with 120 additions and 63 deletions

View file

@ -3,7 +3,8 @@ package epd
import ( import (
"fmt" "fmt"
"image" "image"
"log" "log/slog"
"os"
"periph.io/x/conn/v3/gpio" "periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/physic" "periph.io/x/conn/v3/physic"
"periph.io/x/conn/v3/spi" "periph.io/x/conn/v3/spi"
@ -61,7 +62,8 @@ func (e *EPD) sendCommand(cmd byte) {
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 {
log.Fatalf("writing to spi: %v", err) slog.Error("writing to spi", "err", err)
os.Exit(1)
} }
e.csPin.Out(gpio.High) e.csPin.Out(gpio.High)
} }
@ -70,7 +72,8 @@ func (e *EPD) sendData(data byte) {
e.dcPin.Out(gpio.High) e.dcPin.Out(gpio.High)
e.csPin.Out(gpio.Low) e.csPin.Out(gpio.Low)
if _, err := e.spiWrite([]byte{data}); err != nil { if _, err := e.spiWrite([]byte{data}); err != nil {
log.Fatalf("writing to spi: %v", err) slog.Error("writing to spi", "err", err)
os.Exit(1)
} }
e.csPin.Out(gpio.High) e.csPin.Out(gpio.High)
} }
@ -82,7 +85,8 @@ func (e *EPD) sendDataSlice(data []byte) {
if toSend <= maxSize { if toSend <= maxSize {
e.csPin.Out(gpio.Low) e.csPin.Out(gpio.Low)
if _, err := e.spiWrite(data); err != nil { if _, err := e.spiWrite(data); err != nil {
log.Fatalf("writing to spi: %v", err) slog.Error("writing to spi", "err", err)
os.Exit(1)
} }
e.csPin.Out(gpio.High) e.csPin.Out(gpio.High)
return return
@ -93,7 +97,8 @@ func (e *EPD) sendDataSlice(data []byte) {
chunk := data[cursor:min(cursor+maxSize, toSend)] chunk := data[cursor:min(cursor+maxSize, toSend)]
e.csPin.Out(gpio.Low) e.csPin.Out(gpio.Low)
if _, err := e.spiWrite(chunk); err != nil { if _, err := e.spiWrite(chunk); err != nil {
log.Fatalf("writing to spi: %v", err) slog.Error("writing to spi", "err", err)
os.Exit(1)
} }
e.csPin.Out(gpio.High) e.csPin.Out(gpio.High)
cursor = min(cursor+maxSize, toSend) cursor = min(cursor+maxSize, toSend)
@ -120,7 +125,7 @@ func (e *EPD) readBusy() {
} }
func (e *EPD) turnOn() error { func (e *EPD) turnOn() error {
log.Println("turning on") slog.Info("turning on")
if err := e.resetPin.Out(gpio.Low); err != nil { if err := e.resetPin.Out(gpio.Low); err != nil {
return fmt.Errorf("setting reset pin to low: %w", err) return fmt.Errorf("setting reset pin to low: %w", err)
} }
@ -151,7 +156,7 @@ func (e *EPD) turnOn() error {
} }
func (e *EPD) Init() error { func (e *EPD) Init() error {
log.Println("initializing EPD") slog.Info("initializing EPD")
if err := e.turnOn(); err != nil { if err := e.turnOn(); err != nil {
return fmt.Errorf("turning on: %w", err) return fmt.Errorf("turning on: %w", err)
@ -188,7 +193,7 @@ func (e *EPD) Init() error {
} }
func (e *EPD) InitFast() error { func (e *EPD) InitFast() error {
log.Println("initializing Fast EPD") slog.Info("initializing Fast EPD")
if err := e.turnOn(); err != nil { if err := e.turnOn(); err != nil {
return fmt.Errorf("turning on: %w", err) return fmt.Errorf("turning on: %w", err)
@ -219,19 +224,19 @@ func (e *EPD) InitFast() error {
} }
func (e *EPD) Clear() { func (e *EPD) Clear() {
log.Println("clearing epd") slog.Info("clearing epd")
e.Send(image.White) e.Send(image.White)
} }
func (e *EPD) Refresh() { func (e *EPD) Refresh() {
log.Println("refreshing...") slog.Info("refreshing...")
e.sendCommand(0x12) e.sendCommand(0x12)
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
e.readBusy() e.readBusy()
} }
func (e *EPD) Sleep() error { func (e *EPD) Sleep() error {
log.Println("sleeping display...") slog.Info("sleeping display...")
e.sendCommand(0x02) e.sendCommand(0x02)
e.readBusy() e.readBusy()
@ -247,7 +252,7 @@ func (e *EPD) Sleep() error {
} }
func (e *EPD) turnOff() error { func (e *EPD) turnOff() error {
log.Println("turning off...") slog.Info("turning off...")
if err := e.spiReg.Close(); err != nil { if err := e.spiReg.Close(); err != nil {
return fmt.Errorf("closing SPI: %w", err) return fmt.Errorf("closing SPI: %w", err)
} }
@ -261,11 +266,11 @@ func (e *EPD) turnOff() error {
func (e *EPD) Send(img image.Image) { func (e *EPD) Send(img image.Image) {
if img == nil { if img == nil {
log.Println("empty img") slog.Info("empty img")
return return
} }
log.Println("sending img...") slog.Info("sending img...")
toSend := make([]byte, 0, e.height*e.width/8) toSend := make([]byte, 0, e.height*e.width/8)
toSend2 := make([]byte, 0, e.height*e.width/8) toSend2 := make([]byte, 0, e.height*e.width/8)
for row := 0; row < e.height; row++ { for row := 0; row < e.height; row++ {

View file

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/carlmjohnson/requests" "github.com/carlmjohnson/requests"
"log" "log/slog"
"net/http" "net/http"
"os" "os"
"time" "time"
@ -70,15 +70,15 @@ func (f Fete) dumpToDisk(location string) error {
func (c *Client) GetFete(ctx context.Context, date time.Time) (res *Fete, err error) { func (c *Client) GetFete(ctx context.Context, date time.Time) (res *Fete, err error) {
if val, err := loadFromDisk(c.config.CacheLocation); nil == err { if val, err := loadFromDisk(c.config.CacheLocation); nil == err {
log.Println("found fete in cache") slog.InfoContext(ctx, "found fete in cache")
if val.Day == date.Day() && val.Month == int(date.Month()) { if val.Day == date.Day() && val.Month == int(date.Month()) {
log.Println("fete cache is up to date") slog.InfoContext(ctx, "fete cache is up to date")
return &val, nil return &val, nil
} }
log.Println("fete cache is old, fetching...") slog.InfoContext(ctx, "fete cache is old, fetching...")
} }
log.Println("querying fete") slog.InfoContext(ctx, "querying fete")
err = requests.URL("https://fetedujour.fr"). err = requests.URL("https://fetedujour.fr").
Pathf("/api/v2/%v/json-normal-%d-%d", c.config.APIKey, date.Day(), date.Month()). Pathf("/api/v2/%v/json-normal-%d-%d", c.config.APIKey, date.Day(), date.Month()).
UserAgent("e-paper-display"). UserAgent("e-paper-display").
@ -90,7 +90,7 @@ func (c *Client) GetFete(ctx context.Context, date time.Time) (res *Fete, err er
} }
if err := res.dumpToDisk(c.config.CacheLocation); err != nil { if err := res.dumpToDisk(c.config.CacheLocation); err != nil {
log.Printf("error dumping files to disk: %v\n", err) slog.ErrorContext(ctx, "error dumping files to disk", "err", err)
} }
return res, nil return res, nil

26
img.go
View file

@ -14,7 +14,7 @@ import (
"github.com/llgcode/draw2d/draw2dimg" "github.com/llgcode/draw2d/draw2dimg"
"image" "image"
"image/color" "image/color"
"log" "log/slog"
"math" "math"
"strconv" "strconv"
"strings" "strings"
@ -53,7 +53,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
bus, err = transportsClient.GetTCLPassages(ctx, 290) bus, err = transportsClient.GetTCLPassages(ctx, 290)
if err != nil { if err != nil {
log.Println("error getting bus:", err) slog.ErrorContext(ctx, "error getting bus", "err", err)
} }
}() }()
go func() { go func() {
@ -66,7 +66,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
tram, err = transportsClient.GetTCLPassages(ctx, 34068) tram, err = transportsClient.GetTCLPassages(ctx, 34068)
if err != nil { if err != nil {
log.Println("error getting tram:", err) slog.ErrorContext(ctx, "error getting tram", "err", err)
} }
}() }()
go func() { go func() {
@ -79,7 +79,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
velovRoc, err = transportsClient.GetVelovStation(ctx, 10044) velovRoc, err = transportsClient.GetVelovStation(ctx, 10044)
if err != nil { if err != nil {
log.Println("error getting velov:", err) slog.ErrorContext(ctx, "error getting velov", "err", err)
} }
}() }()
go func() { go func() {
@ -92,7 +92,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
fetes, err = feteClient.GetFete(ctx, nowFunc()) fetes, err = feteClient.GetFete(ctx, nowFunc())
if err != nil { if err != nil {
log.Println("error getting fetes:", err) slog.ErrorContext(ctx, "error getting fetes", "err", err)
} }
}() }()
go func() { go func() {
@ -105,7 +105,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
wthr, err = weatherClient.GetWeather(ctx) wthr, err = weatherClient.GetWeather(ctx)
if err != nil { if err != nil {
log.Println("error getting weather:", err) slog.ErrorContext(ctx, "error getting weather", "err", err)
} }
}() }()
go func() { go func() {
@ -118,7 +118,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
msg, err = hassClient.GetState(ctx, "input_text.e_paper_message") msg, err = hassClient.GetState(ctx, "input_text.e_paper_message")
if err != nil { if err != nil {
log.Println("error getting hass message:", err) slog.ErrorContext(ctx, "error getting hass message", "err", err)
} }
}() }()
@ -141,7 +141,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
drawVelov(gc, velovRoc, 365) drawVelov(gc, velovRoc, 365)
drawDate(gc, nowFunc()) drawDate(gc, nowFunc())
drawFete(gc, fetes) drawFete(gc, fetes)
drawWeather(gc, wthr) drawWeather(ctx, gc, wthr)
drawMsg(gc, msg) drawMsg(gc, msg)
return img, nil return img, nil
@ -152,13 +152,15 @@ func drawMsg(gc *draw2dimg.GraphicContext, quote string) {
} }
func drawWeather(gc *draw2dimg.GraphicContext, wthr *weather.Prevision) { func drawWeather(ctx context.Context, gc *draw2dimg.GraphicContext, wthr *weather.Prevision) {
if wthr == nil { if wthr == nil {
return return
} }
if len(wthr.Daily) == 0 || len(wthr.Daily[0].Weather) == 0 { dailyLen := len(wthr.Daily)
log.Println("missing daily or daily weather") dailyWeatherLen := len(wthr.Daily[0].Weather)
if dailyLen == 0 || dailyWeatherLen == 0 {
slog.ErrorContext(ctx, "missing daily or daily weather", "daily_len", dailyLen, "daily_weather_len", dailyWeatherLen)
return return
} }
@ -166,7 +168,7 @@ func drawWeather(gc *draw2dimg.GraphicContext, wthr *weather.Prevision) {
dailyWeather := daily.Weather[0] dailyWeather := daily.Weather[0]
err := drawWeatherIcon(gc, dailyWeather) err := drawWeatherIcon(gc, dailyWeather)
if err != nil { if err != nil {
log.Println("Failed to draw weather icon:", err) slog.ErrorContext(ctx, "Failed to draw weather icon", "err", err)
} }
text(gc, formatTemp(wthr.Current.Temp), 23, leftX, 120) text(gc, formatTemp(wthr.Current.Temp), 23, leftX, 120)

19
main.go
View file

@ -10,7 +10,7 @@ import (
"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/slog"
"os" "os"
"time" "time"
) )
@ -18,14 +18,16 @@ import (
const fontName = "default" const fontName = "default"
func main() { func main() {
log.Println("starting...")
ctx := context.Background() ctx := context.Background()
slog.InfoContext(ctx, "starting...")
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) slog.ErrorContext(ctx, "error loading font", "err", err)
os.Exit(1)
} }
fontCache := MyFontCache{} fontCache := MyFontCache{}
fontCache.Store(draw2d.FontData{Name: fontName}, font) fontCache.Store(draw2d.FontData{Name: fontName}, font)
draw2d.SetFontCache(fontCache) draw2d.SetFontCache(fontCache)
@ -56,7 +58,9 @@ func main() {
initFastThreshold = minInitFastThreshold initFastThreshold = minInitFastThreshold
} }
log.Printf("sleep duration: %v\n", sleep) slog.InfoContext(ctx, "config",
"sleep_duration", sleep,
"init_fast_threshold", initFastThreshold)
hassClient := home_assistant.New(nil, home_assistant.Config{ hassClient := home_assistant.New(nil, home_assistant.Config{
Token: os.Getenv("HOME_ASSISTANT_TOKEN"), Token: os.Getenv("HOME_ASSISTANT_TOKEN"),
@ -72,8 +76,9 @@ func main() {
weatherClient, weatherClient,
hassClient, hassClient,
); err != nil { ); err != nil {
log.Fatal("error: ", err) slog.ErrorContext(ctx, "error", "err", err)
os.Exit(1)
} }
log.Println("done") slog.InfoContext(ctx, "done")
} }

View file

@ -2,12 +2,12 @@ package main
import ( import (
"context" "context"
"fmt"
"github.com/Crocmagnon/display-epaper/fete" "github.com/Crocmagnon/display-epaper/fete"
"github.com/Crocmagnon/display-epaper/home_assistant" "github.com/Crocmagnon/display-epaper/home_assistant"
"github.com/Crocmagnon/display-epaper/transports" "github.com/Crocmagnon/display-epaper/transports"
"github.com/Crocmagnon/display-epaper/weather" "github.com/Crocmagnon/display-epaper/weather"
"github.com/llgcode/draw2d/draw2dimg" "github.com/llgcode/draw2d/draw2dimg"
"log"
"time" "time"
) )
@ -35,10 +35,11 @@ func run(
hassClient, hassClient,
) )
if err != nil { if err != nil {
log.Fatal(err) return err
} }
if err := draw2dimg.SaveToPngFile("out/black.png", img); err != nil { if err := draw2dimg.SaveToPngFile("out/black.png", img); err != nil {
log.Fatalf("error saving image: %v", err) return fmt.Errorf("saving img: %w", err)
} }
return nil return nil

View file

@ -10,6 +10,7 @@ import (
"github.com/Crocmagnon/display-epaper/weather" "github.com/Crocmagnon/display-epaper/weather"
"image" "image"
"log" "log"
"log/slog"
"os" "os"
"periph.io/x/host/v3" "periph.io/x/host/v3"
"time" "time"
@ -39,12 +40,12 @@ func run(
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Println("stopping because of context") slog.InfoContext(ctx, "stopping because of context")
return ctx.Err() return ctx.Err()
default: default:
} }
log.Println("running loop") slog.InfoContext(ctx, "running loop")
img, err := loop( img, err := loop(
ctx, ctx,
@ -57,12 +58,12 @@ func run(
hassClient, hassClient,
) )
if err != nil { if err != nil {
log.Printf("error looping: %v\n", err) slog.ErrorContext(ctx, "error looping", "err", err)
} }
currentImg = img currentImg = img
log.Printf("time.Sleep(%v)\n", sleep) slog.InfoContext(ctx, "sleep", "duration", sleep)
time.Sleep(sleep) time.Sleep(sleep)
} }
} }
@ -95,17 +96,17 @@ func loop(
} }
if imgEqual(currentImg, img, epd.Width, epd.Height) { if imgEqual(currentImg, img, epd.Width, epd.Height) {
log.Println("Images are equal, doing nothing.") slog.InfoContext(ctx, "Images are equal, doing nothing.")
return img, nil return img, nil
} }
defer func() { defer func() {
if err := display.Sleep(); err != nil { if err := display.Sleep(); err != nil {
log.Printf("error sleeping: %v\n", err) slog.ErrorContext(ctx, "error sleeping", "err", err)
} }
}() }()
err := initDisplay(display, initFastThreshold) err := initDisplay(ctx, display, initFastThreshold)
if err != nil { if err != nil {
return nil, fmt.Errorf("initializing display: %w", err) return nil, fmt.Errorf("initializing display: %w", err)
} }
@ -125,32 +126,31 @@ func shouldDisplay(ctx context.Context, hassClient *home_assistant.Client) bool
return true return true
} }
log.Printf("found dayNight=%v\n", dayNight)
hallLights, err := hassClient.GetState(ctx, "light.couloir") hallLights, err := hassClient.GetState(ctx, "light.couloir")
if err != nil { if err != nil {
log.Printf("error getting hall lights: %v ; displaying anyway\n", err) log.Printf("error getting hall lights: %v ; displaying anyway\n", err)
return true return true
} }
log.Printf("found hallLights=%v\n", hallLights)
presentAway, err := hassClient.GetState(ctx, "input_select.house_present_away") presentAway, err := hassClient.GetState(ctx, "input_select.house_present_away")
if err != nil { if err != nil {
log.Printf("error getting day night: %v ; displaying anyway\n", err) log.Printf("error getting day night: %v ; displaying anyway\n", err)
return true return true
} }
log.Printf("found presentAway=%v\n", presentAway) slog.InfoContext(ctx, "home assistant states",
"hall_lights", hallLights,
"day_night", dayNight,
"present_away", presentAway)
res := (hallLights == "on" || dayNight == "day") && presentAway == "present" res := (hallLights == "on" || dayNight == "day") && presentAway == "present"
log.Printf("shouldDisplay: %v\n", res) slog.InfoContext(ctx, "computed should display", "should_display", res)
return res return res
} }
const filename = "/perm/display-epaper-lastFullRefresh" const filename = "/perm/display-epaper-lastFullRefresh"
func initDisplay(display *epd.EPD, threshold time.Duration) error { func initDisplay(ctx context.Context, display *epd.EPD, threshold time.Duration) error {
if canInitFast(threshold) { if canInitFast(threshold) {
err := display.InitFast() err := display.InitFast()
if err != nil { if err != nil {
@ -164,7 +164,7 @@ func initDisplay(display *epd.EPD, threshold time.Duration) error {
return fmt.Errorf("running full init: %w", err) return fmt.Errorf("running full init: %w", err)
} }
markInitFull() markInitFull(ctx)
return nil return nil
} }
@ -178,10 +178,10 @@ func canInitFast(threshold time.Duration) bool {
return stat.ModTime().Add(threshold).After(time.Now()) return stat.ModTime().Add(threshold).After(time.Now())
} }
func markInitFull() { func markInitFull(ctx context.Context) {
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
log.Printf("error marking full refresh: %v\n", err) slog.ErrorContext(ctx, "error marking full refresh", "err", err)
} }
f.Close() f.Close()

View file

@ -4,7 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/carlmjohnson/requests" "github.com/carlmjohnson/requests"
"log/slog"
"net/http" "net/http"
"time"
) )
type Stop struct { type Stop struct {
@ -26,9 +28,17 @@ type Passages struct {
type Config struct { type Config struct {
} }
const cacheTimeout = 2 * time.Minute
type Client struct { type Client struct {
client *http.Client client *http.Client
config Config config Config
passagesCache *Passages
passagesCacheTime time.Time
stationCache *Station
stationCacheTime time.Time
} }
func New(httpClient *http.Client, config Config) *Client { func New(httpClient *http.Client, config Config) *Client {
@ -48,11 +58,28 @@ func (c *Client) GetTCLPassages(ctx context.Context, stop int) (res *Passages, e
ToJSON(&res). ToJSON(&res).
Fetch(ctx) Fetch(ctx)
if err != nil { if err != nil {
if res = c.getPassagesCache(); res != nil {
slog.WarnContext(ctx, "retrieving passages from cache")
return res, nil
}
return nil, fmt.Errorf("calling api: %w", err) return nil, fmt.Errorf("calling api: %w", err)
} }
c.passagesCache = res
c.passagesCacheTime = time.Now()
return res, nil return res, nil
} }
func (c *Client) getPassagesCache() *Passages {
if c.passagesCache != nil && time.Since(c.passagesCacheTime) < cacheTimeout {
return c.passagesCache
}
return nil
}
type Station struct { type Station struct {
Name string `json:"name"` Name string `json:"name"`
BikesAvailable int `json:"bikes_available"` BikesAvailable int `json:"bikes_available"`
@ -67,7 +94,24 @@ func (c *Client) GetVelovStation(ctx context.Context, station int) (res *Station
ToJSON(&res). ToJSON(&res).
Fetch(ctx) Fetch(ctx)
if err != nil { if err != nil {
if res = c.getStationCache(); res != nil {
slog.WarnContext(ctx, "retrieving station from cache")
return res, nil
}
return nil, fmt.Errorf("calling api: %w", err) return nil, fmt.Errorf("calling api: %w", err)
} }
c.stationCache = res
c.stationCacheTime = time.Now()
return res, nil return res, nil
} }
func (c *Client) getStationCache() *Station {
if c.stationCache != nil && time.Since(c.stationCacheTime) < cacheTimeout {
return c.stationCache
}
return nil
}

View file

@ -6,7 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/carlmjohnson/requests" "github.com/carlmjohnson/requests"
"log" "log/slog"
"net/http" "net/http"
"os" "os"
"time" "time"
@ -152,11 +152,11 @@ type Weather struct {
func (c *Client) GetWeather(ctx context.Context) (res *Prevision, err error) { func (c *Client) GetWeather(ctx context.Context) (res *Prevision, err error) {
if val, err := loadFromDisk(c.config.CacheLocation); nil == err { if val, err := loadFromDisk(c.config.CacheLocation); nil == err {
log.Println("found weather in cache") slog.InfoContext(ctx, "found weather in cache")
return &val, nil return &val, nil
} }
log.Println("querying weather") slog.InfoContext(ctx, "querying weather")
err = requests.URL("https://api.openweathermap.org/data/3.0/onecall"). err = requests.URL("https://api.openweathermap.org/data/3.0/onecall").
Client(c.client). Client(c.client).
@ -173,7 +173,7 @@ func (c *Client) GetWeather(ctx context.Context) (res *Prevision, err error) {
} }
if err := res.dumpToDisk(c.config.CacheLocation); err != nil { if err := res.dumpToDisk(c.config.CacheLocation); err != nil {
log.Printf("error dumping files to disk: %v\n", err) slog.ErrorContext(ctx, "error dumping files to disk", "err", err)
} }
return res, nil return res, nil