ok, so the first thought emerged when one of the Home Assistant’s updated introduced changes to scene system, which required to give “state” to all entites controlled by scene - and because my scenes also control media players’ volume settings, it was a deal breaker for me. WAF dropped by few points, because of constant turning off/breaking the stream played by this or that Google Home Mini and the solution was needed ASAP.
first plan: move all scenes to scripts, so I can make some conditions and check if media player is idle or not, and then change volume if it won’t interrupt anything [well, state is not needed by scripts, so it won’t stop the stream for sure]. but because I use a lot of scenes with many entities, I was just too lazy to rewrite them all.
second plan: make a big fat script to control scenes, bonus points for some kind of universal/relatively simple way of configuring those scenes.
weeks passed by and then the solution just popped to my head: BITS. let’s dismantle all scene config and represent each device with one bit; then join all the bits into one word/variable/string and send it to script. boom! I present to you: one script to rule them all!
some important facts:
- script is universal - no matter how many entities you want to control - it will adapt automatically
- light_profiles.csv file doesn’t work too good, because it can work ONLY with xy_color, and I’ve noticed that some rgb bulbs give different outputs when turned on with xy_color, rgb_color or color_temp - that’s why my script uses it’s own profile “system” with support to color_temp, rgb_color and white_value
- different media players’ volumes for different time of day? no problem! it has a simple volume profile/setting too
- you can have up to 36 different light profiles and up to 36 different volume settings that can be used in scenes
- right now script is compatible with ANY entity that can be turned on/off by it’s service: domain.turn_on(off) [light, switch, input_boolean, media_player etc.]
- media_player additional function: set volume [to given profile, or default setting]
- light additional settings: brightness, rgb_color or color_temp, transition, white_value
- media player won’t be interrupted, as the script checks if it’s idle before any action
- instead of dozen lines of scene config, you can write all as 01102-0-1G - each character, or: bit, is setting for different entity. “0” means turn off, “1” turn on; “-” is “do not touch/change the state”, and higher numbers and letters are different profiles. don’t worry - detailed guide and examples are in the pastebin link!
I’m guessing that script can be updated to work with other entities too [like input_select] but I don’t need that, so didn’t included this part.
ok, and now the code & guide itself - the same thing is also here, at pastebin, where I’m updating it in case of found errors.
#################### documentation #############################################
#
#################### configuration
#
# t_entities - list of all the entities you want to control. remember - order
# will be important when sendig the bits with configuration,
# so think how to organize/sort the entities. I suggest that ones
# used the most should be put first, so when you'll want to turn on
# or off just them - you'll be able to omit the rest of config bits
# t_profile - list of your light profiles. each profile has required parameters
# and those that are optional. there are different lights and bulbs,
# some of them gives better output when set with color_temp, some of
# them work better with rgb_color. there are also RGBW lights, that
# need the white_value parameter if you want to turn it all white.
# script covers them all - profile schema below:
# [A (int), B (int or array of int), C (string), D (string)]
# A = brightness [0-255]
# B = color, set with color_temp [1 number] or rgb_color [3 numbers]
# C = transition time prefixed with "T" [seconds], defaults to 1
# D = white_value setting prefixed with "W" [0-255] defaults to 0
# required values are: A and B.
# the easiest way to describe it is by examples:
# 1) "50% brightness" = [128]
# 2) "100% brightness with color_temp set to 342" = [255, 342]
# 3) "50% brightness red light with rgb_color" = [255, [255, 0, 0]]
# 4) same as 3) + 10 sec. transition = [255, [255, 0, 0], 'T10']
# 5) "full white, white_value set to 100%, and 5 sec. transition" =
# [255, [255, 255, 255], 'T5', 'W255']
# IMPORTANT: first two profiles are kinda standard "turn off", and
# "turn on" being on positions - respectively 0 and 1, so I strongly
# suggest to leave the first two just as they are - [0] and [255].
# t_volume - list of volume profiles - those are used for media_player entities
# and by default are in 0-1 range, so any number you'll enter in the
# profile, would be divided by 100 to recalculate (0-100 int)
# d_volume - default, failsafe volume (0-1 float)
# t_config - argument given at the start of script, containing all (or just the
# first) bits of scene configuration. each bit (place in config)
# represents one entity in the same order that they are listed in the
# t_entities variable. value of 0 means "turn off", 1 means "turn on"
# a dash (-) means "do not change", and other values 0-9 and A-Z are
# for choosing light or volume profiles
#
#################### info
#
# script will evaluate all configuration bits, and will take some actions based
# on entity and bit value. configuration bits are using numbers with base of 36.
# that means, you can use numbers from 0 to 9, and letters from A to Z, to
# represent states of your entities.
#
# most entities will be fine with just two states: 0 and 1 being "turn off" and
# "turn on", but with all the RGB lighting at home, you can have up to 36
# different light colors, temperatures, brightness' etc. configured and then you
# can call them easily with number or letter.
#
# although order of the configuration bits must be corelated with entities order
# given in t_entities variable, you can omit a part of the config - for example:
# first 5 entities are lights in the living room, next 20 entities are not so
# important - they could be anything. you can send only 5 bits of config to the
# script, if you want to control all the living room lights - and then script
# will stop. but because script uses bits order to control entities, if you want
# to control those 20 next entities but not 5 living room lights - you will need
# to give all 25 bits of config.
#
# if the entity is media player, and it is about to have it's volume changed,
# script first checks if it is playing something. volume changes only if there's
# no active playback. but if the scene is going to turn media player off - it
# will, no matter what.
#
#################### running the script
#
# easy, similar as other scripts:
# - service: script.run_scene
# data:
# t_config: 'STRING-WITH-CONFIGURATION-BITS'
#
# example:
# - service: script.run_scene
# data:
# t_config: '--3-20-GH---'
#
#################### some more detailed examples
#
# based on example entities and profiles configured in the script:
# 1. turn on kitchen light, turn off bathroom light, leave rgb_bulb intact, and
# turn xmas tree off, with no changes to party mode and media player:
# t_config: '10-0'
# 2. turn all off:
# t_config: '000000'
# 3. leave kitchen & bathroom intact, turn on rgb_bulb full white (last profile
# = [255,[255,255,255],'W255']), turn off xmas tree, turn on party mode and
# set media player to 50% (4th profile):
# t_config: '--5013'
# ...and so on.
#
#################### important notes
#
# profiles are numbered from 0, so to choose first one, enter 0; to choose third
# enter 2; to choose profile number 10 enter A, profile 16th enter G; etc.
#
# you can have as many entities as you want. script just checks all config bits
# and as long as number of configuration bits is less or equal than number of
# entities - all will work fine ;)
#
# you can use any entity you want - it is only important that it uses services
# turn_on and turn_off - the rest is done automatically
#
#################### companion entity and automation
#
# not needed for script to run, but I included it as another example of using
# the script and also to show how it is possible to manage scenes now - with
# ease and all based in one place. (example automation has "example 2" set as
# the default scene for failsafe configuration)
#
################################################################################
script:
run_scene:
alias: run_scene
icon: "mdi:checkbox-multiple-marked-outline"
description: "scene switcher with n-bit configuration"
variables:
t_entities: ['light.kitchen', 'light.bathroom', 'light.rgb_bulb', 'switch.xmas_tree', 'input_boolean.party_mode', 'media_player.kitchen']
t_profile: [[0], [255], [255,334], [189,342,'T5'], [255,[255,0,0]], [255,[255,255,255],'W255']]
t_volume: [0, 5, 25, 50, 100]
d_volume: 0.25
fields:
t_config:
description: "scene configuration bits"
example: "0-0110--3330------0-0000111333130000--"
mode: restart
sequence:
#################### loopy loop
- repeat:
while: "{{ repeat.index <= t_config|length if t_config|length <= t_entities|length else t_entities|length }}"
sequence:
- variables:
t_bit: "{{ t_config[repeat.index-1]|int(base=36) }}"
- condition: template
value_template: "{{ (t_config|length > 0) and (t_config[repeat.index-1] != '-') }}"
- choose:
#################### media_players - change volume
- conditions: "{{ t_entities[repeat.index-1].split('.')[0] == 'media_player' and t_bit|int > 0 and not is_state(t_entities[repeat.index-1],'playing') }}"
sequence:
- service: media_player.volume_set
data:
entity_id: "{{ t_entities[repeat.index-1] }}"
volume_level: "{{ t_volume[t_config[repeat.index-1]|int]/100 if t_config[repeat.index-1]|int < t_volume|length else d_volume }}"
#################### media_players - don't break anything
- conditions: "{{ t_entities[repeat.index-1].split('.')[0] == 'media_player' and t_bit|int > 0 and is_state(t_entities[repeat.index-1], 'playing') }}"
sequence:
- delay:
milliseconds: 5
#################### turn on lights - with color_temp
- conditions: "{{ t_entities[repeat.index-1].split('.')[0] == 'light' and t_bit|int > 1 and t_profile[t_bit|int]|length > 1 and t_profile[t_bit|int][1][0] is not defined }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ t_entities[repeat.index-1] }}"
brightness: "{{ t_profile[t_bit|int][0] }}"
color_temp: "{{ t_profile[t_bit|int][1] }}"
transition: "{{ t_profile[t_bit|int][2]|regex_replace('T', '')|int if (t_profile[t_bit|int][2] is defined and t_profile[t_bit|int][2][0] == 'T') else 1 }}"
white_value: "{{ t_profile[t_bit|int][2]|regex_replace('W','')|int if (t_profile[t_bit|int][2] is defined and t_profile[t_bit|int][2][0] == 'W') else t_profile[t_bit|int][3]|regex_replace('W','')|int if (t_profile[t_bit|int][3] is defined and t_profile[t_bit|int][3][0] == 'W') else 0 }}"
#################### turn on lights - with rgb_color
- conditions: "{{ t_entities[repeat.index-1].split('.')[0] == 'light' and t_bit|int > 1 and t_profile[t_bit|int]|length > 1 and t_profile[t_bit|int][1][0] is defined }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ t_entities[repeat.index-1] }}"
brightness: "{{ t_profile[t_bit|int][0] }}"
rgb_color:
- '{{ t_profile[t_bit|int][1][0]|int }}'
- '{{ t_profile[t_bit|int][1][1]|int }}'
- '{{ t_profile[t_bit|int][1][2]|int }}'
transition: "{{ t_profile[t_bit|int][2]|regex_replace('T', '')|int if (t_profile[t_bit|int][2] is defined and t_profile[t_bit|int][2][0] == 'T') else 1 }}"
white_value: "{{ t_profile[t_bit|int][2]|regex_replace('W','')|int if (t_profile[t_bit|int][2] is defined and t_profile[t_bit|int][2][0] == 'W') else t_profile[t_bit|int][3]|regex_replace('W','')|int if (t_profile[t_bit|int][3] is defined and t_profile[t_bit|int][3][0] == 'W') else 0 }}"
#################### turn the rest on or off
default:
- service: "{{ t_entities[repeat.index-1].split('.')[0] }}.{{ 'turn_on' if (t_config[repeat.index-1]|int == 1 and not is_state(t_entities[repeat.index-1],'playing')) else 'turn_off' }}"
data:
entity_id: "{{ t_entities[repeat.index-1] }}"
#################### additional, companion entity and automation
input_select:
my_scene:
name: my_scene
options:
- "example 1"
- "example 2"
- "example 3"
automation:
- id: scene_change
alias: scene_change
initial_state: on
mode: restart
trigger:
- platform: state
entity_id: input_select.my_scene
- platform: homeassistant
event: start
action:
- service: script.run_scene
data:
t_config: >
{% set t_scenes = {
'example 1': '10-0',
'example 2': '000000',
'example 3': '--5013'
} %}
{{ t_scenes[states('input_select.my_scene')] if states('input_select.my_scene') in t_scenes.keys() else t_scenes['example 2'] }}