diff --git a/epd/epd.go b/epd/epd.go index d45193d..e0b9120 100644 --- a/epd/epd.go +++ b/epd/epd.go @@ -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++ { diff --git a/fete/fete.go b/fete/fete.go index 1e586ae..f999977 100644 --- a/fete/fete.go +++ b/fete/fete.go @@ -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 diff --git a/img.go b/img.go index 3ae5a87..172db5c 100644 --- a/img.go +++ b/img.go @@ -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) diff --git a/main.go b/main.go index 4e727cb..9576987 100644 --- a/main.go +++ b/main.go @@ -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") } diff --git a/run_darwin_arm64.go b/run_darwin_arm64.go index d93b788..0b7b2b5 100644 --- a/run_darwin_arm64.go +++ b/run_darwin_arm64.go @@ -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 diff --git a/run_linux_arm64.go b/run_linux_arm64.go index 3e88939..c21391a 100644 --- a/run_linux_arm64.go +++ b/run_linux_arm64.go @@ -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() diff --git a/transports/transports.go b/transports/transports.go index dcbd741..af6281d 100644 --- a/transports/transports.go +++ b/transports/transports.go @@ -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 +} diff --git a/weather/weather.go b/weather/weather.go index 31f61b2..e82be5f 100644 --- a/weather/weather.go +++ b/weather/weather.go @@ -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