2026-10-18 20:20:13 +02:00
|
|
|
# Helper sensors for the EL512.256-H ESPHome display (esphome/el512-panel.yaml).
|
|
|
|
|
#
|
|
|
|
|
# Install: put this file in /config/packages/ and make sure configuration.yaml has
|
|
|
|
|
#
|
|
|
|
|
# homeassistant:
|
|
|
|
|
# packages: !include_dir_named packages
|
|
|
|
|
#
|
|
|
|
|
# then restart Home Assistant. All lists use the same compact text format so the
|
|
|
|
|
# ESP32 can split them cheaply: fields separated by "|", entries by ";".
|
|
|
|
|
|
|
|
|
|
template:
|
|
|
|
|
# Forecast and agenda come from actions, so they refresh on a timer.
|
|
|
|
|
- triggers:
|
|
|
|
|
- trigger: time_pattern
|
|
|
|
|
minutes: "/15"
|
|
|
|
|
- trigger: homeassistant
|
|
|
|
|
event: start
|
|
|
|
|
- trigger: event
|
|
|
|
|
event_type: event_template_reloaded
|
|
|
|
|
actions:
|
|
|
|
|
- action: weather.get_forecasts
|
|
|
|
|
target:
|
|
|
|
|
entity_id: weather.knmi_home
|
|
|
|
|
data:
|
|
|
|
|
type: daily
|
|
|
|
|
response_variable: fc
|
|
|
|
|
- action: calendar.get_events
|
|
|
|
|
target:
|
|
|
|
|
entity_id:
|
|
|
|
|
- calendar.mangavalk_gmail_com
|
|
|
|
|
- calendar.li_s_happy_home
|
|
|
|
|
- calendar.feestdagen_in_nederland
|
|
|
|
|
data:
|
|
|
|
|
duration:
|
|
|
|
|
days: 14
|
|
|
|
|
response_variable: cal
|
|
|
|
|
sensor:
|
|
|
|
|
# days: "Sun|cloudy|12|11|0;Mon|partlycloudy|14|10|20;..."
|
|
|
|
|
# weekday|condition|high|low|rain chance %
|
|
|
|
|
- name: EL512 forecast
|
|
|
|
|
unique_id: el512_forecast
|
|
|
|
|
icon: mdi:weather-partly-cloudy
|
|
|
|
|
state: "{{ fc['weather.knmi_home'].forecast | count }}"
|
|
|
|
|
attributes:
|
|
|
|
|
days: >-
|
|
|
|
|
{%- set ns = namespace(out=[]) -%}
|
|
|
|
|
{%- for d in fc['weather.knmi_home'].forecast[:7] -%}
|
|
|
|
|
{%- set ns.out = ns.out + [
|
|
|
|
|
(as_datetime(d.datetime) | as_local).strftime('%a') ~ '|' ~
|
|
|
|
|
d.condition ~ '|' ~
|
|
|
|
|
(d.temperature | round(0) | int) ~ '|' ~
|
|
|
|
|
(d.templow | default(d.temperature) | round(0) | int) ~ '|' ~
|
|
|
|
|
(d.precipitation_probability | default(0) | int)] -%}
|
|
|
|
|
{%- endfor -%}
|
|
|
|
|
{{ ns.out | join(';') }}
|
|
|
|
|
|
|
|
|
|
# events: "Mon 19|11:00|Work;Tue 20||Holiday;..."
|
|
|
|
|
# day|start time (empty = all day)|title
|
|
|
|
|
- name: EL512 agenda
|
|
|
|
|
unique_id: el512_agenda
|
|
|
|
|
icon: mdi:calendar
|
|
|
|
|
state: >-
|
|
|
|
|
{%- set ns = namespace(n=0) -%}
|
|
|
|
|
{%- for c in cal.values() -%}{%- set ns.n = ns.n + c.events | count -%}{%- endfor -%}
|
|
|
|
|
{{ ns.n }}
|
|
|
|
|
attributes:
|
|
|
|
|
events: >-
|
|
|
|
|
{%- set ns = namespace(ev=[]) -%}
|
|
|
|
|
{%- for c in cal.values() -%}
|
|
|
|
|
{%- for e in c.events -%}{%- set ns.ev = ns.ev + [e] -%}{%- endfor -%}
|
|
|
|
|
{%- endfor -%}
|
|
|
|
|
{%- set out = namespace(items=[]) -%}
|
|
|
|
|
{%- for e in (ns.ev | sort(attribute='start'))[:10] -%}
|
|
|
|
|
{%- if e.start | length == 10 -%}
|
|
|
|
|
{%- set day = strptime(e.start, '%Y-%m-%d').strftime('%a %d') -%}
|
|
|
|
|
{%- set time = '' -%}
|
|
|
|
|
{%- else -%}
|
|
|
|
|
{%- set t = as_datetime(e.start) | as_local -%}
|
|
|
|
|
{%- set day = t.strftime('%a %d') -%}
|
|
|
|
|
{%- set time = t.strftime('%H:%M') -%}
|
|
|
|
|
{%- endif -%}
|
|
|
|
|
{%- set title = (e.summary | default('')) | replace('|', '/') | replace(';', ',') -%}
|
|
|
|
|
{%- set out.items = out.items + [day ~ '|' ~ time ~ '|' ~ title[:40]] -%}
|
|
|
|
|
{%- endfor -%}
|
|
|
|
|
{{ out.items | join(';') }}
|
|
|
|
|
|
|
|
|
|
# Whatever media player is playing right now; position advances every 5 s.
|
|
|
|
|
- triggers:
|
|
|
|
|
- trigger: time_pattern
|
|
|
|
|
seconds: "/5"
|
|
|
|
|
sensor:
|
|
|
|
|
- name: EL512 now playing
|
|
|
|
|
unique_id: el512_now_playing
|
|
|
|
|
icon: mdi:play-circle
|
|
|
|
|
state: >-
|
|
|
|
|
{%- set p = states.media_player | selectattr('state', 'eq', 'playing') | list -%}
|
|
|
|
|
{{ p[0].entity_id if p else 'idle' }}
|
|
|
|
|
attributes:
|
|
|
|
|
# "title|artist|album|player|position s|duration s"
|
|
|
|
|
info: >-
|
|
|
|
|
{%- set p = states.media_player | selectattr('state', 'eq', 'playing') | list -%}
|
|
|
|
|
{%- if p -%}
|
|
|
|
|
{%- set a = p[0].attributes -%}
|
|
|
|
|
{%- set pos = a.media_position | default(0) -%}
|
|
|
|
|
{%- if a.media_position_updated_at is defined and a.media_position_updated_at -%}
|
|
|
|
|
{%- set pos = pos + (now() - as_datetime(a.media_position_updated_at)).total_seconds() -%}
|
|
|
|
|
{%- endif -%}
|
|
|
|
|
{%- set dur = a.media_duration | default(0) -%}
|
|
|
|
|
{%- set pos = [pos, dur] | min if dur else pos -%}
|
|
|
|
|
{{ [a.media_title | default(a.app_name | default('')), a.media_artist | default(a.media_series_title | default('')),
|
|
|
|
|
a.media_album_name | default(''), a.friendly_name | default(''), pos | int, dur | int]
|
|
|
|
|
| map('string') | map('replace', '|', '/') | join('|') }}
|
|
|
|
|
{%- endif -%}
|
|
|
|
|
|
2026-10-19 00:03:44 +02:00
|
|
|
# Plex streams with play/pause state and position, from the Plex integration's
|
|
|
|
|
# media players (media_player.plex_*); position advances every 5 s. Without
|
|
|
|
|
# any Plex players, falls back to the server sensor, which has one attribute
|
|
|
|
|
# per stream ("user - client": "title") but no state or position.
|
|
|
|
|
- triggers:
|
|
|
|
|
- trigger: time_pattern
|
|
|
|
|
seconds: "/5"
|
|
|
|
|
sensor:
|
2026-10-18 20:20:13 +02:00
|
|
|
- name: EL512 Plex streams
|
|
|
|
|
unique_id: el512_plex_streams
|
|
|
|
|
icon: mdi:plex
|
|
|
|
|
state: "{{ states('sensor.mangavalkserver') }}"
|
|
|
|
|
attributes:
|
2026-10-19 00:03:44 +02:00
|
|
|
# "who|title|playing or paused|position s|duration s;..."
|
|
|
|
|
# (fallback: "user - client|title;...")
|
2026-10-18 20:20:13 +02:00
|
|
|
streams: >-
|
|
|
|
|
{%- set ns = namespace(out=[]) -%}
|
2026-10-19 00:03:44 +02:00
|
|
|
{%- for p in states.media_player
|
|
|
|
|
if p.entity_id.startswith('media_player.plex_') and p.state in ['playing', 'paused'] -%}
|
|
|
|
|
{%- set a = p.attributes -%}
|
|
|
|
|
{%- set pos = a.media_position | default(0) -%}
|
|
|
|
|
{%- if p.state == 'playing' and a.media_position_updated_at is defined and a.media_position_updated_at -%}
|
|
|
|
|
{%- set pos = pos + (now() - as_datetime(a.media_position_updated_at)).total_seconds() -%}
|
|
|
|
|
{%- endif -%}
|
|
|
|
|
{%- set dur = a.media_duration | default(0) -%}
|
|
|
|
|
{%- set pos = [pos, dur] | min if dur else pos -%}
|
|
|
|
|
{%- set title = a.media_title | default('') -%}
|
|
|
|
|
{%- if a.media_series_title | default('') -%}
|
|
|
|
|
{%- set title = a.media_series_title ~ ' - ' ~ title -%}
|
|
|
|
|
{%- endif -%}
|
|
|
|
|
{%- set who = a.username | default(a.friendly_name | default(p.entity_id)) -%}
|
|
|
|
|
{%- set ns.out = ns.out + [[who, title, p.state, pos | int, dur | int]
|
|
|
|
|
| map('string') | map('replace', '|', '/') | map('replace', ';', ',')
|
|
|
|
|
| join('|')] -%}
|
|
|
|
|
{%- endfor -%}
|
|
|
|
|
{%- set s = states.sensor.mangavalkserver -%}
|
|
|
|
|
{%- if not ns.out and s -%}
|
2026-10-18 20:20:13 +02:00
|
|
|
{%- for k, v in s.attributes.items()
|
|
|
|
|
if k not in ['unit_of_measurement', 'friendly_name', 'icon', 'device_class', 'state_class'] -%}
|
|
|
|
|
{%- set ns.out = ns.out + [(k | replace('|', '/') | replace(';', ',')) ~ '|' ~
|
|
|
|
|
(v | string | replace('|', '/') | replace(';', ','))] -%}
|
|
|
|
|
{%- endfor -%}
|
|
|
|
|
{%- endif -%}
|
|
|
|
|
{{ ns.out | join(';') }}
|