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

View file

@ -5,7 +5,7 @@ import (
"encoding/json"
"fmt"
"github.com/carlmjohnson/requests"
"log"
"log/slog"
"net/http"
"os"
"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) {
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()) {
log.Println("fete cache is up to date")
slog.InfoContext(ctx, "fete cache is up to date")
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").
Pathf("/api/v2/%v/json-normal-%d-%d", c.config.APIKey, date.Day(), date.Month()).
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 {
log.Printf("error dumping files to disk: %v\n", err)
slog.ErrorContext(ctx, "error dumping files to disk", "err", err)
}
return res, nil

26
img.go
View file

@ -14,7 +14,7 @@ import (
"github.com/llgcode/draw2d/draw2dimg"
"image"
"image/color"
"log"
"log/slog"
"math"
"strconv"
"strings"
@ -53,7 +53,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
bus, err = transportsClient.GetTCLPassages(ctx, 290)
if err != nil {
log.Println("error getting bus:", err)
slog.ErrorContext(ctx, "error getting bus", "err", err)
}
}()
go func() {
@ -66,7 +66,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
tram, err = transportsClient.GetTCLPassages(ctx, 34068)
if err != nil {
log.Println("error getting tram:", err)
slog.ErrorContext(ctx, "error getting tram", "err", err)
}
}()
go func() {
@ -79,7 +79,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
velovRoc, err = transportsClient.GetVelovStation(ctx, 10044)
if err != nil {
log.Println("error getting velov:", err)
slog.ErrorContext(ctx, "error getting velov", "err", err)
}
}()
go func() {
@ -92,7 +92,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
fetes, err = feteClient.GetFete(ctx, nowFunc())
if err != nil {
log.Println("error getting fetes:", err)
slog.ErrorContext(ctx, "error getting fetes", "err", err)
}
}()
go func() {
@ -105,7 +105,7 @@ func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *tra
wthr, err = weatherClient.GetWeather(ctx)
if err != nil {
log.Println("error getting weather:", err)
slog.ErrorContext(ctx, "error getting weather", "err", err)
}
}()
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")
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)
drawDate(gc, nowFunc())
drawFete(gc, fetes)
drawWeather(gc, wthr)
drawWeather(ctx, gc, wthr)
drawMsg(gc, msg)
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 {
return
}
if len(wthr.Daily) == 0 || len(wthr.Daily[0].Weather) == 0 {
log.Println("missing daily or daily weather")
dailyLen := len(wthr.Daily)
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
}
@ -166,7 +168,7 @@ func drawWeather(gc *draw2dimg.GraphicContext, wthr *weather.Prevision) {
dailyWeather := daily.Weather[0]
err := drawWeatherIcon(gc, dailyWeather)
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)

19
main.go
View file

@ -10,7 +10,7 @@ import (
"github.com/llgcode/draw2d"
_ "golang.org/x/image/bmp"
"golang.org/x/image/font/gofont/goregular"
"log"
"log/slog"
"os"
"time"
)
@ -18,14 +18,16 @@ import (
const fontName = "default"
func main() {
log.Println("starting...")
ctx := context.Background()
slog.InfoContext(ctx, "starting...")
font, err := truetype.Parse(goregular.TTF)
if err != nil {
log.Fatalf("loading font: %v\n", err)
slog.ErrorContext(ctx, "error loading font", "err", err)
os.Exit(1)
}
fontCache := MyFontCache{}
fontCache.Store(draw2d.FontData{Name: fontName}, font)
draw2d.SetFontCache(fontCache)
@ -56,7 +58,9 @@ func main() {
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{
Token: os.Getenv("HOME_ASSISTANT_TOKEN"),
@ -72,8 +76,9 @@ func main() {
weatherClient,
hassClient,
); 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 (
"context"
"fmt"
"github.com/Crocmagnon/display-epaper/fete"
"github.com/Crocmagnon/display-epaper/home_assistant"
"github.com/Crocmagnon/display-epaper/transports"
"github.com/Crocmagnon/display-epaper/weather"
"github.com/llgcode/draw2d/draw2dimg"
"log"
"time"
)
@ -35,10 +35,11 @@ func run(
hassClient,
)
if err != nil {
log.Fatal(err)
return err
}
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

View file

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

View file

@ -4,7 +4,9 @@ import (
"context"
"fmt"
"github.com/carlmjohnson/requests"
"log/slog"
"net/http"
"time"
)
type Stop struct {
@ -26,9 +28,17 @@ type Passages struct {
type Config struct {
}
const cacheTimeout = 2 * time.Minute
type Client struct {
client *http.Client
config Config
passagesCache *Passages
passagesCacheTime time.Time
stationCache *Station
stationCacheTime time.Time
}
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).
Fetch(ctx)
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)
}
c.passagesCache = res
c.passagesCacheTime = time.Now()
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 {
Name string `json:"name"`
BikesAvailable int `json:"bikes_available"`
@ -67,7 +94,24 @@ func (c *Client) GetVelovStation(ctx context.Context, station int) (res *Station
ToJSON(&res).
Fetch(ctx)
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)
}
c.stationCache = res
c.stationCacheTime = time.Now()
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"
"fmt"
"github.com/carlmjohnson/requests"
"log"
"log/slog"
"net/http"
"os"
"time"
@ -152,11 +152,11 @@ type Weather struct {
func (c *Client) GetWeather(ctx context.Context) (res *Prevision, err error) {
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
}
log.Println("querying weather")
slog.InfoContext(ctx, "querying weather")
err = requests.URL("https://api.openweathermap.org/data/3.0/onecall").
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 {
log.Printf("error dumping files to disk: %v\n", err)
slog.ErrorContext(ctx, "error dumping files to disk", "err", err)
}
return res, nil