Automations
Automations are organized into feature packages under packages/. Each package owns its own automations, scripts, and helper entities.
Entertainment state machine
The media package implements a state machine for three entertainment modes: TV, Movie, and Hygge. Only one mode can be active at a time. The "Master Orchestrator" automation manages transitions between modes based on what's playing and on which device.
Dynamic lighting during playback:
- When a video starts playing, lights dim to a minimum
- When playback pauses, lights brighten slightly so it's easy to move around — but only after sunset
- When all entertainment modes are turned off, lighting returns to the normal adaptive state
The logic tracks both the TV's device state and the Plex client state to handle edge cases reliably.
Sonos night sound is also managed here: enabled automatically at 21:00 and disabled at 07:00.
Master Orchestrator
The automation listens to several triggers and routes to the correct action using choose. Mode is restart — if a new trigger fires while the automation is still running, it starts over from the beginning.
- alias: "Viihde: Master Orchestrator"
triggers:
- trigger: state
entity_id: media_player.olohuoneen_tv
to: "off"
for:
seconds: 3
id: tv_off
- trigger: state
entity_id: media_player.olohuoneen_tv
attribute: source
to: "Live TV"
id: source_live
- trigger: state
entity_id: media_player.shield
id: shield_change
- trigger: state
entity_id: media_player.plex_shield
id: shield_change
- trigger: state
entity_id: input_boolean.hyggetila_paalla
to: "on"
id: hygge_on
- trigger: state
entity_id: input_boolean.elokuvatila_paalla
to: "on"
id: movie_mode_on
conditions:
# Shield fires state changes constantly while playing (timeline updates etc.).
# Only continue if the Shield trigger actually changed state (playing → paused etc.),
# not just updated attributes.
- condition: template
value_template: >
{{ trigger.id != 'shield_change' or
trigger.to_state.state != trigger.from_state.state }}
actions:
- choose:
- conditions:
- condition: trigger
id: tv_off
sequence:
- action: script.reset_entertainment_modes
- action: script.restore_normal_lighting
- conditions:
- condition: trigger
id: hygge_on
sequence:
- action: input_boolean.turn_off
target:
entity_id:
- input_boolean.tv_tila_paalla
- input_boolean.elokuvatila_paalla
- action: script.hyggetila
- conditions:
- condition: trigger
id: movie_mode_on
sequence:
- action: input_boolean.turn_off
target:
entity_id:
- input_boolean.tv_tila_paalla
- input_boolean.hyggetila_paalla
- action: script.elokuvatila
# Shield starts playing → activate movie mode
- conditions:
- condition: trigger
id: shield_change
- condition: state
entity_id: input_boolean.elokuvatila_paalla
state: "off"
- condition: or
conditions:
- condition: state
entity_id: media_player.shield
state: "playing"
- condition: state
entity_id: media_player.plex_shield
state: "playing"
sequence:
- action: input_boolean.turn_on
target:
entity_id: input_boolean.elokuvatila_paalla
# Shield state changes while movie mode is already on
- conditions:
- condition: trigger
id: shield_change
- condition: state
entity_id: input_boolean.elokuvatila_paalla
state: "on"
sequence:
- action: script.handle_movie_mode_state_change
mode: restart
Plex vs. Shield priority
During movie mode, playback state is read from both the Shield media player and the dedicated Plex integration. Plex takes priority: if the Plex entity says paused, that wins even if the Shield entity reports playing. This avoids a false "watching" state when Plex is paused but the Shield hasn't updated yet.
- choose:
# Plex playing → dim lights
- conditions:
- condition: state
entity_id: media_player.plex_shield
state: playing
sequence:
- action: script.apply_movie_viewing_lighting
# Plex paused → brighten lights
- conditions:
- condition: state
entity_id: media_player.plex_shield
state: paused
sequence:
- action: script.apply_movie_paused_lighting
# Shield playing, but NOT via Plex → dim lights
- conditions:
- condition: state
entity_id: media_player.shield
state: playing
- condition: not
conditions:
- condition: state
entity_id: media_player.shield
attribute: app_name
state: "Plex"
sequence:
- action: script.apply_movie_viewing_lighting
Adaptive Lighting
The Adaptive Lighting custom component adjusts brightness and colour temperature throughout the day, following the sun. Activating an entertainment mode disables adaptive lighting for the duration, giving full manual control over the scene.
Lights are grouped into Zigbee groups where possible to avoid the "popcorn effect" (lights turning on one by one with a delay).
Air quality
Two Apollo Air-1 sensors monitor CO2, PM2.5, and VOC levels — one in the bedroom and one in the office. When CO2 exceeds 1000 ppm for more than 5 minutes:
- A notification is sent
- The sensor's RGB LED turns orange as a local visual indicator
The alert only fires when someone is home and the room isn't in sleep mode.
Wake-up routine
A circadian wake-up routine gradually brings up the lights and starts music before the alarm time. It reads the next alarm from the Android Companion App.
One important quirk: the Companion App must be configured with an allow list in its sensor settings, permitting only the clock app (com.google.android.deskclock). Without this, other apps (calendar events, Tasker) can trigger the wake-up routine unintentionally.
The routine also has a quiet hours guard (22:00–06:00) to prevent it from firing if the sensor sends an incorrect alarm time.