Example #1
0
    def get_method_output(self, method_id):
        """ Get output variable from method """
        this_controller = db_retrieve_table_daemon(
            Trigger, unique_id=self.function_id)
        setpoint, ended = calculate_method_setpoint(
            method_id,
            Trigger,
            this_controller,
            Method,
            MethodData,
            self.logger)

        if setpoint is not None:
            if setpoint > 100:
                setpoint = 100
            elif setpoint < 0:
                setpoint = 0

        if ended:
            with session_scope(MYCODO_DB_PATH) as db_session:
                mod_conditional = db_session.query(Trigger)
                mod_conditional = mod_conditional.filter(
                    Trigger.unique_id == self.function_id).first()
                mod_conditional.is_activated = False
                db_session.commit()
            self.is_activated = False
            self.stop_controller()

        return setpoint, ended
Example #2
0
    def check_pid(self):
        """ Get measurement and apply to PID controller """
        # Ensure the timer ends in the future
        while time.time() > self.timer:
            self.timer = self.timer + self.period

        # If PID is active, retrieve measurement and update
        # the control variable.
        # A PID on hold will sustain the current output and
        # not update the control variable.
        if self.is_activated and (not self.is_paused or not self.is_held):
            self.get_last_measurement()

            if self.last_measurement_success:
                if self.method_id != '':
                    # Update setpoint using a method
                    this_pid = db_retrieve_table_daemon(PID,
                                                        unique_id=self.pid_id)
                    setpoint, ended = calculate_method_setpoint(
                        self.method_id, PID, this_pid, Method, MethodData,
                        self.logger)
                    if ended:
                        self.method_start_act = 'Ended'
                    if setpoint is not None:
                        self.setpoint = setpoint
                    else:
                        self.setpoint = self.default_setpoint

                self.write_setpoint_band()  # Write variables to database

                # Calculate new control variable
                self.control_variable = self.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 (not self.is_paused or self.is_held):
            self.manipulate_output()
Example #3
0
    def run(self):
        try:
            self.running = True
            startup_str = "Activated in {:.1f} ms".format(
                (timeit.default_timer() - self.thread_startup_timer) * 1000)
            if self.is_paused:
                startup_str += ", started Paused"
            elif self.is_held:
                startup_str += ", started Held"
            self.logger.info(startup_str)
            self.ready.set()

            while self.running:

                if (self.method_start_act == 'Ended'
                        and self.method_type == 'Duration'):
                    self.stop_controller(ended_normally=False,
                                         deactivate_pid=True)
                    self.logger.warning(
                        "Method has ended. "
                        "Activate the PID controller to start it again.")

                elif t.time() > self.timer:
                    # Ensure the timer ends in the future
                    while t.time() > self.timer:
                        self.timer = self.timer + self.period

                    # If PID is active, retrieve input measurement and update PID output
                    if self.is_activated and not self.is_paused:
                        self.get_last_measurement()

                        if self.last_measurement_success:
                            # Update setpoint using a method if one is selected
                            if self.method_id:
                                this_controller = db_retrieve_table_daemon(
                                    PID, device_id=self.pid_id)
                                setpoint, ended = calculate_method_setpoint(
                                    self.method_id, PID, this_controller,
                                    Method, MethodData, self.logger)
                                if ended:
                                    self.method_start_act = 'Ended'
                                if setpoint is not None:
                                    self.setpoint = setpoint
                                else:
                                    self.setpoint = self.default_setpoint

                            write_setpoint_db = threading.Thread(
                                target=write_influxdb_value,
                                args=(
                                    self.pid_unique_id,
                                    'setpoint',
                                    self.setpoint,
                                ))
                            write_setpoint_db.start()

                            if self.band:
                                band_min = self.setpoint - self.band
                                write_setpoint_db = threading.Thread(
                                    target=write_influxdb_value,
                                    args=(
                                        self.pid_unique_id,
                                        'setpoint_band_min',
                                        band_min,
                                    ))
                                write_setpoint_db.start()

                                band_max = self.setpoint + self.band
                                write_setpoint_db = threading.Thread(
                                    target=write_influxdb_value,
                                    args=(
                                        self.pid_unique_id,
                                        'setpoint_band_max',
                                        band_max,
                                    ))
                                write_setpoint_db.start()

                            # Update PID and get control variable
                            self.control_variable = self.update_pid_output(
                                self.last_measurement)

                            self.write_pid_values()

                    # If PID is active or on hold, activate outputs
                    if ((self.is_activated and not self.is_paused)
                            or (self.is_activated and self.is_held)):
                        self.manipulate_output()
                t.sleep(0.1)

            # Turn off output used in PID when the controller is deactivated
            if self.raise_output_id and self.direction in ['raise', 'both']:
                self.control.relay_off(self.raise_output_id,
                                       trigger_conditionals=True)
            if self.lower_output_id and self.direction in ['lower', 'both']:
                self.control.relay_off(self.lower_output_id,
                                       trigger_conditionals=True)

            self.running = False
            self.logger.info("Deactivated in {:.1f} ms".format(
                (timeit.default_timer() - self.thread_shutdown_timer) * 1000))
        except Exception as except_msg:
            self.logger.exception("Run Error: {err}".format(err=except_msg))
Example #4
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()

            if self.last_measurement_success:
                if self.method_id != '':
                    # Update setpoint using a method
                    this_pid = db_retrieve_table_daemon(
                        PID, unique_id=self.unique_id)
                    setpoint, ended = calculate_method_setpoint(
                        self.method_id, PID, this_pid, Method, MethodData,
                        self.logger)
                    if ended:
                        self.method_start_act = 'Ended'
                    if setpoint is not None:
                        self.setpoint = setpoint
                    else:
                        self.setpoint = self.default_setpoint

                # If autotune activated, determine control variable (output) from autotune
                if self.autotune_activated:
                    if not self.autotune.run(self.last_measurement):
                        self.control_variable = self.autotune.output

                        if self.autotune_debug:
                            self.logger.info('')
                            self.logger.info("state: {}".format(
                                self.autotune.state))
                            self.logger.info("output: {}".format(
                                self.autotune.output))
                    else:
                        # Autotune has finished
                        timestamp = time.time() - self.autotune_timestamp
                        self.logger.info('')
                        self.logger.info('time:  {0} min'.format(
                            round(timestamp / 60)))
                        self.logger.info('state: {0}'.format(
                            self.autotune.state))

                        if self.autotune.state == PIDAutotune.STATE_SUCCEEDED:
                            for rule in self.autotune.tuning_rules:
                                params = self.autotune.get_pid_parameters(rule)
                                self.logger.info('')
                                self.logger.info('rule: {0}'.format(rule))
                                self.logger.info('Kp: {0}'.format(params.Kp))
                                self.logger.info('Ki: {0}'.format(params.Ki))
                                self.logger.info('Kd: {0}'.format(params.Kd))

                        self.stop_controller(deactivate_pid=True)
                else:
                    # Calculate new control variable (output) from PID Controller

                    # Original PID method
                    self.control_variable = self.update_pid_output(
                        self.last_measurement)

                    # New PID method (untested)
                    # self.control_variable = self.PID_Controller.calc(
                    #     self.last_measurement, self.setpoint)

                self.write_pid_values()  # Write variables to database

        # Is PID in a state that allows manipulation of outputs
        if self.is_activated and (not self.is_paused or self.is_held):
            self.manipulate_output()
Example #5
0
    def run(self):
        self.running = True
        self.logger.info("Activated in {:.1f} ms".format(
            (timeit.default_timer() - self.thread_startup_timer) * 1000))
        self.ready.set()

        while self.running:

            # Timer is set to react at a specific hour and minute of the day
            if self.timer_type == 'time':
                if (int(self.start_hour) == datetime.datetime.now().hour and
                        int(self.start_minute) == datetime.datetime.now().minute):
                    # Ensure this is triggered only once at this specific time
                    if self.date_timer_not_executed:
                        self.date_timer_not_executed = False
                        message = "At {st}, turn Output {id} {state}".format(
                            st=self.time_start,
                            id=self.output_id,
                            state=self.state)
                        if self.state == 'on' and self.duration_on:
                            message += " for {sec} seconds".format(
                                sec=self.duration_on)
                        else:
                            self.duration_on = 0
                        self.logger.debug(message)

                        modulate_output = threading.Thread(
                            target=self.control.output_on_off,
                            args=(self.output_id,
                                  self.state,),
                            kwargs={'duration': self.duration_on})
                        modulate_output.start()
                elif not self.date_timer_not_executed:
                    self.date_timer_not_executed = True

            # Timer is set to react at a specific time duration of the day
            elif self.timer_type == 'timespan':
                if time_between_range(self.time_start, self.time_end):
                    current_output_state = self.control.relay_state(self.output_id)
                    if self.state != current_output_state:
                        message = "Output {output} should be {state}, but is " \
                                  "{cstate}. Turning {state}.".format(
                                    output=self.output_id,
                                    state=self.state,
                                    cstate=current_output_state)
                        modulate_output = threading.Thread(
                            target=self.control.output_on_off,
                            args=(self.output_id,
                                  self.state,))
                        modulate_output.start()
                        self.logger.debug(message)

            # Timer is a simple on/off duration timer
            elif self.timer_type == 'duration':
                if time.time() > self.duration_timer:
                    self.duration_timer = (time.time() +
                                           self.duration_on +
                                           self.duration_off)
                    self.logger.debug("Turn Output {output} on for {onsec} "
                                      "seconds, then off for {offsec} "
                                      "seconds".format(
                                        output=self.output_id,
                                        onsec=self.duration_on,
                                        offsec=self.duration_off))
                    output_on = threading.Thread(target=self.control.relay_on,
                                                args=(self.output_id,
                                                      self.duration_on,))
                    output_on.start()

            # Timer is a PWM Method timer
            elif self.timer_type == 'pwm_method':
                try:
                    if time.time() > self.pwm_method_timer:
                        if self.method_start_act == 'Ended':
                            self.stop_controller(ended_normally=False, deactivate_timer=True)
                            self.logger.info(
                                "Method has ended. "
                                "Activate the Timer controller to start it again.")
                        else:
                            this_controller = db_retrieve_table_daemon(
                                Timer, device_id=self.timer_id)
                            setpoint, ended = calculate_method_setpoint(
                                self.method_id,
                                Timer,
                                this_controller,
                                Method,
                                MethodData,
                                self.logger)
                            if ended:
                                self.method_start_act = 'Ended'
                            if setpoint > 100:
                                setpoint = 100
                            elif setpoint < 0:
                                setpoint = 0
                            self.logger.debug(
                                "Turn Output {output} to a PWM duty cycle of "
                                "{dc:.1f} %".format(
                                    output=self.output_id,
                                    dc=setpoint))
                            # Activate pwm with calculated duty cycle
                            self.control.relay_on(
                                self.output_id,
                                duty_cycle=setpoint)
                        self.pwm_method_timer = time.time() + self.method_period
                except Exception:
                    self.logger.exception(1)

            time.sleep(0.1)

        self.control.relay_off(self.output_id)
        self.running = False
        self.logger.info("Deactivated in {:.1f} ms".format(
            (timeit.default_timer() - self.thread_shutdown_timer) * 1000))
Example #6
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)
                    new_setpoint, ended = calculate_method_setpoint(
                        self.setpoint_tracking_id,
                        PID,
                        this_pid,
                        Method,
                        MethodData,
                        self.logger)
                    if ended:
                        self.method_start_act = 'Ended'
                    if new_setpoint is not None:
                        self.PID_Controller.setpoint = new_setpoint
                    else:
                        self.PID_Controller.setpoint = self.setpoint

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

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

                    last_measurement = read_last_influxdb(
                        device_id,
                        measurement.unit,
                        measurement.channel,
                        measure=measurement.measurement,
                        duration_sec=self.setpoint_tracking_max_age)

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

                # If autotune activated, determine control variable (output) from autotune
                if self.autotune_activated:
                    if not self.autotune.run(self.last_measurement):
                        self.PID_Controller.control_variable = self.autotune.output

                        if self.autotune_debug:
                            self.logger.info('')
                            self.logger.info("state: {}".format(self.autotune.state))
                            self.logger.info("output: {}".format(self.autotune.output))
                    else:
                        # Autotune has finished
                        timestamp = time.time() - self.autotune_timestamp
                        self.logger.info('')
                        self.logger.info('time:  {0} min'.format(round(timestamp / 60)))
                        self.logger.info('state: {0}'.format(self.autotune.state))

                        if self.autotune.state == PIDAutotune.STATE_SUCCEEDED:
                            for rule in self.autotune.tuning_rules:
                                params = self.autotune.get_pid_parameters(rule)
                                self.logger.info('')
                                self.logger.info('rule: {0}'.format(rule))
                                self.logger.info('Kp: {0}'.format(params.Kp))
                                self.logger.info('Ki: {0}'.format(params.Ki))
                                self.logger.info('Kd: {0}'.format(params.Kd))

                        self.stop_controller(deactivate_pid=True)
                else:
                    # Calculate new control variable (output) from PID Controller
                    self.PID_Controller.update_pid_output(self.last_measurement)

                self.write_pid_values()  # Write variables to database

        # Is PID in a state that allows manipulation of outputs
        if (self.is_activated and
                self.PID_Controller.setpoint is not None and
                (not self.is_paused or self.is_held)):
            self.manipulate_output()
Example #7
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)
                    new_setpoint, ended = calculate_method_setpoint(
                        self.setpoint_tracking_id, PID, this_pid, Method,
                        MethodData, self.logger)
                    if ended:
                        self.method_start_act = 'Ended'
                    if new_setpoint is not None:
                        self.PID_Controller.setpoint = new_setpoint
                    else:
                        self.PID_Controller.setpoint = self.setpoint

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

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

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

                    last_measurement = read_last_influxdb(
                        device_id,
                        unit,
                        channel,
                        measure=measurement,
                        duration_sec=self.setpoint_tracking_max_age)

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

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

                self.write_pid_values()  # Write variables to database

        # Is PID in a state that allows manipulation of outputs
        if (self.is_activated and self.PID_Controller.setpoint is not None
                and (not self.is_paused or self.is_held)):
            self.manipulate_output()
Example #8
0
    def check_pid(self):
        """ Get measurement and apply to PID controller """
        # Ensure the timer ends in the future
        while time.time() > self.timer:
            self.timer = self.timer + self.period

        # If PID is active, retrieve measurement and update
        # the control variable.
        # A PID on hold will sustain the current output and
        # not update the control variable.
        if self.is_activated and (not self.is_paused or not self.is_held):
            self.get_last_measurement()

            if self.last_measurement_success:
                if self.method_id != '':
                    # Update setpoint using a method
                    this_pid = db_retrieve_table_daemon(
                        PID, unique_id=self.pid_id)
                    setpoint, ended = calculate_method_setpoint(
                        self.method_id,
                        PID,
                        this_pid,
                        Method,
                        MethodData,
                        self.logger)
                    if ended:
                        self.method_start_act = 'Ended'
                    if setpoint is not None:
                        self.setpoint = setpoint
                    else:
                        self.setpoint = self.default_setpoint

                # If autotune activated, determine control variable (output) from autotune
                if self.autotune_activated:
                    if not self.autotune.run(self.last_measurement):
                        self.control_variable = self.autotune.output

                        if self.autotune_debug:
                            self.logger.info('')
                            self.logger.info("state: {}".format(self.autotune.state))
                            self.logger.info("output: {}".format(self.autotune.output))
                    else:
                        # Autotune has finished
                        timestamp = time.time() - self.autotune_timestamp
                        self.logger.info('')
                        self.logger.info('time:  {0} min'.format(round(timestamp / 60)))
                        self.logger.info('state: {0}'.format(self.autotune.state))

                        if self.autotune.state == PIDAutotune.STATE_SUCCEEDED:
                            for rule in self.autotune.tuning_rules:
                                params = self.autotune.get_pid_parameters(rule)
                                self.logger.info('')
                                self.logger.info('rule: {0}'.format(rule))
                                self.logger.info('Kp: {0}'.format(params.Kp))
                                self.logger.info('Ki: {0}'.format(params.Ki))
                                self.logger.info('Kd: {0}'.format(params.Kd))

                        self.stop_controller(deactivate_pid=True)
                else:
                    # Calculate new control variable (output) from PID Controller

                    # Original PID method
                    self.control_variable = self.update_pid_output(
                        self.last_measurement)

                    # New PID method (untested)
                    # self.control_variable = self.PID_Controller.calc(
                    #     self.last_measurement, self.setpoint)

                self.write_pid_values()  # Write variables to database

        # Is PID in a state that allows manipulation of outputs
        if self.is_activated and (not self.is_paused or self.is_held):
            self.manipulate_output()