3208e304e0d2643f07021edca2b9b9cd7f8c1e8f
[weathermon.git] / weathermon.lua
1 #!/usr/bin/lua
2
3 json = require("json")
4 socket = 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_id then
215     sensor_id = web_devid
216   end
217
218   if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
219     if next(sensor)==nil then
220       sensor["command"]="alarm"
221     end
222     for k,v in pairs(sensor) do
223       printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = \""..v.."\"")
224       submitValue(sensor_type,sensor_id,k,v)
225       if mqtt_client then
226         mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
227           function (name) 
228             if name=="dev" then
229               return mqtt_encode(web_devid)
230             elseif name=="type" then
231               return mqtt_encode(sensor_type)
232             elseif name=="id" then
233               return mqtt_encode(sensor_id)
234             elseif name=="param" then
235               return k
236             else
237               return '{'..name..'}'
238             end      
239           end)
240         mqtt_client:publish(mqtt_path,v)
241       end  
242     end
243   else
244     printLog("Cannot parse sensor input: "..str)
245   end
246
247 end
248
249 function processLine(str)
250
251   msg=splitStr(line,':')
252   msg_type=msg[1] or ''
253   msg_body=msg[2] or ''
254   if msg_type=="STATUS" then
255     printLog("Status: "..msg_body)
256   elseif msg_type=="ERROR" then
257     printLog("Error: "..msg_body)  
258   elseif msg_type=="SENSOR" then
259     printLog("SENSOR: "..msg_body)  
260     sens = splitStr(msg_body,",")
261     sensor = {}
262     idx = 1
263     sensor_type = nil
264     sensor_id = web_devid
265     for i,rec in ipairs(sens) do
266       recrd=splitStr(rec,'=')
267       key=recrd[1] or ''
268       value=recrd[2] or ''
269       if value then
270         if key=="TYPE" then
271           sensor_type=value
272         elseif key=="ID" then
273           sensor_id=value
274         else
275           sensor[key]=value
276         end
277       end
278     end
279     if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
280       for k,v in pairs(sensor) do
281         printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = "..v)
282         submitValue(sensor_type,sensor_id,k,v)
283         if mqtt_client then
284           mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
285             function (name) 
286               if name=="dev" then
287                 return web_devid
288               elseif name=="type" then
289                 return sensor_type
290               elseif name=="id" then
291                 return sensor_id
292               elseif name=="param" then
293                 return k
294               else
295                 return '{'..name..'}'
296               end      
297             end)
298           mqtt_client:publish(mqtt_path,v)
299         end  
300       end
301     else
302       printLog("Cannot parse sensor input: "..msg_body)
303     end
304   elseif msg_type=="ALARM" then
305     printLog("ALARM: "..msg_body)  
306     sens = splitStr(msg_body,",")
307     sensor = {}
308     idx = 1
309     sensor_type = nil
310     sensor_id = web_devid
311     mqtt_param = {}
312     for i,rec in ipairs(sens) do
313       recrd=splitStr(rec,'=')
314       key=recrd[1] or ''
315       value=recrd[2] or ''
316       if value then
317         if key=="TYPE" then
318           alarm_type=value
319         elseif key=="ID" then
320           alarm_id=value
321         end
322       end
323     end
324     if not (alarm_type==nil or alarm_id==nil or alarm_type=='' or alarm_id=='') then
325       if mqtt_client then
326         mqtt_path=string.gsub(mqtt_alarm_topic,"{(.-)}", 
327           function (name) 
328             if name=="dev" then
329               return web_devid
330             elseif name=="type" then
331               return sensor_type
332             elseif name=="id" then
333               return sensor_id
334             else
335               return '{'..name..'}'
336             end      
337           end)
338         mqtt_client:publish(mqtt_path,msg_body)
339       end
340       if alarm_exec then
341         command=alarm_exec..
342           " \""..string.gsub(alarm_type,"\"","\\\"")..
343           "\" \""..string.gsub(alarm_id,"\"","\\\"")..
344           "\" \""..string.gsub(msg_body,"\"","\\\"").."\""
345         capture(command)
346       end
347     else
348       printLog("Cannot parse alarm input: "..msg_body)
349     end
350   end
351
352 end
353
354 getConfig(arg[1])
355
356 if mqtt_host then
357   MQTT = require "mosquitto"
358   mqtt_client = MQTT.new(mqtt_id)
359   if mqtt_user then
360     mqtt_client:login_set(mqtt_user, mqtt_passwd)
361   end
362   mqtt_client:connect(mqtt_host,mqtt_port)
363 end
364
365 if serial_port then
366   serialin=io.open(serial_port,"r")
367 elseif input_file == "-" then
368   serialin=io.stdin;
369 elseif input_file then
370   serialin=io.open(input_file,"r")
371 elseif input_exec then
372   serialin=io.popen(input_exec,"r")
373 else
374   printLog("No input selected")
375   return
376 end  
377 while 1 do
378   line=serialin:read("*l")
379   if line == nil then
380     break
381   end
382   printLog("Received: "..line);
383   if startswith(line,'{') then
384     processJson(line)
385   else
386    processLine(line)
387   end
388 end