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