The main reasons to do this are:
- I already needed a dummy switch to activate the scene from Homekit
- I want to know the "real" state of the scene. Domoticz only looks if the devices are 'On' and not to the given setpoint. When I have a scene which also sets a light off the state will be 'Mixed'. In my opinion 'Mixed' is just off. I'm only interested if a scene is active or not. And active means all devices are in the state as defined in the scene, so On/Off, dim level, color, etc.
- For some scenes I want to be able to switch them off. This basically means switch every device off, so no invert of the scene.
(Basically I want the Homekit way of working with scene's)
- To set the security state I need a script that performs a post activate action. I want to set security state in the goodnight and good morning scene
I have finished my first version of the script. It now contains the following functionality:
- I can configure multiple scenes in the same script. (The SCENES table on top)
- Every scene uses a dummy scene switch
- switches, dimmers, Tradfri and Philips Hue with color temperature are now supported
- When I set the lights manually to the correct setpoints the scene switch automatically switches on
- I can set if it's allowed to switch off the state.
- A scene can have an automatic change of dim level and/or color temperature during time
Todo list:
- Add timer trigger
I'm already happy with the script although I don't have it in production yet. Need to include the security feature first.
If anyone have any comments then please don't hesitate
This is my first more advanced and generic script, so probably the are plenty of improvements.
Code: Select all
-- Scene Control version 1.0 (2019-12-08)
-- deviceConfig options per devicetype:
-- Light/Switch : state
-- level 0-100
-- Color Switch : state
-- level 0-100
-- r, g, b 0-255 (always in combination) not working correct yet
-- temp 0-255 or AUTO (always in combination with level)
-- Security Panel: state
idx_checkScenes = idx_sceneswitch_checkScenes
AUTO = -99
DEVICESCENE_MANUAL = '###DeviceSceneManual###'
SCENES = {
{
name = 'Avond',
idxSceneSwitch = idx_sceneswitch_avond,
idxSceneSwitchNoOff = idx_sceneswitch_avondAlleenAan,
deactivateAllowed = true,
deviceConfigs = {
[idx_woonkamer_eettafel] = {state = 'Off'},
[idx_woonkamer_zithoek] = {state = 'On'},
[idx_woonkamer_spotsUitbouw] = {level = AUTO, temp = AUTO},
[idx_woonkamer_dressoir] = {level = AUTO, temp = AUTO},
[idx_woonkamer_ledstrip] = {level = 100, temp = AUTO},
[idx_achtertuin_winterlichtjes] = {state = 'On'},
[idx_woonkamer_kerstboom] = {state = 'On'},
},
autoSetpoints = {
{time = 'between 0:00 and 7:00', temp = 232, level = 20},
{time = 'between 7:00 and 7:05', temp = 224, level = 22},
{time = 'between 7:05 and 7:10', temp = 216, level = 24},
{time = 'between 7:10 and 7:15', temp = 208, level = 26},
{time = 'between 7:15 and 7:20', temp = 200, level = 28},
{time = 'between 7:20 and 7:25', temp = 192, level = 30},
{time = 'between 7:25 and 7:30', temp = 184, level = 30},
{time = 'between 7:30 and 7:35', temp = 176, level = 30},
{time = 'between 7:35 and 7:40', temp = 168, level = 30},
{time = 'between 7:40 and 7:45', temp = 160, level = 30},
{time = 'between 7:45 and 7:50', temp = 152, level = 30},
{time = 'between 7:50 and 7:55', temp = 144, level = 30},
{time = 'between 7:55 and 8:00', temp = 136, level = 30},
{time = 'between 8:00 and 19:00', temp = 128, level = 30},
{time = 'between 19:00 and 19:15', temp = 135, level = 30},
{time = 'between 19:15 and 19:30', temp = 142, level = 30},
{time = 'between 19:30 and 19:45', temp = 149, level = 30},
{time = 'between 19:45 and 20:00', temp = 156, level = 30},
{time = 'between 20:00 and 20:15', temp = 163, level = 30},
{time = 'between 20:15 and 20:30', temp = 170, level = 30},
{time = 'between 20:30 and 20:45', temp = 177, level = 30},
{time = 'between 20:45 and 21:00', temp = 184, level = 30},
{time = 'between 21:00 and 21:15', temp = 191, level = 30},
{time = 'between 21:15 and 21:30', temp = 198, level = 30},
{time = 'between 21:30 and 21:45', temp = 205, level = 30},
{time = 'between 21:45 and 22:00', temp = 212, level = 30},
{time = 'between 22:00 and 22:15', temp = 219, level = 30},
{time = 'between 22:15 and 22:30', temp = 226, level = 30},
{time = 'between 22:30 and 23:00', temp = 232, level = 25},
{time = 'between 23:00 and 0:00', temp = 232, level = 20}
},
},
{
name = 'Welterusten',
idxSceneSwitch = idx_sceneswitch_welterusten,
deactivateAllowed = false,
deviceConfigs = {
[idx_woonkamer_eettafel] = {state = 'Off'},
[idx_woonkamer_zithoek] = {state = 'Off'},
[idx_woonkamer_spotsUitbouw] = {state = 'Off'},
[idx_woonkamer_dressoir] = {state = 'Off'},
[idx_woonkamer_leeslamp] = {state = 'Off'},
[idx_keuken_aanrechtverlichting]= {state = 'Off'},
[idx_achtertuin_zithoek] = {level = 1},
[idx_alarm_securityPanel] = {state = 'Armed Home'},
[idx_woonkamer_ledstrip] = {state = 'Off'},
[idx_achtertuin_winterlichtjes] = {state = 'Off'},
[idx_woonkamer_kerstboom] = {state = 'Off'},
},
},
{
name = 'Goedemorgen',
idxSceneSwitch = idx_sceneswitch_goedemorgen,
deactivateAllowed = false,
deviceConfigs = {
[idx_alarm_securityPanel] = {state = 'Disarmed'},
[idx_woonkamer_kerstboom] = {state = 'On'},
},
},
{
name = 'Woonkamer uit',
idxSceneSwitch = idx_sceneswitch_woonkamerUit,
deactivateAllowed = false,
deviceConfigs = {
[idx_woonkamer_eettafel] = {state = 'Off'},
[idx_woonkamer_zithoek] = {state = 'Off'},
[idx_woonkamer_spotsUitbouw] = {state = 'Off'},
[idx_woonkamer_dressoir] = {state = 'Off'},
[idx_woonkamer_ledstrip] = {state = 'Off'},
[idx_woonkamer_leeslamp] = {state = 'Off'},
[idx_achtertuin_winterlichtjes] = {state = 'Off'},
},
},
}
sceneControl = {
triggerDevices = function()
local tDevs = {}
tDevs[idx_checkScenes] = idx_checkScenes
for _, scene in ipairs(SCENES) do
tDevs[scene.idxSceneSwitch] = scene.idxSceneSwitch
if scene.idxSceneSwitchNoOff ~= nil then
tDevs[scene.idxSceneSwitchNoOff] = scene.idxSceneSwitchNoOff
end
for idx, _ in pairs(scene.deviceConfigs) do
tDevs[idx] = idx
end
end
return(tDevs)
end,
GetAutoTemp = function(dz, scene)
local i = 1
local currentTemp = 0
while scene.autoSetpoints[i] ~= nil and currentTemp == 0 do
if dz.time.matchesRule(scene.autoSetpoints[i].time) then
currentTemp = scene.autoSetpoints[i].temp
end
i = i + 1
end
return currentTemp
end,
GetAutoLevel = function(dz, scene)
local i = 1
local currentLevel = 0
while scene.autoSetpoints[i] ~= nil and currentLevel == 0 do
if dz.time.matchesRule(scene.autoSetpoints[i].time) then
currentLevel = scene.autoSetpoints[i].level
end
i = i + 1
end
return currentLevel
end,
activateScene = function(dz, scene, allowOff)
dz.log('Activate scene "' .. scene.name .. '"')
for idx, deviceConfig in pairs(scene.deviceConfigs) do
local dev = dz.devices(idx)
dz.data['deviceScene'][idx] = scene.name
dz.log('"' .. dev.name .. '" is of type: ' ..dev.deviceType)
dev.cancelQueuedCommands()
if dev.deviceType == 'Security' then
if deviceConfig.state ~= nil and dev.state ~= deviceConfig.state then
dz.log('set state of "' .. dev.name .. '" to ' .. deviceConfig.state)
if deviceConfig.state == 'Armed Home' then
dev.armHome().silent()
elseif deviceConfig.state == 'Armed Away' then
dev.armAway().silent()
elseif deviceConfig.state == 'Disarmed' then
dev.disarm().silent()
end
end
elseif dev.deviceType == 'Color Switch' then
if deviceConfig.r ~= nil and deviceConfig.g ~= nil and deviceConfig.b ~= nil then
dz.log('set color of "' .. dev.name .. '" to rgb ' .. deviceConfig.r .. ',' .. deviceConfig.g .. ',' .. deviceConfig.b)
dev.setColor(deviceConfig.r, deviceConfig.g, deviceConfig.b, deviceConfig.level, 0, 0, 3, 0)
elseif deviceConfig.temp ~= nil then
local newTemp = deviceConfig.temp
if newTemp == AUTO then
newTemp = sceneControl.GetAutoTemp(dz, scene)
end
local newLevel = deviceConfig.level
if newLevel == AUTO then
newLevel = sceneControl.GetAutoLevel(dz, scene)
end
dz.log('set color temp of "' .. dev.name .. '" to ' .. newTemp)
dev.setColor(0,0,0,newLevel,0,0,2,newTemp).silent()
dev.dimTo(newLevel).silent()
elseif deviceConfig.state ~= nil and dev.state ~= deviceConfig.state then
if allowOff or deviceConfig.state ~= 'Off' then
dz.log('set state of "' .. dev.name .. '" to ' .. deviceConfig.state)
dev.setState(deviceConfig.state).silent()
end
end
elseif dev.deviceType == 'Light/Switch' then
if deviceConfig.state ~= nil and dev.state ~= deviceConfig.state then
if allowOff or deviceConfig.state ~= 'Off' then
dz.log('set state of "' .. dev.name .. '" to ' .. deviceConfig.state)
dev.setState(deviceConfig.state).silent()
end
end
if deviceConfig.level ~= nil then
local newLevel = deviceConfig.level
if newLevel == AUTO then
newLevel = sceneControl.GetAutoLevel(dz, scene)
end
dz.log('set level of "' .. dev.name .. '" to level ' .. newLevel)
dev.dimTo(newLevel).silent()
end
end
end
end,
updateAutoSetpoints = function(dz, scene)
dz.log('update auto setpoints for scene "' .. scene.name .. '"')
for idx, deviceConfig in pairs(scene.deviceConfigs) do
local dev = dz.devices(idx)
local deviceScene = dz.data['deviceScene'][idx]
if deviceScene == nil then deviceScene = DEVICESCENE_MANUAL end
if deviceScene == scene.name then
dz.log('"' .. dev.name .. '" is of type: ' ..dev.deviceType)
if dev.deviceType == 'Color Switch' then
local isAuto = false
if deviceConfig.temp ~= nil then
local newTemp = deviceConfig.temp
if newTemp == AUTO then
newTemp = sceneControl.GetAutoTemp(dz, scene)
isAuto = true
end
local newLevel = deviceConfig.level
if newLevel == AUTO then
newLevel = sceneControl.GetAutoLevel(dz, scene)
isAuto = true
end
if isAuto then
dz.log('Update setpoints of "' .. dev.name .. '" to temp: ' .. newTemp .. ', level: newLevel')
dev.cancelQueuedCommands()
dev.setColor(0,0,0,newLevel,0,0,2,newTemp).silent()
dev.dimTo(newLevel).silent()
end
end
elseif dev.deviceType == 'Light/Switch' then
local isAuto = false
if deviceConfig.level ~= nil then
local newLevel = deviceConfig.level
if newLevel == AUTO then
newLevel = sceneControl.GetAutoLevel(dz, scene)
isAuto = true
end
if isAuto then
dz.log('Update setpoints of "' .. dev.name .. '" to level: newLevel')
dev.cancelQueuedCommands()
dev.dimTo(newLevel).silent()
end
end
end
end
end
end,
deactivateScene = function(dz, scene)
dz.log('Deactivate scene "' .. scene.name .. '"')
for idx, deviceConfig in pairs(scene.deviceConfigs) do
local dev = dz.devices(idx)
dz.data['deviceScene'][idx] = DEVICESCENE_MANUAL
dz.log('"' .. dev.name .. '" is of type: ' ..dev.deviceType)
dev.cancelQueuedCommands()
if dev.deviceType == 'Security' then
if deviceConfig.state ~= nil and dev.state ~= 'Disarmed' then
dz.log('set state of "' .. dev.name .. '" to ' .. deviceConfig.state)
dev.disarm().silent()
end
elseif dev.deviceType == 'Light/Switch' or dev.deviceType == 'Color Switch' then
if dev.active then
dz.log('Switch off "' .. dev.name .. '"')
dev.switchOff().silent()
end
end
end
end,
checkScene = function(dz, scene)
dz.log('Check scene "' .. scene.name .. '"')
local active = true
local activeNoOff = true
for idx, deviceConfig in pairs(scene.deviceConfigs) do
local dev = dz.devices(idx)
local deviceScene = dz.data['deviceScene'][idx]
if deviceScene == nil then deviceScene = DEVICESCENE_MANUAL end
dz.log('"' .. dev.name .. '" is of type: ' ..dev.deviceType, dz.LOG_DEBUG)
if dev.deviceType == 'Light/Switch' or dev.deviceType == 'Color Switch' or dev.deviceType == 'Security' then
if deviceConfig.state ~= nil then
local state = dev.state
if state == 'Set Color' then state = 'On' end
local stateCheck = state == deviceConfig.state
if stateCheck == false then dz.log('Scene is inactive because of "' .. dev.name .. '" state = ' .. dev.state .. ' setpoint = ' .. deviceConfig.state) end
active = active and stateCheck
if deviceConfig.state ~= 'Off' then
activeNoOff = activeNoOff and stateCheck
end
end
if deviceConfig.level ~= nil then
local levelSetpoint = deviceConfig.level
if levelSetpoint == AUTO then
active = active and deviceScene == scene.name and dev.active
activeNoOff = activeNoOff and deviceScene == scene.name and dev.active
if deviceScene ~= scene.name then dz.log('Scene is inactive because device "' .. dev.name .. '" has an auto setpoint for scene: ' .. deviceScene) end
else
local levelCheck = dev.levelVal >= levelSetpoint - 1 and dev.levelVal <= levelSetpoint + 1
if levelCheck == false then dz.log('Scene is inactive because of "' .. dev.name .. '" level = ' .. dev.levelVal .. ' setpoint = ' .. levelSetpoint) end
active = active and levelCheck
activeNoOff = activeNoOff and levelCheck
end
end
end
if dev.deviceType == 'Color Switch' then
if deviceConfig.r ~= nil and deviceConfig.g ~= nil and deviceConfig.b ~= nil then
local color = dev.getColor()
local colorCheck = color.r == deviceConfig.r and color.g == deviceConfig.g and color.b == deviceConfig.b
if colorCheck == false then dz.log('Scene is inactive because of "' .. dev.name .. '" RGB = ' .. color.r .. ',' .. color.g .. ',' .. color.b .. ' setpoint = ' .. deviceConfig.r .. ',' .. deviceConfig.g .. ',' .. deviceConfig.b) end
active = active and colorCheck
activeNoOff = activeNoOff and colorCheck
end
if deviceConfig.temp ~= nil then
local tempSetpoint = deviceConfig.temp
if tempSetpoint == AUTO then
active = active and deviceScene == scene.name and dev.active
activeNoOff = activeNoOff and deviceScene == scene.name and dev.active
if deviceScene ~= scene.name then dz.log('Scene is inactive because device "' .. dev.name .. '" has an auto setpoint for scene: ' .. deviceScene) end
else
local color = dev.getColor()
local tempCheck = color.t >= tempSetpoint - 1 and color.t <= tempSetpoint + 1
if tempCheck == false then dz.log('Scene is inactive because of "' .. dev.name .. '" temp = ' .. color.t .. ' setpoint = ' .. tempSetpoint) end
active = active and tempCheck
activeNoOff = activeNoOff and tempCheck
end
end
end
if active == false and activeNoOff == false then break end
end
local sceneSwitch = dz.devices(scene.idxSceneSwitch)
if active then
dz.log('Set scene switch to On')
sceneSwitch.switchOn().checkFirst().silent()
else
dz.log('Set scene switch to Off')
sceneSwitch.switchOff().checkFirst().silent()
end
if scene.idxSceneSwitchNoOff ~= nil then
local sceneSwitchNoOff = dz.devices(scene.idxSceneSwitchNoOff)
if activeNoOff then
dz.log('Set NoOff scene switch to On')
sceneSwitchNoOff.switchOn().checkFirst().silent()
else
dz.log('Set NoOff scene switch to Off')
sceneSwitchNoOff.switchOff().checkFirst().silent()
end
end
end,
}
return {
on = {
devices = sceneControl.triggerDevices(),
timer = {
'every 5 minutes'
}
},
logging = {
-- level = domoticz.LOG_DEBUG,
-- level = domoticz.LOG_INFO,
level = domoticz.LOG_ERROR,
marker = "Scene Control"
},
data = {
deviceScene = {initial = {}},
},
execute = function(dz, item, info)
dz.log('Triggered by '..(item.isDevice and ('device: '..item.name..', device state is: '..item.state) or (item.isTimer and ('timer: '..item.trigger))), dz.LOG_INFO)
if item.isDevice then
for _, scene in ipairs(SCENES) do
local idx_noOff = scene.idxSceneSwitchNoOff
if idx_noOff == nil then idx_noOff = 0 end
if item.idx == scene.idxSceneSwitch or item.idx == idx_noOff then
dz.log('scene switch for scene ' .. scene.name .. ' triggered --> switch scene on/off')
if item.state == 'On' then
sceneControl.activateScene(dz, scene, item.idx == scene.idxSceneSwitch)
else
if scene.deactivateAllowed then
sceneControl.deactivateScene(dz, scene)
else
dz.log('Deactivate not allowed')
item.switchOn().silent()
end
end
elseif item.idx == idx_checkScenes then
if dz.devices(idx_checkScenes).active then
sceneControl.checkScene(dz, scene)
dz.devices(idx_checkScenes).switchOff().silent()
end
else
dz.devices(idx_checkScenes).cancelQueuedCommands()
dz.devices(idx_checkScenes).switchOn().afterSec(2)
end
end
elseif item.isTimer then
for _, scene in ipairs(SCENES) do
sceneControl.updateAutoSetpoints(dz, scene)
end
end
end
}