示例#1
0
    def __init__(self, endpoint_url=None, sensor_name=None, *args, **kwargs):
        self.logger = get_logger()
        self.logger.info(f'Setting up remote sensor {sensor_name}')

        # Setup the DB either from kwargs or config.
        self.db = None
        db_type = get_config('db.type', default='file')

        if 'db_type' in kwargs:
            self.logger.info(f"Setting up {kwargs['db_type']} type database")
            db_type = kwargs.get('db_type', db_type)

        self.db = PanDB(db_type=db_type)

        self.sensor_name = sensor_name
        self.sensor = None

        if endpoint_url is None:
            # Get the config for the sensor
            endpoint_url = get_config(f'environment.{sensor_name}.url')
            if endpoint_url is None:
                raise error.PanError(f'No endpoint_url for {sensor_name}')

        if not endpoint_url.startswith('http'):
            endpoint_url = f'http://{endpoint_url}'

        self.endpoint_url = endpoint_url
示例#2
0
def db_type(request):
    db_list = request.config.option.test_databases
    if request.param not in db_list and 'all' not in db_list:
        pytest.skip("Skipping {} DB, set --test-all-databases=True".format(
            request.param))

    PanDB.permanently_erase_database(request.param,
                                     'panoptes_testing',
                                     really='Yes',
                                     dangerous='Totally')
    return request.param
示例#3
0
def db_type(request):
    db_list = request.config.option.test_databases
    if request.param not in db_list and 'all' not in db_list:  # pragma: no cover
        pytest.skip(f"Skipping {request.param} DB, set --test-all-databases=True")

    PanDB.permanently_erase_database(request.param,
                                     'panoptes_testing',
                                     storage_dir='testing',
                                     really='Yes',
                                     dangerous='Totally')
    return request.param
示例#4
0
def db_type(request):

    db_list = request.config.option.test_databases
    if request.param not in db_list and 'all' not in db_list:
        pytest.skip("Skipping {} DB, set --test-all-databases=True".format(request.param))

    # If testing mongo, make sure we can connect, otherwise skip.
    if request.param == 'mongo' and not can_connect_to_mongo():
        pytest.skip("Can't connect to {} DB, skipping".format(request.param))
    PanDB.permanently_erase_database(
        request.param, 'panoptes_testing', really='Yes', dangerous='Totally')
    return request.param
示例#5
0
def can_connect_to_mongo():
    global _can_connect_to_mongo
    if _can_connect_to_mongo is None:
        logger = get_root_logger()
        try:
            PanDB(db_type='mongo', db_name='panoptes_testing', logger=logger, connect=True)
            _can_connect_to_mongo = True
        except Exception:
            _can_connect_to_mongo = False
        logger.info('can_connect_to_mongo = {}', _can_connect_to_mongo)
    return _can_connect_to_mongo
示例#6
0
    def __init__(self, config_host=None, config_port=None, *args, **kwargs):
        self.__version__ = __version__

        self._config_host = config_host or os.getenv('PANOPTES_CONFIG_HOST', 'localhost')
        self._config_port = config_port or os.getenv('PANOPTES_CONFIG_PORT', 6563)

        self.logger = get_logger()

        global PAN_DB_OBJ
        if PAN_DB_OBJ is None:
            # If the user requests a db_type then update runtime config
            db_type = kwargs.get('db_type', self.get_config('db.type', default='file'))
            db_name = kwargs.get('db_name', self.get_config('db.name', default='panoptes'))
            db_folder = kwargs.get('db_folder', self.get_config('db.folder', default='json_store'))

            PAN_DB_OBJ = PanDB(db_type=db_type, db_name=db_name, storage_dir=db_folder)

        self.db = PAN_DB_OBJ
示例#7
0
def test_delete_file_db():
    with pytest.raises(Exception):
        PanDB.permanently_erase_database('memory',
                                         'panoptes_testing',
                                         really='Nope',
                                         dangerous='Hopefully not')

    with pytest.raises(ValueError):
        PanDB.permanently_erase_database('memory',
                                         'do_not_delete_me',
                                         really='Nope',
                                         dangerous='Again, we hope not')

    PanDB.permanently_erase_database('file',
                                     'panoptes_testing',
                                     storage_dir='testing',
                                     really='Yes',
                                     dangerous='Totally')
    PanDB.permanently_erase_database('memory',
                                     'panoptes_testing',
                                     dangerous='Totally',
                                     really='Yes')
示例#8
0
def db(db_type):
    return PanDB(db_type=db_type,
                 db_name='panoptes_testing',
                 storage_dir='testing',
                 connect=True)
示例#9
0
def test_with_db():
    base = PanBase(db=PanDB(db_type='memory', db_name='tester'))
    assert isinstance(base, PanBase)
示例#10
0
def test_bad_db():
    with pytest.raises(Exception):
        PanDB('foobar', storage_dir='')
示例#11
0
class PanSensorShell(cmd.Cmd):
    """ A simple command loop for the sensors. """
    intro = 'Welcome to PEAS Shell! Type ? for help'
    prompt = 'PEAS > '
    weather = None
    control_board = None
    control_env_board = None
    camera_board = None
    camera_env_board = None
    active_sensors = dict()
    db = PanDB(db_type=get_config('db.type', default='file'))
    _keep_looping = False
    _loop_delay = 60
    _timer = None
    captured_data = list()

    telemetry_relay_lookup = {
        'computer': {
            'pin': 8,
            'board': 'telemetry_board'
        },
        'fan': {
            'pin': 6,
            'board': 'telemetry_board'
        },
        'camera_box': {
            'pin': 7,
            'board': 'telemetry_board'
        },
        'weather': {
            'pin': 5,
            'board': 'telemetry_board'
        },
        'mount': {
            'pin': 4,
            'board': 'telemetry_board'
        },
        'cam_0': {
            'pin': 5,
            'board': 'camera_board'
        },
        'cam_1': {
            'pin': 6,
            'board': 'camera_board'
        },
    }

    # NOTE: These are not pins but zero-based index numbers.
    controlboard_relay_lookup = {
        'computer': {
            'pin': 0,
            'board': 'control_board'
        },
        'mount': {
            'pin': 1,
            'board': 'control_board'
        },
        'camera_box': {
            'pin': 2,
            'board': 'control_board'
        },
        'weather': {
            'pin': 3,
            'board': 'control_board'
        },
        'fan': {
            'pin': 4,
            'board': 'control_board'
        },
    }

    ##################################################################################################
    # Generic Methods
    ##################################################################################################

    def do_status(self, *arg):
        """ Get the entire system status and print it pretty like! """
        if self._keep_looping:
            console.color_print("{:>12s}: ".format('Loop Timer'), "default",
                                "active", "lightgreen")
        else:
            console.color_print("{:>12s}: ".format('Loop Timer'), "default",
                                "inactive", "yellow")

        for sensor_name in ['control_board', 'camera_board', 'weather']:
            if sensor_name in self.active_sensors:
                console.color_print("{:>12s}: ".format(sensor_name.title()),
                                    "default", "active", "lightgreen")
            else:
                console.color_print("{:>12s}: ".format(sensor_name.title()),
                                    "default", "inactive", "yellow")

    def do_last_reading(self, device):
        """ Gets the last reading from the device. """
        if not device:
            print_warning('Usage: last_reading <device>')
            return
        if not hasattr(self, device):
            print_warning('No such sensor: {!r}'.format(device))
            return

        rec = self.db.get_current(device)

        if rec is None:
            print_warning('No reading found for {!r}'.format(device))
            return

        print_info('*' * 80)
        print("{}:".format(device.upper()))
        pprint(rec)
        print_info('*' * 80)

        # Display the age in seconds of the record
        if isinstance(rec.get('date'), datetime.datetime):
            now = current_time(datetime=True).astimezone(utc)
            record_date = rec['date'].astimezone(utc)
            age = (now - record_date).total_seconds()
            if age < 120:
                print_info('{:.1f} seconds old'.format(age))
            else:
                print_info('{:.1f} minutes old'.format(age / 60.0))

    def complete_last_reading(self, text, line, begidx, endidx):
        """Provide completions for sensor names."""
        names = list(self.active_sensors.keys())
        return [name for name in names if name.startswith(text)]

    def do_enable_sensor(self, sensor, delay=None):
        """ Enable the given sensor """
        if delay is None:
            delay = self._loop_delay

        if hasattr(self, sensor) and sensor not in self.active_sensors:
            self.active_sensors[sensor] = {'reader': sensor, 'delay': delay}

    def do_disable_sensor(self, sensor):
        """ Disable the given sensor """
        if hasattr(self, sensor) and sensor in self.active_sensors:
            del self.active_sensors[sensor]

    def do_toggle_debug(self, sensor):
        """ Toggle DEBUG on/off for sensor

        Arguments:
            sensor {str} -- environment, weather
        """
        # TODO(jamessynge): We currently use a single logger, not one per module or sensor.
        # Figure out whether to keep this code and make it work, or get rid of it.
        import logging
        get_level = {
            logging.DEBUG: logging.INFO,
            logging.INFO: logging.DEBUG,
        }

        if hasattr(self, sensor):
            try:
                log = getattr(self, sensor).logger
                log.setLevel(get_level[log.getEffectiveLevel()])
            except Exception:
                print_error("Can't change log level for {}".format(sensor))

    def complete_toggle_debug(self, text, line, begidx, endidx):
        """Provide completions for toggling debug logging."""
        names = list(self.active_sensors.keys())
        return [name for name in names if name.startswith(text)]

##################################################################################################
# Load Methods
##################################################################################################

    def do_load_all(self, *arg):
        """Load the weather and environment sensors."""
        if self._keep_looping:
            print_error('The timer loop is already running.')
            return
        self.do_load_weather()
        self.do_load_control_board()
        self.do_load_camera_board()

    def do_load_control_board(self, *arg):
        """ Load the arduino control_board sensors """
        if self._keep_looping:
            print_error('The timer loop is already running.')
            return
        print("Loading control board sensor")
        self.control_board = ArduinoSerialMonitor(sensor_name='control_board',
                                                  db_type=get_config(
                                                      'db.type',
                                                      default='file'))
        self.do_enable_sensor('control_board', delay=10)

    def do_load_camera_board(self, *arg):
        """ Load the arduino camera_board sensors """
        if self._keep_looping:
            print_error('The timer loop is already running.')
            return
        print("Loading camera board sensor")
        self.camera_board = ArduinoSerialMonitor(
            sensor_name='camera_board',
            db_type=get_config('db.type', default='file'))
        self.do_enable_sensor('camera_board', delay=10)

    def do_load_control_env_board(self, *arg):
        """ Load the arduino control_board sensors """
        if self._keep_looping:
            print_error('The timer loop is already running.')
            return
        print("Loading control box environment board sensor")
        endpoint_url = get_config('environment.control_env_board.url')
        self.control_env_board = RemoteMonitor(endpoint_url=endpoint_url,
                                               sensor_name='control_env_board',
                                               db_type=get_config(
                                                   'db.type', default='file'))
        self.do_enable_sensor('control_env_board', delay=10)

    def do_load_camera_env_board(self, *arg):
        """ Load the arduino control_board sensors """
        if self._keep_looping:
            print_error('The timer loop is already running.')
            return
        print("Loading camera box environment board sensor")
        endpoint_url = get_config('environment.camera_env_board.url')
        self.camera_env_board = RemoteMonitor(endpoint_url=endpoint_url,
                                              sensor_name='camera_env_board',
                                              db_type=get_config(
                                                  'db.type', default='file'))
        self.do_enable_sensor('camera_env_board', delay=10)

    def do_load_weather(self, *arg):
        """ Load the weather reader """
        if self._keep_looping:
            print_error('The timer loop is already running.')
            return

        print("Loading weather reader endpoint")
        endpoint_url = get_config('environment.weather.url')
        self.weather = RemoteMonitor(endpoint_url=endpoint_url,
                                     sensor_name='weather',
                                     db_type=get_config('db.type',
                                                        default='file'))
        self.do_enable_sensor('weather', delay=60)

##################################################################################################
# Relay Methods
##################################################################################################

    def do_turn_off_relay(self, *arg):
        """Turn on relay.

        The argument should be the name of the relay, i.e. on of:

            * fan
            * mount
            * weather
            * camera_box

        The names must correspond to the entries in the lookup tables above.
        """
        relay = arg[0]

        if hasattr(self, 'control_board'):
            relay_lookup = self.controlboard_relay_lookup
        else:
            relay_lookup = self.telemetry_relay_lookup

        try:
            relay_info = relay_lookup[relay]
            serial_connection = self.control_board.serial_readers[
                relay_info['board']]['reader']

            serial_connection.ser.reset_input_buffer()
            serial_connection.write("{},0\n".format(relay_info['pin']))
        except Exception as e:
            print_warning(f"Problem turning relay off {relay} {e!r}")
            print_warning(e)

    def do_turn_on_relay(self, *arg):
        """Turn off relay.

        The argument should be the name of the relay, i.e. on of:

            * fan
            * mount
            * weather
            * camera_box

        The names must correspond to the entries in the lookup tables above.
    """
        relay = arg[0]

        if hasattr(self, 'control_board'):
            relay_lookup = self.controlboard_relay_lookup
        else:
            relay_lookup = self.telemetry_relay_lookup

        try:
            relay_info = relay_lookup[relay]
            serial_connection = self.control_board.serial_readers[
                relay_info['board']]['reader']

            serial_connection.ser.reset_input_buffer()
            serial_connection.write("{},1\n".format(relay_info['pin']))
        except Exception as e:
            print_warning(f"Problem turning relay off {relay} {e!r}")
            print_warning(e)

    def complete_turn_off_relay(self, text, line, begidx, endidx):
        """Provide completions for relay names."""
        if hasattr(self, 'control_board'):
            names = ['camera_box', 'fan', 'mount', 'weather']
        else:
            names = ['cam_0', 'cam_1', 'camera_box', 'fan', 'mount', 'weather']
        return [name for name in names if name.startswith(text)]

    def complete_turn_on_relay(self, text, line, begidx, endidx):
        """Provide completions for relay names."""
        if hasattr(self, 'control_board'):
            names = ['camera_box', 'fan', 'mount', 'weather']
        else:
            names = ['cam_0', 'cam_1', 'camera_box', 'fan', 'mount', 'weather']
        return [name for name in names if name.startswith(text)]

    def do_toggle_computer(self, *arg):
        """Toggle the computer relay off and then on again after 30 seconds.

        Note:

            The time delay is set on the arduino and is blocking.
        """
        relay = 'computer'

        if hasattr(self, 'control_board'):
            relay_lookup = self.controlboard_relay_lookup
        else:
            relay_lookup = self.telemetry_relay_lookup

        try:
            relay_info = relay_lookup[relay]
            serial_connection = self.control_board.serial_readers[
                relay_info['board']]['reader']

            serial_connection.ser.reset_input_buffer()
            serial_connection.write("{},9\n".format(relay_info['pin']))
        except Exception as e:
            print_warning(f"Problem toggling computer: {e!r}")
            print_warning(e)

##################################################################################################
# Start/Stop Methods
##################################################################################################

    def do_start(self, *arg):
        """ Runs all the `active_sensors`. Blocking loop for now """
        if self._keep_looping:
            print_error('The timer loop is already running.')
            return

        self._keep_looping = True

        print_info("Starting sensors")

        self._loop()

    def do_stop(self, *arg):
        """ Stop the loop and cancel next call """
        # NOTE: We don't yet have a way to clear _timer.
        if not self._keep_looping and not self._timer:
            print_error('The timer loop is not running.')
            return

        print_info("Stopping loop")

        self._keep_looping = False

        if self._timer:
            self._timer.cancel()

    def do_change_delay(self, *arg):
        """Change the timing between reads from the named sensor."""
        # NOTE: For at least the Arduinos, we should not need a delay and a timer, but
        # simply a separate thread, reading from the board as data is available.
        # We might use a delay to deal with the case where the device is off-line
        # but we want to periodically check if it becomes available.
        parts = None
        if len(arg) == 1:
            parts = arg[0].split()
        if parts is None or len(parts) != 2:
            print_error('Expected a sensor name and a delay, not "{}"'.format(
                ' '.join(arg)))
            return
        sensor_name, delay = parts
        try:
            delay = float(delay)
            if delay <= 0:
                raise ValueError()
        except ValueError:
            print_warning("Not a positive number: {!r}".format(delay))
            return
        try:
            print_info("Changing sensor {} to a {} second delay".format(
                sensor_name, delay))
            self.active_sensors[sensor_name]['delay'] = delay
        except KeyError:
            print_warning("Sensor not active: {!r}".format(sensor_name))

##################################################################################################
# Shell Methods
##################################################################################################

    def do_shell(self, line):
        """ Run a raw shell command. Can also prepend '!'. """
        print("Shell command:", line)

        output = os.popen(line).read()

        print_info("Shell output: ", output)

        self.last_output = output

    def emptyline(self):
        self.do_status()

    def do_exit(self, *arg):
        """ Exits PEAS Shell """
        print("Shutting down")
        if self._timer or self._keep_looping:
            self.do_stop()

        print(
            "Please be patient and allow for process to finish. Thanks! Bye!")
        return True

##################################################################################################
# Private Methods
##################################################################################################

    def _capture_data(self, sensor_name):
        # We are missing a Mutex here for accessing these from active_sensors and
        # self.
        if sensor_name in self.active_sensors:
            sensor = getattr(self, sensor_name)
            try:
                sensor.capture(store_result=True)
            except Exception as e:
                print_warning(f'Problem storing captured data: {e!r}')

            self._setup_timer(sensor_name,
                              delay=self.active_sensors[sensor_name]['delay'])

    def _loop(self, *arg):
        for sensor_name in self.active_sensors.keys():
            self._capture_data(sensor_name)

    def _setup_timer(self, sensor_name, delay=None):
        if self._keep_looping and len(self.active_sensors) > 0:

            if not delay:
                delay = self._loop_delay

            # WARNING: It appears we have a single _timer attribute, but we create
            # one Timer for each active sensor (i.e. environment and weather).
            self._timer = Timer(delay,
                                self._capture_data,
                                args=(sensor_name, ))

            self._timer.start()
示例#12
0
def test_bad_db():
    with pytest.raises(Exception):
        PanDB('foobar')
示例#13
0
def memory_db():
    PanDB.permanently_erase_database('memory',
                                     'panoptes_testing',
                                     really='Yes',
                                     dangerous='Totally')
    return PanDB(db_type='memory', db_name='panoptes_testing')
示例#14
0
def db(db_type):
    # TODO don't hardcode the db name.
    return PanDB(db_type=db_type, db_name='panoptes_testing', storage_dir='testing', connect=True)
示例#15
0
def db(db_type):
    return PanDB(
        db_type=db_type, db_name='panoptes_testing', logger=get_root_logger(), connect=True)
示例#16
0
class RemoteMonitor(object):
    """Does a pull request on an endpoint to obtain a JSON document."""
    def __init__(self, endpoint_url=None, sensor_name=None, *args, **kwargs):
        self.logger = get_logger()
        self.logger.info(f'Setting up remote sensor {sensor_name}')

        # Setup the DB either from kwargs or config.
        self.db = None
        db_type = get_config('db.type', default='file')

        if 'db_type' in kwargs:
            self.logger.info(f"Setting up {kwargs['db_type']} type database")
            db_type = kwargs.get('db_type', db_type)

        self.db = PanDB(db_type=db_type)

        self.sensor_name = sensor_name
        self.sensor = None

        if endpoint_url is None:
            # Get the config for the sensor
            endpoint_url = get_config(f'environment.{sensor_name}.url')
            if endpoint_url is None:
                raise error.PanError(f'No endpoint_url for {sensor_name}')

        if not endpoint_url.startswith('http'):
            endpoint_url = f'http://{endpoint_url}'

        self.endpoint_url = endpoint_url

    def disconnect(self):
        self.logger.debug('Stop listening on {self.endpoint_url}')

    def capture(self, store_result=True):
        """Read JSON from endpoint url and capture data.

        Note:
            Currently this doesn't do any processing or have a callback.

        Returns:
            sensor_data (dict):     Dictionary of sensors keyed by sensor name.
        """

        self.logger.debug(
            f'Capturing data from remote url: {self.endpoint_url}')
        sensor_data = requests.get(self.endpoint_url).json()
        if isinstance(sensor_data, list):
            sensor_data = sensor_data[0]

        self.logger.debug(f'Captured on {self.sensor_name}: {sensor_data!r}')

        sensor_data['date'] = current_time(flatten=True)

        if store_result and len(sensor_data) > 0:
            self.db.insert_current(self.sensor_name, sensor_data)

            # Make a separate power entry
            if 'power' in sensor_data:
                self.db.insert_current('power', sensor_data['power'])

        self.logger.debug(f'Remote data: {sensor_data}')
        return sensor_data