Exemple #1
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)
Exemple #2
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()
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
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)