pyP2Monitor  1.0.0
Monitor a P2 furnace activity reading data on serial port
 All Data Structures Namespaces Functions Variables Groups Pages
utils.py
1 # -*- coding: utf-8 -*-#
2 
3 # Copyright 2013, 2014 Weber Yann, Weber Laurent
4 #
5 # This file is part of pyP2Monitor.
6 #
7 # pyP2Monitor is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # pyP2Monitor is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with pyP2Monitor. If not, see <http://www.gnu.org/licenses/>.
19 #
20 
21 ##@package Store usefull functions
22 
23 
24 """
25 Doxygen main page
26 """
27 ##@mainpage pyP2Monitor doxygen documentation
28 #
29 # pyP2Monitor is a couple of programm designed to gather and view datas from a Froeling P2 furnace (develloped and tested on a Froeling P2-20 ) using the furnace's serial port.
30 # The data's processing programm can use GnuPlot to generate charts showing datas over time.
31 # The data's reading programm use a sqlite database to store the readed datas.
32 #
33 # @section pyp2licence Licence information
34 #
35 # Copyright 2013, 2014 Weber Yann, Weber Laurent
36 #
37 # pyP2Monitor is free software: you can redistribute it and/or modify
38 # it under the terms of the GNU General Public License as published by
39 # the Free Software Foundation, either version 3 of the License, or
40 # (at your option) any later version.
41 #
42 # pyP2Monitor is distributed in the hope that it will be useful,
43 # but WITHOUT ANY WARRANTY; without even the implied warranty of
44 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45 # GNU General Public License for more details.
46 #
47 # You should have received a copy of the GNU General Public License
48 # along with pyP2Monitor. If not, see <http://www.gnu.org/licenses/>.
49 #
50 # @section mpthanks Thanks
51 #
52 # For helping us understanding the furnace and its communication protocol
53 #
54 # - HervĂ© Wacheux
55 # - Thomas Fahr
56 # - Thomas Rigert
57 #
58 # @section execs User documentation
59 #
60 # - @ref serialmon "Using the monitoring programm"
61 # - @ref dprocess "Data reading and processing"
62 #
63 # @section mpfi Devellopers documentation
64 #
65 # Classes and functions descriptions.
66 #
67 # @subsection mpllfc Low level furnace communication
68 #
69 # - @ref p2com "Serial port communication package"
70 #
71 # @subsection mpfmp Furnace message processing
72 #
73 # - @ref p2msg "Raw message processing and handling package"
74 # - @ref p2dbstore "Database message storage"
75 # - @ref p2data "Furnace frame formating and GnuPlot generation"
76 #
77 # @subsection mpfcp Furnace communication protocol
78 #
79 # - @ref p2proto Furnace protocol handling package
80 #
81 
82 """
83 Doxygen howto page
84 """
85 
86 ##@defgroup lowlevel Low level communication
87 
88 ##@defgroup msgprocess Furnace message processing
89 
90 ##@defgroup comproto Furnace communication protocol
91 
92 ##Stores the monitoring programm version
93 VERSION="pyP2Monitor v1.0.0"
94 ##Stores the reader programm version
95 VERSION_READER="pyP2DataReader v1.0.0"
96 
97 import sys
98 import argparse
99 import logging
100 import logging.handlers
101 
102 ##Function used to parse options and return a dict with all options in it
103 #
104 # Actually options are (see below for meaning):
105 # background
106 # data_wait
107 # database
108 # csv
109 # file
110 # delay
111 # log_file
112 # log_level
113 # log_num
114 # log_size
115 # max_data
116 # max_time
117 # port
118 # print_data
119 # quiet
120 # stage
121 # user
122 # verbosity
123 # version
124 def argParse(prog="monitor", usage = False):
125  u = usage
126  if prog=="monitor":
127  return monitorArgParse(u)
128  else:
129  return readerArgParse(u)
130 
131 ## Use argParse to parse command line options for the monitor programm
132 #
133 # @return A dict with options value
134 def monitorArgParse(usage = False):
135  parser = argparse.ArgumentParser(prog="pyP2Monitor",description=VERSION+' : Get and store datas from a P2 Furnace using a serial port.',
136  epilog='pyP2Monitor is under GNU GPL')
137 
138  serial_arg = parser.add_argument_group('Serial port options')
139 
140  data_arg = parser.add_argument_group('Data processing options')
141 
142  run_arg = parser.add_argument_group('Runtime options')
143 
144  daemon_arg = parser.add_argument_group('Daemon options')
145 
146  log_arg = parser.add_argument_group('Logging options')
147 
148 
149  parser.add_argument('-v', '--version', action='store_const', const=True, default=False,
150  help='Display the programm version and exit')
151 
152  serial_arg.add_argument('-p', '--port', action='store', type=str, default='/dev/ttyS0',
153  help='Set the serial port file wich the furnace is plugged ( exemple /dev/ttyUSB0 )')
154  serial_arg.add_argument('-D', '--delay', action='store', type=int, metavar='MICROSEC',
155  help='Set the number of microseconds to wait between to send on the serial port')
156 
157  data_arg.add_argument('-d', '--database', action='append', type=str, metavar='SQLITE_DB_FILE',
158  help='Tell the programm to store data in SQLITE_DB_FILE sqlite database')
159  data_arg.add_argument('-l', '--last-data', action='store', type=str, metavar='FILENAME',
160  help='Tell the programm to store the latest readed data in FILENAME (used with -L option of the reader)')
161  data_arg.add_argument('-c', '--csv', action='append', type=str, metavar='CSV_FILE',
162  help='Tell the programm to store data in CSV_FILE file in csv format')
163  data_arg.add_argument('-F', '--file', action='append', type=str, metavar='FILENAME',
164  help='Tell the programm to store data in FILENAME file as \'date:[invalid]:hex_string\' (it is the only storage keeping invalid messages)')
165  data_arg.add_argument('-P', '--print-data', action='store_const', const=True, default=False,
166  help='Print received data on stdout')
167  data_arg.add_argument('-w', '--data-wait', action='store', type=float, default=1, metavar='SECS',
168  help='Time to wait between two data request')
169 
170  run_arg.add_argument('-t', '--max-time', action='store', type=int, metavar='SECS',
171  help='Tell the programm to stop after SECS seconds')
172  run_arg.add_argument('-n', '--max-data', action='store', type=int, metavar='INTEGER',
173  help='Tell the programm to stop after receiving NUMBER valid datas')
174  run_arg.add_argument('--stage', action='append', choices=['auth', 'init', 'data', 'all'], default=None,
175  help='Indicate wich stage in wich order to run (for example "--stage data init" while run the exchange stage before the init stage)')
176  run_arg.add_argument('-u', '--user', choices=['plumber', 'normal', 'normal2', 'service'], default='service',
177  help='Set the user used to authenticate on the furnace')
178 
179  daemon_arg.add_argument('-B', '--background', action='store_const', const=True, default=False,
180  help='Tell the programm to run in background (not implemented yet)')
181  daemon_arg.add_argument('-K', '--stop', action='store_const', const=True, default=False,
182  help='Try to kill a pyP2SerialMonitor in background')
183  daemon_arg.add_argument('-i', '--pidfile', action='store', type=str, default="/tmp/pyP2SerialMon.pid", metavar='PIDFILE',
184  help='Pidfile name (used with -B or -K)')
185 
186 
187  log_arg.add_argument('--verbosity', action='store', choices=[ 'critical', 'error', 'warn', 'info', 'debug', 'silent'], default='error',
188  help='Set the log level for console output')
189  log_arg.add_argument('-q', '--quiet', '--silent', action='store_const', const=True, default=False,
190  help='Run silentely (no console output)')
191  log_arg.add_argument('-f', '--log-file', action='store', type=str,
192  help='Set the log file')
193  log_arg.add_argument('--log-level', action='store', choices=['info', 'warn', 'error', 'debug', 'silent'], default='warn',
194  help='Set the log level for logfile')
195  log_arg.add_argument('--log-size', action='store', type=int, default=5120, metavar='BYTES',
196  help='Set the maximum size for a logfile before rotating to another logfile')
197  log_arg.add_argument('--log-num', action='store', type=int, default=5, metavar='INTEGER',
198  help='Set the number of logfile to keep')
199 
200  if usage:
201  parser.print_usage(sys.stderr)
202  exit(1)
203 
204  args = parser.parse_args()
205 
206  res = vars(args)
207 
208  if res['stage'] == None:
209  res['stage'] = ['all']
210 
211  return vars(args)
212 
213 ## Use argParse to parse command line options for the reader programm
214 #
215 # @return A dict with options value
216 def readerArgParse(usage = False):
217  parser = argparse.ArgumentParser(prog="pyP2DataReader",description=VERSION_READER+' : Get datas from a sqlite database and output a picture representing datas giving a format.',
218  epilog='pyP2DataReader is part of pyP2Monitor wich is is under GNU GPL')
219 
220  serial_arg = parser.add_argument_group('Serial port options')
221 
222  db_arg = parser.add_argument_group('Database options')
223 
224  in_arg = parser.add_argument_group('Input file options')
225 
226  out_arg = parser.add_argument_group('Output options')
227 
228  log_arg = parser.add_argument_group('Logging options')
229 
230 
231  db_arg.add_argument('-d', '--database', action='store', type=str, default=False, metavar='SQLITE_DB_FILE',
232  help='The database storing datas')
233  db_arg.add_argument('-q', '--query', action='append', type=str, metavar='QUERY_STRING',
234  help='Queries to be done')
235  db_arg.add_argument('-s', '--separator', action='store', type=str, default=',', metavar='STRING',
236  help='One or more characters used as argument separator in a query (default is ",")')
237  db_arg.add_argument('--field-list', action='store_const', const=True, default=False,
238  help='List data fields and them numbers. Then exit.');
239 
240  out_arg.add_argument('-o', '--output', action='store', type=str, default='out', metavar='FILENAME',
241  help='Output file')
242  out_arg.add_argument('-f', '--format', action='store', choices=['csv', 'png', 'jpg', 'svg', 'gnuplot'], default='gnuplot', metavar='FORMAT',
243  help='Output format (png, jpg, svg, data, gnuplot)')
244  out_arg.add_argument('-t', '--title', action='store', type=str, default=None, metavar='STRING',
245  help='Output title')
246  out_arg.add_argument('-r', '--resolution', action='store', type=str, default=None, metavar='width,height',
247  help='Set the size of an output image')
248 
249  out_arg.add_argument('-L', '--last-data', action='store', type=str, default=None, metavar='FILENAME',
250  help='Output the last furnace data (readed from FILENAME) in CSV on stdout. See -l option from the monitor')
251 
252  out_arg.add_argument('--csvdump', action="store", type=str, default=None, metavar='FILENAME.csv', help='Dump the db into a csv file ( - for stdout)')
253 
254  ############
255  # Not yet implemented
256  #
257  """
258  in_arg.add_argument('-i', '--input', action='store', type=str, metavar='GNUPLOT_DATAFILE',
259  help='Use this datafile as input to generate output')
260 
261  in_arg.add_argument('-m', '--merge', action='store', type=str, metavar='GNUPLOT_DATAFILE',
262  help='Tell the programm to merge datas with this data file')
263  """
264  #
265  #
266  ############
267 
268  parser.add_argument('--verbosity', action='store', choices=[ 'critical', 'error', 'warn', 'info', 'debug', 'silent'], default='error',
269  help='Set the log level for console output')
270  parser.add_argument('-v', '--version', action='store_const', const=True, default=False,
271  help='Display the programm version and exit')
272 
273  if usage:
274  parser.print_usage(sys.stderr)
275  exit(1)
276 
277  args = parser.parse_args()
278 
279  res = vars(args)
280 
281  return vars(args)
282 
283 
284 
285 ##Function used to initialise the logger
286 #
287 # Use logging package
288 #
289 # @param verbosity Use to set the logging level for console output
290 # @param log_file Use to set the base filename for log files
291 # @param log_level Use to set the logging level for log files
292 # @param log_num An integer telling how many logfile to keep
293 # @param log_size Size in byte to trigger logs rotation (
294 def initLogging(verbosity, log_file = None,log_level = None,log_num = None,log_size = None):
295  # create logger
296  logger = getLogger()
297  logger.setLevel(logging.DEBUG)
298 
299  formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
300 
301  # create console handler and set level to debug
302  ch = logging.StreamHandler()
303 
304  lvl = getLogLevelConst(verbosity)
305 
306  if lvl< 1000:
307  ch.setLevel(lvl)
308  logger.addHandler(ch)
309  ch.setFormatter(formatter)
310 
311  if log_file != None and log_num != None and log_size != None:
312  fl = logging.handlers.RotatingFileHandler(log_file, mode='a', maxBytes=log_size, backupCount=log_num, delay=True)
313 
314  lvl = getLogLevelConst(log_level)
315  if lvl < 1000:
316  fl.setLevel(lvl)
317  logger.addHandler(fl)
318  fl.setFormatter(formatter)
319 
320  logger.debug('logger configured')
321 
322 ##Convert a level get from the command line to a real logging level
323 def getLogLevelConst(strlvl):
324  res = logging.ERROR
325 
326  if strlvl == 'debug':
327  res = logging.DEBUG
328  elif strlvl == 'info':
329  res = logging.INFO
330  elif strlvl == 'warn':
331  res = logging.WARNING
332  elif strlvl == 'error':
333  res = logging.ERROR
334  elif strlvl == 'critical':
335  res = logging.CRITICAL
336  elif strlvl == 'silent':
337  res = 1000
338 
339  return res
340 
341 
342 ##Return the logger used in the whole application
343 def getLogger():
344  return logging.getLogger('pyP2Monitor')
345 
346