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
pull/2613/head
Open Home Automation 2016-07-23 22:54:20 +02:00 committed by Paulus Schoutsen
parent 4cf618334c
commit 2484ee53b8
4 changed files with 143 additions and 24 deletions

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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