Utility Script : Restoring State of Lights (or Switch)

Moderator: leecollings

Post Reply
StephaneM60
Posts: 12
Joined: Saturday 10 November 2018 12:47
Target OS: Windows
Domoticz version:
Contact:

Utility Script : Restoring State of Lights (or Switch)

Post by StephaneM60 »

Hi,

I'm sharing this DZVents scripts : a system to store and restore state of a light / switch (it should work with all lights and switch, let me know if something doesn't work for you)

For instance : you have your lights turned on in blue with low intensity; then you want to switch the lights on white with high intensity, because you opened a door, or because you triggered a motion sensor.

After some time you want to return the lights to their previous state : blue / low intensity (instead of simply turning the light off)

The script comes as global functions and a standalone custom event script.

The global functions are to be used in your own scripts.

For instance, you create a DZVents script for a motion sensor :

Code: Select all

return {
	on = {
		devices = { 'Motion Sensor' },
	},
	
	logging = {
		level = domoticz.LOG_INFO,
		marker = 'MOTION SENSOR TEST',
	},
	
	execute = function(domoticz, device)
	    -- Change the light to bright white for a minute
	    -- then restore its state
	    if(device.isDevice) then

		-- Store the current state and ask for restoring it after 60 sec.
		domoticz.helpers.restoreStateAfter(domoticz, 'My Light', 60)

		-- Change the color of the light (white / 100% luminosity)
                domoticz.devices('My Light').setColor(0,0,0,100,0,0,2,128)

	    end

	end
}
To install :

You'll need the CURL utility, you can download CURL here if you don't have it already installed on your system. CURL is available for many platforms (windows / linux).This is a command line utility to make http requests (and other), that will be used by the script to make synchronous http requests (DZVents does offer asynchronous requests, taht's why CURL is needed). Once you downloaded the binary, put it on the folder of your choice and take note of the path you put CURL, we will need it later.

You need to have the "global_data" DZVents scripts. If you don't have a "global_data" script, go into "Setup" -> "More Options" -> "Events", click on "+" and select "DZVENTS" -> "GLOBAL DATA". Instead of "Script #1" type "global_data" (without the quotes). You can now copy and paste the code below and click on "SAVE" (check in the domoticz log for any errors, sometimes on Windows you have to make sure Domoticz can save the scripts file correctly)

If you do have an existing "global_data" script, then you'll need to add the functions and global variables you'll find in the code below.

Code: Select all

-- this scripts holds all the globally persistent variables and helper functions
-- see the documentation in the wiki
-- NOTE:
-- THERE CAN BE ONLY ONE global_data SCRIPT in your Domoticz install. So if

return {
	-- Persistent Global Variables
	data = { 
	         -- RESTORE STATE VARS --
	         previousStates = { initial = { [0] = nil } },
	         -- END RESTORE STATE VARS

	         },

	-- Global Functions
	helpers = {

        -- Does the argument is a number
        -- x : the value to check
        isNumber = function (x)
            if tonumber(x) ~= nil then return true end
            return false
        end,
        
        -- get the color of a light
        -- deviceId : IDX or device name
        getColor = function(domoticz, deviceId)
            local idx = deviceId
            if(not domoticz.helpers.isNumber(deviceId)) then idx = domoticz.devices(deviceId).idx end
            cmd='"C:\\Program Files (x86)\\Domoticz\\scripts\\lua\\curl.exe" '..domoticz.settings.url..'/json.htm?type=devices^&rid='..idx
            local httpRequest = assert(io.popen(cmd))
            local html = httpRequest:read('*all')
	        local json = domoticz.utils.fromJSON(html)
	        -- adding brigthness to the color object
	        local color = domoticz.utils.fromJSON(json.result[1].Color)
	        color.br = domoticz.devices(deviceId).level
	        -- returning the result
            return color
        end,
	    
	    -- RESTORE STATE FUNC --
		
	    -- store the state of a light
	    -- domoticz : domoticz object
	    -- deviceName : device name
	    -- holdFor : validity of the state. if nil the state can be restored at anytime, otherwise state can be restored upto the "holdFor" duration (in seconds)
	    storeState = function(domoticz, deviceName, holdFor)
	        -- Get the device ID
	        local device = domoticz.devices(deviceName)
			local deviceId = device.id
			domoticz.log('Sotring state for device :'..deviceName, domoticz.LOG_INFO)
			-- time when the state will expire (ie : when it should be restored)
			local validTill
			local storeState = true
			local checkStates = false
			if(holdFor == nil) then validTill = nil else validTill = domoticz.time.addSeconds(holdFor).dDate end
			-- should we store this new state
			if(validTill ~= nil) then
			    -- we know when this state must end, we have
			    -- to check that the previous state should end
			    -- before this new one
			    local lastState = domoticz.globalData.previousStates[deviceId]
			    if(lastState ~= nil) then
			        if(lastState.value.validTill == nil) then
			            -- nok : our time is supposed to be past
			            -- the one already stored
			            -- we change the date of the stored state by
			            -- the date of the new state
			            lastState.value.validTill = validTill
			            storeState = false
			            checkStates = true
			        elseif(lastState.value.validTill > validTill) then
			            -- ok our date is past the date of the
			            -- already stored state
			            storeState = true
			        elseif(lastState.value.validTill <= validTill) then
			            -- nok our date is before the date of
			            -- the already stored state
			            -- we change the date of the stored state by
			            -- the one of the new state
			            lastState.value.validTill = validTill
			            storeState = false
			            checkStates = true
			        end
			    end
			else
			    -- we dont know the duration of the state we are storing
			    -- so we cannot store this state if one state is already stored
			    if(domoticz.globalData.previousStates[deviceId] ~= nil) then storeState = false end
			end
			
			-- checking if states are not conflicting
			while(checkStates) do
			    checkStates = false
			    local lastState = domoticz.globalData.previousStates[deviceId]
			    while(lastState.next ~= nil) do
			        if((lastState.next.value.validTill == nill) or
			           (lastState.next.value.validTill <= lastState.value.validTill)) then
			            -- current state will expire before next state
			            -- replacing expiration time of next state by current
			            lastState.next.value.validTill = lastState.value.validTill
			            -- deleting current state
			            lastState.next = lastState.next.next
			            lastState.value = lastState.next.value
			            domoticz.globalData.previousStates[deviceId] = lastState
			            -- need one more pass to check conflicts
			            checkStates = true
			        else
			            -- all is ok no need to continue
			            break
			        end 
			    end
			end
			
			if(storeState) then
			    -- storing state (on/off)
			    local currentState = { ['validTill'] = validTill, ['state'] = 'Off', ['color'] = nil }
			    currentState.state = device.state
		        -- storing color (when RGB+ device)
		        if(device.deviceType == 'Color Switch') then
		            local color = domoticz.helpers.getColor(domoticz, device.idx)
		            currentState.color = color
	            else
	                if(device.switchType == 'Dimmer') then
	                    --storing brightness level
	                    currentState.color = {['br'] = device.level }
	                end
                end
                -- push device state in the states linked list
                domoticz.log('Stored state for device '..deviceId..' : '..domoticz.utils.toJSON(currentState), domoticz.LOG_INFO)
                domoticz.globalData.previousStates[deviceId] = { next = domoticz.globalData.previousStates[deviceId], value = currentState }
            end
	    end,
	    
	    -- restore a device state (if a stored state exists)
	    -- deviceName : idx or device name to restore
	    restoreState = function(domoticz, deviceName)
	        -- get device ID
			local deviceId = domoticz.devices(deviceName).id
	        -- ask for device state restoration now (custom event)
	        domoticz.emitEvent('restoreState', deviceId)
	    end,
	    
	    -- store the current state (color / power) of light and schedule its restoration
	    -- deviceName = idx / device name (light or switch)
	    -- holdFor = time in seconds before restoring the state
		restoreStateAfter = function(domoticz, deviceName, holdFor)
		    -- get device ID
		    local deviceId = domoticz.devices(deviceName).id
		    -- store device state with "holdFor" lifetime
		    domoticz.helpers.storeState(domoticz, deviceName, holdFor)
	        -- schedule device restoration in "holdFor" sec.
	        domoticz.emitEvent('restoreState', deviceId).afterSec(holdFor)
		end,
		
		-- clear the stored state of a device, canceling its future restoration
	    -- this is useful to prevent light to turn back on when you want them
	    -- to stay off and don't know if a state is about to be restored.
		-- deviceName = idx or device name
		cancelRestoreState = function(domoticz, deviceName)
		    -- get device ID
		    local deviceId = domoticz.devices(deviceName).id
		    domoticz.log('Cancel stored states for device '..deviceId, domoticz.LOG_INFO)
		    -- clear all stored device states
		    domoticz.globalData.previousStates[deviceId] = nil
	    end
		-- END RESTORE STATE FUNC
	}
}

you'll have to edit the code to type the path to the CURL executable on your domoticz device, this in on line 30 in the script editor :

cmd='"C:\\Program Files (x86)\\Domoticz\\scripts\\lua\\curl.exe" '..domoticz.settings.url..'/json.htm?type=devices^&rid='..idx

Replace the current path with the path on your system.

Once this is done you can create the script that will hande the state restore : in the script editor, click on "+" then select "DZVENTS" -> "CUSTOM EVENTS", give it the name you want instead of "Script #1", for instance "Restore State", copy and paste the code below and click on "SAVE".

Code: Select all

return {
	on = { customEvents = { 'restoreState' } },
	
	logging = { level = domoticz.LOG_INFO, 
	            marker = 'RESTORE STATE', },
	        
	execute = function(domoticz, item)
	    
	    -- Restore State
	    if(item.isCustomEvent) then
	        
            -- Custom Event = restoreState
	        if(item.trigger == 'restoreState') then
	            -- Get the ID of the device to restore state
	            local deviceId = tonumber(item.data)
	            domoticz.log('Restore state for device '..item.data, domoticz.LOG_INFO)
	            
	            -- if the stored state had a validTill date
	            -- greater that the current time : we do nothing
	            -- because we expect to restore this state at a later time
	            local lastState = nil
	            if(domoticz.globalData.previousStates[deviceId]) then
	                lastState = domoticz.globalData.previousStates[deviceId].value
	                if(lastState.validTill ~= nil and
	                    lastState.validTill > domoticz.time.dDate) then
	                    domoticz.log('State restoration delayed', domoticz.LOG_INFO)
	                    return
	                end
	            end
	            -- we have a state to restore
	            if(lastState ~= nil) then
	                -- restoring previous state
	                domoticz.log('State to restore for device '..deviceId..' : '..domoticz.utils.toJSON(lastState), domoticz.LOG_INFO)
	                if(lastState.state ~= 'Off') then
	                    if(domoticz.devices(deviceId).deviceType == 'Color Switch' and lastState.color ~= nil) then
	                        -- color device : restoring color / white level as well as brightness
	                        domoticz.devices(deviceId).setColor(lastState.color.r, lastState.color.g, lastState.color.b,
	                                                            lastState.color.br,
	                                                            lastState.color.cw, lastState.color.ww,
	                                                            lastState.color.m, lastState.color.t)
                        else
                            -- restoring brightness only
	                        domoticz.devices(deviceId).switchOn()
	                        if(domoticz.devices(deviceId).switchType=='Dimmer' and lastState.color ~= nil) then
	                            domoticz.devices(deviceId).dimTo(lastState.color.br)
	                        end
	                    end
                    else
                        -- restoring power state only
	                    domoticz.devices(deviceId).switchOff()
	                end
	                -- clean up stored states
	                domoticz.globalData.previousStates[deviceId] = domoticz.globalData.previousStates[deviceId].next
	            else
	                domoticz.log('No stored state found', domoticz.LOG_INFO)
	            end
	       end
	      
	    end
	end
}
From now on you can use in your own scripts the following functions :

domoticz.helpers.storeState(domoticz, deviceName, holdFor)
To store the current state of a the device deviceName, holdFor is the time in seconds after which this stored state is supposed to be restored. For instance if you store the state of a light, because you'll change its luminosity for 1 minutes after a motion detection event, holdFor is 60 seconds. If you don't know when the state will be restored just use nil.

You can call this function multiple times, the script can store multiple states for a device. The script will decide if the state should be stored or not (that's why the holdFor variable is for)


domoticz.helpers.restoreState(domoticz, deviceName)
Restore the previous stored state of the device deviceName. Multiple state may be stored, the script choose the state to restore in the logical order (often last in, last out, but not always)

domoticz.helpers.restoreStateAfter(domoticz, deviceName, holdFor)
Store the current state of the device deviceName and restore it after holdFor seconds. Usually right after using this function, you'll actively change the state of the light (for instance turning it on, or changing its brightness / color)

domoticz.helpers.cancelRestoreState(domoticz, deviceName)
Erase all stored state for the device deviceName. This will prevent future restore of the state of this device if any was scheduled. It's mostely used when you turn off a device and you want to make sure it will stays off.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest