Downstairs and upstairs are now separate readings. The station names them
backwards -- "temperature_4" is downstairs and "inside_temperature" is
upstairs -- so ha_config.h says which is which. Downstairs is the primary:
big number on the card, and it is what the furnace controls on, because it
is where people are and heat rises. Upstairs sits beside it, obviously
subordinate by size, and costs no vertical space in a layout that has none.
The anemometer came back on 2026-08-07, so wind gets its own column: a dial
whose needle points at the direction the wind is coming FROM (weather-vane
convention, matching the cardinal below it), the speed, and a 5-minute trend
arrow.
- The needle does not animate. A spin would look better, but every animated
frame is a redraw, and redrawing is what starves the bounce refill ISR.
- Below 0.5 mph it shows "Calm" and draws no needle. A vane that is not
being pushed reports whatever it last settled on, which on this station
is a steady 0 degrees -- a confident needle pointing due north at zero
wind. The live snapshot caught exactly that.
- The trend is local, not HA's 10-minute average, so the window is ours to
pick: 1-minute buckets over 5 minutes, threshold 1.5 mph.
That last point needed the trend engine to stop assuming one hour. Window,
bucket size, minimum age and threshold are now per-channel in a table, which
also removes the threshold arguments main.c was passing in.
Grid drops to 117px cells to make room, so the sun sub-line is just which
event it is and "RAIN TODAY" is "RAIN" -- both were overrunning.
On the artefacts: bounce mode is NOT a DMA read of the framebuffer. The
refill is a CPU memcpy in an ISR, which is why the driver skips the usual
write-back in draw_bitmap ("the data gets read by the CPU as well"). So the
panel streams 750KB per frame at ~44Hz through the DCache, evicting the
working set of everything else on the chip, continuously -- which is the real
reason this board is so sensitive to WiFi and to any extra rendering.
bb_invalidate_cache frees each chunk after it is copied. The header warns it
"can be dangerous if data is written from other core(s)"; that does not apply
here, because it invalidates the buffer being scanned and by design LVGL only
draws into the other one. Whether it is enough is for the panel to say.
Also gives tools/ha_snapshot.py a --token-file so the token no longer has to
travel through a shell command line to reach it.
Co-Authored-By: Claude <noreply@anthropic.com>
103 lines
5.3 KiB
C
103 lines
5.3 KiB
C
/*
|
|
* Home Assistant entity IDs for this installation.
|
|
*
|
|
* Entity IDs are not secrets, so unlike the token in app_config.h they are
|
|
* tracked. They are also not stable: renaming a device or reinstalling an
|
|
* integration changes them, and a wrong ID fails silently as "unavailable"
|
|
* rather than as an error. Re-check with tools/ha_discover.py after touching
|
|
* anything in HA.
|
|
*/
|
|
#ifndef HA_CONFIG_H
|
|
#define HA_CONFIG_H
|
|
|
|
/* --- Weather station (Ambient Weather, exposed locally through HA) ------- */
|
|
|
|
#define HA_ENT_OUTDOOR_TEMP "sensor.moonlight_beach_temperature"
|
|
#define HA_ENT_OUTDOOR_HUMIDITY "sensor.moonlight_beach_humidity"
|
|
|
|
/*
|
|
* Two indoor sensors.
|
|
*
|
|
* DOWN is the console's "4" channel and UP is the one the station calls
|
|
* "inside" -- the names come from the Ambient Weather hardware, not from the
|
|
* house, so they read backwards. Downstairs is the primary: it gets the big
|
|
* card AND it is what the thermostat controls on, because it is where people
|
|
* are. Heat rises, so upstairs typically reads several degrees warmer;
|
|
* controlling on it would under-heat the living space.
|
|
*
|
|
* To swap which one drives the furnace, change HA_ENT_CONTROL_TEMP below --
|
|
* that is the only place the choice is made.
|
|
*/
|
|
#define HA_ENT_INDOOR_DOWN_TEMP "sensor.moonlight_beach_temperature_4"
|
|
#define HA_ENT_INDOOR_DOWN_HUMIDITY "sensor.moonlight_beach_humidity_4"
|
|
#define HA_ENT_INDOOR_UP_TEMP "sensor.moonlight_beach_inside_temperature"
|
|
#define HA_ENT_INDOOR_UP_HUMIDITY "sensor.moonlight_beach_humidity_indoor"
|
|
#define HA_ENT_DEW_POINT "sensor.moonlight_beach_dew_point"
|
|
#define HA_ENT_PRESSURE "sensor.moonlight_beach_relative_pressure"
|
|
#define HA_ENT_RAIN_TODAY "sensor.moonlight_beach_daily_rain"
|
|
#define HA_ENT_RAIN_RATE "sensor.moonlight_beach_precipitation_intensity"
|
|
|
|
/*
|
|
* The anemometer was offline from 2026-07-27 and came back 2026-08-07. All
|
|
* three read live again. The UI still treats them as optional -- ha_value_t
|
|
* carries its own validity and the compass hides itself when direction is
|
|
* missing -- because this sensor has now failed once and will again.
|
|
*
|
|
* The 10-minute averages are the station's own smoothing. The 5-minute trend
|
|
* shown on the compass is computed locally from the instantaneous speed
|
|
* instead, so the trend window is ours to choose; see HA_TREND_CH_WIND.
|
|
*/
|
|
#define HA_ENT_WIND_SPEED "sensor.moonlight_beach_wind_speed"
|
|
#define HA_ENT_WIND_GUST "sensor.moonlight_beach_wind_gust"
|
|
#define HA_ENT_WIND_DIR "sensor.moonlight_beach_wind_direction"
|
|
|
|
/* --- Forecast and sun ---------------------------------------------------- */
|
|
|
|
/* met.no via HA. Supplies the textual condition and the daily forecast. */
|
|
#define HA_ENT_WEATHER "weather.forecast_home"
|
|
#define HA_ENT_SUN "sun.sun"
|
|
|
|
/* --- Lights -------------------------------------------------------------- */
|
|
|
|
/*
|
|
* The X-macro is load-bearing, not decoration.
|
|
*
|
|
* The JSON template asks HA for these entities as an ordered array, and the
|
|
* firmware reads the reply back by index. Two hand-maintained lists would
|
|
* drift the first time one is reordered, and the failure mode is silent: the
|
|
* Dining Room tile would just start controlling the Blue Lamp. Generating the
|
|
* template's entity list and the C table from one source makes that
|
|
* impossible.
|
|
*
|
|
* X(entity_id, label, dimmable, icon)
|
|
*
|
|
* `dimmable` is false for the patio, which is a TP-Link smart plug in the
|
|
* switch domain -- on/off only, and it needs switch.turn_on rather than
|
|
* light.turn_on. See ha_service_domain().
|
|
*
|
|
* `icon` is an ICON_* macro from main/ui/ui_icons.h. It rides along in this
|
|
* list (rather than a parallel array in the UI) so a reorder here can never
|
|
* put the sofa icon on the patio. Only ui_manager.c expands it; the api-layer
|
|
* consumers ignore the argument, so this header does not include ui_icons.h.
|
|
*
|
|
* light.main_room used to be here and was removed deliberately: it is a
|
|
* GROUP of blue_lamp, brown_lamp and landing_chandelier -- three of the
|
|
* other tiles. One tap restyled four tiles at once, which both confused the
|
|
* optimistic-hold logic and triggered a redraw storm big enough to glitch
|
|
* the panel (see README on PSRAM bandwidth). Groups spanning other tiles do
|
|
* not belong in this list. light.tv_room is also a group (of 2), but of
|
|
* lights that have no tile of their own, which is fine.
|
|
*/
|
|
/* Labels must fit one line of Montserrat 16 in a ~125px tile -- roughly 11
|
|
* characters. Longer ones are truncated with an ellipsis, which reads as a
|
|
* mistake. "Dining Room" is one character too wide, hence "Dining". */
|
|
#define HA_LIGHT_LIST(X) \
|
|
X("light.tv_room", "TV Room", true, ICON_TV) \
|
|
X("light.dining_room", "Dining", true, ICON_DINING) \
|
|
X("light.blue_lamp", "Blue Lamp", true, ICON_FLOOR_LAMP) \
|
|
X("light.brown_lamp", "Brown Lamp", true, ICON_DESK_LAMP) \
|
|
X("light.landing_chandelier", "Chandelier", true, ICON_CHANDELIER) \
|
|
X("switch.tp_link_smart_plug_f048_outdoor_hanging_lights", "Patio", false, ICON_STRING_LIGHTS)
|
|
|
|
#endif
|