I’ve designed scene handling this way, hope it can be usefull for someone.
Prerequisite : using the semantic model, having your items are on equipments
, located in locations
The scene will group actions on items in a given location.
Scenes are identified using scene
metadata held by items:
Example items file:
Switch Tapo01_Output "Sonos R Output switch" <PowerOutlet> (gSonosOneR) ["Control", "Power"] {channel="tapocontrol:P115:1:1:actuator#output",
scene="OFF=OFF, TV=OFF, MUSIC=ON, CINEMA=ON, IDLE=OFF"}
Switch Tapo05_Output "Sonos L Output switch" <PowerOutlet> (gSonosOneL) ["Control", "Power"] {channel="tapocontrol:P115:1:5:actuator#output",
scene="OFF=OFF, TV=OFF, MUSIC=ON, CINEMA=ON, IDLE=OFF"}
Switch Tapo03_Output "Sonos Five Output switch" <PowerOutlet> (gSonosFive) ["Control", "Power"] {channel="tapocontrol:P115:1:3:actuator#output"
scene="OFF=OFF, MUSIC=ON"}
Switch Multiprise01_Output1 "Switch Lave-Linge" <PowerOutlet> (gMultipriseBuanderie) ["Control", "Power"] {channel="tapocontrol:P300:1:1:actuator#output1",
scene="OFF=OFF, IDLE=ON"}
Switch Multiprise01_Output2 "Switch Sèche-Linge" <PowerOutlet> (gMultipriseBuanderie) ["Control", "Power"] {channel="tapocontrol:P300:1:1:actuator#output2",
scene="OFF=OFF, IDLE=ON"}
Switch Multiprise01_Output3 "Switch Pompe" <PowerOutlet> (gMultipriseBuanderie) ["Control", "Power"] {channel="tapocontrol:P300:1:1:actuator#output3",
scene="OFF=OFF, IDLE=ON"}
Switch Multiprise02_Output1 "Switch Télévision" <PowerOutlet> (gMultipriseTelevision) ["Control", "Power"] {channel="tapocontrol:P300:1:2:actuator#output1",
scene="OFF=OFF, TV=ON, MUSIC=OFF, CINEMA=ON, IDLE=ON"}
Switch Multiprise02_Output2 "Switch Arc" <PowerOutlet> (gMultipriseTelevision) ["Control", "Power"] {channel="tapocontrol:P300:1:2:actuator#output2",
scene="OFF=OFF, TV=ON, MUSIC=ON, CINEMA=ON, IDLE=OFF"}
Switch Multiprise02_Output3 "Switch Sub" <PowerOutlet> (gMultipriseTelevision) ["Control", "Power"] {channel="tapocontrol:P300:1:2:actuator#output3",
scene="OFF=OFF, TV=OFF, MUSIC=ON, CINEMA=ON, IDLE=OFF"}
The following rule will:
- Identify all items holding the scene metadata, group them by location
- Create a scene action item in the location with appropriate description
- The script makes the assomption that location group name is named gSomeWhere and will name the scene trigger SomeWhere_SCN
- Associate a nice name with the scene raw name (TV => Télévision) for the state description and commands on the scene action item
- Create a rule that will trigger whenever a scene receives a command and send according commands to items
- Monitor scene participants items to identify the appropriate scene whenever one changes.
scenes.rb
# frozen_string_literal: true
require 'json'
LIBELLE = { 'ON' => 'Allumé', 'OFF' => 'Eteint', 'IDLE' => 'En attente', 'CINEMA' => 'Cinéma',
'MUSIC' => 'Musique', 'TV' => 'Télévision' }.freeze
def options_to_hash(options)
options = "{\"#{options}\"}".gsub('=', '":"').gsub(',', '","')
result = {}
JSON.parse(options).each { |k, v| result[k.strip] = v.strip }
result
end
# Get all items holding the 'scene' metadata
SCENE_PARTICIPANTS = items.sort_by(&:name).reject do |item|
item.metadata[:scene].nil?
end
SCENE_PARTICIPANTS = SCENE_PARTICIPANTS.map do |item|
{item: item, location: item.location.name, options: options_to_hash(item.metadata[:scene].value)}
end
logger.debug "SCENE_PARTICIPANTS: #{SCENE_PARTICIPANTS}"
SCENES = Hash.new { |hash, key| hash[key] = {} }
SCENE_PARTICIPANTS.each do |participant|
location = participant[:location]
participant[:options].each do |scene_name, state|
SCENES[location][scene_name] = {} unless SCENES[location].key?(scene_name)
SCENES[location][scene_name][participant[:item]] = state
end
end
# SCENES: {"gSalon"=>{"OFF"=>{"LG_TV_Power"=>"OFF", "Multiprise02_Output1"=>"OFF", "Multiprise02_Output2"=>"OFF", "Multiprise02_Output3"=>"OFF", "Tapo01_Output"=>"OFF", "Tapo05_Output"=>"OFF"},
# "TV"=>{"LG_TV_Power"=>"ON", "Multiprise02_Output1"=>"ON", "Multiprise02_Output2"=>"ON", "Multiprise02_Output3"=>"OFF", "Tapo01_Output"=>"OFF", "Tapo05_Output"=>"OFF"},
# "MUSIC"=>{"LG_TV_Power"=>"OFF", "Multiprise02_Output1"=>"OFF", "Multiprise02_Output2"=>"ON", "Multiprise02_Output3"=>"ON", "Tapo01_Output"=>"ON", "Tapo05_Output"=>"ON"},
# "CINEMA"=>{"LG_TV_Power"=>"ON", "Multiprise02_Output1"=>"ON", "Multiprise02_Output2"=>"ON", "Multiprise02_Output3"=>"ON", "Tapo01_Output"=>"ON", "Tapo05_Output"=>"ON"},
# "IDLE"=>{"Multiprise02_Output1"=>"ON", "Multiprise02_Output2"=>"OFF", "Multiprise02_Output3"=>"OFF", "Tapo01_Output"=>"OFF", "Tapo05_Output"=>"OFF"}},
# "gAtelier"=>{"OFF"=>{"MagicLum1_Output"=>"OFF", "MagicLum2_Output"=>"OFF"},
# "ON"=>{"MagicLum1_Output"=>"ON", "MagicLum2_Output"=>"ON"},
# "MIXTE"=>{"MagicLum1_Output"=>"ON", "MagicLum2_Output"=>"OFF"},
# "MIXTE2"=>{"MagicLum1_Output"=>"OFF", "MagicLum2_Output"=>"ON"}},
logger.debug "SCENES: #{SCENES}"
def array_to_options(arr)
options = ''
arr.each do |elmt|
trad = LIBELLE[elmt]
options += "#{elmt}=#{trad.nil? ? elmt : trad},"
end
options.chop
end
def create_scene_item(place, scenes)
scene_item = items.build do
string_item "#{place.name[1..]}_SCN", "🎉 #{place.label}",
tags: ['Scene', Semantics::Status],
group: place.name,
icon: 'housemode'
end
scene_item.metadata[:stateDescription] = ' ', { options: array_to_options(scenes) }
scene_item.metadata[:autoupdate] = false
place.metadata[:linked_scene] = scene_item.name
scene_item
end
ALL_SCENES = []
SCENES.each do |location, scenes|
ALL_SCENES << create_scene_item(items[location], scenes.keys)
end
rule 'A scene was triggered' do
received_command(*ALL_SCENES)
run do |event|
location = event.item.location.name
scene_name = event.command.to_s
participants = SCENES[location][scene_name]
if participants.nil?
logger.warn "No actions #{scene_name} for #{location} found"
else
participants.each do |k, v|
logger.debug "'#{k.name}' will be positionned to '#{v}'"
k.ensure << v
sleep 0.2
end
end
end
end
SCENES.each do |location, loc_detail|
rule "Monitor scene participants for #{location}", id: "scene_monitoring_#{location}" do
changed(*loc_detail.values[0].keys, attach: location)
debounce_for(2.seconds)
run do |event|
location = event.attachment
scene_item = items[items[location].metadata[:linked_scene].value]
logger.debug "A scene participant of #{location} state's has changed"
result = SCENES[location].find do |_scene, composition|
composition.all? do |member, state|
member.state.to_s == state
end
end
break if result.nil? || result.empty?
logger.debug "Target scene found: #{result[0]}"
scene_item.update result[0] if scene_item.state.to_s != result[0]
end
end
end
Example of scene item created by the script:
I’m still not a ruby guru but making progresses I guess.