Обработка сообщений MQTT в формате rtl_433
[weathermon.git] / bin / weather-filter
1 #!/usr/bin/lua
2
3 local uci = require("uci")
4 local cur = uci.cursor()
5 local json = require "json"
6
7 local logdb = arg[1]
8
9 require "wm_util"
10
11 if not logdb then
12
13   print("no sqlite log defined!")
14   return
15
16 end
17
18 function shallowcopy(orig)
19     local orig_type = type(orig)
20     local copy
21     if orig_type == 'table' then
22         copy = {}
23         for orig_key, orig_value in pairs(orig) do
24             copy[orig_key] = orig_value
25         end
26     else -- number, string, boolean, etc
27         copy = orig
28     end
29     return copy
30 end
31
32 function median(dataset)
33
34   table.sort(shallowcopy(dataset))
35   return dataset[math.floor(#dataset/2)]
36
37 end
38
39 function filter_data(dataset,width)
40
41   if not width then 
42     width = 7
43   end  
44
45   if #dataset <= width then
46     return dataset
47   end  
48
49   local result = {}
50   
51   local window_spread = math.floor(width/2)
52   local window = {}
53
54   for i = 1,window_spread do
55     window[#window+1] = dataset[i]["y"]
56   end
57   
58   for key,value in pairs(dataset) do
59     nextelem = dataset[key+window_spread]
60     if nextelem then
61       window[#window+1] = nextelem["y"]
62     end  
63     if not nextelem or #window>width then
64       table.remove(window,1)
65     end  
66     row = {}
67     row["t"]=value["t"]
68     row["y"]=median(window)
69     result[#result+1] = row
70   end
71   
72   return result
73
74 end
75
76 function average_results(dataset,con)
77   local name = os.tmpname()
78   touch(name)
79   local tmpcon = assert(env:connect(name))
80   assert(tmpcon:execute("create table series(time_stamp datetime,value float)"))
81   for key,value in pairs(dataset) do
82     assert(tmpcon:execute(string.format("INSERT INTO series(time_stamp,value) VALUES ('%s','%s')",value["t"],value["y"])))
83   end
84   local sql = "select rounded as t,avg(value) as y from (select substr(strftime('%Y-%m-%dT%H:%M',time_stamp),1,15)||'5:00' rounded,value from series) group by rounded order by rounded"
85   results = run_sql(sql,tmpcon)
86   tmpcon:close()
87   os.remove(name)
88   return results
89 end
90
91 function run_sql(sql,con)
92   local result = {}
93
94   cursor = assert(con:execute(sql))
95   row = cursor:fetch ({}, "a")
96   while row do
97     result[#result+1] = row
98     row = cursor:fetch ({}, "a")
99   end
100  
101   return result
102 end
103
104 function get_list(day,con)
105   if day == "-" then
106     sql = string.format("SELECT DISTINCT sensor_id,sensor,param FROM log WHERE time_stamp>=datetime('now','-1 day','localtime') ORDER BY sensor_id,sensor,param")
107   else
108     sql = string.format("SELECT DISTINCT sensor_id,sensor,param FROM log WHERE time_stamp>='%s' and time_stamp<date('%s','+1 day') ORDER BY sensor_id,sensor,param",day,day)
109   end  
110   return run_sql(sql,con)
111 end
112
113 function get_raw(day,con,sensor_id,sensor_type,param)
114   format = '%Y-%m-%dT%H:%M:%S'
115   if day == "-" then
116     sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>=datetime('now','-1 day','localtime') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,sensor_id,sensor_type,param)
117   else
118     sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>='%s' and time_stamp<date('%s','+1 day') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,day,day,sensor_id,sensor_type,param)
119   end
120   return run_sql(sql,con)
121 end
122
123 function get_filtered(day,con,sensor_id,sensor_type,param,width)
124   format = '%Y-%m-%dT%H:%M:%S'
125   if day == "-" then
126     sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>=datetime('now','-1 day','localtime') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,sensor_id,sensor_type,param)
127   else
128     sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>='%s' and time_stamp<date('%s','+1 day') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,day,day,sensor_id,sensor_type,param)
129   end
130   return filter_data(run_sql(sql,con),width)
131 end
132
133 function dump_json(dataset,file)
134   local f
135   if file then
136     f = io.open(file,"w")
137     io.output(f)
138   end
139   io.write(json.encode(dataset))
140   if f then
141     io.close(f)
142   end   
143 end
144
145 function dump_txt(dataset,file)
146   local f
147   if file then
148     f = io.open(file,"w")
149     io.output(f)
150   end
151   for key,row in pairs(dataset) do
152     io.write(row["t"].." "..row["y"].."\n")
153   end
154   if f then
155     io.close(f)
156   end   
157 end
158
159 function dump_list(dataset,file)
160   local f
161   if file then
162     f = io.open(file,"w")
163     io.output(f)
164   end
165   for key,row in pairs(dataset) do
166     io.write(row["sensor_id"].." "..row["sensor"].." "..row["param"].."\n")
167   end
168   if f then
169     io.close(f)
170   end   
171 end
172
173 local command = arg[2]
174 local day = arg[3]
175
176 local dbdriver = require "luasql.sqlite3"
177 env = assert(dbdriver.sqlite3())
178 con = assert(env:connect(logdb,'READONLY'))
179
180 if command == "list" then
181
182   dump_list(get_list(day,con))
183
184 elseif command == "get" then
185
186   sensor_id = arg[4]
187   sensor_type = arg[5]
188   param = arg[6]
189
190   dump_txt(get_raw(day,con,sensor_id,sensor_type,param))
191   
192 elseif command == "get-filtered" then
193
194   sensor_id = arg[4]
195   sensor_type = arg[5]
196   param = arg[6]
197
198   width = arg[7]
199   if not width then
200     width = 5
201   end  
202
203   dataset =get_filtered(day,con,sensor_id,sensor_type,param)
204   dump_txt(dataset)
205
206 elseif command == "get-compacted" then
207
208   sensor_id = arg[4]
209   sensor_type = arg[5]
210   param = arg[6]
211
212   width = arg[7]
213   if not width then
214     width = 5
215   end  
216
217   dataset =get_filtered(day,con,sensor_id,sensor_type,param)
218   dump_txt(average_results(dataset))
219
220 elseif command == "dump" then
221
222   local directory = arg[4]
223   if not directory then 
224     directory = "."
225   end  
226   
227   list = get_list(day,con)
228   dump_json(list,directory.."/sensors.json")
229   for key,value in pairs(list) do
230     dump_json(get_filtered(day,con,value["sensor_id"],value["sensor"],value["param"]),directory.."/"..value["sensor_id"].."."..value["sensor"].."."..value["param"]..".json")
231   end
232
233 elseif command == "dump-compacted" then
234
235   local directory = arg[4]
236   if not directory then 
237     directory = "."
238   end  
239   
240   list = get_list(day,con)
241   dump_json(list,directory.."/sensors.json")
242   for key,value in pairs(list) do
243     dump_json(average_results(get_filtered(day,con,value["sensor_id"],value["sensor"],value["param"])),directory.."/"..value["sensor_id"].."."..value["sensor"].."."..value["param"]..".json")
244   end
245   
246 end