Hi,
I am trying (with some success) to show calendar entries on an ESP ePaper.
The 80% seem to work, but now I am fiddeling with the little details. I am pretty sure I did not fully understand when and how Data ist transferred from HomeAssistant to ESPhome, but at the moment I want to tackle another problem.
The calendar is a regular M365 calendar which I synch to HA to a local calendar with the beautiful work of
My idea now is to have a room occupied ePaper, that uses this calendar (which is in M365 connected to the room).
After struggling around with ePaper modules, finally found reTerminal E1002 | E Ink Spectra 6 Full Color ESP32-S3 ePaper Display with 3-Month Battery Life which is supported by seeed studio. So I am using this. Again: Not related to seeed, I just wanted to share my working knowledge, after I had a long journey with homemade ePapers and waveshare, LilyGo touch ePapers (did not get it to work). Seeed is “ready to use hardware”.
But to come to my question:
The calendar has the following entries: (as of today, which is daily-mo-2026-01-13)
data:
- summary: Test 05 overlap
start: "2026-01-13T07:24:00+00:00"
end: "2026-01-13T07:49:00+00:00"
all_day: false
description: ""
location: ""
categories: []
sensitivity: Normal
show_as: Busy
reminder:
minutes: 15
is_on: false
attendees: []
uid: >-
AAMkADcyMDUxMjUxLTExOTktNGFiNy04YzBmLTM1NGQ4ZGI0NWQ2NABGAAAAAADOGox31YQzRqQvV8NGperwBwA1yIXRoo1ZQprE0-z0jJpXAAAE6yeLAAA1yIXRoo1ZQprE0-z0jJpXAADouycXAAA=
- summary: Test 04
start: "2026-01-13T07:34:00+00:00"
end: "2026-01-13T07:44:00+00:00"
all_day: false
description: ""
location: ""
categories: []
sensitivity: Normal
show_as: Busy
reminder:
minutes: 15
is_on: false
attendees: []
uid: >-
AAMkADcyMDUxMjUxLTExOTktNGFiNy04YzBmLTM1NGQ4ZGI0NWQ2NABGAAAAAADOGox31YQzRqQvV8NGperwBwA1yIXRoo1ZQprE0-z0jJpXAAAE6yeLAAA1yIXRoo1ZQprE0-z0jJpXAADouycVAAA=
- summary: Test 05
start: "2026-01-13T07:50:00+00:00"
end: "2026-01-13T07:55:00+00:00"
all_day: false
description: ""
location: ""
categories: []
sensitivity: Normal
show_as: Busy
reminder:
minutes: 15
is_on: false
attendees: []
uid: >-
AAMkADcyMDUxMjUxLTExOTktNGFiNy04YzBmLTM1NGQ4ZGI0NWQ2NABGAAAAAADOGox31YQzRqQvV8NGperwBwA1yIXRoo1ZQprE0-z0jJpXAAAE6yeLAAA1yIXRoo1ZQprE0-z0jJpXAADouycWAAA=
- summary: KA testeintrag
start: "2026-01-13T12:30:00+00:00"
end: "2026-01-13T13:00:00+00:00"
all_day: false
description: ""
location: ""
categories: []
sensitivity: Normal
show_as: Busy
reminder:
minutes: 15
is_on: false
attendees: []
uid: >-
AAMkADcyMDUxMjUxLTExOTktNGFiNy04YzBmLTM1NGQ4ZGI0NWQ2NAFRAAgI3lI2se0AAEYAAAAAzhqMd9WEM0akL1fDRqXq8AcANciF0aKNWUKaxNP89IyaVwAABOsniwAANciF0aKNWUKaxNP89IyaVwAABOurTQAAEA==
sync_state: ok
color: auto
friendly_name: KA Testraum
supported_features: 7
message: Test 05 overlap
all_day: false
start_time: "2026-01-13 08:24:00"
end_time: "2026-01-13 08:49:00"
location: ""
description: ""
offset_reached: false
For each calendar I now want to have the “usable” data from above “ready made” for the ESP, as I want to keep the ESP as stupid as possible to save WIFI data transfer as well. So I have
input_text.rmgr_ka_testraum_today_valid
which should contain the data, that I use within the ePaper.
With the help of chatgpt (sorry for that, but I do want to be honest)
I have this automation working
alias: RMGR KA Testraum - valid entries today (merged) and sleeptime
description: ""
triggers:
- minutes: /1
trigger: time_pattern
enabled: false
- entity_id: calendar.rmgr_ka_testraum
trigger: state
- trigger: state
entity_id:
- binary_sensor.rmgrseeed01_connected
to:
- "on"
actions:
- variables:
merged_text: >-
{%- set cal = 'calendar.rmgr_ka_testraum' -%} {%- set events =
state_attr(cal, 'data') -%} {%- if events is none -%}{%- set events = []
-%}{%- endif -%}
{%- set now_local = now() -%} {%- set ns = namespace(items=[]) -%}
{# Collect today's events that have not ended yet #} {%- for e in events -%}
{%- if e is not none and e['start'] is defined and e['end'] is defined -%}
{%- set s = as_local(as_datetime(e['start'])) -%}
{%- set en = as_local(as_datetime(e['end'])) -%}
{%- if en > now_local and s.date() == now_local.date() -%}
{%- set title = (e['summary'] if e['summary'] is defined else '—') -%}
{%- set ns.items = ns.items + [ {'s': s, 'e': en, 'title': title} ] -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- set items = ns.items | sort(attribute='s') -%}
{# Merge overlaps (no append, only list concatenation) #} {%- set m =
namespace(out=[], cur_s=None, cur_e=None, titles=[]) -%}
{%- for it in items -%}
{%- if m.cur_s is none -%}
{%- set m.cur_s = it.s -%}
{%- set m.cur_e = it.e -%}
{%- set m.titles = [it.title] -%}
{%- else -%}
{%- if it.s <= m.cur_e -%}
{%- if it.e > m.cur_e -%}{%- set m.cur_e = it.e -%}{%- endif -%}
{%- set m.titles = m.titles + [it.title] -%}
{%- else -%}
{%- set m.out = m.out + [ {'s': m.cur_s, 'e': m.cur_e, 'titles': m.titles} ] -%}
{%- set m.cur_s = it.s -%}
{%- set m.cur_e = it.e -%}
{%- set m.titles = [it.title] -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- if m.cur_s is not none -%}
{%- set m.out = m.out + [ {'s': m.cur_s, 'e': m.cur_e, 'titles': m.titles} ] -%}
{%- endif -%}
{# Format output #} {%- if m.out | length == 0 -%}
-
{%- else -%}
{%- for b in m.out -%}
{{ b.s.strftime('%H:%M') }} - {{ b.e.strftime('%H:%M') }} {{ b.titles | join(', ') }}
{%- if not loop.last %}\n{% endif -%}
{%- endfor -%}
{%- endif -%}
- data:
level: warning
message: RMGR merged_text='{{ merged_text }}' len={{ merged_text | length }}
action: system_log.write
- target:
entity_id: input_text.rmgr_ka_testraum_today_valid
data:
value: "{{ merged_text[:100] }}"
action: input_text.set_value
- target:
entity_id: input_number.rmgrseeed01_sleep_minutes
data:
value: "{{ range(10, 31) | random }}"
action: input_number.set_value
mode: single
that generates
08:24 - 08:49 Test 05 overlap, Test 04\n08:50 - 08:55 Test 05\n13:30 - 14:00 KA testeintrag
which only is ASCII with start-end subject and takes overlapping dates into account.
input_text.rmgr_ka_testraum_today_valid
as a state, which I somehow to like, as I than can use state switches in HA to do “fancy stuff”. The attributes of the sensor are
editable: true
min: 0
max: 100
pattern: null
mode: text
icon: mdi:calendar-account
friendly_name: rmgr_ka_testraum_today_valid
I now personally do not like the “\n” which is not a linefeed at all, but is shown as two chars. I would love to have a real linefeed but somehow come to the conclusion by reading Force newlines or linebreaks in a template? that this is not possible. So finally my question:
a.) is a text_sensor a good idea, and using the state as the payload for the ESP as well?
b.) If the answer to a.) is “yes”, I would rather use another character, not to confuse my future self what the linebreak is not working. What is recommended for a separator not to be biten tooo much, if people play with fancy seperators within the subject in the calendar, by using the seperator. (Again: I do want to keep the ESP as stupid as possible, and do not write a parser, that always works.) As the ESP is used internally, it does not have to be bulletproof, but 99% would be nice. I am thinking of “|”, but maybe there are better solutions.
Finally: Don’t be confused by
- target:
entity_id: input_number.rmgrseeed01_sleep_minutes
data:
value: "{{ range(10, 31) | random }}"
action: input_number.set_value
too much. This is my try to set the sleep duration at the ESP from within HA, as I tought it might be an idea to let the ESP sleep as long as possible and only update on changes at the calendar. The randomness works, I later wanted to calculate the sleeptime of the ESP depending on the time, the ESP wakes up now() and the entries of the calendars.
ESP does only need to show entries of “today”
thank you for reading till here. And even more thanks for a hint what to do.
Juergen
P.S.: I am planning to make this available for all, but at the moment it is IMHO too much brute force in some cases. (as I want to have “N” ePapers showing “M” calendars, I have my own bash script that generates the automations ny sed-ing a template and copies them to packages. Which works, but does not feel right as well.
See here the ePaper:
