From 88b9503962e91b37f8e1679c50977161813aeac7 Mon Sep 17 00:00:00 2001 From: Will W Date: Tue, 27 Jun 2017 14:25:54 +0900 Subject: [PATCH] add percentage (DPT_Scaling) KNX sensors (#8168) * add percentage (DPT_Scaling) KNX sensors 1. moved basic functionality to KNXSensorBaseClass instead of KNXSensorFloatClass 2. added "if" clause in setup for a "percentage" sensor type and added KNXSensorDPTScalingClass * support-knx-percentage-sensor: lint correction Updated convert method base sensor class to avoid lint warning (R201 - Method could be a function) * added PLATFORM_SCHEMA for configuration 1. added SCHEMA extension for defined keywords 2. moved fixed data for internal settings out of sensor logic 3. moved everything into standard KNXSensor object 4. added parsing of extra config parameters in __init__ * correct lint errors on support-knx-percentage-sensor --- homeassistant/components/sensor/knx.py | 234 +++++++++++++++---------- 1 file changed, 143 insertions(+), 91 deletions(-) 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)