util-you.lua /size: 14 Kb    last modification: 2020-07-01 14:35
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
util-you
'
]
=
{
2
version
=
1
.
002
,
3
comment
=
"
library for fetching data from youless kwh meter polling device
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
-- See mtx-youless.lua and s-youless.mkiv for examples of usage.
10
--
11
-- todo: already calculate min, max and average per hour and discard
12
-- older data, or maybe a condense option
13
--
14
-- maybe just a special parser but who cares about speed here
15
--
16
-- curl -c pw.txt http://192.168.2.50/L?w=pwd
17
-- curl -b pw.txt http://192.168.2.50/V?...
18
--
19
-- the socket library barks on an (indeed) invalid header ... unfortunately we cannot
20
-- pass a password with each request ... although the youless is a rather nice gadget,
21
-- the weak part is in the http polling
22 23
require
(
"
util-jsn
"
)
24 25
-- the library variant:
26 27
utilities
=
utilities
or
{
}
28
local
youless
=
{
}
29
utilities
.
youless
=
youless
30 31
local
lpegmatch
=
lpeg
.
match
32
local
formatters
=
string
.
formatters
33 34
local
tonumber
,
type
,
next
=
tonumber
,
type
,
next
35 36
local
round
,
div
=
math
.
round
,
math
.
div
37
local
osdate
,
ostime
=
os
.
date
,
os
.
time
38 39
local
report
=
logs
.
reporter
(
"
youless
"
)
40
local
trace
=
false
41 42
-- dofile("http.lua")
43 44
local
http
=
socket
.
http
45 46
-- f=j : json
47 48
local
f_password
=
formatters
[
"
http://%s/L?w=%s
"
]
49 50
local
f_fetchers
=
{
51
electricity
=
formatters
[
"
http://%s/V?%s=%i&f=j
"
]
,
52
gas
=
formatters
[
"
http://%s/W?%s=%i&f=j
"
]
,
53
pulse
=
formatters
[
"
http://%s/Z?%s=%i&f=j
"
]
,
54
}
55 56
local
function
fetch
(
url
,
password
,
what
,
i
,
category
)
57
local
fetcher
=
f_fetchers
[
category
or
"
electricity
"
]
58
if
not
fetcher
then
59
report
(
"
invalid fetcher %a
"
,
category
)
60
else
61
local
url
=
fetcher
(
url
,
what
,
i
)
62
local
data
,
h
=
http
.
request
(
url
)
63
local
result
=
data
and
utilities
.
json
.
tolua
(
data
)
64
return
result
65
end
66
end
67 68
-- "123" " 23" " 1,234"
69 70
local
tovalue
=
lpeg
.
Cs
(
(
lpeg
.
R
(
"
09
"
)
+
lpeg
.
P
(
1
)
/
"
"
)
^
1
)
/
tonumber
71 72
-- "2013-11-12T06:40:00"
73 74
local
totime
=
(
lpeg
.
C
(
4
)
/
tonumber
)
*
lpeg
.
P
(
"
-
"
)
75
*
(
lpeg
.
C
(
2
)
/
tonumber
)
*
lpeg
.
P
(
"
-
"
)
76
*
(
lpeg
.
C
(
2
)
/
tonumber
)
*
lpeg
.
P
(
"
T
"
)
77
*
(
lpeg
.
C
(
2
)
/
tonumber
)
*
lpeg
.
P
(
"
:
"
)
78
*
(
lpeg
.
C
(
2
)
/
tonumber
)
*
lpeg
.
P
(
"
:
"
)
79
*
(
lpeg
.
C
(
2
)
/
tonumber
)
80 81
local
function
collapsed
(
data
,
dirty
)
82
for
list
,
parent
in
next
,
dirty
do
83
local
t
,
n
=
{
}
,
{
}
84
for
k
,
v
in
next
,
list
do
85
local
d
=
div
(
k
,
10
)
*
10
86
t
[
d
]
=
(
t
[
d
]
or
0
)
+
v
87
n
[
d
]
=
(
n
[
d
]
or
0
)
+
1
88
end
89
for
k
,
v
in
next
,
t
do
90
t
[
k
]
=
round
(
t
[
k
]
/
n
[
k
]
)
91
end
92
parent
[
1
]
[
parent
[
2
]
]
=
t
93
end
94
return
data
95
end
96 97
local
function
get
(
url
,
password
,
what
,
step
,
data
,
option
,
category
)
98
if
not
data
then
99
data
=
{
}
100
end
101
local
dirty
=
{
}
102
while
true
do
103
local
d
=
fetch
(
url
,
password
,
what
,
step
,
category
)
104
local
v
=
d
and
d
.
val
105
if
v
and
#
v
>
0
then
106
local
c_year
,
c_month
,
c_day
,
c_hour
,
c_minute
,
c_seconds
=
lpegmatch
(
totime
,
d
.
tm
)
107
if
c_year
and
c_seconds
then
108
local
delta
=
tonumber
(
d
.
dt
)
109
local
tnum
=
ostime
{
110
year
=
c_year
,
111
month
=
c_month
,
112
day
=
c_day
,
113
hour
=
c_hour
,
114
min
=
c_minute
,
115
sec
=
c_seconds
,
116
}
117
for
i
=
1
,
#
v
do
118
local
vi
=
v
[
i
]
119
if
vi
~
=
"
*
"
then
120
local
newvalue
=
lpegmatch
(
tovalue
,
vi
)
121
if
newvalue
then
122
local
t
=
tnum
+
(
i
-1
)
*
delta
123
-- local current = osdate("%Y-%m-%dT%H:%M:%S",t)
124
-- local c_year, c_month, c_day, c_hour, c_minute, c_seconds = lpegmatch(totime,current)
125
local
c
=
osdate
(
"
*t
"
,
tnum
+
(
i
-1
)
*
delta
)
126
local
c_year
=
c
.
year
127
local
c_month
=
c
.
month
128
local
c_day
=
c
.
day
129
local
c_hour
=
c
.
hour
130
local
c_minute
=
c
.
min
131
local
c_seconds
=
c
.
sec
132
if
c_year
and
c_seconds
then
133
local
years
=
data
.
years
if
not
years
then
years
=
{
}
data
.
years
=
years
end
134
local
d_year
=
years
[
c_year
]
if
not
d_year
then
d_year
=
{
}
years
[
c_year
]
=
d_year
end
135
local
months
=
d_year
.
months
if
not
months
then
months
=
{
}
d_year
.
months
=
months
end
136
local
d_month
=
months
[
c_month
]
if
not
d_month
then
d_month
=
{
}
months
[
c_month
]
=
d_month
end
137
local
days
=
d_month
.
days
if
not
days
then
days
=
{
}
d_month
.
days
=
days
end
138
local
d_day
=
days
[
c_day
]
if
not
d_day
then
d_day
=
{
}
days
[
c_day
]
=
d_day
end
139
if
option
=
=
"
average
"
or
option
=
=
"
total
"
then
140
if
trace
then
141
local
oldvalue
=
d_day
[
option
]
142
if
oldvalue
and
oldvalue
~
=
newvalue
then
143
report
(
"
category %s, step %i, time %s: old %s %s updated to %s
"
,
category
,
step
,
osdate
(
"
%Y-%m-%dT%H:%M:%S
"
,
t
)
,
option
,
oldvalue
,
newvalue
)
144
end
145
end
146
d_day
[
option
]
=
newvalue
147
elseif
option
=
=
"
value
"
then
148
local
hours
=
d_day
.
hours
if
not
hours
then
hours
=
{
}
d_day
.
hours
=
hours
end
149
local
d_hour
=
hours
[
c_hour
]
if
not
d_hour
then
d_hour
=
{
}
hours
[
c_hour
]
=
d_hour
end
150
if
trace
then
151
local
oldvalue
=
d_hour
[
c_minute
]
152
if
oldvalue
and
oldvalue
~
=
newvalue
then
153
report
(
"
category %s, step %i, time %s: old %s %s updated to %s
"
,
category
,
step
,
osdate
(
"
%Y-%m-%dT%H:%M:%S
"
,
t
)
,
"
value
"
,
oldvalue
,
newvalue
)
154
end
155
end
156
d_hour
[
c_minute
]
=
newvalue
157
if
not
dirty
[
d_hour
]
then
158
dirty
[
d_hour
]
=
{
hours
,
c_hour
}
159
end
160
else
161
-- can't happen
162
end
163
end
164
end
165
end
166
end
167
end
168
else
169
return
collapsed
(
data
,
dirty
)
170
end
171
step
=
step
+
1
172
end
173
return
collapsed
(
data
,
dirty
)
174
end
175 176
-- day of month (kwh)
177
-- url = http://192.168.1.14/V?m=2
178
-- m = the number of month (jan = 1, feb = 2, ..., dec = 12)
179 180
-- hour of day (watt)
181
-- url = http://192.168.1.14/V?d=1
182
-- d = the number of days ago (today = 0, yesterday = 1, etc.)
183 184
-- 10 minutes (watt)
185
-- url = http://192.168.1.14/V?w=1
186
-- w = 1 for the interval now till 8 hours ago.
187
-- w = 2 for the interval 8 till 16 hours ago.
188
-- w = 3 for the interval 16 till 24 hours ago.
189 190
-- 1 minute (watt)
191
-- url = http://192.168.1.14/V?h=1
192
-- h = 1 for the interval now till 30 minutes ago.
193
-- h = 2 for the interval 30 till 60 minutes ago
194 195
function
youless
.
collect
(
specification
)
196
if
type
(
specification
)
~
=
"
table
"
then
197
return
198
end
199
local
host
=
specification
.
host
or
"
"
200
local
data
=
specification
.
data
or
{
}
201
local
filename
=
specification
.
filename
or
"
"
202
local
variant
=
specification
.
variant
or
"
kwh
"
203
local
detail
=
specification
.
detail
or
false
204
local
nobackup
=
specification
.
nobackup
or
false
205
local
password
=
specification
.
password
or
"
"
206
local
oldstuff
=
false
207
if
host
=
=
"
"
then
208
return
209
end
210
if
filename
=
=
"
"
then
211
return
212
else
213
data
=
table
.
load
(
filename
)
or
data
214
end
215
if
variant
=
=
"
electricity
"
then
216
get
(
host
,
password
,
"
m
"
,
1
,
data
,
"
total
"
,
"
electricity
"
)
217
if
oldstuff
then
218
get
(
host
,
password
,
"
d
"
,
1
,
data
,
"
average
"
,
"
electricity
"
)
219
end
220
get
(
host
,
password
,
"
w
"
,
1
,
data
,
"
value
"
,
"
electricity
"
)
221
if
detail
then
222
get
(
host
,
password
,
"
h
"
,
1
,
data
,
"
value
"
,
"
electricity
"
)
-- todo: get this for calculating the precise max
223
end
224
elseif
variant
=
=
"
pulse
"
then
225
-- It looks like the 'd' option returns the wrong values or at least not the same sort
226
-- as the other ones, so we calculate the means ourselves. And 'w' is not consistent with
227
-- that too, so ...
228
get
(
host
,
password
,
"
m
"
,
1
,
data
,
"
total
"
,
"
pulse
"
)
229
if
oldstuff
then
230
get
(
host
,
password
,
"
d
"
,
1
,
data
,
"
average
"
,
"
pulse
"
)
231
end
232
detail
=
true
233
get
(
host
,
password
,
"
w
"
,
1
,
data
,
"
value
"
,
"
pulse
"
)
234
if
detail
then
235
get
(
host
,
password
,
"
h
"
,
1
,
data
,
"
value
"
,
"
pulse
"
)
236
end
237
elseif
variant
=
=
"
gas
"
then
238
get
(
host
,
password
,
"
m
"
,
1
,
data
,
"
total
"
,
"
gas
"
)
239
if
oldstuff
then
240
get
(
host
,
password
,
"
d
"
,
1
,
data
,
"
average
"
,
"
gas
"
)
241
end
242
get
(
host
,
password
,
"
w
"
,
1
,
data
,
"
value
"
,
"
gas
"
)
243
if
detail
then
244
get
(
host
,
password
,
"
h
"
,
1
,
data
,
"
value
"
,
"
gas
"
)
245
end
246
else
247
return
248
end
249
local
path
=
file
.
dirname
(
filename
)
250
local
base
=
file
.
basename
(
filename
)
251
data
.
variant
=
variant
252
data
.
host
=
host
253
data
.
updated
=
os
.
now
(
)
254
if
nobackup
then
255
-- saved but with checking
256
local
tempname
=
file
.
join
(
path
,
"
youless.tmp
"
)
257
table
.
save
(
tempname
,
data
)
258
local
check
=
table
.
load
(
tempname
)
259
if
type
(
check
)
=
=
"
table
"
then
260
local
keepname
=
file
.
replacesuffix
(
filename
,
"
old
"
)
261
os
.
remove
(
keepname
)
262
if
lfs
.
isfile
(
keepname
)
then
263
report
(
"
error in removing %a
"
,
keepname
)
264
else
265
os
.
rename
(
filename
,
keepname
)
266
os
.
rename
(
tempname
,
filename
)
267
end
268
else
269
report
(
"
error in saving %a
"
,
tempname
)
270
end
271
else
272
local
keepname
=
file
.
join
(
path
,
formatters
[
"
%s-%s
"
]
(
os
.
date
(
"
%Y-%m-%d-%H-%M-%S
"
,
os
.
time
(
)
)
,
base
)
)
273
os
.
rename
(
filename
,
keepname
)
274
if
lfs
.
isfile
(
filename
)
then
275
report
(
"
error in renaming %a
"
,
filename
)
276
else
277
table
.
save
(
filename
,
data
)
278
end
279
end
280
return
data
281
end
282 283
-- local data = youless.collect {
284
-- host = "192.168.2.50",
285
-- variant = "electricity",
286
-- category = "electricity",
287
-- filename = "youless-electricity.lua"
288
-- }
289
--
290
-- inspect(data)
291 292
-- local data = youless.collect {
293
-- host = "192.168.2.50",
294
-- variant = "pulse",
295
-- category = "electricity",
296
-- filename = "youless-pulse.lua"
297
-- }
298
--
299
-- inspect(data)
300 301
-- local data = youless.collect {
302
-- host = "192.168.2.50",
303
-- variant = "gas",
304
-- category = "gas",
305
-- filename = "youless-gas.lua"
306
-- }
307
--
308
-- inspect(data)
309 310
-- We remain compatible so we stick to electricity and not unit fields.
311 312
function
youless
.
analyze
(
data
)
313
if
type
(
data
)
=
=
"
string
"
then
314
data
=
table
.
load
(
data
)
315
end
316
if
type
(
data
)
~
=
"
table
"
then
317
return
false
,
"
no data
"
318
end
319
if
not
data
.
years
then
320
return
false
,
"
no years
"
321
end
322
local
variant
=
data
.
variant
323
local
unit
,
maxunit
324
if
variant
=
=
"
electricity
"
or
variant
=
=
"
watt
"
then
325
unit
=
"
watt
"
326
maxunit
=
"
maxwatt
"
327
elseif
variant
=
=
"
gas
"
then
328
unit
=
"
liters
"
329
maxunit
=
"
maxliters
"
330
elseif
variant
=
=
"
pulse
"
then
331
unit
=
"
watt
"
332
maxunit
=
"
maxwatt
"
333
else
334
return
false
,
"
invalid variant
"
335
end
336
for
y
,
year
in
next
,
data
.
years
do
337
local
a_year
,
n_year
,
m_year
=
0
,
0
,
0
338
if
year
.
months
then
339
for
m
,
month
in
next
,
year
.
months
do
340
local
a_month
,
n_month
=
0
,
0
341
if
month
.
days
then
342
for
d
,
day
in
next
,
month
.
days
do
343
local
a_day
,
n_day
=
0
,
0
344
if
day
.
hours
then
345
for
h
,
hour
in
next
,
day
.
hours
do
346
local
a_hour
,
n_hour
,
m_hour
=
0
,
0
,
0
347
for
k
,
v
in
next
,
hour
do
348
if
type
(
k
)
=
=
"
number
"
then
349
a_hour
=
a_hour
+
v
350
n_hour
=
n_hour
+
1
351
if
v
>
m_hour
then
352
m_hour
=
v
353
end
354
end
355
end
356
n_day
=
n_day
+
n_hour
357
a_day
=
a_day
+
a_hour
358
hour
[
maxunit
]
=
m_hour
359
hour
[
unit
]
=
a_hour
/
n_hour
360
if
m_hour
>
m_year
then
361
m_year
=
m_hour
362
end
363
end
364
end
365
if
n_day
>
0
then
366
a_month
=
a_month
+
a_day
367
n_month
=
n_month
+
n_day
368
day
[
unit
]
=
a_day
/
n_day
369
else
370
day
[
unit
]
=
0
371
end
372
end
373
end
374
if
n_month
>
0
then
375
a_year
=
a_year
+
a_month
376
n_year
=
n_year
+
n_month
377
month
[
unit
]
=
a_month
/
n_month
378
else
379
month
[
unit
]
=
0
380
end
381
end
382
end
383
if
n_year
>
0
then
384
year
[
unit
]
=
a_year
/
n_year
385
year
[
maxunit
]
=
m_year
386
else
387
year
[
unit
]
=
0
388
year
[
maxunit
]
=
0
389
end
390
end
391
return
data
392
end
393