72e48952e83bb12a392df46dd5ff75ff3ff23d83
[weathermon.git] / weathermon.lua
1 #!/usr/bin/lua
2
3 require("json")
4 require("socket")
5
6 function startswith(String,Start)
7    if String then
8      return string.sub(String,1,string.len(Start))==Start
9    else
10      return False
11    end    
12 end
13
14 function url_encode(str)
15   if (str) then
16     str = string.gsub (str, "\n", "\r\n")
17     str = string.gsub (str, "([^%w %-%_%.%~])",
18         function (c) return string.format ("%%%02X", string.byte(c)) end)
19     str = string.gsub (str, " ", "+")
20   end
21   return str    
22 end
23
24 function capture(cmd, raw)
25   local f = assert(io.popen(cmd, 'r'))
26   local s = assert(f:read('*a'))
27   f:close()
28   if raw then return s end
29   s = string.gsub(s, '^%s+', '')
30   s = string.gsub(s, '%s+$', '')
31   s = string.gsub(s, '[\n\r]+', ' ')
32   return s
33 end
34
35 function mqtt_encode(str)
36   if (str) then
37     str = string.gsub (str, "\n", "")
38     str = string.gsub (str, "/", "-")
39   end
40   return str    
41 end
42
43 function getConfig(configname)
44
45   local uci=require("uci")
46   local cur=uci.cursor()
47   local config
48   if configname then
49     config=configname
50   else
51     config="weathermon"
52   end
53
54   web_url  = cur.get(config,"web","url")
55   web_user = cur.get(config,"web","user")
56   web_pass = cur.get(config,"web","password")
57   web_devid = cur.get(config,"web","devid")
58
59   web_iface = cur.get(config,"web","iface")
60
61   if web_iface then
62   
63     command = '/sbin/ifconfig '..web_iface..' | grep \'\\<inet\\>\' | sed -n \'1p\' | tr -s \' \' | cut -d \' \' -f3 | cut -d \':\' -f2'
64     f=io.popen(command,'r')
65     ip_addr=f:read()
66   
67   end
68
69   if not web_devid then
70   
71     if web_iface then
72       io.input("/sys/class/net/"..web_iface.."/address")
73     else
74       io.input("/sys/class/net/eth0/address")
75     end
76
77     mac = io.read("*line")
78     mac = mac:gsub(":","")
79     mac = mac:upper()
80
81     web_devid = mac
82
83   end
84
85   logging = cur.get(config,"logging","enabled") 
86   touch_file = cur.get(config,"logging","touch_file") 
87
88   serial_port = cur.get(config,"serial","port")
89   serial_baud = cur.get(config,"serial","baud")
90
91   input_file = cur.get(config,"input","file")
92   input_exec = cur.get(config,"input","exec")
93   alarm_exec = cur.get(config,"alarm","exec")
94
95   if serial_port then
96
97     command = "stty -F  "..serial_port.." "..serial_baud
98     capture(command)
99
100   end
101
102   mqtt_host = cur.get(config,"mqtt","host")
103   mqtt_port = cur.get(config,"mqtt","port")
104   mqtt_id = cur.get(config,"mqtt","id")
105   mqtt_topic = cur.get(config,"mqtt","topic")
106   mqtt_alarm_topic = cur.get(config,"mqtt","alarm_topic")
107
108   mqtt_user = cur.get(config,"mqtt","user")
109   mqtt_passwd = cur.get(config,"mqtt","password")
110
111   if mqtt_host and not mqtt_id then
112     mqtt_id="weather-"..web_devid
113   end
114
115   if mqtt_host and not mqtt_port then
116     mqtt_port = 1883
117   end
118
119   if mqtt_host and not mqtt_topic then
120     mqtt_topic = 'weathermon/{dev}/{type}/{id}/{param}'
121   end
122
123   if mqtt_host and not mqtt_alarm_topic then
124     mqtt_alarm_topic = 'alarm/{dev}/{type}/{id}'
125   end
126
127 end
128
129 function touch()
130   if touch_file then
131     local file = io.open(touch_file, 'w')
132     file:close()
133   end  
134 end
135
136 function sleep(sec)
137   socket.select(nil, nil, sec)
138 end
139
140 function splitStr(str,char)
141
142   local res = {}
143   local idx = 1
144
145   while str:len()>0 do
146     pos = str:find(char); 
147     if pos == nil then
148       res[idx]=str
149       str=""
150     else
151       res[idx]=str:sub(1,pos-1)
152       idx=idx+1
153       str=str:sub(pos+1)
154     end
155   end
156
157   return res
158
159 end
160
161 function printLog(str)
162   if logging=="on" then
163     capture("logger -t weathermon "..str)
164     print(str)  
165   elseif logging=="syslog" then
166     capture("logger -t weathermon "..str)
167   elseif logging=="stdout" then 
168     print(str)  
169   end 
170 end
171
172 function submitValue(type,id,param,val)
173
174   url = web_url.."?stype="..url_encode(type).."&sid="..url_encode(id).."&param="..url_encode(param).."&value="..url_encode(val)
175
176   command = "curl"
177
178   if web_iface then
179     command = command.." --interface "..ip_addr
180   end
181
182   if web_user then
183     command = command.." -u "..web_user..":"..web_pass
184   end
185
186   command = command.." \""..url.."\" 2>&1"
187
188   result = capture(command)
189
190   touch()
191
192 end
193
194 function processJson(str)
195
196   msg=json.decode(str)
197
198   sensor={}
199
200   for key,value in pairs(msg) do
201     if value then
202       if key=="model" or key=="device" then
203         sensor_type=value
204       elseif key=="id" then
205         sensor_id=value
206       elseif key=='time' then
207         sensor_time=value
208       else
209         sensor[key]=value
210       end
211     end
212   end
213
214   if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
215     if next(sensor)==nil then
216       sensor["command"]="alarm"
217     end
218     for k,v in pairs(sensor) do
219       printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = \""..v.."\"")
220       submitValue(sensor_type,sensor_id,k,v)
221       if mqtt_client then
222         mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
223           function (name) 
224             if name=="dev" then
225               return mqtt_encode(web_devid)
226             elseif name=="type" then
227               return mqtt_encode(sensor_type)
228             elseif name=="id" then
229               return mqtt_encode(sensor_id)
230             elseif name=="param" then
231               return k
232             else
233               return '{'..name..'}'
234             end      
235           end)
236         mqtt_client:publish(mqtt_path,v)
237       end  
238     end
239   else
240     printLog("Cannot parse sensor input: "..msg_body)
241   end
242
243 end
244
245 function processLine(str)
246
247   msg=splitStr(line,':')
248   msg_type=msg[1] or ''
249   msg_body=msg[2] or ''
250   if msg_type=="STATUS" then
251     printLog("Status: "..msg_body)
252   elseif msg_type=="ERROR" then
253     printLog("Error: "..msg_body)  
254   elseif msg_type=="SENSOR" then
255     printLog("SENSOR: "..msg_body)  
256     sens = splitStr(msg_body,",")
257     sensor = {}
258     idx = 1
259     sensor_type = nil
260     sensor_id = web_devid
261     for i,rec in ipairs(sens) do
262       recrd=splitStr(rec,'=')
263       key=recrd[1] or ''
264       value=recrd[2] or ''
265       if value then
266         if key=="TYPE" then
267           sensor_type=value
268         elseif key=="ID" then
269           sensor_id=value
270         else
271           sensor[key]=value
272         end
273       end
274     end
275     if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
276       for k,v in pairs(sensor) do
277         printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = "..v)
278         submitValue(sensor_type,sensor_id,k,v)
279         if mqtt_client then
280           mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
281             function (name) 
282               if name=="dev" then
283                 return web_devid
284               elseif name=="type" then
285                 return sensor_type
286               elseif name=="id" then
287                 return sensor_id
288               elseif name=="param" then
289                 return k
290               else
291                 return '{'..name..'}'
292               end      
293             end)
294           mqtt_client:publish(mqtt_path,v)
295         end  
296       end
297     else
298       printLog("Cannot parse sensor input: "..msg_body)
299     end
300   elseif msg_type=="ALARM" then
301     printLog("ALARM: "..msg_body)  
302     sens = splitStr(msg_body,",")
303     sensor = {}
304     idx = 1
305     sensor_type = nil
306     sensor_id = web_devid
307     mqtt_param = {}
308     for i,rec in ipairs(sens) do
309       recrd=splitStr(rec,'=')
310       key=recrd[1] or ''
311       value=recrd[2] or ''
312       if value then
313         if key=="TYPE" then
314           alarm_type=value
315         elseif key=="ID" then
316           alarm_id=value
317         end
318       end
319     end
320     if not (alarm_type==nil or alarm_id==nil or alarm_type=='' or alarm_id=='') then
321       if mqtt_client then
322         mqtt_path=string.gsub(mqtt_alarm_topic,"{(.-)}", 
323           function (name) 
324             if name=="dev" then
325               return web_devid
326             elseif name=="type" then
327               return sensor_type
328             elseif name=="id" then
329               return sensor_id
330             else
331               return '{'..name..'}'
332             end      
333           end)
334         mqtt_client:publish(mqtt_path,msg_body)
335       end
336       if alarm_exec then
337         command=alarm_exec..
338           " \""..string.gsub(alarm_type,"\"","\\\"")..
339           "\" \""..string.gsub(alarm_id,"\"","\\\"")..
340           "\" \""..string.gsub(msg_body,"\"","\\\"").."\""
341         capture(command)
342       end
343     else
344       printLog("Cannot parse alarm input: "..msg_body)
345     end
346   end
347
348 end
349
350 getConfig(arg[1])
351
352 if mqtt_host then
353   MQTT = require "mosquitto"
354   mqtt_client = MQTT.new(mqtt_id)
355   if mqtt_user then
356     mqtt_client:login_set(mqtt_user, mqtt_passwd)
357   end
358   mqtt_client:connect(mqtt_host,mqtt_port)
359 end
360
361 if serial_port then
362   serialin=io.open(serial_port,"r")
363 elseif input_file == "-" then
364   serialin=io.stdin;
365 elseif input_file then
366   serialin=io.open(input_file,"r")
367 elseif input_exec then
368   serialin=io.popen(input_exec,"r")
369 else
370   printLog("No input selected")
371   return
372 end  
373 while 1 do
374   line=serialin:read()
375   if line == nil then
376     break
377   end
378   printLog("Received: "..line);
379   if startswith(line,'{') then
380     processJson(line)
381   else
382     processLine(line)
383   end
384 end