summaryrefslogtreecommitdiff
path: root/scripts/context/lua/mtx-youless.lua
blob: 18304571b7171edbbed429f24a721199cd935fd4 (plain)
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
if not modules then modules = { } end modules ['mtx-youless'] = {
    version   = 1.002,
    comment   = "script tp fetch data from kwk meter polling device",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE",
    license   = "see context related readme files"
}

-- This script can fetch data from a youless device (http://www.youless.nl/) where data
-- is merged into a file. The data concerns energy consumption (current wattage as well
-- as kwh usage). There is an accompanying module to generate graphics.

require("util-jsn")

-- the library variant:

local youless     = { }
utilities.youless = youless

local lpegmatch  = lpeg.match
local formatters = string.formatters

local http = socket.http

-- maybe just a special parser but who cares about speed here

local function fetch(url,what,i)
    local url    = formatters["http://%s/V?%s=%i&f=j"](url,what,i)
    local data   = http.request(url)
    local result = data and utilities.json.tolua(data)
    return result
end

-- "123"  "  1,234"

local tovalue = lpeg.Cs((lpeg.R("09") + lpeg.P(1)/"")^1) / tonumber

-- "2013-11-12T06:40:00"

local totime = (lpeg.C(4) / tonumber) * lpeg.P("-")
             * (lpeg.C(2) / tonumber) * lpeg.P("-")
             * (lpeg.C(2) / tonumber) * lpeg.P("T")
             * (lpeg.C(2) / tonumber) * lpeg.P(":")
             * (lpeg.C(2) / tonumber) * lpeg.P(":")
             * (lpeg.C(2) / tonumber)

local function get(url,what,i,data,average)
    if not data then
        data = { }
    end
    while true do
        local d = fetch(url,what,i)
        if d and next(d) then
            local c_year, c_month, c_day, c_hour, c_minute, c_seconds = lpegmatch(totime,d.tm)
            if c_year and c_seconds then
                local delta = tonumber(d.dt)
                local tnum = os.time { year = c_year, month = c_month, day = c_day, hour = c_hour, minute = c_minute }
                local v = d.val
                for i=1,#v do
                    local newvalue = lpegmatch(tovalue,v[i])
                    if newvalue then
                        local t = tnum + (i-1)*delta
                        local current = os.date("%Y-%m-%dT%H:%M:%S",t)
                        local c_year, c_month, c_day, c_hour, c_minute, c_seconds = lpegmatch(totime,current)
                        if c_year and c_seconds then
                            local years   = data.years      if not years   then years   = { } data.years      = years   end
                            local d_year  = years[c_year]   if not d_year  then d_year  = { } years[c_year]   = d_year  end
                            local months  = d_year.months   if not months  then months  = { } d_year.months   = months  end
                            local d_month = months[c_month] if not d_month then d_month = { } months[c_month] = d_month end
                            local days    = d_month.days    if not days    then days    = { } d_month.days    = days    end
                            local d_day   = days[c_day]     if not d_day   then d_day   = { } days[c_day]     = d_day   end
                            if average then
                                d_day.average  = newvalue
                            else
                                local hours   = d_day.hours     if not hours   then hours   = { } d_day.hours     = hours   end
                                local d_hour  = hours[c_hour]   if not d_hour  then d_hour  = { } hours[c_hour]   = d_hour  end
                                d_hour[c_minute] = newvalue
                            end
                        end
                    end
                end
            end
        else
            return data
        end
        i = i + 1
    end
    return data
end

-- day of month (kwh)
--     url = http://192.168.1.14/V?m=2
--     m = the number of month (jan = 1, feb = 2, ..., dec = 12)

-- hour of day (watt)
--     url = http://192.168.1.14/V?d=1
--     d = the number of days ago (today = 0, yesterday = 1, etc.)

-- 10 minutes (watt)
--     url = http://192.168.1.14/V?w=1
--     w = 1 for the interval now till 8 hours ago.
--     w = 2 for the interval 8 till 16 hours ago.
--     w = 3 for the interval 16 till 24 hours ago.

-- 1 minute (watt)
--     url = http://192.168.1.14/V?h=1
--     h = 1 for the interval now till 30 minutes ago.
--     h = 2 for the interval 30 till 60 minutes ago

function youless.collect(specification)
    if type(specification) ~= "table" then
        return
    end
    local host     = specification.host     or ""
    local data     = specification.data     or { }
    local filename = specification.filename or ""
    local variant  = specification.variant  or "kwh"
    local detail   = specification.detail   or false
    local nobackup = specification.nobackup or false
    if host == "" then
        return
    end
    if name then
        data = table.load(name) or data
    end
    if variant == "kwh" then
        get(host,"m",1,data,true)
    elseif variant == "watt" then
        get(host,"d",0,data,true)
        get(host,"w",1,data)
        if detail then
            get(host,"h",1,data)
        end
    end
    if filename == "" then
        return
    end
    local path = file.dirname(filename)
    local base = file.basename(filename)
    if nobackup then
        -- saved but with checking
        local tempname = file.join(path,"youless.tmp")
        table.save(tempname,data)
        local check = table.load(tempname)
        if type(check) == "table" then
            local keepname = file.replacesuffix(filename,"old")
            os.remove(keepname)
            if not lfs.isfile(keepname) then
                os.rename(filename,keepname)
                os.rename(tempname,filename)
            end
        end
    else
        local keepname = file.join(path,formatters["%s-%s"](os.date("%Y-%m-%d-%H-%M-%S",os.time()),base))
        os.rename(filename,keepname)
        if not lfs.isfile(filename) then
            table.save(filename,data)
        end
    end
    return data
end

-- local data = youless.collect {
--     host     = "192.168.2.50",
--     variant  = "watt",
--     filename = "youless-watt.lua"
-- }

-- inspect(data)

-- local data = youless.collect {
--     host    = "192.168.2.50",
--     variant = "kwh",
--     filename = "youless-kwh.lua"
-- }

-- inspect(data)

-- the script

local helpinfo = [[
<?xml version="1.0"?>
<application>
 <metadata>
  <entry name="name">mtx-youless</entry>
  <entry name="detail">youless Fetcher</entry>
  <entry name="version">1.00</entry>
 </metadata>
 <flags>
  <category name="basic">
   <subcategory>
    <flag name="collect"><short>collect data from device</short></flag>
    <flag name="nobackup"><short>don't backup old datafile</short></flag>
    <flag name="nofile"><short>don't write data to file (for checking)</short></flag>
    <flag name="kwh"><short>summative kwk data</short></flag>
    <flag name="watt"><short>collected watt data</short></flag>
    <flag name="host"><short>ip address of device</short></flag>
   </subcategory>
  </category>
 </flags>
 <examples>
  <category>
   <title>Example</title>
   <subcategory>
    <example><command>mtxrun --script youless --collect --host=192.168.2.50 --kwk</command></example>
    <example><command>mtxrun --script youless --collect --host=192.168.2.50 --watt somefile.lua</command></example>
   </subcategory>
  </category>
 </examples>
</application>
]]

local application = logs.application {
    name     = "mtx-youless",
    banner   = "youless Fetcher",
    helpinfo = helpinfo,
}

local report = application.report

scripts         = scripts         or { }
scripts.youless = scripts.youless or { }

function scripts.youless.collect()
    local host     = environment.arguments.host
    local variant  = environment.arguments.kwh and "kwh" or environment.arguments.watt and "watt"
    local nobackup = environment.arguments.nobackup
    local nofile   = environment.arguments.nofile
    local filename = environment.files[1]
    if not variant then
        report("provide variant --kwh or --watt")
        return
    else
        report("using variant %a",variant)
    end
    if not host then
        host = "192.168.2.50"
        report("using default host %a",host)
    else
        report("using host %a",host)
    end
    if nobackup then
        report("not backing up data file")
    end
    if not filename and not nofile then
        filename = formatters["youless-%s.lua"](variant)
    end
    if filename ~= "" then
        report("using file %a",filename)
    end
    local data = youless.collect {
        filename = filename,
        host     = host,
        variant  = variant,
        nobackup = nobackup,
    }
    if type(data) ~= "table" then
        report("no data collected")
    elseif filename == "" then
        report("data collected but not saved")
    end
end

if environment.argument("collect") then
    scripts.youless.collect()
elseif environment.argument("exporthelp") then
    application.export(environment.argument("exporthelp"),environment.files[1])
else
    application.help()
end