2024-09-15 09:01:13 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-09-15 10:46:07 +02:00
|
|
|
"context"
|
2024-09-15 23:25:17 +02:00
|
|
|
"embed"
|
2024-09-15 10:46:07 +02:00
|
|
|
"fmt"
|
2024-09-15 09:01:13 +02:00
|
|
|
"github.com/Crocmagnon/display-epaper/epd"
|
2024-09-15 12:00:36 +02:00
|
|
|
"github.com/Crocmagnon/display-epaper/fete"
|
2024-11-26 02:34:53 +01:00
|
|
|
"github.com/Crocmagnon/display-epaper/fonts"
|
2024-09-18 22:26:40 +02:00
|
|
|
"github.com/Crocmagnon/display-epaper/home_assistant"
|
2024-09-15 23:55:31 +02:00
|
|
|
"github.com/Crocmagnon/display-epaper/quotes"
|
2024-09-15 10:46:07 +02:00
|
|
|
"github.com/Crocmagnon/display-epaper/transports"
|
2024-09-15 23:25:17 +02:00
|
|
|
"github.com/Crocmagnon/display-epaper/weather"
|
2024-09-15 09:01:13 +02:00
|
|
|
"github.com/llgcode/draw2d"
|
|
|
|
"github.com/llgcode/draw2d/draw2dimg"
|
|
|
|
"image"
|
|
|
|
"image/color"
|
2024-10-21 23:14:55 +02:00
|
|
|
"log/slog"
|
2024-09-15 23:25:17 +02:00
|
|
|
"math"
|
2024-09-15 12:00:36 +02:00
|
|
|
"strconv"
|
2024-09-15 23:25:17 +02:00
|
|
|
"strings"
|
2024-09-16 14:20:59 +02:00
|
|
|
"sync"
|
2024-09-15 12:00:36 +02:00
|
|
|
"time"
|
2024-09-15 09:01:13 +02:00
|
|
|
)
|
|
|
|
|
2024-09-15 23:25:17 +02:00
|
|
|
//go:embed icons
|
|
|
|
var icons embed.FS
|
|
|
|
|
|
|
|
const (
|
|
|
|
leftX = 20
|
|
|
|
rightX = 530
|
|
|
|
)
|
|
|
|
|
2024-09-18 22:26:40 +02:00
|
|
|
func getImg(ctx context.Context, nowFunc func() time.Time, transportsClient *transports.Client, feteClient *fete.Client, weatherClient *weather.Client, hassClient *home_assistant.Client) (*image.RGBA, error) {
|
2024-09-16 14:20:59 +02:00
|
|
|
var (
|
|
|
|
bus *transports.Passages
|
|
|
|
tram *transports.Passages
|
|
|
|
velovRoc *transports.Station
|
|
|
|
fetes *fete.Fete
|
|
|
|
wthr *weather.Prevision
|
2024-09-18 22:26:40 +02:00
|
|
|
msg string
|
2024-09-16 14:20:59 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
wg := &sync.WaitGroup{}
|
2024-09-18 22:26:40 +02:00
|
|
|
wg.Add(6)
|
2024-09-16 14:20:59 +02:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
2024-10-12 15:47:32 +02:00
|
|
|
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
2024-09-16 14:20:59 +02:00
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
bus, err = transportsClient.GetTCLPassages(ctx, 290)
|
|
|
|
if err != nil {
|
2024-10-21 23:14:55 +02:00
|
|
|
slog.ErrorContext(ctx, "error getting bus", "err", err)
|
2024-09-16 14:20:59 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
2024-10-12 15:47:32 +02:00
|
|
|
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
2024-09-16 14:20:59 +02:00
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
tram, err = transportsClient.GetTCLPassages(ctx, 34068)
|
|
|
|
if err != nil {
|
2024-10-21 23:14:55 +02:00
|
|
|
slog.ErrorContext(ctx, "error getting tram", "err", err)
|
2024-09-16 14:20:59 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
2024-10-12 15:47:32 +02:00
|
|
|
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
2024-09-16 14:20:59 +02:00
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
velovRoc, err = transportsClient.GetVelovStation(ctx, 10044)
|
|
|
|
if err != nil {
|
2024-10-21 23:14:55 +02:00
|
|
|
slog.ErrorContext(ctx, "error getting velov", "err", err)
|
2024-09-16 14:20:59 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
fetes, err = feteClient.GetFete(ctx, nowFunc())
|
|
|
|
if err != nil {
|
2024-10-21 23:14:55 +02:00
|
|
|
slog.ErrorContext(ctx, "error getting fetes", "err", err)
|
2024-09-16 14:20:59 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
wthr, err = weatherClient.GetWeather(ctx)
|
|
|
|
if err != nil {
|
2024-10-21 23:14:55 +02:00
|
|
|
slog.ErrorContext(ctx, "error getting weather", "err", err)
|
2024-09-16 14:20:59 +02:00
|
|
|
}
|
|
|
|
}()
|
2024-09-18 22:26:40 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
var err error
|
2024-09-16 14:20:59 +02:00
|
|
|
|
2024-09-18 22:26:40 +02:00
|
|
|
msg, err = hassClient.GetState(ctx, "input_text.e_paper_message")
|
|
|
|
if err != nil {
|
2024-10-21 23:14:55 +02:00
|
|
|
slog.ErrorContext(ctx, "error getting hass message", "err", err)
|
2024-09-18 22:26:40 +02:00
|
|
|
}
|
|
|
|
}()
|
2024-09-15 12:00:36 +02:00
|
|
|
|
2024-09-15 09:01:13 +02:00
|
|
|
img := newWhite()
|
|
|
|
|
|
|
|
gc := draw2dimg.NewGraphicContext(img)
|
|
|
|
|
2024-09-15 10:46:07 +02:00
|
|
|
gc.SetFillRule(draw2d.FillRuleWinding)
|
2024-09-15 13:07:32 +02:00
|
|
|
gc.SetFillColor(color.RGBA{255, 255, 255, 255})
|
2024-09-15 09:01:13 +02:00
|
|
|
gc.SetStrokeColor(color.RGBA{0, 0, 0, 255})
|
|
|
|
|
2024-09-16 14:20:59 +02:00
|
|
|
wg.Wait()
|
|
|
|
|
2024-09-18 22:26:40 +02:00
|
|
|
if msg == "" {
|
|
|
|
msg = quotes.GetQuote(nowFunc())
|
|
|
|
}
|
|
|
|
|
2024-11-26 02:15:59 +01:00
|
|
|
drawTCL(gc, bus, 45)
|
2024-10-19 10:13:45 +02:00
|
|
|
drawTCL(gc, tram, 205)
|
|
|
|
drawVelov(gc, velovRoc, 365)
|
2024-09-15 23:25:17 +02:00
|
|
|
drawDate(gc, nowFunc())
|
|
|
|
drawFete(gc, fetes)
|
2024-10-21 23:14:55 +02:00
|
|
|
drawWeather(ctx, gc, wthr)
|
2024-09-18 22:26:40 +02:00
|
|
|
drawMsg(gc, msg)
|
2024-09-15 12:00:36 +02:00
|
|
|
|
|
|
|
return img, nil
|
|
|
|
}
|
|
|
|
|
2024-09-18 22:26:40 +02:00
|
|
|
func drawMsg(gc *draw2dimg.GraphicContext, quote string) {
|
2024-11-26 02:15:59 +01:00
|
|
|
text(gc, quote, 15, leftX, 450, fontItalic)
|
2024-09-15 23:55:31 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-10-21 23:14:55 +02:00
|
|
|
func drawWeather(ctx context.Context, gc *draw2dimg.GraphicContext, wthr *weather.Prevision) {
|
2024-09-15 23:25:17 +02:00
|
|
|
if wthr == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-10-21 23:14:55 +02:00
|
|
|
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)
|
2024-09-15 23:25:17 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
daily := wthr.Daily[0]
|
|
|
|
dailyWeather := daily.Weather[0]
|
2024-11-26 02:15:59 +01:00
|
|
|
err := drawWeatherIcon(gc, wthr.Current.Weather[0])
|
2024-09-15 23:25:17 +02:00
|
|
|
if err != nil {
|
2024-10-21 23:14:55 +02:00
|
|
|
slog.ErrorContext(ctx, "Failed to draw weather icon", "err", err)
|
2024-09-15 23:25:17 +02:00
|
|
|
}
|
|
|
|
|
2024-11-26 02:15:59 +01:00
|
|
|
text(gc, formatTemp(wthr.Current.Temp), 23, leftX, 125, fontRegular)
|
2024-09-16 00:35:45 +02:00
|
|
|
|
2024-11-26 02:15:59 +01:00
|
|
|
const xAlign = 120
|
|
|
|
const fontSize = 18
|
|
|
|
|
|
|
|
text(gc, "journée", fontSize, xAlign, 35, fontBold)
|
|
|
|
text(gc, "max "+formatTemp(daily.Temp.Max), fontSize, xAlign, 65, fontRegular)
|
|
|
|
text(gc, fmt.Sprintf("pluie %v%%", int(math.Round(daily.Pop*100))), fontSize, xAlign, 95, fontRegular)
|
|
|
|
text(gc, dailyWeather.Description, fontSize, xAlign, 125, fontRegular)
|
2024-09-15 23:25:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func drawWeatherIcon(gc *draw2dimg.GraphicContext, dailyWeather weather.Weather) error {
|
|
|
|
icon := strings.TrimSuffix(dailyWeather.Icon, "d")
|
|
|
|
icon = strings.TrimSuffix(icon, "n")
|
|
|
|
f, err := icons.Open(fmt.Sprintf("icons/%sd.png", icon))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("opening icon: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
img, _, err := image.Decode(f)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decoding icon: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
gc.DrawImage(img)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-09-16 00:35:45 +02:00
|
|
|
func formatTemp(temp float64) string {
|
|
|
|
return fmt.Sprintf("%v°C", int(math.Round(temp)))
|
2024-09-15 23:25:17 +02:00
|
|
|
}
|
|
|
|
|
2024-09-15 14:19:01 +02:00
|
|
|
func drawVelov(gc *draw2dimg.GraphicContext, station *transports.Station, yOffset float64) {
|
2024-09-15 23:25:17 +02:00
|
|
|
if station == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-11-26 01:18:38 +01:00
|
|
|
text(gc, station.Name, 23, rightX, yOffset, fontBold)
|
2024-11-26 02:34:53 +01:00
|
|
|
|
|
|
|
yOffset += 30
|
|
|
|
|
|
|
|
text(gc, "\uE0D6", 22, rightX, yOffset+fonts.IconYOffset, fontIcons) // bike icon
|
|
|
|
text(gc, strconv.Itoa(station.BikesAvailable), 22, rightX+fonts.IconXOffset, yOffset, fontRegular)
|
|
|
|
|
|
|
|
nextCol := rightX + 100.0
|
|
|
|
text(gc, "\uEC08", 22, nextCol, yOffset+fonts.IconYOffset, fontIcons) // parking icon
|
|
|
|
text(gc, strconv.Itoa(station.DocksAvailable), 22, nextCol+fonts.IconXOffset, yOffset, fontRegular)
|
2024-09-15 23:25:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func drawDate(gc *draw2dimg.GraphicContext, now time.Time) {
|
2024-11-26 01:18:38 +01:00
|
|
|
text(gc, now.Format("15:04"), 110, leftX, 300, fontBold)
|
|
|
|
text(gc, getDate(now), 30, leftX, 345, fontRegular)
|
2024-09-15 14:19:01 +02:00
|
|
|
}
|
|
|
|
|
2024-09-15 23:25:17 +02:00
|
|
|
func drawFete(gc *draw2dimg.GraphicContext, fetes *fete.Fete) {
|
|
|
|
if fetes == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-11-26 01:18:38 +01:00
|
|
|
text(gc, fmt.Sprintf("On fête les %s", fetes.Name), 18, leftX, 380, fontRegular)
|
2024-09-15 12:00:36 +02:00
|
|
|
}
|
|
|
|
|
2024-09-15 13:07:32 +02:00
|
|
|
func drawTCL(gc *draw2dimg.GraphicContext, passages *transports.Passages, yoffset float64) {
|
2024-09-15 23:25:17 +02:00
|
|
|
if passages == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-15 10:46:07 +02:00
|
|
|
for i, passage := range passages.Passages {
|
2024-09-15 23:25:17 +02:00
|
|
|
x := float64(rightX + i*120)
|
2024-11-26 02:34:53 +01:00
|
|
|
text(gc, "\uE106", 23, x, yoffset+fonts.IconYOffset, fontIcons)
|
|
|
|
text(gc, passage.Ligne, 23, x+fonts.IconXOffset, yoffset, fontBold)
|
2024-09-15 10:46:07 +02:00
|
|
|
for j, delay := range passage.Delays {
|
2024-09-15 23:25:17 +02:00
|
|
|
y := yoffset + float64(j+1)*35
|
2024-11-26 01:18:38 +01:00
|
|
|
text(gc, delay, 22, x, y, fontRegular)
|
2024-09-15 13:07:32 +02:00
|
|
|
if j >= 2 { // limit number of delays displayed
|
|
|
|
break
|
|
|
|
}
|
2024-09-15 10:46:07 +02:00
|
|
|
}
|
|
|
|
}
|
2024-09-15 09:01:13 +02:00
|
|
|
}
|
|
|
|
|
2024-11-26 01:18:38 +01:00
|
|
|
func text(gc *draw2dimg.GraphicContext, s string, size, x, y float64, fontName string) {
|
2024-09-15 13:07:32 +02:00
|
|
|
gc.SetFillColor(color.RGBA{0, 0, 0, 255})
|
2024-09-15 09:01:13 +02:00
|
|
|
gc.SetFontData(draw2d.FontData{Name: fontName})
|
|
|
|
gc.SetFontSize(size)
|
2024-09-15 10:46:07 +02:00
|
|
|
gc.FillStringAt(s, x, y)
|
2024-09-15 09:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func newWhite() *image.RGBA {
|
|
|
|
img := image.NewRGBA(image.Rect(0, 0, epd.Width, epd.Height))
|
|
|
|
for y := 0; y < epd.Height; y++ {
|
|
|
|
for x := 0; x < epd.Width; x++ {
|
|
|
|
img.Set(x, y, color.White)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return img
|
|
|
|
}
|
|
|
|
|
|
|
|
func rect(gc *draw2dimg.GraphicContext, x1, y1, x2, y2 float64) {
|
|
|
|
gc.BeginPath()
|
|
|
|
gc.MoveTo(x1, y1)
|
|
|
|
gc.LineTo(x2, y1)
|
|
|
|
gc.LineTo(x2, y2)
|
|
|
|
gc.LineTo(x1, y2)
|
|
|
|
gc.Close()
|
|
|
|
gc.FillStroke()
|
|
|
|
}
|
2024-09-15 12:00:36 +02:00
|
|
|
|
|
|
|
func getDate(now time.Time) string {
|
2024-09-15 23:25:17 +02:00
|
|
|
return fmt.Sprintf("%v %v %v", getDow(now), getDay(now), getMonth(now))
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDow(now time.Time) string {
|
|
|
|
switch now.Weekday() {
|
|
|
|
case time.Monday:
|
|
|
|
return "lun"
|
|
|
|
case time.Tuesday:
|
|
|
|
return "mar"
|
|
|
|
case time.Wednesday:
|
|
|
|
return "mer"
|
|
|
|
case time.Thursday:
|
|
|
|
return "jeu"
|
|
|
|
case time.Friday:
|
|
|
|
return "ven"
|
|
|
|
case time.Saturday:
|
|
|
|
return "sam"
|
|
|
|
case time.Sunday:
|
|
|
|
return "dim"
|
|
|
|
}
|
|
|
|
|
|
|
|
return "?"
|
2024-09-15 12:00:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func getDay(now time.Time) string {
|
|
|
|
if now.Day() == 1 {
|
|
|
|
return "1er"
|
|
|
|
}
|
|
|
|
|
|
|
|
return strconv.Itoa(now.Day())
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMonth(t time.Time) string {
|
|
|
|
switch t.Month() {
|
|
|
|
case time.January:
|
|
|
|
return "jan."
|
|
|
|
case time.February:
|
|
|
|
return "fev."
|
|
|
|
case time.March:
|
|
|
|
return "mars"
|
|
|
|
case time.April:
|
|
|
|
return "avr."
|
|
|
|
case time.May:
|
|
|
|
return "mai"
|
|
|
|
case time.June:
|
|
|
|
return "juin"
|
|
|
|
case time.July:
|
|
|
|
return "juil."
|
|
|
|
case time.August:
|
|
|
|
return "août"
|
|
|
|
case time.September:
|
|
|
|
return "sept."
|
|
|
|
case time.October:
|
|
|
|
return "oct."
|
|
|
|
case time.November:
|
|
|
|
return "nov."
|
|
|
|
case time.December:
|
|
|
|
return "dec."
|
|
|
|
}
|
|
|
|
return "?"
|
|
|
|
}
|