Example #1
0
    def get(self, unique_id, unit, channel, past_seconds):
        """
        Return the last measurement found within a duration from the past to the present
        """
        if not utils_general.user_has_permission('view_settings'):
            abort(403)

        if unit not in add_custom_units(Unit.query.all()):
            abort(422, custom='Unit ID not found')
        if channel < 0:
            abort(422, custom='channel must be >= 0')
        if past_seconds < 1:
            abort(422, custom='past_seconds must be >= 1')

        try:
            return_ = read_influxdb_single(unique_id,
                                           unit,
                                           channel,
                                           duration_sec=past_seconds)
            if return_ and len(return_) == 2:
                return {'time': return_[0], 'value': return_[1]}, 200
            else:
                return return_, 200
        except Exception:
            abort(500,
                  message='An exception occurred',
                  error=traceback.format_exc())
Example #2
0
def test_influxdb():
    """Verify measurements can be written and read from influxdb."""
    print("\nTest: test_influxdb")
    device_id = 'ID_ASDF'
    channel = 0
    measurement = 'duty_cycle'
    unit = 'percent'
    written_measurement = 123.45

    measurements_dict = {
        channel: {
            'measurement': measurement,
            'unit': unit,
            'value': written_measurement
        }
    }

    add_measurements_influxdb(device_id, measurements_dict, block=True)

    last_measurement = read_influxdb_single(
        device_id,
        unit,
        0,
        measure=measurement,
        duration_sec=1000,
        value='LAST')

    print(f"Returned measurement: {last_measurement}")

    assert last_measurement

    returned_measurement = last_measurement[1]

    assert returned_measurement == written_measurement
Example #3
0
    def get_last_measurement_pid(self):
        """
        Retrieve the latest input measurement from InfluxDB

        :rtype: None
        """
        self.last_measurement_success = False

        # Get latest measurement from influxdb
        try:
            device_measurement = get_measurement(self.measurement_id)

            if device_measurement:
                conversion = db_retrieve_table_daemon(
                    Conversion, unique_id=device_measurement.conversion_id)
            else:
                conversion = None
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)

            self.last_measurement = read_influxdb_single(
                self.device_id,
                unit,
                channel,
                measure=measurement,
                duration_sec=int(self.max_measure_age),
                value='LAST')

            if self.last_measurement:
                self.last_time = self.last_measurement[0]
                self.last_measurement = self.last_measurement[1]

                utc_dt = datetime.datetime.strptime(
                    self.last_time.split(".")[0], '%Y-%m-%dT%H:%M:%S')
                utc_timestamp = calendar.timegm(utc_dt.timetuple())
                local_timestamp = str(
                    datetime.datetime.fromtimestamp(utc_timestamp))
                self.logger.debug(
                    f"Latest (CH{channel}, Unit: {unit}): {self.last_measurement} @ {local_timestamp}"
                )
                if calendar.timegm(
                        time.gmtime()) - utc_timestamp > self.max_measure_age:
                    sec = calendar.timegm(time.gmtime()) - utc_timestamp
                    self.logger.error(
                        f"Last measurement was {sec} seconds ago, however the maximum "
                        f"measurement age is set to {self.max_measure_age} seconds."
                    )
                self.last_measurement_success = True
            else:
                self.logger.warning("No data returned from influxdb")
        except requests.ConnectionError:
            self.logger.error(
                "Failed to read measurement from the influxdb database: Could not connect."
            )
        except Exception:
            self.logger.exception(
                "Exception while reading measurement from the influxdb database"
            )
Example #4
0
    def initialize(self):
        self.setup_output_variables(OUTPUT_INFORMATION)

        if not self.options_channels['pwm_command'][0]:
            self.logger.error("Output must have Python Code set")
            return

        try:
            full_command, file_run = generate_code(
                self.options_channels['pwm_command'][0], self.unique_id)

            module_name = "mycodo.output.{}".format(
                os.path.basename(file_run).split('.')[0])
            spec = importlib.util.spec_from_file_location(
                module_name, file_run)
            output_run_pwm = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(output_run_pwm)
            self.output_run_python_pwm = output_run_pwm.OutputRun(
                self.logger, self.unique_id)

            self.output_setup = True

            if self.options_channels['state_startup'][0] == 0:
                self.output_switch('off')
            elif self.options_channels['state_startup'][0] == 'set_duty_cycle':
                self.output_switch(
                    'on', amount=self.options_channels['startup_value'][0])
            elif self.options_channels['state_startup'][
                    0] == 'last_duty_cycle':
                device_measurement = db_retrieve_table_daemon(
                    DeviceMeasurements).filter(
                        and_(DeviceMeasurements.device_id == self.unique_id,
                             DeviceMeasurements.channel == 0)).first()

                last_measurement = None
                if device_measurement:
                    channel, unit, measurement = return_measurement_info(
                        device_measurement, None)
                    last_measurement = read_influxdb_single(
                        self.unique_id,
                        unit,
                        channel,
                        measure=measurement,
                        value='LAST')

                if last_measurement:
                    self.logger.info(
                        "Setting startup duty cycle to last known value of {dc} %"
                        .format(dc=last_measurement[1]))
                    self.output_switch('on', amount=last_measurement[1])
                else:
                    self.logger.error(
                        "Output instructed at startup to be set to "
                        "the last known duty cycle, but a last known "
                        "duty cycle could not be found in the measurement "
                        "database")
        except Exception:
            self.logger.exception("Could not set up output")
Example #5
0
    def loop(self):
        if self.timer_loop > time.time():
            return

        while self.timer_loop < time.time():
            self.timer_loop += self.period

        measurements = []
        for each_id_set in self.select_measurement:
            device_device_id = each_id_set.split(",")[0]
            device_measure_id = each_id_set.split(",")[1]

            device_measurement = get_measurement(device_measure_id)

            if not device_measurement:
                self.logger.error("Could not find Device Measurement")
                return

            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)

            last_measurement = read_influxdb_single(
                device_device_id,
                unit,
                channel,
                measure=measurement,
                duration_sec=self.max_measure_age,
                value='LAST')

            if not last_measurement:
                self.logger.error(
                    "Could not find measurement within the set Max Age")
                return False
            else:
                measurements.append(last_measurement[1])

        sum_measurements = float(sum(measurements))

        measurement_dict = {
            0: {
                'measurement': self.channels_measurement[0].measurement,
                'unit': self.channels_measurement[0].unit,
                'value': sum_measurements
            }
        }

        if measurement_dict:
            self.logger.debug(
                "Adding measurements to InfluxDB with ID {}: {}".format(
                    self.unique_id, measurement_dict))
            add_measurements_influxdb(self.unique_id, measurement_dict)
        else:
            self.logger.debug(
                "No measurements to add to InfluxDB with ID {}".format(
                    self.unique_id))
Example #6
0
    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")
Example #7
0
    def initialize(self):
        import pigpio

        self.pigpio = pigpio

        self.setup_output_variables(OUTPUT_INFORMATION)

        error = []
        if self.options_channels['pin'][0] is None:
            error.append("Pin must be set")
        if self.options_channels['pwm_hertz'][0] <= 0:
            error.append("PWM Hertz must be a positive value")
        if error:
            for each_error in error:
                self.logger.error(each_error)
            return

        try:
            self.pwm_output = self.pigpio.pi()
            if not self.pwm_output.connected:
                self.logger.error("Could not connect to pigpiod")
                self.pwm_output = None
                return
            if self.options_channels['pwm_library'][0] == 'pigpio_hardware':
                self.pwm_output.hardware_PWM(
                    self.options_channels['pin'][0],
                    self.options_channels['pwm_hertz'][0], 0)
                self.logger.info(
                    "Output setup on Hardware pin {pin} at {hz} Hertz".format(
                        pin=self.options_channels['pin'][0],
                        hz=self.options_channels['pwm_hertz'][0]))
            elif self.options_channels['pwm_library'][0] == 'pigpio_any':
                self.pwm_output.set_PWM_frequency(
                    self.options_channels['pin'][0],
                    self.options_channels['pwm_hertz'][0])
                self.logger.info(
                    "Output setup on Any pin {pin} at {hz} Hertz".format(
                        pin=self.options_channels['pin'][0],
                        hz=self.options_channels['pwm_hertz'][0]))

            self.output_setup = True

            state_string = ""
            if self.options_channels['state_startup'][0] == 0:
                self.output_switch('off')
                self.logger.info("PWM turned off (0 % duty cycle)")
            elif self.options_channels['state_startup'][0] == 'set_duty_cycle':
                self.output_switch(
                    'on', amount=self.options_channels['startup_value'][0])
                self.logger.info(
                    "PWM set to {} % duty cycle (user-specified value)".format(
                        self.options_channels['startup_value'][0]))
            elif self.options_channels['state_startup'][
                    0] == 'last_duty_cycle':
                device_measurement = db_retrieve_table_daemon(
                    DeviceMeasurements).filter(
                        and_(DeviceMeasurements.device_id == self.unique_id,
                             DeviceMeasurements.channel == 0)).first()

                last_measurement = None
                if device_measurement:
                    channel, unit, measurement = return_measurement_info(
                        device_measurement, None)
                    last_measurement = read_influxdb_single(
                        self.unique_id,
                        unit,
                        channel,
                        measure=measurement,
                        value='LAST')

                if last_measurement:
                    self.logger.info(
                        "PWM set to {} % duty cycle (last known value)".format(
                            last_measurement[1]))
                    self.output_switch('on', amount=last_measurement[1])
                else:
                    self.logger.error(
                        "Output instructed at startup to be set to "
                        "the last known duty cycle, but a last known "
                        "duty cycle could not be found in the measurement "
                        "database")
        except Exception as except_msg:
            self.logger.exception(
                "Output was unable to be setup on pin {pin}: {err}".format(
                    pin=self.options_channels['pin'][0], err=except_msg))
Example #8
0
    def initialize(self):
        import Adafruit_PCA9685

        self.setup_output_variables(OUTPUT_INFORMATION)

        try:
            self.pwm_output = Adafruit_PCA9685.PCA9685(
                address=int(str(self.output.i2c_location), 16),
                busnum=self.output.i2c_bus)

            self.pwm_output.set_pwm_freq(self.pwm_hertz)

            self.output_setup = True
            self.logger.debug("Output setup on bus {} at {}".format(
                self.output.i2c_bus, self.output.i2c_location))

            for i in range(16):
                if self.options_channels['state_startup'][i] == 0:
                    self.logger.debug(
                        "Startup state channel {ch}: off".format(ch=i))
                    self.output_switch('off', output_channel=i)
                elif self.options_channels['state_startup'][
                        i] == 'set_duty_cycle':
                    self.logger.debug(
                        "Startup state channel {ch}: on ({dc:.2f} %)".format(
                            ch=i,
                            dc=self.options_channels['startup_value'][i]))
                    self.output_switch(
                        'on',
                        output_channel=i,
                        amount=self.options_channels['startup_value'][i])
                elif self.options_channels['state_startup'][
                        i] == 'last_duty_cycle':
                    self.logger.debug(
                        "Startup state channel {ch}: last".format(ch=i))
                    device_measurement = db_retrieve_table_daemon(
                        DeviceMeasurements).filter(
                            and_(
                                DeviceMeasurements.device_id == self.unique_id,
                                DeviceMeasurements.channel == i)).first()

                    last_measurement = None
                    if device_measurement:
                        channel, unit, measurement = return_measurement_info(
                            device_measurement, None)
                        last_measurement = read_influxdb_single(
                            self.unique_id,
                            unit,
                            channel,
                            measure=measurement,
                            value='LAST')

                    if last_measurement:
                        self.logger.debug(
                            "Setting channel {ch} startup duty cycle to last known value of {dc} %"
                            .format(ch=i, dc=last_measurement[1]))
                        self.output_switch('on', amount=last_measurement[1])
                    else:
                        self.logger.error(
                            "Output channel {} instructed at startup to be set to "
                            "the last known duty cycle, but a last known "
                            "duty cycle could not be found in the measurement "
                            "database".format(i))
                else:
                    self.logger.debug(
                        "Startup state channel {ch}: no change".format(ch=i))
        except Exception as except_msg:
            self.logger.exception(
                "Output was unable to be setup: {err}".format(err=except_msg))
Example #9
0
    def check_pid(self):
        """Get measurement and apply to PID controller."""
        # If PID is active, retrieve measurement and update
        # the control variable.
        # A PID on hold will sustain the current output and
        # not update the control variable.
        if self.is_activated and (not self.is_paused or not self.is_held):
            self.get_last_measurement_pid()

            if self.last_measurement_success:
                if self.setpoint_tracking_type == 'method' and self.setpoint_tracking_id != '':
                    # Update setpoint using a method
                    this_pid = db_retrieve_table_daemon(
                        PID, unique_id=self.unique_id)

                    now = datetime.datetime.now()

                    method = load_method_handler(self.setpoint_tracking_id,
                                                 self.logger)
                    new_setpoint, ended = method.calculate_setpoint(
                        now, this_pid.method_start_time)
                    self.logger.debug(
                        f"Method {self.setpoint_tracking_id} {method} {now} {this_pid.method_start_time}"
                    )

                    if ended:
                        # point in time is out of method range
                        with session_scope(MYCODO_DB_PATH) as db_session:
                            # Overwrite this_controller with committable connection
                            this_pid = db_session.query(PID).filter(
                                PID.unique_id == self.unique_id).first()

                            self.logger.debug("Ended")
                            # Duration method has ended, reset method_start_time locally and in DB
                            this_pid.method_start_time = None
                            this_pid.method_end_time = None
                            this_pid.is_activated = False
                            db_session.commit()

                            self.is_activated = False
                            self.stop_controller()

                            db_session.commit()

                    if new_setpoint is not None:
                        self.logger.debug(
                            f"New setpoint = {new_setpoint} {ended}")
                        self.PID_Controller.setpoint = new_setpoint
                    else:
                        self.logger.debug(
                            f"New setpoint = default {self.setpoint} {ended}")
                        self.PID_Controller.setpoint = self.setpoint

                if self.setpoint_tracking_type == 'input-math' and self.setpoint_tracking_id != '':
                    # Update setpoint using an Input
                    device_id = self.setpoint_tracking_id.split(',')[0]
                    measurement_id = self.setpoint_tracking_id.split(',')[1]

                    measurement = get_measurement(measurement_id)
                    if not measurement:
                        return False, None

                    conversion = db_retrieve_table_daemon(
                        Conversion, unique_id=measurement.conversion_id)
                    channel, unit, measurement = return_measurement_info(
                        measurement, conversion)

                    last_measurement = read_influxdb_single(
                        device_id,
                        unit,
                        channel,
                        measure=measurement,
                        duration_sec=self.setpoint_tracking_max_age,
                        value='LAST')

                    if last_measurement[1] is not None:
                        self.PID_Controller.setpoint = last_measurement[1]
                    else:
                        self.logger.debug(
                            "Could not find measurement for Setpoint "
                            f"Tracking. Max Age of {self.setpoint_tracking_max_age} exceeded for measuring "
                            f"device ID {device_id} (measurement {measurement_id})"
                        )
                        self.PID_Controller.setpoint = None

                # Calculate new control variable (output) from PID Controller
                self.PID_Controller.update_pid_output(self.last_measurement)

                self.write_pid_values()  # Write variables to database

        # Is PID in a state that allows manipulation of outputs
        if (self.is_activated and self.PID_Controller.setpoint is not None
                and (not self.is_paused or self.is_held)):
            self.manipulate_output()
    def loop(self):
        if self.timer_loop > time.time():
            return

        while self.timer_loop < time.time():
            self.timer_loop += self.period

        measure = []
        for each_id_set in self.select_measurement:
            device_device_id = each_id_set.split(",")[0]
            device_measure_id = each_id_set.split(",")[1]

            device_measurement = get_measurement(device_measure_id)

            if not device_measurement:
                self.logger.error("Could not find Device Measurement")
                return

            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)

            last_measurement = read_influxdb_single(
                device_device_id,
                unit,
                channel,
                measure=measurement,
                duration_sec=self.max_measure_age,
                value='LAST')

            if not last_measurement:
                self.logger.error(
                    "Could not find measurement within the set Max Age for Device {} and Measurement {}"
                    .format(device_device_id, device_measure_id))
                if self.halt_on_missing_measure:
                    self.logger.debug(
                        "Instructed to halt on the first missing measurement. Halting."
                    )
                    return False
            else:
                measure.append(last_measurement[1])

        if len(measure) > 1:
            stat_mean = float(sum(measure) / float(len(measure)))
            stat_median = median(measure)
            stat_minimum = min(measure)
            stat_maximum = max(measure)
            stdev_ = stdev(measure)
            stdev_mean_upper = stat_mean + stdev_
            stdev_mean_lower = stat_mean - stdev_

            list_measurement = [
                stat_mean, stat_median, stat_minimum, stat_maximum, stdev_,
                stdev_mean_upper, stdev_mean_lower
            ]

            for each_channel, each_measurement in self.channels_measurement.items(
            ):
                if each_measurement.is_enabled:
                    channel, unit, measurement = return_measurement_info(
                        each_measurement,
                        self.channels_conversion[each_channel])

                    self.logger.debug(
                        "Saving {} to channel {} with measurement {} and unit {}"
                        .format(list_measurement[each_channel], each_channel,
                                measurement, unit))

                    write_influxdb_value(self.unique_id,
                                         unit,
                                         value=list_measurement[each_channel],
                                         measure=measurement,
                                         channel=each_channel)
        else:
            self.logger.debug(
                "Less than 2 measurements found within Max Age. "
                "Calculations need at least 2 measurements. Not calculating.")