Exemplo n.º 1
0
 def __init__(self, host, log_file, **kwargs):
     if 'debug' in kwargs:
         self.debug = kwargs['debug']
         del kwargs['debug']
     else:
         self.debug = False
     self.TPL_GLOBALS['debug_mode'] = self.debug
     self.o20 = Opus20(host, **kwargs)
     self.o20.disconnect()
     self.logfile = log_file
     self.ps = PickleStore(log_file)
     self._current_values_last_call = -1E13
     self._download_device_data_last_call = -1E13
     super(PlotWebServer, self).__init__()
     self.route('/list/devices', callback = self._list_devices)
     self.route('/download/<device_id>', callback = self._download_device_data)
     self.route('/status/<device_id>', callback = self._status_device)
     self.route('/plot/<device_id>_history.<fileformat>', callback = self._plot_history)
     self.route('/static/<filename:path>', callback = self._serve_static)
     if self.debug: self.route('/debug', callback = self._debug_page)
     self.route('/plots', callback = self._plots_page)
     self.route('/about', callback = self._about_page)
     self.route('/', callback = self._status_page)
Exemplo n.º 2
0
class PlotWebServer(Bottle):

    DPI = 72
    TPL_GLOBALS = {}
    MIME_MAP = {
      'pdf': 'application/pdf',
      'png': 'image/png',
      'svg': 'image/svg+xml'
    }

    def __init__(self, host, log_file, **kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
            del kwargs['debug']
        else:
            self.debug = False
        self.TPL_GLOBALS['debug_mode'] = self.debug
        self.o20 = Opus20(host, **kwargs)
        self.o20.disconnect()
        self.logfile = log_file
        self.ps = PickleStore(log_file)
        self._current_values_last_call = -1E13
        self._download_device_data_last_call = -1E13
        super(PlotWebServer, self).__init__()
        self.route('/list/devices', callback = self._list_devices)
        self.route('/download/<device_id>', callback = self._download_device_data)
        self.route('/status/<device_id>', callback = self._status_device)
        self.route('/plot/<device_id>_history.<fileformat>', callback = self._plot_history)
        self.route('/static/<filename:path>', callback = self._serve_static)
        if self.debug: self.route('/debug', callback = self._debug_page)
        self.route('/plots', callback = self._plots_page)
        self.route('/about', callback = self._about_page)
        self.route('/', callback = self._status_page)

    def _atg(self, vals):
        """ Add template globals
            A wrapper function for the templated routes decorated with a @view() """
        vals.update(self.TPL_GLOBALS)
        return vals

    @view('status.jinja2')
    def _status_page(self):
        return self._atg({'device_id': self._connected_device, 'current_values': self.current_values, 'active': 'status'})

    @view('about.jinja2')
    def _about_page(self):
        version = require("opus20")[0].version
        return self._atg({'active': 'about', 'opus20_version': version})

    @view('plots.jinja2')
    def _plots_page(self):
        return self._atg({'device_id': self._connected_device, 'active': 'plots'})

    @view('debug.jinja2')
    def _debug_page(self):
        return self._atg({
          'active': 'debug',
          'debug_dict': {
            'self._current_values_last_call': self._current_values_last_call,
            'self._download_device_data_last_call': self._download_device_data_last_call,
          }
        })

    @property
    def current_values(self):
        """ the current values """
        if  clock() - self._current_values_last_call < 2.0:
            return self._cached_current_values
        self._current_values_last_call = clock()
        cur = Object()
        values = self.o20.multi_channel_value( [0x0064, 0x006E, 0x00C8, 0x00CD, 0x2724] )
        cur.device_id =         self.o20.device_id
        cur.temperature =       values[0]
        cur.dewpoint =          values[1]
        cur.relative_humidity = values[2]
        cur.absolute_humidity = values[3]
        cur.battery_voltage =   values[4]
        cur.ts = datetime.now().replace(microsecond=0)
        self._cached_current_values = cur
        return cur

    def _status_device(self, device_id):
        assert device_id == self._connected_device
        status = self.current_values.to_dict()
        status['ts'] = status['ts'].isoformat()
        return {
                'success': True,
                'status': status,
               }

    def _serve_static(self, filename):
        return static_file(filename, root=os.path.join(PATH, 'static'))

    @property
    def _connected_device(self):
        """ As long as the webserver can handle only a single
            OPUS20 device, we return this single one here. """
        return self.o20.device_id

    def _list_devices(self):
        return {
                'success': True,
                'devices': list(set([self._connected_device]) | set(self.ps.get_device_ids()))
               }

    def _download_device_data(self, device_id):
        if  clock() - self._download_device_data_last_call < 10.0:
            return {'success': True, 'cached': True}
        self._download_device_data_last_call = clock()
        try:
            max_ts = self.ps.max_ts()[device_id]
        except KeyError:
            max_ts = None
        log_data = self.o20.download_logs(start_datetime=max_ts)
        self.o20.disconnect()
        self.ps.add_data(self.o20.device_id, log_data)
        self.ps.persist()
        return {'success': True}

    def _plot_history(self, device_id, fileformat):
        from io import BytesIO
        import matplotlib
        matplotlib.use('Agg')
        import matplotlib.pyplot as plt
        import numpy as np
        import pandas as pd

        df = pd.DataFrame(self.ps.get_data(device_id))

        df = df.set_index('ts', drop=True)
        df.columns = [OPUS20_CHANNEL_SPEC[col]['name'] for col in df.columns]

        # Handling of URL query variables
        color = request.query.get('color', 'b,m,y,r,g,k').split(',')
        ylabel =  request.query.get('ylabel',  'temperature [°C]')
        y2label = request.query.get('y2label', 'humidity [%]')
        q_range = request.query.range
        if q_range:
            if ',' in q_range:
                q_range = q_range.split(',')
                df = df[q_range[0]:q_range[1]]
            else:
                df = df[q_range]
        else:
            q_range = 'All Time'
        figsize = request.query.figsize or '10,6'
        figsize = (float(num) for num in figsize.split(','))
        dpi = request.query.dpi or self.DPI
        dpi = float(dpi)
        #resample = request.query.resample or '2min'
        measures = request.query.measures
        if not measures:
            measures = ('temperature', 'relative humidity')
        else:
            measures = measures.split(',')
        right = request.query.get('right', None)
        if right is None:
            right = ('relative humidity',)
        else:
            right = right.split(',')
        #selected_cols = df.columns
        selected_cols = []
        for measure in measures:
            for col in df.columns:
                if measure in col:
                    if col not in selected_cols: selected_cols.append(col)
        right_cols = []
        for col in selected_cols:
            for measure in right:
                if measure in col:
                    if col not in right_cols: right_cols.append(col)
        # / End handling URL query variables

        fig, ax = plt.subplots(figsize=figsize)
        if len(selected_cols) == 1: color = color[0]
        df.ix[:,selected_cols].plot(ax=ax, color=color, grid=True, secondary_y=right_cols, x_compat=True)
        ax.set_xlabel('')
        ax.set_ylabel(ylabel)
        if len(right_cols): plt.ylabel(y2label)
        ax.set_title("OPUS20 device: " + device_id)
        #start, end = ax.get_xlim()
        #ax.xaxis.set_ticks(np.arange(start, end, 1.0))
        #ax.xaxis.grid(True, which="minor")
        #ax.legend()

        io = BytesIO()
        plt.savefig(io, format=fileformat, dpi=dpi)
        plt.close()
        response.content_type = self.MIME_MAP[fileformat]
        return io.getvalue()

    def disconnect_opus20(self):
        self.o20.close()
Exemplo n.º 3
0
def main():

    parser = argparse.ArgumentParser(description="CLI for the Lufft OPUS20. Note that the subcommands provide their own --help!")
    parser.add_argument('host', help='hostname of the device')
    parser.add_argument('--port', '-p', type=int, help='TCP port of the OPUS20')
    parser.add_argument('--timeout', '-t', type=float, help='Timeout of the TCP connection in seconds')
    parser.add_argument('--loglevel', choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'], help='Sets the verbosity of this script')
    subparsers = parser.add_subparsers(title='commands', help='', dest='cmd')
    parser_list = subparsers.add_parser('list', help='list all possible measurement channels')
    parser_get = subparsers.add_parser('get', help='get the value(s) of specific channel(s)')
    parser_get.add_argument('channel', type=extended_int, nargs='+', help='The selected channel(s)')
    parser_download = subparsers.add_parser('download', help='download the logs and store them locally')
    parser_download.add_argument('persistance_file', help='file to store the logs in')
    parser_logging = subparsers.add_parser('logging', help='change or query global logging settings (start, stop, clear)')
    subsubparsers = parser_logging.add_subparsers(help='Action to perform w/ respect to logging', dest='action')
    parser_logging_action_status = subsubparsers.add_parser('status', help='Query the current logging status of the device')
    parser_logging_action_start  = subsubparsers.add_parser('start', help='Start logging altogether on the device')
    parser_logging_action_stop   = subsubparsers.add_parser('stop', help='Stop logging altogether on the device')
    parser_logging_action_clear  = subsubparsers.add_parser('clear', help='Clear the log history on the device')
    parser_enable = subparsers.add_parser('enable', help='enable logging for a specific channel')
    parser_enable.add_argument('channel', type=extended_int, nargs='+', help='The selected channel(s)')
    parser_disable = subparsers.add_parser('disable', help='disable logging for a specific channel')
    parser_disable.add_argument('channel', type=extended_int, nargs='+', help='The selected channel(s)')
    args = parser.parse_args()

    if not args.cmd: parser.error('please select a command')
    if args.cmd == 'logging' and not args.action: parser.error('please select a logging action')

    if args.loglevel:
        logging.basicConfig(level=getattr(logging, args.loglevel.upper()))

    start = clock()
    o20 = None
    try:
        kwargs = {}
        if args.port: kwargs['port'] = args.port
        if args.timeout: kwargs['timeout'] = args.timeout
        o20 = Opus20(args.host, **kwargs)

        if args.cmd == 'list':
            for channel in o20.available_channels:
                print("Channel {:5d} (0x{:04X}): {name:22s}  unit: {unit:6s} offset: {offset}".format(channel, channel, **OPUS20_CHANNEL_SPEC[channel]))
        if args.cmd == 'get':
            if len(args.channel) > 1:
                for channel in o20.multi_channel_value(args.channel):
                    print("{:.3f}".format(channel))
            else:
                print("{:.3f}".format(o20.channel_value(args.channel[0])))
        if args.cmd == 'download':
            ps = PickleStore(args.persistance_file)
            try:
                max_ts = ps.max_ts()[o20.device_id]
            except KeyError:
                max_ts = None
            log_data = o20.download_logs(start_datetime=max_ts)
            ps.add_data(o20.device_id, log_data)
            ps.persist()
        if args.cmd == 'logging':
            def logging_in_words(): return 'enabled' if o20.get_logging_state() else 'disabled'
            if args.action == 'status':
                print("Logging is currently " + logging_in_words() + ".")
            elif args.action in ('start', 'stop'):
                o20.set_logging_state(args.action == 'start')
                logger.info("Logging is now " + logging_in_words() + ".")
            elif args.action == 'clear':
                o20.clear_log()
                print('Clearing the log now. This will take a couple of minutes.')
                print('You cannot make requests to the device during that time.')
                o20.disconnect()
        if args.cmd in ('enable', 'disable'):
            enable = args.cmd == 'enable'
            for channel in args.channel:
                o20.set_channel_logging_state(enable)

    except Opus20ConnectionException as e:
        parser.error(str(e))

    finally:
        try:
            o20.disconnect()
            o20 = None
        except:
            pass
    end = clock()
    logger.info("script running time (net): {:.6f} seconds.".format(end-start))