Skip to content

ADR-0054: Tracked sensors — any numeric sensor chosen in Home Assistant, as a curve left of the clock and in a « Suivi » popup, pushed by tab5_maj_suivi

Status: Accepted (2026-10-10, asked for by the author; not tried on a tablet when written). Date: 2026-10-10

Context

The zone left of the clock shows a content chosen in the blueprint (ADR-0051): the voice controls, a chart of the coming hours or a compact music player. The author asked for a fourth one: the curve of a numeric sensor chosen in Home Assistant (his first use: share prices, but the tablet must not know that), « in the style of the weather chart », with its value and its change; and a full-screen popup listing every sensor he follows, opened by a tap on that card.

Constraints: push-only and events-only (ADR-0001, ADR-0025); no entity ID in the firmware or in the public HA files (ADR-0024); lv_chart and lv_canvas are not compiled; colours from the palette (ADR-0029).

Decision

  • Generic, not « stocks ». The code and the screen say « Suivi » (tracking), « capteur » (sensor); no word about shares anywhere. Any sensor with state_class: measurement (a share price, a temperature, a power, a level…).
  • Chosen in Home Assistant. A multiple-choice list « Tab5 · capteurs suivis · tracked sensors » (select.tab5_capteurs_suivis, packages/tab5_reglages.yaml), built like « Tab5 · lecteurs de musique » (ADR-0050): « ✓ x » removes a sensor, « ○ x » adds it, six at most, order of choice, memory input_text.tab5_choix_suivis. Only measurement sensors are offered: the others have no statistics, so no curve. Never guessed.
  • One new action, one compact payload. tab5_maj_suivi(payload): one record per sensor, name|value|unit|change|kind|points separated by ; — name = friendly_name (40 characters; empty: the screen writes « Capteur », never an entity ID), value = the state as is when numeric (empty otherwise: « -- »), change + kind = change_pct and p when the entity gives it (a share's change of the day, shown with « Aujourd'hui »), otherwise the difference between the value and the first hour of the curve and a (shown in the sensor's unit, with « 24 h »), points = the 24 hourly means of the recorder's statistics then the current value, scaled 0–100 (50 for a flat curve, empty for an hour without a row). Read in pure C++ by suivis_lire() (Tab5/socle/tab5_parse.*, section 11; cases in tools/test_parse.cpp, called by the libFuzzer harness): at most 6 sensors and 32 points, values bounded, a record without at least a name and a value field skipped, more than 3 decimals rounded without trailing zeros, the sign and the arrow decided on the rounded change (never « -0.00 »).
  • Pushed by a new package, rate-limited. packages/tab5_suivi.yaml: script.tab5_suivi_pousser reads recorder.get_statistics (period hour, type mean, from the round hour minus 24 h in UTC: no daylight-saving trap) and calls the action. A mirror (« Tab5 · signature du suivi ») changes when a tracked sensor changes; an automation in mode single pushes at once, waits five minutes (changes meanwhile are dropped silently) and pushes again only if the signature moved: a sensor that changes every second costs one push every five minutes at most. A second automation pushes at once when the list changes, when the tablet connects, on « MAJ Écran », at HA start and every hour (the curve moves on). Both are guarded like the other packages (tab5_origine, tab5_connectee).
  • The zone content « capteur ». ZoneGauche::CAPTEUR, code capteur, at the end of the enum (NVS keeps the index), in the blueprint's options and default cycle. A card with the chart's footprint and glass (zone_suivi, suivi_zone.yaml, 405 × 184 at x 20, y 42): the name top left (cut with « … »), the change top right (trend arrow and text in SUCCESS up, ERROR down, TEXT_DIM flat), the value in the date's font with its unit, and the 24 h curve over the chart's gradient. The first sensor of the list is the one shown (the simplest rule; no scrolling of sensors in the card). Skipped by the tap and at start while Home Assistant says the list is empty (suivi_zone_disponible(), like the player). A tap opens the popup.
  • One gradient buffer for two cards. The chart and the sensor card are never shown together: they share the chart's 156 KiB PSRAM buffer through zone_degrade_peindre() (tab5_zone_gauche.cpp), which hands it over and tells the previous owner (callback) that its picture is gone, so it repaints at its next appearance. No second buffer.
  • The « Suivi » popup. Full-screen modal with the shared chrome (ADR-0009), one ModalRegistry::add line (ADR-0013), Ecran::SUIVI (option « Suivi » at the end of « Aller à l'écran », gesture code suivi at the end of kCodesGestes, index 26, icon chart-line). One card per sensor (suivi_carte.yaml, six instances), placed by tab5_suivi.cpp: one row up to three sensors, two rows above (two columns for four), the last row centred, inside the modal body (x 24–1226, y 72–670). Each card: name, value and unit, arrow + change + period, and the curve (ui_courbe_lisse(), lv_line, its last point ringed in the background colour). No sensor: « En attente de Home Assistant », or « Aucun capteur suivi » and where to choose them.
  • Painted only when needed. The popup at its opening and on each push while open; the zone card only while shown (suivi_zone_montrer(), called by zone_gauche_appliquer()), otherwise marked dirty. Theme change: suivi_rejouer_theme().

Consequences

  • Contract 1.2.0 (minor: one action added). Update order: firmware first, then packages/tab5_reglages.yaml, packages/tab5_suivi.yaml and the blueprint. New HA files with an older firmware: the push fails without stopping anything else (continue_on_error); new firmware with older HA files: the popup says « En attente de Home Assistant » and the zone card too (it is not skipped before a first push).
  • A curve is a trend, not a scale: 0 is the lowest of the 24 hours, 100 the highest, so a 0.1 % move looks as large as a 10 % one. The change and the value carry the size.
  • A sensor without state_class (not offered by the list, but a hand-written memory could name it) shows its value without a curve.
  • Four new screen texts in six languages; MDI glyphs chart-line (26, 32, 70) and trending-up/down/neutral (32).
  • Render: four captures (suivi-vide, suivi, accueil-zone-capteur, accueil-zone-capteur-gelule).