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
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()
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))
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()
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))
def check_pid(self): """ Get measurement and apply to PID controller """ # If PID is active, retrieve measurement and update # the control variable. # A PID on hold will sustain the current output and # not update the control variable. if self.is_activated and (not self.is_paused or not self.is_held): self.get_last_measurement_pid() if self.last_measurement_success: if self.setpoint_tracking_type == 'method' and self.setpoint_tracking_id != '': # Update setpoint using a method this_pid = db_retrieve_table_daemon( PID, unique_id=self.unique_id) new_setpoint, ended = calculate_method_setpoint( self.setpoint_tracking_id, PID, this_pid, Method, MethodData, self.logger) if ended: self.method_start_act = 'Ended' if new_setpoint is not None: self.PID_Controller.setpoint = new_setpoint else: self.PID_Controller.setpoint = self.setpoint if self.setpoint_tracking_type == 'input-math' and self.setpoint_tracking_id != '': # Update setpoint using an Input or Math device_id = self.setpoint_tracking_id.split(',')[0] measurement_id = self.setpoint_tracking_id.split(',')[1] measurement = get_measurement(measurement_id) if not measurement: return False, None last_measurement = read_last_influxdb( device_id, measurement.unit, measurement.channel, measure=measurement.measurement, duration_sec=self.setpoint_tracking_max_age) if last_measurement[1] is not None: self.PID_Controller.setpoint = last_measurement[1] else: self.logger.debug( "Could not find measurement for Setpoint " "Tracking. Max Age of {} exceeded for measuring " "device ID {} (measurement {})".format( self.setpoint_tracking_max_age, device_id, measurement_id)) self.PID_Controller.setpoint = None # If autotune activated, determine control variable (output) from autotune if self.autotune_activated: if not self.autotune.run(self.last_measurement): self.PID_Controller.control_variable = self.autotune.output if self.autotune_debug: self.logger.info('') self.logger.info("state: {}".format(self.autotune.state)) self.logger.info("output: {}".format(self.autotune.output)) else: # Autotune has finished timestamp = time.time() - self.autotune_timestamp self.logger.info('') self.logger.info('time: {0} min'.format(round(timestamp / 60))) self.logger.info('state: {0}'.format(self.autotune.state)) if self.autotune.state == PIDAutotune.STATE_SUCCEEDED: for rule in self.autotune.tuning_rules: params = self.autotune.get_pid_parameters(rule) self.logger.info('') self.logger.info('rule: {0}'.format(rule)) self.logger.info('Kp: {0}'.format(params.Kp)) self.logger.info('Ki: {0}'.format(params.Ki)) self.logger.info('Kd: {0}'.format(params.Kd)) self.stop_controller(deactivate_pid=True) else: # Calculate new control variable (output) from PID Controller self.PID_Controller.update_pid_output(self.last_measurement) self.write_pid_values() # Write variables to database # Is PID in a state that allows manipulation of outputs if (self.is_activated and self.PID_Controller.setpoint is not None and (not self.is_paused or self.is_held)): self.manipulate_output()
def check_pid(self): """ Get measurement and apply to PID controller """ # If PID is active, retrieve measurement and update # the control variable. # A PID on hold will sustain the current output and # not update the control variable. if self.is_activated and (not self.is_paused or not self.is_held): self.get_last_measurement_pid() if self.last_measurement_success: if self.setpoint_tracking_type == 'method' and self.setpoint_tracking_id != '': # Update setpoint using a method this_pid = db_retrieve_table_daemon( PID, unique_id=self.unique_id) new_setpoint, ended = calculate_method_setpoint( self.setpoint_tracking_id, PID, this_pid, Method, MethodData, self.logger) if ended: self.method_start_act = 'Ended' if new_setpoint is not None: self.PID_Controller.setpoint = new_setpoint else: self.PID_Controller.setpoint = self.setpoint if self.setpoint_tracking_type == 'input-math' and self.setpoint_tracking_id != '': # Update setpoint using an Input or Math device_id = self.setpoint_tracking_id.split(',')[0] measurement_id = self.setpoint_tracking_id.split(',')[1] measurement = get_measurement(measurement_id) if not measurement: return False, None conversion = db_retrieve_table_daemon( Conversion, unique_id=measurement.conversion_id) channel, unit, measurement = return_measurement_info( measurement, conversion) last_measurement = read_last_influxdb( device_id, unit, channel, measure=measurement, duration_sec=self.setpoint_tracking_max_age) if last_measurement[1] is not None: self.PID_Controller.setpoint = last_measurement[1] else: self.logger.debug( "Could not find measurement for Setpoint " "Tracking. Max Age of {} exceeded for measuring " "device ID {} (measurement {})".format( self.setpoint_tracking_max_age, device_id, measurement_id)) self.PID_Controller.setpoint = None # Calculate new control variable (output) from PID Controller self.PID_Controller.update_pid_output(self.last_measurement) self.write_pid_values() # Write variables to database # Is PID in a state that allows manipulation of outputs if (self.is_activated and self.PID_Controller.setpoint is not None and (not self.is_paused or self.is_held)): self.manipulate_output()
def 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()