Make you Sunscreen smart with dzVents Scripting

Tutorial Domoticz Sunscreen script

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.

sunscreen

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
}

One thought on “Make you Sunscreen smart with dzVents Scripting”

Comments are closed.

%d bloggers like this: