Улучшена обработка ошибок при работе с проводными датчиками.
[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 baud = 57600
28 path = None
29 timeout = 5
30
31 external_submit_interval = 320
32 owm_submit_interval = 320
33 expire_interval = 1200
34 submit_time = time()
35 submit_time_owm = time()
36 submit_queue = {}
37
38 import MySQLdb
39 import ConfigParser
40
41 def find_port():
42
43   global serial_num
44   global path
45
46   files = listdir(searchpath)
47   for f in files:
48     if serialnum in f:
49       return join(searchpath,f)
50   return None
51
52 def open_port(path):
53
54   ser = serial.Serial(path,baud,timeout)
55   if ser.portstr:
56     tcflush(ser,TCIOFLUSH);
57   return ser
58   
59 def read_port(ser):
60
61   line = ser.readline()
62   return line.strip()
63   
64 def read_loop(ser,callback):
65
66   while True:
67   
68     try:
69       line=read_port(ser)
70       if line:
71         callback(line)
72     except KeyboardInterrupt:
73       break
74     finally:
75       None
76
77 def print_log(str):
78   global logging
79   print str
80   if logging == "on":
81     system("logger -t weathermon \""+str+"\"")
82
83 def submit_narodmon(queue):
84
85   param = { 'ID':"{:X}".format(getnode())}
86
87   pprint(queue)
88
89   for sensor in queue:
90     value = submit_queue[sensor]['val']
91     timestamp = submit_queue[sensor]['timestamp']
92     digest = md5(sensor).hexdigest()[:18]
93     param[digest] = value;
94   
95   pprint (param)
96
97   url = "http://narodmon.ru/post.php"
98
99   try:
100       
101     response_buffer = StringIO()
102     curl = pycurl.Curl()
103                                               
104     curl.setopt(curl.URL, url)
105     curl.setopt(curl.WRITEFUNCTION, response_buffer.write)   
106     curl.setopt(curl.POST, 1)
107     curl.setopt(curl.POSTFIELDS, urlencode(param))
108                                                                   
109     curl.perform()
110     curl.close()  
111                                                                           
112     response_value = response_buffer.getvalue()
113                                                                               
114     print_log('Narodmon response: '+response_value)
115                                                                                   
116     return True
117                                                                                       
118   except:
119                                                                                         
120     exc_type, exc_value, exc_traceback = sys.exc_info()
121     traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
122     traceback.print_exception(exc_type, exc_value, exc_traceback,
123                               limit=2, file=sys.stdout)  
124     return False  
125                                       
126 def submit_owm(queue):
127
128   url = "http://openweathermap.org/data/post"
129   params = {'name':owm_station, 'lat':owm_lat, 'long':owm_lon}
130
131   try:
132
133     try:
134       params['temp'] = queue[owm_temp]['val']
135       params['pressure'] = queue[owm_pres]['val']
136       params['humidity'] = queue[owm_humi]['val']
137     except:
138       return False  
139
140     response_buffer = StringIO()
141     curl = pycurl.Curl()
142
143     curl.setopt(curl.URL, url)
144     curl.setopt(curl.USERPWD, '%s:%s' % (owmuser, owmpasswd))
145     curl.setopt(curl.WRITEFUNCTION, response_buffer.write)
146     curl.setopt(curl.POST, 1)
147     curl.setopt(curl.POSTFIELDS, urlencode(params))
148
149     curl.perform()
150     curl.close()
151     
152     response_value = response_buffer.getvalue()
153     
154     print_log('Openweathermap response: '+response_value)
155   
156     return True
157   
158   except:
159
160     exc_type, exc_value, exc_traceback = sys.exc_info()
161     traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
162     traceback.print_exception(exc_type, exc_value, exc_traceback,
163                                   limit=2, file=sys.stdout)
164     return False  
165
166 def purge_queue():
167   global submit_queue
168   clean = []
169   for key in submit_queue:
170     if submit_queue[key]['timestamp'] < time()-expire_interval:
171       print_log("Expired value for "+key)
172       clean.append(key)
173   for i in clean:
174     del submit_queue[i]    
175   
176 def submit_data(sensor_type,sensor_id,sensor_param,param_value):
177   global submit_time
178   global submit_time_owm
179   global external_submit_interval
180   global owm_submit_interval
181   global submit_queue
182   c = database.cursor()
183   c.execute('CALL meteo.submit_value(%s,%s,%s,%s,NULL)', (sensor_type,sensor_id,sensor_param,param_value))
184   database.commit()
185   submit_queue[sensor_type+'.'+sensor_id+'.'+sensor_param]={'val':param_value,'timestamp':time()}
186   if time()>submit_time+external_submit_interval:
187     submit_narodmon(submit_queue)
188     submit_time=time()
189   if owmuser and time()>submit_time_owm+owm_submit_interval:
190     submit_owm(submit_queue)
191     submit_time_owm=time()
192   purge_queue()
193         
194  
195 def process_str(str):
196   print_log("Received: "+str)
197   try:
198     msg_type, msg_body = str.split(':')
199   except:
200     return
201   try:  
202     if msg_type == 'STATUS':
203       print_log('Status: '+msg_body)
204     elif msg_type == 'ERROR':
205       print_log('Error: '+ msg_body)
206     elif msg_type == 'SENSOR':
207       sens = msg_body.split(',')
208       sensor = {}
209       sensor_type = None
210       sensor_id = None
211       for rec in sens:
212         key,value = rec.split('=')
213         value=value.strip()
214         if len(value)>0:
215           if key == 'TYPE':
216             sensor_type = value
217           elif key == 'ID':
218             sensor_id = value  
219           else:  
220             sensor[key] = value
221       if sensor_type:    
222         if not sensor_id:
223           sensor_id='DEFAULT';    
224       for key in sensor:
225         if sensor[key]:
226           print_log('Type = '+sensor_type+', ID = '+sensor_id+', Param = '+key+', Value = '+sensor[key])
227           submit_data(sensor_type,sensor_id,key,sensor[key])
228         else:
229           print_log('Error: got empty parameter value for '+sensor_type+'.'+sensor_id+'.'+key)
230   except:
231     print_log('Exception processing...')
232     exc_type, exc_value, exc_traceback = sys.exc_info()
233     traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
234     traceback.print_exception(exc_type, exc_value, exc_traceback,
235                               limit=5, file=sys.stdout)  
236     try:
237       database.close()
238     except:
239       None
240     reconnect()
241
242 def weather_mon():
243
244   global path
245
246   if path is None:
247     path = find_port()
248   ser = open_port(path)
249   read_loop(ser,process_str)
250
251 def reconnect():
252
253   connected = False
254
255   while not connected:            
256
257     try:
258
259       global database
260       database = MySQLdb.connect(host=dbhost,user=dbuser,passwd=dbpasswd,use_unicode=True,connect_timeout=10)
261       database.set_character_set('utf8')
262       c = database.cursor()
263       c.execute('SET NAMES utf8;')
264       print_log("Database connected...")
265       connected = True 
266   
267     except:
268         
269       print_log("Error connecting database")
270       sleep(30)
271       
272 def main():
273   weather_mon()
274
275 try:
276
277   cfg = ConfigParser.RawConfigParser(allow_no_value=True)
278   cfg.readfp(open('/etc/weathermon.conf'))
279   dbhost = cfg.get("mysql","host")
280   dbuser = cfg.get("mysql","user")
281   dbpasswd = cfg.get("mysql","passwd")
282   try:
283     path = cfg.get("serial","port");
284   except:
285     path = None
286   try:    
287     serialnum = cfg.get("serial","id")
288   except:
289     serialnum = None  
290   try:
291     logging = cfg.get("logging","enabled")
292   except:
293     logging = None
294   owmuser = cfg.get("openweathermap","user")
295   owmpasswd = cfg.get("openweathermap",'passwd')
296   if owmuser:
297     owm_temp = cfg.get("openweathermap",'temp')
298     owm_pres = cfg.get("openweathermap",'pres')
299     owm_humi = cfg.get("openweathermap",'humi')
300     owm_lat = cfg.get("openweathermap",'lat')
301     owm_lon = cfg.get("openweathermap",'lon')
302     owm_station = cfg.get("openweathermap",'station')
303   reconnect()
304  
305 except:
306
307   print_log("Cannot intialize system")
308   exit() 
309   
310 if __name__ == "__main__":
311   import sys
312   reload(sys)
313   sys.setdefaultencoding('utf-8')
314   main()