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 setup_lcd_line(self, display_id, line, device_id, measurement_id): if measurement_id == 'output': device_measurement = db_retrieve_table_daemon( Output, unique_id=device_id) elif measurement_id in ['BLANK', 'IP']: device_measurement = None else: device_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=measurement_id) if device_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) else: channel = None unit = None measurement = None self.lcd_line[display_id][line]['setup'] = False self.lcd_line[display_id][line]['id'] = device_id self.lcd_line[display_id][line]['name'] = None self.lcd_line[display_id][line]['unit'] = unit self.lcd_line[display_id][line]['measure'] = measurement self.lcd_line[display_id][line]['channel'] = channel if 'time' in measurement_id: self.lcd_line[display_id][line]['measure'] = 'time' elif measurement_id in ['BLANK', 'IP']: self.lcd_line[display_id][line]['measure'] = measurement_id self.lcd_line[display_id][line]['name'] = '' if not device_id: return if unit in self.dict_units: self.lcd_line[display_id][line]['unit'] = unit else: self.lcd_line[display_id][line]['unit'] = '' # Determine the name controllers = [ Output, PID, Input, Math ] for each_controller in controllers: controller_found = db_retrieve_table_daemon(each_controller, unique_id=device_id) if controller_found: self.lcd_line[display_id][line]['name'] = controller_found.name if (self.lcd_line[display_id][line]['measure'] in ['BLANK', 'IP', 'time'] or None not in [self.lcd_line[display_id][line]['name'], self.lcd_line[display_id][line]['unit']]): self.lcd_line[display_id][line]['setup'] = True
def return_single_conversion_info(self): """ Return channel, unit, and measurement of a math device measurement """ math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() if math_dev_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) return channel, unit, measurement
def write_pid_values(self): """ Write PID values to the measurement database """ if self.PID_Controller.band: setpoint_band_lower = self.PID_Controller.setpoint - self.PID_Controller.band setpoint_band_upper = self.PID_Controller.setpoint + self.PID_Controller.band else: setpoint_band_lower = None setpoint_band_upper = None list_measurements = [ self.PID_Controller.setpoint, setpoint_band_lower, setpoint_band_upper, self.PID_Controller.P_value, self.PID_Controller.I_value, self.PID_Controller.D_value ] measurement_dict = {} measurements = self.device_measurements.filter( DeviceMeasurements.device_id == self.unique_id).all() for each_channel, each_measurement in enumerate(measurements): if (each_measurement.channel not in measurement_dict and each_measurement.channel < len(list_measurements)): # If setpoint, get unit from PID measurement if each_measurement.measurement_type == 'setpoint': setpoint_pid = db_retrieve_table_daemon( PID, unique_id=each_measurement.device_id) if setpoint_pid and ',' in setpoint_pid.measurement: pid_measurement = setpoint_pid.measurement.split(',')[1] setpoint_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=pid_measurement) if setpoint_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=setpoint_measurement.conversion_id) _, unit, _ = return_measurement_info( setpoint_measurement, conversion) measurement_dict[each_channel] = { 'measurement': each_measurement.measurement, 'unit': unit, 'value': list_measurements[each_channel] } else: measurement_dict[each_channel] = { 'measurement': each_measurement.measurement, 'unit': each_measurement.unit, 'value': list_measurements[each_channel] } add_measurements_influxdb(self.unique_id, measurement_dict)
def write_pid_values(self): """ Write PID values to the measurement database """ if self.band: setpoint_band_lower = self.setpoint - self.band setpoint_band_upper = self.setpoint + self.band else: setpoint_band_lower = None setpoint_band_upper = None list_measurements = [ self.setpoint, setpoint_band_lower, setpoint_band_upper, self.P_value, self.I_value, self.D_value ] measurement_dict = {} measurements = self.device_measurements.filter( DeviceMeasurements.device_id == self.pid_id).all() for each_channel, each_measurement in enumerate(measurements): if (each_measurement.channel not in measurement_dict and each_measurement.channel < len(list_measurements)): # If setpoint, get unit from PID measurement if each_measurement.measurement_type == 'setpoint': setpoint_pid = db_retrieve_table_daemon( PID, unique_id=each_measurement.device_id) if setpoint_pid and ',' in setpoint_pid.measurement: pid_measurement = setpoint_pid.measurement.split(',')[1] setpoint_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=pid_measurement) if setpoint_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=setpoint_measurement.conversion_id) _, unit, _ = return_measurement_info( setpoint_measurement, conversion) measurement_dict[each_channel] = { 'measurement': each_measurement.measurement, 'unit': unit, 'value': list_measurements[each_channel] } else: measurement_dict[each_channel] = { 'measurement': each_measurement.measurement, 'unit': each_measurement.unit, 'value': list_measurements[each_channel] } add_measurements_influxdb(self.pid_id, measurement_dict)
def setup_output(self): self.setup_on_off_output(OUTPUT_INFORMATION) if not self.options_channels['pwm_command'][0]: self.logger.error("Output must have Python Code set") return try: self.save_output_python_pwm_code(self.unique_id) file_run_pwm = '{}/output_pwm_{}.py'.format(PATH_PYTHON_CODE_USER, self.unique_id) module_name = "mycodo.output.{}".format(os.path.basename(file_run_pwm).split('.')[0]) spec = importlib.util.spec_from_file_location(module_name, file_run_pwm) 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_last_influxdb( self.unique_id, unit, channel, measure=measurement, duration_sec=None) 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 form_pid_choices(choices, each_pid, dict_units, dict_measurements): device_measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == each_pid.unique_id).all() for each_measure in device_measurements: conversion = Conversion.query.filter( Conversion.unique_id == each_measure.conversion_id).first() channel, unit, measurement = return_measurement_info( each_measure, conversion) value = '{input_id},{meas_id}'.format( input_id=each_pid.unique_id, meas_id=each_measure.unique_id) if unit: display_unit = find_name_unit( dict_units, unit) display_measurement = find_name_measurement( dict_measurements, measurement) elif each_measure.measurement_type == 'setpoint': display_unit = None display_measurement = 'Setpoint' else: display_unit = None display_measurement = None if each_measure.name: channel_info = 'CH{cnum} ({cname})'.format( cnum=channel + 1, cname=each_measure.name) else: channel_info = 'CH{cnum}'.format(cnum=channel + 1) if display_measurement and display_unit: measurement_unit = '{meas} ({unit})'.format( meas=display_measurement, unit=display_unit) elif display_measurement: measurement_unit = '{meas}'.format( meas=display_measurement) else: measurement_unit = '({unit})'.format(unit=display_unit) display = '[PID {id:02d}] {i_name} {chan} {meas}'.format( id=each_pid.id, i_name=each_pid.name, chan=channel_info, meas=measurement_unit) choices.update({value: display}) return choices
def form_input_choices(choices, each_input, dict_units, dict_measurements): device_measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == each_input.unique_id).all() for each_measure in device_measurements: conversion = Conversion.query.filter( Conversion.unique_id == each_measure.conversion_id).first() channel, unit, measurement = return_measurement_info( each_measure, conversion) if unit: value = '{input_id},{meas_id}'.format( input_id=each_input.unique_id, meas_id=each_measure.unique_id) display_unit = find_name_unit( dict_units, unit) display_measurement = find_name_measurement( dict_measurements, measurement) if isinstance(channel, int): channel_num = ' CH{cnum}'.format(cnum=channel + 1) else: channel_num = '' if each_measure.name: channel_name = ' ({name})'.format(name=each_measure.name) else: channel_name = '' if display_measurement and display_unit: measurement_unit = ' {meas} ({unit})'.format( meas=display_measurement, unit=display_unit) elif display_measurement: measurement_unit = ' {meas}'.format( meas=display_measurement) else: measurement_unit = ' ({unit})'.format(unit=display_unit) display = '[Input {id:02d}] {i_name}{chan_num}{chan_name}{meas}'.format( id=each_input.id, i_name=each_input.name, chan_num=channel_num, chan_name=channel_name, meas=measurement_unit) choices.update({value: display}) return choices
def form_pid_choices(choices, each_pid, dict_units, dict_measurements): device_measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == each_pid.unique_id).all() for each_measure in device_measurements: conversion = Conversion.query.filter( Conversion.unique_id == each_measure.conversion_id).first() channel, unit, measurement = return_measurement_info( each_measure, conversion) value = '{input_id},{meas_id}'.format( input_id=each_pid.unique_id, meas_id=each_measure.unique_id) if unit: display_unit = find_name_unit( dict_units, unit) display_measurement = find_name_measurement( dict_measurements, measurement) elif each_measure.measurement_type == 'setpoint': display_unit = None display_measurement = 'Setpoint' else: display_unit = None display_measurement = None if each_measure.name: channel_info = 'CH{cnum} ({cname})'.format( cnum=channel + 1, cname=each_measure.name) else: channel_info = 'CH{cnum}'.format(cnum=channel + 1) if display_measurement and display_unit: measurement_unit = '{meas} ({unit})'.format( meas=display_measurement, unit=display_unit) elif display_measurement: measurement_unit = '{meas}'.format( meas=display_measurement) else: measurement_unit = '({unit})'.format(unit=display_unit) display = '[PID {id:02d}] {i_name} {chan} {meas}'.format( id=each_pid.id, i_name=each_pid.name, chan=channel_info, meas=measurement_unit) choices.update({value: display}) return choices
def return_single_measure_info(self): math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() math_conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) _, math_unit, _ = return_measurement_info(math_dev_measurement, math_conversion) device_id = self.inputs.split(',')[0] measurement_id = self.inputs.split(',')[1] device_measurement = db_retrieve_table_daemon(DeviceMeasurements, unique_id=measurement_id) if device_measurement: measure_conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) else: measure_conversion = None (measure_channel, measure_unit, measure_measurement) = return_measurement_info( device_measurement, measure_conversion) return (device_id, math_dev_measurement, math_unit, measure_channel, measure_unit, measure_measurement)
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 conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) sum_measurements = sum_past_seconds( self.select_measurement_device_id, unit, channel, self.max_measure_age, measure=measurement) if not sum_measurements: self.logger.error( "Could not find measurement within the set Max Age") return False 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 get_last_measurement(device_id, measurement_id, max_age=None): device_measurement = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.unique_id == measurement_id).first() if device_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( device_measurement, conversion) last_measurement = read_last_influxdb(device_id, unit, measurement, channel, max_age) return last_measurement
def form_input_choices(choices, each_input, dict_units, dict_measurements): device_measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == each_input.unique_id).all() for each_measure in device_measurements: conversion = Conversion.query.filter( Conversion.unique_id == each_measure.conversion_id).first() channel, unit, measurement = return_measurement_info( each_measure, conversion) if unit: value = '{input_id},{meas_id}'.format( input_id=each_input.unique_id, meas_id=each_measure.unique_id) display_unit = find_name_unit( dict_units, unit) display_measurement = find_name_measurement( dict_measurements, measurement) if each_measure.name: channel_name = ' ({name})'.format(name=each_measure.name) else: channel_name = '' channel_info = "CH{cnum}{cname}".format( cnum=channel + 1, cname=channel_name) if display_measurement and display_unit: measurement_unit = '{meas} ({unit})'.format( meas=display_measurement, unit=display_unit) elif display_measurement: measurement_unit = '{meas}'.format( meas=display_measurement) else: measurement_unit = '({unit})'.format(unit=display_unit) display = '[Input {id:02d}] {i_name} {chan} {meas}'.format( id=each_input.id, i_name=each_input.name, chan=channel_info, meas=measurement_unit) choices.update({value: display}) return choices
def get_condition_value_dict(condition_id): """ Returns dict of multiple condition measurements for Conditional controllers :param condition_id: Conditional condition ID :return: measurement: dict of float measurements """ # Check Measurement Conditions sql_condition = db_retrieve_table_daemon(ConditionalConditions).filter( ConditionalConditions.unique_id == condition_id).first() if sql_condition.condition_type == 'measurement_dict': device_id = sql_condition.measurement.split(',')[0] measurement_id = sql_condition.measurement.split(',')[1] max_age = sql_condition.max_age device_measurement = db_retrieve_table_daemon(DeviceMeasurements, unique_id=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) if None in [channel, unit]: logger.error( "Could not determine channel or unit from measurement ID: " "{}".format(measurement_id)) return past_measurements_dict = get_past_measurements(device_id, measurement_id, max_age=max_age) # TODO: Change to return dictionary in next major release string_ts_values = '' if past_measurements_dict: string_ts_values = '' for index, each_set in enumerate(past_measurements_dict): string_ts_values += '{},{}'.format(each_set[0], each_set[1]) if index + 1 < len(past_measurements_dict): string_ts_values += ';' return string_ts_values
def get_measurements_from_id(self, device_id, measure_id): device_measurement = get_measurement(measure_id) if not device_measurement: return False, None conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) channel, unit, measurement = return_measurement_info( device_measurement, conversion) measure = read_last_influxdb(device_id, unit, channel, measure=measurement, duration_sec=self.max_measure_age) if not measure: return False, None return True, measure
def form_math_choices(choices, each_math, dict_units, dict_measurements): device_measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == each_math.unique_id).all() for each_measure in device_measurements: conversion = Conversion.query.filter( Conversion.unique_id == each_measure.conversion_id).first() channel, unit, measurement = return_measurement_info( each_measure, conversion) if unit: value = '{input_id},{meas_id}'.format( input_id=each_math.unique_id, meas_id=each_measure.unique_id) display_unit = find_name_unit( dict_units, unit) display_measurement = find_name_measurement( dict_measurements, measurement) if each_measure.name: channel_info = 'CH{cnum} ({cname})'.format( cnum=channel, cname=each_measure.name) else: channel_info = 'CH{cnum}'.format(cnum=channel) if display_measurement and display_unit: measurement_unit = '{meas} ({unit})'.format( meas=display_measurement, unit=display_unit) elif display_measurement: measurement_unit = '{meas}'.format( meas=display_measurement) else: measurement_unit = '({unit})'.format(unit=display_unit) display = '[Math {id:02d}] {i_name} {chan} {meas}'.format( id=each_math.id, i_name=each_math.name, chan=channel_info, meas=measurement_unit) choices.append({'value': value, 'item': display}) return choices
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 get_condition_measurement(sql_condition): device_id = sql_condition.measurement.split(',')[0] measurement_id = sql_condition.measurement.split(',')[1] device_measurement = db_retrieve_table_daemon(DeviceMeasurements, unique_id=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) if None in [channel, unit]: logger.error( "Could not determine channel or unit from measurement ID: " "{}".format(measurement_id)) return max_age = sql_condition.max_age # Check Measurement Conditions if sql_condition.condition_type == 'measurement': # Check if there hasn't been a measurement in the last set number # of seconds. If not, trigger conditional last_measurement = get_last_measurement(device_id, unit, measurement, channel, max_age) return last_measurement # If the edge detection variable is set, calling this function will # trigger an edge detection event. This will merely produce the correct # message based on the edge detection settings. elif sql_condition.condition_type == 'gpio_state': try: GPIO.setmode(GPIO.BCM) GPIO.setup(int(sql_condition.gpio_pin), GPIO.IN) gpio_state = GPIO.input(int(sql_condition.gpio_pin)) except: gpio_state = None logger.error("Exception reading the GPIO pin") return gpio_state
def get_condition_measurement(sql_condition): device_id = sql_condition.measurement.split(',')[0] measurement_id = sql_condition.measurement.split(',')[1] device_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=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) if None in [channel, unit]: logger.error( "Could not determine channel or unit from measurement ID: " "{}".format(measurement_id)) return max_age = sql_condition.max_age # Check Measurement Conditions if sql_condition.condition_type == 'measurement': # Check if there hasn't been a measurement in the last set number # of seconds. If not, trigger conditional last_measurement = get_last_measurement( device_id, unit, measurement, channel, max_age) return last_measurement # If the edge detection variable is set, calling this function will # trigger an edge detection event. This will merely produce the correct # message based on the edge detection settings. elif sql_condition.condition_type == 'gpio_state': try: GPIO.setmode(GPIO.BCM) GPIO.setup(int(sql_condition.gpio_pin), GPIO.IN) gpio_state = GPIO.input(int(sql_condition.gpio_pin)) except: gpio_state = None logger.error("Exception reading the GPIO pin") return gpio_state
def math_redundancy(self, measurement_dict): list_order = self.order_of_use.split(';') measurement_success = False for each_id_measurement_id in list_order: device_id = each_id_measurement_id.split(',')[0] measurement_id = each_id_measurement_id.split(',')[1] math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() if math_dev_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) try: success_measure, measure = self.get_measurements_from_id( device_id, measurement_id) if success_measure: measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': float(measure[1]), 'timestamp_utc': measure[0], } } measurement_success = True break except Exception as msg: self.logger.exception( "redundancy Error: {err}".format(err=msg)) if not measurement_success: self.error_not_within_max_age() return measurement_dict
def math_average(self, measurement_dict): math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() channel, unit, measurement = return_measurement_info( math_dev_measurement, None) success, measure = self.get_measurements_from_str(self.inputs) if success: average = float(sum(measure) / float(len(measure))) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': average } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() return measurement_dict
def get_temp_data(self): """Get the temperature.""" if self.temperature_comp_meas_measurement_id: self.logger.debug("Temperature corrections will be applied") 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: {temp} C".format(temp=out_value)) else: self.logger.error( "Temperature measurement not found within the " "past {} seconds".format(self.max_age)) out_value = None else: self.logger.debug("No temperature corrections applied") out_value = None return out_value
def get_condition_value_dict(condition_id): """ Returns dict of multiple condition measurements for Conditional controllers :param condition_id: Conditional condition ID :return: measurement: dict of float measurements """ # Check Measurement Conditions sql_condition = db_retrieve_table_daemon(ConditionalConditions).filter( ConditionalConditions.unique_id == condition_id).first() if sql_condition.condition_type == 'measurement_dict': device_id = sql_condition.measurement.split(',')[0] measurement_id = sql_condition.measurement.split(',')[1] device_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=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) if None in [channel, unit]: logger.error( "Could not determine channel or unit from measurement ID: " "{}".format(measurement_id)) return max_age = sql_condition.max_age # Check if there hasn't been a measurement in the last set number # of seconds. If not, trigger conditional past_measurements = get_past_measurements( device_id, unit, measurement, channel, max_age) return past_measurements
def last_data_pid(pid_id, input_period): """Return the most recent time and value from influxdb.""" if not current_user.is_authenticated: return "You are not logged in and cannot access this endpoint" if not str_is_float(input_period): return '', 204 try: pid = PID.query.filter(PID.unique_id == pid_id).first() if len(pid.measurement.split(',')) == 2: device_id = pid.measurement.split(',')[0] measurement_id = pid.measurement.split(',')[1] else: device_id = None measurement_id = None actual_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == measurement_id).first() if actual_measurement: actual_conversion = Conversion.query.filter( Conversion.unique_id == actual_measurement.conversion_id).first() else: actual_conversion = None (actual_channel, actual_unit, actual_measurement) = return_measurement_info(actual_measurement, actual_conversion) setpoint_unit = None if pid and ',' in pid.measurement: pid_measurement = pid.measurement.split(',')[1] setpoint_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == pid_measurement).first() if setpoint_measurement: conversion = Conversion.query.filter( Conversion.unique_id == setpoint_measurement.conversion_id).first() _, setpoint_unit, _ = return_measurement_info( setpoint_measurement, conversion) p_value = return_point_timestamp(pid_id, 'pid_value', input_period, measurement='pid_p_value') i_value = return_point_timestamp(pid_id, 'pid_value', input_period, measurement='pid_i_value') d_value = return_point_timestamp(pid_id, 'pid_value', input_period, measurement='pid_d_value') if None not in (p_value[1], i_value[1], d_value[1]): pid_value = [ p_value[0], f'{float(p_value[1]) + float(i_value[1]) + float(d_value[1]):.3f}' ] else: pid_value = None setpoint_band = None if pid.band: try: daemon = DaemonControl() setpoint_band = daemon.pid_get(pid.unique_id, 'setpoint_band') except: logger.debug("Couldn't get setpoint") live_data = { 'activated': pid.is_activated, 'paused': pid.is_paused, 'held': pid.is_held, 'setpoint': return_point_timestamp(pid_id, setpoint_unit, input_period, channel=0), 'setpoint_band': setpoint_band, 'pid_p_value': p_value, 'pid_i_value': i_value, 'pid_d_value': d_value, 'pid_pid_value': pid_value, 'duration_time': return_point_timestamp(pid_id, 's', input_period, measurement='duration_time'), 'duty_cycle': return_point_timestamp(pid_id, 'percent', input_period, measurement='duty_cycle'), 'actual': return_point_timestamp(device_id, actual_unit, input_period, measurement=actual_measurement, channel=actual_channel) } return jsonify(live_data) except KeyError: logger.debug("No Data returned form influxdb") return '', 204 except Exception as err: logger.exception(f"URL for 'last_pid' raised and error: {err}") return '', 204
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 calculate_math(self): measurement_dict = {} if self.math_type == 'average': device_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).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) success, measure = self.get_measurements_from_str(self.inputs) if success: average = float(sum(measure) / float(len(measure))) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': average } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() elif self.math_type == 'average_single': device_id = self.inputs.split(',')[0] measurement_id = self.inputs.split(',')[1] device_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=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) try: last_measurements = read_past_influxdb( device_id, unit, measurement, channel, self.max_measure_age) if last_measurements: measure_list = [] for each_set in last_measurements: if len(each_set) == 2: measure_list.append(each_set[1]) average = sum(measure_list) / float(len(measure_list)) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': average } } else: self.error_not_within_max_age() except Exception as msg: self.logger.exception("average_single Error: {err}".format(err=msg)) elif self.math_type == 'difference': device_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).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) success, measure = self.get_measurements_from_str(self.inputs) if success: if self.difference_reverse_order: difference = measure[1] - measure[0] else: difference = measure[0] - measure[1] if self.difference_absolute: difference = abs(difference) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': difference } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() elif self.math_type == 'equation': device_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).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) success, measure = self.get_measurements_from_str(self.inputs) if success: replaced_str = self.equation.replace('x', str(measure[0])) equation_output = eval(replaced_str) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': float(equation_output) } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() elif self.math_type == 'redundancy': list_order = self.order_of_use.split(';') measurement_success = False for each_id_measurement_id in list_order: device_id = each_id_measurement_id.split(',')[0] measurement_id = each_id_measurement_id.split(',')[1] device_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).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) try: success_measure, measure = self.get_measurements_from_id( device_id, measurement_id) if success_measure: measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': float(measure[1]), 'timestamp': measure[0], } } measurement_success = True break except Exception as msg: self.logger.exception("redundancy Error: {err}".format(err=msg)) if not measurement_success: self.error_not_within_max_age() elif self.math_type == 'statistics': success, measure = self.get_measurements_from_str(self.inputs) if success: # Perform some math 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(): conversion = db_retrieve_table_daemon( Conversion, unique_id=each_measurement.conversion_id) channel, unit, measurement = return_measurement_info( each_measurement, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': list_measurement[channel] } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() elif self.math_type == 'verification': device_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).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) success, measure = self.get_measurements_from_str(self.inputs) if (success and max(measure) - min(measure) < self.max_difference): difference = max(measure) - min(measure) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': difference } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() elif self.math_type == 'humidity': pressure_pa = 101325 critical_error = False if self.pressure_pa_id and self.pressure_pa_measure_id: success_pa, pressure = self.get_measurements_from_id( self.pressure_pa_id, self.pressure_pa_measure_id) if success_pa: pressure_pa = int(pressure[1]) # Pressure must be in Pa, convert if not if db_retrieve_table_daemon(DeviceMeasurements, unique_id=self.pressure_pa_measure_id): measurement = db_retrieve_table_daemon(DeviceMeasurements, unique_id=self.pressure_pa_measure_id) else: self.logger.error("Could not find pressure measurement") measurement = None critical_error = True if measurement and measurement.unit != 'Pa': for each_conv in db_retrieve_table_daemon(Conversion, entry='all'): if (each_conv.convert_unit_from == measurement.unit and each_conv.convert_unit_to == 'Pa'): pressure_pa = convert_units( each_conv.unique_id, pressure_pa) else: self.logger.error( "Could not find conversion for unit " "{unit} to Pa (Pascals)".format( unit=measurement.unit)) critical_error = True success_dbt, dry_bulb_t = self.get_measurements_from_id( self.dry_bulb_t_id, self.dry_bulb_t_measure_id) success_wbt, wet_bulb_t = self.get_measurements_from_id( self.wet_bulb_t_id, self.wet_bulb_t_measure_id) if success_dbt and success_wbt: dbt_kelvin = float(dry_bulb_t[1]) wbt_kelvin = float(wet_bulb_t[1]) if db_retrieve_table_daemon(DeviceMeasurements, unique_id=self.dry_bulb_t_measure_id): measurement = db_retrieve_table_daemon(DeviceMeasurements, unique_id=self.dry_bulb_t_measure_id) else: self.logger.error("Could not find pressure measurement") measurement = None critical_error = True if measurement and measurement.unit != 'K': conversion_found = False for each_conv in db_retrieve_table_daemon(Conversion, entry='all'): if (each_conv.convert_unit_from == measurement.unit and each_conv.convert_unit_to == 'K'): dbt_kelvin = convert_units( each_conv.unique_id, dbt_kelvin) conversion_found = True if not conversion_found: self.logger.error( "Could not find conversion for unit " "{unit} to K (Kelvin)".format( unit=measurement.unit)) critical_error = True if db_retrieve_table_daemon(DeviceMeasurements, unique_id=self.dry_bulb_t_measure_id): measurement = db_retrieve_table_daemon(DeviceMeasurements, unique_id=self.dry_bulb_t_measure_id) else: self.logger.error("Could not find pressure measurement") measurement = None critical_error = True if measurement and measurement.unit != 'K': conversion_found = False for each_conv in db_retrieve_table_daemon(Conversion, entry='all'): if (each_conv.convert_unit_from == measurement.unit and each_conv.convert_unit_to == 'K'): wbt_kelvin = convert_units( each_conv.unique_id, wbt_kelvin) conversion_found = True if not conversion_found: self.logger.error( "Could not find conversion for unit " "{unit} to K (Kelvin)".format( unit=measurement.unit)) critical_error = True # Convert temperatures to Kelvin (already done above) # dbt_kelvin = celsius_to_kelvin(dry_bulb_t_c) # wbt_kelvin = celsius_to_kelvin(wet_bulb_t_c) psypi = None try: if not critical_error: psypi = SI.state( "DBT", dbt_kelvin, "WBT", wbt_kelvin, pressure_pa) else: self.logger.error( "One or more critical errors prevented the " "humidity from being calculated") except TypeError as err: self.logger.error("TypeError: {msg}".format(msg=err)) if psypi: 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]) list_measurement = [ specific_enthalpy, humidity, specific_volume, humidity_ratio ] for each_measurement in self.device_measurements.all(): conversion = db_retrieve_table_daemon( Conversion, unique_id=each_measurement.conversion_id) channel, unit, measurement = return_measurement_info( each_measurement, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': list_measurement[channel] } else: self.error_not_within_max_age() elif self.math_type == 'vapor_pressure_deficit': vpd_pa = None critical_error = False success_dbt, temperature = self.get_measurements_from_id( self.unique_id_1, self.unique_measurement_id_1) success_wbt, humidity = self.get_measurements_from_id( self.unique_id_2, self.unique_measurement_id_2) if success_dbt and success_wbt: vpd_temperature_celsius = float(temperature[1]) vpd_humidity_percent = float(humidity[1]) if db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_1): measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_1) else: self.logger.error("Could not find temperature measurement") measurement = None critical_error = True if measurement and measurement.unit != 'C': conversion_found = False for each_conv in db_retrieve_table_daemon(Conversion, entry='all'): if (each_conv.convert_unit_from == measurement.unit and each_conv.convert_unit_to == 'C'): vpd_temperature_celsius = convert_units( each_conv.unique_id, vpd_temperature_celsius) conversion_found = True if not conversion_found: self.logger.error( "Could not find conversion for unit " "{unit} to C (Celsius)".format( unit=measurement.unit)) critical_error = True if db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_2): measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_2) else: self.logger.error("Could not find humidity measurement") measurement = None critical_error = True if measurement and measurement.unit != 'percent': conversion_found = False for each_conv in db_retrieve_table_daemon(Conversion, entry='all'): if (each_conv.convert_unit_from == measurement.unit and each_conv.convert_unit_to == 'percent'): vpd_humidity_percent = convert_units( each_conv.unique_id, vpd_humidity_percent) conversion_found = True if not conversion_found: self.logger.error( "Could not find conversion for unit " "{unit} to percent (%)".format( unit=measurement.unit)) critical_error = True try: if not critical_error: vpd_pa = calculate_vapor_pressure_deficit( vpd_temperature_celsius, vpd_humidity_percent) else: self.logger.error( "One or more critical errors prevented the " "vapor pressure deficit from being calculated") except TypeError as err: self.logger.error("TypeError: {msg}".format(msg=err)) if vpd_pa: measure = self.device_measurements.first() conversion = db_retrieve_table_daemon( Conversion, unique_id=measure.conversion_id) channel, unit, measurement = return_measurement_info( measure, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': vpd_pa } else: self.error_not_within_max_age() else: self.logger.error("Unknown math type: {type}".format(type=self.math_type)) # Finally, add measurements to influxdb add_measurements_influxdb(self.unique_id, measurement_dict)
def export_data(unique_id, measurement_id, start_seconds, end_seconds): """ Return data from start_seconds to end_seconds from influxdb. Used for exporting data. """ current_app.config['INFLUXDB_USER'] = INFLUXDB_USER current_app.config['INFLUXDB_PASSWORD'] = INFLUXDB_PASSWORD current_app.config['INFLUXDB_DATABASE'] = INFLUXDB_DATABASE current_app.config['INFLUXDB_TIMEOUT'] = 5 dbcon = influx_db.connection output = Output.query.filter(Output.unique_id == unique_id).first() input_dev = Input.query.filter(Input.unique_id == unique_id).first() math = Math.query.filter(Math.unique_id == unique_id).first() if output: name = output.name elif input_dev: name = input_dev.name elif math: name = math.name else: name = None device_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == measurement_id).first() if device_measurement: conversion = Conversion.query.filter( Conversion.unique_id == device_measurement.conversion_id).first() else: conversion = None channel, unit, measurement = return_measurement_info( device_measurement, conversion) utc_offset_timedelta = datetime.datetime.utcnow() - datetime.datetime.now() start = datetime.datetime.fromtimestamp(float(start_seconds)) start += utc_offset_timedelta start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') end = datetime.datetime.fromtimestamp(float(end_seconds)) end += utc_offset_timedelta end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') query_str = query_string( unit, unique_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str) if query_str == 1: flash('Invalid query string', 'error') return redirect(url_for('routes_page.page_export')) raw_data = dbcon.query(query_str).raw if not raw_data or 'series' not in raw_data: flash('No measurements to export in this time period', 'error') return redirect(url_for('routes_page.page_export')) # Generate column names col_1 = 'timestamp (UTC)' col_2 = '{name} {meas} ({id})'.format( name=name, meas=measurement, id=unique_id) csv_filename = '{id}_{meas}.csv'.format(id=unique_id, meas=measurement) # Populate list of dictionary entries for each column to convert to CSV # and send to the user to download csv_data = [] for each_data in raw_data['series'][0]['values']: csv_data.append({col_1: str(each_data[0][:-4]).replace('T', ' '), col_2: each_data[1]}) return send_csv(csv_data, csv_filename, [col_1, col_2])
def calculate_math(self): measurement_dict = {} # # Average (multiple channels) # if self.math_type == 'average': math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() channel, unit, measurement = return_measurement_info( math_dev_measurement, None) success, measure = self.get_measurements_from_str(self.inputs) if success: average = float(sum(measure) / float(len(measure))) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': average } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() # # Average (single channel) # elif self.math_type == 'average_single': math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() math_conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) (math_channel, math_unit, math_measurement) = return_measurement_info( math_dev_measurement, math_conversion) device_id = self.inputs.split(',')[0] measurement_id = self.inputs.split(',')[1] if measurement_id == 'output': output = db_retrieve_table_daemon(Output, unique_id=device_id) measure_channel = output.channel measure_unit = output.unit measure_measurement = output.measurement else: device_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=measurement_id) if device_measurement: measure_conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) else: measure_conversion = None (measure_channel, measure_unit, measure_measurement) = return_measurement_info( device_measurement, measure_conversion) try: return_value = average_past_seconds( device_id, measure_unit, measure_channel, self.max_measure_age, measure=measure_measurement) if math_dev_measurement.conversion_id: return_value = convert_units( math_dev_measurement.conversion_id, return_value) if return_value: measurement_dict = { math_dev_measurement.channel: { 'measurement': measure_measurement, 'unit': math_unit, 'value': return_value } } else: self.error_not_within_max_age() except Exception as msg: self.logger.exception( "average_single Error: {err}".format(err=msg)) # # Sum (multiple channels) # elif self.math_type == 'sum': math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() if math_dev_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) success, measure = self.get_measurements_from_str(self.inputs) if success: sum_value = float(sum(measure)) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': sum_value } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() # # Sum (single channel) # elif self.math_type == 'sum_single': math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() math_conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) (math_channel, math_unit, math_measurement) = return_measurement_info( math_dev_measurement, math_conversion) device_id = self.inputs.split(',')[0] measurement_id = self.inputs.split(',')[1] if measurement_id == 'output': output = db_retrieve_table_daemon(Output, unique_id=device_id) measure_channel = output.channel measure_unit = output.unit measure_measurement = output.measurement else: device_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=measurement_id) if device_measurement: measure_conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) else: measure_conversion = None (measure_channel, measure_unit, measure_measurement) = return_measurement_info( device_measurement, measure_conversion) try: return_value = sum_past_seconds( device_id, measure_unit, measure_channel, self.max_measure_age, measure=measure_measurement) if math_dev_measurement.conversion_id: return_value = convert_units( math_dev_measurement.conversion_id, return_value) if return_value: measurement_dict = { math_dev_measurement.channel: { 'measurement': measure_measurement, 'unit': math_unit, 'value': return_value } } else: self.error_not_within_max_age() except Exception as msg: self.logger.exception( "sum_single Error: {err}".format(err=msg)) # # Difference between two channels # elif self.math_type == 'difference': math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() if math_dev_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) success, measure = self.get_measurements_from_str(self.inputs) if success: if self.difference_reverse_order: difference = measure[1] - measure[0] else: difference = measure[0] - measure[1] if self.difference_absolute: difference = abs(difference) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': difference } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() # # Equation (math performed on measurement) # elif self.math_type == 'equation': math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() if math_dev_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) success, measure = self.get_measurements_from_str( self.equation_input) if success: if 'x' in self.equation: replaced_str = self.equation.replace('x', str(measure[0])) else: replaced_str = self.equation equation_output = eval(replaced_str) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': float(equation_output) } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() # # Redundancy (Use next measurement if one isn't currently available) # elif self.math_type == 'redundancy': list_order = self.order_of_use.split(';') measurement_success = False for each_id_measurement_id in list_order: device_id = each_id_measurement_id.split(',')[0] measurement_id = each_id_measurement_id.split(',')[1] math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() if math_dev_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) try: success_measure, measure = self.get_measurements_from_id( device_id, measurement_id) if success_measure: measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': float(measure[1]), 'timestamp_utc': measure[0], } } measurement_success = True break except Exception as msg: self.logger.exception( "redundancy Error: {err}".format(err=msg)) if not measurement_success: self.error_not_within_max_age() # # Statistical analysis on all measurements from a period of time # elif self.math_type == 'statistics': success, measure = self.get_measurements_from_str(self.inputs) if success: # Perform some math 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(): conversion = db_retrieve_table_daemon( Conversion, unique_id=each_measurement.conversion_id) channel, unit, measurement = return_measurement_info( each_measurement, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': list_measurement[channel] } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() # # Verification (only use measurement if it's close to another measurement) # elif self.math_type == 'verification': math_dev_measurement = self.device_measurements.filter( DeviceMeasurements.channel == 0).first() if math_dev_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) success, measure = self.get_measurements_from_str(self.inputs) if (success and max(measure) - min(measure) < self.max_difference): difference = max(measure) - min(measure) measurement_dict = { channel: { 'measurement': measurement, 'unit': unit, 'value': difference } } elif measure: self.logger.error(measure) else: self.error_not_within_max_age() # # Calculate humidity from wet- and dry-bulb temperatures # elif self.math_type == 'humidity': pressure_pa = 101325 critical_error = False if self.pressure_pa_id and self.pressure_pa_measure_id: success_pa, pressure = self.get_measurements_from_id( self.pressure_pa_id, self.pressure_pa_measure_id) if success_pa: pressure_pa = int(pressure[1]) # Pressure must be in Pa, convert if not measurement_press = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.pressure_pa_measure_id) if not measurement_press: self.logger.error( "Could not find pressure measurement") measurement_press = None critical_error = True if measurement_press and measurement_press.unit != 'Pa': pressure_pa, status = self.is_measurement_unit( measurement_press.unit, 'Pa', pressure_pa) if status == 'error': critical_error = True success_dbt, dry_bulb_t = self.get_measurements_from_id( self.dry_bulb_t_id, self.dry_bulb_t_measure_id) success_wbt, wet_bulb_t = self.get_measurements_from_id( self.wet_bulb_t_id, self.wet_bulb_t_measure_id) if success_dbt and success_wbt: dbt_kelvin = float(dry_bulb_t[1]) wbt_kelvin = float(wet_bulb_t[1]) measurement_db_temp = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.dry_bulb_t_measure_id) if not measurement_db_temp: self.logger.error( "Could not find dry bulb temperature measurement") measurement_db_temp = None critical_error = True if measurement_db_temp and measurement_db_temp.unit != 'K': dbt_kelvin, status = self.is_measurement_unit( measurement_db_temp.unit, 'K', dbt_kelvin) if status == 'error': critical_error = True measurement_wb_temp = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.wet_bulb_t_measure_id) if not measurement_wb_temp: self.logger.error( "Could not find wet bulb temperature measurement") measurement_wb_temp = None critical_error = True if measurement_wb_temp and measurement_wb_temp.unit != 'K': wbt_kelvin, status = self.is_measurement_unit( measurement_wb_temp.unit, 'K', wbt_kelvin) if status == 'error': critical_error = True # Convert temperatures to Kelvin (already done above) # dbt_kelvin = celsius_to_kelvin(dry_bulb_t_c) # wbt_kelvin = celsius_to_kelvin(wet_bulb_t_c) psypi = None try: if not critical_error: psypi = SI.state( "DBT", dbt_kelvin, "WBT", wbt_kelvin, pressure_pa) else: self.logger.error( "One or more critical errors prevented the " "humidity from being calculated") except TypeError as err: self.logger.error("TypeError: {msg}".format(msg=err)) if psypi: 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]) list_measurement = [ specific_enthalpy, humidity, specific_volume, humidity_ratio ] for each_measurement in self.device_measurements.all(): conversion = db_retrieve_table_daemon( Conversion, unique_id=each_measurement.conversion_id) channel, unit, measurement = return_measurement_info( each_measurement, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': list_measurement[channel] } else: self.error_not_within_max_age() # # Calculate vapor pressure deficit from temperature and humidity # elif self.math_type == 'vapor_pressure_deficit': vpd_pa = None critical_error = False success_temp, temperature = self.get_measurements_from_id( self.unique_id_1, self.unique_measurement_id_1) success_hum, humidity = self.get_measurements_from_id( self.unique_id_2, self.unique_measurement_id_2) if success_temp and success_hum: vpd_temperature_celsius = float(temperature[1]) vpd_humidity_percent = float(humidity[1]) measurement_temp = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_1) if not measurement_temp: self.logger.error("Could not find temperature measurement") measurement_temp = None critical_error = True if measurement_temp and measurement_temp.unit != 'C': vpd_temperature_celsius, status = self.is_measurement_unit( measurement_temp.unit, 'C', vpd_temperature_celsius) if status == 'error': critical_error = True measurement_hum = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_2) if not measurement_hum: self.logger.error("Could not find humidity measurement") measurement_hum = None critical_error = True if measurement_hum and measurement_hum.unit != 'percent': vpd_humidity_percent, status = self.is_measurement_unit( measurement_hum.unit, 'percent', vpd_humidity_percent) if status == 'error': critical_error = True try: if not critical_error: vpd_pa = calculate_vapor_pressure_deficit( vpd_temperature_celsius, vpd_humidity_percent) else: self.logger.error( "One or more critical errors prevented the " "vapor pressure deficit from being calculated") except TypeError as err: self.logger.error("TypeError: {msg}".format(msg=err)) if vpd_pa: math_dev_measurement = self.device_measurements.first() conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': vpd_pa } else: self.error_not_within_max_age() else: self.logger.error("Unknown math type: {type}".format(type=self.math_type)) # Finally, add measurements 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))
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 get_condition_value(condition_id): """ Returns condition measurements for Conditional controllers :param condition_id: Conditional condition ID :return: measurement: multiple types """ sql_condition = db_retrieve_table_daemon(ConditionalConditions).filter( ConditionalConditions.unique_id == condition_id).first() # Check Measurement Conditions if sql_condition.condition_type in ['measurement', 'measurement_past_average', 'measurement_past_sum']: device_id = sql_condition.measurement.split(',')[0] measurement_id = sql_condition.measurement.split(',')[1] device_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=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) if None in [channel, unit]: logger.error( "Could not determine channel or unit from measurement ID: " "{}".format(measurement_id)) return max_age = sql_condition.max_age if sql_condition.condition_type == 'measurement': return_measurement = get_last_measurement( device_id, unit, measurement, channel, max_age) elif sql_condition.condition_type == 'measurement_past_average': measurement_list = [] measurements_str = get_past_measurements( device_id, unit, measurement, channel, max_age) for each_set in measurements_str.split(';'): measurement_list.append(float(each_set.split(',')[1])) return_measurement = sum(measurement_list) / len(measurement_list) elif sql_condition.condition_type == 'measurement_past_sum': measurement_list = [] measurements_str = get_past_measurements( device_id, unit, measurement, channel, max_age) for each_set in measurements_str.split(';'): measurement_list.append(float(each_set.split(',')[1])) return_measurement = sum(measurement_list) else: return return return_measurement # Return GPIO state elif sql_condition.condition_type == 'gpio_state': try: import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(int(sql_condition.gpio_pin), GPIO.IN) gpio_state = GPIO.input(int(sql_condition.gpio_pin)) except Exception as e: gpio_state = None logger.error("Exception reading the GPIO pin: {}".format(e)) return gpio_state # Return output state elif sql_condition.condition_type == 'output_state': control = DaemonControl() return control.output_state(sql_condition.output_id) # Return the duration the output is currently on for elif sql_condition.condition_type == 'output_duration_on': control = DaemonControl() return control.output_sec_currently_on(sql_condition.output_id) # Return controller active state elif sql_condition.condition_type == 'controller_status': controller_type, _, _ = which_controller(sql_condition.controller_id) control = DaemonControl() return control.controller_is_active(sql_condition.controller_id)
def past_data(unique_id, measure_type, measurement_id, past_seconds): """Return data from past_seconds until present from influxdb""" if not str_is_float(past_seconds): return '', 204 if measure_type == 'tag': notes_list = [] tag = NoteTags.query.filter(NoteTags.unique_id == unique_id).first() notes = Notes.query.filter( Notes.date_time >= (datetime.datetime.utcnow() - datetime.timedelta(seconds=int(past_seconds)))).all() for each_note in notes: if tag.unique_id in each_note.tags.split(','): notes_list.append( [each_note.date_time.strftime("%Y-%m-%dT%H:%M:%S.000000000Z"), each_note.name, each_note.note]) if notes_list: return jsonify(notes_list) else: return '', 204 elif measure_type in ['input', 'math', 'output', 'pid']: current_app.config['INFLUXDB_USER'] = INFLUXDB_USER current_app.config['INFLUXDB_PASSWORD'] = INFLUXDB_PASSWORD current_app.config['INFLUXDB_DATABASE'] = INFLUXDB_DATABASE current_app.config['INFLUXDB_TIMEOUT'] = 5 dbcon = influx_db.connection if measure_type in ['input', 'math', 'pid']: measure = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == measurement_id).first() elif measure_type == 'output': measure = Output.query.filter( Output.unique_id == unique_id).first() else: measure = None if not measure: return "Could not find measurement" if measure: conversion = Conversion.query.filter( Conversion.unique_id == measure.conversion_id).first() else: conversion = None channel, unit, measurement = return_measurement_info( measure, conversion) if hasattr(measure, 'measurement_type') and measure.measurement_type == 'setpoint': setpoint_pid = PID.query.filter(PID.unique_id == measure.device_id).first() if setpoint_pid and ',' in setpoint_pid.measurement: pid_measurement = setpoint_pid.measurement.split(',')[1] setpoint_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == pid_measurement).first() if setpoint_measurement: conversion = Conversion.query.filter( Conversion.unique_id == setpoint_measurement.conversion_id).first() _, unit, measurement = return_measurement_info(setpoint_measurement, conversion) try: query_str = query_string( unit, unique_id, measure=measurement, channel=channel, past_sec=past_seconds) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw if 'series' in raw_data: return jsonify(raw_data['series'][0]['values']) else: return '', 204 except Exception as e: logger.debug("URL for 'past_data' raised and error: " "{err}".format(err=e)) return '', 204
def async_data(device_id, device_type, measurement_id, start_seconds, end_seconds): """ Return data from start_seconds to end_seconds from influxdb. Used for asynchronous graph display of many points (up to millions). """ if device_type == 'tag': notes_list = [] tag = NoteTags.query.filter(NoteTags.unique_id == device_id).first() start = datetime.datetime.utcfromtimestamp(float(start_seconds)) if end_seconds == '0': end = datetime.datetime.utcnow() else: end = datetime.datetime.utcfromtimestamp(float(end_seconds)) notes = Notes.query.filter( and_(Notes.date_time >= start, Notes.date_time <= end)).all() for each_note in notes: if tag.unique_id in each_note.tags.split(','): notes_list.append( [each_note.date_time.strftime("%Y-%m-%dT%H:%M:%S.000000000Z"), each_note.name, each_note.note]) if notes_list: return jsonify(notes_list) else: return '', 204 current_app.config['INFLUXDB_USER'] = INFLUXDB_USER current_app.config['INFLUXDB_PASSWORD'] = INFLUXDB_PASSWORD current_app.config['INFLUXDB_DATABASE'] = INFLUXDB_DATABASE current_app.config['INFLUXDB_TIMEOUT'] = 5 dbcon = influx_db.connection if device_type in ['input', 'math', 'pid']: measure = DeviceMeasurements.query.filter(DeviceMeasurements.unique_id == measurement_id).first() elif device_type == 'output': measure = Output.query.filter(Output.unique_id == device_id).first() else: measure = None if not measure: return "Could not find measurement" if measure: conversion = Conversion.query.filter( Conversion.unique_id == measure.conversion_id).first() else: conversion = None channel, unit, measurement = return_measurement_info( measure, conversion) # Set the time frame to the past year if start/end not specified if start_seconds == '0' and end_seconds == '0': # Get how many points there are in the past year query_str = query_string( unit, device_id, measure=measurement, channel=channel, value='COUNT') if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw count_points = raw_data['series'][0]['values'][0][1] # Get the timestamp of the first point in the past year query_str = query_string( unit, device_id, measure=measurement, channel=channel, limit=1) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw first_point = raw_data['series'][0]['values'][0][0] end = datetime.datetime.utcnow() end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') # Set the time frame to the past start epoch to now elif start_seconds != '0' and end_seconds == '0': start = datetime.datetime.utcfromtimestamp(float(start_seconds)) start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') end = datetime.datetime.utcnow() end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') query_str = query_string( unit, device_id, measure=measurement, channel=channel, value='COUNT', start_str=start_str, end_str=end_str) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw count_points = raw_data['series'][0]['values'][0][1] # Get the timestamp of the first point in the past year query_str = query_string( unit, device_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str, limit=1) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw first_point = raw_data['series'][0]['values'][0][0] else: start = datetime.datetime.utcfromtimestamp(float(start_seconds)) start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') end = datetime.datetime.utcfromtimestamp(float(end_seconds)) end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') query_str = query_string( unit, device_id, measure=measurement, channel=channel, value='COUNT', start_str=start_str, end_str=end_str) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw count_points = raw_data['series'][0]['values'][0][1] # Get the timestamp of the first point in the past year query_str = query_string( unit, device_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str, limit=1) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw first_point = raw_data['series'][0]['values'][0][0] start = datetime.datetime.strptime(first_point[:26], '%Y-%m-%dT%H:%M:%S.%f') start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') logger.debug('Count = {}'.format(count_points)) logger.debug('Start = {}'.format(start)) logger.debug('End = {}'.format(end)) # How many seconds between the start and end period time_difference_seconds = (end - start).total_seconds() logger.debug('Difference seconds = {}'.format(time_difference_seconds)) # If there are more than 700 points in the time frame, we need to group # data points into 700 groups with points averaged in each group. if count_points > 700: # Average period between input reads seconds_per_point = time_difference_seconds / count_points logger.debug('Seconds per point = {}'.format(seconds_per_point)) # How many seconds to group data points in group_seconds = int(time_difference_seconds / 700) logger.debug('Group seconds = {}'.format(group_seconds)) try: query_str = query_string( unit, device_id, measure=measurement, channel=channel, value='MEAN', start_str=start_str, end_str=end_str, group_sec=group_seconds) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw return jsonify(raw_data['series'][0]['values']) except Exception as e: logger.error("URL for 'async_data' raised and error: " "{err}".format(err=e)) return '', 204 else: try: query_str = query_string( unit, device_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw return jsonify(raw_data['series'][0]['values']) except Exception as e: logger.error("URL for 'async_data' raised and error: " "{err}".format(err=e)) return '', 204
def math_vapor_pressure_deficit(self, measurement_dict): vpd_pa = None critical_error = False success_temp, temperature = self.get_measurements_from_id( self.unique_id_1, self.unique_measurement_id_1) success_hum, humidity = self.get_measurements_from_id( self.unique_id_2, self.unique_measurement_id_2) if success_temp and success_hum: vpd_temperature_celsius = float(temperature[1]) vpd_humidity_percent = float(humidity[1]) measurement_temp = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_1) if not measurement_temp: self.logger.error("Could not find temperature measurement") measurement_temp = None critical_error = True if measurement_temp and measurement_temp.unit != 'C': vpd_temperature_celsius, status = self.is_measurement_unit( measurement_temp.unit, 'C', vpd_temperature_celsius) if status == 'error': critical_error = True measurement_hum = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.unique_measurement_id_2) if not measurement_hum: self.logger.error("Could not find humidity measurement") measurement_hum = None critical_error = True if measurement_hum and measurement_hum.unit != 'percent': vpd_humidity_percent, status = self.is_measurement_unit( measurement_hum.unit, 'percent', vpd_humidity_percent) if status == 'error': critical_error = True try: if not critical_error: vpd_pa = calculate_vapor_pressure_deficit( vpd_temperature_celsius, vpd_humidity_percent) else: self.logger.error( "One or more critical errors prevented the " "vapor pressure deficit from being calculated") except TypeError as err: self.logger.error("TypeError: {msg}".format(msg=err)) if vpd_pa: math_dev_measurement = self.device_measurements.first() conversion = db_retrieve_table_daemon( Conversion, unique_id=math_dev_measurement.conversion_id) channel, unit, measurement = return_measurement_info( math_dev_measurement, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': vpd_pa } else: self.error_not_within_max_age() return measurement_dict
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 graph_y_axes(dict_measurements): """ Determine which y-axes to use for each Graph """ y_axes = {} device_measurements = DeviceMeasurements.query.all() graph = Dashboard.query.all() input_dev = Input.query.all() math = Math.query.all() output = Output.query.all() pid = PID.query.all() devices_list = [input_dev, math, output, pid] # Iterate through each Dashboard object for each_graph in graph: # Iterate through device tables for each_device in devices_list: if each_device == input_dev: dev_and_measure_ids = each_graph.input_ids_measurements.split(';') elif each_device == math: dev_and_measure_ids = each_graph.math_ids.split(';') elif each_device == output: dev_and_measure_ids = each_graph.output_ids.split(';') elif each_device == pid: dev_and_measure_ids = each_graph.pid_ids.split(';') else: dev_and_measure_ids = [] # Iterate through each set of ID and measurement of the # dashboard element for each_id_measure in dev_and_measure_ids: if each_device in [input_dev, math, pid] and ',' in each_id_measure: if each_graph.unique_id not in y_axes: y_axes[each_graph.unique_id] = [] measure_id = each_id_measure.split(',')[1] for each_measurement in device_measurements: if each_measurement.unique_id == measure_id: unit = None if each_measurement.measurement_type == 'setpoint': setpoint_pid = PID.query.filter(PID.unique_id == each_measurement.device_id).first() if setpoint_pid and ',' in setpoint_pid.measurement: pid_measurement = setpoint_pid.measurement.split(',')[1] setpoint_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == pid_measurement).first() if setpoint_measurement: conversion = Conversion.query.filter( Conversion.unique_id == setpoint_measurement.conversion_id).first() _, unit, measurement = return_measurement_info(setpoint_measurement, conversion) else: conversion = Conversion.query.filter( Conversion.unique_id == each_measurement.conversion_id).first() _, unit, _ = return_measurement_info(each_measurement, conversion) if unit: if not y_axes[each_graph.unique_id]: y_axes[each_graph.unique_id] = [unit] elif y_axes[each_graph.unique_id] and unit not in y_axes[each_graph.unique_id]: y_axes.setdefault(each_graph.unique_id, []).append(unit) elif each_device == output and ',' in each_id_measure: if each_graph.unique_id not in y_axes: y_axes[each_graph.unique_id] = [] output_id = each_id_measure.split(',')[0] for each_output in output: if each_output.unique_id == output_id: if not y_axes[each_graph.unique_id]: y_axes[each_graph.unique_id] = [each_output.unit] elif y_axes[each_graph.unique_id] and each_output.unit not in y_axes[each_graph.unique_id]: y_axes.setdefault(each_graph.unique_id, []).append(each_output.unit) elif len(each_id_measure.split(',')) == 4: if each_graph.unique_id not in y_axes: y_axes[each_graph.unique_id] = [] unit = each_id_measure.split(',')[2] if not y_axes[each_graph.unique_id]: y_axes[each_graph.unique_id] = [unit] elif y_axes[each_graph.unique_id] and unit not in y_axes[each_graph.unique_id]: y_axes.setdefault(each_graph.unique_id, []).append(unit) elif len(each_id_measure.split(',')) == 2: if each_graph.unique_id not in y_axes: y_axes[each_graph.unique_id] = [] unique_id = each_id_measure.split(',')[0] measurement = each_id_measure.split(',')[1] y_axes[each_graph.unique_id] = check_func( each_device, unique_id, y_axes[each_graph.unique_id], measurement, dict_measurements, device_measurements, input_dev, output, math) elif len(each_id_measure.split(',')) == 3: if each_graph.unique_id not in y_axes: y_axes[each_graph.unique_id] = [] unique_id = each_id_measure.split(',')[0] measurement = each_id_measure.split(',')[1] unit = each_id_measure.split(',')[2] y_axes[each_graph.unique_id] = check_func( each_device, unique_id, y_axes[each_graph.unique_id], measurement, dict_measurements, device_measurements, input_dev, output, math, unit=unit) return y_axes
def async_data(device_id, device_type, measurement_id, start_seconds, end_seconds): """ Return data from start_seconds to end_seconds from influxdb. Used for asynchronous graph display of many points (up to millions). """ if device_type == 'tag': notes_list = [] tag = NoteTags.query.filter(NoteTags.unique_id == device_id).first() start = datetime.datetime.utcfromtimestamp(float(start_seconds)) if end_seconds == '0': end = datetime.datetime.utcnow() else: end = datetime.datetime.utcfromtimestamp(float(end_seconds)) notes = Notes.query.filter( and_(Notes.date_time >= start, Notes.date_time <= end)).all() for each_note in notes: if tag.unique_id in each_note.tags.split(','): notes_list.append([ each_note.date_time.strftime( "%Y-%m-%dT%H:%M:%S.000000000Z"), each_note.name, each_note.note ]) if notes_list: return jsonify(notes_list) else: return '', 204 dbcon = InfluxDBClient(INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER, INFLUXDB_PASSWORD, INFLUXDB_DATABASE) if device_type in ['input', 'math', 'output', 'pid']: measure = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == measurement_id).first() else: measure = None if not measure: return "Could not find measurement" if measure: conversion = Conversion.query.filter( Conversion.unique_id == measure.conversion_id).first() else: conversion = None channel, unit, measurement = return_measurement_info(measure, conversion) # Set the time frame to the past year if start/end not specified if start_seconds == '0' and end_seconds == '0': # Get how many points there are in the past year query_str = query_string(unit, device_id, measure=measurement, channel=channel, value='COUNT') if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw count_points = raw_data['series'][0]['values'][0][1] # Get the timestamp of the first point in the past year query_str = query_string(unit, device_id, measure=measurement, channel=channel, limit=1) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw first_point = raw_data['series'][0]['values'][0][0] end = datetime.datetime.utcnow() end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') # Set the time frame to the past start epoch to now elif start_seconds != '0' and end_seconds == '0': start = datetime.datetime.utcfromtimestamp(float(start_seconds)) start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') end = datetime.datetime.utcnow() end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') query_str = query_string(unit, device_id, measure=measurement, channel=channel, value='COUNT', start_str=start_str, end_str=end_str) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw count_points = raw_data['series'][0]['values'][0][1] # Get the timestamp of the first point in the past year query_str = query_string(unit, device_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str, limit=1) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw first_point = raw_data['series'][0]['values'][0][0] else: start = datetime.datetime.utcfromtimestamp(float(start_seconds)) start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') end = datetime.datetime.utcfromtimestamp(float(end_seconds)) end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') query_str = query_string(unit, device_id, measure=measurement, channel=channel, value='COUNT', start_str=start_str, end_str=end_str) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw count_points = raw_data['series'][0]['values'][0][1] # Get the timestamp of the first point in the past year query_str = query_string(unit, device_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str, limit=1) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw first_point = raw_data['series'][0]['values'][0][0] start = datetime.datetime.strptime( influx_time_str_to_milliseconds(first_point), '%Y-%m-%dT%H:%M:%S.%f') start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') logger.debug('Count = {}'.format(count_points)) logger.debug('Start = {}'.format(start)) logger.debug('End = {}'.format(end)) # How many seconds between the start and end period time_difference_seconds = (end - start).total_seconds() logger.debug('Difference seconds = {}'.format(time_difference_seconds)) # If there are more than 700 points in the time frame, we need to group # data points into 700 groups with points averaged in each group. if count_points > 700: # Average period between input reads seconds_per_point = time_difference_seconds / count_points logger.debug('Seconds per point = {}'.format(seconds_per_point)) # How many seconds to group data points in group_seconds = int(time_difference_seconds / 700) logger.debug('Group seconds = {}'.format(group_seconds)) try: query_str = query_string(unit, device_id, measure=measurement, channel=channel, value='MEAN', start_str=start_str, end_str=end_str, group_sec=group_seconds) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw return jsonify(raw_data['series'][0]['values']) except Exception as e: logger.error("URL for 'async_data' raised and error: " "{err}".format(err=e)) return '', 204 else: try: query_str = query_string(unit, device_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str) if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw return jsonify(raw_data['series'][0]['values']) except Exception as e: logger.error("URL for 'async_data' raised and error: " "{err}".format(err=e)) return '', 204
def export_data(unique_id, measurement_id, start_seconds, end_seconds): """ Return data from start_seconds to end_seconds from influxdb. Used for exporting data. """ dbcon = InfluxDBClient(INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER, INFLUXDB_PASSWORD, INFLUXDB_DATABASE, timeout=100) output = Output.query.filter(Output.unique_id == unique_id).first() input_dev = Input.query.filter(Input.unique_id == unique_id).first() math = Math.query.filter(Math.unique_id == unique_id).first() if output: name = output.name elif input_dev: name = input_dev.name elif math: name = math.name else: name = None device_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == measurement_id).first() if device_measurement: conversion = Conversion.query.filter( Conversion.unique_id == device_measurement.conversion_id).first() else: conversion = None channel, unit, measurement = return_measurement_info( device_measurement, conversion) utc_offset_timedelta = datetime.datetime.utcnow() - datetime.datetime.now() start = datetime.datetime.fromtimestamp(float(start_seconds)) start += utc_offset_timedelta start_str = start.strftime('%Y-%m-%dT%H:%M:%S.%fZ') end = datetime.datetime.fromtimestamp(float(end_seconds)) end += utc_offset_timedelta end_str = end.strftime('%Y-%m-%dT%H:%M:%S.%fZ') query_str = query_string(unit, unique_id, measure=measurement, channel=channel, start_str=start_str, end_str=end_str) if query_str == 1: flash('Invalid query string', 'error') return redirect(url_for('routes_page.page_export')) raw_data = dbcon.query(query_str).raw if not raw_data or 'series' not in raw_data or not raw_data['series']: flash('No measurements to export in this time period', 'error') return redirect(url_for('routes_page.page_export')) # Generate column names col_1 = 'timestamp (UTC)' col_2 = '{name} {meas} ({id})'.format(name=name, meas=measurement, id=unique_id) csv_filename = '{id}_{name}_{meas}.csv'.format(id=unique_id, name=name, meas=measurement) from flask import Response import csv from io import StringIO def iter_csv(data): """ Stream CSV file to user for download """ line = StringIO() writer = csv.writer(line) writer.writerow([col_1, col_2]) for csv_line in data: writer.writerow( [str(csv_line[0][:-4]).replace('T', ' '), csv_line[1]]) line.seek(0) yield line.read() line.truncate(0) line.seek(0) response = Response(iter_csv(raw_data['series'][0]['values']), mimetype='text/csv') response.headers[ 'Content-Disposition'] = 'attachment; filename="{}"'.format( csv_filename) return response
def last_data_pid(pid_id, input_period): """Return the most recent time and value from influxdb""" if not str_is_float(input_period): return '', 204 try: pid = PID.query.filter(PID.unique_id == pid_id).first() device_id = pid.measurement.split(',')[0] measurement_id = pid.measurement.split(',')[1] actual_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == measurement_id).first() if actual_measurement: actual_cnversion = Conversion.query.filter( Conversion.unique_id == actual_measurement.conversion_id).first() else: actual_cnversion = None (actual_channel, actual_unit, actual_measurement) = return_measurement_info( actual_measurement, actual_cnversion) setpoint_measurement = None setpoint_unit = None setpoint_pid = PID.query.filter(PID.unique_id == pid_id).first() if setpoint_pid and ',' in setpoint_pid.measurement: pid_measurement = setpoint_pid.measurement.split(',')[1] setpoint_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == pid_measurement).first() if setpoint_measurement: conversion = Conversion.query.filter( Conversion.unique_id == setpoint_measurement.conversion_id).first() _, setpoint_unit, _ = return_measurement_info(setpoint_measurement, conversion) p_value = return_point_timestamp( pid_id, 'pid_value', input_period, measurement='pid_p_value') i_value = return_point_timestamp( pid_id, 'pid_value', input_period, measurement='pid_i_value') d_value = return_point_timestamp( pid_id, 'pid_value', input_period, measurement='pid_d_value') pid_value = [p_value[0], '{:.3f}'.format(float(p_value[1]) + float(i_value[1]) + float(d_value[1]))] live_data = { 'activated': pid.is_activated, 'paused': pid.is_paused, 'held': pid.is_held, 'setpoint': return_point_timestamp( pid_id, setpoint_unit, input_period, measurement=setpoint_measurement.measurement), 'pid_p_value': p_value, 'pid_i_value': i_value, 'pid_d_value': d_value, 'pid_pid_value': pid_value, 'duration_time': return_point_timestamp( pid_id, 's', input_period, measurement='duration_time'), 'duty_cycle': return_point_timestamp( pid_id, 'percent', input_period, measurement='duty_cycle'), 'actual': return_point_timestamp( device_id, actual_unit, input_period, measurement=actual_measurement, channel=actual_channel) } return jsonify(live_data) except KeyError: logger.debug("No Data returned form influxdb") return '', 204 except Exception as e: logger.exception("URL for 'last_pid' raised and error: " "{err}".format(err=e)) return '', 204
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) 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) 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 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 math_humidity(self, measurement_dict): pressure_pa = 101325 critical_error = False if self.pressure_pa_id and self.pressure_pa_measure_id: success_pa, pressure = self.get_measurements_from_id( self.pressure_pa_id, self.pressure_pa_measure_id) if success_pa: pressure_pa = int(pressure[1]) # Pressure must be in Pa, convert if not measurement_press = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.pressure_pa_measure_id) if not measurement_press: self.logger.error("Could not find pressure measurement") measurement_press = None critical_error = True if measurement_press and measurement_press.unit != 'Pa': pressure_pa, status = self.is_measurement_unit( measurement_press.unit, 'Pa', pressure_pa) if status == 'error': critical_error = True success_dbt, dry_bulb_t = self.get_measurements_from_id( self.dry_bulb_t_id, self.dry_bulb_t_measure_id) success_wbt, wet_bulb_t = self.get_measurements_from_id( self.wet_bulb_t_id, self.wet_bulb_t_measure_id) if success_dbt and success_wbt: dbt_kelvin = float(dry_bulb_t[1]) wbt_kelvin = float(wet_bulb_t[1]) measurement_db_temp = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.dry_bulb_t_measure_id) if not measurement_db_temp: self.logger.error( "Could not find dry bulb temperature measurement") measurement_db_temp = None critical_error = True if measurement_db_temp and measurement_db_temp.unit != 'K': dbt_kelvin, status = self.is_measurement_unit( measurement_db_temp.unit, 'K', dbt_kelvin) if status == 'error': critical_error = True measurement_wb_temp = db_retrieve_table_daemon( DeviceMeasurements, unique_id=self.wet_bulb_t_measure_id) if not measurement_wb_temp: self.logger.error( "Could not find wet bulb temperature measurement") measurement_wb_temp = None critical_error = True if measurement_wb_temp and measurement_wb_temp.unit != 'K': wbt_kelvin, status = self.is_measurement_unit( measurement_wb_temp.unit, 'K', wbt_kelvin) if status == 'error': critical_error = True # Convert temperatures to Kelvin (already done above) # dbt_kelvin = celsius_to_kelvin(dry_bulb_t_c) # wbt_kelvin = celsius_to_kelvin(wet_bulb_t_c) psypi = None try: if not critical_error: psypi = SI.state("DBT", dbt_kelvin, "WBT", wbt_kelvin, pressure_pa) else: self.logger.error( "One or more critical errors prevented the " "humidity from being calculated") except TypeError as err: self.logger.error("TypeError: {msg}".format(msg=err)) if psypi: 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]) list_measurement = [ specific_enthalpy, humidity, specific_volume, humidity_ratio ] for each_measurement in self.device_measurements.all(): conversion = db_retrieve_table_daemon( Conversion, unique_id=each_measurement.conversion_id) channel, unit, measurement = return_measurement_info( each_measurement, conversion) measurement_dict[channel] = { 'measurement': measurement, 'unit': unit, 'value': list_measurement[channel] } else: self.error_not_within_max_age() return measurement_dict
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 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.log_level_debug = form_mod_math.log_level_debug.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] 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] 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_input'))
def last_data(unique_id, measure_type, measurement_id, period): """Return the most recent time and value from influxdb""" if not str_is_float(period): return '', 204 if measure_type in ['input', 'math', 'output', 'pid']: current_app.config['INFLUXDB_USER'] = INFLUXDB_USER current_app.config['INFLUXDB_PASSWORD'] = INFLUXDB_PASSWORD current_app.config['INFLUXDB_DATABASE'] = INFLUXDB_DATABASE current_app.config['INFLUXDB_TIMEOUT'] = 5 dbcon = influx_db.connection if measure_type in ['input', 'math', 'pid']: measure = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == measurement_id).first() elif measure_type == 'output': measure = Output.query.filter( Output.unique_id == unique_id).first() else: return '', 204 if measure: conversion = Conversion.query.filter( Conversion.unique_id == measure.conversion_id).first() else: conversion = None channel, unit, measurement = return_measurement_info( measure, conversion) if hasattr(measure, 'measurement_type') and measure.measurement_type == 'setpoint': setpoint_pid = PID.query.filter(PID.unique_id == measure.device_id).first() if setpoint_pid and ',' in setpoint_pid.measurement: pid_measurement = setpoint_pid.measurement.split(',')[1] setpoint_measurement = DeviceMeasurements.query.filter( DeviceMeasurements.unique_id == pid_measurement).first() if setpoint_measurement: conversion = Conversion.query.filter( Conversion.unique_id == setpoint_measurement.conversion_id).first() _, unit, measurement = return_measurement_info(setpoint_measurement, conversion) try: if period != '0': query_str = query_string( unit, unique_id, measure=measurement, channel=channel, value='LAST', past_sec=period) else: query_str = query_string( unit, unique_id, measure=measurement, channel=channel, value='LAST') if query_str == 1: return '', 204 raw_data = dbcon.query(query_str).raw number = len(raw_data['series'][0]['values']) time_raw = raw_data['series'][0]['values'][number - 1][0] value = raw_data['series'][0]['values'][number - 1][1] value = float(value) # Convert date-time to epoch (potential bottleneck for data) dt = date_parse(time_raw) timestamp = calendar.timegm(dt.timetuple()) * 1000 live_data = '[{},{}]'.format(timestamp, value) return Response(live_data, mimetype='text/json') except KeyError: logger.debug("No Data returned form influxdb") return '', 204 except Exception as e: logger.exception("URL for 'last_data' raised and error: " "{err}".format(err=e)) return '', 204