Utility Script : Animate lights (blinking / transition ....)

Moderator: leecollings

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

Utility Script : Animate lights (blinking / transition ....)

Post by StephaneM60 »

Hi,

I'm sharing this DZVents scripts : a collection of functions to animate lights, from transitionning to blinking, alternating scenes...

For instance you want to be notified when someone comes home by having a light blinking you can do it like this. Note this is also
using my Restore State script to put back the light to the state it was before we start blinking it...

Code: Select all

return {
	on = {
		devices = { 'Phone Sensor' }
	},
	logging = {
		level = domoticz.LOG_INFO,
		marker = 'BLINKING TEST',
	},
	execute = function(domoticz, device)
	    
		if(device.isDevice) then
	        	domoticz.log('XXXX got home, blink our living room light', domoticz.LOG_INFO)
	        
			-- Store the state of the living room light and restore it after 35 sec.
	                domoticz.helpers.restoreStateAfter(domoticz, 'Living Room Light', 35)
			
			-- Start binking the living room light for 30 sec (here it blink off and red, every 0.5 sec
			-- the color change off / red / off / red ....)
	                domoticz.helpers.blink(domoticz, 'Living Room Light',
							{ ['r']=0,['g']=0,['b']=0,['br']=100,['cw']=0,['ww']=0,['t']=0,['m']=3 },
							{ ['r']=255,['g']=0,['b']=0,['br']=100,['cw']=0,['ww']=0,['t']=0,['m']=3 },
							0.5, 30)
	        
		end
        
	end
}
Multiple animations are possible :

Blinking
Transitionning to another color
Swapping colors of the lamps in a group : to create Xmas light effects and easy animation design thanks to the group / scene editor in domoticz)
Alternating scene / group : to create more complex animation.

To install this script, 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.

return {
	-- Variables Globales Persistentes
	data = { 
	         -- ANIMATE COLOR VARS --
	         cancelTransition = { initial = { [0] = nil } }
	         -- END ANIMATE COLOR VARS --
	         },

	-- Fonction Globales
	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,
        
        -- ANIMATE COLOR FUNC --
        --
        cap255 = function(x)
            if(x>255) then return 255 end
            if(x<=0) then return nil end
            return math.floor(x)
        end,
        
        cap100 = function(x)
            if(x>100) then return 100 end
            if(x<=0) then return nil end
            return math.floor(x)
        end,
        
        -- Transition a light to a new color over time
        -- device : light to be transitionned
        -- color : the new color and brightness : { ['r']=0, ['g']=0, ['b']=0, ['br']=0, ['cw']=0, ['ww']=0, ['t']=0, ['m']=1/2/3/4 }
        -- m = 1, white only br means something, br 0 to 100
        -- m = 2, white temp only t mean something, t 0 to 255
        -- m = 3, color only r,g,b mean something, rgb 0 to 255
        -- m = 4, extended color only r,g,b,cw,ww mean something, cw,ww,rgb 0 to 255
        -- duration : duration of the effect in seconds
        -- NOTE : if the light is off, no transition is done other
        -- than brightness (basically Off = color 0,0,0)
        transitionTo = function(domoticz, device, color, duration)
            -- compute the number of steps needed
            -- minimum duration : 1 sec.
            -- max. steps number : 30
            local timeLapse = 1
            local totalSteps = 30
            if(duration >= 30) then
                timeLapse = duration / 30
                totalSteps = 30
            else
                timeLapse = 1
                totalSteps = duration
            end
            
            -- creating array of steps
            local startColor = domoticz.helpers.getColor(domoticz, device)
            -- special case : device is off
            if(domoticz.devices(device).inActive) then
                startColor.r = 0
                startColor.g = 0 
                startColor.b = 0 
                startColor.cw = 0 
                startColor.ww = 0 
                startColor.t = 0 
                startColor.br = 0
            end
            -- filling the array will all steps states
            local colorSteps = { ['next'] = nil, ['color'] = color}
            for step = totalSteps,1,-1 do
                local stepColor = { ['r']=domoticz.helpers.cap255(startColor.r + (step * (color.r-startColor.r)/totalSteps)),
                                    ['g']=domoticz.helpers.cap255(startColor.g + (step * (color.g-startColor.g)/totalSteps)),
                                    ['b']=domoticz.helpers.cap255(startColor.b + (step * (color.b-startColor.b)/totalSteps)),
                                    ['br']=domoticz.helpers.cap100(startColor.br + (step * (color.br-startColor.br)/totalSteps)),
                                    ['cw']=domoticz.helpers.cap255(startColor.cw + (step * (color.cw-startColor.cw)/totalSteps)),
                                    ['ww']=domoticz.helpers.cap255(startColor.ww + (step * (color.ww-startColor.ww)/totalSteps)),
                                    ['t']=domoticz.helpers.cap255(startColor.t + (step * (color.t-startColor.t)/totalSteps)),
                                    ['m']=startColor.m }
                colorSteps = { ['next'] = colorSteps, ['color'] = stepColor}
            end
            
            -- start the transition effect
            domoticz.emitEvent('transitionState', { ['steps'] = colorSteps, ['delay'] = timeLapse, ['device'] = device })
        end,
        
        -- make a light blink
        -- device : idx or device name to blink
        -- color1 : first color { ['r']=0, ['g']=0, ['b']=0, ['br']=0, ['cw']=0, ['ww']=0, ['t']=0, ['m']=1/2/3/4 }
        -- color2 : other color (blink = alternate color1 and color2)
        -- if one of the color has br=0 or r=0 and g=0 and b=0 then light is turn off (blinking on / off)
        -- speed : in seconds time between each color change
        -- duration : total duration of the effect in seconds (if 60 : the light will blink for a minute)
        blink = function(domoticz, device, color1, color2, speed, duration)
            
            local colorSteps = { ['next'] = color2, ['color'] = color1}
            
            domoticz.emitEvent('alternateState', { ['steps'] = colorSteps, ['delay'] = speed, ['duration'] = duration, ['device'] = device })
        end,
        
        -- swap the colors of each light in a group scenes
        -- group : idx or name of group / scene
        -- speed : in seconds, time between each color change
        -- duration : total duration of the effect in seconds
        animateGroup = function(domoticz, group, speed, duration)
            -- create the list of colors and states of all device in group
            -- when the animation starts
            local colors = {}
            local devices = {}
            local count = 0
            
            local idx = nil
            if(domoticz.utils.sceneExists(group)) then idx = domoticz.scenes(group).idx
            elseif(domoticz.utils.groupExists(group)) then idx = domoticz.groups(group).idx end
            
            -- We have a valid group or scene
            if(idx) then
                -- get the devices and state in the group / scene
                local cmd='"C:\\Program Files (x86)\\Domoticz\\scripts\\lua\\curl.exe" '..domoticz.settings.url..'/json.htm?type=command^&param=getscenedevices^&isscene=true^&idx='..idx
                local httpRequest = assert(io.popen(cmd))
                local html = httpRequest:read('*all')
	            local json = domoticz.utils.fromJSON(html)
	            -- results are valid JSON
                if(json.status == 'OK') then
                    -- review each device and store its id and state (color / on/off...) in array
                    for i, device in ipairs(json.result) do
                        count = count + 1
                        -- device ID
                        devices[count] = device.DevID
                        -- device state : color / on/off / Level
                        if(device.Color) then device.Color = domoticz.utils.fromJSON(device.Color) end
                        if(device.Color) then device.Color.br = device.Level else device.Color = { ['br'] = device.Level } end
                        -- if device is not On then the device is off, we simplify by stating its brightness is 0
                        if(device.Command == "On") then colors[count] = device.Color else colors[count] = { ['br'] = 0 } end
                    end
                end
            end
            -- starting the transition
            domoticz.emitEvent('switchState', { ['devices'] = devices, ['colors'] = colors, ['step'] = 0, ['delay'] = speed, ['duration'] = duration })
        end,
        
        -- alternate scenes : change a scene each xx sec.
        -- scenes : scenes / groups array to alternat ex : {'Scene 1', 'Scene 2', 'Scene 3'}
        -- speed : in seconds, time between each scene change
        -- duration : total duration of the effect in seconds
        animateScenes = function(domoticz, scenes, speed, duration)
            
            domoticz.emitEvent('sceneChangeState', { ['scenes'] = scenes, ['step'] = 0, ['delay'] = speed, ['duration'] = duration })
        end,
        
        -- cancel a running transition
        -- work only for "transitionTo" et "blink"
        cancelTransition = function(domoticz, device)
            if(not domoticz.helpers.isNumber(device)) then device = domoticz.devices(device).id end
		    -- signal the transition to end early
		    domoticz.globalData.cancelTransition[device] = true
        end,
        --
        -- END ANIMATE COLOR 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 all the animations : in the script editor, click on "+" then select "DZVENTS" -> "CUSTOM EVENTS", give it the name you want instead of "Script #1", for instance "Transition State", copy and paste the code below and click on "SAVE".

Code: Select all

return {
	on = { customEvents = { 'transitionState', 'alternateState', 'switchState', 'sceneChangeState' } },
	logging = { level = domoticz.LOG_ERROR, 
	            marker = 'TRANSITION STATE', },
	
	execute = function(domoticz, item)
	    
	    -- TRANSITION
		if (item.isCustomEvent and item.trigger == 'transitionState') then
		    -- Get JSON in event data
		    local eventData = domoticz.utils.fromJSON(item.data)
		    
		    -- should we cancel our transition ?
		    local deviceId = domoticz.devices(eventData.device).id
		    if(domoticz.globalData.cancelTransition[deviceId]) then
		        domoticz.globalData.cancelTransition[deviceId] = nil
		        return
		    end
		   
		    -- color data for the transition
		    local currentColor = eventData.steps.color
		    local nextColorSteps = eventData.steps.next 
		    
		    -- checking colors values (we use nil instead of 0 
		    -- as it shortens the string for the custom event)
		    if(not currentColor.r) then currentColor.r = 0 end
		    if(not currentColor.g) then currentColor.g = 0 end
		    if(not currentColor.b) then currentColor.b = 0 end
		    if(not currentColor.br) then currentColor.br = 0 end
		    if(not currentColor.cw) then currentColor.cw = 0 end
		    if(not currentColor.ww) then currentColor.ww = 0 end
		    if(not currentColor.t) then currentColor.t = 0 end
		    
		    -- setting color / state
		    if(domoticz.devices(eventData.device).deviceType == 'Color Switch') then
		        domoticz.helpers.setColor(domoticz, eventData.device, 
		                                  currentColor.r,currentColor.g,currentColor.b,
		                                  currentColor.br,
		                                  currentColor.cw,currentColor.ww,
		                                  currentColor.m,currentColor.t)
		    elseif(domoticz.devices(eventData.device).switchType =='Dimmer') then
		        domoticz.devices(eventData.device).dimTo(currentColor.br)
		    end
		    
		    -- schedule next transition
		    if(nextColorSteps) then
		        domoticz.emitEvent('transitionState', { ['steps'] = nextColorSteps, ['delay'] = eventData.delay, ['device'] = eventData.device }).afterSec(eventData.delay)
		    end
	    end
	
	    -- BLINKING
	    if (item.isCustomEvent and item.trigger == 'alternateState') then
	        -- Get JSON in event data
		    local eventData = domoticz.utils.fromJSON(item.data)
	        -- should we cancel our transition ?
	        if(eventData.duration <= 0) then return end
	        local deviceId = domoticz.devices(eventData.device).id
		    if(domoticz.globalData.cancelTransition[deviceId]) then
		        domoticz.globalData.cancelTransition[deviceId] = nil
		        return
		    end
		    
		    -- color date for the transition
		    local currentColor = eventData.steps.color
		    local nextColorSteps = eventData.steps.next
		    
		    -- swappping
		    eventData.steps.color = nextColorSteps
		    eventData.steps.next = currentColor
		    
		    -- reducing duration
		    eventData.duration = eventData.duration - eventData.delay
		    
		    -- checking colors values (we use nil instead of 0 
		    -- as it shortens the string for the custom event)
		    if(not currentColor.r) then currentColor.r = 0 end
		    if(not currentColor.g) then currentColor.g = 0 end
		    if(not currentColor.b) then currentColor.b = 0 end
		    if(not currentColor.br) then currentColor.br = 0 end
		    if(not currentColor.cw) then currentColor.cw = 0 end
		    if(not currentColor.ww) then currentColor.ww = 0 end
		    if(not currentColor.t) then currentColor.t = 0 end
	        
	        -- setting color / state
	        if((currentColor.r == 0 and currentColor.g == 0 and currentColor.b == 0 and currentColor.m == 3) or
	           (currentColor.br == 0)) then
	            domoticz.devices(eventData.device).switchOff()
	        elseif(domoticz.devices(eventData.device).deviceType == 'Color Switch') then
		        domoticz.helpers.setColor(domoticz, eventData.device, 
		                                  currentColor.r,currentColor.g,currentColor.b,
		                                  currentColor.br,
		                                  currentColor.cw,currentColor.ww,
		                                  currentColor.m,currentColor.t)
		    elseif(domoticz.devices(eventData.device).switchType =='Dimmer') then
		        domoticz.devices(eventData.device).dimTo(currentColor.br)
		    end
	        
	        --scheduling next event
	        domoticz.emitEvent('alternateState', { ['steps'] = eventData.steps, ['delay'] = eventData.delay, ['duration'] = eventData.duration, ['device'] = eventData.device }).afterSec(eventData.delay)
	    end
	    
	    -- GROUP SWAPPING
	    if (item.isCustomEvent and item.trigger == 'switchState') then
	        -- Get JSON in event data
		    local eventData = domoticz.utils.fromJSON(item.data)
		    
		    -- should we cancel our transition ?
	        if(eventData.duration <= 0) then return end
	        
	        -- compute number of steps
	        local colorCount = 0
	        for i, color in ipairs(eventData.colors) do
	            colorCount = colorCount + 1
	        end
		    
		    -- setting color and state
		    -- enum devices and choose state based on step number
		    for i, deviceId in ipairs(eventData.devices) do
		        deviceId = tonumber(deviceId)
		        
	            if(eventData.step >= colorCount) then eventData.step = 0 end
		        local colorIndex = i + eventData.step
		        if(colorIndex > colorCount) then colorIndex = colorIndex - colorCount end
		        
		        if(eventData.colors[colorIndex].br == 0)then
		            -- turning off
		            domoticz.devices(deviceId).switchOff()
		        elseif(eventData.colors[colorIndex].m) then
		            -- set color if device is capable of color
		            if(domoticz.devices(deviceId).deviceType == 'Color Switch') then
		                domoticz.devices(deviceId).setColor(eventData.colors[colorIndex].r,eventData.colors[colorIndex].g,eventData.colors[colorIndex].b,
		                                                    eventData.colors[colorIndex].br,
		                                                    eventData.colors[colorIndex].cw,eventData.colors[colorIndex].ww,
		                                                    eventData.colors[colorIndex].m,eventData.colors[colorIndex].t)
		            else
		                -- at least set brightness or turn the device on...
		                if(domoticz.devices(deviceId).switchType =='Dimmer') then
		                    domoticz.devices(deviceId).Level = eventData.colors[colorIndex].br
		                else
		                    domoticz.devices(deviceId).switchOn()
		                end
		            end
		        else
		            -- set level / turning on
		            if(domoticz.devices(deviceId).switchType =='Dimmer') then
		                domoticz.devices(deviceId).Level = eventData.colors[colorIndex].br
		            else
		                domoticz.devices(deviceId).switchOn()
		            end
		        end
	        end
	        -- schedule next transition
	        domoticz.emitEvent('switchState', { ['devices'] = eventData.devices, ['colors'] = eventData.colors, ['step'] = eventData.step + 1, ['delay'] = eventData.delay, ['duration'] = eventData.duration - eventData.delay }).afterSec(eventData.delay)
	    end
	    
	    -- CHANGEMENT DE SCENE
	    if (item.isCustomEvent and item.trigger == 'sceneChangeState') then
	        -- Get JSON in event data
		    local eventData = domoticz.utils.fromJSON(item.data)
		    
		    -- should we cancel our transition ?
	        if(eventData.duration <= 0) then return end
	        
	        -- compute number of steps
	        local sceneCount = 0
	        for i, scene in ipairs(eventData.scenes) do
	            sceneCount = sceneCount + 1
	        end
	        
	        -- selecting scene to apply / group to turn on
	        if(eventData.step >= sceneCount) then eventData.step = 0 end
	        local sceneIndex = 1 + eventData.step
	        if(sceneIndex > sceneCount) then sceneIndex = sceneIndex - sceneCount end
	        -- applying
	        if(domoticz.utils.sceneExists(eventData.scenes[sceneIndex])) then
	            domoticz.scenes(eventData.scenes[sceneIndex]).switchOn()
	        elseif(domoticz.utils.groupExists(eventData.scenes[sceneIndex])) then
	            domoticz.groups(eventData.scenes[sceneIndex]).switchOn()
	        end
	        -- schedule next transition
	        domoticz.emitEvent('sceneChangeState', { ['scenes'] = eventData.scenes, ['step'] = eventData.step + 1, ['delay'] = eventData.delay, ['duration'] = eventData.duration - eventData.delay }).afterSec(eventData.delay)
	    end
	end
}

From now on you can use in your own scripts the following functions :

domoticz.helpers.transitionTo(domoticz, device, color, duration)
Transition a light to a new color over time. The light will fade from its current state to the new color. You can choose the duration of the transition (in seconds). Color is in the usual format { ['r']=0, ['g']=0, ['b']=0, ['br']=0, ['cw']=0, ['ww']=0, ['t']=0, ['m']=1/2/3/4 }.
You can do a transition to off also by using a black color or a brightness of 0.

domoticz.helpers.blink(domoticz, device, color1, color2, speed, duration)
Make a light blink. You can alternate between two colors with the speed and duration of your choice. If one color is black or brightness is zero, means the lamp will turn off. There is no smooth transition between colors.

domoticz.helpers.animateGroup(domoticz, group, speed, duration)
swap the colors of each light in a group scenes. You can choose the speed and duration of the effect

domoticz.helpers.animateScenes(domoticz, scenes, speed, duration)
alternate scenes : change a scene each xx sec. Again you can choose speed and duration

domoticz.helpers.cancelTransition(domoticz, device)
cancel a running transition for a device. It currently works only for blink and transitionTo.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest