mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
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
This commit is contained in:
parent
4cf618334c
commit
2484ee53b8
@ -91,6 +91,7 @@ omit =
|
|||||||
homeassistant/components/knx.py
|
homeassistant/components/knx.py
|
||||||
homeassistant/components/switch/knx.py
|
homeassistant/components/switch/knx.py
|
||||||
homeassistant/components/binary_sensor/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/alarmdotcom.py
|
||||||
homeassistant/components/alarm_control_panel/nx584.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
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
DOMAIN = "knx"
|
DOMAIN = "knx"
|
||||||
REQUIREMENTS = ['knxip==0.3.0']
|
REQUIREMENTS = ['knxip==0.3.2']
|
||||||
|
|
||||||
EVENT_KNX_FRAME_RECEIVED = "knx_frame_received"
|
EVENT_KNX_FRAME_RECEIVED = "knx_frame_received"
|
||||||
|
|
||||||
@ -45,7 +45,12 @@ def setup(hass, config):
|
|||||||
|
|
||||||
KNXTUNNEL = KNXIPTunnel(host, port)
|
KNXTUNNEL = KNXIPTunnel(host, port)
|
||||||
try:
|
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:
|
except KNXException as ex:
|
||||||
_LOGGER.exception("Can't connect to KNX/IP interface: %s", ex)
|
_LOGGER.exception("Can't connect to KNX/IP interface: %s", ex)
|
||||||
KNXTUNNEL = None
|
KNXTUNNEL = None
|
||||||
@ -74,7 +79,10 @@ class KNXConfig(object):
|
|||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
self.should_poll = config.get("poll", True)
|
self.should_poll = config.get("poll", True)
|
||||||
|
if config.get("address"):
|
||||||
self._address = parse_group_address(config.get("address"))
|
self._address = parse_group_address(config.get("address"))
|
||||||
|
else:
|
||||||
|
self._address = None
|
||||||
if self.config.get("state_address"):
|
if self.config.get("state_address"):
|
||||||
self._state_address = parse_group_address(
|
self._state_address = parse_group_address(
|
||||||
self.config.get("state_address"))
|
self.config.get("state_address"))
|
||||||
@ -198,7 +206,7 @@ class KNXGroupAddress(Entity):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class KNXMultiAddressDevice(KNXGroupAddress):
|
class KNXMultiAddressDevice(Entity):
|
||||||
"""Representation of devices connected to a multiple KNX group address.
|
"""Representation of devices connected to a multiple KNX group address.
|
||||||
|
|
||||||
This is needed for devices like dimmers or shutter actuators as they have
|
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
|
from knxip.core import parse_group_address, KNXException
|
||||||
|
|
||||||
super().__init__(self, hass, config)
|
self._config = config
|
||||||
|
self._state = False
|
||||||
self.config = config
|
self._data = None
|
||||||
|
_LOGGER.debug("Initalizing KNX multi address device")
|
||||||
|
|
||||||
# parse required addresses
|
# parse required addresses
|
||||||
for name in required:
|
for name in required:
|
||||||
|
_LOGGER.info(name)
|
||||||
paramname = name + "_address"
|
paramname = name + "_address"
|
||||||
addr = self._config.config.get(paramname)
|
addr = self._config.config.get(paramname)
|
||||||
if addr is None:
|
if addr is None:
|
||||||
_LOGGER.exception("Required KNX group address %s missing",
|
_LOGGER.exception("Required KNX group address %s missing",
|
||||||
paramname)
|
paramname)
|
||||||
raise KNXException("Group address missing in configuration")
|
raise KNXException("Group address for %s missing "
|
||||||
|
"in configuration", paramname)
|
||||||
addr = parse_group_address(addr)
|
addr = parse_group_address(addr)
|
||||||
self.names[addr] = name
|
self.names[addr] = name
|
||||||
|
|
||||||
@ -244,23 +255,25 @@ class KNXMultiAddressDevice(KNXGroupAddress):
|
|||||||
_LOGGER.exception("Cannot parse group address %s", addr)
|
_LOGGER.exception("Cannot parse group address %s", addr)
|
||||||
self.names[addr] = name
|
self.names[addr] = name
|
||||||
|
|
||||||
def handle_frame(frame):
|
@property
|
||||||
"""Handle an incoming KNX frame.
|
def name(self):
|
||||||
|
"""The entity's display name."""
|
||||||
|
return self._config.name
|
||||||
|
|
||||||
Handle an incoming frame and update our status if it contains
|
@property
|
||||||
information relating to this device.
|
def config(self):
|
||||||
"""
|
"""The entity's configuration."""
|
||||||
addr = frame.data[0]
|
return self._config
|
||||||
|
|
||||||
if addr in self.names:
|
@property
|
||||||
self.values[addr] = frame.data[1]
|
def should_poll(self):
|
||||||
self.update_ha_state()
|
"""Return the state of the polling, if needed."""
|
||||||
|
return self._config.should_poll
|
||||||
|
|
||||||
hass.bus.listen(EVENT_KNX_FRAME_RECEIVED, handle_frame)
|
@property
|
||||||
|
def cache(self):
|
||||||
def group_write_address(self, name, value):
|
"""The name given to the entity."""
|
||||||
"""Write to the group address with the given name."""
|
return self._config.config.get("cache", True)
|
||||||
KNXTUNNEL.group_write(self.address, [value])
|
|
||||||
|
|
||||||
def has_attribute(self, name):
|
def has_attribute(self, name):
|
||||||
"""Check if the attribute with the given name is defined.
|
"""Check if the attribute with the given name is defined.
|
||||||
@ -277,7 +290,7 @@ class KNXMultiAddressDevice(KNXGroupAddress):
|
|||||||
from knxip.core import KNXException
|
from knxip.core import KNXException
|
||||||
|
|
||||||
addr = None
|
addr = None
|
||||||
for attributename, attributeaddress in self.names.items():
|
for attributeaddress, attributename in self.names.items():
|
||||||
if attributename == name:
|
if attributename == name:
|
||||||
addr = attributeaddress
|
addr = attributeaddress
|
||||||
|
|
||||||
@ -293,3 +306,25 @@ class KNXMultiAddressDevice(KNXGroupAddress):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return res
|
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
|
||||||
|
83
homeassistant/components/thermostat/knx.py
Normal file
83
homeassistant/components/thermostat/knx.py
Normal 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()
|
@ -186,7 +186,7 @@ insteon_hub==0.4.5
|
|||||||
jsonrpc-requests==0.3
|
jsonrpc-requests==0.3
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
knxip==0.3.0
|
knxip==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.light.lifx
|
# homeassistant.components.light.lifx
|
||||||
liffylights==0.9.4
|
liffylights==0.9.4
|
||||||
|
Loading…
x
Reference in New Issue
Block a user