Добавлена опциональная авторизация MQTT
[weathermon.git] / weathermon
1 #!/usr/bin/python
2
3 import serial
4
5 from os import listdir,system
6 from os.path import isfile, join
7
8 from pprint import pprint
9
10 from termios import tcflush, TCIOFLUSH
11
12 from time import sleep,time
13
14 from uuid import getnode
15
16 from hashlib import md5
17
18 import socket
19
20 import sys,traceback
21
22 import pycurl
23 from urllib import urlencode
24 from StringIO import StringIO
25
26 searchpath = '/dev/serial/by-id/'
27 path = None
28 baud = None
29 timeout = None
30
31 proc = None
32
33 external_submit_interval = 320
34 owm_submit_interval = 320
35 expire_interval = 1200
36 submit_time = time()
37 submit_time_owm = time()
38
39 import MySQLdb
40 import ConfigParser
41
42 import thread
43 from threading import Timer
44
45 def find_port():
46
47   global serial_num
48   global path
49
50   files = listdir(searchpath)
51   for f in files:
52     if serialnum in f:
53       return join(searchpath,f)
54   return None
55
56 def open_port(path):
57
58   global proc, debug
59
60   if debug>0:
61     print "Opening path "+path
62
63   if path == "-":
64     return sys.stdin
65
66   if path[0] == "=":
67     import subprocess
68     sleep(3)
69     command=path[1:]
70     try:
71       command,args=command.split(' ',1)
72       proc = subprocess.Popen([command,args],stdout=subprocess.PIPE)
73     except:
74       proc = subprocess.Popen([command],stdout=subprocess.PIPE)
75     import subprocess
76     return proc.stdout
77
78   ser = serial.Serial(path,baud,timeout=timeout)
79   if ser.portstr:
80     tcflush(ser,TCIOFLUSH);
81   return ser
82   
83 def read_port(ser):
84
85   try: 
86     timeout_timer = Timer(timeout, thread.interrupt_main)
87     timeout_timer.start()
88
89     line=''
90     while line=='':
91       line = ser.readline()
92       line = line.strip()
93       
94     return line
95     
96   except KeyboardInterrupt:
97     return "<<TIMEOUT>>"
98   finally:
99     timeout_timer.cancel()
100   
101 def read_loop(ser,callback):
102
103   global proc
104
105   while True:
106   
107     try:
108       line=read_port(ser)
109       if line=="<<TIMEOUT>>":
110         if debug>0:
111           print "Reopening port..."
112           print line
113         ser.close()
114         if proc:
115           try:
116             if debug>0:
117               print "Terminating process..."
118             proc.terminate()
119             sleep(5)
120           finally:
121             None  
122         ser=open_port(path)
123       if line:
124         callback(line)
125     except KeyboardInterrupt:
126       break
127     finally:
128       None
129
130 def print_log(str):
131   global logging
132   if debug>0:
133     print str
134   if logging == "on":
135     system("logger -t weathermon \""+str+"\"")
136
137 def submit_narodmon():
138
139   param = { 'ID':devid }
140
141   c = database.cursor()
142   c.execute(
143     '''
144     select nm_id,value from
145     (
146     SELECT sensor_id,parameter_id,max(timestamp) timestamp,round(avg(value),1) value FROM meteo.sensor_values
147     where 
148     timestamp>=date_add(now(), INTERVAL -300 SECOND)
149     group by sensor_id,parameter_id
150     ) v,meteo.ext_sensors e
151     where v.sensor_id=e.sensor_id and v.parameter_id=e.param_id
152     and nm_id is not null
153     '''
154   )
155
156   queue=c.fetchall()
157
158   if debug>1:
159     pprint(queue)
160
161   for (sensor,value) in queue:
162     param[sensor] = value  
163   
164   if debug>1:
165     pprint (param)
166
167   url = "http://narodmon.ru/post.php"
168
169   try:
170       
171     response_buffer = StringIO()
172     curl = pycurl.Curl()
173                                               
174     curl.setopt(curl.URL, url)
175     curl.setopt(curl.WRITEFUNCTION, response_buffer.write)   
176     curl.setopt(curl.POST, 1)
177     curl.setopt(curl.POSTFIELDS, urlencode(param))
178                                                                   
179     curl.perform()
180     curl.close()  
181                                                                           
182     response_value = response_buffer.getvalue()
183                                                                               
184     print_log('Narodmon response: '+response_value)
185                                                                                   
186     return True
187                                                                                       
188   except:
189            
190     exc_type, exc_value, exc_traceback = sys.exc_info()
191     traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
192     traceback.print_exception(exc_type, exc_value, exc_traceback,
193                               limit=2, file=sys.stdout)  
194     return False  
195                                       
196 def submit_owm():
197
198   url = "http://openweathermap.org/data/post"
199   params = {'name':owm_station, 'lat':owm_lat, 'long':owm_lon}
200
201   c = database.cursor()
202   c.execute(
203     '''
204     select owm_id,value from
205     (
206     SELECT sensor_id,parameter_id,max(timestamp) timestamp,round(avg(value),1) value FROM meteo.sensor_values
207     where 
208     timestamp>=date_add(now(), INTERVAL -300 SECOND)
209     group by sensor_id,parameter_id
210     ) v,meteo.ext_sensors e
211     where v.sensor_id=e.sensor_id and v.parameter_id=e.param_id
212     and owm_id is not null
213     '''
214   )
215
216   queue=c.fetchall()
217
218   if debug>1:
219     pprint(queue)
220
221   for (sensor,value) in queue:
222     params[sensor]=value
223   if debug>1:  
224     pprint (params)
225
226   try:
227
228     response_buffer = StringIO()
229     curl = pycurl.Curl()
230
231     curl.setopt(curl.URL, url)
232     curl.setopt(curl.USERPWD, '%s:%s' % (owmuser, owmpasswd))
233     curl.setopt(curl.WRITEFUNCTION, response_buffer.write)
234     curl.setopt(curl.POST, 1)
235     curl.setopt(curl.POSTFIELDS, urlencode(params))
236
237     curl.perform()
238     curl.close()
239     
240     response_value = response_buffer.getvalue()
241     
242     print_log('Openweathermap response: '+response_value)
243   
244     return True
245   
246   except:
247
248     exc_type, exc_value, exc_traceback = sys.exc_info()
249     traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
250     traceback.print_exception(exc_type, exc_value, exc_traceback,
251                                   limit=2, file=sys.stdout)
252     return False  
253
254 def submit_data(sensor_type,sensor_id,sensor_param,param_value):
255   global submit_time
256   global submit_time_owm
257   global external_submit_interval
258   global owm_submit_interval
259   c = database.cursor()
260   c.execute('CALL meteo.submit_value(%s,%s,%s,%s,NULL)', (sensor_type,sensor_id,sensor_param,param_value))
261   database.commit()
262   if narmon=='on' and time()>submit_time+external_submit_interval:
263     submit_narodmon()
264     submit_time=time()
265   if owmuser and time()>submit_time_owm+owm_submit_interval:
266     submit_owm()
267     submit_time_owm=time()        
268  
269 def process_str(str):
270   print_log("Received: "+str)
271   try:
272     msg_type, msg_body = str.split(':')
273   except:
274     return
275   try:  
276     if msg_type == 'STATUS':
277       print_log('Status: '+msg_body)
278     elif msg_type == 'ERROR':
279       print_log('Error: '+ msg_body)
280     elif msg_type == 'SENSOR':
281       sens = msg_body.split(',')
282       sensor = {}
283       sensor_type = None
284       sensor_id = None
285       for rec in sens:
286         key,value = rec.split('=')
287         value=value.strip()
288         if len(value)>0:
289           if key == 'TYPE':
290             sensor_type = value
291           elif key == 'ID':
292             sensor_id = value  
293           else:  
294             sensor[key] = value
295       if sensor_type:    
296         if not sensor_id:
297           sensor_id=devid;    
298       for key in sensor:
299         if sensor[key]:
300           print_log('Type = '+sensor_type+', ID = '+sensor_id+', Param = '+key+', Value = '+sensor[key])
301           submit_data(sensor_type,sensor_id,key,sensor[key])
302         else:
303           print_log('Error: got empty parameter value for '+sensor_type+'.'+sensor_id+'.'+key)
304     elif msg_type == "ALARM":
305       alarm = msg_body.split(',')
306       device_type = None
307       device_id = None
308       for rec in alarm:
309         key,value = rec.split('=')
310         value=value.strip()
311         if len(value)>0:
312           if key == 'TYPE':
313             device_type = value
314           if key == 'ID':
315             device_id = value
316       if device_type:
317         if not device_id:
318           device_id=devid;
319         print_log("Alarm: Type = "+device_type+", ID = "+device_id)
320         if alarm_script:
321           try:
322             proc = subprocess.Popen([alarm_script,device_type,device_id,msg_body])
323           except:
324             print_log("Failed to execute alarm script")          
325   except:
326     print_log('Exception processing...')
327     exc_type, exc_value, exc_traceback = sys.exc_info()
328     traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
329     traceback.print_exception(exc_type, exc_value, exc_traceback,
330                               limit=5, file=sys.stdout)  
331     try:
332       database.close()
333     except:
334       None
335     reconnect()
336
337 def weather_mon():
338
339   global path
340
341   if path is None:
342     path = find_port()
343   ser = open_port(path)
344   read_loop(ser,process_str)
345
346 def reconnect():
347
348   connected = False
349
350   while not connected:            
351
352     try:
353
354       global database
355       database = MySQLdb.connect(host=dbhost,user=dbuser,passwd=dbpasswd,use_unicode=True,connect_timeout=10)
356       database.set_character_set('utf8')
357       c = database.cursor()
358       c.execute('SET NAMES utf8;')
359       print_log("Database connected...")
360       connected = True 
361   
362     except:
363         
364       print_log("Error connecting database")
365       sleep(30)
366       
367 def main():
368   weather_mon()
369
370 def init():
371
372   global dbhost,dbuser,dbpasswd,path,serialnum,logging,debug;
373   global timeout,baud,narmon,devid;
374   global owmuser,owmpasswd,owm_temp,owm_pres,owm_humi,owm_lat,owm_lon,owm_station;
375   global alarm_script;
376
377   try:
378
379     cfg = ConfigParser.RawConfigParser(allow_no_value=True)
380     cfg.readfp(open('/etc/weathermon.conf'))
381     dbhost = cfg.get("mysql","host")
382     dbuser = cfg.get("mysql","user")
383     dbpasswd = cfg.get("mysql","passwd")
384     try:
385       debug = cfg.get("logging","debug")
386     except:
387       debug = 0  
388     try:
389       path = cfg.get("serial","port");
390     except:
391       path = None
392     try:    
393       serialnum = cfg.get("serial","id")
394     except:
395       serialnum = None  
396     try:
397       logging = cfg.get("logging","enabled")
398     except:
399       logging = None
400     try:
401       timeout = int(cfg.get("serial","timeout"))
402     except:
403       timeout = 120
404     try:
405       baud = int(cfg.get("serial","baud"))
406     except:
407       baud = 57600
408     try:
409       narmon = cfg.get("narodmon","enable")
410     except:
411       narmon = 'off'  
412     try:
413       devid = cfg.get("narodmon","devid")
414     except:
415       devid = "{:0>12X}".format(getnode())   
416     try:  
417       owmuser = cfg.get("openweathermap","user")
418       owmpasswd = cfg.get("openweathermap",'passwd')
419     except:
420       owmuser = None  
421     if owmuser:
422       owm_temp = cfg.get("openweathermap",'temp')
423       owm_pres = cfg.get("openweathermap",'pres')
424       owm_humi = cfg.get("openweathermap",'humi')
425       owm_lat = cfg.get("openweathermap",'lat')
426       owm_lon = cfg.get("openweathermap",'lon')
427       owm_station = cfg.get("openweathermap",'station')
428     try:
429       alarm_script = cfg.get("alarm","exec")
430       import subprocess
431     except:
432       alarm_script = None
433         
434     reconnect()
435  
436   except:
437
438     print_log("Cannot intialize system")
439     exit() 
440   
441 if __name__ == "__main__":
442   import sys
443   reload(sys)
444   sys.setdefaultencoding('utf-8')
445   init()
446   main()