Page 1 of 1

Generic roller shutter script for multiple shutters

Posted: Sunday 08 December 2019 22:10
by ronaldbro
I created a generic script to controll all my roller shutters in my house. I didn't want a separate script for each shutter but I do want to let them behave a little different. So I made it generic and for each shutter I configure the behaviour.

'Generic' means generic in my house ;) Some tuning might be necessary depending on the situation.

Functionality per shutter:
- partly open when it's light and after a specific time per day
- open completely when it's light and after a specific time per day
- close when it's dark or at a specific time per day
- partly close on a bright day when the sun shines in my window
- keep the shutter closed when it really cold outside
- close the shutter when it's very hot and when it's cooler inside then outside
- open window detection (Do nothing while window is open)
- dummy switch can be used to set the shutter to manual mode
- dummy text device can be used to display the state of the shutter
- manual operation detection (Operated by wall switch or manually in Domoticz/HomeKit/...) In this case automation is suspended until Day->Night, Night->Day change or when manual mode switch is turned off

All functionalities can be enabled and configures per shutter.

Dependencies:
- Domoticz-SunMoon-Plugin
- variable DayNight which is set in another script. Possible values are defined in global_data constants.
- global_data entries:
CONST_DAGNACHT_DAG = 'DAG'
CONST_DAGNACHT_NACHT = 'NACHT'
SUN = 1 -- dz.time.wday returns 1 for sunday
MON = 2
TUE = 3
WED = 4
THU = 5
FRI = 6
SAT = 7

- All my device idx's are defined in my global_data, but also the IDXes numbers can be used.

At this moment the script runs without any problems. But it can always be improved. Every feedback is appreciated.

Code: Select all


-- Version: 1.3
-- Date: 04-05-2920

-- changelog
-- 1.0  initial Version
-- 1.1  for open en partly open use sunday time on public holidays
-- 1.2  idx_sunAltitude and idx_sunAzimuth are made optional
--      for close nighttime use Saturday time for night before public holiday
-- 1.3  Added switch for sleep late
-- 1.4  Added sunscreen functionality

local idx_outsideTemp     = idx_achtertuin_thb                      -- idx for outside temperasture
local idx_wind            = idx_achtertuin_wind                     -- idx for wind sensor
local idx_rain            = idx_achtertuin_regen                    -- idx for rain sensor
local idx_sunAltitude     = idx_sunmoon_sunAltitude                 -- idx for sunAltitude device (Domoticz-SunMoon plugin), nil =  don't use
local idx_sunAzimuth      = idx_sunmoon_sunAzimuth                  -- idx for sunAzimuth device (Domoticz-SunMoon plugin), nil =  don't use
local idxv_dagNacht       = var_idx_dagNacht                        -- idx for Day/Night variable

local dumpDeviceConfigToLog = false                                 -- set true to dump device config to log. Normally false
local WINDOW_SENSOR_VALID_TIME  = 360 --minutes
local TEMP_SENSOR_VALID_TIME    = 120 --minutes
local WIND_SENSOR_VALID_TIME    = 90 --minutes
local RAIN_SENSOR_VALID_TIME    = 90 --minutes
local COMMAND_EXECUTE_TIME      = 90 --seconds

SHUTTERS = {
    {
        name = 'Woonkamer',                                         -- Name of the shutter. Just for logging.
        enabled = true,                                             -- enable/disable controll of this shutter

        -- devices
        idx_controller          = idx_woonkamer_rolluik,            -- idx of shutter device
        idx_manualModeSwitch    = idx_woonkamer_rolluikManualMode,  -- idx of virtual switch. On = manual mode
        idx_windowSensor        = nil,                              -- idx of window sensor. Can be nil
        idx_roomTemp            = idx_woonkamer_thb,                -- idx of room temperature sensor
        idx_status              = idx_woonkamer_rolluikStatus,      -- idx of virtual text device for status messages
        idx_luxSensor_down      = idx_omgeving_lux_voorkant_avg,    -- idx of lux sensor for down (for example a dummy device with avg value of a lux sensor)
        idx_luxSensor_up        = idx_omgeving_lux_voorkant_max,    -- idx of lux sensor for up (for example a dummy device with max value of a lux sensor)
        idx_sleepLate           = nil,                              -- idx of virtual switch to indicate you want to sleep late tomorrow. Can be nil
        
        -- settings        
        supportsPercentage              = true,                     -- if true setpoints can be from 0 to 100, when false only up/down (>= 50 is up)
        manualOperatedDetection         = true,                      -- manual operation detection doesn't work for all shutters

        partlyOpenAtDaytime             = true,
        partlyOpenPercentage            = 30,

        openAtDaytime                   = true,

        closeAtNightTime                = true,
        openAtNightTime                 = false,

        partlyCloseWhenSunlight         = true,
        minSunAltitude                  = 0,
        maxSunAltitude                  = 75,
        minSunAzimuth                   = 30,
        maxSunAzimuth                   = 170,
        luxThresholdDown                = 45000,
        luxThresholdUp                  = 30000,
        partlyCloseSunlightPercentage   = 35,

        closeWhenColdOutside            = false,
        closeWhenColdOutsideThreshold   = 20,

        closeWhenHotOutside             = true,
        closeWhenHotOutsideThreshold    = 25,
        
        sleepLateClosedUntil            = nil,
        sleepLatePartlyClosedUntil      = nil,
        
        openWhenWind                    = false,
        maxWindSpeed                    = 40,
        openWhenRain                    = false,
        maxRain                         = 0.1,
        
        commandDelay                    = 0,             -- ms
        
        closedUntil = {
            [MON] = '07:00',
            [TUE] = '07:00',    
            [WED] = '07:00',    
            [THU] = '07:00',    
            [FRI] = '07:00',    
            [SAT] = '08:00',                        
            [SUN] = '08:00'
        },
        partlyClosedUntil = {
            [MON] = '08:30', 
            [TUE] = '08:30', 
            [WED] = '08:30', 
            [THU] = '08:30', 
            [FRI] = '08:30', 
            [SAT] = '09:00', 
            [SUN] = '10:00'
        },
        closeTime = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
    },
    {
        name = 'Screen achter',
        enabled = true,

        -- devices
        idx_controller          = idx_woonkamer_screenAchter,
        idx_manualModeSwitch    = idx_woonkamer_screenAchterManualMode,
        idx_windowSensor        = nil,
        idx_roomTemp            = nil,
        idx_status              = idx_woonkamer_screenAchterStatus,
        idx_luxSensor_down      = idx_omgeving_lux_achterkant_avg,
        idx_luxSensor_up        = idx_omgeving_lux_achterkant_max,
        idx_sleepLate           = nil,
        
        -- settings        
        supportsPercentage              = false,
        manualOperatedDetection         = true,

        partlyOpenAtDaytime             = false,
        partlyOpenPercentage            = 0,

        openAtDaytime                   = true,

        closeAtNightTime                = false,
        openAtNightTime                 = true,

        partlyCloseWhenSunlight         = true,
        minSunAltitude                  = 0,
        maxSunAltitude                  = 75,
        minSunAzimuth                   = 30,
        maxSunAzimuth                   = 170,
        luxThresholdDown                = 35000,
        luxThresholdUp                  = 20000,
        partlyCloseSunlightPercentage   = 0,

        closeWhenColdOutside            = false,
        closeWhenColdOutsideThreshold   = 20,

        closeWhenHotOutside             = false,
        closeWhenHotOutsideThreshold    = 25,
        
        sleepLateClosedUntil            = nil,
        sleepLatePartlyClosedUntil      = nil,

        openWhenWind                    = true,
        maxWindSpeed                    = 40,
        openWhenRain                    = true,
        maxRain                         = 0.1,
        
        commandDelay                    = 0,             -- ms
        
        closedUntil = {
            [MON] = '00:00',
            [TUE] = '00:00',    
            [WED] = '00:00',    
            [THU] = '00:00',    
            [FRI] = '00:00',    
            [SAT] = '00:00',                        
            [SUN] = '00:00'
        },
        partlyClosedUntil = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
        closeTime = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
    },
    {
        name = 'Screen zijkant',
        enabled = true,

        -- devices
        idx_controller          = idx_woonkamer_screenZijkant,
        idx_manualModeSwitch    = idx_woonkamer_screenZijkantManualMode,
        idx_windowSensor        = nil,
        idx_roomTemp            = nil,
        idx_status              = idx_woonkamer_screenZijkantStatus,
        idx_luxSensor_down      = idx_omgeving_lux_zijkant_avg,
        idx_luxSensor_up        = idx_omgeving_lux_zijkant_max,
        idx_sleepLate           = nil,
        
        -- settings        
        supportsPercentage              = false,
        manualOperatedDetection         = true,

        partlyOpenAtDaytime             = false,
        partlyOpenPercentage            = 0,

        openAtDaytime                   = true,

        closeAtNightTime                = false,
        openAtNightTime                 = true,

        partlyCloseWhenSunlight         = true,
        minSunAltitude                  = 0,
        maxSunAltitude                  = 75,
        minSunAzimuth                   = 30,
        maxSunAzimuth                   = 170,
        luxThresholdDown                = 35000,
        luxThresholdUp                  = 20000,
        partlyCloseSunlightPercentage   = 0,

        closeWhenColdOutside            = false,
        closeWhenColdOutsideThreshold   = 20,

        closeWhenHotOutside             = false,
        closeWhenHotOutsideThreshold    = 25,
        
        sleepLateClosedUntil            = nil,
        sleepLatePartlyClosedUntil      = nil,
        
        openWhenWind                    = true,
        maxWindSpeed                    = 40,
        openWhenRain                    = true,
        maxRain                         = 0.1,
        
        commandDelay                    = 0,             -- ms

        closedUntil = {
            [MON] = '00:00',
            [TUE] = '00:00',    
            [WED] = '00:00',    
            [THU] = '00:00',    
            [FRI] = '00:00',    
            [SAT] = '00:00',                        
            [SUN] = '00:00'
        },
        partlyClosedUntil = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
        closeTime = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
    },
    {
        name = 'Slaapkamer',
        enabled = true,

        -- devices
        idx_controller          = idx_slaapkamer_rolluik,
        idx_manualModeSwitch    = idx_slaapkamer_rolluikManualMode,
        idx_windowSensor        = idx_slaapkamer_raam,
        idx_roomTemp            = idx_slaapkamer_thb,
        idx_status              = idx_slaapkamer_rolluikStatus,
        idx_luxSensor_down      = idx_omgeving_lux_achterkantBoven_avg,
        idx_luxSensor_up        = idx_omgeving_lux_achterkantBoven_max,
        idx_sleepLate           = idx_slaapkamer_uitslapen,

        -- settings        
        supportsPercentage              = true,
        manualOperatedDetection         = true,

        partlyOpenAtDaytime             = true,
        partlyOpenPercentage            = 35,

        openAtDaytime                   = true,

        closeAtNightTime                = true,
        openAtNightTime                 = false,

        partlyCloseWhenSunlight         = true,
        minSunAltitude                  = 0,
        maxSunAltitude                  = 90,
        minSunAzimuth                   = 210,
        maxSunAzimuth                   = 350,
        luxThresholdDown                = 30000,
        luxThresholdUp                  = 15000,
        partlyCloseSunlightPercentage   = 35,

        closeWhenColdOutside            = true,
        closeWhenColdOutsideThreshold   = 0,

        closeWhenHotOutside             = true,
        closeWhenHotOutsideThreshold    = 24,
        
        sleepLateClosedUntil            = '10:30',
        sleepLatePartlyClosedUntil      = '11:00',
        
        openWhenWind                    = false,
        maxWindSpeed                    = 40,
        openWhenRain                    = false,
        maxRain                         = 0.1,
        
        commandDelay                    = 200,             -- ms

        closedUntil = {
            [MON] = '07:00',
            [TUE] = '07:00',    
            [WED] = '07:00',    
            [THU] = '07:00',    
            [FRI] = '07:00',    
            [SAT] = '08:00',    
            [SUN] = '09:00'
        },
        partlyClosedUntil = {
            [MON] = '08:30', 
            [TUE] = '08:30', 
            [WED] = '08:30', 
            [THU] = '08:30', 
            [FRI] = '08:30', 
            [SAT] = '09:00', 
            [SUN] = '10:30'
        },
        closeTime = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
    },
    {
        name = 'Floris',
        enabled = true,
        
        -- devices
        idx_controller          = idx_floris_rolluik,
        idx_manualModeSwitch    = idx_floris_rolluikManualMode,
        idx_windowSensor        = idx_floris_raam,
        idx_roomTemp            = idx_floris_thb,
        idx_status              = idx_floris_rolluikStatus,
        idx_luxSensor_down      = idx_omgeving_lux_voorkant_avg,
        idx_luxSensor_up        = idx_omgeving_lux_voorkant_max,
        idx_sleepLate           = idx_floris_uitslapen,
        
        -- settings        
        supportsPercentage              = true,
        manualOperatedDetection         = true,                      -- manual operation detection doesn't work for all shutters

        partlyOpenAtDaytime             = true,
        partlyOpenPercentage            = 35,

        openAtDaytime                   = true,
        openAtNightTime                 = false,

        closeAtNightTime                = true,

        partlyCloseWhenSunlight         = true,
        minSunAltitude                  = 10,
        maxSunAltitude                  = 75,
        minSunAzimuth                   = 30,
        maxSunAzimuth                   = 170,
        luxThresholdDown                = 30000,
        luxThresholdUp                  = 15000,
        partlyCloseSunlightPercentage   = 35,

        closeWhenColdOutside            = true,
        closeWhenColdOutsideThreshold   = 0,

        closeWhenHotOutside             = true,
        closeWhenHotOutsideThreshold    = 24,
        
        sleepLateClosedUntil            = '10:30',
        sleepLatePartlyClosedUntil      = '11:00',

        openWhenWind                    = false,
        maxWindSpeed                    = 40,
        openWhenRain                    = false,
        maxRain                         = 0.1,
        
        commandDelay                    = 400,             -- ms
        
        closedUntil = {
            [MON] = '07:00',
            [TUE] = '07:00',    
            [WED] = '07:00',    
            [THU] = '07:00',    
            [FRI] = '07:00',    
            [SAT] = '09:00',    
            [SUN] = '09:00'
        },
        partlyClosedUntil = {
            [MON] = '08:30', 
            [TUE] = '08:30', 
            [WED] = '08:30', 
            [THU] = '08:30', 
            [FRI] = '08:30', 
            [SAT] = '09:30', 
            [SUN] = '10:30'
        },
        closeTime = {
            [MON] = '19:45',
            [TUE] = '19:45',
            [WED] = '19:45',
            [THU] = '19:45',
            [FRI] = '20:15',
            [SAT] = '20:15',
            [SUN] = '19:45'
        },
    },
    {
        name = 'Olivier',
        enabled = true,
        
        -- devices
        idx_controller          = idx_olivier_rolluik,
        idx_manualModeSwitch    = idx_olivier_rolluikManualMode,
        idx_windowSensor        = nil,
        idx_roomTemp            = idx_olivier_thb,
        idx_status              = idx_olivier_rolluikStatus,
        idx_luxSensor_down      = idx_omgeving_lux_voorkant_avg,
        idx_luxSensor_up        = idx_omgeving_lux_voorkant_max,
        idx_sleepLate           = idx_olivier_uitslapen,
        
        -- settings        
        supportsPercentage              = true,
        manualOperatedDetection         = true,                      -- manual operation detection doesn't work for all shutters

        partlyOpenAtDaytime             = true,
        partlyOpenPercentage            = 35,

        openAtDaytime                   = true,

        closeAtNightTime                = true,
        openAtNightTime                 = false,

        partlyCloseWhenSunlight         = true,
        minSunAltitude                  = 10,
        maxSunAltitude                  = 75,
        minSunAzimuth                   = 30,
        maxSunAzimuth                   = 170,
        luxThresholdDown                = 30000,
        luxThresholdUp                  = 15000,
        partlyCloseSunlightPercentage   = 35,

        closeWhenColdOutside            = true,
        closeWhenColdOutsideThreshold   = 0,

        closeWhenHotOutside             = true,
        closeWhenHotOutsideThreshold    = 24,
        
        sleepLateClosedUntil            = '10:30',
        sleepLatePartlyClosedUntil      = '11:00',

        openWhenWind                    = false,
        maxWindSpeed                    = 40,
        openWhenRain                    = false,
        maxRain                         = 0.1,
        
        commandDelay                    = 600,             -- ms
        
        closedUntil = {
            [MON] = '07:00',
            [TUE] = '07:00',    
            [WED] = '07:00',    
            [THU] = '07:00',    
            [FRI] = '07:00',    
            [SAT] = '08:00',    
            [SUN] = '09:00'
        },
        partlyClosedUntil = {
            [MON] = '08:30', 
            [TUE] = '08:30', 
            [WED] = '08:30', 
            [THU] = '08:30', 
            [FRI] = '08:30', 
            [SAT] = '09:00', 
            [SUN] = '10:30'
        },
        closeTime = {
            [MON] = '19:15',
            [TUE] = '19:15',
            [WED] = '19:15',
            [THU] = '19:15',
            [FRI] = '19:45',
            [SAT] = '19:45',
            [SUN] = '19:15'
        },
    },
    {
        name = 'kantoor',
        enabled = true,
       
        -- devices
        idx_controller          = idx_kantoor_rolluik,
        idx_manualModeSwitch    = idx_kantoor_rolluikManualMode,
        idx_windowSensor        = idx_kantoor_raam,
        idx_roomTemp            = idx_kantoor_thb,
        idx_status              = idx_kantoor_rolluikStatus,
        idx_luxSensor_down      = idx_omgeving_lux_achterkantBoven_avg,
        idx_luxSensor_up        = idx_omgeving_lux_achterkantBoven_max,
        idx_sleepLate           = nil,
        
        -- settings        
        supportsPercentage              = true,
        manualOperatedDetection         = true,                      -- manual operation detection doesn't work for all shutters

        partlyOpenAtDaytime             = false,
        partlyOpenPercentage            = 35,

        openAtDaytime                   = false,
        openAtNightTime                 = false,

        closeAtNightTime                = true,

        partlyCloseWhenSunlight         = false,
        minSunAltitude                  = 10,
        maxSunAltitude                  = 75,
        minSunAzimuth                   = 210,
        maxSunAzimuth                   = 350,
        luxThresholdDown                = 35000,
        luxThresholdUp                  = 20000,
        partlyCloseSunlightPercentage   = 35,

        closeWhenColdOutside            = false,
        closeWhenColdOutsideThreshold   = 0,

        closeWhenHotOutside             = false,
        closeWhenHotOutsideThreshold    = 24,
        
        sleepLateClosedUntil            = nil,
        sleepLatePartlyClosedUntil      = nil,
    
        openWhenWind                    = false,
        maxWindSpeed                    = 40,
        openWhenRain                    = false,
        maxRain                         = 0.1,
        
        commandDelay                    = 800,             -- ms
    
        closedUntil = {
            [MON] = '07:00',
            [TUE] = '07:00',    
            [WED] = '07:00',    
            [THU] = '07:00',    
            [FRI] = '07:00',    
            [SAT] = '08:00',    
            [SUN] = '09:00'
        },
        partlyClosedUntil = {
            [MON] = '08:30', 
            [TUE] = '08:30', 
            [WED] = '08:30', 
            [THU] = '08:30', 
            [FRI] = '08:30', 
            [SAT] = '09:00', 
            [SUN] = '10:30'
        },
        closeTime = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
    },
    {
        name = 'Badkamer',
        enabled = true,
        
        -- devices
        idx_controller          = idx_badkamer_rolluik,
        idx_manualModeSwitch    = idx_badkamer_rolluikManualMode,
        idx_windowSensor        = idx_badkamer_raam,
        idx_roomTemp            = idx_badkamer_thb,
        idx_status              = idx_badkamer_rolluikStatus,
        idx_luxSensor_down      = idx_omgeving_lux_voorkant_avg,
        idx_luxSensor_up        = idx_omgeving_lux_voorkant_max,
        idx_sleepLate           = nil,

        -- settings        
        supportsPercentage              = true,
        manualOperatedDetection         = true,                      -- manual operation detection doesn't work for all shutters

        partlyOpenAtDaytime             = true,
        partlyOpenPercentage            = 20,

        openAtDaytime                   = false,
        openAtNightTime                 = false,

        closeAtNightTime                = true,

        partlyCloseWhenSunlight         = false,
        minSunAltitude                  = 10,
        maxSunAltitude                  = 75,
        minSunAzimuth                   = 30,
        maxSunAzimuth                   = 170,
        luxThresholdDown                = 35000,
        luxThresholdUp                  = 20000,
        partlyCloseSunlightPercentage   = 20,

        closeWhenColdOutside            = true,
        closeWhenColdOutsideThreshold   = 0,

        closeWhenHotOutside             = true,
        closeWhenHotOutsideThreshold    = 24,
        
        sleepLateClosedUntil            = nil,
        sleepLatePartlyClosedUntil      = nil,
        
        openWhenWind                    = false,
        maxWindSpeed                    = 40,
        openWhenRain                    = false,
        maxRain                         = 0.1,
        
        commandDelay                    = 1000,             -- ms

        closedUntil = {
            [MON] = '07:00',
            [TUE] = '07:00',    
            [WED] = '07:00',    
            [THU] = '07:00',    
            [FRI] = '07:00',    
            [SAT] = '08:00',    
            [SUN] = '09:00'
        },
        partlyClosedUntil = {
            [MON] = nil, 
            [TUE] = nil, 
            [WED] = nil, 
            [THU] = nil, 
            [FRI] = nil, 
            [SAT] = nil, 
            [SUN] = nil
        },
        closeTime = {
            [MON] = nil,
            [TUE] = nil,
            [WED] = nil,
            [THU] = nil,
            [FRI] = nil,
            [SAT] = nil,
            [SUN] = nil
        },
    }
}

-------------------------------------------------------------
-- Configuration ends here
-- Start of script
-------------------------------------------------------------

local lastSeenCallback = 'RolluikenDevicesLastSeenCallback'

shutterControl = {
     triggerDevices = function()
    	local tDevs = {}
    	for _, shutter in ipairs(SHUTTERS) do
            if shutter.idx_manualModeSwitch ~= nil then tDevs[shutter.idx_manualModeSwitch] = shutter.idx_manualModeSwitch end
            if shutter.idx_windowSensor ~= nil then tDevs[shutter.idx_windowSensor] = shutter.idx_windowSensor end
    	end
    	return(tDevs)
    end,

     allDevices = function()
    	local tDevs = {}
    	tDevs[idx_outsideTemp]  = idx_outsideTemp
    	tDevs[idx_wind]         = idx_wind
    	tDevs[idx_rain]         = idx_rain
    	tDevs[idx_outsideTemp]  = idx_outsideTemp
        tDevs[idx_sunAltitude]  = idx_sunAltitude
        tDevs[idx_sunAzimuth]   = idx_sunAzimuth

    	for _, shutter in ipairs(SHUTTERS) do
            if shutter.idx_manualModeSwitch ~= nil then tDevs[shutter.idx_manualModeSwitch] = shutter.idx_manualModeSwitch end
            if shutter.idx_windowSensor ~= nil then tDevs[shutter.idx_windowSensor] = shutter.idx_windowSensor end
            if shutter.idx_roomTemp ~= nil then tDevs[shutter.idx_roomTemp] = shutter.idx_roomTemp end
            if shutter.idx_luxSensor_down ~= nil then tDevs[shutter.idx_luxSensor_down] = shutter.idx_luxSensor_down end
            if shutter.idx_luxSensor_up ~= nil then tDevs[shutter.idx_luxSensor_up] = shutter.idx_luxSensor_up end
            if shutter.idx_sleepLate ~= nil then tDevs[shutter.idx_sleepLate] = shutter.idx_sleepLate end
    	end
    	return(tDevs)
    end,
    
    dumpDeviceConfig = function(dz)
        local tmpName
        
    	dz.log('General devices')
        if idx_outsideTemp ~= nil then tmpName = dz.devices(idx_outsideTemp).name else tmpName = 'not set' end
	    dz.log('.....Outside temp.: ' .. tmpName)

        if idx_rain ~= nil then tmpName = dz.devices(idx_rain).name else tmpName = 'not set' end
	    dz.log('.....rain.........: ' .. tmpName)

        if idx_wind ~= nil then tmpName = dz.devices(idx_wind).name else tmpName = 'not set' end
	    dz.log('.....Wind.........: ' .. tmpName)

        if idx_sunAltitude ~= nil then tmpName = dz.devices(idx_sunAltitude).name else tmpName = 'not set' end
	    dz.log('.....Sun altitude.: ' .. tmpName)

        if idx_sunAzimuth ~= nil then tmpName = dz.devices(idx_sunAzimuth).name else tmpName = 'not set' end
	    dz.log('.....Sun azimuth..: ' .. tmpName)

    	for _, shutter in ipairs(SHUTTERS) do
    	    dz.log('Shutter name...........: ' .. shutter.name)
            tmpName = dz.devices(shutter.idx_controller).name
    	    dz.log('.....Controller........: ' .. tmpName)
            
            if shutter.idx_manualModeSwitch ~= nil then tmpName = dz.devices(shutter.idx_manualModeSwitch).name else tmpName = 'not set' end
    	    dz.log('.....Manual switch.....: ' .. tmpName)
        
            if shutter.idx_windowSensor ~= nil then tmpName = dz.devices(shutter.idx_windowSensor).name else tmpName = 'not set' end
    	    dz.log('.....Window sensor.....: ' .. tmpName)

            if shutter.idx_roomTemp ~= nil then tmpName = dz.devices(shutter.idx_roomTemp).name else tmpName = 'not set' end
    	    dz.log('.....Room temp.........: ' .. tmpName)

            if shutter.idx_status ~= nil then tmpName = dz.devices(shutter.idx_status).name else tmpName = 'not set' end
    	    dz.log('.....Status device.....: ' .. tmpName)

            if shutter.idx_luxSensor_down ~= nil then tmpName = dz.devices(shutter.idx_luxSensor_down).name else tmpName = 'not set' end
    	    dz.log('.....Lux down..........: ' .. tmpName)

            if shutter.idx_luxSensor_up ~= nil then tmpName = dz.devices(shutter.idx_luxSensor_up).name else tmpName = 'not set' end
    	    dz.log('.....Lux up............: ' .. tmpName)

            if shutter.idx_sleepLate ~= nil then tmpName = dz.devices(shutter.idx_sleepLate).name else tmpName = 'not set' end
    	    dz.log('.....Sleep late switch.: ' .. tmpName)
    	end
    end,

    isExecuting = function(dz, shutter)
        local Time = require('Time')
        dz.log(shutter.name .. ' lastSetpointSend = ' .. tostring(dz.data.lastSetpointSend[shutter.idx_controller]))
        if dz.data.lastSetpoint[shutter.idx_controller] == nil then
            dz.data.lastSetpoint[shutter.idx_controller] = dz.devices(shutter.idx_controller).level
        end
        if dz.data.lastSetpointSend[shutter.idx_controller] ~= nil then
            local lastSetpointSend = Time(dz.data.lastSetpointSend[shutter.idx_controller])
            dz.log(shutter.name .. ' lastSetpointSend is ' .. lastSetpointSend.secondsAgo .. ' seconds ago')
            return lastSetpointSend.secondsAgo < COMMAND_EXECUTE_TIME
        else
            return false
        end
    end,

    resetManualOperated = function(dz, shutter)
        dz.data.resetManualOperated[shutter.idx_controller] = true
        dz.data.manualOperated[shutter.idx_controller] = false
    end,

    shouldPartlyOpenForDaytime = function(dz, shutter)
        local result = shutter.partlyOpenAtDaytime
        local day = dz.time.wday
        
        if dz.helpers.isHolidayToday(dz) then
            day = SUN
        end
        local closedUntilTime = shutter.closedUntil[day]
        local partlyClosedUntilTime = shutter.partlyClosedUntil[day]
        
        if shutter.idx_sleepLate ~= nil and dz.devices(idx_sleepLate).active then
            if shutter.sleepLateClosedUntil ~= nil then
                closedUntilTime = shutter.sleepLateClosedUntil
            end
            if shutter.sleepLatePartlyClosedUntil ~= nil then
                partlyClosedUntilTime = shutter.sleepLatePartlyClosedUntil
            end
        end
        
        result = result and dz.variables(idxv_dagNacht).value == CONST_DAGNACHT_DAG
        
        if closedUntilTime ~= nil then
            if partlyClosedUntilTime ~= nil then
                result = result and dz.time.matchesRule('between ' .. closedUntilTime .. ' and ' .. partlyClosedUntilTime)
            else
                result = result and dz.time.matchesRule('between ' .. closedUntilTime .. ' and 23:59')
            end
        end
    
        if shutter.idx_sleepLate ~= nil and shutter.sleepLatePartlyClosedUntil == nil and dz.devices(idx_sleepLate).active and result then
            dz.devices(idx_sleepLate).switchOff()
        end
        
        return result
    end,

    shouldOpenForDaytime = function(dz, shutter)
        local result = shutter.openAtDaytime
        local day = dz.time.wday

        if dz.helpers.isHolidayToday(dz) then
            day = SUN
        end
        local partlyClosedUntilTime = shutter.partlyClosedUntil[day]
        
        if shutter.idx_sleepLate ~= nil and dz.devices(idx_sleepLate).active then
            if shutter.sleepLatePartlyClosedUntil ~= nil then
                partlyClosedUntilTime = shutter.sleepLatePartlyClosedUntil
            end
        end
        
        result = result and dz.variables(idxv_dagNacht).value == CONST_DAGNACHT_DAG

        if partlyClosedUntilTime ~= nil then
            result = result and dz.time.matchesRule('between ' .. partlyClosedUntilTime .. ' and 23:59')
        end

        if shutter.idx_sleepLate ~= nil and dz.devices(idx_sleepLate).active and result then
            dz.devices(idx_sleepLate).switchOff()
        end
        
        return result
    end,
    
    shouldCloseForNighttime = function(dz, shutter)
        local result = shutter.closeAtNightTime
        local day = dz.time.wday

        if dz.helpers.isHolidayTomorrow(dz) then
            day = SAT
        end

        result = result and dz.variables(idxv_dagNacht).value == CONST_DAGNACHT_NACHT

        if shutter.closeTime[day] ~= nil then
            result = result or dz.time.matchesRule('between ' .. shutter.closeTime[day] .. ' and 23:59')
        end
        return result
    end,

    shouldOpenForNighttime = function(dz, shutter)
        local result = shutter.openAtNightTime and shutter.closeAtNightTime == false
        local day = dz.time.wday

        if shutter.openAtNightTime and shutter.closeAtNightTime then
            dz.log(shutter.name .. ' Setting openAtNightTime and closeAtNightTime can not be both true.', dz.LOG_ERROR)
        end

        if dz.helpers.isHolidayTomorrow(dz) then
            day = SAT
        end

        result = result and dz.variables(idxv_dagNacht).value == CONST_DAGNACHT_NACHT

        if shutter.closeTime[day] ~= nil then
            result = result or dz.time.matchesRule('between ' .. shutter.closeTime[day] .. ' and 23:59')
        end
        return result
    end,
    
    shouldCloseForCold = function(dz, shutter)
        local result = shutter.closeWhenColdOutside
        local outsideTemp = dz.devices(idx_outsideTemp)
        local Time = require('Time')
        
        local lastUpdateStr = dz.data['lastUpdate'][idx_outsideTemp]
        
        if lastUpdateStr == nil then
            dz.log(shutter.name .. ' Temperature sensor "' .. outsideTemp.name .. '"has no lastUpdate, do not close for cold', dz.LOG_ERROR)
            result = false
        elseif Time(lastUpdateStr).minutesAgo > TEMP_SENSOR_VALID_TIME then
            if dz.data.notificationSend[outsideTemp.idx] == false then
                dz.data.notificationSend[outsideTemp.idx] = true
                dz.log(shutter.name .. ' Value of temperature sensor "' .. outsideTemp.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '. Do not close for cold', dz.LOG_ERROR)
            else
                dz.log(shutter.name .. ' Value of temperature sensor "' .. outsideTemp.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '. Do not close for cold')
            end
            result = false
        else
            dz.data.notificationSend[outsideTemp.idx] = false
            dz.log(shutter.name .. ' Outside temperature = ' .. tostring(outsideTemp.temperature) .. ', Threshold value = ' .. tostring(shutter.closeWhenColdOutsideThreshold))
            result = result and outsideTemp.temperature < shutter.closeWhenColdOutsideThreshold
        end
        return result
    end,
    
    shouldCloseForHotOutside = function(dz, shutter)
        if shutter.idx_roomTemp == nil then
            dz.log(shutter.name .. ' idx_roomTemp is nil')
            return false
        end

        local result = shutter.closeWhenColdOutside
        local outsideTemp = dz.devices(idx_outsideTemp)
        local roomTemp = dz.devices(shutter.idx_roomTemp)
        local Time = require('Time')
        
        local lastUpdateStrOutsideTemp = dz.data['lastUpdate'][idx_outsideTemp]
        local lastUpdateStrRoomTemp = dz.data['lastUpdate'][shutter.idx_roomTemp]
        
        if lastUpdateStrOutsideTemp == nil then
            dz.log(shutter.name .. ' Temperature sensor "' .. outsideTemp.name .. '"has no lastUpdate, do not close for hot outside', dz.LOG_ERROR)
            result = false
        elseif lastUpdateStrRoomTemp == nil then
            dz.log(shutter.name .. ' Temperature sensor "' .. roomTemp.name .. '"has no lastUpdate, do not close for hot outside', dz.LOG_ERROR)
            result = false
        elseif Time(lastUpdateStrOutsideTemp).minutesAgo > TEMP_SENSOR_VALID_TIME then
            if dz.data.notificationSend[outsideTemp.idx] == false then
                dz.data.notificationSend[outsideTemp.idx] = true
                dz.log(shutter.name .. ' Value of temperature sensor "' .. outsideTemp.name .. '"too old, lastUpdate = ' .. lastUpdateStrOutsideTemp .. '. Do not close for hot outside', dz.LOG_ERROR)
            else
                dz.log(shutter.name .. ' Value of temperature sensor "' .. outsideTemp.name .. '"too old, lastUpdate = ' .. lastUpdateStrOutsideTemp .. '. Do not close for hot outside')
            end
            result = false
        elseif Time(lastUpdateStrRoomTemp).minutesAgo > TEMP_SENSOR_VALID_TIME then
            if dz.data.notificationSend[roomTemp.idx] == false then
                dz.data.notificationSend[roomTemp.idx] = true
                dz.log(shutter.name .. ' Value of temperature sensor "' .. roomTemp.name .. '"too old, lastUpdate = ' .. lastUpdateStrRoomTemp .. '. Do not close for hot outside', dz.LOG_ERROR)
            else
                dz.log(shutter.name .. ' Value of temperature sensor "' .. roomTemp.name .. '"too old, lastUpdate = ' .. lastUpdateStrRoomTemp .. '. Do not close for hot outside')
            end
            result = false
        else
            dz.data.notificationSend[outsideTemp.idx] = false
            dz.data.notificationSend[roomTemp.idx] = false
            dz.log(shutter.name .. ' Outside temperature = ' .. tostring(outsideTemp.temperature) .. ', Threshold value = ' .. tostring(shutter.closeWhenColdOutsideThreshold))
            result = result and outsideTemp.temperature > shutter.closeWhenHotOutsideThreshold and outsideTemp.temperature >= roomTemp.temperature
        end
        return result
    end,
    
    shouldCloseForSunlight = function(dz, shutter)
        if idx_sunAltitude == nil or idx_sunAzimuth == nil or shutter.idx_luxSensor_up == nil or shutter.idx_luxSensor_down == nil then
            dz.log(shutter.name .. ' required devices for shouldCloseForSunlight are nil')
            return false
        end
        
        local result = shutter.partlyCloseWhenSunlight
        local logstring = ''

        if idx_sunAltitude ~= nil then
            local sunAltitude = dz.devices(idx_sunAltitude)
            result = result and tonumber(sunAltitude.sValue) > shutter.minSunAltitude and tonumber(sunAltitude.sValue) < shutter.maxSunAltitude
            logstring = logstring .. 'sunAltitude = ' .. sunAltitude.sValue .. ' '
        end
        if idx_sunAzimuth ~= nil then
            local sunAzimuth = dz.devices(idx_sunAzimuth)
            result = result and tonumber(sunAzimuth.sValue) > shutter.minSunAzimuth and tonumber(sunAzimuth.sValue) > shutter.minSunAzimuth
            logstring = logstring .. ' sunAzimuth = ' .. sunAzimuth.sValue .. ' '
        end

        dz.log(shutter.name .. ' ' .. logstring .. ' lux_up = ' .. tostring(dz.devices(shutter.idx_luxSensor_up).lux) .. ' lux_down = ' .. tostring(dz.devices(shutter.idx_luxSensor_down).lux))

        if dz.data.closedForSun[shutter.idx_controller] then
            result = result and dz.devices(shutter.idx_luxSensor_up).lux > shutter.luxThresholdUp
        else
            result = result and dz.devices(shutter.idx_luxSensor_down).lux > shutter.luxThresholdDown
        end
        dz.data.closedForSun[shutter.idx_controller] = result
            
        return result
    end,
    
    shouldOpenForWind = function(dz, shutter)
        if idx_wind == nil then
            dz.log(shutter.name .. ' required devices for shouldOpenForWind are nil')
            return false
        end
        
        local result = shutter.openWhenWind
        local wind = dz.devices(idx_wind)
        local Time = require('Time')
        
        local lastUpdateStr = dz.data['lastUpdate'][idx_wind]
        
        if lastUpdateStr == nil then
            dz.log(shutter.name .. ' Wind sensor "' .. wind.name .. '"has no lastUpdate.', dz.LOG_ERROR)
            result = false
        elseif Time(lastUpdateStr).minutesAgo > WIND_SENSOR_VALID_TIME then
            if dz.data.notificationSend[wind.idx] == false then
                dz.data.notificationSend[wind.idx] = true
                dz.log(shutter.name .. ' Value of wind sensor "' .. wind.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '.', dz.LOG_ERROR)
            else
                dz.log(shutter.name .. ' Value of wind sensor "' .. wind.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '.')
            end
            result = false
        else
            dz.data.notificationSend[wind.idx] = false
            dz.log(shutter.name .. ' wind speed = ' .. tostring(wind.speed) .. ', Threshold value = ' .. tostring(shutter.maxWindSpeed))
            result = result and wind.speed > shutter.maxWindSpeed
        end
        return result
    end,

    shouldOpenForRain = function(dz, shutter)
        if idx_rain == nil then
            dz.log(shutter.name .. ' required devices for shouldOpenForRain are nil')
            return false
        end
        
        local result = shutter.openWhenRain
        local rain = dz.devices(idx_rain)
        local Time = require('Time')
        
        local lastUpdateStr = dz.data['lastUpdate'][idx_rain]
        
        if lastUpdateStr == nil then
            dz.log(shutter.name .. ' Rain sensor "' .. rain.name .. '"has no lastUpdate.', dz.LOG_ERROR)
            result = false
        elseif Time(lastUpdateStr).minutesAgo > RAIN_SENSOR_VALID_TIME then
            if dz.data.notificationSend[rain.idx] == false then
                dz.data.notificationSend[rain.idx] = true
                dz.log(shutter.name .. ' Value of rain sensor "' .. rain.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '.', dz.LOG_ERROR)
            else
                dz.log(shutter.name .. ' Value of rain sensor "' .. rain.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '.')
            end
            result = false
        else
            dz.data.notificationSend[rain.idx] = false
            dz.log(shutter.name .. ' rain rate = ' .. tostring(rain.rainRate) .. ', Threshold value = ' .. tostring(shutter.maxRain))
            result = result and rain.rainRate > shutter.maxRain
        end
        return result
    end,

    updateStatus = function(dz, idx_status, text)
        if idx_status ~= nil then
            local statusDev = dz.devices(idx_status)
            if statusDev.text ~= text then
                statusDev.updateText(text)      
            end
        end
    end,
    
    doWorkLoop = function(dz, shutter)
        dz.log(shutter.name .. ' Starting workloop')
        local desiredSetpoint = 999
        local isNight = dz.variables(idxv_dagNacht).value == CONST_DAGNACHT_NACHT
        local isDay = dz.variables(idxv_dagNacht).value == CONST_DAGNACHT_DAG
        local Time = require('Time')
        local controller = dz.devices(shutter.idx_controller)

        -- Check first if any action is allowed
        if shutter.enabled == false then
            dz.log(shutter.name .. ' Shutter configuration is disabled, do nothing')
            shutterControl.updateStatus(dz, shutter.idx_status, 'Shutter configuration is disabled.')
            return
        end

        if shutter.idx_manualModeSwitch ~= nil and dz.devices(shutter.idx_manualModeSwitch).active then
            dz.log(shutter.name .. ' Manual mode switch is active, do nothing')
            shutterControl.updateStatus(dz, shutter.idx_status, 'Manual mode switch is active.')
            return
        end
        
        if dz.data.manualOperated[shutter.idx_controller] then
            dz.log(shutter.name .. ' Shutter is manual operated, do nothing')
            return
        end
    
        if shutterControl.isExecuting(dz, shutter) then
            dz.log(shutter.name .. ' Shutter is executing previous command, do nothing')
            return
        end
        
        if dz.data.resetManualOperated[shutter.idx_controller] == nil or dz.data.resetManualOperated[shutter.idx_controller] then
            dz.log(shutter.name .. ' Reset manual mode.')
            dz.data.resetManualOperated[shutter.idx_controller] = false    
            dz.data.lastSetpoint[controller.idx] = controller.level
        elseif shutter.supportsPercentage and shutter.manualOperatedDetection and controller.level ~= dz.data.lastSetpoint[shutter.idx_controller] then
            dz.log(shutter.name .. ' Manual mode detected. Controller level = ' .. controller.level .. ' and last setpoint = ' .. tostring(dz.data.lastSetpoint[shutter.idx_controller]))
            shutterControl.updateStatus(dz, shutter.idx_status, 'Shutter is manual operated.')
            dz.data.manualOperated[shutter.idx_controller] = true
            return
        elseif shutter.supportsPercentage == false and ((dz.data.lastSetpoint[shutter.idx_controller] >= 50 and dz.data.lastSetpoint[shutter.idx_controller] <= 100 and controller.state ~= 'Open') or (dz.data.lastSetpoint[shutter.idx_controller] < 50 and controller.state ~= 'Closed')) then
            dz.log(shutter.name .. ' Manual mode detected. Controller state = ' .. controller.state .. ' and last setpoint = ' .. tostring(dz.data.lastSetpoint[shutter.idx_controller]))
            shutterControl.updateStatus(dz, shutter.idx_status, 'Shutter is manual operated.')
            dz.data.manualOperated[shutter.idx_controller] = true
            return
        end

        if shutter.idx_windowSensor ~= nil then
            
            local lastUpdateStr = dz.data['lastUpdate'][shutter.idx_windowSensor]
            local windowSensor = dz.devices(shutter.idx_windowSensor)

            if windowSensor.active then
                dz.log(shutter.name .. ' Window is open, do nothing')
                shutterControl.updateStatus(dz, shutter.idx_status, 'Window is open.')
                return
            elseif lastUpdateStr == nil then
                dz.log(shutter.name .. ' Windows sensor "' .. windowSensor.name .. '"has no lastUpdate, abort work loop for shutter ' .. shutter.name, dz.LOG_ERROR)
                shutterControl.updateStatus(dz, shutter.idx_status, 'Windows sensor "' .. windowSensor.name .. '"has no lastUpdate')
                return
            elseif Time(lastUpdateStr).minutesAgo > WINDOW_SENSOR_VALID_TIME then
                if dz.data.notificationSend[windowSensor.idx] == false then
                    dz.data.notificationSend[windowSensor.idx] = true
                    dz.log('Value of windows sensor "' .. windowSensor.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '. Abort work loop for shutter ' .. shutter.name, dz.LOG_ERROR)
                else
                    dz.log('Value of windows sensor "' .. windowSensor.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '. Abort work loop for shutter ' .. shutter.name)
                end
                shutterControl.updateStatus(dz, shutter.idx_status, 'Value of windows sensor "' .. windowSensor.name .. '"too old, lastUpdate = ' .. lastUpdateStr .. '.')
                return
            end
            dz.data.notificationSend[windowSensor.idx] = false
        end

        -- determine desired setpoint
        -- lowest setpoint should be send to shutter
        local newStatus

        if shutterControl.shouldCloseForNighttime(dz, shutter) then
            dz.log(shutter.name .. ' shouldCloseForNighttime = true')
            newStatus = 'Closed for night time.'
            desiredSetpoint = 0
        end
        
        if 0 < desiredSetpoint and shutterControl.shouldCloseForCold(dz, shutter) then
            dz.log(shutter.name .. ' shouldCloseForCold = true')
            newStatus = 'Closed for cold'
            desiredSetpoint = 0
        end
    
        if 0 < desiredSetpoint and shutterControl.shouldCloseForHotOutside(dz, shutter) then
            dz.log(shutter.name .. ' shouldCloseForHotOutside = true')
            newStatus = 'Closed because it is hot outside.'
            desiredSetpoint = 0
        end
        
        if shutter.partlyOpenPercentage < desiredSetpoint and shutterControl.shouldPartlyOpenForDaytime(dz, shutter) then
            dz.log(shutter.name .. ' shouldPartlyOpenForDaytime = true')
            newStatus = 'Partly open for day time.'
            desiredSetpoint = math.min(desiredSetpoint, shutter.partlyOpenPercentage)
        end

        if shutter.partlyCloseSunlightPercentage < desiredSetpoint and shutterControl.shouldCloseForSunlight(dz, shutter) then
            dz.log(shutter.name .. ' shouldCloseForSunlight = true')
            newStatus = 'Partly closed for sunlight.'
            desiredSetpoint = math.min(desiredSetpoint, shutter.partlyCloseSunlightPercentage)
        end
        
        if 100 < desiredSetpoint and shutterControl.shouldOpenForDaytime(dz, shutter) then
            dz.log(shutter.name .. ' shouldOpenForDaytime = true')
            newStatus = 'Open for daytime.'
            desiredSetpoint = math.min(desiredSetpoint, 100)
        end

        if 100 < desiredSetpoint and shutterControl.shouldOpenForNighttime(dz, shutter) then
            dz.log(shutter.name .. ' shouldOpenForNighttime = true')
            newStatus = 'Open for night time.'
            desiredSetpoint = math.min(desiredSetpoint, 100)
        end
        
        if desiredSetpoint ~= 100 and shutterControl.shouldOpenForWind(dz, shutter) then
            dz.log(shutter.name .. ' shouldOpenForWind = true')
            newStatus = 'Open for wind.'
            desiredSetpoint = 100
        end
        
        if desiredSetpoint ~= 100 and shutterControl.shouldOpenForRain(dz, shutter) then
            dz.log(shutter.name .. ' shouldOpenForRain = true')
            newStatus = 'Open for rain.'
            desiredSetpoint = 100
        end
        
        dz.log(shutter.name .. ' desiredSetpoint = ' .. desiredSetpoint)
        dz.log(shutter.name .. ' controller level = ' .. controller.level .. ' ' .. controller.state)
        -- Send setpoint to shutter
        if desiredSetpoint <= 100 then
            shutterControl.updateStatus(dz, shutter.idx_status, newStatus)
            if shutter.supportsPercentage and (desiredSetpoint ~= controller.level or desiredSetpoint ~= dz.data.lastSetpoint[controller.idx] or (desiredSetpoint == 0 and controller.state == 'Open') or (desiredSetpoint == 100 and controller.state == 'Closed')) then
--                if desiredSetpoint == 0 then
--                    controller.close()
--                elseif desiredSetpoint == 100 then
--                    controller.open()
--                else
                    controller.dimTo(desiredSetpoint).afterSec(shutter.commandDelay/1000)
--                end
                dz.data.lastSetpointSend[controller.idx] = dz.time.rawDateTime
                dz.data.lastSetpoint[controller.idx] = desiredSetpoint
                dz.log(shutter.name .. ' new setpoint send to controller (' .. controller.idx .. '): ' .. dz.data.lastSetpoint[controller.idx] .. ' at ' .. dz.data.lastSetpointSend[controller.idx])
            elseif shutter.supportsPercentage == false and ((desiredSetpoint >= 50 and controller.state ~= 'Open') or (desiredSetpoint < 50 and controller.state ~= 'Closed') or desiredSetpoint ~= dz.data.lastSetpoint[controller.idx]) then
                if desiredSetpoint >= 50 then
                    controller.open()
                else
                    controller.close()
                end
                dz.data.lastSetpointSend[controller.idx] = dz.time.rawDateTime
                dz.data.lastSetpoint[controller.idx] = desiredSetpoint
                dz.log(shutter.name .. ' new setpoint send to controller: ' .. dz.data.lastSetpoint[controller.idx] .. ' at ' .. dz.data.lastSetpointSend[controller.idx])
            end
        end
    end,
}

return {
	on = {
        devices = shutterControl.triggerDevices(),
		timer = { 'every minute' },
		variables = { idxv_dagNacht },
		httpResponses = { lastSeenCallback }
	},

	data = {
	    manualOperated = {initial = {}},        --true/false per controller, nil should be handled as true
	    resetManualOperated = {initial = {}},   --true/false per controller, 
	    lastUpdate = {initial = {}},            --last update rawDateTime
	    lastSetpointSend = {initial = {}},      --last setpoint send to shutter
	    lastSetpoint = {initial = {}},          --rawDateTime of last setpoint command
	    notificationSend = {initial = {}},       -- true/false notification already send, so don't send every minute
	    closedForSun = {initial = {}}           -- true/false per controller 
	},

	logging = {
--        level = domoticz.LOG_DEBUG,
--        level = domoticz.LOG_INFO,
        level = domoticz.LOG_ERROR,
        marker = "Rolluiken"
    },

	execute = function(dz, triggeredItem, info)
        local doWorkloopForAll = false
	    
	    if dumpDeviceConfigToLog then shutterControl.dumpDeviceConfig(dz) end

        if triggeredItem.isDevice then
            -- find the device which caused the trigger
        	for _, shutter in ipairs(SHUTTERS) do
                if triggeredItem.idx == shutter.idx_manualModeSwitch and triggeredItem.active == false then
                    -- manual mode switched off
                    dz.log('Manual mode switch is turned off for ' .. shutter.name)
                    shutterControl.resetManualOperated(dz, shutter)
        	        -- do workloop for only this shutter
        	        shutterControl.doWorkLoop(dz, shutter)

                elseif triggeredItem.idx == shutter.idx_windowSensor and triggeredItem.active == false then
                    -- window is closed
                    dz.log('Window is closed for ' .. shutter.name)
        	        shutterControl.doWorkLoop(dz, shutter)
        	    end
	        end
	    
        elseif triggeredItem.isVariable and triggeredItem.id == idxv_dagNacht then
        	for _, shutter in ipairs(SHUTTERS) do
        	    shutterControl.resetManualOperated(dz, shutter)
                shutterControl.doWorkLoop(dz, shutter)
            end
            
        elseif triggeredItem.isTimer then
            -- request last update values
            dz.openURL({url = dz.settings['Domoticz url'] .. '/json.htm?type=devices&used=true',callback = lastSeenCallback })

        	for _, shutter in ipairs(SHUTTERS) do
        	    -- triggered by timer do work
                shutterControl.doWorkLoop(dz, shutter)
            end
        elseif triggeredItem.isHTTPResponse then
            -- process http response of last update request
            local devs = shutterControl.allDevices()
            
            for _, node in pairs(triggeredItem.json.result) do
                local idx = tonumber(node.idx)
                if devs[idx] then
                    dz.log(node.Name .. ' was last updated at ' .. node.LastUpdate, dz.LOG_DEBUG)
                    dz.data['lastUpdate'][idx] = node.LastUpdate
                end
            end            
        end
	end
}


Re: Generic roller shutter script for multiple shutters

Posted: Saturday 21 November 2020 14:06
by mcjackdus
Hi,

Im looking for a way i can let my window sensor prevent controlling the shutter.
So when the window sensor is open, closing the shutter is not possible.
I looked for different solutions on the web but i can't seem to make it work.
Looking at your topic you also have this implemented in your script.
Can you help me out with just that part?

Thank you very much in advance,
Jack

Re: Generic roller shutter script for multiple shutters

Posted: Monday 23 November 2020 20:22
by Superdeboer
ronaldbro wrote: Sunday 08 December 2019 22:10 I created a generic script to controll all my roller shutters in my house. I didn't want a separate script for each shutter but I do want to let them behave a little different. So I made it generic and for each shutter I configure the behaviour.

'Generic' means generic in my house ;) Some tuning might be necessary depending on the situation.
This script looks awesome! It looks like nice coding with great customization possibilities. It seems like this is exactly what I want to use in my situation.
But I have some questions, especially with respect to the dependencies that you mentioned. I hope that you can clarify some things.
Dependencies:
- Domoticz-SunMoon-Plugin
I managed to install this plugin correctly, which took me some time.
For future reference: the plugin can be found at https://github.com/Xorfor/Domoticz-SunMoon-Plugin
I used the example on https://www.domoticz.com/wiki/Using_Python_plugins to install the plugin from Git in Domoticz.
For installation of Ephem, which is required for the plugin, I used: see viewtopic.php?t=25612 and especially viewtopic.php?p=197122#p197122, but in my case I needed to modify the command to Python version 3.7.
I needed to Domoticz service multiple times in between the steps of installing the plugin, before it worked.
- variable DayNight which is set in another script. Possible values are defined in global_data constants.
Can you please explain what this variable is and what you do to create and set it. Is it a dummy switch in Domoticz? If so, what is the exact name of the switch and would you please be so kind as to post (an excerpt of) the script that you use to flip the switch?
- global_data entries:
CONST_DAGNACHT_DAG = 'DAG'
CONST_DAGNACHT_NACHT = 'NACHT'
SUN = 1 -- dz.time.wday returns 1 for sunday
MON = 2
TUE = 3
WED = 4
THU = 5
FRI = 6
SAT = 7
I have several functioning Dzvents scripts in Domoticz now, some of which rely on persistent variables using "data" within the script. However, I am not yet familiar with using global_data.lua.
I tried to use your values in global_data.lua (which I created through the web interface) as follows.

Code: Select all

return {
	-- global persistent data
	data = {
		CONST_DAGNACHT_DAG = 'DAG',
        CONST_DAGNACHT_NACHT = 'NACHT',
        SUN = 1, -- dz.time.wday returns 1 for sunday
        MON = 2,
        TUE = 3,
        WED = 4,
        THU = 5,
        FRI = 6,
        SAT = 7,
	},
}
This leads to the following error being displayed in the log: "Error: EventSystem: in /home/pi/domoticz/dzVents/runtime/dzVents.lua: /home/pi/domoticz/dzVents/runtime/EventHelpers.lua:105: attempt to index a number value (local 'def')" If I delete my global_data.lua, this error is gone immediately.
Could you please post (an excerpt of) your global_data.lua script here, so I can see what it is that you did exactly?
At this moment the script runs without any problems. But it can always be improved. Every feedback is appreciated.
Maybe once I succeeded in making my own implementation of your script, I will be able to make suggestions for improvements. Right now I am trying to understand what is needed to make the script working in my situation in the first place.

Can you clarify the following:
  1. You seem to use one device for measuring outside outsideTemp and lux, in your back yard (achtertuin_thb and achtertuin_lux). Which device do you use for this? So far I have not found a Zwave-compatible lux sensor that can be used outdoor.
  2. Also: where did you place the outside lux sensor? Is it directly in the sun?
  3. Is it correct that you use a separate room temperature sensor in each room that has roller shutters?
  4. Are these the only sensors besides possible window sensors? So this means to get the script working, it is necessary to have an outside lux sensor and temperature sensors in each room?
  5. You don't measure lux from inside the rooms?
  6. What is the virtual text device for status messages that you mention in the script? Can you please elaborate on what it is and what the purpose of this is?
  7. Last but not least: manual mode. You mention a virtual switch, that indicates manual mode when switched on. How does this work exactly? Would you be so kind as to post examples of the setup and naming of the virtual switch and the coding that it relies on?

Re: Generic roller shutter script for multiple shutters

Posted: Monday 23 November 2020 21:16
by ronaldbro
I just updated the script in the first post. It has added support for sunscreens and multiple lux sensors.

The variable NightDay is a user variable. Check the menu settings -> User variable. It's like a switch but without a widget. The idea is that it get the value as defined in the constant when it's day or when it's night. How it gets set is not important for this script. But as example I use multiple Xioami lux sensors to determine day or night. And because these sensors give me an update every 2 or 3 seconds at daytime I also use the same script to determine an average and sloping max value which I use as lux sensors in the shutter script.
Here's the script:

Code: Select all

local LUX_THRESHOLD_NIGHT   = 85 -- 1000 wanneer idx 54
local LUX_THRESHOLD_DAY     = 20 -- 1000 wanneer idx 54
local HISTORY_MINUTES       = 10 -- time for Avg and Max calculation, max 60 minutes

--local idx_lux = idx_oprit_lux -- idx 54 alternatief
local idx_lux_voorkant              = idx_omgeving_lux_voorkant
local idx_lux_zijkant               = idx_omgeving_lux_zijkant
local idx_lux_achterkant            = idx_omgeving_lux_achterkant
local idx_lux_achterkantBoven       = idx_omgeving_lux_achterkantBoven

local idx_lux_voorkant_avg          = idx_omgeving_lux_voorkant_avg
local idx_lux_zijkant_avg           = idx_omgeving_lux_zijkant_avg
local idx_lux_achterkant_avg        = idx_omgeving_lux_achterkant_avg
local idx_lux_achterkantBoven_avg   = idx_omgeving_lux_achterkantBoven_avg

local idx_lux_voorkant_max          = idx_omgeving_lux_voorkant_max
local idx_lux_zijkant_max           = idx_omgeving_lux_zijkant_max
local idx_lux_achterkant_max        = idx_omgeving_lux_achterkant_max
local idx_lux_achterkantBoven_max   = idx_omgeving_lux_achterkantBoven_max

return {	
	on = {
	    devices = {
	        idx_lux_voorkant,
	        idx_lux_zijkant,
	        idx_lux_achterkant,
	        idx_lux_achterkantBoven
	    },
	    
		timer = {
		    'every minute'
		}
    },
    
	data = {
	    luxVoorkant         = { history = true, maxMinutes = HISTORY_MINUTES },
	    luxZijkant          = { history = true, maxMinutes = HISTORY_MINUTES },
	    luxAchterkant       = { history = true, maxMinutes = HISTORY_MINUTES },
	    luxAchterkantBoven  = { history = true, maxMinutes = HISTORY_MINUTES }
	},
    
	-- custom logging level for this script
	logging = {
--        level = domoticz.LOG_DEBUG,
--        level = domoticz.LOG_INFO,
        level = domoticz.LOG_ERROR,
        marker = "Environment variables"
    },
    
    execute = function(dz, triggeredItem, info)
        
        local varDagNacht           = dz.variables(var_idx_dagNacht)

        local luxVoorkant           = dz.devices(idx_lux_voorkant)
        local luxZijkant            = dz.devices(idx_lux_zijkant)
        local luxAchterkant         = dz.devices(idx_lux_achterkant)
        local luxAchterkantBoven    = dz.devices(idx_lux_achterkantBoven)

        local luxVoorkantAvg        = dz.devices(idx_lux_voorkant_avg)
        local luxZijkantAvg         = dz.devices(idx_lux_zijkant_avg)
        local luxAchterkantAvg      = dz.devices(idx_lux_achterkant_avg)
        local luxAchterkantBovenAvg = dz.devices(idx_lux_achterkantBoven_avg)
        
        local luxVoorkantMax        = dz.devices(idx_lux_voorkant_max)
        local luxZijkantMax         = dz.devices(idx_lux_zijkant_max)
        local luxAchterkantMax      = dz.devices(idx_lux_achterkant_max)
        local luxAchterkantBovenMax = dz.devices(idx_lux_achterkantBoven_max)
        
        if triggeredItem.isDevice then
            if triggeredItem.idx == idx_lux_voorkant then
                dz.data.luxVoorkant.add(luxVoorkant.lux)
                luxVoorkantAvg.updateLux(dz.data.luxVoorkant.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxVoorkantMax.updateLux(dz.data.luxVoorkant.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Voorkant = ' .. tostring(luxVoorkant.lux) .. ' Avg = ' .. tostring(dz.data.luxVoorkant.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxVoorkant.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            elseif triggeredItem.idx == idx_lux_zijkant then
                dz.data.luxZijkant.add(luxZijkant.lux)
                luxZijkantAvg.updateLux(dz.data.luxZijkant.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxZijkantMax.updateLux(dz.data.luxZijkant.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Zijkant = ' .. tostring(luxZijkant.lux) .. ' Avg = ' .. tostring(dz.data.luxZijkant.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxZijkant.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            elseif triggeredItem.idx == idx_lux_achterkant then
                dz.data.luxAchterkant.add(luxAchterkant.lux)
                luxAchterkantAvg.updateLux(dz.data.luxAchterkant.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxAchterkantMax.updateLux(dz.data.luxAchterkant.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Achterkant = ' .. tostring(luxAchterkant.lux) .. ' Avg = ' .. tostring(dz.data.luxAchterkant.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxAchterkant.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            elseif triggeredItem.idx == idx_lux_achterkantBoven then
                dz.data.luxAchterkantBoven.add(luxAchterkantBoven.lux)
                luxAchterkantBovenAvg.updateLux(dz.data.luxAchterkantBoven.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxAchterkantBovenMax.updateLux(dz.data.luxAchterkantBoven.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Achterkant Boven= ' .. tostring(luxAchterkantBoven.lux) .. ' Avg = ' .. tostring(dz.data.luxAchterkantBoven.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxAchterkantBoven.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            end
            
        elseif triggeredItem.isTimer then
            if dz.data.luxVoorkant.get(1) == nil or dz.data.luxVoorkant.get(1).time.secondsAgo >= 30 then
                dz.data.luxVoorkant.add(luxVoorkant.lux)
                luxVoorkantAvg.updateLux(dz.data.luxVoorkant.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxVoorkantMax.updateLux(dz.data.luxVoorkant.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Voorkant = ' .. tostring(luxVoorkant.lux) .. ' Avg = ' .. tostring(dz.data.luxVoorkant.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxVoorkant.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            end
            if dz.data.luxZijkant.get(1) == nil or dz.data.luxZijkant.get(1).time.secondsAgo >= 30 then
                dz.data.luxZijkant.add(luxZijkant.lux)
                luxZijkantAvg.updateLux(dz.data.luxZijkant.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxZijkantMax.updateLux(dz.data.luxZijkant.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Zijkant = ' .. tostring(luxZijkant.lux) .. ' Avg = ' .. tostring(dz.data.luxZijkant.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxZijkant.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            end
            if dz.data.luxAchterkant.get(1) == nil or dz.data.luxAchterkant.get(1).time.secondsAgo >= 30 then
                dz.data.luxAchterkant.add(luxAchterkant.lux)
                luxAchterkantAvg.updateLux(dz.data.luxAchterkant.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxAchterkantMax.updateLux(dz.data.luxAchterkant.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Achterkant = ' .. tostring(luxAchterkant.lux) .. ' Avg = ' .. tostring(dz.data.luxAchterkant.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxAchterkant.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            end
            if dz.data.luxAchterkantBoven.get(1) == nil or dz.data.luxAchterkantBoven.get(1).time.secondsAgo >= 30 then
                dz.data.luxAchterkantBoven.add(luxAchterkantBoven.lux)
                luxAchterkantBovenAvg.updateLux(dz.data.luxAchterkantBoven.avgSince('00:' .. HISTORY_MINUTES .. ':00'))
                luxAchterkantBovenMax.updateLux(dz.data.luxAchterkantBoven.maxSince('00:' .. HISTORY_MINUTES .. ':00'))
                dz.log('Lux Achterkant Boven= ' .. tostring(luxAchterkantBoven.lux) .. ' Avg = ' .. tostring(dz.data.luxAchterkantBoven.avgSince('00:' .. HISTORY_MINUTES .. ':00')) .. ' Max = ' .. tostring(dz.data.luxAchterkantBoven.maxSince('00:' .. HISTORY_MINUTES .. ':00')))
            end
        end
    
        if varDagNacht.value ~= CONST_DAGNACHT_DAG then
            if dz.time.matchesRule('between civiltwilightstart and 30 minutes after sunrise') 
            and luxVoorkant.lux >= LUX_THRESHOLD_DAY 
 --           and luxZijkant.lux >= LUX_THRESHOLD_DAY 
            and luxAchterkant.lux >= LUX_THRESHOLD_DAY 
            then
                dz.log('Set variable DagNacht to Dag')
                varDagNacht.set(CONST_DAGNACHT_DAG)
            elseif dz.time.matchesRule('between 30 minutes after sunrise and 12:00') then
                dz.log('Lightsensors did not detect day yet at sunrise. Setting variable DagNacht to Dag', dz.LOG_ERROR)
                varDagNacht.set(CONST_DAGNACHT_DAG)
            end
            
        elseif varDagNacht.value ~= CONST_DAGNACHT_NACHT then
            if dz.time.matchesRule('between 60 minutes before sunset and civiltwilightend') 
            and luxVoorkant.lux <= LUX_THRESHOLD_NIGHT 
  --          and luxZijkant.lux <= LUX_THRESHOLD_NIGHT 
            and luxAchterkant.lux <= LUX_THRESHOLD_NIGHT 
            then
                dz.log('Set variable DagNacht to Nacht')
                varDagNacht.set(CONST_DAGNACHT_NACHT)
            elseif dz.time.matchesRule('after civiltwilightend') then
                dz.log('Lightsensors did not detect night yet at civiltwilightend. Setting variable DagNacht to Nacht', dz.LOG_ERROR)
                varDagNacht.set(CONST_DAGNACHT_NACHT)
            end
        end

	end
}
Also see my global_data:

Code: Select all

idx_achtertuin_winterlichtjes           = 503
idx_woonkamer_kerstboom                 = 560

idx_power_line3                         = 564
idx_pihole_status                       = 609
idx_externalIP                          = 1128
idx_system_DBcheck                      = 1133

idx_notification_ovenwarm               = 573

idx_test_switch                         = 1068

idx_gpio_26                             = 551

idx_achtertuin_beweging                 = 1063
idx_achtertuin_zithoek	                = 742 
idx_achtertuin_thb                      = 1186
idx_achtertuin_beekloop                 = 845
idx_achtertuin_beeklooplamp             = 1016
idx_achtertuin_lantaarnpaal             = 1067
idx_achtertuin_vijverNiveauOk           = 761
idx_achtertuin_regen                    = 1189
idx_achtertuin_wind                     = 1190

idx_aanwezig_iemand                     = 172
idx_aanwezig_ronald                     = 830 --idetect: 830, homekit: 542
idx_aanwezig_eline                      = 831 --idetect: 831, homekit: 543

idx_alarm_knop                          = 695
idx_alarm_securityPanel                 = 70
idx_alarm_toggleAlarmAway               = 122
idx_alarm_sirene                        = 135 
idx_alarm_sireneSound                   = 75
idx_alarm_sireneVolume                  = 79
idx_alarm_status                        = 493
idx_alarm_uitgeschakeld                 = 639  -- workaround voor missende alarm off notificatie in homekit

idx_badkamer_raam                       = 915
idx_badkamer_relVocht                   = 939
idx_badkamer_rolluik                    = 1058
idx_badkamer_rolluikManualMode          = 526
idx_badkamer_rolluikStatus              = 536
idx_badkamer_ventilator                 = 1049
idx_badkamer_ventilator_PowerUsage      = 1048
idx_badkamer_thb                        = 936

idx_bijkeuken_achterdeur                = 759
idx_bijkeuken_vriezerdeur               = 1032

idx_floris_dimmer                       = 907
idx_floris_plafond                      = 906
idx_floris_raam                         = 909
idx_floris_relVocht                     = 944
idx_floris_rolluik                      = 1055
idx_floris_rolluikManualMode            = 524
idx_floris_rolluikStatus                = 537
idx_floris_thb                          = 941
idx_floris_uitslapen                    = 997
idx_floris_lampSync                     = 1130

idx_garage_deur                         = 833
idx_garage_garagedeur                   = 832

idx_hal_beweging                        = 917
idx_hal_bewegingLux                     = 918
idx_hal_deurbel                         = 539
idx_hal_deurbelVolume                   = 540
idx_hal_deurbelRingtone                 = 518
idx_hal_voordeur                        = 762
idx_hal_spots                           = 898
idx_hal_opple_knop1                     = 982
idx_hal_opple_knop2                     = 983
idx_hal_opple_knop3                     = 984
idx_hal_opple_knop4                     = 985
idx_hal_opple_knop5                     = 986
idx_hal_opple_knop6                     = 987

idx_kantoor_raam                        = 911
idx_kantoor_relVocht                    = 951
idx_kantoor_rolluik                     = 1053
idx_kantoor_rolluikManualMode           = 523
idx_kantoor_rolluik_switch              = 935
idx_kantoor_rolluikStatus               = 535
idx_kantoor_thb                         = 948

idx_keuken_aanrechtverlichting          = 745
idx_keuken_beweging                     = 746
idx_keuken_ventilator                   = 1209

idx_slaapkamer_bewegingLinks            = 1009
idx_slaapkamer_bewegingRechts           = 1010
idx_slaapkamer_bedlamp                  = 118
idx_slaapkamer_raam                     = 916
idx_slaapkamer_relVocht                 = 962
idx_slaapkamer_rolluik                  = 1054
idx_slaapkamer_rolluikManualMode        = 279
idx_slaapkamer_rolluikStatus            = 534
idx_slaapkamer_thb                      = 1191
idx_slaapkamer_uitslapen                = 996

idx_olivier_lamp                        = 931
idx_olivier_rolluik                     = 1056
idx_olivier_rolluikManualMode           = 525
idx_olivier_rolluikStatus               = 538
idx_olivier_thb                         = 954
idx_olivier_uitslapen                   = 998

idx_omgeving_lux_voorkant               = 808
idx_omgeving_lux_voorkant_avg           = 825
idx_omgeving_lux_voorkant_max           = 1042
idx_omgeving_lux_zijkant                = 807
idx_omgeving_lux_zijkant_avg            = 826
idx_omgeving_lux_zijkant_max            = 1043
idx_omgeving_lux_achterkant             = 806
idx_omgeving_lux_achterkant_avg         = 827
idx_omgeving_lux_achterkant_max         = 1044
idx_omgeving_lux_achterkantBoven        = 1019
idx_omgeving_lux_achterkantBoven_avg    = 1021
idx_omgeving_lux_achterkantBoven_max    = 1045

idx_overloop_beweging                   = 919
idx_overloop_bewegingLux                = 920
idx_overloop_spots                      = 901
idx_overloop_spot1                      = 900
idx_overloop_spot2                      = 899
idx_overloop_xiaomiGateway              = 74

idx_voortuin_luifel                     = 764
idx_voortuin_beweging                   = 1064

idx_woonkamer_airQuality                = 1188
idx_woonkamer_dressoir 		            = 743
idx_woonkamer_eettafel		            = 1051
idx_woonkamer_ledstrip                  = 749
idx_woonkamer_leeslampKnop              = 728
idx_woonkamer_leeslamp                  = 744
idx_woonkamer_rolluik                   = 1050
idx_woonkamer_rolluikManualMode         = 43
idx_woonkamer_rolluikStatus             = 533
idx_woonkamer_screenAchter              = 1046
idx_woonkamer_screenAchterStatus        = 1038
idx_woonkamer_screenAchterManualMode    = 1040
idx_woonkamer_screenZijkant             = 1047
idx_woonkamer_screenZijkantStatus       = 1039
idx_woonkamer_screenZijkantManualMode   = 1041
idx_woonkamer_schuifpui                 = 755
idx_woonkamer_spotsUitbouw		        = 737
idx_woonkamer_thb                       = 1187
idx_woonkamer_zithoek		            = 1052

idx_sceneswitch_avond                   = 149
idx_sceneswitch_avondAlleenAan          = 300
idx_sceneswitch_welterusten             = 150
idx_sceneswitch_goedemorgen             = 151
idx_sceneswitch_woonkamerUit            = 541
idx_sceneswitch_checkScenes             = 430

idx_sunmoon_sunAltitude                 = 166
idx_sunmoon_sunAzimuth                  = 165

idx_zolder_beweging                     = 1069
idx_zolder_bewegingLux                  = 1070
idx_zolder_spot1                        = 904
idx_zolder_spot2                        = 902
idx_zolder_spot3                        = 903
idx_zolder_spots                        = 905

var_idx_dagNacht                        = 5
var_idx_TelegramApiKeyRonald            = 9
var_idx_TelegramApiKeyEline             = 10
var_idx_TelegramChatIdRonald            = 11
var_idx_TelegramChatIdEline             = 12

CONST_DAGNACHT_DAG                      = 'DAG'
CONST_DAGNACHT_NACHT                    = 'NACHT'

SUN = 1 -- dz.time.wday returns 1 for sunday
MON = 2
TUE = 3
WED = 4
THU = 5
FRI = 6
SAT = 7

return {
	-- global persistent data
--	data = {
--		myGlobalVar = { initial = 12 }
--	},

	-- global helper functions
	helpers = {
        ------------------------------------------------------------------------
        -- isHoliday
        -- Is het vandaag een feestdag?
        -- return true/false
        ------------------------------------------------------------------------
        isHolidayToday = function(domoticz)
		    return domoticz.helpers.isHoliday(domoticz.time.year, domoticz.time.month, domoticz.time.day)
		end,

        isHolidayTomorrow = function(domoticz)
            local day = 24*3600
        
            local Time = require('Time')
            local tomorrow = Time( os.date("%Y-%m-%d %H:%M:%S", os.time() + day )) 

		    return domoticz.helpers.isHoliday(tomorrow.year, tomorrow.month, tomorrow.day)
		end,
		    
        isHoliday = function(year, month, day)
		    local result = false
		    
		    if (month==1 and day==1)    --Nieuwjaar
            or (month==4 and day==27)   --Koningsdag
            or (month==12 and day==25)  --Eerste kerstdag
            or (month==12 and day==26)  --Tweede kerstdag
            then
                result = true

            elseif year==2019 then
    		    if (month==4 and day==21)   --Eerste paasdag
                or (month==4 and day==22)   --Tweede paasdag
                or (month==5 and day==30)   --Hemelvaart
                or (month==6 and day==9)    --1e pinksterdag
                or (month==6 and day==10)   --2e pinksterdag
                then
                    result = true
                end

            elseif year==2020 then
    		    if (month==4 and day==12)   --Eerste paasdag
                or (month==4 and day==13)   --Tweede paasdag
                or (month==5 and day==5)    --bevrijdingsdag
                or (month==5 and day==21)   --Hemelvaart
                or (month==5 and day==31)   --1e pinksterdag
                or (month==6 and day==1)    --2e pinksterdag
                then
                    result = true
                end

            elseif year==2021 then
    		    if (month==4 and day==4)    --Eerste paasdag
                or (month==4 and day==5)    --Tweede paasdag
                or (month==5 and day==13)   --Hemelvaart
                or (month==5 and day==23)   --1e pinksterdag
                or (month==5 and day==24)   --2e pinksterdag
                then
                    result = true
                end
                    
            elseif year==2022 then
    		    if (month==4 and day==17)   --Eerste paasdag
                or (month==4 and day==18)   --Tweede paasdag
                or (month==5 and day==26)   --Hemelvaart
                or (month==6 and day==5)    --1e pinksterdag
                or (month==6 and day==6)    --2e pinksterdag
                then
                    result = true
                end
                
            elseif year==2023 then
    		    if (month==4 and day==9)    --Eerste paasdag
                or (month==4 and day==10)   --Tweede paasdag
                or (month==5 and day==18)   --Hemelvaart
                or (month==5 and day==28)   --1e pinksterdag
                or (month==5 and day==29)   --2e pinksterdag
                then
                    result = true
                end

            elseif year==2024 then
    		    if (month==3 and day==31)   --Eerste paasdag
                or (month==4 and day==1)    --Tweede paasdag
                or (month==5 and day==9)    --Hemelvaart
                or (month==5 and day==19)   --1e pinksterdag
                or (month==5 and day==20)   --2e pinksterdag
                then
                    result = true
                end
            end

			return result
		end,

        ------------------------------------------------------------------------
        -- isSunOrHoliday
        -- Is het vandaag een zondag of een feestdag?
        -- return true/false
        ------------------------------------------------------------------------
		isSunOrHoliday = function(domoticz)
		    local result = false

            result = domoticz.time.wday == SUN

            if result == false then
                result = domoticz.helpers.isHoliday(domoticz)
            end

			return result
	    end,
	    
        telegramRonald = function(dz, text, onlyAtHome)
            if dz.devices(idx_aanwezig_ronald).active or onlyAtHome == false or onlyAtHome == nil then
                dz.openURL('https://api.telegram.org/bot' .. dz.variables(var_idx_TelegramApiKeyRonald).value .. '/sendMessage?chat_id=' .. dz.variables(var_idx_TelegramChatIdRonald).value .. '&text=' .. text)
            end
    	end,
	    
        telegramEline = function(dz, text, onlyAtHome)
            if dz.devices(idx_aanwezig_eline).active or onlyAtHome == false or onlyAtHome == nil then
                dz.openURL('https://api.telegram.org/bot' .. dz.variables(var_idx_TelegramApiKeyEline).value .. '/sendMessage?chat_id=' .. dz.variables(var_idx_TelegramChatIdEline).value .. '&text=' .. text)
            end
    	end,
    	
        telegramAll = function(dz, text)
            dz.helpers.telegramRonald(dz, text, false)
            dz.helpers.telegramEline(dz, text, false)
    	end,
    	
        telegramHome = function(dz, text)
            dz.helpers.telegramRonald(dz, text, true)
            dz.helpers.telegramEline(dz, text, true)
    	end,
	}
}
See here the answers to your other questions:
You seem to use one device for measuring outside outsideTemp and lux, in your back yard (achtertuin_thb and achtertuin_lux). Which device do you use for this? So far I have not found a Zwave-compatible lux sensor that can be used outdoor.
- I use the Netatmo weather station for temperature en 4 Xioami zigbee lux sensors for lux. (For the z-wave part... I do have a Fibaro PIR outside which has a temp and lux sensors outside, but under a roof)

Also: where did you place the outside lux sensor? Is it directly in the sun?
- The lux sensors are in direct sunlight. Basically you want to get the amount of light which enters the house so it's alway a bit of a puzzle where to place them to get the best result. I have one on the front side of the house, no problem with shades there, one at the side and two at the back. One downstairs and one upstairs. (Of course all outside, these sensors are weather proof)

Is it correct that you use a separate room temperature sensor in each room that has roller shutters?
- Correct, I measure the temperature in every room of the house. If you don't want that you can use the same idx for multiple shutters.

Are these the only sensors besides possible window sensors? So this means to get the script working, it is necessary to have an outside lux sensor and temperature sensors in each room?
- You don't need them. You can use nil as value. See the comments in the script.

You don't measure lux from inside the rooms?
- No, that would only help closing the shutter fur sunlight and then the sensor is useless... All my lux sensors are outside.

What is the virtual text device for status messages that you mention in the script? Can you please elaborate on what it is and what the purpose of this is?
- It is a dummy text sensor and it shows the state of the shutter. For example 'Closed for sunlight', 'closed for night', 'window open', etc. You don't need it but it's helpful for debugging.

Last but not least: manual mode. You mention a virtual switch, that indicates manual mode when switched on. How does this work exactly? Would you be so kind as to post examples of the setup and naming of the virtual switch and the coding that it relies on?
- Just a dummy switch and then it's on nothing happens automatically. So basically the script skips that shutter. Also have one now for sleep late, when this switch is on the shutter stays closed until a different time as normally.