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 codepull/2613/head
parent
4cf618334c
commit
2484ee53b8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue