/* * The Jinja template POSTed to Home Assistant's /api/template. * * Why a template rather than plain REST calls: * * - One request instead of a dozen. /api/states returns all ~204 entities * (far too much to buffer); fetching entities one at a time would mean a * dozen round-trips inside the window where the UI is frozen. This is a * single ~430 byte reply, measured at ~85ms on the LAN. * - HA formats the times. sunrise/sunset/clock/date come back as local-time * strings, so the device needs neither SNTP nor a timezone database, and * DST is somebody else's problem. The board's clock starts at the epoch * and that no longer matters. * - Unavailable sensors arrive as JSON null, not a sentinel number. A * sentinel would be indistinguishable from a real reading -- -1 mph of * wind is impossible, but -1 degF of dew point is not. * * The reply is compact JSON; see ha_parse_json(). */ #ifndef HA_TEMPLATE_H #define HA_TEMPLATE_H #include "ha_config.h" /* Emits the entity list for the lights loop, in HA_LIGHT_LIST order. The * trailing comma is deliberate -- Jinja accepts it, and it avoids a * last-element special case. The icon argument is the UI layer's. */ #define HA_TMPL_LIGHT_X(id, label, dimmable, icon) "'" id "'," /* * num() -- a state as a number, or null if it is not one ("unavailable", * "unknown", or a non-numeric state). * attrnum() -- ditto for an attribute; brightness is None when a light is off. */ #define HA_STATE_TEMPLATE \ "{%- macro num(e) -%}{%- set v = states(e) -%}" \ "{{ v | float if is_number(v) else 'null' }}{%- endmacro -%}" \ "{%- macro attrnum(e, a) -%}{%- set v = state_attr(e, a) -%}" \ "{{ v if v is number else 'null' }}{%- endmacro -%}" \ "{" \ "\"out_t\":" "{{ num('" HA_ENT_OUTDOOR_TEMP "') }}," \ "\"out_h\":" "{{ num('" HA_ENT_OUTDOOR_HUMIDITY "') }}," \ "\"dn_t\":" "{{ num('" HA_ENT_INDOOR_DOWN_TEMP "') }}," \ "\"dn_h\":" "{{ num('" HA_ENT_INDOOR_DOWN_HUMIDITY "') }}," \ "\"up_t\":" "{{ num('" HA_ENT_INDOOR_UP_TEMP "') }}," \ "\"up_h\":" "{{ num('" HA_ENT_INDOOR_UP_HUMIDITY "') }}," \ "\"dew\":" "{{ num('" HA_ENT_DEW_POINT "') }}," \ "\"press\":" "{{ num('" HA_ENT_PRESSURE "') }}," \ "\"rain_d\":" "{{ num('" HA_ENT_RAIN_TODAY "') }}," \ "\"rain_r\":" "{{ num('" HA_ENT_RAIN_RATE "') }}," \ "\"wind\":" "{{ num('" HA_ENT_WIND_SPEED "') }}," \ "\"gust\":" "{{ num('" HA_ENT_WIND_GUST "') }}," \ "\"wdir\":" "{{ num('" HA_ENT_WIND_DIR "') }}," \ "\"cond\":\"" "{{ states('" HA_ENT_WEATHER "') }}\"," \ "\"sunrise\":\"{{ (state_attr('" HA_ENT_SUN "','next_rising')" \ " | as_datetime | as_local).strftime('%-I:%M %p') }}\"," \ "\"sunset\":\"{{ (state_attr('" HA_ENT_SUN "','next_setting')" \ " | as_datetime | as_local).strftime('%-I:%M %p') }}\"," \ "\"sun_up\":{{ (states('" HA_ENT_SUN "') == 'above_horizon') | lower }}," \ "\"clock\":\"{{ now().strftime('%-I:%M %p') }}\"," \ "\"date\":\"{{ now().strftime('%a %b %-d') }}\"," \ "\"lights\":[" \ "{%- for e in [" HA_LIGHT_LIST(HA_TMPL_LIGHT_X) "] -%}" \ "{\"s\":\"{{ states(e) }}\",\"b\":{{ attrnum(e,'brightness') }}}" \ "{{ \",\" if not loop.last }}" \ "{%- endfor -%}" \ "]}" /* * Forecast body for POST /api/services/weather/get_forecasts?return_response. * * The forecast is deliberately NOT part of the template above: HA removed the * `forecast` attribute from weather entities in 2024.4, and Jinja templates * cannot call services. It also changes far more slowly than the sensors, so * it is polled on its own longer interval. */ #define HA_FORECAST_BODY \ "{\"entity_id\":\"" HA_ENT_WEATHER "\",\"type\":\"daily\"}" #endif