diff --git a/homeassistant/components/sensor/knx.py b/homeassistant/components/sensor/knx.py index 229f8790291..d4752666821 100644 --- a/homeassistant/components/sensor/knx.py +++ b/homeassistant/components/sensor/knx.py @@ -4,109 +4,125 @@ Sensors of a KNX Device. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/knx/ """ +from enum import Enum + +import logging +import voluptuous as vol + from homeassistant.const import ( - TEMP_CELSIUS, TEMPERATURE, CONF_TYPE, ILLUMINANCE, SPEED_MS, CONF_MINIMUM, - CONF_MAXIMUM) + CONF_NAME, CONF_MAXIMUM, CONF_MINIMUM, + CONF_TYPE, TEMP_CELSIUS +) from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) +from homeassistant.components.sensor import PLATFORM_SCHEMA +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['knx'] -# Speed units -SPEED_METERPERSECOND = 'm/s' # type: str +DEFAULT_NAME = "KNX sensor" -# Illuminance units -ILLUMINANCE_LUX = 'lx' # type: str - -# Predefined Minimum, Maximum Values for Sensors -# Temperature as defined in KNX Standard 3.10 - 9.001 DPT_Value_Temp -KNX_TEMP_MIN = -273 -KNX_TEMP_MAX = 670760 - -# Luminance(LUX) as Defined in KNX Standard 3.10 - 9.004 DPT_Value_Lux -KNX_LUX_MIN = 0 -KNX_LUX_MAX = 670760 - -# Speed m/s as defined in KNX Standard 3.10 - 9.005 DPT_Value_Wsp -KNX_SPEED_MS_MIN = 0 -KNX_SPEED_MS_MAX = 670760 +CONF_TEMPERATURE = 'temperature' +CONF_ADDRESS = 'address' +CONF_ILLUMINANCE = 'illuminance' +CONF_PERCENTAGE = 'percentage' +CONF_SPEED_MS = 'speed_ms' -def setup_platform(hass, config, add_entities, discovery_info=None): +class KNXAddressType(Enum): + """Enum to indicate conversion type for the KNX address.""" + + FLOAT = 1 + PERCENT = 2 + + +# define the fixed settings required for each sensor type +FIXED_SETTINGS_MAP = { + # Temperature as defined in KNX Standard 3.10 - 9.001 DPT_Value_Temp + CONF_TEMPERATURE: { + 'unit': TEMP_CELSIUS, + 'default_minimum': -273, + 'default_maximum': 670760, + 'address_type': KNXAddressType.FLOAT + }, + # Speed m/s as defined in KNX Standard 3.10 - 9.005 DPT_Value_Wsp + CONF_SPEED_MS: { + 'unit': 'm/s', + 'default_minimum': 0, + 'default_maximum': 670760, + 'address_type': KNXAddressType.FLOAT + }, + # Luminance(LUX) as defined in KNX Standard 3.10 - 9.004 DPT_Value_Lux + CONF_ILLUMINANCE: { + 'unit': 'lx', + 'default_minimum': 0, + 'default_maximum': 670760, + 'address_type': KNXAddressType.FLOAT + }, + # Percentage(%) as defined in KNX Standard 3.10 - 5.001 DPT_Scaling + CONF_PERCENTAGE: { + 'unit': '%', + 'default_minimum': 0, + 'default_maximum': 100, + 'address_type': KNXAddressType.PERCENT + } +} + +SENSOR_TYPES = set(FIXED_SETTINGS_MAP.keys()) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES), + vol.Required(CONF_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MINIMUM): vol.Coerce(float), + vol.Optional(CONF_MAXIMUM): vol.Coerce(float) +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the KNX Sensor platform.""" - # KNX Datapoint 9.001 DPT_Value_Temp - if config[CONF_TYPE] == TEMPERATURE: - minimum_value, maximum_value = \ - update_and_define_min_max(config, KNX_TEMP_MIN, KNX_TEMP_MAX) - - add_entities([ - KNXSensorFloatClass( - hass, KNXConfig(config), TEMP_CELSIUS, minimum_value, - maximum_value) - ]) - - # Add KNX Speed Sensors(Like Wind Speed) - # KNX Datapoint 9.005 DPT_Value_Wsp - elif config[CONF_TYPE] == SPEED_MS: - minimum_value, maximum_value = \ - update_and_define_min_max( - config, KNX_SPEED_MS_MIN, KNX_SPEED_MS_MAX) - - add_entities([ - KNXSensorFloatClass(hass, KNXConfig(config), SPEED_METERPERSECOND, - minimum_value, maximum_value) - ]) - - # Add KNX Illuminance Sensors(Lux) - # KNX Datapoint 9.004 DPT_Value_Lux - elif config[CONF_TYPE] == ILLUMINANCE: - minimum_value, maximum_value = \ - update_and_define_min_max(config, KNX_LUX_MIN, KNX_LUX_MAX) - - add_entities([ - KNXSensorFloatClass(hass, KNXConfig(config), ILLUMINANCE_LUX, - minimum_value, maximum_value) - ]) + add_devices([KNXSensor(hass, KNXConfig(config))]) -def update_and_define_min_max(config, minimum_default, maximum_default): - """Determine a min/max value defined in the configuration.""" - minimum_value = minimum_default - maximum_value = maximum_default - if config.get(CONF_MINIMUM): - minimum_value = config.get(CONF_MINIMUM) +class KNXSensor(KNXGroupAddress): + """Representation of a KNX Sensor device.""" - if config.get(CONF_MAXIMUM): - maximum_value = config.get(CONF_MAXIMUM) - - return minimum_value, maximum_value - - -class KNXSensorBaseClass(): - """Sensor Base Class for all KNX Sensors.""" - - @property - def cache(self): - """We don't want to cache any Sensor Value.""" - return False - - -class KNXSensorFloatClass(KNXGroupAddress, KNXSensorBaseClass): - """ - Base Implementation of a 2byte Floating Point KNX Telegram. - - Defined in KNX 3.7.2 - 3.10 - """ - - def __init__(self, hass, config, unit_of_measurement, minimum_sensor_value, - maximum_sensor_value): + def __init__(self, hass, config): """Initialize a KNX Float Sensor.""" - self._unit_of_measurement = unit_of_measurement - self._minimum_value = minimum_sensor_value - self._maximum_value = maximum_sensor_value - self._value = None - + # set up the KNX Group address KNXGroupAddress.__init__(self, hass, config) + device_type = config.config.get(CONF_TYPE) + sensor_config = FIXED_SETTINGS_MAP.get(device_type) + + if not sensor_config: + raise NotImplementedError() + + # set up the conversion function based on the address type + address_type = sensor_config.get('address_type') + if address_type == KNXAddressType.FLOAT: + self.convert = convert_float + elif address_type == KNXAddressType.PERCENT: + self.convert = convert_percent + else: + raise NotImplementedError() + + # other settings + self._unit_of_measurement = sensor_config.get('unit') + default_min = float(sensor_config.get('default_minimum')) + default_max = float(sensor_config.get('default_maximum')) + self._minimum_value = config.config.get(CONF_MINIMUM, default_min) + self._maximum_value = config.config.get(CONF_MAXIMUM, default_max) + _LOGGER.debug( + "%s: configured additional settings: unit=%s, " + "min=%f, max=%f, type=%s", + self.name, self._unit_of_measurement, + self._minimum_value, self._maximum_value, str(address_type) + ) + + self._value = None + @property def state(self): """Return the Value of the KNX Sensor.""" @@ -119,13 +135,49 @@ class KNXSensorFloatClass(KNXGroupAddress, KNXSensorBaseClass): def update(self): """Update KNX sensor.""" - from knxip.conversion import knx2_to_float - super().update() self._value = None if self._data: - value = 0 if self._data == 0 else knx2_to_float(self._data) + if self._data == 0: + value = 0 + else: + value = self.convert(self._data) if self._minimum_value <= value <= self._maximum_value: self._value = value + + @property + def cache(self): + """We don't want to cache any Sensor Value.""" + return False + + +def convert_float(raw_value): + """Conversion for 2 byte floating point values. + + 2byte Floating Point KNX Telegram. + Defined in KNX 3.7.2 - 3.10 + """ + from knxip.conversion import knx2_to_float + + return knx2_to_float(raw_value) + + +def convert_percent(raw_value): + """Conversion for scaled byte values. + + 1byte percentage scaled KNX Telegram. + Defined in KNX 3.7.2 - 3.10. + """ + summed_value = 0 + try: + # convert raw value in bytes + for val in raw_value: + summed_value *= 256 + summed_value += val + except TypeError: + # pknx returns a non-iterable type for unsuccessful reads + pass + + return round(summed_value * 100 / 255)