b43b72ca358912c38b6a913cd285d477bff78708
[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
87   serial_port = cur.get(config,"serial","port")
88   serial_baud = cur.get(config,"serial","baud")
89
90   input_file = cur.get(config,"input","file")
91   input_exec = cur.get(config,"input","exec")
92   alarm_exec = cur.get(config,"alarm","exec")
93
94   if serial_port then
95
96     command = "stty -F  "..serial_port.." "..serial_baud
97     capture(command)
98
99   end
100
101   mqtt_host = cur.get(config,"mqtt","host")
102   mqtt_port = cur.get(config,"mqtt","port")
103   mqtt_id = cur.get(config,"mqtt","id")
104   mqtt_topic = cur.get(config,"mqtt","topic")
105   mqtt_alarm_topic = cur.get(config,"mqtt","alarm_topic")
106
107   mqtt_user = cur.get(config,"mqtt","user")
108   mqtt_passwd = cur.get(config,"mqtt","password")
109
110   if mqtt_host and not mqtt_id then
111     mqtt_id="weather-"..web_devid
112   end
113
114   if mqtt_host and not mqtt_port then
115     mqtt_port = 1883
116   end
117
118   if mqtt_host and not mqtt_topic then
119     mqtt_topic = 'weathermon/{dev}/{type}/{id}/{param}'
120   end
121
122   if mqtt_host and not mqtt_alarm_topic then
123     mqtt_alarm_topic = 'alarm/{dev}/{type}/{id}'
124   end
125
126 end
127
128 function sleep(sec)
129   socket.select(nil, nil, sec)
130 end
131
132 function splitStr(str,char)
133
134   local res = {}
135   local idx = 1
136
137   while str:len()>0 do
138     pos = str:find(char); 
139     if pos == nil then
140       res[idx]=str
141       str=""
142     else
143       res[idx]=str:sub(1,pos-1)
144       idx=idx+1
145       str=str:sub(pos+1)
146     end
147   end
148
149   return res
150
151 end
152
153 function printLog(str)
154   if logging=="on" then
155     capture("logger -t weathermon "..str)
156   else 
157     print(str)  
158   end 
159 end
160
161 function submitValue(type,id,param,val)
162
163   url = web_url.."?stype="..url_encode(type).."&sid="..url_encode(id).."&param="..url_encode(param).."&value="..url_encode(val)
164
165   command = "curl"
166
167   if web_iface then
168     command = command.." --interface "..ip_addr
169   end
170
171   if web_user then
172     command = command.." -u "..web_user..":"..web_pass
173   end
174
175   command = command.." \""..url.."\""
176
177   result = capture(command)
178
179 end
180
181 function processJson(str)
182
183   msg=json.decode(str)
184
185   sensor={}
186
187   for key,value in pairs(msg) do
188     if value then
189       if key=="model" or key=="device" then
190         sensor_type=value
191       elseif key=="id" then
192         sensor_id=value
193       elseif key=='time' then
194         sensor_time=value
195       else
196         sensor[key]=value
197       end
198     end
199   end
200
201   if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
202     if next(sensor)==nil then
203       sensor["command"]="alarm"
204     end
205     for k,v in pairs(sensor) do
206       printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = "..v)
207       submitValue(sensor_type,sensor_id,k,v)
208       if mqtt_client then
209         mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
210           function (name) 
211             if name=="dev" then
212               return mqtt_encode(web_devid)
213             elseif name=="type" then
214               return mqtt_encode(sensor_type)
215             elseif name=="id" then
216               return mqtt_encode(sensor_id)
217             elseif name=="param" then
218               return k
219             else
220               return '{'..name..'}'
221             end      
222           end)
223         mqtt_client:publish(mqtt_path,v)
224       end  
225     end
226   else
227     printLog("Cannot parse sensor input: "..msg_body)
228   end
229
230 end
231
232 function processLine(str)
233
234   msg=splitStr(line,':')
235   msg_type=msg[1] or ''
236   msg_body=msg[2] or ''
237   if msg_type=="STATUS" then
238     printLog("Status: "..msg_body)
239   elseif msg_type=="ERROR" then
240     printLog("Error: "..msg_body)  
241   elseif msg_type=="SENSOR" then
242     printLog("SENSOR: "..msg_body)  
243     sens = splitStr(msg_body,",")
244     sensor = {}
245     idx = 1
246     sensor_type = nil
247     sensor_id = web_devid
248     for i,rec in ipairs(sens) do
249       recrd=splitStr(rec,'=')
250       key=recrd[1] or ''
251       value=recrd[2] or ''
252       if value then
253         if key=="TYPE" then
254           sensor_type=value
255         elseif key=="ID" then
256           sensor_id=value
257         else
258           sensor[key]=value
259         end
260       end
261     end
262     if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
263       for k,v in pairs(sensor) do
264         printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = "..v)
265         submitValue(sensor_type,sensor_id,k,v)
266         if mqtt_client then
267           mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
268             function (name) 
269               if name=="dev" then
270                 return web_devid
271               elseif name=="type" then
272                 return sensor_type
273               elseif name=="id" then
274                 return sensor_id
275               elseif name=="param" then
276                 return k
277               else
278                 return '{'..name..'}'
279               end      
280             end)
281           mqtt_client:publish(mqtt_path,v)
282         end  
283       end
284     else
285       printLog("Cannot parse sensor input: "..msg_body)
286     end
287   elseif msg_type=="ALARM" then
288     printLog("ALARM: "..msg_body)  
289     sens = splitStr(msg_body,",")
290     sensor = {}
291     idx = 1
292     sensor_type = nil
293     sensor_id = web_devid
294     mqtt_param = {}
295     for i,rec in ipairs(sens) do
296       recrd=splitStr(rec,'=')
297       key=recrd[1] or ''
298       value=recrd[2] or ''
299       if value then
300         if key=="TYPE" then
301           alarm_type=value
302         elseif key=="ID" then
303           alarm_id=value
304         end
305       end
306     end
307     if not (alarm_type==nil or alarm_id==nil or alarm_type=='' or alarm_id=='') then
308       if mqtt_client then
309         mqtt_path=string.gsub(mqtt_alarm_topic,"{(.-)}", 
310           function (name) 
311             if name=="dev" then
312               return web_devid
313             elseif name=="type" then
314               return sensor_type
315             elseif name=="id" then
316               return sensor_id
317             else
318               return '{'..name..'}'
319             end      
320           end)
321         mqtt_client:publish(mqtt_path,msg_body)
322       end
323       if alarm_exec then
324         command=alarm_exec..
325           " \""..string.gsub(alarm_type,"\"","\\\"")..
326           "\" \""..string.gsub(alarm_id,"\"","\\\"")..
327           "\" \""..string.gsub(msg_body,"\"","\\\"").."\""
328         capture(command)
329       end
330     else
331       printLog("Cannot parse alarm input: "..msg_body)
332     end
333   end
334
335 end
336
337 getConfig(arg[1])
338
339 if mqtt_host then
340   MQTT = require "paho.mqtt"
341   mqtt_client = MQTT.client.create(mqtt_host, mqtt_port)
342   if mqtt_user then
343     mqtt_client:auth(mqtt_user, mqtt_passwd)
344   end
345   mqtt_client:connect(mqtt_id)
346 end
347
348 if serial_port then
349   serialin=io.open(serial_port,"r")
350 elseif input_file == "-" then
351   serialin=io.stdin;
352 elseif input_file then
353   serialin=io.open(input_file,"r")
354 elseif input_exec then
355   serialin=io.popen(input_exec,"r")
356 else
357   printLog("No input selected")
358   return
359 end  
360 while 1 do
361   line=serialin:read()
362   if line == nil then
363     break
364   end
365   printLog("Received: "..line);
366   if startswith(line,'{') then
367     processJson(line)
368   else
369     processLine(line)
370   end
371 end