diff --git a/.env.example b/.env.example index c4743d3..95c71f3 100644 --- a/.env.example +++ b/.env.example @@ -10,10 +10,12 @@ MONITORINK_CLAUDE_UA=claude-code/2.1.172 # Burn rate via ccusage (nécessite ccusage installé + ~/.claude/projects monté). 0/1 MONITORINK_CCUSAGE=0 -# Affichage +# Affichage — canevas de rendu en PAYSAGE ; le PNG est pivoté de 90° pour la Kobo. MONITORINK_TZ=Europe/Paris -MONITORINK_WIDTH=1264 -MONITORINK_HEIGHT=1680 +MONITORINK_WIDTH=1680 +MONITORINK_HEIGHT=1264 +# Sens de rotation : "cw" = Kobo posée bouton à droite, "ccw" = bouton à gauche. +MONITORINK_ROTATE=cw MONITORINK_CACHE_TTL=120 # Météo (Open-Meteo, sans clé) — coordonnées diff --git a/backend/config.py b/backend/config.py index 6e4200c..52086d7 100644 --- a/backend/config.py +++ b/backend/config.py @@ -44,8 +44,12 @@ class Config: # --- Affichage --- timezone: str = field(default_factory=lambda: _get("MONITORINK_TZ", "Europe/Paris")) locale: str = field(default_factory=lambda: _get("MONITORINK_LOCALE", "fr_FR")) - width: int = field(default_factory=lambda: int(_get("MONITORINK_WIDTH", "1264"))) - height: int = field(default_factory=lambda: int(_get("MONITORINK_HEIGHT", "1680"))) + # Canevas de RENDU en paysage (1680x1264). Le PNG est ensuite pivoté de 90° dans + # render.py pour le panneau e-ink physiquement en portrait (1264x1680). + width: int = field(default_factory=lambda: int(_get("MONITORINK_WIDTH", "1680"))) + height: int = field(default_factory=lambda: int(_get("MONITORINK_HEIGHT", "1264"))) + # Sens de rotation pour l'affichage Kobo : "cw" (bouton à droite) ou "ccw". + rotate: str = field(default_factory=lambda: _get("MONITORINK_ROTATE", "cw").lower()) # --- Claude --- # Chemin du fichier .credentials.json d'un login Claude ISOLÉ dédié à Monitorink diff --git a/backend/render.py b/backend/render.py index 3566b53..9998d24 100644 --- a/backend/render.py +++ b/backend/render.py @@ -1,4 +1,5 @@ -"""Construit le contexte, rend le HTML puis le capture en PNG niveaux de gris (1264x1680).""" +"""Construit le contexte, rend le HTML en paysage (1680x1264) puis le capture en PNG +niveaux de gris, pivoté de 90° pour le panneau e-ink portrait (1264x1680).""" from __future__ import annotations import asyncio @@ -98,6 +99,10 @@ async def render_png() -> bytes: # Conversion niveaux de gris (mode 'L') -> e-ink friendly, fichier plus léger. img = Image.open(io.BytesIO(png_bytes)).convert("L") + # Le canevas est rendu en paysage (1680x1264) ; on pivote de 90° pour le panneau + # e-ink physiquement en portrait. "cw" = bouton à droite (rotation horaire). + rota = Image.ROTATE_270 if config.rotate == "cw" else Image.ROTATE_90 + img = img.transpose(rota) out = io.BytesIO() img.save(out, format="PNG", optimize=True) return out.getvalue() diff --git a/backend/templates/dashboard.html b/backend/templates/dashboard.html index af31bc4..6760041 100644 --- a/backend/templates/dashboard.html +++ b/backend/templates/dashboard.html @@ -3,7 +3,8 @@ -
-
{{ time }}
-
-
{{ dow }}
-
{{ date }}
-
-
-
+ +
+
+
{{ time }}
+
+ {{ dow }} · {{ date }} +
+
+
-
- {% if weather.ok %} -
{{ weather.icon }}
-
-
{{ weather.temp | round | int }}°
+
+ {% if weather.ok %} +
+
{{ weather.icon }}
+
{{ weather.temp | round | int }}°
+
{{ weather.label }} · ressenti {{ weather.feels_like | round | int }}°
-
-
-
min {{ weather.temp_min | round | int }}° / max {{ weather.temp_max | round | int }}°
- {% if weather.precip_prob is not none %}
pluie {{ weather.precip_prob }}%
{% endif %} -
- {% else %} -
Météo indisponible
- {% endif %} -
-
- -
-
Abonnement Claude{% if claude.ok %} · Max 5x{% endif %}
- {% if not claude.ok %} -
⚠ {{ claude.error }}
- {% else %} - {% for g in gauges %} -
-
- {{ g.name }} - {{ g.remaining | round | int }}% restant +
+ min {{ weather.temp_min | round | int }}° / max {{ weather.temp_max | round | int }}°{% if weather.precip_prob is not none %} · pluie {{ weather.precip_prob }}%{% endif %}
-
-
-
-
{{ (100 - g.remaining) | round | int }}% utilisé · reset dans {{ g.resets_in }}{% if g.extra %} · {{ g.extra }}{% endif %}
-
- {% endfor %} - {% if claude.extra %} -
{{ claude.extra.label }}
+ {% else %} +
Météo indisponible
{% endif %} - {% if claude.burn_rate %} -
Burn rate : {{ claude.burn_rate | round | int }} tok/min
- {% endif %} - {% endif %} -
- - {% if ha_states %} -
-
-
Maison
-
- {% for s in ha_states %} -
{{ s.label }}{{ s.display }}
- {% endfor %}
- {% endif %} + + +
+
+
Abonnement Claude{% if claude.ok %} · Max 5x{% endif %}
+ {% if not claude.ok %} +
⚠ {{ claude.error }}
+ {% else %} + {% for g in gauges %} +
+
+ {{ g.name }} + {{ g.remaining | round | int }}% restant +
+
+
+
+
{{ (100 - g.remaining) | round | int }}% utilisé · reset dans {{ g.resets_in }}{% if g.extra %} · {{ g.extra }}{% endif %}
+
+ {% endfor %} + {% if claude.extra %} +
{{ claude.extra.label }}
+ {% endif %} + {% if claude.burn_rate %} +
Burn rate : {{ claude.burn_rate | round | int }} tok/min
+ {% endif %} + {% endif %} +
+ + {% if ha_states %} +
+
Maison
+
+ {% for s in ha_states %} +
{{ s.label }}{{ s.display }}
+ {% endfor %} +
+
+ {% endif %} +