From 2484ee53b81fa176dd416fb12f2d748db070f284 Mon Sep 17 00:00:00 2001 From: Open Home Automation Date: Sat, 23 Jul 2016 22:54:20 +0200 Subject: [PATCH] Knx thermostat (#2575) * Major rewrite of the KNX multi address device. This class wasn't used before, but the new class will be the base for the LNX thermostat module * newer KNXIP version needed as the previous version had a serious bug * Update knxip to later version * Added thermostat module * First implementation of a KNX thermostat module * Minor cleanup * Removed unsed code --- .coveragerc | 1 + homeassistant/components/knx.py | 81 +++++++++++++++------ homeassistant/components/thermostat/knx.py | 83 ++++++++++++++++++++++ requirements_all.txt | 2 +- 4 files changed, 143 insertions(+), 24 deletions(-) create mode 100644 homeassistant/components/thermostat/knx.py diff --git a/.coveragerc b/.coveragerc index fc86425f537..2d3e967351b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -91,6 +91,7 @@ omit = homeassistant/components/knx.py homeassistant/components/switch/knx.py homeassistant/components/binary_sensor/knx.py + homeassistant/components/thermostat/knx.py homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/nx584.py diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index 8b43c0f2da9..763b9d81ded 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -10,7 +10,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity DOMAIN = "knx" -REQUIREMENTS = ['knxip==0.3.0'] +REQUIREMENTS = ['knxip==0.3.2'] EVENT_KNX_FRAME_RECEIVED = "knx_frame_received" @@ -45,7 +45,12 @@ def setup(hass, config): KNXTUNNEL = KNXIPTunnel(host, port) try: - KNXTUNNEL.connect() + res = KNXTUNNEL.connect() + _LOGGER.debug("Res = %s", res) + if not res: + _LOGGER.exception("Could not connect to KNX/IP interface %s", host) + return False + except KNXException as ex: _LOGGER.exception("Can't connect to KNX/IP interface: %s", ex) KNXTUNNEL = None @@ -74,7 +79,10 @@ class KNXConfig(object): self.config = config self.should_poll = config.get("poll", True) - self._address = parse_group_address(config.get("address")) + if config.get("address"): + self._address = parse_group_address(config.get("address")) + else: + self._address = None if self.config.get("state_address"): self._state_address = parse_group_address( self.config.get("state_address")) @@ -198,7 +206,7 @@ class KNXGroupAddress(Entity): return False -class KNXMultiAddressDevice(KNXGroupAddress): +class KNXMultiAddressDevice(Entity): """Representation of devices connected to a multiple KNX group address. This is needed for devices like dimmers or shutter actuators as they have @@ -218,18 +226,21 @@ class KNXMultiAddressDevice(KNXGroupAddress): """ from knxip.core import parse_group_address, KNXException - super().__init__(self, hass, config) - - self.config = config + self._config = config + self._state = False + self._data = None + _LOGGER.debug("Initalizing KNX multi address device") # parse required addresses for name in required: + _LOGGER.info(name) paramname = name + "_address" addr = self._config.config.get(paramname) if addr is None: _LOGGER.exception("Required KNX group address %s missing", paramname) - raise KNXException("Group address missing in configuration") + raise KNXException("Group address for %s missing " + "in configuration", paramname) addr = parse_group_address(addr) self.names[addr] = name @@ -244,23 +255,25 @@ class KNXMultiAddressDevice(KNXGroupAddress): _LOGGER.exception("Cannot parse group address %s", addr) self.names[addr] = name - def handle_frame(frame): - """Handle an incoming KNX frame. + @property + def name(self): + """The entity's display name.""" + return self._config.name - Handle an incoming frame and update our status if it contains - information relating to this device. - """ - addr = frame.data[0] + @property + def config(self): + """The entity's configuration.""" + return self._config - if addr in self.names: - self.values[addr] = frame.data[1] - self.update_ha_state() + @property + def should_poll(self): + """Return the state of the polling, if needed.""" + return self._config.should_poll - hass.bus.listen(EVENT_KNX_FRAME_RECEIVED, handle_frame) - - def group_write_address(self, name, value): - """Write to the group address with the given name.""" - KNXTUNNEL.group_write(self.address, [value]) + @property + def cache(self): + """The name given to the entity.""" + return self._config.config.get("cache", True) def has_attribute(self, name): """Check if the attribute with the given name is defined. @@ -277,7 +290,7 @@ class KNXMultiAddressDevice(KNXGroupAddress): from knxip.core import KNXException addr = None - for attributename, attributeaddress in self.names.items(): + for attributeaddress, attributename in self.names.items(): if attributename == name: addr = attributeaddress @@ -293,3 +306,25 @@ class KNXMultiAddressDevice(KNXGroupAddress): return False return res + + def set_value(self, name, value): + """Set the value of a given named attribute.""" + from knxip.core import KNXException + + addr = None + for attributeaddress, attributename in self.names.items(): + if attributename == name: + addr = attributeaddress + + if addr is None: + _LOGGER.exception("Attribute %s undefined", name) + return False + + try: + KNXTUNNEL.group_write(addr, value) + except KNXException: + _LOGGER.exception("Unable to write to KNX address: %s", + addr) + return False + + return True diff --git a/homeassistant/components/thermostat/knx.py b/homeassistant/components/thermostat/knx.py new file mode 100644 index 00000000000..621830c828e --- /dev/null +++ b/homeassistant/components/thermostat/knx.py @@ -0,0 +1,83 @@ +""" +Support for KNX thermostats. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/knx/ +""" +import logging + +from homeassistant.components.thermostat import ThermostatDevice +from homeassistant.const import TEMP_CELSIUS + +from homeassistant.components.knx import ( + KNXConfig, KNXMultiAddressDevice) + +DEPENDENCIES = ["knx"] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Create and add an entity based on the configuration.""" + add_entities([ + KNXThermostat(hass, KNXConfig(config)) + ]) + + +class KNXThermostat(KNXMultiAddressDevice, ThermostatDevice): + """Representation of a KNX thermostat. + + A KNX thermostat will has the following parameters: + - temperature (current temperature) + - setpoint (target temperature in HASS terms) + - hvac mode selection (comfort/night/frost protection) + + This version supports only polling. Messages from the KNX bus do not + automatically update the state of the thermostat (to be implemented + in future releases) + """ + + def __init__(self, hass, config): + """Initialize the thermostat based on the given configuration.""" + KNXMultiAddressDevice.__init__(self, hass, config, + ["temperature", "setpoint"], + ["mode"]) + + self._unit_of_measurement = TEMP_CELSIUS # KNX always used celcius + self._away = False # not yet supported + self._is_fan_on = False # not yet supported + + @property + def should_poll(self): + """Polling is needed for the KNX thermostat.""" + return True + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._unit_of_measurement + + @property + def current_temperature(self): + """Return the current temperature.""" + from knxip.conversion import knx2_to_float + + return knx2_to_float(self.value("temperature")) + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + from knxip.conversion import knx2_to_float + + return knx2_to_float(self.value("setpoint")) + + def set_temperature(self, temperature): + """Set new target temperature.""" + from knxip.conversion import float_to_knx2 + + self.set_value("setpoint", float_to_knx2(temperature)) + _LOGGER.debug("Set target temperature to %s", temperature) + + def set_hvac_mode(self, hvac_mode): + """Set hvac mode.""" + raise NotImplementedError() diff --git a/requirements_all.txt b/requirements_all.txt index 3b42d548d2d..392804c10bb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -186,7 +186,7 @@ insteon_hub==0.4.5 jsonrpc-requests==0.3 # homeassistant.components.knx -knxip==0.3.0 +knxip==0.3.2 # homeassistant.components.light.lifx liffylights==0.9.4