Thank you Nautilus -- I wasn't sure if Domoticz already provided some built-in solution, but your suggestion of storing state in a user variable is a good one. I've gone ahead and implemented this in a very general way and it works REALLY WELL! The LUA routines are attached below if perhaps useful to others:
Code: Select all
-- ==================================================
-- Support for Distinguishing Manual Switch Presses
-- ==================================================
-- These routines allow LUA code to distinguish manual switch presses
-- from identical looking device events that were initiated by the
-- controller. For each device that needs such help simply create a
-- string user variable having the exact same name prefixed with
-- 'MSW-' to hold the state info that will be needed to discern the
-- two events. If you have a switch named 'MySwitch' then create a
-- user variable named 'MSW-MySwitch'.
--
-- The newly created user variable can be empty at first and will be
-- filled in with a single letter flag ('m' or 'a') indicating when a
-- controller activation was just requested, followed by the os.time
-- of the last manual switch press that was seen.
-- Helper function to get (flag,time) contents of user variable that
-- is paired with a given device name. Returns (nil,nil) if the user
-- variable either is not defined or is not yet formatted properly.
--
local mswPrefix = 'MSW-'
local mswOneYear = 31536000
local function mswGetVariableContents( device )
local flag, time
local userVar = uservariables[ mswPrefix..device ]
if( userVar ) then
_,_,flag,time = string.find( userVar, "(%a)(%d+)" )
end
return flag,time
end
-- ------------------------------
-- Return TRUE if the switch device was just manually pressed
--
function mswSwitchWasManuallyPressed( device )
if( devicechanged and devicechanged[device] ) then
local flag,time = mswGetVariableContents( device )
if( flag and time and flag == 'm' ) then return true end
end
return false
end
-- ------------------------------
-- Return number of seconds since we last saw a manual switch press on
-- the given device, or one year if we haven't seen one at all.
--
function mswAgeOfLastManualPress( device )
local flag,time = mswGetVariableContents( device )
if( flag and time ) then return ( os.time() - time ) end
return mswOneYear
end
-- ------------------------------
-- Function to be called at the end of every LUA script that might be
-- commanding a switch transition whose manual status we later want to
-- check. The global commandArray{} is both read and modified here in
-- order to possibly modify the associated user variables.
--
function mswUpdateDetectionVars()
local device, command
-- First update the user variables for every tracked device that
-- has just changed. The flag is always set back to 'manual'
-- (since it has done its work already), and the time will either
-- be updated to the present if we just saw a manual push, else
-- left unchanged.
--
if( devicechanged ) then
for device,command in pairs( devicechanged ) do
if( uservariables[ mswPrefix..device ] ) then
local flag,time = mswGetVariableContents( device )
if( flag and time ) then
if( flag == 'm' ) then time = os.time() ; end
else
time = os.time() - mswOneYear -- Initialize new Var
end
commandArray['Variable:'..mswPrefix..device] = string.format( "m%d", time )
end
end
end
-- Then update the user variables for every tracked device whose
-- state is about to change as a result of a controller-based
-- (non-manual) command. Set the 'auto' flag and preserve whatever
-- previous time was there already.
--
for device,command in pairs( commandArray ) do
if( uservariables[ mswPrefix..device ] ) then
local flag,time = mswGetVariableContents( device )
if( time == nil ) then
time = os.time() - mswOneYear -- Initialize new Var
end
commandArray['Variable:'..mswPrefix..device] = string.format( "a%d", time )
end
end
end
Simply define a string user variable named 'MSW-MySwitch' for each 'MySwitch' device you want to monitor, and put the update routine at the very end of every script that might control such switches. You can then call mswSwitchWasManuallyPressed() to detect manual button presses in your device scripts, or mswAgeOfLastManualPress() in any script to tell how long it's been since a person last pressed the button with their finger.
I've already put this to several uses. It allows a dumb switch to function as a poor man's scene controller by LUA doing something different when you physically enter/leave the room versus other automated things that might be happening with that same switch. The following code now turns on a secondary light when we enter our bedroom, and turns off both lights plus a fan upon exiting. Meanwhile, our MiniMote still functions as always as a general scene controller while we're in the room.
Code: Select all
-- Wall switch at the door turns on extra light upon entry, and turns
-- everything off upon exit.
--
if( mswSwitchWasManuallyPressed( 'BED3-WallSwitch' ) ) then
local swState = otherdevices[ 'BED3-WallSwitch' ]
commandArray['BED3-FloorLamp'] = swState
if( swState == 'Off' ) then
commandArray['BED3-FloorFan'] = 'Off'
end
end
Another application is a light that is turned On by a PIR sensor in a device script, and Off by a timeout in a time script. When you leave the room and manually turn off the light you can now inhibit the PIR sensor for perhaps 20 seconds so that the light won't turn back on a moment later before you make it out. I had originally been testing the lastupdate globals associated with the switch to allow time to leave, but the problem is that those times get updated by the automatic turn-Off -- so if the lights turn Off while you're still in the room you have to wait 20-seconds before waving your hands will turn them back on. With the new setup you get free passage out of the room plus instant-On if they happen to turn Off while you're still in there.
One Bug/Feature is that switch transitions initiated from the Domoticz floorplan (or through the JSON interface) now count as manual button presses. This is desirable or not, depending on how you think of it.