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(unique_id, unit, measurement, channel, duration_sec): """ Retrieve the latest input measurement :return: The latest input value or None if no data available :rtype: float or None :param unique_id: What unique_id tag to query in the Influxdb database (eg. '00000001') :type unique_id: str :param unit: What unit to query in the Influxdb database (eg. 'C', 's') :type unit: str :param measurement: What measurement to query in the Influxdb database (eg. 'temperature', 'duration_time') :type measurement: str or None :param channel: Channel :type channel: int or None :param duration_sec: How many seconds to look for a past measurement :type duration_sec: int or None """ last_measurement = read_last_influxdb( unique_id, unit, measurement, channel, duration_sec=duration_sec) if last_measurement is not None: last_value = last_measurement[1] return last_value
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 get_last_measurement(unique_id, measurement, duration_sec): """ Retrieve the latest input measurement :return: The latest input value or None if no data available :rtype: float or None :param unique_id: ID of controller :type unique_id: str :param measurement: Environmental condition of a input (e.g. temperature, humidity, pressure, etc.) :type measurement: str :param duration_sec: number of seconds to check for a measurement in the past. :type duration_sec: int """ last_measurement = read_last_influxdb(unique_id, measurement, duration_sec=duration_sec) if last_measurement is not None: last_value = last_measurement[1] return last_value
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)) return False else: measure.append(last_measurement[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_measurement in self.device_measurements.all(): if each_measurement.is_enabled: conversion = db_retrieve_table_daemon( Conversion, unique_id=each_measurement.conversion_id) channel, unit, measurement = return_measurement_info( each_measurement, conversion) write_influxdb_value(self.unique_id, unit, value=list_measurement[channel], measure=measurement, channel=0)
def setup_output(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_last_influxdb(self.unique_id, unit, channel, measure=measurement, duration_sec=None) 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 get_measurement(self, display_id, i): try: if self.lcd_line[display_id][i]['measure'] == 'BLANK': self.lcd_line[display_id][i]['name'] = '' self.lcd_line[display_id][i]['unit'] = '' self.lcd_line[display_id][i]['measure_val'] = '' return True elif self.lcd_line[display_id][i]['measure'] == 'IP': str_ip_cmd = "ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/'" ip_out, _, _ = cmd_output(str_ip_cmd) self.lcd_line[display_id][i]['name'] = '' self.lcd_line[display_id][i]['unit'] = '' self.lcd_line[display_id][i]['measure_val'] = ip_out.rstrip( ).decode("utf-8") return True elif self.lcd_line[display_id][i]['measure'] == 'output_state': self.lcd_line[display_id][i][ 'measure_val'] = self.output_state( self.lcd_line[display_id][i]['id']) return True else: if self.lcd_line[display_id][i]['measure'] == 'time': last_measurement = read_last_influxdb( self.lcd_line[display_id][i]['id'], '/.*/', None, None, duration_sec=self.lcd_max_age[display_id][i]) else: last_measurement = read_last_influxdb( self.lcd_line[display_id][i]['id'], self.lcd_line[display_id][i]['unit'], self.lcd_line[display_id][i]['measure'], self.lcd_line[display_id][i]['channel'], duration_sec=self.lcd_max_age[display_id][i]) if last_measurement: self.lcd_line[display_id][i]['time'] = last_measurement[0] if self.lcd_decimal_places[display_id][i] == 0: self.lcd_line[display_id][i]['measure_val'] = int( last_measurement[1]) else: self.lcd_line[display_id][i]['measure_val'] = round( last_measurement[1], self.lcd_decimal_places[display_id][i]) utc_dt = datetime.datetime.strptime( self.lcd_line[display_id][i]['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 {}: {} @ {}".format( self.lcd_line[display_id][i]['measure'], self.lcd_line[display_id][i]['measure_val'], local_timestamp)) return True else: self.lcd_line[display_id][i]['time'] = None self.lcd_line[display_id][i]['measure_val'] = None self.logger.debug("No data returned from influxdb") return False except Exception as except_msg: self.logger.debug( "Failed to read measurement from the influxdb database: " "{err}".format(err=except_msg)) return False
def get_measurements_from_id(self, measure_id, measure_name): measurement = read_last_influxdb(measure_id, measure_name, self.max_measure_age) if not measurement: return False, None return True, measurement
def get_measurement(self): """ Gets the sensor's pH measurement via UART/I2C """ ph = None return_dict = measurements_dict.copy() # Calibrate the pH measurement based on a temperature measurement if (self.calibrate_sensor_measure and ',' in self.calibrate_sensor_measure): self.logger.debug("pH sensor set to calibrate temperature") device_id = self.calibrate_sensor_measure.split(',')[0] measurement_id = self.calibrate_sensor_measure.split(',')[1] device_measurement = self.device_measurements.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, measurement.unit, measurement.measurement, measurement.channel, self.max_age) if last_measurement: self.logger.debug( "Latest temperature used to calibrate: {temp}".format( temp=last_measurement[1])) atlas_command = AtlasScientificCommand(self.input_dev) ret_value, ret_msg = atlas_command.calibrate( 'temperature', temperature=last_measurement[1]) 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 if self.interface == 'FTDI': if self.atlas_sensor_ftdi.setup: lines = self.atlas_sensor_ftdi.query('R') if lines: self.logger.debug( "All Lines: {lines}".format(lines=lines)) # 'check probe' indicates an error reading the sensor if 'check probe' in lines: self.logger.error( '"check probe" returned from sensor') # if a string resembling a float value is returned, this # is out measurement value elif str_is_float(lines[0]): ph = float(lines[0]) self.logger.debug( 'Value[0] is float: {val}'.format(val=ph)) else: # During calibration, the sensor is put into # continuous mode, which causes a return of several # values in one string. If the return value does # not represent a float value, it is likely to be a # string of several values. This parses and returns # the first value. if str_is_float(lines[0].split(b'\r')[0]): ph = lines[0].split(b'\r')[0] # Lastly, this is called if the return value cannot # be determined. Watchthe output in the GUI to see # what it is. else: ph = lines[0] self.logger.error( 'Value[0] is not float or "check probe": ' '{val}'.format(val=ph)) else: self.logger.error('FTDI device is not set up.' 'Check the log for errors.') # Read sensor via UART elif self.interface == 'UART': if self.atlas_sensor_uart.setup: lines = self.atlas_sensor_uart.query('R') if lines: self.logger.debug( "All Lines: {lines}".format(lines=lines)) # 'check probe' indicates an error reading the sensor if 'check probe' in lines: self.logger.error( '"check probe" returned from sensor') # if a string resembling a float value is returned, this # is out measurement value elif str_is_float(lines[0]): ph = float(lines[0]) self.logger.debug( 'Value[0] is float: {val}'.format(val=ph)) else: # During calibration, the sensor is put into # continuous mode, which causes a return of several # values in one string. If the return value does # not represent a float value, it is likely to be a # string of several values. This parses and returns # the first value. if str_is_float(lines[0].split(b'\r')[0]): ph = lines[0].split(b'\r')[0] # Lastly, this is called if the return value cannot # be determined. Watchthe output in the GUI to see # what it is. else: ph = lines[0] self.logger.error( 'Value[0] is not float or "check probe": ' '{val}'.format(val=ph)) else: self.logger.error('UART device is not set up.' 'Check the log for errors.') # Read sensor via I2C elif self.interface == 'I2C': if self.atlas_sensor_i2c.setup: ph_status, ph_str = self.atlas_sensor_i2c.query('R') if ph_status == 'error': self.logger.error( "Sensor read unsuccessful: {err}".format( err=ph_str)) elif ph_status == 'success': ph = float(ph_str) else: self.logger.error( 'I2C device is not set up. Check the log for errors.') return_dict[0]['value'] = ph return return_dict
def get_measurement(self, display_id, i): try: if self.lcd_line[display_id][i]['measure'] == 'TEXT': # Display custom, user-entered text self.lcd_line[display_id][i]['name'] = '' self.lcd_line[display_id][i]['unit'] = '' if len(self.lcd_text[display_id][i]) > self.lcd_x_characters: self.lcd_line[display_id][i]['measure_val'] = self.lcd_text[display_id][i][:self.lcd_x_characters] else: self.lcd_line[display_id][i]['measure_val'] = self.lcd_text[display_id][i] return True elif self.lcd_line[display_id][i]['measure'] == 'BLANK': # Display an empty line self.lcd_line[display_id][i]['name'] = '' self.lcd_line[display_id][i]['unit'] = '' self.lcd_line[display_id][i]['measure_val'] = '' return True elif self.lcd_line[display_id][i]['measure'] == 'IP': # Display the device's IP address str_ip_cmd = "ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/'" ip_out, _, _ = cmd_output(str_ip_cmd) self.lcd_line[display_id][i]['name'] = '' self.lcd_line[display_id][i]['unit'] = '' self.lcd_line[display_id][i]['measure_val'] = ip_out.rstrip().decode("utf-8") return True elif self.lcd_line[display_id][i]['measure'] == 'output_state': # Display the GPIO state # TODO: Currently not a selectable option in the LCD Line dropdown self.lcd_line[display_id][i]['measure_val'] = self.output_state( self.lcd_line[display_id][i]['id']) return True else: if self.lcd_line[display_id][i]['measure'] == 'time': # Display the time of the last measurement last_measurement = read_last_influxdb( self.lcd_line[display_id][i]['id'], '/.*/', None, duration_sec=self.lcd_max_age[display_id][i]) else: last_measurement = read_last_influxdb( self.lcd_line[display_id][i]['id'], self.lcd_line[display_id][i]['unit'], self.lcd_line[display_id][i]['channel'], measure=self.lcd_line[display_id][i]['measure'], duration_sec=self.lcd_max_age[display_id][i]) if last_measurement: self.lcd_line[display_id][i]['time'] = last_measurement[0] if self.lcd_decimal_places[display_id][i] == 0: self.lcd_line[display_id][i]['measure_val'] = int(last_measurement[1]) else: self.lcd_line[display_id][i]['measure_val'] = round( last_measurement[1], self.lcd_decimal_places[display_id][i]) utc_dt = datetime.datetime.strptime( self.lcd_line[display_id][i]['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 {}: {} @ {}".format( self.lcd_line[display_id][i]['measure'], self.lcd_line[display_id][i]['measure_val'], local_timestamp)) return True else: self.lcd_line[display_id][i]['time'] = None self.lcd_line[display_id][i]['measure_val'] = None self.logger.debug("No data returned from influxdb") return False except Exception as except_msg: self.logger.debug( "Failed to read measurement from the influxdb database: " "{err}".format(err=except_msg)) return False
def setup_output(self): import Adafruit_PCA9685 self.setup_output_variables(OUTPUT_INFORMATION) error = [] if self.pwm_hertz < 40: error.append("PWM Hertz must be a value between 40 and 1600") if error: for each_error in error: self.logger.error(each_error) return 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_last_influxdb( self.unique_id, unit, channel, measure=measurement, duration_sec=None) 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) 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 last_measurement = read_last_influxdb( device_id, measurement.unit, measurement.channel, measure=measurement.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 # If autotune activated, determine control variable (output) from autotune if self.autotune_activated: if not self.autotune.run(self.last_measurement): self.PID_Controller.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 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 all_outputs_set_state(self): """Turn all outputs on that are set to be on at startup""" for each_output_id in self.output_unique_id: try: if self.output_state_startup[each_output_id] is None: pass # Don't turn on or off # PWM Outputs elif self.output_type[each_output_id] in self.output_types[ 'pwm']: if self.output_state_startup[each_output_id] == '0': self.logger.info( "Setting Output {id} startup duty cycle to 0 %". format(id=each_output_id.split('-')[0])) self.output_on_off( each_output_id, 'off', trigger_conditionals=self. trigger_functions_at_start[each_output_id]) elif self.output_state_startup[ each_output_id] == 'set_duty_cycle': self.logger.info( "Setting Output {id} startup duty cycle to user-set value of {dc} %" .format( id=each_output_id.split('-')[0], dc=self.output_startup_value[each_output_id])) self.output_on_off( each_output_id, 'on', output_type='pwm', amount=self.output_startup_value[each_output_id], trigger_conditionals=self. trigger_functions_at_start[each_output_id]) elif self.output_state_startup[ each_output_id] == 'last_duty_cycle': device_measurement = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.device_id == each_output_id).all() measurement = None for each_meas in device_measurement: if each_meas.measurement == 'duty_cycle': measurement = each_meas channel, unit, measurement = return_measurement_info( measurement, None) last_measurement = read_last_influxdb( each_output_id, unit, channel, measure=measurement, duration_sec=None) if last_measurement: self.logger.info( "Setting Output {id} startup duty cycle to last known value of {dc} %" .format(id=each_output_id.split('-')[0], dc=last_measurement[1])) self.output_on_off( each_output_id, 'on', output_type='pwm', amount=last_measurement[1], trigger_conditionals=self. trigger_functions_at_start[each_output_id]) else: self.logger.error( "Output {id} 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( id=each_output_id.split('-')[0])) # Non-PWM outputs elif self.output_state_startup[each_output_id] == '1': self.logger.info( "Setting Output {id} startup state to ON".format( id=each_output_id.split('-')[0])) self.output_on_off( each_output_id, 'on', trigger_conditionals=self. trigger_functions_at_start[each_output_id]) elif self.output_state_startup[each_output_id] == '0': self.logger.info( "Setting Output {id} startup state to OFF".format( id=each_output_id.split('-')[0])) self.output_on_off(each_output_id, 'off', trigger_conditionals=False) except: self.logger.error( "Could not set state for output {}".format(each_output_id))
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 via UART/I2C """ self._ph = None ph = None # Calibrate the pH measurement based on a temperature measurement if (self.calibrate_sensor_measure and ',' in self.calibrate_sensor_measure): self.logger.debug("pH sensor set to calibrate temperature") device_id = self.calibrate_sensor_measure.split(',')[0] measurement = self.calibrate_sensor_measure.split(',')[1] last_measurement = read_last_influxdb(device_id, measurement, duration_sec=300) if last_measurement: self.logger.debug( "Latest temperature used to calibrate: {temp}".format( temp=last_measurement[1])) atlas_command = AtlasScientificCommand(self.input_dev) ret_value, ret_msg = atlas_command.calibrate( 'temperature', temperature=last_measurement[1]) time.sleep(0.5) self.logger.debug("Calibration returned: {val}, {msg}".format( val=ret_value, msg=ret_msg)) # Read sensor via UART if self.interface == 'UART': if self.atlas_sensor_uart.setup: lines = self.atlas_sensor_uart.query('R') if lines: self.logger.debug("All Lines: {lines}".format(lines=lines)) # 'check probe' indicates an error reading the sensor if 'check probe' in lines: self.logger.error('"check probe" returned from sensor') # if a string resembling a float value is returned, this # is out measurement value elif str_is_float(lines[0]): ph = float(lines[0]) self.logger.debug( 'Value[0] is float: {val}'.format(val=ph)) else: # During calibration, the sensor is put into # continuous mode, which causes a return of several # values in one string. If the return value does # not represent a float value, it is likely to be a # string of several values. This parses and returns # the first value. if str_is_float(lines[0].split(b'\r')[0]): ph = lines[0].split(b'\r')[0] # Lastly, this is called if the return value cannot # be determined. Watchthe output in the GUI to see # what it is. else: ph = lines[0] self.logger.error( 'Value[0] is not float or "check probe": ' '{val}'.format(val=ph)) else: self.logger.error('UART device is not set up.' 'Check the log for errors.') # Read sensor via I2C elif self.interface == 'I2C': if self.atlas_sensor_i2c.setup: ph_status, ph_str = self.atlas_sensor_i2c.query('R') if ph_status == 'error': self.logger.error( "Sensor read unsuccessful: {err}".format(err=ph_str)) elif ph_status == 'success': ph = float(ph_str) else: self.logger.error( 'I2C device is not set up. Check the log for errors.') return ph
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 get_measurement(self): """ Gets the sensor's pH measurement via UART/I2C """ self._ph = None try: if ',' in self.sensor_sel.calibrate_sensor_measure: logger.debug("pH sensor set to calibrate temperature") device_id = self.sensor_sel.calibrate_sensor_measure.split( ',')[0] measurement = self.sensor_sel.calibrate_sensor_measure.split( ',')[1] last_measurement = read_last_influxdb(device_id, measurement, duration_sec=300) if last_measurement: logger.debug( "Latest temperature used to calibrate: {temp}".format( temp=last_measurement[1])) atlas_command = AtlasScientificCommand(self.sensor_sel) ret_value, ret_msg = atlas_command.calibrate( 'temperature', temperature=last_measurement[1]) time.sleep(0.5) logger.debug("Calibration returned: {val}, {msg}".format( val=ret_value, msg=ret_msg)) ph = None if self.interface == 'UART': if self.atlas_sensor_uart.setup: lines = self.atlas_sensor_uart.query('R') if lines: logger.debug("All Lines: {lines}".format(lines=lines)) if 'check probe' in lines: logger.error('"check probe" returned from sensor') elif str_is_float(lines[0]): ph = float(lines[0]) logger.debug( 'Value[0] is float: {val}'.format(val=ph)) else: ph = lines[0] logger.error( 'Value[0] is not float or "check probe": ' '{val}'.format(val=ph)) else: logger.error('UART device is not set up.' 'Check the log for errors.') elif self.interface == 'I2C': if self.atlas_sensor_i2c.setup: ph_status, ph_str = self.atlas_sensor_i2c.query('R') if ph_status == 'error': logger.error("Sensor read unsuccessful: {err}".format( err=ph_str)) elif ph_status == 'success': ph = float(ph_str) else: logger.error('I2C device is not set up.' 'Check the log for errors.') return ph except: logger.exception(1)
def get_measurement(self, display_id, i): try: if self.lcd_line[display_id][i]['measure'] == 'BLANK': self.lcd_line[display_id][i]['name'] = '' self.lcd_line[display_id][i]['unit'] = '' self.lcd_line[display_id][i]['measure_val'] = '' return True elif self.lcd_line[display_id][i]['measure'] == 'IP': str_ip_cmd = "ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/'" ip_out, _, _ = cmd_output(str_ip_cmd) self.lcd_line[display_id][i]['name'] = '' self.lcd_line[display_id][i]['unit'] = '' self.lcd_line[display_id][i]['measure_val'] = ip_out.rstrip().decode("utf-8") return True elif self.lcd_line[display_id][i]['measure'] == 'output_state': self.lcd_line[display_id][i]['measure_val'] = self.output_state( self.lcd_line[display_id][i]['id']) return True else: if self.lcd_line[display_id][i]['measure'] == 'time': last_measurement = read_last_influxdb( self.lcd_line[display_id][i]['id'], '/.*/', None, None, duration_sec=self.lcd_max_age[display_id][i]) else: last_measurement = read_last_influxdb( self.lcd_line[display_id][i]['id'], self.lcd_line[display_id][i]['unit'], self.lcd_line[display_id][i]['measure'], self.lcd_line[display_id][i]['channel'], duration_sec=self.lcd_max_age[display_id][i]) if last_measurement: self.lcd_line[display_id][i]['time'] = last_measurement[0] if self.lcd_decimal_places[display_id][i] == 0: self.lcd_line[display_id][i]['measure_val'] = int(last_measurement[1]) else: self.lcd_line[display_id][i]['measure_val'] = round( last_measurement[1], self.lcd_decimal_places[display_id][i]) utc_dt = datetime.datetime.strptime( self.lcd_line[display_id][i]['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 {}: {} @ {}".format( self.lcd_line[display_id][i]['measure'], self.lcd_line[display_id][i]['measure_val'], local_timestamp)) return True else: self.lcd_line[display_id][i]['time'] = None self.lcd_line[display_id][i]['measure_val'] = None self.logger.debug("No data returned from influxdb") return False except Exception as except_msg: self.logger.debug( "Failed to read measurement from the influxdb database: " "{err}".format(err=except_msg)) return False
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_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 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, 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))