示例#1
0
    def __init__(self, serial_device, baudrate=9600):
        super(AtlasScientificUART,
              self).__init__(interface='UART',
                             name=serial_device.replace("/", "_"))

        self.logger = logging.getLogger("{}{}".format(
            __name__, serial_device.replace("/", "_")))

        self.setup = False
        self.serial_device = serial_device
        self.lockfile = LockFile()

        try:
            self.atlas_device = serial.Serial(port=serial_device,
                                              baudrate=baudrate,
                                              timeout=5,
                                              writeTimeout=5)

            cmd_return = self.send_cmd(
                'C,0')  # Disable continuous measurements

            if cmd_return:
                (board, revision, firmware_version) = self.get_board_version()

                self.logger.info(
                    "Atlas Scientific Board: {brd}, Rev: {rev}, Firmware: {fw}"
                    .format(brd=board, rev=revision, fw=firmware_version))
                self.setup = True

        except serial.SerialException as err:
            self.logger.exception(
                "{cls} raised an exception when initializing: "
                "{err}".format(cls=type(self).__name__, err=err))
            self.logger.exception('Opening serial')
    def output_switch(self, state, output_type=None, amount=None, output_channel=None):
        if not self.is_setup():
            msg = "Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info."
            self.logger.error(msg)
            return msg

        try:
            if state == 'on':
                lf = LockFile()
                if lf.lock_acquire(self.lock_file[output_channel], timeout=self.lock_timeout):
                    try:
                        now = time.time()
                        self.change_state[output_channel] = True
                        while self.change_state[output_channel] is not None and time.time() < now + 10:
                            time.sleep(0.1)
                    finally:
                        lf.lock_release(self.lock_file[output_channel])
            elif state == 'off':
                lf = LockFile()
                if lf.lock_acquire(self.lock_file[output_channel], timeout=self.lock_timeout):
                    try:
                        now = time.time()
                        self.change_state[output_channel] = False
                        while self.change_state[output_channel] is not None and time.time() < now + 10:
                            time.sleep(0.1)
                    finally:
                        lf.lock_release(self.lock_file[output_channel])
        except Exception as err:
            self.logger.exception(f"State change error: {err}")
示例#3
0
    def get_measurement(self):
        """Gets the battery. humidity, and temperature."""
        if not self.sensor:
            self.logger.error(
                "Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info."
            )
            return

        self.return_dict = copy.deepcopy(measurements_dict)

        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=3600):
            try:
                self.sensor.run()

                global measurement_values
                self.logger.debug(
                    "Measurements: {}".format(measurement_values))

                if measurement_values:
                    if self.is_enabled(0):
                        self.value_set(0, measurement_values["hum"])
                    if self.is_enabled(1):
                        self.value_set(1, measurement_values["temp"])
                    if self.is_enabled(2):
                        self.value_set(2, measurement_values["bat_percent"])
                    if self.is_enabled(3):
                        self.value_set(3, measurement_values["bat_volt"])
                return self.return_dict
            except:
                self.logger.exception("Acquiring measurements")
            finally:
                lf.lock_release(self.lock_file)
示例#4
0
    def turn_on_off(self, switch_channel, state):
        msg = ""
        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=10):
            try:
                for channel in channels_dict:
                    if switch_channel == channel:
                        if state == 'on':
                            self.pins[channel].switch_to_output(value=bool(
                                self.options_channels['on_state'][channel]))
                            self.output_states[switch_channel] = True
                            self.logger.debug("Output turned on")
                        elif state == 'off':
                            self.pins[channel].switch_to_output(value=bool(
                                not self.options_channels['on_state'][channel])
                                                                )
                            self.output_states[switch_channel] = False
                            self.logger.debug("Output turned off")
                msg = "success"
            except Exception as err:
                msg = f"CH{switch_channel} state change error: {err}"
                self.logger.error(msg)
            finally:
                lf.lock_release(self.lock_file)

        return msg
示例#5
0
    def query(self, query_str):
        """Send command to board and read response"""
        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=self.lock_timeout):
            try:
                # write a command to the board, wait the correct timeout,
                # and read the response
                self.write(query_str)

                # the read and calibration commands require a longer timeout
                if ((query_str.upper().startswith("R")) or
                        (query_str.upper().startswith("CAL"))):
                    time.sleep(self.long_timeout)
                elif query_str.upper().startswith("SLEEP"):
                    return "success", "sleep mode"
                else:
                    time.sleep(self.short_timeout)

                return self.read()
            except Exception as err:
                self.logger.debug(
                    "{cls} raised an exception when taking a reading: "
                    "{err}".format(cls=type(self).__name__, err=err))
                return "error", err
            finally:
                lf.lock_release(self.lock_file)
示例#6
0
    def __init__(self, unique_id=None, testing=False, name=__name__):

        logger_name = "{}".format(name)
        if not testing and unique_id:
            logger_name += "_{}".format(unique_id.split('-')[0])
        self.logger = logging.getLogger(logger_name)

        self.lockfile = LockFile()
    def __init__(self, unique_id=None, testing=False, name=__name__):
        logger_name = "{}".format(name)
        if not testing and unique_id:
            logger_name += "_{}".format(unique_id.split('-')[0])
        self.logger = logging.getLogger(logger_name)

        self.lockfile = LockFile()
        self.channels_conversion = {}
        self.channels_measurement = {}
        self.device_measurements = None
示例#8
0
    def __init__(self, ready, unique_id):
        threading.Thread.__init__(self)
        super().__init__(ready, unique_id=unique_id, name=__name__)

        self.unique_id = unique_id
        self.sample_rate = None
        self.lf = LockFile()

        self.control = DaemonControl()

        self.stop_iteration_counter = 0
        self.measurement = None
        self.measurement_success = False
        self.pause_loop = False
        self.verify_pause_loop = True
        self.dict_inputs = None
        self.device_measurements = None
        self.conversions = None
        self.input_dev = None
        self.input_name = None
        self.log_level_debug = None
        self.gpio_location = None
        self.device = None
        self.interface = None
        self.period = None
        self.start_offset = None

        # Pre-Output: Activates prior to input measurement
        self.pre_output_id = None
        self.pre_output_channel_id = None
        self.pre_output_channel = None
        self.pre_output_duration = None
        self.pre_output_during_measure = None
        self.pre_output_lock_file = None
        self.pre_output_setup = None
        self.last_measurement = None
        self.next_measurement = None
        self.get_new_measurement = None
        self.trigger_cond = None
        self.measurement_acquired = None
        self.pre_output_activated = None
        self.pre_output_timer = None

        # SMTP options
        self.smtp_max_count = None
        self.email_count = None
        self.allowed_to_send_notice = None

        self.i2c_address = None
        self.switch_edge_gpio = None
        self.measure_input = None
        self.device_recognized = None

        self.input_timer = time.time()
        self.lastUpdate = None
示例#9
0
    def get_measurement(self):
        """Gets the measurement in units by reading the."""
        self.return_dict = copy.deepcopy(measurements_dict)

        if self.is_enabled(0):
            self.value_set(0, self.sensor.read_temperature())

        if self.is_enabled(1):
            self.value_set(1, self.sensor.read_humidity())

        if self.is_enabled(2):
            self.value_set(2, self.sensor.read_pressure())

        if (self.is_enabled(3) and self.is_enabled(0) and self.is_enabled(1)):
            dewpoint = calculate_dewpoint(self.value_get(0), self.value_get(1))
            self.value_set(3, dewpoint)

        if self.is_enabled(4) and self.is_enabled(2):
            altitude = calculate_altitude(self.value_get(2))
            self.value_set(4, altitude)

        if (self.is_enabled(5) and self.is_enabled(0) and self.is_enabled(1)):
            vpd = calculate_vapor_pressure_deficit(self.value_get(0),
                                                   self.value_get(1))
            self.value_set(5, vpd)

        try:
            now = time.time()
            if now > self.timer:
                self.timer = now + 80
                # "B" designates this data belonging to the BME280
                string_send = 'B,{},{},{}'.format(self.value_get(1),
                                                  self.value_get(2),
                                                  self.value_get(0))

                lf = LockFile()
                if lf.lock_acquire(self.lock_file, timeout=10):
                    try:
                        self.serial_send = self.serial.Serial(
                            port=self.serial_device,
                            baudrate=9600,
                            timeout=5,
                            writeTimeout=5)
                        self.serial_send.write(string_send.encode())
                        time.sleep(4)
                    finally:
                        lf.lock_release(self.lock_file)
                self.ttn_serial_error = False
        except Exception as e:
            if not self.ttn_serial_error:
                # Only send this error once if it continually occurs
                self.logger.error("TTN: Could not send serial: {}".format(e))
                self.ttn_serial_error = True

        return self.return_dict
示例#10
0
    def get_measurement(self):
        """Gets the K30's CO2 concentration in ppmv via UART."""
        if not self.ser:  # Don't measure if device isn't validated
            return None

        self.return_dict = copy.deepcopy(measurements_dict)

        co2 = None

        self.ser.flushInput()
        time.sleep(1)
        self.ser.write(bytearray([0xfe, 0x44, 0x00, 0x08, 0x02, 0x9f, 0x25]))
        time.sleep(.01)
        resp = self.ser.read(7)
        if len(resp) != 0:
            high = resp[3]
            low = resp[4]
            co2 = (high * 256) + low

        self.value_set(0, co2)

        try:
            now = time.time()
            if now > self.timer:
                self.timer = now + min_seconds_between_transmissions
                # "K" designates this data belonging to the K30
                string_send = 'K,{}'.format(self.value_get(0))

                lf = LockFile()
                if lf.lock_acquire(self.lock_file, timeout=10):
                    try:
                        self.serial_send = self.serial.Serial(
                            port=self.serial_device,
                            baudrate=9600,
                            timeout=5,
                            writeTimeout=5)
                        self.serial_send.write(string_send.encode())
                        time.sleep(4)
                    finally:
                        lf.lock_release(self.lock_file)
                self.ttn_serial_error = False
        except Exception as e:
            if not self.ttn_serial_error:
                # Only send this error once if it continually occurs
                self.logger.error("TTN: Could not send serial: {}".format(e))
                self.ttn_serial_error = True

        return self.return_dict
示例#11
0
    async def try_connect(self):
        from kasa import SmartStrip

        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=self.lock_timeout):
            try:
                self.strip = SmartStrip(self.plug_address)
                await self.strip.update()
                self.logger.debug(
                    f'Strip {self.strip.alias}: {self.strip.hw_info}')
                self.output_setup = True
            except Exception as err:
                self.logger.error(f"Output was unable to be setup: {err}")
            finally:
                time.sleep(self.switch_wait)
                lf.lock_release(self.lock_file)
示例#12
0
 def query(self, query_str):
     """Send command and return reply."""
     lf = LockFile()
     if lf.lock_acquire(self.lock_file, timeout=self.lock_timeout):
         try:
             self.send_cmd(query_str)
             time.sleep(1.3)
             response = self.read_lines()
             return 'success', response
         except Exception as err:
             self.logger.exception(
                 "{cls} raised an exception when taking a reading: "
                 "{err}".format(cls=type(self).__name__, err=err))
             return 'error', err
         finally:
             lf.lock_release(self.lock_file)
示例#13
0
 def change_outlet_state_try(self, channel, state):
     lf = LockFile()
     if lf.lock_acquire(self.lock_file, timeout=self.lock_timeout):
         try:
             loops = 4
             msg = None
             for i in range(loops):
                 try:
                     asyncio.run(self.change_outlet_state(channel, state))
                     msg = "success"
                     break
                 except Exception as err:
                     msg = "State change error: {}".format(err)
                     if i + 1 < loops:
                         time.sleep(1.5)
             return msg
         finally:
             time.sleep(self.switch_wait)
             lf.lock_release(self.lock_file)
示例#14
0
    def get_measurement(self):
        """Gets the light, moisture, and temperature"""
        if not self.sensor:
            self.logger.error(
                "Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info."
            )
            return

        self.return_dict = copy.deepcopy(measurements_dict)

        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=3600):
            try:
                from miflora.miflora_poller import MI_CONDUCTIVITY
                from miflora.miflora_poller import MI_MOISTURE
                from miflora.miflora_poller import MI_LIGHT
                from miflora.miflora_poller import MI_TEMPERATURE
                from miflora.miflora_poller import MI_BATTERY

                if self.is_enabled(0):
                    self.value_set(0, self.sensor.parameter_value(MI_BATTERY))

                if self.is_enabled(1):
                    self.value_set(
                        1, self.sensor.parameter_value(MI_CONDUCTIVITY))

                if self.is_enabled(2):
                    self.value_set(2, self.sensor.parameter_value(MI_LIGHT))

                if self.is_enabled(3):
                    self.value_set(3, self.sensor.parameter_value(MI_MOISTURE))

                if self.is_enabled(4):
                    self.value_set(4,
                                   self.sensor.parameter_value(MI_TEMPERATURE))

                return self.return_dict
            except:
                self.logger.exception("acquiring measurements")
            finally:
                lf.lock_release(self.lock_file)
示例#15
0
    async def get_status(self):
        from kasa import SmartStrip

        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=self.lock_timeout):
            try:
                self.strip = SmartStrip(self.plug_address)
                await self.strip.update()
                for channel in channels_dict:
                    if self.strip.children[channel].is_on:
                        self.output_states[channel] = True
                    else:
                        self.output_states[channel] = False
            except Exception as err:
                self.logger.error(
                    f"get_status() raised an exception when taking a reading: {err}"
                )
                return 'error', err
            finally:
                time.sleep(self.switch_wait)
                lf.lock_release(self.lock_file)
示例#16
0
class AbstractBaseController(object):
    """
    Base Controller class that ensures certain methods and values are present
    in controllers.
    """
    def __init__(self, unique_id=None, testing=False, name=__name__):

        logger_name = "{}".format(name)
        if not testing and unique_id:
            logger_name += "_{}".format(unique_id.split('-')[0])
        self.logger = logging.getLogger(logger_name)

        self.lockfile = LockFile()

    def setup_custom_options(self, custom_options, custom_controller):
        if not hasattr(custom_controller, 'custom_options'):
            self.logger.error(
                "custom_controller missing attribute custom_options")
            return
        elif custom_controller.custom_options.startswith("{"):
            return self.setup_custom_options_json(custom_options,
                                                  custom_controller)
        else:
            return self.setup_custom_options_csv(custom_options,
                                                 custom_controller)

    # TODO: Remove in place of JSON function, below, in next major version
    def setup_custom_options_csv(self, custom_options, custom_controller):
        for each_option_default in custom_options:
            try:
                required = False
                custom_option_set = False
                error = []
                if 'type' not in each_option_default:
                    error.append("'type' not found in custom_options")
                if 'id' not in each_option_default:
                    error.append("'id' not found in custom_options")
                if 'default_value' not in each_option_default:
                    error.append("'default_value' not found in custom_options")
                for each_error in error:
                    self.logger.error(each_error)
                if error:
                    return

                if 'required' in each_option_default and each_option_default[
                        'required']:
                    required = True

                option_value = each_option_default['default_value']
                device_id = None
                measurement_id = None

                if not hasattr(custom_controller, 'custom_options'):
                    self.logger.error(
                        "custom_controller missing attribute custom_options")
                    return

                if custom_controller.custom_options:
                    for each_option in custom_controller.custom_options.split(
                            ';'):
                        option = each_option.split(',')[0]

                        if option == each_option_default['id']:
                            custom_option_set = True

                            if (each_option_default['type']
                                    == 'select_measurement'
                                    and len(each_option.split(',')) > 2):
                                device_id = each_option.split(',')[1]
                                measurement_id = each_option.split(',')[2]
                            else:
                                option_value = each_option.split(',')[1]

                if required and not custom_option_set:
                    self.logger.error(
                        "Custom option '{}' required but was not found to be set by the user"
                        .format(each_option_default['id']))

                elif each_option_default['type'] == 'integer':
                    setattr(self, each_option_default['id'], int(option_value))

                elif each_option_default['type'] == 'float':
                    setattr(self, each_option_default['id'],
                            float(option_value))

                elif each_option_default['type'] == 'bool':
                    setattr(self, each_option_default['id'],
                            bool(option_value))

                elif each_option_default['type'] in ['multiline_text', 'text']:
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select':
                    option_value = str(option_value)
                    if 'cast_value' in each_option_default and each_option_default[
                            'cast_value']:
                        if each_option_default['cast_value'] == 'integer':
                            option_value = int(option_value)
                        elif each_option_default['cast_value'] == 'float':
                            option_value = float(option_value)
                    setattr(self, each_option_default['id'], option_value)

                elif each_option_default['type'] == 'select_measurement':
                    setattr(self,
                            '{}_device_id'.format(each_option_default['id']),
                            device_id)
                    setattr(
                        self,
                        '{}_measurement_id'.format(each_option_default['id']),
                        measurement_id)

                elif each_option_default['type'] == 'select_device':
                    setattr(self, '{}_id'.format(each_option_default['id']),
                            str(option_value))

                else:
                    self.logger.error("Unknown custom_option type '{}'".format(
                        each_option_default['type']))
            except Exception:
                self.logger.exception("Error parsing custom_options")

    def setup_custom_options_json(self, custom_options, custom_controller):
        for each_option_default in custom_options:
            try:
                required = False
                custom_option_set = False
                error = []
                if 'type' not in each_option_default:
                    error.append("'type' not found in custom_options")
                if 'id' not in each_option_default:
                    error.append("'id' not found in custom_options")
                if 'default_value' not in each_option_default:
                    error.append("'default_value' not found in custom_options")
                for each_error in error:
                    self.logger.error(each_error)
                if error:
                    return

                if 'required' in each_option_default and each_option_default[
                        'required']:
                    required = True

                # set default value
                option_value = each_option_default['default_value']
                device_id = None
                measurement_id = None

                if not hasattr(custom_controller, 'custom_options'):
                    self.logger.error(
                        "custom_controller missing attribute custom_options")
                    return

                if getattr(custom_controller, 'custom_options'):
                    for each_option, each_value in json.loads(
                            getattr(custom_controller,
                                    'custom_options')).items():
                        if each_option == each_option_default['id']:
                            custom_option_set = True

                            if (each_option_default['type']
                                    == 'select_measurement'
                                    and len(each_value.split(',')) > 1):
                                device_id = each_value.split(',')[0]
                                measurement_id = each_value.split(',')[1]
                            else:
                                option_value = each_value

                if required and not custom_option_set:
                    self.logger.error(
                        "Option '{}' required but was not found to be set by the user"
                        .format(each_option_default['id']))

                elif each_option_default['type'] in [
                        'integer', 'float', 'bool', 'multiline_text', 'text',
                        'select'
                ]:
                    setattr(self, each_option_default['id'], option_value)

                elif each_option_default['type'] == 'select_measurement':
                    setattr(self,
                            '{}_device_id'.format(each_option_default['id']),
                            device_id)
                    setattr(
                        self,
                        '{}_measurement_id'.format(each_option_default['id']),
                        measurement_id)

                elif each_option_default['type'] == 'select_device':
                    setattr(self, '{}_id'.format(each_option_default['id']),
                            str(option_value))
                else:
                    self.logger.error("Unknown option type '{}'".format(
                        each_option_default['type']))
            except Exception:
                self.logger.exception("Error parsing options")

    def setup_custom_channel_options_json(self, custom_options,
                                          custom_controller_channels):
        dict_values = {}
        for each_option_default in custom_options:
            try:
                dict_values[each_option_default['id']] = OrderedDict()
                required = False
                custom_option_set = False
                error = []
                if 'type' not in each_option_default:
                    error.append("'type' not found in custom_options")
                if 'id' not in each_option_default:
                    error.append("'id' not found in custom_options")
                if 'default_value' not in each_option_default:
                    error.append("'default_value' not found in custom_options")
                for each_error in error:
                    self.logger.error(each_error)
                if error:
                    return

                if 'required' in each_option_default and each_option_default[
                        'required']:
                    required = True

                for each_chan in custom_controller_channels:
                    # set default value
                    dict_values[each_option_default['id']][
                        each_chan.
                        channel] = each_option_default['default_value']

                    if each_option_default['type'] == 'select_measurement':
                        dict_values[each_option_default['id']][
                            each_chan.channel] = {}
                        dict_values[each_option_default['id']][
                            each_chan.channel]['device_id'] = None
                        dict_values[each_option_default['id']][
                            each_chan.channel]['measurement_id'] = None
                        dict_values[each_option_default['id']][
                            each_chan.channel]['channel_id'] = None

                    if not hasattr(each_chan, 'custom_options'):
                        self.logger.error(
                            "custom_controller missing attribute custom_options"
                        )
                        return

                    if getattr(each_chan, 'custom_options'):
                        for each_option, each_value in json.loads(
                                getattr(each_chan, 'custom_options')).items():
                            if each_option == each_option_default['id']:
                                custom_option_set = True

                                if each_option_default[
                                        'type'] == 'select_measurement':
                                    if len(each_value.split(',')) > 1:
                                        dict_values[each_option_default['id']][
                                            each_chan.channel][
                                                'device_id'] = each_value.split(
                                                    ',')[0]
                                        dict_values[each_option_default['id']][
                                            each_chan.channel][
                                                'measurement_id'] = each_value.split(
                                                    ',')[1]
                                    if len(each_value.split(',')) > 2:
                                        dict_values[each_option_default['id']][
                                            each_chan.channel][
                                                'channel_id'] = each_value.split(
                                                    ',')[2]
                                else:
                                    dict_values[each_option_default['id']][
                                        each_chan.channel] = each_value

                    if required and not custom_option_set:
                        self.logger.error(
                            "Option '{}' required but was not found to be set by the user"
                            .format(each_option_default['id']))
            except Exception:
                self.logger.exception("Error parsing options")

        return dict_values

    @staticmethod
    def get_last_measurement(device_id, measurement_id, max_age=None):
        device_measurement = db_retrieve_table_daemon(
            DeviceMeasurements).filter(
                DeviceMeasurements.unique_id == measurement_id).first()
        if device_measurement:
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
        else:
            conversion = None
        channel, unit, measurement = return_measurement_info(
            device_measurement, conversion)

        last_measurement = read_last_influxdb(device_id,
                                              unit,
                                              channel,
                                              measure=measurement,
                                              duration_sec=max_age)

        return last_measurement

    def lock_acquire(self, lockfile, timeout):
        self.lockfile.lock_acquire(lockfile, timeout)

    def lock_locked(self, lockfile):
        return self.lockfile.lock_locked(lockfile)

    def lock_release(self, lockfile):
        self.lockfile.lock_release(lockfile)
示例#17
0
class AbstractBaseController(object):
    """
    Base Controller class that ensures certain methods and values are present
    in controllers.
    """
    def __init__(self, unique_id=None, testing=False, name=__name__):
        logger_name = "{}".format(name)
        if not testing and unique_id:
            logger_name += "_{}".format(unique_id.split('-')[0])
        self.logger = logging.getLogger(logger_name)

        self.lockfile = LockFile()
        self.channels_conversion = {}
        self.channels_measurement = {}
        self.device_measurements = None

    def setup_custom_options(self, custom_options, custom_controller):
        if not hasattr(custom_controller, 'custom_options'):
            self.logger.error(
                "custom_controller missing attribute custom_options")
            return
        elif custom_controller.custom_options.startswith("{"):
            return self.setup_custom_options_json(custom_options,
                                                  custom_controller)
        else:
            return self.setup_custom_options_csv(custom_options,
                                                 custom_controller)

    def setup_device_measurement(self, unique_id):
        for _ in range(5):  # Make 5 attempts to access database
            try:
                self.device_measurements = db_retrieve_table_daemon(
                    DeviceMeasurements).filter(
                        DeviceMeasurements.device_id == unique_id)

                for each_measure in self.device_measurements.all():
                    self.channels_measurement[
                        each_measure.channel] = each_measure
                    self.channels_conversion[
                        each_measure.channel] = db_retrieve_table_daemon(
                            Conversion, unique_id=each_measure.conversion_id)
                return
            except Exception as msg:
                self.logger.debug("Error: {}".format(msg))
            time.sleep(0.1)

    # TODO: Remove in place of JSON function, below, in next major version
    def setup_custom_options_csv(self, custom_options, custom_controller):
        for each_option_default in custom_options:
            try:
                required = False
                custom_option_set = False
                error = []

                if 'type' not in each_option_default:
                    error.append("'type' not found in custom_options")
                if ('id' not in each_option_default
                        and ('type' in each_option_default
                             and each_option_default['type']
                             not in ['new_line', 'message'])):
                    error.append("'id' not found in custom_options")
                if ('default_value' not in each_option_default
                        and ('type' in each_option_default
                             and each_option_default['type'] != 'new_line')):
                    error.append("'default_value' not found in custom_options")

                for each_error in error:
                    self.logger.error(each_error)
                if error:
                    return

                if each_option_default['type'] in ['new_line', 'message']:
                    continue

                if 'required' in each_option_default and each_option_default[
                        'required']:
                    required = True

                option_value = each_option_default['default_value']
                device_id = None
                measurement_id = None
                channel_id = None

                if not hasattr(custom_controller, 'custom_options'):
                    self.logger.error(
                        "custom_controller missing attribute custom_options")
                    return

                if custom_controller.custom_options:
                    for each_option in custom_controller.custom_options.split(
                            ';'):
                        option = each_option.split(',')[0]

                        if option == each_option_default['id']:
                            custom_option_set = True

                            if (each_option_default['type']
                                    == 'select_measurement'
                                    and len(each_option.split(',')) > 2):
                                device_id = each_option.split(',')[1]
                                measurement_id = each_option.split(',')[2]
                            if (each_option_default['type']
                                    == 'select_measurement_channel'
                                    and len(each_option.split(',')) > 3):
                                device_id = each_option.split(',')[1]
                                measurement_id = each_option.split(',')[2]
                                channel_id = each_option.split(',')[3]
                            else:
                                option_value = each_option.split(',')[1]

                if required and not custom_option_set:
                    self.logger.error(
                        "Option '{}' required but was not found to be set by the user. Setting to default."
                        .format(each_option_default['id']))

                if each_option_default['type'] == 'integer':
                    setattr(self, each_option_default['id'], int(option_value))

                elif each_option_default['type'] == 'float':
                    setattr(self, each_option_default['id'],
                            float(option_value))

                elif each_option_default['type'] == 'bool':
                    setattr(self, each_option_default['id'],
                            bool(option_value))

                elif each_option_default['type'] in ['multiline_text', 'text']:
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select_multi_measurement':
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select':
                    option_value = str(option_value)
                    if 'cast_value' in each_option_default and each_option_default[
                            'cast_value']:
                        if each_option_default['cast_value'] == 'integer':
                            option_value = int(option_value)
                        elif each_option_default['cast_value'] == 'float':
                            option_value = float(option_value)
                    setattr(self, each_option_default['id'], option_value)

                elif each_option_default['type'] == 'select_measurement':
                    setattr(self,
                            '{}_device_id'.format(each_option_default['id']),
                            device_id)
                    setattr(
                        self,
                        '{}_measurement_id'.format(each_option_default['id']),
                        measurement_id)

                elif each_option_default[
                        'type'] == 'select_measurement_channel':
                    setattr(self,
                            '{}_device_id'.format(each_option_default['id']),
                            device_id)
                    setattr(
                        self,
                        '{}_measurement_id'.format(each_option_default['id']),
                        measurement_id)
                    setattr(self,
                            '{}_channel_id'.format(each_option_default['id']),
                            channel_id)

                elif each_option_default['type'] in [
                        'select_type_measurement', 'select_type_unit'
                ]:
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select_device':
                    setattr(self, '{}_id'.format(each_option_default['id']),
                            str(option_value))

                elif each_option_default['type'] in ['message', 'new_line']:
                    pass

                else:
                    self.logger.error(
                        "setup_custom_options_csv() Unknown custom_option type '{}'"
                        .format(each_option_default['type']))
            except Exception:
                self.logger.exception("Error parsing custom_options")

    def setup_custom_options_json(self, custom_options, custom_controller):
        for each_option_default in custom_options:
            try:
                required = False
                custom_option_set = False
                error = []

                if 'type' not in each_option_default:
                    error.append("'type' not found in custom_options")
                if ('id' not in each_option_default
                        and ('type' in each_option_default
                             and each_option_default['type']
                             not in ['new_line', 'message'])):
                    error.append("'id' not found in custom_options")
                if ('default_value' not in each_option_default
                        and ('type' in each_option_default
                             and each_option_default['type'] != 'new_line')):
                    error.append("'default_value' not found in custom_options")

                for each_error in error:
                    self.logger.error(each_error)
                if error:
                    return

                if each_option_default['type'] in ['new_line', 'message']:
                    continue

                if 'required' in each_option_default and each_option_default[
                        'required']:
                    required = True

                # set default value
                option_value = each_option_default['default_value']
                device_id = None
                measurement_id = None
                channel_id = None

                if not hasattr(custom_controller, 'custom_options'):
                    self.logger.error(
                        "custom_controller missing attribute custom_options")
                    return

                if getattr(custom_controller, 'custom_options'):
                    for each_option, each_value in json.loads(
                            getattr(custom_controller,
                                    'custom_options')).items():
                        if each_option == each_option_default['id']:
                            custom_option_set = True

                            if (each_option_default['type']
                                    == 'select_measurement'
                                    and len(each_value.split(',')) > 1):
                                device_id = each_value.split(',')[0]
                                measurement_id = each_value.split(',')[1]
                            if (each_option_default['type']
                                    == 'select_measurement_channel'
                                    and len(each_value.split(',')) > 2):
                                device_id = each_value.split(',')[0]
                                measurement_id = each_value.split(',')[1]
                                channel_id = each_value.split(',')[2]
                            else:
                                option_value = each_value

                if required and not custom_option_set:
                    self.logger.error(
                        "Option '{}' required but was not found to be set by the user. Setting to default."
                        .format(each_option_default['id']))

                if each_option_default['type'] in [
                        'integer', 'float', 'bool', 'multiline_text',
                        'select_multi_measurement', 'text', 'select'
                ]:
                    setattr(self, each_option_default['id'], option_value)

                elif each_option_default['type'] == 'select_measurement':
                    setattr(self,
                            '{}_device_id'.format(each_option_default['id']),
                            device_id)
                    setattr(
                        self,
                        '{}_measurement_id'.format(each_option_default['id']),
                        measurement_id)

                elif each_option_default[
                        'type'] == 'select_measurement_channel':
                    setattr(self,
                            '{}_device_id'.format(each_option_default['id']),
                            device_id)
                    setattr(
                        self,
                        '{}_measurement_id'.format(each_option_default['id']),
                        measurement_id)
                    setattr(self,
                            '{}_channel_id'.format(each_option_default['id']),
                            channel_id)

                elif each_option_default['type'] == 'select_type_measurement':
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select_type_unit':
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select_device':
                    setattr(self, '{}_id'.format(each_option_default['id']),
                            str(option_value))

                elif each_option_default['type'] in ['message', 'new_line']:
                    pass

                else:
                    self.logger.error(
                        "setup_custom_options_json() Unknown option type '{}'".
                        format(each_option_default['type']))
            except Exception:
                self.logger.exception("Error parsing options")

    def setup_custom_channel_options_json(self, custom_options,
                                          custom_controller_channels):
        dict_values = {}
        for each_option_default in custom_options:
            try:
                dict_values[each_option_default['id']] = OrderedDict()
                required = False
                custom_option_set = False
                error = []
                if 'type' not in each_option_default:
                    error.append("'type' not found in custom_options")
                if 'id' not in each_option_default:
                    error.append("'id' not found in custom_options")
                if 'default_value' not in each_option_default:
                    error.append("'default_value' not found in custom_options")
                for each_error in error:
                    self.logger.error(each_error)
                if error:
                    return

                if 'required' in each_option_default and each_option_default[
                        'required']:
                    required = True

                for each_channel in custom_controller_channels:
                    channel = each_channel.channel

                    # set default value
                    dict_values[each_option_default['id']][
                        channel] = each_option_default['default_value']

                    if each_option_default['type'] == 'select_measurement':
                        dict_values[each_option_default['id']][channel] = {}
                        dict_values[each_option_default['id']][channel][
                            'device_id'] = None
                        dict_values[each_option_default['id']][channel][
                            'measurement_id'] = None
                        dict_values[each_option_default['id']][channel][
                            'channel_id'] = None

                    if not hasattr(each_channel, 'custom_options'):
                        self.logger.error(
                            "custom_controller missing attribute custom_options"
                        )
                        return

                    if getattr(each_channel, 'custom_options'):
                        for each_option, each_value in json.loads(
                                getattr(each_channel,
                                        'custom_options')).items():
                            if each_option == each_option_default['id']:
                                custom_option_set = True

                                if each_option_default[
                                        'type'] == 'select_measurement':
                                    if len(each_value.split(',')) > 1:
                                        dict_values[each_option_default['id']][
                                            channel][
                                                'device_id'] = each_value.split(
                                                    ',')[0]
                                        dict_values[each_option_default['id']][
                                            channel][
                                                'measurement_id'] = each_value.split(
                                                    ',')[1]
                                    if len(each_value.split(',')) > 2:
                                        dict_values[each_option_default['id']][
                                            channel][
                                                'channel_id'] = each_value.split(
                                                    ',')[2]
                                else:
                                    dict_values[each_option_default['id']][
                                        channel] = each_value

                    if required and not custom_option_set:
                        self.logger.error(
                            "Option '{}' required but was not found to be set by the user"
                            .format(each_option_default['id']))
            except Exception:
                self.logger.exception("Error parsing options")

        return dict_values

    def lock_acquire(self, lockfile, timeout):
        self.logger.debug("Acquiring lock")
        return self.lockfile.lock_acquire(lockfile, timeout)

    def lock_locked(self, lockfile):
        return self.lockfile.lock_locked(lockfile)

    def lock_release(self, lockfile):
        self.logger.debug("Releasing lock")
        self.lockfile.lock_release(lockfile)

    def _delete_custom_option(self, controller, unique_id, option):
        try:
            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_function = new_session.query(controller).filter(
                    controller.unique_id == unique_id).first()
                try:
                    dict_custom_options = json.loads(
                        mod_function.custom_options)
                except:
                    dict_custom_options = {}
                dict_custom_options.pop(option)
                mod_function.custom_options = json.dumps(dict_custom_options)
                new_session.commit()
        except Exception:
            self.logger.exception("delete_custom_option")

    def _set_custom_option(self, controller, unique_id, option, value):
        try:
            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_function = new_session.query(controller).filter(
                    controller.unique_id == unique_id).first()
                try:
                    dict_custom_options = json.loads(
                        mod_function.custom_options)
                except:
                    dict_custom_options = {}
                dict_custom_options[option] = value
                mod_function.custom_options = json.dumps(dict_custom_options)
                new_session.commit()
        except Exception:
            self.logger.exception("set_custom_option")

    @staticmethod
    def _get_custom_option(custom_options, option):
        try:
            dict_custom_options = json.loads(custom_options)
        except:
            dict_custom_options = {}
        if option in dict_custom_options:
            return dict_custom_options[option]

    @staticmethod
    def get_last_measurement(device_id, measurement_id, max_age=None):
        return get_last_measurement(device_id, measurement_id, max_age=max_age)

    @staticmethod
    def get_past_measurements(device_id, measurement_id, max_age=None):
        return get_past_measurements(device_id,
                                     measurement_id,
                                     max_age=max_age)

    @staticmethod
    def get_output_channel_from_channel_id(channel_id):
        """Return channel number from channel ID"""
        output_channel = db_retrieve_table_daemon(OutputChannel).filter(
            OutputChannel.unique_id == channel_id).first()
        if output_channel:
            return output_channel.channel
示例#18
0
class InputController(AbstractController, threading.Thread):
    """
    Class for controlling the input
    """
    def __init__(self, ready, unique_id):
        threading.Thread.__init__(self)
        super().__init__(ready, unique_id=unique_id, name=__name__)

        self.unique_id = unique_id
        self.sample_rate = None
        self.lf = LockFile()

        self.control = DaemonControl()

        self.stop_iteration_counter = 0
        self.measurement = None
        self.measurement_success = False
        self.pause_loop = False
        self.verify_pause_loop = True
        self.dict_inputs = None
        self.device_measurements = None
        self.conversions = None
        self.input_dev = None
        self.input_name = None
        self.log_level_debug = None
        self.gpio_location = None
        self.device = None
        self.interface = None
        self.period = None
        self.start_offset = None

        # Pre-Output: Activates prior to input measurement
        self.pre_output_id = None
        self.pre_output_channel_id = None
        self.pre_output_channel = None
        self.pre_output_duration = None
        self.pre_output_during_measure = None
        self.pre_output_lock_file = None
        self.pre_output_setup = None
        self.last_measurement = None
        self.next_measurement = None
        self.get_new_measurement = None
        self.trigger_cond = None
        self.measurement_acquired = None
        self.pre_output_activated = None
        self.pre_output_timer = None

        # SMTP options
        self.smtp_max_count = None
        self.email_count = None
        self.allowed_to_send_notice = None

        self.i2c_address = None
        self.switch_edge_gpio = None
        self.measure_input = None
        self.device_recognized = None

        self.input_timer = time.time()
        self.lastUpdate = None

    def __str__(self):
        return str(self.__class__)

    def loop(self):
        # Pause loop to modify conditional statements.
        # Prevents execution of conditional while variables are
        # being modified.
        if self.pause_loop:
            self.verify_pause_loop = True
            while self.pause_loop:
                time.sleep(0.1)

        if ('listener' in self.dict_inputs[self.device] and
                self.dict_inputs[self.device]['listener']):
            pass
        else:
            now = time.time()
            # Signal that a measurement needs to be obtained
            if (now > self.next_measurement and
                    not self.get_new_measurement):

                # Prevent double measurement if previous acquisition of a measurement was delayed
                if self.last_measurement < self.next_measurement:
                    self.get_new_measurement = True
                    self.trigger_cond = True

                # Ensure the next measure event will occur in the future
                while self.next_measurement < now:
                    self.next_measurement += self.period

            # if signaled and a pre output is set up correctly, turn the
            # output on or on for the set duration
            if (self.get_new_measurement and
                    self.pre_output_setup and
                    not self.pre_output_activated):

                if self.lf.lock_acquire(self.pre_output_lock_file, timeout=30):
                    self.pre_output_timer = time.time() + self.pre_output_duration
                    self.pre_output_activated = True

                    # Only run the pre-output before measurement
                    # Turn on for a duration, measure after it turns off
                    if not self.pre_output_during_measure:
                        output_on = threading.Thread(
                            target=self.control.output_on,
                            args=(self.pre_output_id,),
                            kwargs={'output_channel': self.pre_output_channel,
                                    'amount': self.pre_output_duration})
                        output_on.start()

                    # Run the pre-output during the measurement
                    # Just turn on, then off after the measurement
                    else:
                        output_on = threading.Thread(
                            target=self.control.output_on,
                            args=(self.pre_output_id,),
                            kwargs={'output_channel': self.pre_output_channel})
                        output_on.start()
                else:
                    self.logger.error(f"Could not acquire pre-output lock at {self.pre_output_lock_file}")

            # If using a pre output, wait for it to complete before
            # querying the input for a measurement
            if self.get_new_measurement:

                if (self.pre_output_setup and
                        self.pre_output_activated and
                        now > self.pre_output_timer):

                    if self.lf.lock_locked(self.pre_output_lock_file):
                        try:
                            if self.pre_output_during_measure:
                                # Measure then turn off pre-output
                                self.update_measure()
                                output_off = threading.Thread(
                                    target=self.control.output_off,
                                    args=(self.pre_output_id,),
                                    kwargs={'output_channel': self.pre_output_channel})
                                output_off.start()
                            else:
                                # Pre-output has turned off, now measure
                                self.update_measure()

                            self.pre_output_activated = False
                            self.get_new_measurement = False
                        finally:
                            # always remove lock
                            self.lf.lock_release(self.pre_output_lock_file)
                    else:
                        self.logger.error(f"Pre-output lock not found at {self.pre_output_lock_file}")

                elif not self.pre_output_setup:
                    # Pre-output not enabled, just measure
                    self.update_measure()
                    self.get_new_measurement = False

                # Acquiring measurements was successful
                if self.measurement_success:
                    measurements_dict = self.create_measurements_dict()

                    # Run any actions
                    message = "Executing actions of Input."
                    actions = db_retrieve_table_daemon(Actions).filter(
                        Actions.function_id == self.unique_id).all()
                    for each_action in actions:
                        message = self.control.trigger_action(
                            each_action.unique_id,
                            value=measurements_dict,
                            message=message,
                            debug=self.log_level_debug)

                    # Add measurement(s) to influxdb
                    use_same_timestamp = True
                    if ('measurements_use_same_timestamp' in self.dict_inputs[self.device] and
                            not self.dict_inputs[self.device]['measurements_use_same_timestamp']):
                        use_same_timestamp = False

                    add_measurements_influxdb(
                        self.unique_id,
                        measurements_dict,
                        use_same_timestamp=use_same_timestamp)
                    self.measurement_success = False

        self.trigger_cond = False

    def run_finally(self):
        try:
            self.measure_input.stop_input()
        except:
            pass

    def initialize_variables(self):
        input_dev = db_retrieve_table_daemon(
            Input, unique_id=self.unique_id)

        self.log_level_debug = input_dev.log_level_debug
        self.set_log_level_debug(self.log_level_debug)

        self.dict_inputs = parse_input_information()

        self.sample_rate = db_retrieve_table_daemon(
            Misc, entry='first').sample_rate_controller_input

        self.device_measurements = db_retrieve_table_daemon(
            DeviceMeasurements).filter(
            DeviceMeasurements.device_id == self.unique_id)

        self.conversions = db_retrieve_table_daemon(Conversion)

        self.input_dev = input_dev
        self.input_name = input_dev.name
        self.unique_id = input_dev.unique_id
        self.gpio_location = input_dev.gpio_location
        self.device = input_dev.device
        self.interface = input_dev.interface
        self.period = input_dev.period
        self.start_offset = input_dev.start_offset

        # Pre-Output (activates output prior to and/or during input measurement)
        self.pre_output_setup = False

        if self.input_dev.pre_output_id and "," in self.input_dev.pre_output_id:
            try:
                self.pre_output_id = input_dev.pre_output_id.split(",")[0]
                self.pre_output_channel_id = input_dev.pre_output_id.split(",")[1]

                self.pre_output_duration = input_dev.pre_output_duration
                self.pre_output_during_measure = input_dev.pre_output_during_measure
                self.pre_output_activated = False
                self.pre_output_timer = time.time()
                self.pre_output_lock_file = f'/var/lock/input_pre_output_{self.pre_output_id}_{self.pre_output_channel_id}'

                # Check if Pre Output and channel IDs exists
                output = db_retrieve_table_daemon(Output, unique_id=self.pre_output_id)
                output_channel = db_retrieve_table_daemon(OutputChannel, unique_id=self.pre_output_channel_id)
                if output and output_channel and self.pre_output_duration:
                    self.pre_output_channel = output_channel.channel
                    self.logger.debug("Pre output successfully set up")
                    self.pre_output_setup = True
            except:
                self.logger.exception("Could not set up pre-output")

        self.last_measurement = 0
        self.next_measurement = time.time() + self.start_offset
        self.get_new_measurement = False
        self.trigger_cond = False
        self.measurement_acquired = False

        smtp = db_retrieve_table_daemon(SMTP, entry='first')
        self.smtp_max_count = smtp.hourly_max
        self.email_count = 0
        self.allowed_to_send_notice = True

        # Convert string I2C address to base-16 int
        if self.interface == 'I2C' and self.input_dev.i2c_location:
            self.i2c_address = int(str(self.input_dev.i2c_location), 16)

        self.device_recognized = True

        if self.device in self.dict_inputs:
            input_loaded, status = load_module_from_file(
                self.dict_inputs[self.device]['file_path'], 'inputs')

            if input_loaded:
                self.measure_input = input_loaded.InputModule(self.input_dev)
            self.ready.set()
            self.running = True
        else:
            self.device_recognized = False
            self.ready.set()
            self.running = False
            self.logger.error(f"'{self.device}' is not a valid device type. Deactivating controller.")
            return

        self.input_timer = time.time()
        self.lastUpdate = None

        # Set up listener (e.g. MQTT, EDGE)
        if ('listener' in self.dict_inputs[self.device] and
              self.dict_inputs[self.device]['listener']):
            self.logger.debug("Detected as listener. Starting listener thread.")
            input_listener = threading.Thread(
                target=self.measure_input.listener())
            input_listener.daemon = True
            input_listener.start()

    def update_measure(self):
        """
        Retrieve measurement from input

        :return: None if success, 0 if fail
        :rtype: int or None
        """
        measurements = None

        if not self.device_recognized:
            self.logger.debug(f"Device not recognized: {self.device}")
            self.measurement_success = False
            return 1

        try:
            # Get measurement from input
            measurements = self.measure_input.next()
            # Reset StopIteration counter on successful read
            if self.stop_iteration_counter:
                self.stop_iteration_counter = 0
        except StopIteration:
            self.stop_iteration_counter += 1
            # Notify after 3 consecutive errors. Prevents filling log
            # with many one-off errors over long periods of time
            if self.stop_iteration_counter > 2:
                self.stop_iteration_counter = 0
                self.logger.error(
                    "StopIteration raised 3 times. Possibly could not read "
                    "input. Ensure it's connected properly and "
                    "detected.")
        except AttributeError:
            self.logger.error(
                "Mycodo is attempting to acquire measurement(s) from an Input that has already critically errored. "
                "Review the log lines following Input Activation to investigate why this happened.")
        except Exception as except_msg:
            if except_msg == "'NoneType' object has no attribute 'next'":
                self.logger.exception("This Input has already crashed. Look before this message "
                                      "for the relevant error that indicates what the issue was.")
            else:
                self.logger.exception("Error while attempting to read input")

        if self.device_recognized and measurements is not None:
            self.measurement = Measurement(measurements)
            self.last_measurement = time.time()
            self.measurement_success = True
        else:
            self.measurement_success = False

        self.lastUpdate = time.time()

    def create_measurements_dict(self):
        measurements_record = {}
        for each_channel, each_measurement in self.measurement.values.items():
            measurement = self.device_measurements.filter(
                DeviceMeasurements.channel == each_channel).first()

            if measurement and 'value' in each_measurement:
                conversion = self.conversions.filter(
                    Conversion.unique_id == measurement.conversion_id).first()

                # If a timestamp is passed from the module, use it
                if 'timestamp_utc' in each_measurement:
                    timestamp = each_measurement['timestamp_utc']
                else:
                    timestamp = None

                measurements_record = parse_measurement(
                    conversion,
                    measurement,
                    measurements_record,
                    each_channel,
                    each_measurement,
                    timestamp=timestamp)
        self.logger.debug(
            f"Adding measurements to InfluxDB with ID {self.unique_id}: {measurements_record}")
        return measurements_record

    def force_measurements(self):
        """Signal that a measurement needs to be obtained."""
        self.next_measurement = time.time()
        return 0, "Input instructed to begin acquiring measurements"

    def call_module_function(self, button_id, args_dict, thread=True):
        """Execute function from custom action button press."""
        try:
            run_command = getattr(self.measure_input, button_id)
            if thread:
                thread_run_command = threading.Thread(
                    target=run_command,
                    args=(args_dict,))
                thread_run_command.start()
                return 0, "Command sent to Input Controller and is running in the background."
            else:
                return_val = run_command(args_dict)
                return 0, f"Command sent to Input Controller. Returned: {return_val}"
        except Exception as err:
            msg = f"Error executing button press function '{button_id}': {err}"
            self.logger.exception(msg)
            return 1, msg

    def pre_stop(self):
        # Ensure pre-output is off
        if self.pre_output_setup:
            output_off = threading.Thread(
                target=self.control.output_off,
                args=(self.pre_output_id,),
                kwargs={'output_channel': self.pre_output_channel})
            output_off.start()
示例#19
0
class AtlasScientificUART(AbstractBaseAtlasScientific):
    """A Class to communicate with Atlas Scientific sensors via UART"""
    def __init__(self, serial_device, baudrate=9600):
        super(AtlasScientificUART,
              self).__init__(interface='UART',
                             name=serial_device.replace("/", "_"))

        self.logger = logging.getLogger("{}{}".format(
            __name__, serial_device.replace("/", "_")))

        self.setup = False
        self.serial_device = serial_device
        self.lockfile = LockFile()

        try:
            self.atlas_device = serial.Serial(port=serial_device,
                                              baudrate=baudrate,
                                              timeout=5,
                                              writeTimeout=5)

            cmd_return = self.send_cmd(
                'C,0')  # Disable continuous measurements

            if cmd_return:
                (board, revision, firmware_version) = self.get_board_version()

                self.logger.info(
                    "Atlas Scientific Board: {brd}, Rev: {rev}, Firmware: {fw}"
                    .format(brd=board, rev=revision, fw=firmware_version))
                self.setup = True

        except serial.SerialException as err:
            self.logger.exception(
                "{cls} raised an exception when initializing: "
                "{err}".format(cls=type(self).__name__, err=err))
            self.logger.exception('Opening serial')

    def read_line(self):
        """
        taken from the ftdi library and modified to
        use the ezo line separator "\r"
        """
        lsl = len('\r')
        line_buffer = []
        while True:
            next_char = self.atlas_device.read(1)
            if next_char in [b'', b'\r', '']:
                break
            line_buffer.append(next_char)
            if (len(line_buffer) >= lsl and line_buffer[-lsl:] == list('\r')):
                break
        return b''.join(line_buffer)

    def query(self, query_str):
        """ Send command and return reply """
        lock_file_amend = '/var/lock/sensor-atlas.{dev}'.format(
            dev=self.serial_device.replace("/", "-"))

        if self.lockfile.lock_acquire(lock_file_amend, timeout=3600):
            try:
                self.send_cmd(query_str)
                time.sleep(1.3)
                response = self.read_lines()
                return 'success', response
            finally:
                self.lockfile.lock_release(lock_file_amend)
        return None, None

    def read_lines(self):
        """
        also taken from ftdi lib to work with modified readline function
        """
        lines = []
        try:
            while True:
                line = self.read_line().decode()
                if not line:
                    break
                    # self.atlas_device.flush_input()
                lines.append(line)
            return lines
        except SerialException:
            self.logger.exception('Read Lines')
            return None
        except AttributeError:
            self.logger.exception('UART device not initialized')
            return None

    def atlas_write(self, cmd):
        self.write(cmd)

    def write(self, cmd):
        self.send_cmd(cmd)

    def send_cmd(self, cmd):
        """
        Send command to the Atlas Sensor.
        Before sending, add Carriage Return at the end of the command.
        :param cmd:
        :return:
        """
        buf = "{cmd}\r".format(cmd=cmd)  # add carriage return
        try:
            self.atlas_device.write(buf.encode())
            return True
        except SerialTimeoutException:
            self.logger.error(
                "SerialTimeoutException: Write timeout. This indicates "
                "you may not have the correct device configured.")
        except SerialException:
            self.logger.exception('Send CMD')
            return None
        except AttributeError:
            self.logger.exception('UART device not initialized')
            return None
示例#20
0
class AbstractBaseController(object):
    """
    Base Controller class that ensures certain methods and values are present
    in controllers.
    """
    def __init__(self, unique_id=None, testing=False, name=__name__):

        logger_name = "{}".format(name)
        if not testing and unique_id:
            logger_name += "_{}".format(unique_id.split('-')[0])
        self.logger = logging.getLogger(logger_name)

        self.lockfile = LockFile()

    def setup_custom_options(self, custom_options, custom_controller):
        for each_option_default in custom_options:
            try:
                required = False
                custom_option_set = False
                error = []
                if 'type' not in each_option_default:
                    error.append("'type' not found in custom_options")
                if 'id' not in each_option_default:
                    error.append("'id' not found in custom_options")
                if 'default_value' not in each_option_default:
                    error.append("'default_value' not found in custom_options")
                for each_error in error:
                    self.logger.error(each_error)
                if error:
                    return

                if 'required' in each_option_default and each_option_default[
                        'required']:
                    required = True

                option_value = each_option_default['default_value']
                device_id = None
                measurement_id = None

                if not hasattr(custom_controller, 'custom_options'):
                    self.logger.error(
                        "custom_controller missing attribute custom_options")
                    return

                if custom_controller.custom_options:
                    for each_option in custom_controller.custom_options.split(
                            ';'):
                        option = each_option.split(',')[0]

                        if option == each_option_default['id']:
                            custom_option_set = True

                            if (each_option_default['type']
                                    == 'select_measurement'
                                    and len(each_option.split(',')) > 2):
                                device_id = each_option.split(',')[1]
                                measurement_id = each_option.split(',')[2]
                            else:
                                option_value = each_option.split(',')[1]

                if required and not custom_option_set:
                    self.logger.error(
                        "Custom option '{}' required but was not found to be set by the user"
                        .format(each_option_default['id']))

                elif each_option_default['type'] == 'integer':
                    setattr(self, each_option_default['id'], int(option_value))

                elif each_option_default['type'] == 'float':
                    setattr(self, each_option_default['id'],
                            float(option_value))

                elif each_option_default['type'] == 'bool':
                    setattr(self, each_option_default['id'],
                            bool(option_value))

                elif each_option_default['type'] == 'text':
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select':
                    setattr(self, each_option_default['id'], str(option_value))

                elif each_option_default['type'] == 'select_measurement':
                    setattr(self,
                            '{}_device_id'.format(each_option_default['id']),
                            device_id)
                    setattr(
                        self,
                        '{}_measurement_id'.format(each_option_default['id']),
                        measurement_id)

                elif each_option_default['type'] == 'select_device':
                    setattr(self, '{}_id'.format(each_option_default['id']),
                            str(option_value))

                else:
                    self.logger.error("Unknown custom_option type '{}'".format(
                        each_option_default['type']))
            except Exception:
                self.logger.exception("Error parsing custom_options")

    @staticmethod
    def get_last_measurement(device_id, measurement_id, max_age=None):
        device_measurement = db_retrieve_table_daemon(
            DeviceMeasurements).filter(
                DeviceMeasurements.unique_id == measurement_id).first()
        if device_measurement:
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
        else:
            conversion = None
        channel, unit, measurement = return_measurement_info(
            device_measurement, conversion)

        last_measurement = read_last_influxdb(device_id,
                                              unit,
                                              channel,
                                              measure=measurement,
                                              duration_sec=max_age)

        return last_measurement

    def lock_acquire(self, lockfile, timeout):
        self.lockfile.lock_acquire(lockfile, timeout)

    def lock_locked(self, lockfile):
        return self.lockfile.lock_locked(lockfile)

    def lock_release(self, lockfile):
        self.lockfile.lock_release(lockfile)
示例#21
0
    def get_measurement(self):
        """Obtain and return the measurements."""
        self.return_dict = copy.deepcopy(measurements_dict)

        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=3600):
            self.logger.debug("Starting measurement")
            try:
                if not self.initialized:
                    self.try_initialize()

                if not self.initialized:
                    self.logger.error("Count not initialize sensor.")

                if self.initialized and not self.connected:
                    self.connect()

                if not self.connected:
                    self.logger.error("Count not connect to sensor.")

                if self.connected:
                    try:
                        # Download stored data
                        if self.download_stored_data:
                            self.download_data()
                            if not self.running:
                                return

                        # Set logging interval if not already set
                        if ('logger_interval_ms' in self.device_information
                                and self.logging_interval_ms != self.device_information['logger_interval_ms']):
                            self.set_logging_interval()

                        self.logger.debug("Acquiring present measurements")
                        # Get battery percent charge
                        if self.is_enabled(2):
                            self.value_set(2, self.gadget.readBattery())

                        # Get temperature and humidity last so their timestamp in the
                        # database will be the most accurate
                        if self.is_enabled(0):
                            self.value_set(0, self.gadget.readTemperature())

                        if self.is_enabled(1):
                            self.value_set(1, self.gadget.readHumidity())
                        self.logger.debug("Acquired present measurements")
                    except self.btle.BTLEDisconnectError:
                        self.logger.error("Disconnected")
                        return
                    except Exception:
                        self.logger.exception("Unknown Error")
                        return
                    finally:
                        self.disconnect()

                    if (self.is_enabled(3) and
                            self.is_enabled(0) and
                            self.is_enabled(1)):
                        self.value_set(3, calculate_dewpoint(
                            self.value_get(0), self.value_get(1)))

                    if (self.is_enabled(4) and
                            self.is_enabled(0) and
                            self.is_enabled(1)):
                        self.value_set(4, calculate_vapor_pressure_deficit(
                            self.value_get(0), self.value_get(1)))

                    self.logger.debug("Completed measurement")
                    return self.return_dict
                else:
                    self.logger.debug("Not connected: Not measuring")
            finally:
                lf.lock_release(self.lock_file)
                time.sleep(1)
示例#22
0
    def get_measurement(self):
        """Obtain and return the measurements."""
        if not self.sensor:
            self.logger.error(
                "Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info."
            )
            return

        self.return_dict = copy.deepcopy(measurements_dict)

        lf = LockFile()
        if lf.lock_acquire(self.lock_file, timeout=3600):
            self.logger.debug("Starting measurement")
            try:
                cmd = 'timeout -k 11 10 /var/mycodo-root/env/bin/python ' \
                      '/var/mycodo-root/mycodo/inputs/scripts/ruuvitag_values.py ' \
                      '--mac_address {mac} --bt_adapter {bta}'.format(
                          mac=self.location, bta=self.bt_adapter)
                cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
                cmd_return, _ = cmd.communicate()
                cmd.wait()

                if not cmd_return:
                    self.logger.debug("Measurement command returned no data")
                    return

                values = cmd_return.decode('ascii').split(',')

                if not str_is_float(values[0]):
                    self.logger.debug(
                        "Error: Could not convert string to float: string '{}'"
                        .format(str(values[0])))
                    return

                temperature = float(str(values[0]))
                humidity = float(values[1])
                pressure = float(values[2])
                battery = float(values[3]) / 1000
                acceleration_g_force = float(values[4]) / 1000
                acceleration_x_g_force = float(values[5]) / 1000
                acceleration_y_g_force = float(values[6]) / 1000
                acceleration_z_g_force = float(values[7]) / 1000

                if battery < 1 or battery > 4:
                    self.logger.debug(
                        "Not recording measurements: "
                        "Battery outside expected range (1 < battery volts < 4): {bat}"
                        .format(bat=battery))
                    return

                if self.is_enabled(0):
                    self.value_set(0, temperature)

                if self.is_enabled(1):
                    self.value_set(1, humidity)

                if self.is_enabled(2):
                    self.value_set(2, pressure)

                if self.is_enabled(3):
                    self.value_set(3, battery)

                if self.is_enabled(4):
                    self.value_set(4, acceleration_g_force)

                if self.is_enabled(5):
                    self.value_set(5, acceleration_x_g_force)

                if self.is_enabled(6):
                    self.value_set(6, acceleration_y_g_force)

                if self.is_enabled(7):
                    self.value_set(7, acceleration_z_g_force)

                if (self.is_enabled(8) and self.is_enabled(0)
                        and self.is_enabled(1)):
                    self.value_set(
                        8,
                        calculate_dewpoint(self.value_get(0),
                                           self.value_get(1)))

                if (self.is_enabled(9) and self.is_enabled(0)
                        and self.is_enabled(1)):
                    self.value_set(
                        9,
                        calculate_vapor_pressure_deficit(
                            self.value_get(0), self.value_get(1)))

                self.logger.debug("Completed measurement")
                return self.return_dict

            except Exception as msg:
                self.logger.debug("Error: {}".format(msg))

            finally:
                lf.lock_release(self.lock_file)
                time.sleep(1)