util-evo.lua /size: 36 Kb    last modification: 2021-10-28 13:50
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
util-evo
'
]
=
{
2
version
=
1
.
002
,
3
comment
=
"
library for fetching data from an evohome device
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
-- When I needed a new boiler for heating I decided to replace a partial
10
-- (experimental) zwave few-zone solution by the honeywell evohome system that can
11
-- drive opentherm. I admit that I was not that satified beforehand with the fact
12
-- that one has to go via some outside portal to communicate with the box but lets
13
-- hope that this will change (I will experiment with the additional usb interface
14
-- later). Anyway, apart from integrating it into my home automation setup so that I
15
-- can add control based on someone present in a zone, I wanted to be able to render
16
-- statistics. So that's why we have a module in ConTeXt for doing that. It's also
17
-- an example of Lua and abusing LuaTeX for something not related to typesetting.
18
--
19
-- As with other scripts, it assumes that mtxrun is used so that we have the usual
20
-- Lua libraries present.
21
--
22
-- The code is not that complex but figuring out the right request takes bit of
23
-- searching the web. There is an api specification at:
24
--
25
-- https://developer.honeywell.com/api-methods?field_smart_method_tags_tid=All
26
--
27
-- Details like the application id can be found in several places. There are snippets
28
-- of (often partial or old) code on the web but still one needs to experiment and
29
-- combine information. We assume unique zone names and ids across gateways; I only
30
-- have one installed anyway.
31
--
32
-- The original application was to just get the right information for generating
33
-- statistics but in the meantime I also use this code to add additional functionality
34
-- to the system, for instance switching between rooms (office, living room, attic) and
35
-- absence for one or more rooms.
36 37
-- todo: %path% in filenames
38 39
require
(
"
util-jsn
"
)
40 41
local
next
,
type
,
setmetatable
,
rawset
,
rawget
=
next
,
type
,
setmetatable
,
rawset
,
rawget
42
local
json
=
utilities
.
json
43
local
formatters
=
string
.
formatters
44
local
floor
,
div
=
math
.
floor
,
math
.
div
45
local
resultof
,
ostime
,
osdate
,
ossleep
=
os
.
resultof
,
os
.
time
,
os
.
date
,
os
.
sleep
46
local
jsontolua
,
jsontostring
=
json
.
tolua
,
json
.
tostring
47
local
savetable
,
loadtable
,
sortedkeys
,
sortedhash
=
table
.
save
,
table
.
load
,
table
.
sortedkeys
,
table
.
sortedhash
48
local
setmetatableindex
,
setmetatablenewindex
=
table
.
setmetatableindex
,
table
.
setmetatablenewindex
49
local
replacer
=
utilities
.
templates
.
replacer
50
local
lower
=
string
.
lower
-- no utf support yet (encoding needs checking in evohome)
51 52
local
applicationid
=
"
b013aa26-9724-4dbd-8897-048b9aada249
"
53
----- applicationid = "91db1612-73fd-4500-91b2-e63b069b185c"
54 55
local
report
=
logs
.
reporter
(
"
evohome
"
)
56
local
trace
=
false
57 58
trackers
.
register
(
"
evohome.trace
"
,
function
(
v
)
trace
=
v
end
)
-- not yet used
59 60
local
defaultpresets
=
{
61
interval
=
30
*
60
,
62
files
=
{
63
everything
=
"
evohome-everything.lua
"
,
64
history
=
"
evohome-history.lua
"
,
65
latest
=
"
evohome-latest.lua
"
,
66
schedules
=
"
evohome-schedules.lua
"
,
67
actions
=
"
evohome-actions.lua
"
,
68
template
=
"
evohome.lmx
"
,
69
}
,
70
credentials
=
{
71
-- username = "unset",
72
-- password = "unset",
73
-- accesstoken = "unset",
74
-- userid = "unset",
75
}
,
76
}
77 78
local
validzonetypes
=
{
79
ZoneTemperatureControl
=
true
,
80
RadiatorZone
=
true
,
81
ZoneValves
=
true
,
82
}
83 84
local
function
validfile
(
presets
,
filename
)
85
if
lfs
.
isfile
(
filename
)
then
86
-- we're okay
87
return
filename
88
end
89
if
file
.
pathpart
(
filename
)
~
=
"
"
then
90
-- can be a file that has to be created
91
return
filename
92
end
93
local
presetsname
=
presets
.
filename
94
if
not
presetsname
then
95
-- hope for the best
96
return
filename
97
end
98
-- we now have the full path
99
return
file
.
join
(
file
.
pathpart
(
presetsname
)
,
filename
)
100
end
101 102
local
function
validpresets
(
presets
)
103
if
type
(
presets
)
~
=
"
table
"
then
104
report
(
"
invalid presets, no table
"
)
105
return
106
end
107
local
credentials
=
presets
.
credentials
108
if
not
credentials
then
109
report
(
"
invalid presets, no credentials
"
)
110
return
111
end
112
local
gateways
=
presets
.
gateways
113
if
not
gateways
then
114
report
(
"
invalid presets, no gateways
"
)
115
return
116
end
117
local
files
=
presets
.
files
118
if
not
files
then
119
report
(
"
invalid presets, no files
"
)
120
return
121
end
122
for
k
,
v
in
next
,
files
do
123
files
[
k
]
=
validfile
(
presets
,
v
)
or
v
124
end
125
local
data
=
presets
.
data
126
if
not
data
then
127
data
=
{
}
128
presets
.
data
=
data
129
end
130
local
g
=
data
.
gateways
131
if
not
g
then
132
local
g
=
{
}
133
data
.
gateways
=
g
134
for
i
=
1
,
#
gateways
do
135
local
gi
=
gateways
[
i
]
136
g
[
gi
.
macaddress
]
=
gi
137
end
138
end
139
local
zones
=
data
.
zones
140
if
not
zones
then
141
zones
=
{
}
142
data
.
zones
=
zones
143
setmetatablenewindex
(
zones
,
function
(
t
,
k
,
v
)
rawset
(
t
,
lower
(
k
)
,
v
)
end
)
144
setmetatableindex
(
zones
,
function
(
t
,
k
)
return
rawget
(
t
,
lower
(
k
)
)
end
)
145
end
146
local
states
=
data
.
states
147
if
not
states
then
148
states
=
{
}
149
data
.
states
=
states
150
setmetatablenewindex
(
states
,
function
(
t
,
k
,
v
)
rawset
(
t
,
lower
(
k
)
,
v
)
end
)
151
setmetatableindex
(
states
,
function
(
t
,
k
)
return
rawget
(
t
,
lower
(
k
)
)
end
)
152
end
153
setmetatableindex
(
presets
,
defaultpresets
)
154
setmetatableindex
(
credentials
,
defaultpresets
.
credentials
)
155
setmetatableindex
(
files
,
defaultpresets
.
files
)
156
return
presets
157
end
158 159
local
function
loadedtable
(
filename
)
160
if
type
(
filename
)
=
=
"
string
"
then
161
for
i
=
1
,
10
do
162
local
t
=
loadtable
(
filename
)
163
if
t
then
164
report
(
"
file %a loaded
"
,
filename
)
165
return
t
166
else
167
ossleep
(
1
/
4
)
168
end
169
end
170
end
171
report
(
"
file %a not loaded
"
,
filename
)
172
return
{
}
173
end
174 175
local
function
savedtable
(
filename
,
data
,
trace
)
176
savetable
(
filename
,
data
)
177
if
trace
then
178
report
(
"
file %a saved
"
,
filename
)
179
end
180
end
181 182
local
function
loadpresets
(
filename
)
183
local
presets
=
loadtable
(
filename
)
184
if
presets
then
185
presets
.
filename
=
filename
186
presets
.
filepath
=
file
.
expandname
(
file
.
pathpart
(
filename
)
)
187
-- package.extraluapath(presets.filepath) -- better do that elsewhere and once
188
end
189
return
presets
190
end
191 192
local
function
loadhistory
(
filename
)
193
if
type
(
filename
)
=
=
"
table
"
and
validpresets
(
filename
)
then
194
filename
=
filename
.
files
and
filename
.
files
.
history
195
end
196
return
loadedtable
(
filename
)
197
end
198 199
local
function
loadeverything
(
filename
)
200
if
type
(
filename
)
=
=
"
table
"
and
validpresets
(
filename
)
then
201
filename
=
filename
.
files
and
filename
.
files
.
everything
202
end
203
return
loadedtable
(
filename
)
204
end
205 206
local
function
loadlatest
(
filename
)
207
if
type
(
filename
)
=
=
"
table
"
and
validpresets
(
filename
)
then
208
filename
=
filename
.
files
and
filename
.
files
.
latest
209
end
210
return
loadedtable
(
filename
)
211
end
212 213
local
function
result
(
t
,
fmt
,
a
,
b
,
c
)
214
if
t
then
215
report
(
fmt
,
a
or
"
done
"
,
b
or
"
done
"
,
c
or
"
done
"
,
"
done
"
)
216
return
t
217
else
218
report
(
fmt
,
a
or
"
failed
"
,
b
or
"
failed
"
,
c
or
"
failed
"
,
"
failed
"
)
219
end
220
end
221 222
local
f
=
replacer
(
223
[[
curl
]]
.
.
224
[[
--silent --insecure
]]
.
.
225
[[
-X POST
]]
.
.
226
[[
-H "Authorization: Basic YjAxM2FhMjYtOTcyNC00ZGJkLTg4OTctMDQ4YjlhYWRhMjQ5OnRlc3Q="
]]
.
.
227
[[
-H "Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml"
]]
.
.
228
[[
-d "Content-Type=application/x-www-form-urlencoded; charset=utf-8"
]]
.
.
229
[[
-d "Host=rs.alarmnet.com/"
]]
.
.
230
[[
-d "Cache-Control=no-store no-cache"
]]
.
.
231
[[
-d "Pragma=no-cache"
]]
.
.
232
[[
-d "grant_type=password"
]]
.
.
233
[[
-d "scope=EMEA-V1-Basic EMEA-V1-Anonymous EMEA-V1-Get-Current-User-Account"
]]
.
.
234
[[
-d "Username=%username%"
]]
.
.
235
[[
-d "Password=%password%"
]]
.
.
236
[[
-d "Connection=Keep-Alive"
]]
.
.
237
[[
"https://tccna.honeywell.com/Auth/OAuth/Token"
]]
238
)
239 240
local
function
getaccesstoken
(
presets
)
241
if
validpresets
(
presets
)
then
242
local
c
=
presets
.
credentials
243
local
s
=
c
and
f
{
244
username
=
c
.
username
,
245
password
=
c
.
password
,
246
applicationid
=
applicationid
,
247
}
248
local
r
=
s
and
resultof
(
s
)
249
local
t
=
r
and
jsontolua
(
r
)
250
return
result
(
t
,
"
getting access token %a
"
)
251
end
252
return
result
(
false
,
"
getting access token %a
"
)
253
end
254 255
local
f
=
replacer
(
256
[[
curl
]]
.
.
257
[[
--silent --insecure
]]
.
.
258
[[
-H "Authorization: bearer %accesstoken%"
]]
.
.
259
[[
-H "Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml"
]]
.
.
260
[[
-H "applicationId: %applicationid%"
]]
.
.
261
[[
"https://tccna.honeywell.com/WebAPI/emea/api/v1/userAccount"
]]
262
)
263 264
local
function
getuserinfo
(
presets
)
265
if
validpresets
(
presets
)
then
266
local
c
=
presets
.
credentials
267
local
s
=
c
and
f
{
268
accesstoken
=
c
.
accesstoken
,
269
applicationid
=
c
.
applicationid
,
270
}
271
local
r
=
s
and
resultof
(
s
)
272
local
t
=
r
and
jsontolua
(
r
)
273
return
result
(
t
,
"
getting user info for %a
"
)
274
end
275
return
result
(
false
,
"
getting user info for %a
"
)
276
end
277 278
local
f
=
replacer
(
279
[[
curl
]]
.
.
280
[[
--silent --insecure
]]
.
.
281
[[
-H "Authorization: bearer %accesstoken%"
]]
.
.
282
[[
-H "Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml"
]]
.
.
283
[[
-H "applicationId: %applicationid%"
]]
.
.
284
[[
"https://tccna.honeywell.com/WebAPI/emea/api/v1/location/installationInfo?userId=%userid%&includeTemperatureControlSystems=True"
]]
285
)
286 287
local
function
getlocationinfo
(
presets
)
288
if
validpresets
(
presets
)
then
289
local
c
=
presets
.
credentials
290
local
s
=
c
and
f
{
291
accesstoken
=
c
.
accesstoken
,
292
applicationid
=
applicationid
,
293
userid
=
c
.
userid
,
294
}
295
local
r
=
s
and
resultof
(
s
)
296
local
t
=
r
and
jsontolua
(
r
)
297
return
result
(
t
,
"
getting location info for %a
"
)
298
end
299
return
result
(
false
,
"
getting location info for %a
"
)
300
end
301 302
local
f
=
replacer
(
303
[[
curl
]]
.
.
304
[[
--silent --insecure
]]
.
.
305
[[
-H "Authorization: bearer %accesstoken%"
]]
.
.
306
[[
-H "Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml"
]]
.
.
307
[[
-H "applicationId: %applicationid%"
]]
.
.
308
[[
"https://tccna.honeywell.com/WebAPI/emea/api/v1/temperatureZone/%zoneid%/schedule"
]]
309
)
310 311
local
function
getschedule
(
presets
,
zonename
)
312
if
validpresets
(
presets
)
then
313
local
zoneid
=
presets
.
data
.
zones
[
zonename
]
.
zoneId
314
if
zoneid
then
315
local
c
=
presets
.
credentials
316
local
s
=
c
and
f
{
317
accesstoken
=
c
.
accesstoken
,
318
applicationid
=
applicationid
,
319
zoneid
=
zoneid
,
320
}
321
local
r
=
s
and
resultof
(
s
)
322
local
t
=
r
and
jsontolua
(
r
)
323
return
result
(
t
,
"
getting schedule for zone %a, %s
"
,
zonename
or
"
?
"
)
324
end
325
end
326
return
result
(
false
,
"
getting schedule for zone %a, %s
"
,
zonename
or
"
?
"
)
327
end
328 329
local
f
=
replacer
(
330
[[
curl
]]
.
.
331
[[
--silent --insecure
]]
.
.
332
[[
-H "Authorization: bearer %accesstoken%"
]]
.
.
333
[[
-H "Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml"
]]
.
.
334
[[
-H "applicationId: %applicationid%"
]]
.
.
335
[[
"https://tccna.honeywell.com/WebAPI/emea/api/v1/location/%locationid%/status?includeTemperatureControlSystems=True"
]]
336
)
337 338
local
function
getstatus
(
presets
,
locationid
,
locationname
)
339
if
locationid
and
validpresets
(
presets
)
then
340
local
c
=
presets
.
credentials
341
local
s
=
c
and
f
{
342
accesstoken
=
c
.
accesstoken
,
343
applicationid
=
applicationid
,
344
locationid
=
locationid
,
345
}
346
local
r
=
s
and
resultof
(
s
)
347
local
t
=
r
and
jsontolua
(
r
)
348
return
result
(
t
and
t
.
gateways
and
t
,
"
getting status for location %a, %s
"
,
locationname
or
"
?
"
)
349
end
350
return
result
(
false
,
"
getting status for location %a, %s
"
,
locationname
or
"
?
"
)
351
end
352 353
local
function
validated
(
presets
)
354
if
validpresets
(
presets
)
then
355
local
data
=
getlocationinfo
(
presets
)
356
if
data
and
type
(
data
)
=
=
"
table
"
and
data
[
1
]
and
data
[
1
]
.
locationInfo
then
357
return
true
358
else
359
local
data
=
getaccesstoken
(
presets
)
360
if
data
then
361
presets
.
credentials
.
accesstoken
=
data
.
access_token
362
local
data
=
getuserinfo
(
presets
)
363
if
data
then
364
presets
.
credentials
.
userid
=
data
.
userId
365
return
true
366
end
367
end
368
end
369
end
370
end
371 372
local
function
findzone
(
presets
,
name
)
373
if
not
presets
then
374
return
375
end
376
local
data
=
presets
.
data
377
if
not
data
then
378
return
379
end
380
local
usedzones
=
data
.
zones
381
return
usedzones
and
usedzones
[
name
]
382
end
383 384
local
function
getzonenames
(
presets
)
385
if
not
presets
then
386
return
{
}
387
end
388
local
data
=
presets
.
data
389
if
not
data
then
390
return
{
}
391
end
392
local
t
=
sortedkeys
(
data
.
zones
or
{
}
)
393
for
i
=
1
,
#
t
do
394
t
[
i
]
=
lower
(
t
[
i
]
)
395
end
396
return
t
397
end
398 399
local
function
gettargets
(
zone
)
-- maybe also for a day
400
local
schedule
=
zone
.
schedule
401
local
min
=
false
402
local
max
=
false
403
if
schedule
then
404
local
schedules
=
schedule
.
dailySchedules
405
if
schedules
then
406
for
i
=
1
,
#
schedules
do
407
local
switchpoints
=
schedules
[
i
]
.
switchpoints
408
for
i
=
1
,
#
switchpoints
do
409
local
m
=
switchpoints
[
i
]
.
temperature
410
if
not
min
or
m
<
min
then
411
min
=
m
412
end
413
if
not
max
or
m
>
max
then
414
max
=
m
415
end
416
end
417
end
418
else
419
report
(
"
zone %a has no schedule
"
,
name
)
420
end
421
end
422
return
min
,
max
423
end
424 425
local
function
updatezone
(
presets
,
name
,
zone
)
426
if
not
zone
then
427
zone
=
findzone
(
presets
,
name
)
428
end
429
if
zone
then
430
local
oldtarget
=
presets
.
data
.
states
[
name
]
431
local
min
=
zone
.
heatSetpointCapabilities
.
minHeatSetpoint
or
5
432
local
max
=
zone
.
heatSetpointCapabilities
.
maxHeatSetpoint
or
12
433
local
mintarget
,
maxtarget
=
gettargets
(
zone
)
434
-- todo: maybe get these from presets
435
if
mintarget
=
=
false
then
436
if
min
<
5
then
437
mintarget
=
5
438
-- report("zone %a, min target limited to %a",name,mintarget)
439
else
440
mintarget
=
min
441
end
442
end
443
if
maxtarget
=
=
false
then
444
if
max
>
18
.
5
then
445
maxtarget
=
18
.
5
446
-- report("zone %a, max target limited to %a",name,maxtarget)
447
else
448
maxtarget
=
max
449
end
450
end
451
local
current
=
zone
.
temperatureStatus
.
temperature
or
0
452
local
target
=
zone
.
heatSetpointStatus
.
targetTemperature
453
local
mode
=
zone
.
heatSetpointStatus
.
setpointMode
454
local
state
=
(
mode
=
=
"
FollowSchedule
"
and
"
schedule
"
)
or
455
(
mode
=
=
"
PermanentOverride
"
and
target
<
=
mintarget
and
"
permanent
"
)
or
456
(
mode
=
=
"
TemporaryOverride
"
and
target
<
=
mintarget
and
"
off
"
)
or
457
(
mode
=
=
"
TemporaryOverride
"
and
target
>
=
maxtarget
and
"
on
"
)
or
458
(
"
unknown
"
)
459
local
t
=
{
460
name
=
zone
.
name
,
461
id
=
zone
.
zoneId
,
462
schedule
=
zone
.
schedule
,
463
mode
=
mode
,
464
current
=
current
,
465
target
=
target
,
466
min
=
min
,
467
max
=
max
,
468
state
=
state
,
469
lowest
=
mintarget
,
470
highest
=
maxtarget
,
471
}
472
-- report("zone %a, current %a, target %a",name,current,target)
473
presets
.
data
.
states
[
name
]
=
t
474
return
t
475
end
476
end
477 478 479
local
function
geteverything
(
presets
,
noschedules
)
480
if
validated
(
presets
)
then
481
local
data
=
getlocationinfo
(
presets
)
482
if
data
then
483
local
usedgateways
=
presets
.
data
.
gateways
484
local
usedzones
=
presets
.
data
.
zones
485
for
i
=
1
,
#
data
do
486
local
gateways
=
data
[
i
]
.
gateways
487
local
locationinfo
=
data
[
i
]
.
locationInfo
488
local
locationid
=
locationinfo
and
locationinfo
.
locationId
489
if
gateways
and
locationid
then
490
local
status
=
getstatus
(
presets
,
locationid
,
locationinfo
.
name
)
491
if
status
then
492
for
i
=
1
,
#
gateways
do
493
local
gatewaystatus
=
status
.
gateways
[
i
]
494
local
gatewayinfo
=
gateways
[
i
]
495
local
gatewaysystems
=
gatewayinfo
.
temperatureControlSystems
496
local
info
=
gatewayinfo
.
gatewayInfo
497
local
statussystems
=
gatewaystatus
.
temperatureControlSystems
498
if
gatewaysystems
and
statussystems
and
info
then
499
local
mac
=
info
.
mac
500
if
usedgateways
[
mac
]
then
501
report
(
"
%s gateway with mac address %a
"
,
"
using
"
,
mac
)
502
for
j
=
1
,
#
gatewaysystems
do
503
local
gatewayzones
=
gatewaysystems
[
j
]
.
zones
504
local
zonestatus
=
statussystems
[
j
]
.
zones
505
if
gatewayzones
and
zonestatus
then
506
for
k
=
1
,
#
gatewayzones
do
507
local
zonestatus
=
zonestatus
[
k
]
508
local
gatewayzone
=
gatewayzones
[
k
]
509
if
zonestatus
and
gatewayzone
then
510
local
zonename
=
zonestatus
.
name
511
local
zoneid
=
zonestatus
.
zoneId
512
if
validzonetypes
[
gatewayzone
.
zoneType
]
and
zonename
=
=
gatewayzone
.
name
then
513
gatewayzone
.
heatSetpointStatus
=
zonestatus
.
heatSetpointStatus
514
gatewayzone
.
temperatureStatus
=
zonestatus
.
temperatureStatus
515
local
zonestatus
=
usedzones
[
zonename
]
-- findzone(states,zonename)
516
local
schedule
=
zonestatus
and
zonestatus
.
schedule
517
usedzones
[
zonename
]
=
gatewayzone
518
if
schedule
and
noschedules
then
519
gatewayzone
.
schedule
=
schedule
520
else
521
gatewayzone
.
schedule
=
getschedule
(
presets
,
zonename
)
522
end
523
updatezone
(
presets
,
zonename
,
gatewayzone
)
524
end
525
end
526
end
527
end
528
end
529
else
530
report
(
"
%s gateway with mac address %a
"
,
"
skipping
"
,
mac
)
531
end
532
end
533
end
534
end
535
end
536
end
537
savedtable
(
presets
.
files
.
everything
,
data
)
538
return
result
(
data
,
"
getting everything, %s
"
)
539
end
540
end
541
return
result
(
false
,
"
getting everything, %s
"
)
542
end
543 544
local
function
gettemperatures
(
presets
)
545
if
validated
(
presets
)
then
546
local
data
=
loadeverything
(
presets
)
547
if
not
data
or
not
next
(
data
)
then
548
data
=
geteverything
(
presets
)
549
end
550
if
data
then
551
local
updated
=
false
552
for
i
=
1
,
#
data
do
553
local
gateways
=
data
[
i
]
.
gateways
554
local
locationinfo
=
data
[
i
]
.
locationInfo
555
if
locationinfo
then
556
local
locationid
=
locationinfo
.
locationId
557
if
gateways
then
558
local
status
=
getstatus
(
presets
,
locationid
,
locationinfo
.
name
)
559
if
status
then
560
for
i
=
1
,
#
gateways
do
561
local
g
=
status
.
gateways
[
i
]
562
local
gateway
=
gateways
[
i
]
563
local
systems
=
gateway
.
temperatureControlSystems
564
if
systems
then
565
local
s
=
g
.
temperatureControlSystems
566
for
i
=
1
,
#
systems
do
567
local
zones
=
systems
[
i
]
.
zones
568
if
zones
then
569
local
z
=
s
[
i
]
.
zones
570
for
i
=
1
,
#
zones
do
571
local
zone
=
zones
[
i
]
572
if
validzonetypes
[
zone
.
zoneType
]
then
573
local
z
=
z
[
i
]
574
if
z
.
name
=
=
zone
.
name
then
575
zone
.
temperatureStatus
=
z
.
temperatureStatus
576
updated
=
true
577
end
578
end
579
end
580
end
581
end
582
end
583
end
584
end
585
else
586
report
(
"
no gateways
"
)
587
end
588
else
589
report
(
"
no location info
"
)
590
end
591
end
592
if
updated
then
593
data
.
time
=
ostime
(
)
594
savedtable
(
presets
.
files
.
latest
,
data
)
595
end
596
return
result
(
data
,
"
getting temperatures, %s
"
)
597
end
598
end
599
return
result
(
false
,
"
getting temperatures, %s
"
)
600
end
601 602
local
function
setmoment
(
target
,
time
,
data
)
603
if
not
time
then
604
time
=
ostime
(
)
605
end
606
local
t
=
osdate
(
"
*t
"
,
time
)
607
local
c_year
,
c_month
,
c_day
,
c_hour
,
c_minute
=
t
.
year
,
t
.
month
,
t
.
day
,
t
.
hour
,
t
.
min
608
--
609
local
years
=
target
.
years
if
not
years
then
years
=
{
}
target
.
years
=
years
end
610
local
d_year
=
years
[
c_year
]
if
not
d_year
then
d_year
=
{
}
years
[
c_year
]
=
d_year
end
611
local
months
=
d_year
.
months
if
not
months
then
months
=
{
}
d_year
.
months
=
months
end
612
local
d_month
=
months
[
c_month
]
if
not
d_month
then
d_month
=
{
}
months
[
c_month
]
=
d_month
end
613
local
days
=
d_month
.
days
if
not
days
then
days
=
{
}
d_month
.
days
=
days
end
614
local
d_day
=
days
[
c_day
]
if
not
d_day
then
d_day
=
{
}
days
[
c_day
]
=
d_day
end
615
local
hours
=
d_day
.
hours
if
not
hours
then
hours
=
{
}
d_day
.
hours
=
hours
end
616
local
d_hour
=
hours
[
c_hour
]
if
not
d_hour
then
d_hour
=
{
}
hours
[
c_hour
]
=
d_hour
end
617
--
618
c_minute
=
div
(
c_minute
,
15
)
+
1
619
--
620
local
d_last
=
d_hour
[
c_minute
]
621
if
d_last
then
622
for
k
,
v
in
next
,
data
do
623
local
d
=
d_last
[
k
]
624
if
d
then
625
data
[
k
]
=
(
d
+
v
)
/
2
626
end
627
end
628
end
629
d_hour
[
c_minute
]
=
data
630
--
631
target
.
lasttime
=
{
632
year
=
c_year
,
633
month
=
c_month
,
634
day
=
c_day
,
635
hour
=
c_hour
,
636
minute
=
c_minute
,
637
}
638
end
639 640
local
function
loadtemperatures
(
presets
)
641
if
validpresets
(
presets
)
then
642
local
status
=
loadlatest
(
presets
)
643
if
not
status
or
not
next
(
status
)
then
644
status
=
loadeverything
(
presets
)
645
end
646
if
status
then
647
local
usedgateways
=
presets
.
data
.
gateways
648
for
i
=
1
,
#
status
do
649
local
gateways
=
status
[
i
]
.
gateways
650
if
gateways
then
651
for
i
=
1
,
#
gateways
do
652
local
gatewayinfo
=
gateways
[
i
]
653
local
systems
=
gatewayinfo
.
temperatureControlSystems
654
local
info
=
gatewayinfo
.
gatewayInfo
655
if
systems
and
info
and
usedgateways
[
info
.
mac
]
then
656
for
i
=
1
,
#
systems
do
657
local
zones
=
systems
[
i
]
.
zones
658
if
zones
then
659
local
summary
=
{
time
=
status
.
time
}
660
for
i
=
1
,
#
zones
do
661
local
zone
=
zones
[
i
]
662
if
validzonetypes
[
zone
.
zoneType
]
then
663
summary
[
#
summary
+
1
]
=
updatezone
(
presets
,
zone
.
name
,
zone
)
664
end
665
end
666
return
result
(
summary
,
"
loading temperatures, %s
"
)
667
end
668
end
669
end
670
end
671
end
672
end
673
end
674
end
675
return
result
(
false
,
"
loading temperatures, %s
"
)
676
end
677 678
local
function
updatetemperatures
(
presets
)
679
if
validpresets
(
presets
)
then
680
local
everythingname
=
presets
.
files
.
everything
681
local
latestname
=
presets
.
files
.
latest
682
local
historyname
=
presets
.
files
.
history
683
if
(
everythingname
or
latestname
)
and
historyname
then
684
gettemperatures
(
presets
)
685
local
t
=
loadtemperatures
(
presets
)
686
if
t
then
687
local
data
=
{
}
688
for
i
=
1
,
#
t
do
689
local
ti
=
t
[
i
]
690
data
[
ti
.
name
]
=
ti
.
current
691
end
692
local
history
=
loadhistory
(
historyname
)
or
{
}
693
setmoment
(
history
,
ostime
(
)
,
data
)
694
savedtable
(
historyname
,
history
)
695
return
result
(
t
,
"
updating temperatures, %s
"
)
696
end
697
end
698
end
699
return
result
(
false
,
"
updating temperatures, %s
"
)
700
end
701 702
local
function
getzonestate
(
presets
,
name
)
703
return
validpresets
(
presets
)
and
presets
.
data
.
states
[
name
]
704
end
705 706
local
f
=
replacer
(
707
[[
curl
]]
.
.
708
[[
--silent --insecure
]]
.
.
709
[[
-X PUT
]]
.
.
710
[[
-H "Authorization: bearer %accesstoken%"
]]
.
.
711
[[
-H "Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml"
]]
.
.
712
[[
-H "applicationId: %applicationid%"
]]
.
.
713
[[
-H "Content-Type: application/json"
]]
.
.
714
[[
-d "%[settings]%"
]]
.
.
715
[[
"https://tccna.honeywell.com/WebAPI/emea/api/v1/temperatureZone/%zoneid%/heatSetpoint"
]]
716
)
717 718
local
function
untilmidnight
(
)
719
local
t
=
osdate
(
"
*t
"
)
720
t
.
hour
=
23
721
t
.
min
=
59
722
t
.
sec
=
59
723
return
osdate
(
"
%Y-%m-%dT%H:%M:%SZ
"
,
ostime
(
t
)
)
724
end
725 726
local
followschedule
=
{
727
-- HeatSetpointValue = 0,
728
SetpointMode
=
"
FollowSchedule
"
,
729
}
730 731
local
function
setzonestate
(
presets
,
name
,
temperature
,
permanent
)
732
local
zone
=
findzone
(
presets
,
name
)
733
if
zone
then
734
local
m
=
followschedule
735
if
type
(
temperature
)
=
=
"
number
"
and
temperature
>
0
then
736
if
permanent
then
737
m
=
{
738
HeatSetpointValue
=
temperature
,
739
SetpointMode
=
"
PermanentOverride
"
,
740
}
741
else
742
m
=
{
743
HeatSetpointValue
=
temperature
,
744
SetpointMode
=
"
TemporaryOverride
"
,
745
TimeUntil
=
untilmidnight
(
)
,
746
}
747
end
748
end
749
local
s
=
f
{
750
accesstoken
=
presets
.
credentials
.
accesstoken
,
751
applicationid
=
applicationid
,
752
zoneid
=
zone
.
zoneId
,
753
settings
=
jsontostring
(
m
)
,
754
}
755
local
r
=
s
and
resultof
(
s
)
756
local
t
=
r
and
jsontolua
(
r
)
757
-- inspect(r)
758
-- inspect(t)
759
return
result
(
t
,
"
setting state of zone %a, %s
"
,
name
)
760
end
761
return
result
(
false
,
"
setting state of zone %a, %s
"
,
name
)
762
end
763 764
local
function
resetzonestate
(
presets
,
name
)
765
setzonestate
(
presets
,
name
)
766
end
767 768
--
769 770
local
function
update
(
presets
,
noschedules
)
771
local
everything
=
geteverything
(
presets
,
noschedules
)
772
if
everything
then
773
presets
.
data
.
everything
=
everything
774
return
presets
775
end
776
end
777 778
local
function
initialize
(
filename
)
779
local
presets
=
loadpresets
(
filename
)
780
if
presets
then
781
return
update
(
presets
)
782
end
783
end
784 785
local
function
off
(
presets
,
name
)
786
local
zone
=
presets
and
getzonestate
(
presets
,
name
)
787
if
zone
then
788
setzonestate
(
presets
,
name
,
zone
.
lowest
)
789
end
790
end
791 792
local
function
on
(
presets
,
name
,
temperature
)
793
local
zone
=
presets
and
getzonestate
(
presets
,
name
)
794
if
zone
then
795
setzonestate
(
presets
,
name
,
temperature
or
zone
.
highest
)
796
end
797
end
798 799
local
function
schedule
(
presets
,
name
)
800
local
zone
=
presets
and
getzonestate
(
presets
,
name
)
801
if
zone
then
802
resetzonestate
(
presets
,
name
)
803
end
804
end
805 806
local
function
permanent
(
presets
,
name
)
807
local
zone
=
presets
and
getzonestate
(
presets
,
name
)
808
if
zone
then
809
setzonestate
(
presets
,
name
,
zone
.
lowest
,
true
)
810
end
811
end
812 813
-- tasks
814 815
local
function
settask
(
presets
,
when
,
tag
,
action
)
816
if
when
=
=
"
tomorrow
"
then
817
local
list
=
presets
.
scheduled
818
if
not
list
then
819
list
=
loadtable
(
presets
.
files
.
schedules
)
or
{
}
820
presets
.
scheduled
=
list
821
end
822
if
action
then
823
list
[
tag
]
=
{
824
time
=
ostime
(
)
+
24
*
60
*
60
,
825
done
=
false
,
826
category
=
category
,
827
action
=
action
,
828
tag
=
tag
,
829
}
830
else
831
list
[
tag
]
=
nil
832
end
833
savedtable
(
presets
.
files
.
schedules
,
list
,
false
)
834
end
835
end
836 837
local
function
gettask
(
presets
,
when
,
tag
)
838
if
when
=
=
"
tomorrow
"
then
839
local
list
=
presets
.
scheduled
840
if
not
list
then
841
list
=
loadtable
(
presets
.
files
.
schedules
)
or
{
}
842
presets
.
scheduled
=
list
843
end
844
return
list
[
tag
]
845
end
846
end
847 848
local
function
resettask
(
presets
,
when
,
tag
)
849
settask
(
presets
,
when
,
tag
)
850
end
851 852
local
function
checktasks
(
presets
)
853
local
list
=
presets
.
scheduled
854
if
not
list
then
855
list
=
loadtable
(
presets
.
files
.
schedules
)
or
{
}
856
presets
.
scheduled
=
list
857
end
858
if
list
then
859
local
t
=
osdate
(
"
*t
"
)
860
local
q
=
{
}
861
for
k
,
v
in
next
,
list
do
862
local
d
=
osdate
(
"
*t
"
,
v
.
time
)
863
if
not
v
.
done
and
d
.
year
=
=
t
.
year
and
d
.
month
=
=
t
.
month
and
d
.
day
=
=
t
.
day
then
864
local
a
=
v
.
action
865
if
type
(
a
)
=
=
"
function
"
then
866
a
(
)
867
end
868
v
.
done
=
true
869
end
870
if
d
.
year
<
=
t
.
year
and
d
.
month
<
=
t
.
month
and
d
.
day
<
t
.
day
then
871
q
[
k
]
=
true
872
end
873
end
874
if
next
(
q
)
then
875
for
k
,
v
in
next
,
q
do
876
list
[
q
]
=
nil
877
end
878
savedtable
(
presets
.
files
.
schedules
,
list
)
879
end
880
return
list
881
end
882
end
883 884
-- predefined tasks
885 886
local
function
settomorrow
(
presets
,
tag
,
action
)
887
settask
(
presets
,
"
tomorrow
"
,
tag
,
action
)
888
end
889 890
local
function
resettomorrow
(
presets
,
tag
)
891
settask
(
presets
,
"
tomorrow
"
,
tag
)
892
end
893 894
local
function
tomorrowset
(
presets
,
tag
)
895
return
gettask
(
presets
,
"
tomorrow
"
,
tag
)
and
true
or
false
896
end
897 898
--
899 900
local
evohome
901 902
local
function
poller
(
presets
)
903
--
904
if
type
(
presets
)
~
=
"
string
"
then
905
report
(
"
invalid presets file
"
)
906
os
.
exit
(
)
907
end
908
report
(
"
loading presets from %a
"
,
presets
)
909
local
presets
=
loadpresets
(
presets
)
910
if
not
validpresets
(
presets
)
then
911
report
(
"
invalid presets, aborting
"
)
912
os
.
exit
(
)
913
end
914
--
915
local
actions
=
presets
.
files
.
actions
916
if
type
(
actions
)
~
=
"
string
"
then
917
report
(
"
invalid actions file
"
)
918
os
.
exit
(
)
919
end
920
report
(
"
loading actions from %a
"
,
actions
)
921
local
actions
=
loadtable
(
actions
)
922
if
type
(
actions
)
~
=
"
table
"
then
923
report
(
"
invalid actions, aborting
"
)
924
os
.
exit
(
)
925
end
926
actions
=
actions
.
actions
927
if
type
(
actions
)
~
=
"
table
"
then
928
report
(
"
invalid actions file, no actions subtable
"
)
929
os
.
exit
(
)
930
end
931
--
932
report
(
"
updating device status
"
)
933
update
(
presets
)
934
--
935
presets
.
report
=
report
936
presets
.
evohome
=
evohome
937
presets
.
results
=
{
}
938
--
939
function
presets
.
getstate
(
name
)
940
return
getzonestate
(
presets
,
name
)
941
end
942
function
presets
.
tomorrowset
(
name
)
943
return
tomorrowset
(
presets
,
name
)
944
end
945
--
946
local
template
=
actions
.
template
or
presets
.
files
.
template
947
--
948
local
process
=
function
(
t
)
949
local
category
=
t
.
category
950
local
action
=
t
.
action
951
if
category
and
action
then
952
local
c
=
actions
[
category
]
953
if
c
then
954
local
a
=
c
[
action
]
955
if
type
(
a
)
=
=
"
function
"
then
956
report
(
"
category %a, action %a, executing
"
,
category
,
action
)
957
presets
.
results
.
template
=
template
-- can be overloaded by action
958
a
(
presets
)
959
update
(
presets
,
true
)
960
else
961
report
(
"
category %a, action %a, invalid action, known: %, t
"
,
category
,
action
,
sortedkeys
(
c
)
)
962
end
963
else
964
report
(
"
category %a, action %a, invalid category, known categories: %, t
"
,
category
,
action
,
sortedkeys
(
actions
)
)
965
end
966
else
967
-- logs.report("invalid category and action")
968
end
969
end
970
--
971
local
delay
=
presets
.
delay
or
10
972
local
interval
=
15
*
60
-- 15 minutes
973
local
interval
=
60
*
60
-- 60 minutes
974
local
refresh
=
5
*
60
975
local
passed
=
0
976
local
step
=
function
(
)
977
if
passed
>
interval
then
978
report
(
"
refreshing states, every %i seconds
"
,
interval
)
979
-- todo: update stepwise as this also updates the schedules that we don't really
980
-- change often and definitely not in the middle of the night, so maybe just
981
-- update 9:00 12:00 15:00 18:00 21:00
982
update
(
presets
)
983
passed
=
0
984
else
985
passed
=
passed
+
delay
986
end
987
checktasks
(
presets
)
988
return
delay
989
end
990
--
991
presets
.
refreshtime
=
refresh
992
--
993
return
step
,
process
,
presets
994
end
995 996
local
function
alloff
(
presets
)
997
local
zones
=
getzonenames
(
presets
)
998
if
zones
then
999
for
i
=
1
,
#
zones
do
1000
setzonestate
(
presets
,
zones
[
i
]
,
5
,
true
)
1001
end
1002
end
1003
end
1004 1005
--
1006 1007
evohome
=
{
1008
helpers
=
{
1009
getaccesstoken
=
getaccesstoken
,
-- presets
1010
getuserinfo
=
getuserinfo
,
-- presets
1011
getlocationinfo
=
getlocationinfo
,
-- presets
1012
getschedule
=
getschedule
,
-- presets, name
1013
--
1014
geteverything
=
geteverything
,
-- presets, noschedules
1015
gettemperatures
=
gettemperatures
,
-- presets
1016
getzonestate
=
getzonestate
,
-- presets, name
1017
setzonestate
=
setzonestate
,
-- presets, name, temperature
1018
resetzonestate
=
resetzonestate
,
-- presets, name
1019
getzonedata
=
findzone
,
-- presets, name
1020
getzonenames
=
getzonenames
,
-- presets
1021
--
1022
loadpresets
=
loadpresets
,
-- filename
1023
loadhistory
=
loadhistory
,
-- presets | filename
1024
loadeverything
=
loadeverything
,
-- presets | filename
1025
loadtemperatures
=
loadtemperatures
,
-- presets | filename
1026
--
1027
updatetemperatures
=
updatetemperatures
,
-- presets
1028
}
,
1029
actions
=
{
1030
initialize
=
initialize
,
-- filename
1031
update
=
update
,
-- presets
1032
--
1033
off
=
off
,
-- presets, name
1034
on
=
on
,
-- presets, name
1035
schedule
=
schedule
,
-- presets, name
1036
permanent
=
permanent
,
-- presets, name
1037
--
1038
alloff
=
alloff
,
-- presets
1039
--
1040
settomorrow
=
settomorrow
,
-- presets, tag, function
1041
resettomorrow
=
resettomorrow
,
-- presets, tag
1042
tomorrowset
=
tomorrowset
,
-- presets, tag
1043
--
1044
poller
=
poller
,
-- presets
1045
}
1046
}
1047 1048
if
utilities
then
1049
utilities
.
evohome
=
evohome
1050
end
1051 1052
-- local presets = evohome.helpers.loadpresets("c:/data/develop/domotica/code/evohome-presets.lua")
1053
-- evohome.helpers.setzonestate(presets,"Voorkamer",22)
1054
-- evohome.helpers.setzonestate(presets,"Voorkamer")
1055 1056
return
evohome
1057 1058