# 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 -%} # 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: - name: EL512 Plex streams unique_id: el512_plex_streams icon: mdi:plex state: "{{ states('sensor.mangavalkserver') }}" attributes: # "who|title|playing or paused|position s|duration s;..." # (fallback: "user - client|title;...") streams: >- {%- set ns = namespace(out=[]) -%} {%- 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 -%} {%- 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(';') }}