79 lines
4.8 KiB
C
79 lines
4.8 KiB
C
/*
|
|
* 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. */
|
|
#define HA_TMPL_LIGHT_X(id, label, dimmable) "'" 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 "') }}," \
|
|
"\"in_t\":" "{{ num('" HA_ENT_INDOOR_TEMP "') }}," \
|
|
"\"in_h\":" "{{ num('" HA_ENT_INDOOR_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
|