Automations

Each member has a sensor whose state is the subject they are in right now, or free between and after lessons. It changes at the lesson boundaries and nowhere else, because that is when the timetable says it changes — there is no polling.

sensor.schoolday_ben        →  Sport
Attribute Contents
member The child’s name as typed, for an announcement about several children at once
member_id The id Schoolday knows this child by. It is how the cards find this sensor, so it is also how a template can
color / avatar What this child is drawn in and with, for anything rendering them alongside the cards
today Today’s lessons: subject, room, period, start, end
today_subjects Today’s subjects, de-duplicated: ["Deutsch", "Mathe", "Sport"]
today_summary The same, ready to speak: "Deutsch, Mathe, Sport"
tomorrow_subjects / tomorrow_summary The same for tomorrow — empty when tomorrow is a weekend, a holiday, a care day, a trip or a sick day
exams Exams from today to the end of the outlook, from this child’s exams calendar: date, title, start and period — start and period are null for an all-day one
tomorrow_exams Tomorrow’s exam titles: ["Mathe"] — empty when tomorrow is not a school day, like tomorrow_subjects
school_start / school_end When today’s first lesson begins and the last one ends, "08:00" / "13:10", after what the school system cancelled — null on a day without school
tomorrow_start / tomorrow_end The same for tomorrow
leave_at The next time this child has to set off, as a timestamp — null without a way to school or a time to leave
leave_until Until when that leaving time is worth showing: the first lesson for a child who walks, ten minutes past a bus time or a day in holiday care
lesson_now / lesson_next The running and the next lesson, or null
schedule Which named set of lesson times this child’s school rings to, or null for the household’s
day_mode school, care, free, event or sick — which routine is showing, and why
sick_until The last day of an illness, or null
outlook This week, starting on the household’s first day of the week (Monday by default), and the next seven days, each with its date, mode and week of the cycle — plus changes where a period is covered or cancelled, and exams where there are any
timetable The whole week, for the card
routine_morning / routine_evening Today’s steps, each with done
routine_stats The record of the last 30 days: rate, streak, best_streak, blocks, steps and days

The morning announcement — “today Ben has PE” — is a time trigger and a template:

alias: Schultag-Durchsage
triggers:
  - trigger: time
    at: "07:00:00"
conditions:
  # Nothing to announce at the weekend, or on a day with no school.
  - condition: template
    value_template: "{{ state_attr('sensor.schoolday_ben', 'today_subjects') | count > 0 }}"
actions:
  - action: tts.speak
    target:
      entity_id: tts.home_assistant_cloud
    data:
      media_player_entity_id: media_player.homepod_kuche
      message: >-
        Guten Morgen! Ben hat heute {{ state_attr('sensor.schoolday_ben', 'today_summary') }}.
        {% if 'Sport' in state_attr('sensor.schoolday_ben', 'today_subjects') %}
          Sportsachen nicht vergessen.
        {% endif %}

The evening before

The evening is when the PE kit still gets packed, so tomorrow_subjects and tomorrow_summary say the same for the next day. They are the same answer the packing list is made from, so the sentence and the bag cannot disagree, and they are empty whenever tomorrow is not a school day — Friday evening is quiet, Sunday evening is not, without a weekday condition anywhere.

One child:

Ben hat morgen {{ state_attr('sensor.schoolday_ben', 'tomorrow_summary') }}.

Every child at once, without naming any of them: each member sensor carries member, so the template finds them all and the automation does not need editing when a child joins.

alias: Morgen ist Sport
triggers:
  - trigger: time
    at: "19:00:00"
variables:
  sport: >-
    {{ states.sensor
       | selectattr('attributes.tomorrow_subjects', 'defined')
       | selectattr('attributes.tomorrow_subjects', 'contains', 'Sport')
       | map(attribute='attributes.member') | list }}
conditions:
  - condition: template
    value_template: "{{ sport | count > 0 }}"
actions:
  - action: tts.speak
    target:
      entity_id: tts.home_assistant_cloud
    data:
      media_player_entity_id: media_player.homepod_kuche
      message: >-
        {% if sport | count == 1 %}
          Morgen hat {{ sport[0] }} Sport.
        {% else %}
          Morgen haben {{ sport[:-1] | join(', ') }} und {{ sport[-1] }} Sport.
        {% endif %}
        Sportsachen einpacken!

That reads “Morgen haben Ben, Mia und Lea Sport.” The comparison is exact, like every other one on this page — see below for matching loosely.

They move on at midnight, so an announcement at half past midnight is already about the day after.

A lesson the school’s own system cancelled is already gone from tomorrow_subjects — a substitute is already in it — and tomorrow_exams has the tests, so one evening sentence can carry both — see From the school’s own system:

{% set exams = state_attr('sensor.schoolday_ben', 'tomorrow_exams') %}
{% if exams %}Ben schreibt morgen {{ exams | join(' und ') }}.{% endif %}

When the school changes the plan

With a lessons calendar from the school system on a child, Schoolday reads it again every ten minutes. When that changes a day that is still to come — a lesson cancelled, a substitute, another room — it fires schoolday_plan_changed, once per child and day:

Field Contents
member / member_id Who
date / days_ahead Which day, and how far off: 0 today, 1 tomorrow
cancelled The lessons that are no longer on: subject, room, period, start, end
changed The periods holding something new, the same way, plus instead — the subject that was there, or null where there was none — and restored, true for a cancellation taken back
start / end When the day now begins and ends, null when nothing is left
previous_start / previous_end When it began and ended before
message All of it as a sentence, in Home Assistant’s language

The message reads, in a German household:

Ben morgen: Musik (1. Std.) fällt aus. Deutsch statt Mathe (3. Std., Raum 007). Beginn 08:45 statt 08:00.

and in an English one “Ben tomorrow: Musik (period 1) is cancelled. Deutsch instead of Mathe (period 3, room 007). Starts 08:45 instead of 08:00.” A double lesson is one lesson — “Sport (5.–6. Std.) fällt aus” — and a day with nothing left says so rather than listing it.

A change is believed once it has been read twice: when a reading disagrees, Schoolday looks again a minute later. A school system’s calendar that is being reloaded is briefly there and empty, and without the second look the next full reading would announce every cancellation the school already had.

Some things are deliberately not news. The first reading after Home Assistant starts is only the baseline: what the school changed while it was off cannot be dated, and a phone that buzzes with the whole week after every restart is a phone nobody reads. Changes you make yourself — under Exceptions, or to the timetable — are not announced either, and nor is a holiday appearing in the holiday calendar. Today counts only from now on: a cancelled lesson that is already over has nothing left for anybody to do about it.

The quickest way to a notification is the blueprint. By hand:

alias: Stundenplan geändert
triggers:
  - trigger: event
    event_type: schoolday_plan_changed
conditions:
  # Today and tomorrow. Next Thursday can wait until it is tomorrow.
  - condition: template
    value_template: "{{ trigger.event.data.days_ahead <= 1 }}"
actions:
  - action: notify.mobile_app_dominik
    data:
      title: Stundenplan
      message: "{{ trigger.event.data.message }}"

Time to leave

Give a child a way to school — minutes from the front door to the classroom, set with the member under Edit a family member or in the admin card’s Family section — and Schoolday works out when they have to set off: the first lesson that is actually on, minus the way. A cancelled first period moves it later by itself, and on a day without school there is none.

It is published twice. leave_at on the child’s sensor, for templates, and a sensor of its own, sensor.schoolday_ben_time_to_leave (in German …_losgehen), whose state is the time. Being a timestamp sensor, a time trigger can follow it without a template:

alias: Ben – Schuhe an
triggers:
  - trigger: time
    at: sensor.schoolday_ben_time_to_leave
actions:
  - action: tts.speak
    target:
      entity_id: tts.home_assistant_cloud
    data:
      media_player_entity_id: media_player.flur
      message: Ben, Schuhe an, es geht los!

It shows today’s leaving time until the first lesson has begun, then moves on to the next school day. The child without a way to school has no such sensor at all; 0 minutes is a way to school, for a child living next door. The header card counts down to it on the wall.

A bus, a lift, holiday care

A school bus does not come later because the first lesson is cancelled, and a child who catches it cannot sleep in. For them, set Leaves on school days at instead — the time they go out of the door, whatever the school cancelled. It wins over the way to school, so a child can keep both and the bus time is what counts.

Holiday care rarely starts when school does, and has no first lesson to count back from. Leaves in holiday care at is the time on a day the child’s own calendar marks as holiday care. When one holiday’s care starts earlier than another’s, put the time in the calendar instead: a care event that starts at 06:45, rather than lasting all day, makes 06:45 the leaving time for that day.

Day Leaving time
School, with a time on school days That time, every school day
School, with only a way to school The first lesson that is on, minus the way
Holiday care, with a timed care event The event’s start — the earliest, if there are two
Holiday care, with a time for care That time
Anything else None

A time by the clock shows as “go now” for ten minutes and then moves on to the next day, where a walk to school does so at the first bell. leave_time and care_leave_time are on the leave sensor too, next to travel_minutes; its starts_at is null on a care day.

Blueprints

Three come with Schoolday. They are copied into blueprints/automation/schoolday/ every time Home Assistant starts, so after installing or updating they are simply under Settings → Automations & scenes → Blueprints, and an automation made from one keeps up with the sensors it reads. A copy you edited is left alone, and one you deleted stays deleted.

Blueprint What it does Needs
The school changed the plan A push notification on the phones you pick, with the message above. Choose which children and how far ahead — today and tomorrow unless you say otherwise. A lessons calendar
Time-to-leave light A lamp in the hall turns amber a few minutes before leaving, red when it is time, and goes off again. Colours, brightness and minutes are yours to pick. A way to school or a time to leave
Before leaving for school Anything you like, a set time before leaving: the wake-up light an hour before, the radio, the bathroom heating. It moves with the first lesson — unless the child has a bus time. A way to school or a time to leave

The last two follow the leaving time to the minute, so “an hour before” on a morning whose first lesson is cancelled is an hour before the second one — the extra 45 minutes of sleep arrive without anybody touching the alarm. A child with a bus time gets up for the bus regardless, and on a day in holiday care for the care time.

The phones are the Home Assistant app on each one. The notification goes to the notify.mobile_app_… action the app registered, which carries the phone’s own name, so renaming the device in Home Assistant does not break it. Anything else — a sentence in the kitchen, a notify group — goes under Anything else, where the text is message.

Subject names

Subject names are free text. Werken, AG Robotik, Bläserklasse — nothing here knows what a school teaches, and no list of subjects is kept anywhere, so the automation above works whatever the subject is called.

The flip side is that comparisons are exact: 'Sport' in today_subjects matches neither sport nor Sportunterricht. Within one child’s week Schoolday settles that for you — save a week with Sport on Monday and sport on Wednesday and both become the first spelling. Across children it deliberately does not, because that would undo a rename; two spellings then show up as two entries in the subject-colour step, which is where you notice.

To not have to care at all, match loosely:

{{ state_attr('sensor.schoolday_ben', 'today_subjects') | select('search', '(?i)sport') | list | count > 0 }}

No holiday condition is needed: on a day one of the holiday calendars covers, today_subjects is empty and the automation stops at the condition. Without those calendars configured it would be cheerfully wrong for six weeks every summer.

The board sensor says the same thing outright for the household, and each member sensor says it for that child — including whether they are in holiday care today:

sensor.schoolday_board          sensor.schoolday_ben
  school_today: false             day_mode: care
  no_school_reason: "Sommerferien"

So a care day gets its own sentence without a second entity to check:

{% if state_attr('sensor.schoolday_ben', 'day_mode') == 'care' %}
  Ben ist heute in der Ferienbetreuung. Badesachen einpacken.
{% endif %}

Two events fire at every lesson boundary, carrying member, member_id, subject, room, period, start and end:

Event When
schoolday_lesson_started A lesson begins
schoolday_lesson_ended A lesson ends
schoolday_plan_changed The school system changed a day still to come — see below

They exist alongside the state because the state cannot show everything: two periods of the same subject in a row are one state but two lessons. Nothing fires on a Home Assistant restart — the sensor adopts whatever is running without announcing it, so a restart mid-morning does not shout “PE has started” into the kitchen.

alias: Sport beginnt
triggers:
  - trigger: event
    event_type: schoolday_lesson_started
    event_data:
      member: Ben
      subject: Sport
actions:
  - action: notify.mobile_app_dominik
    data:
      message: "Ben ist jetzt in Sport ({{ trigger.event.data.room }})."

What the routines are doing

routine_stats is the record rather than today’s state, so it answers questions today’s ticks cannot — and it answers them on Sunday evening, which is when anybody is going to act on them.

alias: Wochenrückblick Routinen
triggers:
  - trigger: time
    at: "19:00:00"
conditions:
  - condition: time
    weekday: [sun]
  # A step that is missed once was a bad day; one missed half the time is a step in the
  # wrong place. Only the second is worth saying anything about.
  - condition: template
    value_template: >-
      {{ (state_attr('sensor.schoolday_ben', 'routine_stats').steps | first).rate < 60 }}
actions:
  - action: notify.mobile_app_dominik
    data:
      message: >-
        {% set worst = state_attr('sensor.schoolday_ben', 'routine_stats').steps | first %}
        „{{ worst.step }}“ hat Ben in den letzten vier Wochen {{ worst.done }} von
        {{ worst.asked }} Mal gemacht. Vielleicht gehört der Schritt woanders hin.

steps arrives worst first, so | first is the step most often skipped. A rate is null rather than 0 where nothing was ever asked, which is worth a is not none before comparing it.

Services

Everything the options dialog can change is also a service, because a Lovelace card cannot edit a config entry but can call one. They validate exactly what the options flow validates: a card must not be able to write a timetable the sensors can no longer read.

Service Purpose
schoolday.set_routine_step Tick a routine step off, or put it back
schoolday.reset_routine Clear today’s ticks
schoolday.set_absent Mark a child ill, optionally through a date
schoolday.set_periods Replace the lesson times — the household’s, or one named school’s
schoolday.set_lesson One cell of one week
schoolday.set_day A whole weekday at once
schoolday.set_routine Replace one member’s steps for one block on one day
schoolday.set_cycle One-week or two-week timetable, where week A starts, and which day a week begins on (week_start, monday to sunday)
schoolday.set_subject_color Recolour a subject, or hand it back its derived colour
schoolday.set_exception What one date does differently
schoolday.clear_exception Put a date back
schoolday.set_materials What a subject needs brought along
schoolday.set_member Add or change a family member, including which school they ring to, their way to school, their times to leave on school days and in holiday care (leave_time, care_leave_time, HH:MM) and their school system’s calendars and homework list (lessons_calendar, exams_calendar, homework_list)
schoolday.remove_member Remove one, and everything that only existed for them
schoolday.set_calendars The holiday calendars, the holiday-care keywords, and the cancellation and substitution words

Every one that takes a member accepts a name as well as an id, so a script stays readable.

The first three are open to every user, because ticking a step off is what a child’s wall tablet is for. Everything below them rewrites the configuration and needs an administrator, exactly like the options dialog: a call from any other user is refused. An automation runs without a user and is not affected.


This site uses Just the Docs, a documentation theme for Jekyll.