Contents
Intro
I have a sunscreen with a Somfy motor (I can control it by Domoticz through my rflink and RFXCom) but wanted to automate it. I started a long time ago with my first Domoticz Sunscreen automation script in Lua, a simple version based on one rule: When it is not raining and the temperature is above x degrees then let the sunscreen go down. But unfortunately that was not sufficient, other dependent factors resulted in a script with a lot of features, parameters and debugging lines.
It became a massive script that was hard to debug, adjust and to maintain.
There are so many variables: uv, wind, manual override, up at nighttime, other condition when we are in the garden, etc etc. And with Lua sometimes it was hard to script out a simple condition. So I started over (inspired by an example I can’t retrieve anymore) in dzVents so many handwritten functions are replaced by default functionalities of dzVents.
Features
The following features are included in the current script:
- Let the sunscreen go up and down.
- Based on current weather conditions goes up and down.
- Predict rain/wind.
- Prevent moving the whole time and not going up and down within a certain period of time.
- A dry-run option to simulate everything without really opening and closing the sunscreen itself and log it into a text file (comma separated). Very useful when experimenting with general settings and for setting the thresholds.
To Do:
The current version of the script does the job for me. I do have some wishes for features that I would like to implement:
- Configure notifications
- Mix weather devices (create a fallback if one device returns no data)
- Include a home and away function.
- Include a virtual season device
If you have other ideas, found bugs or have other feedback, feel free to contact me.
Installation
prerequisites
remark: if you don’t want to log to CSV files: delete of comment the function and the lines in switchoff and switchon functions that start the logaction function. If you lines are commented you don’t need to create the log folder and use the function library. Also the include of the Myfunc isn’t needed anymore and can be commented or deleted.
- Folder for Log.csv files:
- mkdir /home/pi/domoticz/Logs
- Function library
- place the function library into: /home/pi/domoticz/scripts/lua/
- Paste and adjust the sunscreen script into the web-editor of Domoticz
Testing
While testing your script what’s the logging. the logging looks like this:
It’s important that the sensors that are active contain data, paths are correct.
it’s handy to test with the variable dryRun on true. After you noticed that all thresholds and conditions are fine, change this variable and enjoy.
Download the scripts
download function library: MyFunc.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | -- MyFunc -- -- Function library -- -- -- -- Release notes: -- -- 2016-06-01 First release -- -------------------------------------------------------------------------------- MyScript = 'MyFunc' -- MyFuncVersion = 100 -- MyFuncVersion_Type = 'Beta 01' -- LuaDebug = false -- -------------------------------------------------------------------------------- -- -- Functions -- tempfilename = '/home/pi/domoticz/Logs/buienradar.tmp' -- local My ={} function My.isBlank(x) return not not tostring(x):find("^%s*$") end function My.PlaySound(msg) Debug='Y' if (Debug == 'Y') then print('>> Alarm zegt: '.. msg) end os.execute ("sudo killall mplayer") os.execute ("sh /home/pi/domoticz/scripts/bash/Play_sound.sh '" .. msg .. "' ") end function My.GetUserVar(UserVar) Waarde=uservariables[UserVar] if Waarde==nil then print(". User variable not set for : " .. UserVar) if My.isBlank(UserVarErr) or UserVarErr==nil then UserVarErr=0 end UserVarErr=UserVarErr+1 end return Waarde end function My.File_exists(file) local f = io.open(file, "rb") if f then f:close() end return f ~= nil end function My.Round(num, idp) local mult = 10^(idp or 0) return math.floor(num * mult + 0.5) / mult end function My.GetValue(Text, GetNr) Part=1 if Text==nil then print(". GetValue error at : null " .. GetNr) MyValue=0 else for match in (Text..';'):gmatch("(.-)"..';') do if Part==GetNr then MyValue = tonumber(match) end Part=Part+1 end end return MyValue end -- replace the last character function My.EnumClear(Text) a=string.len(Text) b=string.sub(Text,a,a) if b=="," or b==" " then Text=string.sub(Text,1,a-1) end a=string.len(Text) b=string.sub(Text,a,a) if b=="," or b==" " then Text=string.sub(Text,1,a-1) end return Text end function My.ConvTime(TimeX) year = string.sub(TimeX, 1, 4) month = string.sub(TimeX, 6, 7) day = string.sub(TimeX, 9, 10) hour = string.sub(TimeX, 12, 13) minutes = string.sub(TimeX, 15, 16) seconds = string.sub(TimeX, 18, 19) ResTime = os.time{year=year, month=month, day=day, hour=hour, min=minutes, sec=seconds} return ResTime end function My.TimeDiff(Time1,Time2) if Time1==nil then print(". TimeDiff with a nill value : Time1 ") ResTime=0 else if string.len(Time1)>12 then Time1 = My.ConvTime(Time1) end end if Time2==nil then print(". TimeDiff with a nill value : Time2 ") ResTime=0 else if string.len(Time2)>12 then Time2 = My.ConvTime(Time2) end ResTime=os.difftime (Time1,Time2) end return ResTime end function My.IsItGonnaRain( minutesinfuture, lat, lon ) url='http://gadgets.buienradar.nl/data/raintext?lat='..lat..'&lon='..lon if LuaDebug then print(url) end --read = os.execute('curl -s -o '..tempfilename..' "'..url..'"') read = os.execute('curl --retry 1 --connect-timeout 1 -s -o '..tempfilename..' "'..url..'"') file = io.open(tempfilename, "r") totalrain=0 rainlines=0 -- now analyse the received lines, format is like 000|15:30 per line. -- print (". http://www.google.com/maps/place/" .. lat .. "," .. lon .."/data=!3m1!1e3") -- http://www.google.com/maps/place/51.840756,4.6249/data=!3m1!1e3 while true do line = file:read("*line") if not line then break end if LuaDebug then print('Line:'..line) end linetime=string.sub(tostring(line), 5, 9) if LuaDebug then print('Linetime: '..linetime) end -- Linetime2 holds the full date calculated from the time on a line linetime2 = os.time{year=os.date('%Y'), month=os.date('%m'), day=os.date('%d'), hour=string.sub(linetime,1,2), min=string.sub(linetime,4,5), sec=os.date('%S')} difference = os.difftime (linetime2,os.time()) -- When a line entry has a time in the future AND is in the given range, then totalize the rainfall if (difference > 0) then if (difference<=minutesinfuture*60) then if LuaDebug then print('Line in time range found') end rain=tonumber(string.sub(tostring(line), 0, 3)) totalrain = totalrain+rain rainlines=rainlines+1 if LuaDebug then print('Rain in timerange: '..rain) end if LuaDebug then print('Total rain now: '..totalrain) end else if LuaDebug then print('Done processing rain data') end break end end end file:close() -- Returned value is average rain fall for next time -- 0 is no rain, 255 is very heavy rain -- When needed, mm/h is calculated by 10^((value -109)/32) (example: 77 = 0.1 mm/hour) averagerain=totalrain/rainlines return(averagerain) end return My |
download sunscreen script: sunscreen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | -- Define all the sensors which needs to be considered for the sunscreen to close local sensors = { temperature = { active = true, device = 'Daikin - buitentemperatuur', --'Buienradar - Temperature', closeRule = function(device) return device.temperature <= 19 end }, wind = { active = true, device = 'Wu - Wind', closeRule = function(device) return device.speed >= 50 or device.gust >= 150 end }, rain = { active = true, device = 'Wu - Rain', closeRule = function(device) return device.rainRate > 0 end }, rainExpected = { active = true, device = 'IsItGonnaRain', -- This needs to be a virtual sensor of type 'percentage' closeRule = function(device) return device.percentage > 15 end }, uv = { active = false, device = 'Wu - Uv', closeRule = function(device) return device.uv <= 2 end }, lux = { active = false, device = 'Lux', closeRule = function(device) return device.lux <= 500 end } } local sunscreenDevice = 'Sunscreen' -- Define the name of your sunscreen device local dryRun = 'Y' -- Enable dry run mode to test the sunscreen script without actually activating the sunscreen -- Define the name of a virtual switch which you can use to disable the sunscreen automation script, Set to false to disable this feature local manualOverrideSwitch = 'Sunscreen - Manual' local timeBetweenOpens = 10 -- Minutes to wait after a sunscreen close before opening it again. local LogFile= '/home/pi/domoticz/Logs/Sunscreen.csv ~m.csv' local LogAction = 'Y' local message = '.' return { active = true, on = { timer = {'every minute'} }, logging = { level = domoticz.LOG_DEBUG, marker = 'Sunscreen: ' }, execute = function(domoticz) -- FUNCTIONS function LogActions() LogFile=string.gsub(LogFile,"~d",os.date("%Y-%m-%d")) LogFile=string.gsub(LogFile,"~m",os.date("%Y-%m")) if not My.File_exists(LogFile) then f=io.open(LogFile,"a") f:write("Datum ; Tijd ; dryRun ; Manual_or_Auto ; Message") f:write("\r\n") else f=io.open(LogFile,"r") c=f:read("*line") f:close() f=io.open(LogFile,"a") end f:write(os.date("%Y-%m-%d;%H:%M:%S") .. ";" .. dryRun .. ";" .. domoticz.devices(manualOverrideSwitch).state .. ";" .. message ) f:write("\r\n") f:close() end local function switchOn(sunscreen, message) if LogAction == 'Y' or LogAction == 'A' then LogActions() end if (sunscreen.state == 'Closed') then if dryRun == 'N' then sunscreen.switchOn() --domoticz.notify('Sunscreen', message) end domoticz.log(message, domoticz.LOG_INFO) --domoticz.notify('Sunscreen', message) else domoticz.log('Sunscreen is already down' , domoticz.LOG_INFO) end if LogAction == 'Y' or LogAction == 'A' then LogActions() end end local function switchOff(sunscreen, message) if (sunscreen.state == 'Open') then if dryRun == 'N' then sunscreen.switchOff() end domoticz.log(message, domoticz.LOG_INFO) --domoticz.notify('Sunscreen', message) end if LogAction == 'Y' or LogAction == 'A' then LogActions() end end -- PROGRAM STARTS if (manualOverrideSwitch and domoticz.devices(manualOverrideSwitch).state == 'On') then domoticz.log('Automatic sunscreen script is manually disabled', domoticz.LOG_DEBUG) return end local sunscreen = domoticz.devices(sunscreenDevice) -- Sunscreen must always be up during nighttime if (domoticz.time.isNightTime) then switchOff(sunscreen, 'Closing sunscreen, It is night') message = 'Closing sunscreen, It is night' if LogAction == 'Y' or LogAction == 'A' then LogActions() end return end -- Check all sensor tresholds and if any exeeded close sunscreen for sensorType, sensor in pairs(sensors) do if (sensor['active'] == true) then local device = domoticz.devices(sensor['device']) local closeRule = sensor['closeRule'] domoticz.log('Checking sensor: ' .. sensorType, domoticz.LOG_DEBUG) if (closeRule(device)) then switchOff(sunscreen, sensorType .. ' treshold exceeded, Sunscreen up') domoticz.log(sensorType .. ' treshold exceeded', domoticz.LOG_DEBUG) message = (sensorType .. ' treshold exceeded') if LogAction == 'Y' or LogAction == 'A' then LogActions() end -- Return early when we exeed any tresholds return end else domoticz.log('Sensor not active skipping: ' .. sensorType, domoticz.LOG_DEBUG) end end -- All tresholds OK, sunscreen may be lowered if (sunscreen.lastUpdate.minutesAgo > timeBetweenOpens) then message = 'Sun is shining, all thresholds OK, lowering sunscreen' switchOn(sunscreen, message) end end } |
-
Design
-
Quality
-
Usability
-
Home-Automation compatibility
-
Price