def get_measurements_from_str(self, device): try: measurements = [] device_list = device.split(';') for each_device_set in device_list: device_id = each_device_set.split(',')[0] device_measure_id = each_device_set.split(',')[1] measurement = get_measurement( device_measure_id) if not measurement: return False, None last_measurement = read_last_influxdb( device_id, measurement.unit, measurement.measurement, measurement.channel, self.max_measure_age) if not last_measurement: return False, None else: measurements.append(last_measurement[1]) return True, measurements except urllib3.exceptions.NewConnectionError: return False, "Influxdb: urllib3.exceptions.NewConnectionError" except Exception as msg: return False, "Influxdb: Unknown Error: {err}".format(err=msg)
def get_last_measurement(self): """ Retrieve the latest input measurement from InfluxDB :rtype: None """ self.last_measurement_success = False # Get latest measurement from influxdb try: device_measurement = get_measurement(self.measurement_id) 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) self.last_measurement = read_last_influxdb( self.device_id, unit, measurement, channel, int(self.max_measure_age)) if self.last_measurement: self.last_time = self.last_measurement[0] self.last_measurement = self.last_measurement[1] utc_dt = datetime.datetime.strptime( self.last_time.split(".")[0], '%Y-%m-%dT%H:%M:%S') utc_timestamp = calendar.timegm(utc_dt.timetuple()) local_timestamp = str(datetime.datetime.fromtimestamp(utc_timestamp)) self.logger.debug("Latest (CH{ch}, Unit: {unit}): {last} @ {ts}".format( ch=channel, unit=unit, last=self.last_measurement, ts=local_timestamp)) if calendar.timegm(time.gmtime()) - utc_timestamp > self.max_measure_age: self.logger.error( "Last measurement was {last_sec} seconds ago, however" " the maximum measurement age is set to {max_sec}" " seconds.".format( last_sec=calendar.timegm(time.gmtime()) - utc_timestamp, max_sec=self.max_measure_age )) self.last_measurement_success = True else: self.logger.warning("No data returned from influxdb") except requests.ConnectionError: self.logger.error("Failed to read measurement from the " "influxdb database: Could not connect.") except Exception as except_msg: self.logger.exception( "Exception while reading measurement from the influxdb " "database: {err}".format(err=except_msg))
def get_measurements_from_id(self, device_id, measure_id): measurement = get_measurement(measure_id) measurement = read_last_influxdb( device_id, measurement.unit, measurement.measurement, measurement.channel, self.max_measure_age) if not measurement: return False, None return True, measurement
def loop(self): if self.timer_loop < time.time(): while self.timer_loop < time.time(): self.timer_loop += self.period measure = [] for each_id_set in self.select_measurement: device_device_id = each_id_set.split(",")[0] device_measure_id = each_id_set.split(",")[1] device_measurement = get_measurement(device_measure_id) if not device_measurement: self.logger.error("Could not find Device Measurement") return conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) last_measurement = read_last_influxdb( device_device_id, unit, channel, measure=measurement, duration_sec=self.max_measure_age) if not last_measurement: self.logger.error( "Could not find measurement within the set Max Age for Device {} and Measurement {}" .format(device_device_id, device_measure_id)) if self.halt_on_missing_measure: self.logger.debug( "Instructed to halt on the first missing measurement. Halting." ) return False else: measure.append(last_measurement[1]) if len(measure) > 1: stat_mean = float(sum(measure) / float(len(measure))) stat_median = median(measure) stat_minimum = min(measure) stat_maximum = max(measure) stdev_ = stdev(measure) stdev_mean_upper = stat_mean + stdev_ stdev_mean_lower = stat_mean - stdev_ list_measurement = [ stat_mean, stat_median, stat_minimum, stat_maximum, stdev_, stdev_mean_upper, stdev_mean_lower ] for each_channel, each_measurement in self.channels_measurement.items( ): if each_measurement.is_enabled: channel, unit, measurement = return_measurement_info( each_measurement, self.channels_conversion[each_channel]) self.logger.debug( "Saving {} to channel {} with measurement {} and unit {}" .format(list_measurement[each_channel], each_channel, measurement, unit)) write_influxdb_value( self.unique_id, unit, value=list_measurement[each_channel], measure=measurement, channel=each_channel) else: self.logger.debug( "Less than 2 measurements found within Max Age. " "Calculations need at least 2 measurements. Not calculating." )
def math_mod(form_mod_math, form_mod_type=None): action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['math']['title']) error = [] try: mod_math = Math.query.filter( Math.unique_id == form_mod_math.math_id.data).first() if mod_math.is_activated: error.append( gettext("Deactivate Math controller before modifying its " "settings")) if mod_math.math_type != 'redundancy' and form_mod_type and not form_mod_type.validate( ): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_type) mod_math.name = form_mod_math.name.data mod_math.period = form_mod_math.period.data mod_math.max_measure_age = form_mod_math.max_measure_age.data mod_math.start_offset = form_mod_math.start_offset.data measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_math.math_id.data).all() # Set each measurement to the same measurement/unit if form_mod_math.select_measurement_unit.data: for each_measurement in measurements: if ',' in form_mod_math.select_measurement_unit.data: each_measurement.measurement = form_mod_math.select_measurement_unit.data.split( ',')[0] each_measurement.unit = form_mod_math.select_measurement_unit.data.split( ',')[1] else: each_measurement.measurement = '' each_measurement.unit = '' # Enable/disable Channels if form_mod_math.measurements_enabled.data: for each_measurement in measurements: if each_measurement.unique_id in form_mod_math.measurements_enabled.data: each_measurement.is_enabled = True else: each_measurement.is_enabled = False original_inputs = mod_math.inputs # Collect inputs and measurement name and units if mod_math.math_type in [ 'average', 'difference', 'redundancy', 'statistics', 'sum', 'verification' ]: if len(form_mod_math.inputs.data) < 2: error.append("At least two Inputs must be selected") if form_mod_math.inputs.data: inputs_joined = ";".join(form_mod_math.inputs.data) mod_math.inputs = inputs_joined else: mod_math.inputs = '' if mod_math.math_type == 'average_single': mod_math.inputs = form_mod_type.average_input.data # Change measurement information if form_mod_type.average_input.data and ',' in form_mod_type.average_input.data: measurement_id = form_mod_type.average_input.data.split(',')[1] if measurement_id == 'output': output = Output.query.filter( Output.unique_id == form_mod_type.sum_input.data.split( ',')[0]).first() unit = output.unit measurement = output.measurement else: selected_measurement = get_measurement(measurement_id) if selected_measurement: conversion = Conversion.query.filter( Conversion.unique_id == selected_measurement.conversion_id).first() else: conversion = None _, unit, measurement = return_measurement_info( selected_measurement, conversion) mod_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_math.math_id.data).first() mod_measurement.measurement = measurement mod_measurement.unit = unit if mod_math.math_type == 'sum_single': mod_math.inputs = form_mod_type.sum_input.data # Change measurement information if form_mod_type.sum_input.data and ',' in form_mod_type.sum_input.data: measurement_id = form_mod_type.sum_input.data.split(',')[1] if measurement_id == 'output': output = Output.query.filter( Output.unique_id == form_mod_type.sum_input.data.split( ',')[0]).first() unit = output.unit measurement = output.measurement else: selected_measurement = get_measurement(measurement_id) if selected_measurement: conversion = Conversion.query.filter( Conversion.unique_id == selected_measurement.conversion_id).first() else: conversion = None _, unit, measurement = return_measurement_info( selected_measurement, conversion) mod_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_math.math_id.data).first() mod_measurement.measurement = measurement mod_measurement.unit = unit elif mod_math.math_type == 'redundancy': # If input selection changes, create the default order list that can then be modified if original_inputs != ';'.join(form_mod_math.inputs.data): mod_math.order_of_use = ';'.join(form_mod_math.inputs.data) else: # Ensure order_of_use includes all input IDs and is properly formatted if mod_math.inputs: mod_math.order_of_use = ';'.join( form_mod_type.order_of_use.data) elif mod_math.math_type == 'difference': if len(form_mod_math.inputs.data) != 2: error.append("Only two Inputs must be selected") mod_math.difference_reverse_order = form_mod_type.difference_reverse_order.data mod_math.difference_absolute = form_mod_type.difference_absolute.data elif mod_math.math_type == 'equation': mod_math.equation_input = form_mod_type.equation_input.data mod_math.equation = form_mod_type.equation.data elif mod_math.math_type == 'humidity': mod_math.dry_bulb_t_id = form_mod_type.dry_bulb_temperature.data.split( ',')[0] mod_math.dry_bulb_t_measure_id = form_mod_type.dry_bulb_temperature.data.split( ',')[1] dbt_input = Input.query.filter( Input.unique_id == mod_math.dry_bulb_t_id).first() dbt_math = Input.query.filter( Math.unique_id == mod_math.dry_bulb_t_id).first() if not dbt_input and not dbt_math: error.append( "Invalid dry-bulb temperature selection: Must be a valid Input or Math" ) mod_math.wet_bulb_t_id = form_mod_type.wet_bulb_temperature.data.split( ',')[0] mod_math.wet_bulb_t_measure_id = form_mod_type.wet_bulb_temperature.data.split( ',')[1] wbt_input = Input.query.filter( Input.unique_id == mod_math.wet_bulb_t_id).first() wbt_math = Input.query.filter( Math.unique_id == mod_math.wet_bulb_t_id).first() if not wbt_input and not wbt_math: error.append( "Invalid wet-bulb temperature selection: Must be a valid Input or Math" ) if form_mod_type.pressure.data: mod_math.pressure_pa_id = form_mod_type.pressure.data.split( ',')[0] mod_math.pressure_pa_measure_id = form_mod_type.pressure.data.split( ',')[1] else: mod_math.pressure_pa_id = None mod_math.pressure_pa_measure_id = None elif mod_math.math_type == 'verification': mod_math.max_difference = form_mod_type.max_difference.data elif mod_math.math_type == 'vapor_pressure_deficit': mod_math.unique_id_1 = form_mod_type.unique_id_1.data.split(',')[0] mod_math.unique_measurement_id_1 = form_mod_type.unique_id_1.data.split( ',')[1] vpd_input = Input.query.filter( Input.unique_id == mod_math.unique_id_1).first() vpd_math = Input.query.filter( Math.unique_id == mod_math.unique_id_1).first() if not vpd_input and not vpd_math: error.append( "Invalid vapor pressure deficit temperature selection: Must be a valid Input or Math" ) mod_math.unique_id_2 = form_mod_type.unique_id_2.data.split(',')[0] mod_math.unique_measurement_id_2 = form_mod_type.unique_id_2.data.split( ',')[1] vpd_input = Input.query.filter( Input.unique_id == mod_math.unique_id_2).first() vpd_math = Input.query.filter( Math.unique_id == mod_math.unique_id_2).first() if not vpd_input and not vpd_math: error.append( "Invalid vapor pressure deficit humidity selection: Must be a valid Input or Math" ) if not error: db.session.commit() except Exception as except_msg: logger.exception(1) error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_data'))
def get_last_measurement_pid(self): """ Retrieve the latest input measurement from InfluxDB :rtype: None """ self.last_measurement_success = False # Get latest measurement from influxdb try: device_measurement = get_measurement(self.measurement_id) 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) self.last_measurement = read_last_influxdb( self.device_id, unit, channel, measure=measurement, duration_sec=int(self.max_measure_age)) if self.last_measurement: self.last_time = self.last_measurement[0] self.last_measurement = self.last_measurement[1] utc_dt = datetime.datetime.strptime( self.last_time.split(".")[0], '%Y-%m-%dT%H:%M:%S') utc_timestamp = calendar.timegm(utc_dt.timetuple()) local_timestamp = str( datetime.datetime.fromtimestamp(utc_timestamp)) self.logger.debug( "Latest (CH{ch}, Unit: {unit}): {last} @ {ts}".format( ch=channel, unit=unit, last=self.last_measurement, ts=local_timestamp)) if calendar.timegm( time.gmtime()) - utc_timestamp > self.max_measure_age: self.logger.error( "Last measurement was {last_sec} seconds ago, however" " the maximum measurement age is set to {max_sec}" " seconds.".format( last_sec=calendar.timegm(time.gmtime()) - utc_timestamp, max_sec=self.max_measure_age)) self.last_measurement_success = True else: self.logger.warning("No data returned from influxdb") except requests.ConnectionError: self.logger.error( "Failed to read measurement from the influxdb database: Could not connect." ) except Exception as except_msg: self.logger.exception( "Exception while reading measurement from the influxdb database: {err}" .format(err=except_msg))
def get_measurement(self): """Gets the sensor's Electrical Conductivity measurement""" if not self.atlas_device.setup: self.logger.error("Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info.") return return_string = None self.return_dict = copy.deepcopy(measurements_dict) # Compensate measurement based on a temperature measurement if self.temperature_comp_meas_measurement_id and self.atlas_command: self.logger.debug("pH sensor set to calibrate temperature") last_measurement = self.get_last_measurement( self.temperature_comp_meas_device_id, self.temperature_comp_meas_measurement_id, max_age=self.max_age) if last_measurement and len(last_measurement) > 1: device_measurement = get_measurement( self.temperature_comp_meas_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) _, unit, _ = return_measurement_info( device_measurement, conversion) if unit != "C": out_value = convert_from_x_to_y_unit( unit, "C", last_measurement[1]) else: out_value = last_measurement[1] self.logger.debug( "Latest temperature used to calibrate: {temp}".format( temp=out_value)) ret_value, ret_msg = self.atlas_command.calibrate( 'temperature', set_amount=out_value) time.sleep(0.5) self.logger.debug("Calibration returned: {val}, {msg}".format( val=ret_value, msg=ret_msg)) else: self.logger.error( "Calibration measurement not found within the past " "{} seconds".format(self.max_age)) # Read device atlas_status, atlas_return = self.atlas_device.query('R') self.logger.debug("Device Returned: {}: {}".format(atlas_status, atlas_return)) if atlas_status == 'error': self.logger.error("Sensor read unsuccessful: {err}".format(err=atlas_return)) return # Parse device return data if self.interface in ['FTDI', 'UART']: # Check for "check probe" for each_split in atlas_return: if 'check probe' in each_split: self.logger.error('"check probe" returned from sensor') return # Find float value in list for each_split in atlas_return: if "," in each_split or str_is_float(each_split): return_string = each_split break elif self.interface == 'I2C': return_string = atlas_return if return_string and ',' in return_string: # Multiple values returned index_place = 0 return_list = return_string.split(',') if (self.is_enabled(0) and len(return_list) > index_place and str_is_float(return_list[index_place])): self.value_set(0, float(return_list[index_place])) index_place += 1 if (self.is_enabled(1) and len(return_list) > index_place and str_is_float(return_list[index_place])): self.value_set(1, float(return_list[index_place])) index_place += 1 if (self.is_enabled(2) and len(return_list) > index_place and str_is_float(return_list[index_place])): self.value_set(2, float(return_list[index_place])) index_place += 1 if (self.is_enabled(3) and len(return_list) > index_place and str_is_float(return_list[index_place])): self.value_set(3, float(return_list[index_place])) elif str_is_float(return_string): # Single value returned if self.is_enabled(0): self.value_set(0, float(return_string)) elif self.is_enabled(1): self.value_set(1, float(return_string)) elif self.is_enabled(2): self.value_set(2, float(return_string)) elif self.is_enabled(3): self.value_set(3, float(return_string)) return self.return_dict
def check_pid(self): """ Get measurement and apply to PID controller """ # If PID is active, retrieve measurement and update # the control variable. # A PID on hold will sustain the current output and # not update the control variable. if self.is_activated and (not self.is_paused or not self.is_held): self.get_last_measurement_pid() if self.last_measurement_success: if self.setpoint_tracking_type == 'method' and self.setpoint_tracking_id != '': # Update setpoint using a method this_pid = db_retrieve_table_daemon( PID, unique_id=self.unique_id) new_setpoint, ended = calculate_method_setpoint( self.setpoint_tracking_id, PID, this_pid, Method, MethodData, self.logger) if ended: self.method_start_act = 'Ended' if new_setpoint is not None: self.PID_Controller.setpoint = new_setpoint else: self.PID_Controller.setpoint = self.setpoint if self.setpoint_tracking_type == 'input-math' and self.setpoint_tracking_id != '': # Update setpoint using an Input or Math device_id = self.setpoint_tracking_id.split(',')[0] measurement_id = self.setpoint_tracking_id.split(',')[1] measurement = get_measurement(measurement_id) if not measurement: return False, None conversion = db_retrieve_table_daemon( Conversion, unique_id=measurement.conversion_id) channel, unit, measurement = return_measurement_info( measurement, conversion) last_measurement = read_last_influxdb( device_id, unit, channel, measure=measurement, duration_sec=self.setpoint_tracking_max_age) if last_measurement[1] is not None: self.PID_Controller.setpoint = last_measurement[1] else: self.logger.debug( "Could not find measurement for Setpoint " "Tracking. Max Age of {} exceeded for measuring " "device ID {} (measurement {})".format( self.setpoint_tracking_max_age, device_id, measurement_id)) self.PID_Controller.setpoint = None # Calculate new control variable (output) from PID Controller self.PID_Controller.update_pid_output(self.last_measurement) self.write_pid_values() # Write variables to database # Is PID in a state that allows manipulation of outputs if (self.is_activated and self.PID_Controller.setpoint is not None and (not self.is_paused or self.is_held)): self.manipulate_output()
def math_mod(form_mod_math, form_mod_type=None): action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['math']['title']) error = [] try: mod_math = Math.query.filter( Math.unique_id == form_mod_math.math_id.data).first() if mod_math.is_activated: error.append(gettext( "Deactivate Math controller before modifying its " "settings")) if mod_math.math_type != 'redundancy' and form_mod_type and not form_mod_type.validate(): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_type) mod_math.name = form_mod_math.name.data mod_math.period = form_mod_math.period.data mod_math.max_measure_age = form_mod_math.max_measure_age.data mod_math.start_offset = form_mod_math.start_offset.data measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_math.math_id.data).all() # Set each measurement to the same measurement/unit if form_mod_math.select_measurement_unit.data: for each_measurement in measurements: if ',' in form_mod_math.select_measurement_unit.data: each_measurement.measurement = form_mod_math.select_measurement_unit.data.split(',')[0] each_measurement.unit = form_mod_math.select_measurement_unit.data.split(',')[1] else: each_measurement.measurement = '' each_measurement.unit = '' # Enable/disable Channels if form_mod_math.measurements_enabled.data: for each_measurement in measurements: if each_measurement.unique_id in form_mod_math.measurements_enabled.data: each_measurement.is_enabled = True else: each_measurement.is_enabled = False original_inputs = mod_math.inputs # Collect inputs and measurement name and units if mod_math.math_type in ['average', 'redundancy', 'difference', 'statistics', 'verification']: if len(form_mod_math.inputs.data) < 2: error.append("At least two Inputs must be selected") if form_mod_math.inputs.data: inputs_joined = ";".join(form_mod_math.inputs.data) mod_math.inputs = inputs_joined else: mod_math.inputs = '' if mod_math.math_type == 'average_single': mod_math.inputs = form_mod_type.average_input.data # Change measurement information if form_mod_type.average_input.data and ',' in form_mod_type.average_input.data: measurement_id = form_mod_type.average_input.data.split(',')[1] selected_measurement = get_measurement(measurement_id) if selected_measurement: conversion = Conversion.query.filter( Conversion.unique_id == selected_measurement.conversion_id).first() else: conversion = None _, unit, measurement = return_measurement_info( selected_measurement, conversion) mod_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_math.math_id.data).first() mod_measurement.measurement = measurement mod_measurement.unit = unit elif mod_math.math_type == 'redundancy': # If input selection changes, create the default order list that can then be modified if original_inputs != ';'.join(form_mod_math.inputs.data): mod_math.order_of_use = ';'.join(form_mod_math.inputs.data) else: # Ensure order_of_use includes all input IDs and is properly formatted if mod_math.inputs: mod_math.order_of_use = ';'.join(form_mod_type.order_of_use.data) elif mod_math.math_type == 'difference': if len(form_mod_math.inputs.data) != 2: error.append("Only two Inputs must be selected") mod_math.difference_reverse_order = form_mod_type.difference_reverse_order.data mod_math.difference_absolute = form_mod_type.difference_absolute.data elif mod_math.math_type == 'equation': mod_math.equation_input = form_mod_type.equation_input.data mod_math.equation = form_mod_type.equation.data elif mod_math.math_type == 'humidity': mod_math.dry_bulb_t_id = form_mod_type.dry_bulb_temperature.data.split(',')[0] mod_math.dry_bulb_t_measure_id = form_mod_type.dry_bulb_temperature.data.split(',')[1] dbt_input = Input.query.filter( Input.unique_id == mod_math.dry_bulb_t_id).first() dbt_math = Input.query.filter( Math.unique_id == mod_math.dry_bulb_t_id).first() if not dbt_input and not dbt_math: error.append("Invalid dry-bulb temperature selection: Must be a valid Input or Math") mod_math.wet_bulb_t_id = form_mod_type.wet_bulb_temperature.data.split(',')[0] mod_math.wet_bulb_t_measure_id = form_mod_type.wet_bulb_temperature.data.split(',')[1] wbt_input = Input.query.filter( Input.unique_id == mod_math.wet_bulb_t_id).first() wbt_math = Input.query.filter( Math.unique_id == mod_math.wet_bulb_t_id).first() if not wbt_input and not wbt_math: error.append("Invalid wet-bulb temperature selection: Must be a valid Input or Math") if form_mod_type.pressure.data: mod_math.pressure_pa_id = form_mod_type.pressure.data.split(',')[0] mod_math.pressure_pa_measure_id = form_mod_type.pressure.data.split(',')[1] else: mod_math.pressure_pa_id = None mod_math.pressure_pa_measure_id = None elif mod_math.math_type == 'verification': mod_math.max_difference = form_mod_type.max_difference.data elif mod_math.math_type == 'vapor_pressure_deficit': mod_math.unique_id_1 = form_mod_type.unique_id_1.data.split(',')[0] mod_math.unique_measurement_id_1 = form_mod_type.unique_id_1.data.split(',')[1] vpd_input = Input.query.filter( Input.unique_id == mod_math.unique_id_1).first() vpd_math = Input.query.filter( Math.unique_id == mod_math.unique_id_1).first() if not vpd_input and not vpd_math: error.append("Invalid vapor pressure deficit temperature selection: Must be a valid Input or Math") mod_math.unique_id_2 = form_mod_type.unique_id_2.data.split(',')[0] mod_math.unique_measurement_id_2 = form_mod_type.unique_id_2.data.split(',')[1] vpd_input = Input.query.filter( Input.unique_id == mod_math.unique_id_2).first() vpd_math = Input.query.filter( Math.unique_id == mod_math.unique_id_2).first() if not vpd_input and not vpd_math: error.append("Invalid vapor pressure deficit humidity selection: Must be a valid Input or Math") if not error: db.session.commit() except Exception as except_msg: logger.exception(1) error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_data'))
def check_pid(self): """ Get measurement and apply to PID controller """ # If PID is active, retrieve measurement and update # the control variable. # A PID on hold will sustain the current output and # not update the control variable. if self.is_activated and (not self.is_paused or not self.is_held): self.get_last_measurement() if self.last_measurement_success: if self.setpoint_tracking_type == 'method' and self.setpoint_tracking_id != '': # Update setpoint using a method this_pid = db_retrieve_table_daemon( PID, unique_id=self.unique_id) setpoint, ended = calculate_method_setpoint( self.setpoint_tracking_id, PID, this_pid, Method, MethodData, self.logger) if ended: self.method_start_act = 'Ended' if setpoint is not None: self.setpoint = setpoint else: self.setpoint = self.default_setpoint if self.setpoint_tracking_type == 'input-math' and self.setpoint_tracking_id != '': # Update setpoint using an Input or Math device_id = self.setpoint_tracking_id.split(',')[0] measurement_id = self.setpoint_tracking_id.split(',')[1] measurement = get_measurement(measurement_id) if not measurement: return False, None last_measurement = read_last_influxdb( device_id, measurement.unit, measurement.measurement, measurement.channel, self.setpoint_tracking_max_age) if last_measurement[1] is not None: self.setpoint = last_measurement[1] else: self.logger.debug( "Could not find measurement for Setpoint " "Tracking. Max Age of {} exceeded for measuring " "device ID {} (measurement {})".format( self.setpoint_tracking_max_age, device_id, measurement_id)) self.setpoint = None # If autotune activated, determine control variable (output) from autotune if self.autotune_activated: if not self.autotune.run(self.last_measurement): self.control_variable = self.autotune.output if self.autotune_debug: self.logger.info('') self.logger.info("state: {}".format(self.autotune.state)) self.logger.info("output: {}".format(self.autotune.output)) else: # Autotune has finished timestamp = time.time() - self.autotune_timestamp self.logger.info('') self.logger.info('time: {0} min'.format(round(timestamp / 60))) self.logger.info('state: {0}'.format(self.autotune.state)) if self.autotune.state == PIDAutotune.STATE_SUCCEEDED: for rule in self.autotune.tuning_rules: params = self.autotune.get_pid_parameters(rule) self.logger.info('') self.logger.info('rule: {0}'.format(rule)) self.logger.info('Kp: {0}'.format(params.Kp)) self.logger.info('Ki: {0}'.format(params.Ki)) self.logger.info('Kd: {0}'.format(params.Kd)) self.stop_controller(deactivate_pid=True) else: # Calculate new control variable (output) from PID Controller # Original PID method self.control_variable = self.update_pid_output( self.last_measurement) # New PID method (untested) # self.control_variable = self.PID_Controller.calc( # self.last_measurement, self.setpoint) self.write_pid_values() # Write variables to database # Is PID in a state that allows manipulation of outputs if (self.is_activated and self.setpoint is not None and (not self.is_paused or self.is_held)): self.manipulate_output()
def pid_mod(form_mod_pid_base, form_mod_pid_pwm_raise, form_mod_pid_pwm_lower, form_mod_pid_output_raise, form_mod_pid_output_lower, form_mod_pid_volume_raise, form_mod_pid_volume_lower): action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['pid']['title']) error = [] dict_outputs = parse_output_information() if not form_mod_pid_base.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_base) mod_pid = PID.query.filter( PID.unique_id == form_mod_pid_base.function_id.data).first() mod_pid.name = form_mod_pid_base.name.data mod_pid.measurement = form_mod_pid_base.measurement.data mod_pid.direction = form_mod_pid_base.direction.data mod_pid.period = form_mod_pid_base.period.data mod_pid.log_level_debug = form_mod_pid_base.log_level_debug.data mod_pid.start_offset = form_mod_pid_base.start_offset.data mod_pid.max_measure_age = form_mod_pid_base.max_measure_age.data mod_pid.setpoint = form_mod_pid_base.setpoint.data mod_pid.band = abs(form_mod_pid_base.band.data) mod_pid.store_lower_as_negative = form_mod_pid_base.store_lower_as_negative.data mod_pid.p = form_mod_pid_base.k_p.data mod_pid.i = form_mod_pid_base.k_i.data mod_pid.d = form_mod_pid_base.k_d.data mod_pid.integrator_min = form_mod_pid_base.integrator_max.data mod_pid.integrator_max = form_mod_pid_base.integrator_min.data mod_pid.setpoint_tracking_type = form_mod_pid_base.setpoint_tracking_type.data if form_mod_pid_base.setpoint_tracking_type.data == 'method': mod_pid.setpoint_tracking_id = form_mod_pid_base.setpoint_tracking_method_id.data elif form_mod_pid_base.setpoint_tracking_type.data == 'input-math': mod_pid.setpoint_tracking_id = form_mod_pid_base.setpoint_tracking_input_math_id.data if form_mod_pid_base.setpoint_tracking_max_age.data: mod_pid.setpoint_tracking_max_age = form_mod_pid_base.setpoint_tracking_max_age.data else: mod_pid.setpoint_tracking_max_age = 120 else: mod_pid.setpoint_tracking_id = '' # Change measurement information if ',' in form_mod_pid_base.measurement.data: measurement_id = form_mod_pid_base.measurement.data.split(',')[1] selected_measurement = get_measurement(measurement_id) measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_pid_base.function_id.data).all() for each_measurement in measurements: # Only set channels 0, 1, 2 if each_measurement.channel in [0, 1, 2]: each_measurement.measurement = selected_measurement.measurement each_measurement.unit = selected_measurement.unit # # Handle Raise Output Settings # if form_mod_pid_base.raise_output_id.data: raise_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.raise_output_id.data).first().output_type def default_raise_output_settings(mod): if mod.raise_output_type == 'on_off': mod.raise_min_duration = 0 mod.raise_max_duration = 0 mod.raise_min_off_duration = 0 elif mod.raise_output_type == 'pwm': mod.raise_min_duration = 2 mod.raise_max_duration = 98 elif mod.raise_output_type == 'volume': mod.raise_min_duration = 0 mod.raise_max_duration = 0 return mod raise_output_id_changed = False if mod_pid.raise_output_id != form_mod_pid_base.raise_output_id.data: mod_pid.raise_output_id = form_mod_pid_base.raise_output_id.data raise_output_id_changed = True # Output ID changed if ('output_types' in dict_outputs[raise_output_type] and mod_pid.raise_output_id and raise_output_id_changed): if len(dict_outputs[raise_output_type]['output_types']) == 1: mod_pid.raise_output_type = dict_outputs[ raise_output_type]['output_types'][0] else: mod_pid.raise_output_type = None mod_pid = default_raise_output_settings(mod_pid) # Output ID unchanged elif ('output_types' in dict_outputs[raise_output_type] and mod_pid.raise_output_id and not raise_output_id_changed): if (not mod_pid.raise_output_type or mod_pid.raise_output_type != form_mod_pid_base.raise_output_type.data): if len(dict_outputs[raise_output_type] ['output_types']) > 1: mod_pid.raise_output_type = form_mod_pid_base.raise_output_type.data mod_pid = default_raise_output_settings(mod_pid) elif mod_pid.raise_output_type == 'on_off': if not form_mod_pid_output_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_output_raise) else: mod_pid.raise_min_duration = form_mod_pid_output_raise.raise_min_duration.data mod_pid.raise_max_duration = form_mod_pid_output_raise.raise_max_duration.data mod_pid.raise_min_off_duration = form_mod_pid_output_raise.raise_min_off_duration.data elif mod_pid.raise_output_type == 'pwm': if not form_mod_pid_pwm_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_pwm_raise) else: mod_pid.raise_min_duration = form_mod_pid_pwm_raise.raise_min_duty_cycle.data mod_pid.raise_max_duration = form_mod_pid_pwm_raise.raise_max_duty_cycle.data mod_pid.raise_always_min_pwm = form_mod_pid_pwm_raise.raise_always_min_pwm.data elif mod_pid.raise_output_type == 'volume': if not form_mod_pid_volume_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_volume_raise) else: mod_pid.raise_min_duration = form_mod_pid_volume_raise.raise_min_amount.data mod_pid.raise_max_duration = form_mod_pid_volume_raise.raise_max_amount.data else: mod_pid.raise_output_id = None # # Handle Lower Output Settings # if form_mod_pid_base.lower_output_id.data: lower_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.lower_output_id.data).first().output_type def default_lower_output_settings(mod): if mod.lower_output_type == 'on_off': mod.lower_min_duration = 0 mod.lower_max_duration = 0 mod.lower_min_off_duration = 0 elif mod.lower_output_type == 'pwm': mod.lower_min_duration = 2 mod.lower_max_duration = 98 elif mod.lower_output_type == 'volume': mod.lower_min_duration = 0 mod.lower_max_duration = 0 return mod lower_output_id_changed = False if mod_pid.lower_output_id != form_mod_pid_base.lower_output_id.data: mod_pid.lower_output_id = form_mod_pid_base.lower_output_id.data lower_output_id_changed = True # Output ID changed if ('output_types' in dict_outputs[lower_output_type] and mod_pid.lower_output_id and lower_output_id_changed): if len(dict_outputs[lower_output_type]['output_types']) == 1: mod_pid.lower_output_type = dict_outputs[ lower_output_type]['output_types'][0] else: mod_pid.lower_output_type = None mod_pid = default_lower_output_settings(mod_pid) # Output ID unchanged elif ('output_types' in dict_outputs[lower_output_type] and mod_pid.lower_output_id and not lower_output_id_changed): if (not mod_pid.lower_output_type or mod_pid.lower_output_type != form_mod_pid_base.lower_output_type.data): if len(dict_outputs[lower_output_type] ['output_types']) > 1: mod_pid.lower_output_type = form_mod_pid_base.lower_output_type.data mod_pid = default_lower_output_settings(mod_pid) elif mod_pid.lower_output_type == 'on_off': if not form_mod_pid_output_lower.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_output_lower) else: mod_pid.lower_min_duration = form_mod_pid_output_lower.lower_min_duration.data mod_pid.lower_max_duration = form_mod_pid_output_lower.lower_max_duration.data mod_pid.lower_min_off_duration = form_mod_pid_output_lower.lower_min_off_duration.data elif mod_pid.lower_output_type == 'pwm': if not form_mod_pid_pwm_lower.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_pwm_lower) else: mod_pid.lower_min_duration = form_mod_pid_pwm_lower.lower_min_duty_cycle.data mod_pid.lower_max_duration = form_mod_pid_pwm_lower.lower_max_duty_cycle.data mod_pid.lower_always_min_pwm = form_mod_pid_pwm_lower.lower_always_min_pwm.data elif mod_pid.lower_output_type == 'volume': if not form_mod_pid_volume_lower.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_volume_lower) else: mod_pid.lower_min_duration = form_mod_pid_volume_lower.lower_min_amount.data mod_pid.lower_max_duration = form_mod_pid_volume_lower.lower_max_amount.data else: mod_pid.lower_output_id = None if (mod_pid.raise_output_id and mod_pid.lower_output_id and mod_pid.raise_output_id == mod_pid.lower_output_id): error.append(gettext("Raise and lower outputs cannot be the same")) try: if not error: db.session.commit() # If the controller is active or paused, refresh variables in thread if mod_pid.is_activated: control = DaemonControl() return_value = control.pid_mod( form_mod_pid_base.function_id.data) flash( "PID Controller settings refresh response: " "{resp}".format(resp=return_value), "success") except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
def loop(self): if self.timer_loop > time.time(): return while self.timer_loop < time.time(): self.timer_loop += self.period temp_c = None hum_percent = None vpd_pa = None last_measurement_temp = self.get_last_measurement( self.select_measurement_temperature_c_device_id, self.select_measurement_temperature_c_measurement_id, max_age=self.max_measure_age_temperature_c) self.logger.debug("Temp: {}".format(last_measurement_temp)) if last_measurement_temp: device_measurement = get_measurement( self.select_measurement_temperature_c_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) temp_c = convert_from_x_to_y_unit(unit, 'C', last_measurement_temp[1]) last_measurement_hum = self.get_last_measurement( self.select_measurement_humidity_device_id, self.select_measurement_humidity_measurement_id, max_age=self.max_measure_age_humidity) self.logger.debug("Hum: {}".format(last_measurement_hum)) if last_measurement_hum: device_measurement = get_measurement( self.select_measurement_humidity_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) hum_percent = convert_from_x_to_y_unit(unit, 'percent', last_measurement_hum[1]) if temp_c and hum_percent: measurement_dict = copy.deepcopy(measurements_dict) try: vpd_pa = calculate_vapor_pressure_deficit(temp_c, hum_percent) except TypeError as err: self.logger.error("Error: {msg}".format(msg=err)) if vpd_pa: dev_measurement = self.channels_measurement[0] channel, unit, measurement = return_measurement_info( dev_measurement, self.channels_conversion[0]) vpd_store = convert_from_x_to_y_unit('Pa', unit, vpd_pa) measurement_dict[0] = { 'measurement': measurement, 'unit': unit, 'value': vpd_store } # Add measurement(s) to influxdb if measurement_dict: self.logger.debug( "Adding measurements to InfluxDB with ID {}: {}".format( self.unique_id, measurement_dict)) add_measurements_influxdb(self.unique_id, measurement_dict) else: self.logger.debug( "No measurements to add to InfluxDB with ID {}".format( self.unique_id)) else: self.logger.debug( "Could not acquire both temperature and humidity measurements." )
def loop(self): if self.timer_loop > time.time(): return while self.timer_loop < time.time(): self.timer_loop += self.period device_measurement = get_measurement( self.select_measurement_measurement_id) if not device_measurement: self.logger.error("Could not find Device Measurement") return past_measurements = self.get_past_measurements( self.select_measurement_device_id, self.select_measurement_measurement_id, max_age=self.max_measure_age) self.logger.debug( "Past Measurements returned: {}".format(past_measurements)) if not past_measurements: self.logger.error( "Could not find measurements within the set Max Age") return False measure = [] for each_measure in past_measurements: measure.append(each_measure[1]) if len(measure) > 1: stat_mean = float(sum(measure) / float(len(measure))) stat_median = median(measure) stat_minimum = min(measure) stat_maximum = max(measure) stdev_ = stdev(measure) stdev_mean_upper = stat_mean + stdev_ stdev_mean_lower = stat_mean - stdev_ list_measurement = [ stat_mean, stat_median, stat_minimum, stat_maximum, stdev_, stdev_mean_upper, stdev_mean_lower ] for each_channel, each_measurement in self.channels_measurement.items( ): if each_measurement.is_enabled: channel, unit, measurement = return_measurement_info( each_measurement, self.channels_conversion[each_channel]) self.logger.debug( "Saving {} to channel {} with measurement {} and " "unit {}".format(list_measurement[each_channel], each_channel, measurement, unit)) write_influxdb_value(self.unique_id, unit, value=list_measurement[each_channel], measure=measurement, channel=each_channel) else: self.logger.debug( "Less than 2 measurements found within Max Age. " "Calculations need at least 2 measurements. Not calculating.")
def loop(self): if self.timer_loop > time.time(): return while self.timer_loop < time.time(): self.timer_loop += self.period measurements = [] for each_id_set in self.select_measurement: device_device_id = each_id_set.split(",")[0] device_measure_id = each_id_set.split(",")[1] device_measurement = get_measurement(device_measure_id) if not device_measurement: self.logger.error("Could not find Device Measurement") return conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) self.logger.debug("Dev: {}, unit: {}, channel: {}, measurement: {}, max age:; {}".format( device_device_id, unit, channel, measurement, self.max_measure_age)) last_measurement = read_influxdb_single( device_device_id, unit, channel, measure=measurement, duration_sec=self.max_measure_age, value='LAST') self.logger.debug("last_measurement: {}".format(last_measurement)) if not last_measurement: self.logger.error( "One more more measurements were not within the set Max Age. Not calculating average.") return False else: measurements.append(last_measurement[1]) average = float(sum(measurements) / float(len(measurements))) measurement_dict = { 0: { 'measurement': self.channels_measurement[0].measurement, 'unit': self.channels_measurement[0].unit, 'value': average } } if measurement_dict: self.logger.debug( "Adding measurements to InfluxDB with ID {}: {}".format( self.unique_id, measurement_dict)) add_measurements_influxdb(self.unique_id, measurement_dict) else: self.logger.debug( "No measurements to add to InfluxDB with ID {}".format( self.unique_id))
def pid_mod(form_mod_pid_base, form_mod_pid_pwm_raise, form_mod_pid_pwm_lower, form_mod_pid_output_raise, form_mod_pid_output_lower): action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['pid']['title']) error = [] if not form_mod_pid_base.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_base) mod_pid = PID.query.filter( PID.unique_id == form_mod_pid_base.function_id.data).first() mod_pid.name = form_mod_pid_base.name.data mod_pid.measurement = form_mod_pid_base.measurement.data mod_pid.direction = form_mod_pid_base.direction.data mod_pid.period = form_mod_pid_base.period.data mod_pid.log_level_debug = form_mod_pid_base.log_level_debug.data mod_pid.start_offset = form_mod_pid_base.start_offset.data mod_pid.max_measure_age = form_mod_pid_base.max_measure_age.data mod_pid.setpoint = form_mod_pid_base.setpoint.data mod_pid.band = abs(form_mod_pid_base.band.data) mod_pid.store_lower_as_negative = form_mod_pid_base.store_lower_as_negative.data mod_pid.p = form_mod_pid_base.k_p.data mod_pid.i = form_mod_pid_base.k_i.data mod_pid.d = form_mod_pid_base.k_d.data mod_pid.integrator_min = form_mod_pid_base.integrator_max.data mod_pid.integrator_max = form_mod_pid_base.integrator_min.data mod_pid.setpoint_tracking_type = form_mod_pid_base.setpoint_tracking_type.data if form_mod_pid_base.setpoint_tracking_type.data == 'method': mod_pid.setpoint_tracking_id = form_mod_pid_base.setpoint_tracking_method_id.data elif form_mod_pid_base.setpoint_tracking_type.data == 'input-math': mod_pid.setpoint_tracking_id = form_mod_pid_base.setpoint_tracking_input_math_id.data if form_mod_pid_base.setpoint_tracking_max_age.data: mod_pid.setpoint_tracking_max_age = form_mod_pid_base.setpoint_tracking_max_age.data else: mod_pid.setpoint_tracking_max_age = 120 else: mod_pid.setpoint_tracking_id = '' # Change measurement information if ',' in form_mod_pid_base.measurement.data: measurement_id = form_mod_pid_base.measurement.data.split(',')[1] selected_measurement = get_measurement(measurement_id) measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_pid_base.function_id.data).all() for each_measurement in measurements: # Only set channels 0, 1, 2 if each_measurement.channel in [0, 1, 2]: each_measurement.measurement = selected_measurement.measurement each_measurement.unit = selected_measurement.unit if form_mod_pid_base.raise_output_id.data: raise_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.raise_output_id.data).first().output_type if mod_pid.raise_output_id == form_mod_pid_base.raise_output_id.data: if raise_output_type in OUTPUTS_PWM: if not form_mod_pid_pwm_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_pwm_raise) else: mod_pid.raise_min_duration = form_mod_pid_pwm_raise.raise_min_duty_cycle.data mod_pid.raise_max_duration = form_mod_pid_pwm_raise.raise_max_duty_cycle.data else: if not form_mod_pid_output_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_output_raise) else: mod_pid.raise_min_duration = form_mod_pid_output_raise.raise_min_duration.data mod_pid.raise_max_duration = form_mod_pid_output_raise.raise_max_duration.data mod_pid.raise_min_off_duration = form_mod_pid_output_raise.raise_min_off_duration.data else: if raise_output_type in OUTPUTS_PWM: mod_pid.raise_min_duration = 2 mod_pid.raise_max_duration = 98 else: mod_pid.raise_min_duration = 0 mod_pid.raise_max_duration = 0 mod_pid.raise_min_off_duration = 0 mod_pid.raise_output_id = form_mod_pid_base.raise_output_id.data else: mod_pid.raise_output_id = None if form_mod_pid_base.lower_output_id.data: lower_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.lower_output_id.data).first().output_type if mod_pid.lower_output_id == form_mod_pid_base.lower_output_id.data: if lower_output_type in OUTPUTS_PWM: if not form_mod_pid_pwm_lower.validate(): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_pid_pwm_lower) else: mod_pid.lower_min_duration = form_mod_pid_pwm_lower.lower_min_duty_cycle.data mod_pid.lower_max_duration = form_mod_pid_pwm_lower.lower_max_duty_cycle.data else: if not form_mod_pid_output_lower.validate(): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_pid_output_lower) else: mod_pid.lower_min_duration = form_mod_pid_output_lower.lower_min_duration.data mod_pid.lower_max_duration = form_mod_pid_output_lower.lower_max_duration.data mod_pid.lower_min_off_duration = form_mod_pid_output_lower.lower_min_off_duration.data else: if lower_output_type in OUTPUTS_PWM: mod_pid.lower_min_duration = 2 mod_pid.lower_max_duration = 98 else: mod_pid.lower_min_duration = 0 mod_pid.lower_max_duration = 0 mod_pid.lower_min_off_duration = 0 mod_pid.lower_output_id = form_mod_pid_base.lower_output_id.data else: mod_pid.lower_output_id = None if (mod_pid.raise_output_id and mod_pid.lower_output_id and mod_pid.raise_output_id == mod_pid.lower_output_id): error.append(gettext("Raise and lower outputs cannot be the same")) try: if not error: db.session.commit() # If the controller is active or paused, refresh variables in thread if mod_pid.is_activated: control = DaemonControl() return_value = control.pid_mod( form_mod_pid_base.function_id.data) flash( "PID Controller settings refresh response: " "{resp}".format(resp=return_value), "success") except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
def loop(self): if self.timer_loop > time.time(): return while self.timer_loop < time.time(): self.timer_loop += self.period temp_wet_k = None temp_dry_k = None pressure_pa = 101325 if (self.select_measurement_pressure_pa_device_id and self.select_measurement_pressure_pa_measurement_id and self.max_measure_age_pressure_pa): device_measurement = get_measurement( self.select_measurement_pressure_pa_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) last_measurement_pa = self.get_last_measurement( self.select_measurement_pressure_pa_device_id, self.select_measurement_pressure_pa_measurement_id, max_age=self.max_measure_age_pressure_pa) if last_measurement_pa: pressure_pa = convert_from_x_to_y_unit(unit, 'Pa', last_measurement_pa[1]) last_measurement_wet = self.get_last_measurement( self.select_measurement_temp_wet_c_device_id, self.select_measurement_temp_wet_c_measurement_id, max_age=self.max_measure_age_temp_wet_c) if last_measurement_wet: device_measurement = get_measurement( self.select_measurement_temp_wet_c_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) temp_wet_k = convert_from_x_to_y_unit(unit, 'K', last_measurement_wet[1]) last_measurement_dry = self.get_last_measurement( self.select_measurement_temp_dry_c_device_id, self.select_measurement_temp_dry_c_measurement_id, max_age=self.max_measure_age_temp_dry_c) if last_measurement_dry: device_measurement = get_measurement( self.select_measurement_temp_dry_c_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) temp_dry_k = convert_from_x_to_y_unit(unit, 'K', last_measurement_dry[1]) if temp_wet_k and temp_dry_k: measurements = copy.deepcopy(measurements_dict) psypi = None try: psypi = SI.state("DBT", temp_dry_k, "WBT", temp_wet_k, pressure_pa) except TypeError as err: self.logger.error("TypeError: {msg}".format(msg=err)) if not psypi: self.logger.error( "Could not calculate humidity from wet/dry bulbs") return percent_relative_humidity = psypi[2] * 100 # Ensure percent humidity stays within 0 - 100 % range if percent_relative_humidity > 100: percent_relative_humidity = 100 elif percent_relative_humidity < 0: percent_relative_humidity = 0 # Dry bulb temperature: psypi[0]) # Wet bulb temperature: psypi[5]) specific_enthalpy = float(psypi[1]) humidity = float(percent_relative_humidity) specific_volume = float(psypi[3]) humidity_ratio = float(psypi[4]) self.logger.debug("Dry Temp: {dtk}, " "Wet Temp: {wtk}, " "Pressure: {pres}," "Humidity: {rh}".format(dtk=temp_dry_k, wtk=temp_wet_k, pres=pressure_pa, rh=humidity)) list_measurement = [ humidity, humidity_ratio, specific_enthalpy, specific_volume ] for each_channel, each_measurement in self.channels_measurement.items( ): if each_measurement.is_enabled: channel, unit, measurement = return_measurement_info( each_measurement, self.channels_conversion[each_channel]) measurements[channel] = { 'measurement': measurement, 'unit': unit, 'value': list_measurement[channel] } # Add measurement(s) to influxdb if measurements: self.logger.debug( "Adding measurements to InfluxDB with ID {}: {}".format( self.unique_id, measurements)) add_measurements_influxdb(self.unique_id, measurements) else: self.logger.debug( "No measurements to add to InfluxDB with ID {}".format( self.unique_id)) else: self.logger.debug( "One or more temperature measurements could not be found within the Max Age. Not calculating." )
def check_pid(self): """ Get measurement and apply to PID controller """ # If PID is active, retrieve measurement and update # the control variable. # A PID on hold will sustain the current output and # not update the control variable. if self.is_activated and (not self.is_paused or not self.is_held): self.get_last_measurement_pid() if self.last_measurement_success: if self.setpoint_tracking_type == 'method' and self.setpoint_tracking_id != '': # Update setpoint using a method this_pid = db_retrieve_table_daemon(PID, unique_id=self.unique_id) now = datetime.datetime.now() method = load_method_handler(self.setpoint_tracking_id, self.logger) new_setpoint, ended = method.calculate_setpoint(now, this_pid.method_start_time) self.logger.debug("Method {} {} {} {}".format( self.setpoint_tracking_id, method, now, this_pid.method_start_time)) if ended: # point in time is out of method range with session_scope(MYCODO_DB_PATH) as db_session: # Overwrite this_controller with committable connection this_pid = db_session.query(PID).filter( PID.unique_id == self.unique_id).first() self.logger.debug("Ended") # Duration method has ended, reset method_start_time locally and in DB this_pid.method_start_time = None this_pid.method_end_time = None this_pid.is_activated = False db_session.commit() self.is_activated = False self.stop_controller() db_session.commit() if new_setpoint is not None: self.logger.debug("New setpoint = {} {}".format(new_setpoint, ended)) self.PID_Controller.setpoint = new_setpoint else: self.logger.debug("New setpoint = default {} {}".format(self.setpoint, ended)) self.PID_Controller.setpoint = self.setpoint if self.setpoint_tracking_type == 'input-math' and self.setpoint_tracking_id != '': # Update setpoint using an Input or Math device_id = self.setpoint_tracking_id.split(',')[0] measurement_id = self.setpoint_tracking_id.split(',')[1] measurement = get_measurement(measurement_id) if not measurement: return False, None conversion = db_retrieve_table_daemon( Conversion, unique_id=measurement.conversion_id) channel, unit, measurement = return_measurement_info( measurement, conversion) last_measurement = read_last_influxdb( device_id, unit, channel, measure=measurement, duration_sec=self.setpoint_tracking_max_age) if last_measurement[1] is not None: self.PID_Controller.setpoint = last_measurement[1] else: self.logger.debug( "Could not find measurement for Setpoint " "Tracking. Max Age of {} exceeded for measuring " "device ID {} (measurement {})".format( self.setpoint_tracking_max_age, device_id, measurement_id)) self.PID_Controller.setpoint = None # Calculate new control variable (output) from PID Controller self.PID_Controller.update_pid_output(self.last_measurement) self.write_pid_values() # Write variables to database # Is PID in a state that allows manipulation of outputs if (self.is_activated and self.PID_Controller.setpoint is not None and (not self.is_paused or self.is_held)): self.manipulate_output()
def get_measurement(self): """Gets the sensor's pH measurement.""" if not self.atlas_device.setup: self.logger.error( "Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info." ) return ph = None self.return_dict = copy.deepcopy(measurements_dict) # Compensate measurement based on a temperature measurement if self.temperature_comp_meas_measurement_id and self.atlas_command: self.logger.debug("pH sensor set to calibrate temperature") last_measurement = self.get_last_measurement( self.temperature_comp_meas_device_id, self.temperature_comp_meas_measurement_id, max_age=self.max_age) if last_measurement and len(last_measurement) > 1: device_measurement = get_measurement( self.temperature_comp_meas_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) _, unit, _ = return_measurement_info(device_measurement, conversion) if unit != "C": out_value = convert_from_x_to_y_unit( unit, "C", last_measurement[1]) else: out_value = last_measurement[1] self.logger.debug( "Latest temperature used to calibrate: {temp}".format( temp=out_value)) ret_value, ret_msg = self.atlas_command.calibrate( 'temperature', set_amount=out_value) time.sleep(0.5) self.logger.debug("Calibration returned: {val}, {msg}".format( val=ret_value, msg=ret_msg)) else: self.logger.error( "Calibration measurement not found within the past {} seconds" .format(self.max_age)) # Read device atlas_status, atlas_return = self.atlas_device.query('R') self.logger.debug("Device Returned: {}: {}".format( atlas_status, atlas_return)) if atlas_status == 'error': self.logger.error( "Sensor read unsuccessful: {err}".format(err=atlas_return)) return # Parse device return data if self.interface in ['FTDI', 'UART']: # Find float value in list float_value = None for each_split in atlas_return: if str_is_float(each_split): float_value = each_split break if 'check probe' in atlas_return: self.logger.error('"check probe" returned from sensor') elif str_is_float(float_value): ph = float(float_value) self.logger.debug('Found float value: {val}'.format(val=ph)) else: self.logger.error( 'Value or "check probe" not found in list: {val}'.format( val=atlas_return)) elif self.interface == 'I2C': if ',' in atlas_return and str_is_float( atlas_return.split(',')[2]): ph = float(atlas_return.split(',')[2]) elif str_is_float(atlas_return): ph = float(atlas_return) else: self.logger.error( "Could not determine pH from returned value: '{}'".format( atlas_return)) self.value_set(0, ph) return self.return_dict
def pid_mod(form_mod_pid_base, form_mod_pid_pwm_raise, form_mod_pid_pwm_lower, form_mod_pid_output_raise, form_mod_pid_output_lower): action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['pid']['title']) error = [] if not form_mod_pid_base.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_base) mod_pid = PID.query.filter( PID.unique_id == form_mod_pid_base.function_id.data).first() # Check if a specific setting can be modified if the PID is active if mod_pid.is_activated: error = can_set_output( error, form_mod_pid_base.function_id.data, form_mod_pid_base.raise_output_id.data, form_mod_pid_base.lower_output_id.data) mod_pid.name = form_mod_pid_base.name.data mod_pid.measurement = form_mod_pid_base.measurement.data mod_pid.direction = form_mod_pid_base.direction.data mod_pid.period = form_mod_pid_base.period.data mod_pid.start_offset = form_mod_pid_base.start_offset.data mod_pid.max_measure_age = form_mod_pid_base.max_measure_age.data mod_pid.setpoint = form_mod_pid_base.setpoint.data mod_pid.band = abs(form_mod_pid_base.band.data) mod_pid.store_lower_as_negative = form_mod_pid_base.store_lower_as_negative.data mod_pid.p = form_mod_pid_base.k_p.data mod_pid.i = form_mod_pid_base.k_i.data mod_pid.d = form_mod_pid_base.k_d.data mod_pid.integrator_min = form_mod_pid_base.integrator_max.data mod_pid.integrator_max = form_mod_pid_base.integrator_min.data mod_pid.method_id = form_mod_pid_base.method_id.data # Change measurement information if ',' in form_mod_pid_base.measurement.data: measurement_id = form_mod_pid_base.measurement.data.split(',')[1] selected_measurement = get_measurement(measurement_id) measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_pid_base.function_id.data).all() for each_measurement in measurements: # Only set channels 0, 1, 2 if each_measurement.channel in [0, 1, 2]: each_measurement.measurement = selected_measurement.measurement each_measurement.unit = selected_measurement.unit if form_mod_pid_base.raise_output_id.data: raise_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.raise_output_id.data).first().output_type if mod_pid.raise_output_id == form_mod_pid_base.raise_output_id.data: if raise_output_type in ['pwm', 'command_pwm']: if not form_mod_pid_pwm_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_pwm_raise) else: mod_pid.raise_min_duration = form_mod_pid_pwm_raise.raise_min_duty_cycle.data mod_pid.raise_max_duration = form_mod_pid_pwm_raise.raise_max_duty_cycle.data else: if not form_mod_pid_output_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_output_raise) else: mod_pid.raise_min_duration = form_mod_pid_output_raise.raise_min_duration.data mod_pid.raise_max_duration = form_mod_pid_output_raise.raise_max_duration.data mod_pid.raise_min_off_duration = form_mod_pid_output_raise.raise_min_off_duration.data else: if raise_output_type in ['pwm', 'command_pwm']: mod_pid.raise_min_duration = 2 mod_pid.raise_max_duration = 98 else: mod_pid.raise_min_duration = 0 mod_pid.raise_max_duration = 0 mod_pid.raise_min_off_duration = 0 mod_pid.raise_output_id = form_mod_pid_base.raise_output_id.data else: mod_pid.raise_output_id = None if form_mod_pid_base.lower_output_id.data: lower_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.lower_output_id.data).first().output_type if mod_pid.lower_output_id == form_mod_pid_base.lower_output_id.data: if lower_output_type in ['pwm', 'command_pwm']: if not form_mod_pid_pwm_lower.validate(): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_pid_pwm_lower) else: mod_pid.lower_min_duration = form_mod_pid_pwm_lower.lower_min_duty_cycle.data mod_pid.lower_max_duration = form_mod_pid_pwm_lower.lower_max_duty_cycle.data else: if not form_mod_pid_output_lower.validate(): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_pid_output_lower) else: mod_pid.lower_min_duration = form_mod_pid_output_lower.lower_min_duration.data mod_pid.lower_max_duration = form_mod_pid_output_lower.lower_max_duration.data mod_pid.lower_min_off_duration = form_mod_pid_output_lower.lower_min_off_duration.data else: if lower_output_type in ['pwm', 'command_pwm']: mod_pid.lower_min_duration = 2 mod_pid.lower_max_duration = 98 else: mod_pid.lower_min_duration = 0 mod_pid.lower_max_duration = 0 mod_pid.lower_min_off_duration = 0 mod_pid.lower_output_id = form_mod_pid_base.lower_output_id.data else: mod_pid.lower_output_id = None if (mod_pid.raise_output_id and mod_pid.lower_output_id and mod_pid.raise_output_id == mod_pid.lower_output_id): error.append(gettext("Raise and lower outputs cannot be the same")) try: if not error: db.session.commit() # If the controller is active or paused, refresh variables in thread if mod_pid.is_activated: control = DaemonControl() return_value = control.pid_mod(form_mod_pid_base.function_id.data) flash("PID Controller settings refresh response: " "{resp}".format(resp=return_value), "success") except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
def get_measurement(self): """ Gets the sensor's pH measurement """ if not self.atlas_device.setup: self.logger.error("Input not set up") return ph = None self.return_dict = copy.deepcopy(measurements_dict) # Compensate measurement based on a temperature measurement if self.temperature_comp_meas_measurement_id and self.atlas_command: self.logger.debug("pH sensor set to calibrate temperature") device_measurement = get_measurement( self.temperature_comp_meas_measurement_id) conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) last_measurement = self.get_last_measurement( self.temperature_comp_meas_device_id, self.temperature_comp_meas_measurement_id, max_age=self.max_age) out_value = convert_from_x_to_y_unit(unit, "C", last_measurement[1]) if last_measurement: self.logger.debug( "Latest temperature used to calibrate: {temp}".format( temp=out_value)) ret_value, ret_msg = self.atlas_command.calibrate( 'temperature', set_amount=out_value) time.sleep(0.5) self.logger.debug("Calibration returned: {val}, {msg}".format( val=ret_value, msg=ret_msg)) else: self.logger.error( "Calibration measurement not found within the past {} seconds" .format(self.max_age)) # Read sensor via FTDI or UART if self.interface in ['FTDI', 'UART']: ph_status, ph_list = self.atlas_device.query('R') if ph_list: self.logger.debug( "Returned list: {lines}".format(lines=ph_list)) # Find float value in list float_value = None for each_split in ph_list: if str_is_float(each_split): float_value = each_split break if 'check probe' in ph_list: self.logger.error('"check probe" returned from sensor') elif str_is_float(float_value): ph = float(float_value) self.logger.debug('Found float value: {val}'.format(val=ph)) else: self.logger.error( 'Value or "check probe" not found in list: {val}'.format( val=ph_list)) # Read sensor via I2C elif self.interface == 'I2C': ph_status, ph_str = self.atlas_device.query('R') if ph_status == 'error': self.logger.error( "Sensor read unsuccessful: {err}".format(err=ph_str)) elif ph_status == 'success': if ',' in ph_str and str_is_float(ph_str.split(',')[2]): ph = float(ph_str.split(',')[2]) elif str_is_float(ph_str): ph = float(ph_str) else: self.logger.error( "Could not determine pH from returned string: '{}'". format(ph_str)) self.value_set(0, ph) return self.return_dict