def get(self, unique_id, unit, channel, past_seconds): """ Return the last measurement found within a duration from the past to the present """ if not utils_general.user_has_permission('view_settings'): abort(403) if unit not in add_custom_units(Unit.query.all()): abort(422, custom='Unit ID not found') if channel < 0: abort(422, custom='channel must be >= 0') if past_seconds < 1: abort(422, custom='past_seconds must be >= 1') try: return_ = read_influxdb_single(unique_id, unit, channel, duration_sec=past_seconds) if return_ and len(return_) == 2: return {'time': return_[0], 'value': return_[1]}, 200 else: return return_, 200 except Exception: abort(500, message='An exception occurred', error=traceback.format_exc())
def test_influxdb(): """Verify measurements can be written and read from influxdb.""" print("\nTest: test_influxdb") device_id = 'ID_ASDF' channel = 0 measurement = 'duty_cycle' unit = 'percent' written_measurement = 123.45 measurements_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': written_measurement } } add_measurements_influxdb(device_id, measurements_dict, block=True) last_measurement = read_influxdb_single( device_id, unit, 0, measure=measurement, duration_sec=1000, value='LAST') print(f"Returned measurement: {last_measurement}") assert last_measurement returned_measurement = last_measurement[1] assert returned_measurement == written_measurement
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_influxdb_single( self.device_id, unit, channel, measure=measurement, duration_sec=int(self.max_measure_age), value='LAST') 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( f"Latest (CH{channel}, Unit: {unit}): {self.last_measurement} @ {local_timestamp}" ) if calendar.timegm( time.gmtime()) - utc_timestamp > self.max_measure_age: sec = calendar.timegm(time.gmtime()) - utc_timestamp self.logger.error( f"Last measurement was {sec} seconds ago, however the maximum " f"measurement age is set to {self.max_measure_age} seconds." ) 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: self.logger.exception( "Exception while reading measurement from the influxdb database" )
def initialize(self): self.setup_output_variables(OUTPUT_INFORMATION) if not self.options_channels['pwm_command'][0]: self.logger.error("Output must have Python Code set") return try: full_command, file_run = generate_code( self.options_channels['pwm_command'][0], self.unique_id) module_name = "mycodo.output.{}".format( os.path.basename(file_run).split('.')[0]) spec = importlib.util.spec_from_file_location( module_name, file_run) output_run_pwm = importlib.util.module_from_spec(spec) spec.loader.exec_module(output_run_pwm) self.output_run_python_pwm = output_run_pwm.OutputRun( self.logger, self.unique_id) self.output_setup = True if self.options_channels['state_startup'][0] == 0: self.output_switch('off') elif self.options_channels['state_startup'][0] == 'set_duty_cycle': self.output_switch( 'on', amount=self.options_channels['startup_value'][0]) elif self.options_channels['state_startup'][ 0] == 'last_duty_cycle': device_measurement = db_retrieve_table_daemon( DeviceMeasurements).filter( and_(DeviceMeasurements.device_id == self.unique_id, DeviceMeasurements.channel == 0)).first() last_measurement = None if device_measurement: channel, unit, measurement = return_measurement_info( device_measurement, None) last_measurement = read_influxdb_single( self.unique_id, unit, channel, measure=measurement, value='LAST') if last_measurement: self.logger.info( "Setting startup duty cycle to last known value of {dc} %" .format(dc=last_measurement[1])) self.output_switch('on', amount=last_measurement[1]) else: self.logger.error( "Output instructed at startup to be set to " "the last known duty cycle, but a last known " "duty cycle could not be found in the measurement " "database") except Exception: self.logger.exception("Could not set up output")
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) last_measurement = read_influxdb_single( device_device_id, unit, channel, measure=measurement, duration_sec=self.max_measure_age, value='LAST') if not last_measurement: self.logger.error( "Could not find measurement within the set Max Age") return False else: measurements.append(last_measurement[1]) sum_measurements = float(sum(measurements)) measurement_dict = { 0: { 'measurement': self.channels_measurement[0].measurement, 'unit': self.channels_measurement[0].unit, 'value': sum_measurements } } 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 initialize(self): self.setup_output_variables(OUTPUT_INFORMATION) if self.options_channels['pwm_command'][0]: self.output_setup = True if self.options_channels['state_startup'][0] == 0: self.output_switch('off') elif self.options_channels['state_startup'][0] == 'set_duty_cycle': self.output_switch( 'on', amount=self.options_channels['startup_value'][0]) elif self.options_channels['state_startup'][ 0] == 'last_duty_cycle': device_measurement = db_retrieve_table_daemon( DeviceMeasurements).filter( and_(DeviceMeasurements.device_id == self.unique_id, DeviceMeasurements.channel == 0)).first() last_measurement = None if device_measurement: channel, unit, measurement = return_measurement_info( device_measurement, None) last_measurement = read_influxdb_single( self.unique_id, unit, channel, measure=measurement, value='LAST') if last_measurement: self.logger.info( "Setting startup duty cycle to last known value of {dc} %" .format(dc=last_measurement[1])) self.output_switch('on', amount=last_measurement[1]) else: self.logger.error( "Output instructed at startup to be set to " "the last known duty cycle, but a last known " "duty cycle could not be found in the measurement " "database") else: self.logger.error("Output must have command set")
def initialize(self): import pigpio self.pigpio = pigpio self.setup_output_variables(OUTPUT_INFORMATION) error = [] if self.options_channels['pin'][0] is None: error.append("Pin must be set") if self.options_channels['pwm_hertz'][0] <= 0: error.append("PWM Hertz must be a positive value") if error: for each_error in error: self.logger.error(each_error) return try: self.pwm_output = self.pigpio.pi() if not self.pwm_output.connected: self.logger.error("Could not connect to pigpiod") self.pwm_output = None return if self.options_channels['pwm_library'][0] == 'pigpio_hardware': self.pwm_output.hardware_PWM( self.options_channels['pin'][0], self.options_channels['pwm_hertz'][0], 0) self.logger.info( "Output setup on Hardware pin {pin} at {hz} Hertz".format( pin=self.options_channels['pin'][0], hz=self.options_channels['pwm_hertz'][0])) elif self.options_channels['pwm_library'][0] == 'pigpio_any': self.pwm_output.set_PWM_frequency( self.options_channels['pin'][0], self.options_channels['pwm_hertz'][0]) self.logger.info( "Output setup on Any pin {pin} at {hz} Hertz".format( pin=self.options_channels['pin'][0], hz=self.options_channels['pwm_hertz'][0])) self.output_setup = True state_string = "" if self.options_channels['state_startup'][0] == 0: self.output_switch('off') self.logger.info("PWM turned off (0 % duty cycle)") elif self.options_channels['state_startup'][0] == 'set_duty_cycle': self.output_switch( 'on', amount=self.options_channels['startup_value'][0]) self.logger.info( "PWM set to {} % duty cycle (user-specified value)".format( self.options_channels['startup_value'][0])) elif self.options_channels['state_startup'][ 0] == 'last_duty_cycle': device_measurement = db_retrieve_table_daemon( DeviceMeasurements).filter( and_(DeviceMeasurements.device_id == self.unique_id, DeviceMeasurements.channel == 0)).first() last_measurement = None if device_measurement: channel, unit, measurement = return_measurement_info( device_measurement, None) last_measurement = read_influxdb_single( self.unique_id, unit, channel, measure=measurement, value='LAST') if last_measurement: self.logger.info( "PWM set to {} % duty cycle (last known value)".format( last_measurement[1])) self.output_switch('on', amount=last_measurement[1]) else: self.logger.error( "Output instructed at startup to be set to " "the last known duty cycle, but a last known " "duty cycle could not be found in the measurement " "database") except Exception as except_msg: self.logger.exception( "Output was unable to be setup on pin {pin}: {err}".format( pin=self.options_channels['pin'][0], err=except_msg))
def initialize(self): import Adafruit_PCA9685 self.setup_output_variables(OUTPUT_INFORMATION) try: self.pwm_output = Adafruit_PCA9685.PCA9685( address=int(str(self.output.i2c_location), 16), busnum=self.output.i2c_bus) self.pwm_output.set_pwm_freq(self.pwm_hertz) self.output_setup = True self.logger.debug("Output setup on bus {} at {}".format( self.output.i2c_bus, self.output.i2c_location)) for i in range(16): if self.options_channels['state_startup'][i] == 0: self.logger.debug( "Startup state channel {ch}: off".format(ch=i)) self.output_switch('off', output_channel=i) elif self.options_channels['state_startup'][ i] == 'set_duty_cycle': self.logger.debug( "Startup state channel {ch}: on ({dc:.2f} %)".format( ch=i, dc=self.options_channels['startup_value'][i])) self.output_switch( 'on', output_channel=i, amount=self.options_channels['startup_value'][i]) elif self.options_channels['state_startup'][ i] == 'last_duty_cycle': self.logger.debug( "Startup state channel {ch}: last".format(ch=i)) device_measurement = db_retrieve_table_daemon( DeviceMeasurements).filter( and_( DeviceMeasurements.device_id == self.unique_id, DeviceMeasurements.channel == i)).first() last_measurement = None if device_measurement: channel, unit, measurement = return_measurement_info( device_measurement, None) last_measurement = read_influxdb_single( self.unique_id, unit, channel, measure=measurement, value='LAST') if last_measurement: self.logger.debug( "Setting channel {ch} startup duty cycle to last known value of {dc} %" .format(ch=i, dc=last_measurement[1])) self.output_switch('on', amount=last_measurement[1]) else: self.logger.error( "Output channel {} instructed at startup to be set to " "the last known duty cycle, but a last known " "duty cycle could not be found in the measurement " "database".format(i)) else: self.logger.debug( "Startup state channel {ch}: no change".format(ch=i)) except Exception as except_msg: self.logger.exception( "Output was unable to be setup: {err}".format(err=except_msg))
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( f"Method {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( f"New setpoint = {new_setpoint} {ended}") self.PID_Controller.setpoint = new_setpoint else: self.logger.debug( f"New setpoint = default {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 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_influxdb_single( device_id, unit, channel, measure=measurement, duration_sec=self.setpoint_tracking_max_age, value='LAST') if last_measurement[1] is not None: self.PID_Controller.setpoint = last_measurement[1] else: self.logger.debug( "Could not find measurement for Setpoint " f"Tracking. Max Age of {self.setpoint_tracking_max_age} exceeded for measuring " f"device ID {device_id} (measurement {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 loop(self): if self.timer_loop > time.time(): return 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_influxdb_single( device_device_id, unit, channel, measure=measurement, duration_sec=self.max_measure_age, value='LAST') 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.")