def _update_override(self, dpack): # types: (DataPacket) -> DataPacket # Only update the override mode if the thermostat controller is on return_status = 'PO:NACK' if self._thermo_on: # Check the status command if dpack.packet.subcommand == STATUS_ON: # Set the override mode and setpoint self._logger.info(' Thermostat is turning override mode on with setpoint %.2f', dpack.packet.data) self._override_on = True self._override_temp = dpack.packet.data self._setpoint = self._override_temp button_status = display.BTN_ON else: # Set override mode self._logger.info(' Thermostat is turning override mode off') self._override_on = False button_status = display.BTN_OFF if dpack.packet.data: self._override_temp = dpack.packet.data # Update the display if command was from the LAN if dpack.host != 'DISPLAY': self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.OVER_BTN, button_status))) self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.OVER_SCALE, self._override_temp))) # Re-evaluation relay status against the setpoint self._evaluate_programming(True) # Force an update based on status return_status = 'PO:ACK' # Return an acknowledgement return messaging.DataPacket(dpack.host, dpack.port, return_status)
def run(self): # Connect to the GPIO bus self._logger.debug('Starting thermostat thread') # Initialize status of thermostat (set to on and relay closed) self._set_thermo_status(THERMOSTAT_ON) self._set_relay_status(RELAY_OFF) # Initialize the settings on the display self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.POWER_LED, display.LED_ON))) self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.OVER_BTN, display.BTN_OFF))) self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.OVER_SCALE, self._override_temp))) # Loop until time to close non_printing_loops = self._data_cycles while not self._kill_event.is_set(): self._logger.info('Thermostat control cycle %i of %i initiated', non_printing_loops, self._data_cycles) # Evaluate thermostat programming force_print = False if non_printing_loops == self._data_cycles: non_printing_loops = 1 force_print = True else: non_printing_loops += 1 self._evaluate_programming(force_print) # Wait until next control cycle self._kill_event.wait(self._sensor_period) # Shutdown the GPIO bus self._gpio_bus.stop() self._logger.debug('Thermostat thread closing')
def _set_thermo_status(self, PowerStatus): # types: (boolean) -> none # Check to see if anything needs to change if self._thermo_on != PowerStatus: # Update object status self._logger.info(' Turning thermostat %s', 'on' if PowerStatus == THERMOSTAT_ON else 'off') self._thermo_on = PowerStatus # Update the power indicators if PowerStatus == THERMOSTAT_ON: # Turn on the LED # self._gpio_bus.write(THERMO_POWER_PIN, 1) # Power up the button self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.PROGRAM_BTN, display.BTN_ON))) else: # Turn off the LED # self._gpio_bus.write(THERMO_POWER_PIN, 0) # Power down the button self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.PROGRAM_BTN, display.BTN_OFF)))
def _update_override(self, status, setpoint): # Create the packet and send to the thermostat button_status = thermostat.STATUS_ON if status == BTN_ON else thermostat.STATUS_OFF data_packet = messaging.DisplayPacket( messaging.Command(thermostat.CMD_OVERRIDE, button_status, setpoint)) self._ehandler(messaging.ThermostatTxMessage(data_packet))
def _update_power_status(self, status): # Create the packet and send to the thermostat button_status = thermostat.STATUS_ON if status == BTN_ON else thermostat.STATUS_OFF data_packet = messaging.DisplayPacket( messaging.Command(thermostat.CMD_THERMO_POWER, button_status, None)) self._ehandler(messaging.ThermostatTxMessage(data_packet))
def _set_relay_status(self, RelayStatus): # types: (boolean) -> none # Check if relay status is actually changing, and take action if self._relay_on != RelayStatus: # Update object status self._logger.info(' Turning relay %s', 'on' if RelayStatus else 'off') self._relay_on = RelayStatus # Update the power indicators if RelayStatus == RELAY_ON: # Close relay to turn on heat # self._gpio_bus.write(RELAY_STATUS_PIN, 1) self._gpio_bus.write(RELAY_POWER_PIN, 0) self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.RELAY_LED, display.LED_ON))) else: # Open relay to turn off heat # self._gpio_bus.write(RELAY_STATUS_PIN, 0) self._gpio_bus.write(RELAY_POWER_PIN, 1) self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.RELAY_LED, display.LED_OFF)))
def _update_display(self, data): # types: (map) -> None # Iterate through the data looking for the temperature - this coding assumes that the data is ok as a response # string was created in a previous function call. No data quality checks are made. data_length = len(data['rf_data']) - 1 num_sensors = data_length / 5 # Data transferred in 5-byte chunks for i in range(num_sensors): type_byte = ord(data['rf_data'][5 * i + 1]) if type_byte == TEMPERATURE_CODE: # Found temperature, convert to string and send to the display float_value = struct.unpack('f', data['rf_data'][5*i+2:5*i+6]) # Convert the bytes to a single-precision float display_str = 'Outdoor: %.1f' % float_value self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.OUTSIDE_TEMP, display_str)))
def _handle_command(self, host_address, command): # types: (string, string) -> none # Parse the command tokens = command.split(':') # Setup data structure based on command command_error = False if tokens[1] == 'TS': # Thermostat power control # Check if the command is to turn on the thermostat if tokens[2] == 'ON': self._logger.info( ' Received LAN command to turn on the thermostat') cmd = messaging.Command(thermostat.CMD_THERMO_POWER, thermostat.STATUS_ON, None) elif tokens[2] == 'OFF': self._logger.info( ' Received LAN command to turn off the thermostat') cmd = messaging.Command(thermostat.CMD_THERMO_POWER, thermostat.STATUS_OFF, None) else: self._logger.error( ' Received unrecognized thermostat power command %s - no action taken', tokens[2]) command_error = True elif tokens[1] == 'PO': # Program override # Check if the command is to turn the override on if tokens[2] == 'ON': self._logger.info( ' Received LAN command to turn on override mode with a setpoint of %s', tokens[3]) cmd = messaging.Command(thermostat.CMD_OVERRIDE, thermostat.STATUS_ON, float(tokens[3])) elif tokens[2] == 'OFF': self._logger.info( ' Received LAN command to turn off override mode') cmd = messaging.Command( thermostat.CMD_OVERRIDE, thermostat.STATUS_OFF, float(tokens[3]) if len(tokens) >= 4 else None) else: self._logger.error( ' Received unrecognized override command %s - no action taken', tokens[2]) command_error = True elif tokens[1] == 'TR': # Thermostat rule change self._logger.warning( ' Thermostat rule change logic not implemented yet') command_error = True elif tokens[1] == 'CR': # Clock control # Check clock control command if tokens[2] == 'GET': # Get the current time information self._logger.info( ' Received LAN command to get the current thermostat time' ) cmd = messaging.Command(thermostat.CMD_TIME_REQUEST, thermostat.STATUS_GET, None) else: self._logger.error( ' Received unrecognized clock control command %s - no action taken', tokens[2]) command_error = True elif tokens[1] == 'ST': # Thermostat status self._logger.info( ' Received LAN command to return the status of the thermostat' ) cmd = messaging.Command(thermostat.CMD_STATUS, None, None) elif tokens[1] == 'XX': # Program shutdown self._logger.info( ' Received LAN command to shutdown the thermostat program') cmd = messaging.Command(thermostat.CMD_SHUTDOWN, None, None) else: # Unrecognized command type self._logger.warning( ' Received LAN command %s was unexpected - no action taken', tokens[1]) command_error = True # Pass the command on if not command_error: self._ehandler( messaging.ThermostatTxMessage( messaging.DataPacket(host_address, tokens[0], cmd))) else: err_response = tokens[1] err_response += ':NACK' if tokens[1] else 'NACK' self._ehandler( messaging.LANTxMessage( messaging.DataPacket(host_address, tokens[0], err_response)))
def _evaluate_programming(self, ForceUpdate = False): # types: (boolean) -> none # Read the current temperature try: # Protect against I2C communication error cur_temp = self._temp_sensor.read_temperature() self._temperature = cur_temp # Store the last temperature read temp_str = '%.1f' % cur_temp update_db = ForceUpdate except pigpio.error: self._logger.critical(' ERROR READING TEMPERATURE DURING CONTROL LOOP - SKIPPING THERMOSTAT EVALUATION') else: # Get the current time cur_time = time.localtime() cur_hour = cur_time.tm_hour + (60*cur_time.tm_min + cur_time.tm_sec)/3600.0 cur_day = cur_time.tm_wday self._logger.debug(' Measured temperature %.2f Celsius on day %i at hour %.2f', cur_temp, cur_day, cur_hour) # Update the display with the current temperature if self._indoor_temp_str != temp_str: self._logger.debug(' Writing string %s to the display', temp_str) self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.INSIDE_TEMP, temp_str))) self._indoor_temp_str = temp_str # Temperature checks # ---------------------------------------------------------------------- if cur_temp < MIN_TEMPERATURE: # Temperature below limit, turn on relay self._logger.debug(' Temperature below minimum temperature limit of %.2f Celsius', MIN_TEMPERATURE) if not self._relay_on: # Turn on the relay if it is off self._set_relay_status(RELAY_ON) # Turn on relay update_db = True # Update the database due to relay state change elif cur_temp > MAX_TEMPERATURE: # Temperature above limit, turn off relay self._logger.debug(' Temperature above maximum temperature limit of %.2f Celsius', MAX_TEMPERATURE) if self._relay_on: # Turn off the relay if it is on self._set_relay_status(RELAY_OFF) # Turn off relay update_db = True # Update due to relay state change elif self._override_on: # Override is on, so evaluate against setpoint # Evaluate against the override setpoint self._logger.debug(' Override Mode On - Checking against override setpoint (%.2f)', self._setpoint) if self._relay_on and (cur_temp > (self._setpoint + TEMPERATURE_BUFFER)): # Temperature exceeds override, so turn off relay self._set_relay_status(RELAY_OFF) update_db = True elif not self._relay_on and (cur_temp < (self._setpoint - TEMPERATURE_BUFFER)): # Temperature below override, so turn on relay self._set_relay_status(RELAY_ON) update_db = True else: # No change self._logger.debug(' Relay not changed') elif self._thermo_on: # Temperature is within limits, so check against rules self._logger.debug(' Programming Mode On - Checking against programmed rules') rule_found = False # Flag for finding the rule while not rule_found: # Iterate through the rules until one is found for cur_rule in self._user_config['programming_rules']: if self._rule_applies(cur_rule, cur_day, cur_hour): # Rule applies # Determine how to control the relay if self._relay_on and (cur_temp > (cur_rule['temperature'] + TEMPERATURE_BUFFER)): # Temperature exceeds rule, so turn off relay self._logger.debug(' Relay turned off as temperature greater than setpoint (%.2f Celsius)', cur_rule['temperature']) self._set_relay_status(RELAY_OFF) update_db = True elif not self._relay_on and (cur_temp < (cur_rule['temperature'] - TEMPERATURE_BUFFER)): # Temperature below rule, so turn on relay self._logger.debug(' Relay turned on as temperature less than setpoint (%.2f Celsius)', cur_rule['temperature']) self._set_relay_status(RELAY_ON) update_db = True else: # No change self._logger.debug(' Relay remains %s as setpoint is %.2f Celsius', 'on' if self._relay_on else 'off', cur_rule['temperature']) # Rule found, so break from the loop rule_found = True self._setpoint = cur_rule['temperature'] # Keep track of current temperature setpoint break else: # Rule not found # Decrease the day, but increase the time self._logger.debug(' Could not find the appropriate rule, moving back one day') if cur_day == RULE_DAYS['Monday']: cur_day = RULE_DAYS['Sunday'] else: cur_day -= 1 cur_hour += 24.0 else: # Thermostat is off, so no update self._logger.debug(' Thermostat off - no change') # Send thermostat status to database #----------------------------------------------------------------------- if update_db: self._update_database(cur_temp) # Update the display with the setpoint #----------------------------------------------------------------------- setpoint_str = 'Setpoint: %.1f' % self._setpoint if self._setpoint_str != setpoint_str: # Only update if new string self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.SETPOINT_TEMP, setpoint_str))) self._setpoint_str = setpoint_str
def _force_shutdown(self): # Shutdown the thermostat self._set_thermo_status(THERMOSTAT_OFF) self._set_relay_status(RELAY_ON) self._update_database() self._ehandler(messaging.DisplayTxMessage(messaging.Command(display.SET_STATUS, display.POWER_LED, display.LED_OFF)))