Tag: Home Assistant

  • Interrupting the Stream: A Home Assistant Bedtime Reminder

    Interrupting the Stream: A Home Assistant Bedtime Reminder

    We rise early, so getting to bed at a reasonable hour can be a challenge, whether it’s summer and the sun is still up past 21:00, or we’re sucked into a streaming show. One of the first things I added to Home Assistant was an evening time announcement to subtly remind or shame us that it is getting late. Since we generally stream using the Apple TV, I added the Apple TV integration for Home Assistant.

    At first, I thought I could send a message to display on the screen every half hour in the evening using HomeKit, but I could not get any version of this example to work. I’m sure I am doing something wrong, or my old Apple TV, which barely runs tvOS 26, has issues. I would still love to add an on-screen notification if anyone has a working example.

    I landed on checking whether media was playing on the Apple TV, pausing whatever was playing, making the time announcement, and then letting it play again. Pausing what is playing, even ever so briefly, gets our attention and makes us aware of the time announcement.

    To do this, I first set a variable to get the state of the Apple TV:

      - variables:
          appletv_was_playing: '{{ is_state(''media_player.living_room'', ''playing'') }}'
    

    With that variable set, I can check its state and take action:

      - if:
          - condition: template
            value_template: '{{ appletv_was_playing }}'
        then:
          - action: remote.send_command
            target:
              entity_id: remote.living_room
            data:
              command: pause
    

    I announce the time on the various media players throughout the place using Chime TTS.

         - action: chime_tts.say
           target:
             entity_id:
               - media_player.bedroom
               - media_player.living_room
           data:
             announce: true
             cache: true
             volume_level: 0.4
             message: >
               {% from 'speech_macros.jinja' import say_time %}
               The time is {{ say_time(now()) }}.
    

    Then, as an extra measure, add a half-second delay as padding, and then resume whatever was playing. The announce flag interrupts any playing audio, but I have found it just plays over whatever the Apple TV is doing.

      - delay:
          milliseconds: 500
      - if:
          - condition: template
            value_template: '{{ appletv_was_playing }}'
        then:
          - action: remote.send_command
            target:
              entity_id: remote.living_room
            data:
              command: play
    

    I’m not accounting for a bunch of edge cases here: What happens if the Apple TV is paused, or if someone presses play during the announcement? I figured I would address these if they ever became an issue.

    As an aside, text-to-speech is unpredictable and sometimes entertaining in how it handles whatever now() tunneled into strftime() outputs. I found a set of natural-language speech macros written in Jinja and tweaked them to my liking, since we prefer 24-hour time. To use this, save these two macros as speech_macros.jinja file in your /config/custom_templates/ folder. Restart or run homeassistant.reload_custom_templates after adding the file.

    {% macro _round_min(t, round_min) %}
      {% set minute = ((t.minute + round_min / 2) // round_min) * round_min %}
      {% set hour = t.hour + (1 if minute == 60 else 0) %}
      {% set minute = 0 if minute == 60 else minute %}
      {{ "%02d:%02d:00" % (hour % 24, minute) }}
    {% endmacro %}
    
    {% macro say_time(t, round_min=0, format_24h=True) %}
      {% if t is undefined or not t is datetime %}
        Macro Arg Type Error: First argument must be a datetime object
      {% elif round_min is defined and not round_min is integer %}
        Macro Arg Type Error: Second argument must be an integer
      {% else %}
        {% if round_min > 0 %}
          {% set t = today_at(_round_min(t, round_min)) %}
        {% endif %}
        {% set hour = t.hour %}
        {% set minute = t.minute %}
    
        {% if format_24h %}
          {% if minute == 0 %}
            {{ t.strftime('%H') }} hundred
          {% else %}
            {{ t.strftime('%H:%M') }}
          {% endif %}
        {% else %}
          {% if hour == 0 and minute == 0 %}
            midnight
          {% elif hour == 12 and minute == 0 %}
            noon
          {% elif minute == 0 %}
            {{ t.strftime('%-I') }} o'clock {{ t.strftime('%p') }}
          {% elif minute < 10 %}
            {{ t.strftime('%-I Oh %-M %p') }}
          {% else %}
            {{ t.strftime('%-I:%M %p') }}
          {% endif %}
        {% endif %}
      {% endif %}
    {% endmacro %}
    

    You call this with {% from 'speech_macros.jinja' import say_time %} The time is {{ say_time(now()) }}. as your message.

    So these don’t play after we go to bed; I check, for of all things, that the white noise machine is turned on. A sign we have gone to bed. You may have other triggers, or you might set a boolean that resets every day.

    Do we get to bed on time? Most nights.

  • Boring, Reliable, and Quiet: Motion Alerts in Home Assistant

    Boring, Reliable, and Quiet: Motion Alerts in Home Assistant

    Automation projects often start with curiosity or convenience. This one started with care.

    My partner’s parents, whom we affectionately call the parental-units, are aging. My partner’s mom is experiencing dementia, and her dad has been looking for ways to better support his wife day-to-day. We wanted something simple: passive awareness. Not cameras inside the home. Not constant monitoring. Just a quiet way to know when someone was up, moving, or possibly leaving the house.

    Home Assistant felt like it could be part of that solution.

    The goal was not automation; it was trust.

    The problem we were trying to solve had very little to do with Home Assistant itself.

    We needed low-noise awareness for an aging parent with early dementia. Sometimes my partner’s dad, in another room, wouldn’t realize his wife had gotten out of bed. Other times, we needed confidence that things were calm while someone was out, napping, or focused elsewhere.

    Good alerting in this context meant:

    • a simple heads-up that motion was detected in another room
    • or that a door had been opened

    What we explicitly did not want:

    • sirens
    • panic
    • or a feeling of constant surveillance

    Think alarm system sensor states, but without the alarm.

    Doors were easy. Motion was not.

    Door and window sensors behaved exactly as expected. A door opens, it stays open for a moment, and an alert fires. If it remains open, the alert can repeat. This maps cleanly to how Home Assistant’s alert: integration (a feature that manages notifications) works.

    Motion sensors were a different story.

    A simple walk around the bedroom could generate dozens, sometimes hundreds, of alerts. Motion crossed from useful into pure noise almost immediately.

    The failed mental model

    I assumed motion alerts would behave as they do in most alarm systems:

    • motion detected
    • sensor enters a “motion” state
    • remains active for a short period
    • alert repeats at a reasonable cadence

    Instead, I quickly learned that Passive Infrared (PIR) motion sensors do not behave that way.

    They emit frequent detection events, sometimes every second, along with data such as ambient light. Home Assistant was operating as expected, but my assumptions did not match reality.

    The alert: integration only repeats while an entity remains in the on state. Motion sensors were constantly flipping on and off. Each new detection triggered a new alert.

    The key insight: motion is event-based, not stateful

    Once that clicked, the problem reframed itself.

    The motion sensors did not have a concept of recent motion. They only knew how to say motion just happened. If I wanted alerts that matched human expectations, I needed to implement that concept myself.

    That is where latching came in.

    Latching motion: turning events into state

    The most important thing I learned in this project is that motion alerts only become usable once motion becomes stateful.

    To explain how this worked in practice, it helps to walk through a single example: the bedroom motion sensor.

    1. Raw motion

    This is the physical sensor. It reports motion whenever it sees it, and clears almost immediately afterward.

    binary_sensor.bedroom_motion

    On its own, this sensor is too noisy to alert on directly. It is accurate, but not human-friendly.

    2. Latched motion

    The next step was to create a latched version of that sensor: one that stays on for a fixed period after motion is detected.

    Conceptually:

    • motion happens → latched sensor turns on
    • additional motion extends the latch
    • no motion for five minutes → latch clears

    In Home Assistant, this was implemented as a template binary sensor with a delay_off (a setting that keeps the sensor on for a set period after motion is no longer detected):

    template:
    - binary_sensor:
    - name: "Bedroom motion latched"
    state: "{{ is_state('binary_sensor.bedroom_motion', 'on') }}"
    delay_off: "00:05:00"

    This change transformed motion from raw events to a useful state: recently detected motion.

    Five minutes was not chosen because it was perfect. It was chosen because it dramatically reduced alert noise and aligned with how people actually move through a room.

    Separating concerns: alerts are not sensors

    Once motion was latched, the next realization was that alerting logic should not live in the sensor itself.

    I wanted to be able to answer different questions independently:

    • Was there motion recently?
    • Should I be notified about it right now?
    • Are alerts temporarily muted?

    That led to a second layer.

    3. Alert conditions

    Instead of pointing alerts directly at the latched sensor, I created an alert-condition sensor that combined:

    • latched motion
    • global mute state

    Conceptually:

    latched motion AND alerts not muted

    In practice:

    template:
    - binary_sensor:
    - name: "Bedroom motion alert condition"
    state: >
    {{ is_state('binary_sensor.bedroom_motion_latched', 'on')
    and is_state('input_boolean.alerts_muted', 'off') }}

    This sensor answers a very specific question:

    Should an alert be active right now?

    The alert: integration then watches this entity, not the raw motion.

    Muting alerts without hiding reality

    The global mute was intentionally simple. It does not disable sensors. It does not suppress the state. It only gates notifications.

    That distinction matters.

    When alerts are muted:

    • motion still latches
    • dashboards still update
    • last motion time still changes

    What stops is the noise.

    That made it possible to quiet the system quickly without losing confidence that it was still working.

    Making state visible

    Once motion was latched and alerting was gated, it became important to clearly surface that state.

    Dashboards answered questions, notifications could not:

    • Has there been motion recently?
    • How long ago?
    • Are alerts muted intentionally?

    Here’s the card I ended up with using the bedroom motion sensor as an example:

    type: custom:button-card
    entity: binary_sensor.bedroom_motion_latched
    name: Bedroom
    icon: mdi:bed
    tap_action:
    action: none
    
    show_label: true
    label: |
    [[[
    const motion = states['binary_sensor.motion_bedroom_motion'];
    const muted = states['input_boolean.alerts_muted']?.state === 'on';
    
    if (!motion) {
    return muted ? '🔕 Muted • Last motion: unknown'
    : 'Last motion: unknown';
    }
    
    const last = new Date(motion.last_changed);
    const now = new Date();
    const diffMs = now - last;
    
    const sec = Math.floor(diffMs / 1000);
    const min = Math.floor(sec / 60);
    const hr = Math.floor(min / 60);
    
    let ago;
    if (sec < 60) ago = `${sec}s ago`;
    else if (min < 60) ago = `${min}m ago`;
    else ago = `${hr}h ${min % 60}m ago`;
    
    return muted
    ? `🔕 Muted • Last motion: ${ago}`
    : `Last motion: ${ago}`;
    ]]]
    
    state:
    - value: "on"
    color: rgb(255,0,0)
    icon: mdi:bed-empty
    - value: "off"
    color: rgb(192,192,192)
    icon: mdi:bed

    A few important things are happening here:

    • The entity is the latched motion sensor, so the card remains active for a meaningful duration.
    • The label calculates how long ago the motion last occurred.
    • When alerts are muted, the card still updates, but clearly shows 🔕 Muted.
    • Color and icon changes make the state obvious without interaction.

    This visibility is not just for today’s caregivers. It is for future caregivers, and for future me, checking in remotely.

    Button-card as a communication tool

    I used button-card not because I needed buttons, but because I needed clarity.

    Color and icon changes instantly communicate state. For example, a garage door card turning red when open tells the story without interaction:

    type: custom:button-card
    entity: alert.garage_door
    state:
    - value: "on"
    color: rgb(255,0,0)
    icon: mdi:garage-open-variant

    Motion cards followed the same pattern: boring, obvious, and readable at a glance.

    Constraints I accepted on purpose

    There were many things I chose not to solve:

    • exposing every discovered device
    • chasing perfect real-time countdowns
    • eliminating alerts entirely

    My focus stayed narrow: door openings, motion, and understanding the system’s current state.

    In this context, boring and reliable always beat clever.

    Where this landed (so far)

    The system is a little noisy, so alerts are muted fairly often, but it is much more usable now.

    Most importantly, it has increased confidence: one parent can step away, we can check in, and we can trust that the system is working, even when it’s quiet.

    The takeaway

    The real lesson from building motion alerts for aging parents is that motion-based alerts are much harder than they appear—especially when real people have to live with the system.

    Home Assistant has a place here. Not as a flashy automation platform, but as a flexible, respectful alternative to camera-centric or alarm-first solutions. When the goal is care, quiet systems matter.