Give the history card axes, ranges and a finger readout (0.2.59)

A sensor, number, binary sensor, person or held switch opens a history card:
a line with round axis steps, clock times and the highest and lowest moment
for numbers, a timeline with the time in each state for on/off, zones and
statuses. The screen asks with esphome.screen_history for 1 hour, 24 hours or
a week; the app answers with one compact history message from the recorder's
statistics or the state changes. A finger on the graph reads the value and
time of that moment at the top while the graph stays as it is. The Guition
weather card starts below its round back button, and the README shows the new
cards and the light and weather cards as they look now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-16 23:20:51 +02:00
parent d6e5b35dcf
commit 2bbb8f4e11
27 changed files with 1857 additions and 60 deletions
+4 -2
View File
@@ -143,8 +143,10 @@ Click **Save & send to screen** to send your changes.
- Climate, vacuum, and cover: tap to open the control card.
- Long press a fan: speed, if the device supports percentages.
- Scene/script: tap to run; button/input_button: tap to press.
- Sensor: tap for the history card; set 1, 6, or 24 hours on the tile.
- Binary sensor: view status. Select/input_select: open the picker menu.
- Sensor, number, binary sensor, and person: tap for the history card, for 1 hour,
24 hours, or 1 week. Long press a switch for its history. A sensor's graph on the tile
shows 1, 6, or 24 hours.
- Select/input_select: open the picker menu.
From firmware 0.2.7, twenty tiles fit across up to four pages. Older
firmware keeps the limit of ten until you update. Under **General settings
+37
View File
@@ -176,6 +176,43 @@ icons sit off-center in the browser); run `generate_packages.py` afterward.
The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual
profiles must come from the set. No changed preferences or keys.
### Compatibility 0.2.59 / firmware 0.2.51
App, both board profiles and `components/smart_display`. Storage, tile protocol, preferences and keys are
unchanged. One new event from the screen and one new message to it; each side ignores what it does not know.
- The history card replaces the sensor card (24 bars from the tile's own graph samples) and the switch card (a large
toggle) for `sensor`, `binary_sensor`, `switch`, `input_boolean`, `person`, `number` and `input_number`
(`history_card` in runtime_tiles.h; `render_history_detail`). Opening it, or choosing a range, fires the event
`esphome.screen_history` with `inbox`, `entity` and `hours` (1, 24 or 168) through `send_homeassistant_action` with
`is_event`, like `esphome.screen_setting`: no permission to call actions is needed. The card asks again after 30 s
without an answer and says "No history available" after 8 s, so it stays usable against an app from before 0.2.59.
- The app answers with one `{"v":1,"op":"history"}` message through the screen's `screen_message` action
(`Manager.card_history_loop` → `answer_history`, only for an entity on that screen's layout and a screen that
takes whole messages). Numbers (`kind: "line"`): `values` (24 time-weighted averages, null without data), `dom`,
`yt` (axis values and their text, from `history_card.axis`), `dec`, `unit`, `hi` and `lo` ([value, unix time]).
States (`kind: "timeline"`): `slots` (96), `states` ([words, colour, seconds], at most six and "Other"), `seg`
([slot, legend index or -1, begin, end, seconds], the real times of the run's state in seconds after `start`),
`words` ([raw state, words] for the heading), `began` and `active`. Both carry `start`, `end`, `off` (the UTC offset
at `end`) and `xt` (round times for the time axis). The largest possible timeline is about 3.6 KB.
- Data: a day or a week from `recorder/statistics_during_period` (hourly mean, min and max, or state for a total),
starting an hour before the range; an hour, an entity without statistics and every timeline from
`/api/history/period` with `minimal_response` and `no_attributes` (the call the tile graphs already use, capped at
2 MB). Answers are cached per entity and range for 30 s, 2 min or 10 min, and screens asking at once share one
fetch. A fetch that fails sends and keeps nothing.
- The screen keeps only the answer it waits for (`history_asked_entity`, `history_asked_hours`), draws the line
as a monotone cubic through the averages and through the highest and lowest moment (`history_view::monotone`,
`through`), and the timeline as rectangles in one draw event. The value now joins the highest or lowest moment
when it lies beyond the history. A transparent area with `LV_OBJ_FLAG_PRESS_LOCK` reads PRESSED and PRESSING
and only changes the heading's labels; nothing on the graph moves. Range keys are detail commands 160-162 left
out of `detail_actions`, so a command waiting on Home Assistant does not disable them.
- `runtime_tiles::small_font` (axis labels and legend) is set to `sublabel` in `on_boot` of both profiles. The
tile option `history_hours` still sets the tile's graph; a card opens on 1 hour when it is 1, else on 24 hours.
- Host redraw of the line card is about 70 ms, like the cover and weather cards (fill about 6 ms, line about 12 ms).
- `render_weather_detail` starts at y 84 on the Guition (was 62, from before the round back button of 0.2.43, which
reaches y 76). The CYD keeps 38: its days card has no room to spare, and its back button overlaps the card by 10 px
as before.
### Compatibility 0.2.58 / firmware 0.2.50
App, both board profiles and `components/smart_display`. Storage, tile protocol, preferences and keys are
+90
View File
@@ -0,0 +1,90 @@
# Test results app 0.2.59 / firmware 0.2.51 (2026-09-16)
Max found the history card unusable ("no y axis, no real x axis") and chose option A of two designs: a line with
axes for numbers, with the touch readout of option B (a finger on the graph shows the value and time of that
moment). History for on/off, people and numbers too, fetched when the card opens, for 1 hour, 24 hours or a week
with the same number of points. During the work he asked for three changes, all in this release: the graph stays
as it is under a finger (only the words at the top change), the rings of the highest and lowest moment sit on the
line's peak and valley, and a sensor that only exists for two hours shows no data before that. See CHANGELOG
0.2.59 and the compatibility note in docs/RELEASING.md.
## Automated
- Python (`.venv-portal`): 282 tests OK. New `tests/test_history_card.py` (28): which entities get a line or a
timeline; time-weighted averages with gaps; statistics rows (milliseconds, gaps, min and max); the highest and
lowest moment; axes in round steps for °C, °F, %, W, kWh, negative values, lux, m³/h, ppm, V and a flat line;
decimals from the display precision; time ticks every quarter hour, six hours or midnight, across a daylight
saving change; a door's timeline with Home Assistant's words, runs with the real times of their state (an
opening of 5 minutes in a 15-minute slot reads 01:00-01:05, 300 s), people with zones and their own colours,
statuses beyond six states joining "Other"; a sensor two hours old (averages only for its own hours, also from
statistics with one compiled hour, and a sensor with one state) and a binary sensor two hours old (no data before
it); a motion sensor that changes every 30 s for a week (20,160 changes: 13 ms, 354 bytes); the largest possible
timeline (96 runs, 32-character states) at 3.6 KB of the 4096 a screen takes. The manager: a number and a door
each get one message through the screen's action, a day from hourly statistics and an hour from the changes;
screens asking at once share one fetch and a reopened card uses the cache; a failed fetch sends and keeps nothing
and the next question is answered; entities that are not on the screen, other inboxes, other ranges, a light and
an offline screen get nothing; the event loop survives a failure. Firmware source checks: the event, the answer
it waits for, the entities with a history card, range keys kept out of the disabled keys, the press lock, and
`small_font` in both profiles and packages.
- C++ (`clang++ -std=c++17 -Wall -Wextra -Werror -I. -I components/smart_display`): 15/15 PASS. New
`test_history_view`: clock times in 24 and 12 hours with weekdays and negative offsets, axis times ("4 PM",
"4:15 PM"), durations, numbers with thousands and glued units, the part and the run under a finger, the monotone
line (a setpoint stepping 55-45-60 stays between 45 and 60, flat stays flat, every point on the line, a small
buffer is not overrun) and `through` (a moment takes its part's point, a second moment in the same part joins
beside it, a full buffer refuses).
- `generate_packages.py --check` green.
## Builds (ESPHome 2026.6.2)
Check profiles with the generated packages and local components, the same as for 0.2.58.
| Profile | RAM | Flash | vs 0.2.58 |
|---|---|---|---|
| CYD | 23.5% (77,060 B) | 77.8% (1,427,015 B) | +512 B RAM, +15,192 B flash |
| Guition | 24.5% (80,436 B) | 18.8% (1,530,331 B) | +512 B RAM, +15,068 B flash |
No warnings from `runtime_tiles.h` or `history_view.h` (the switch's part/state selector, moved from the old switch
card, now casts to `lv_style_selector_t`).
## Firmware on the Mac (host builds, driven over the ESPHome API)
A demo home with a week of history (door, coffee machine, a person with zones, a washing machine status, a boiler
setpoint, and a plug added two hours ago) answers with the real `Manager.card_history`; a virtual LVGL pointer
drives the card. Guition 40/40, CYD 40/40:
- Opening a card fires one `esphome.screen_history` event (inbox, entity, 24); the card shows the value now and
"Loading history..." until the answer, then the line with High and Low and their times and the axis labels the
message carries.
- A finger reads the average of the hour under it with its time range and "average of 1 h" in the accent colour;
dragging moves the readout, past the edge it holds the last hour, lifting restores the header. The objects of the
graph (count, positions, visibility) are identical before and under the finger, on the line and the timeline.
- "1 week" asks for 168 hours and waits with the week selected; a late answer for the day is ignored; the week's
High and Low name the weekday and a week part reads as a weekday time range averaged over 7 h. "1 hour" asks for
1 hour and a part reads "average"; tapping the range shown asks nothing; a new state updates the header without a
new question; reopening starts on 24 hours and asks again.
- The door: the heading follows the state ("Off" before the answer, "Closed" with it, "Open" when the door opens
while the card is open), "Open · 22 min" and "6 times"; a finger on the opening of 12:30 reads "12:30 – 12:35"
and "5 min", the unavailable stretch "14:00 – 14:20" and "20 min".
- A person with zones (home, "once"), the week; a washing machine status without counts; the coffee machine with its
toggle, which sends `switch.turn_off`; the boiler setpoint with its slider.
- The plug added two hours ago: its line only over its last three parts, High 1,850 W and Low; a finger before it
existed reads "No data" with the time, on its last hour its average.
- Without an answer "No history available" after 8 s, and a late answer is still drawn; an empty hour says
"No history in this period"; malformed history messages change nothing; no errors in the log.
- Full redraws on the host: the line card about 70 ms like the cover, weather and vacuum cards (the fill about
6 ms, the line about 12 ms). The host's LVGL loop stalls for 1-2 s at random, on any card; the checks wait for
the state they expect instead of fixed pauses.
- Renders of every card on both boards; the README shows the new history cards. All README screens were rendered
again from the standard demo home: the pages and the alert are unchanged, the light card and both weather cards
changed since 0.2.41 and were replaced. The Guition weather card's round back button lay half under the card
(since 0.2.43); it now starts below the button, checked in a new render. On the CYD moving it squeezed the days
too much, so the CYD keeps its layout. A playing media player's volume slider is grey on the tile since 0.2.43
(Home Assistant colours it); left for a separate change.
## Not tested / for the owner
- A live read-only check against Home Assistant could not run: both local tokens were refused. The card uses the
same two calls the tile graphs already use (`recorder/statistics_during_period`, now with min and max, and
`/api/history/period`).
- Real hardware: on Studio 1 after the update, open a temperature, a door or a person, switch between 1 hour,
24 hours and 1 week, and slide a finger over the graph. Free heap on a CYD with a week's timeline open.
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB