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)))
Esempio n. 7
0
	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)))