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