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)
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)