From 3bfe9e757ef950d7c4a80d6c8c6ef582d36758ec Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Fri, 21 Sep 2018 12:51:02 +0200 Subject: [PATCH] Add Carbon Monoxide HomeKit Sensor (#16664) --- homeassistant/components/homekit/__init__.py | 8 ++- homeassistant/components/homekit/const.py | 7 +++ .../components/homekit/type_sensors.py | 47 +++++++++++--- .../homekit/test_get_accessories.py | 2 + tests/components/homekit/test_type_sensors.py | 62 ++++++++++++++++--- 5 files changed, 105 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index eac02855b0b..8c12243ee8f 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -22,9 +22,9 @@ from homeassistant.util import get_local_ip from homeassistant.util.decorator import Registry from .const import ( BRIDGE_NAME, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, - CONF_FILTER, DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, - DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, - TYPE_OUTLET, TYPE_SWITCH) + CONF_FILTER, DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE, + SERVICE_HOMEKIT_START, TYPE_OUTLET, TYPE_SWITCH) from .util import ( show_setup_message, validate_entity_config, validate_media_player_features) @@ -150,6 +150,8 @@ def get_accessory(hass, driver, state, aid, config): elif device_class == DEVICE_CLASS_PM25 \ or DEVICE_CLASS_PM25 in state.entity_id: a_type = 'AirQualitySensor' + elif device_class == DEVICE_CLASS_CO: + a_type = 'CarbonMonoxideSensor' elif device_class == DEVICE_CLASS_CO2 \ or DEVICE_CLASS_CO2 in state.entity_id: a_type = 'CarbonDioxideSensor' diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 33d2c0bfb85..df488d4a73a 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -69,6 +69,8 @@ CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected' CHAR_CARBON_DIOXIDE_LEVEL = 'CarbonDioxideLevel' CHAR_CARBON_DIOXIDE_PEAK_LEVEL = 'CarbonDioxidePeakLevel' CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected' +CHAR_CARBON_MONOXIDE_LEVEL = 'CarbonMonoxideLevel' +CHAR_CARBON_MONOXIDE_PEAK_LEVEL = 'CarbonMonoxidePeakLevel' CHAR_CHARGING_STATE = 'ChargingState' CHAR_COLOR_TEMPERATURE = 'ColorTemperature' CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState' @@ -114,6 +116,7 @@ PROP_MIN_VALUE = 'minValue' PROP_CELSIUS = {'minValue': -273, 'maxValue': 999} # #### Device Classes #### +DEVICE_CLASS_CO = 'co' DEVICE_CLASS_CO2 = 'co2' DEVICE_CLASS_DOOR = 'door' DEVICE_CLASS_GARAGE_DOOR = 'garage_door' @@ -125,3 +128,7 @@ DEVICE_CLASS_OPENING = 'opening' DEVICE_CLASS_PM25 = 'pm25' DEVICE_CLASS_SMOKE = 'smoke' DEVICE_CLASS_WINDOW = 'window' + +# #### Thresholds #### +THRESHOLD_CO = 25 +THRESHOLD_CO2 = 1000 diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index d4c2cb58209..d2101b1e6f9 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -13,6 +13,7 @@ from .const import ( CHAR_AIR_PARTICULATE_DENSITY, CHAR_AIR_QUALITY, CHAR_CARBON_DIOXIDE_DETECTED, CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL, CHAR_CARBON_MONOXIDE_DETECTED, + CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL, CHAR_CONTACT_SENSOR_STATE, CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, CHAR_LEAK_DETECTED, CHAR_MOTION_DETECTED, CHAR_OCCUPANCY_DETECTED, CHAR_SMOKE_DETECTED, @@ -23,7 +24,7 @@ from .const import ( SERV_CARBON_DIOXIDE_SENSOR, SERV_CARBON_MONOXIDE_SENSOR, SERV_CONTACT_SENSOR, SERV_HUMIDITY_SENSOR, SERV_LEAK_SENSOR, SERV_LIGHT_SENSOR, SERV_MOTION_SENSOR, SERV_OCCUPANCY_SENSOR, - SERV_SMOKE_SENSOR, SERV_TEMPERATURE_SENSOR) + SERV_SMOKE_SENSOR, SERV_TEMPERATURE_SENSOR, THRESHOLD_CO, THRESHOLD_CO2) from .util import ( convert_to_float, temperature_to_homekit, density_to_air_quality) @@ -114,6 +115,34 @@ class AirQualitySensor(HomeAccessory): _LOGGER.debug('%s: Set to %d', self.entity_id, density) +@TYPES.register('CarbonMonoxideSensor') +class CarbonMonoxideSensor(HomeAccessory): + """Generate a CarbonMonoxidSensor accessory as CO sensor.""" + + def __init__(self, *args): + """Initialize a CarbonMonoxideSensor accessory object.""" + super().__init__(*args, category=CATEGORY_SENSOR) + + serv_co = self.add_preload_service(SERV_CARBON_MONOXIDE_SENSOR, [ + CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL]) + self.char_level = serv_co.configure_char( + CHAR_CARBON_MONOXIDE_LEVEL, value=0) + self.char_peak = serv_co.configure_char( + CHAR_CARBON_MONOXIDE_PEAK_LEVEL, value=0) + self.char_detected = serv_co.configure_char( + CHAR_CARBON_MONOXIDE_DETECTED, value=0) + + def update_state(self, new_state): + """Update accessory after state change.""" + value = convert_to_float(new_state.state) + if value: + self.char_level.set_value(value) + if value > self.char_peak.value: + self.char_peak.set_value(value) + self.char_detected.set_value(value > THRESHOLD_CO) + _LOGGER.debug('%s: Set to %d', self.entity_id, value) + + @TYPES.register('CarbonDioxideSensor') class CarbonDioxideSensor(HomeAccessory): """Generate a CarbonDioxideSensor accessory as CO2 sensor.""" @@ -124,7 +153,7 @@ class CarbonDioxideSensor(HomeAccessory): serv_co2 = self.add_preload_service(SERV_CARBON_DIOXIDE_SENSOR, [ CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL]) - self.char_co2 = serv_co2.configure_char( + self.char_level = serv_co2.configure_char( CHAR_CARBON_DIOXIDE_LEVEL, value=0) self.char_peak = serv_co2.configure_char( CHAR_CARBON_DIOXIDE_PEAK_LEVEL, value=0) @@ -133,13 +162,13 @@ class CarbonDioxideSensor(HomeAccessory): def update_state(self, new_state): """Update accessory after state change.""" - co2 = convert_to_float(new_state.state) - if co2: - self.char_co2.set_value(co2) - if co2 > self.char_peak.value: - self.char_peak.set_value(co2) - self.char_detected.set_value(co2 > 1000) - _LOGGER.debug('%s: Set to %d', self.entity_id, co2) + value = convert_to_float(new_state.state) + if value: + self.char_level.set_value(value) + if value > self.char_peak.value: + self.char_peak.set_value(value) + self.char_detected.set_value(value > THRESHOLD_CO2) + _LOGGER.debug('%s: Set to %d', self.entity_id, value) @TYPES.register('LightSensor') diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 92f8736d1fe..5b76618d460 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -106,6 +106,8 @@ def test_type_covers(type_name, entity_id, state, attrs): ('AirQualitySensor', 'sensor.air_quality_pm25', '40', {}), ('AirQualitySensor', 'sensor.air_quality', '40', {ATTR_DEVICE_CLASS: 'pm25'}), + ('CarbonMonoxideSensor', 'sensor.airmeter', '2', + {ATTR_DEVICE_CLASS: 'co'}), ('CarbonDioxideSensor', 'sensor.airmeter_co2', '500', {}), ('CarbonDioxideSensor', 'sensor.airmeter', '500', {ATTR_DEVICE_CLASS: 'co2'}), diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index 901a8e76856..ebc1c3e1306 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -1,8 +1,9 @@ """Test different accessory types: Sensors.""" -from homeassistant.components.homekit.const import PROP_CELSIUS +from homeassistant.components.homekit.const import ( + PROP_CELSIUS, THRESHOLD_CO, THRESHOLD_CO2) from homeassistant.components.homekit.type_sensors import ( - AirQualitySensor, BinarySensor, CarbonDioxideSensor, HumiditySensor, - LightSensor, TemperatureSensor, BINARY_SENSOR_SERVICE_MAP) + AirQualitySensor, BinarySensor, CarbonMonoxideSensor, CarbonDioxideSensor, + HumiditySensor, LightSensor, TemperatureSensor, BINARY_SENSOR_SERVICE_MAP) from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, STATE_HOME, STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT) @@ -94,6 +95,45 @@ async def test_air_quality(hass, hk_driver): assert acc.char_quality.value == 5 +async def test_co(hass, hk_driver): + """Test if accessory is updated after state change.""" + entity_id = 'sensor.co' + + hass.states.async_set(entity_id, None) + await hass.async_block_till_done() + acc = CarbonMonoxideSensor(hass, hk_driver, 'CO', entity_id, 2, None) + await hass.async_add_job(acc.run) + + assert acc.aid == 2 + assert acc.category == 10 # Sensor + + assert acc.char_level.value == 0 + assert acc.char_peak.value == 0 + assert acc.char_detected.value == 0 + + hass.states.async_set(entity_id, STATE_UNKNOWN) + await hass.async_block_till_done() + assert acc.char_level.value == 0 + assert acc.char_peak.value == 0 + assert acc.char_detected.value == 0 + + value = 32 + assert value > THRESHOLD_CO + hass.states.async_set(entity_id, str(value)) + await hass.async_block_till_done() + assert acc.char_level.value == 32 + assert acc.char_peak.value == 32 + assert acc.char_detected.value == 1 + + value = 10 + assert value < THRESHOLD_CO + hass.states.async_set(entity_id, str(value)) + await hass.async_block_till_done() + assert acc.char_level.value == 10 + assert acc.char_peak.value == 32 + assert acc.char_detected.value == 0 + + async def test_co2(hass, hk_driver): """Test if accessory is updated after state change.""" entity_id = 'sensor.co2' @@ -106,25 +146,29 @@ async def test_co2(hass, hk_driver): assert acc.aid == 2 assert acc.category == 10 # Sensor - assert acc.char_co2.value == 0 + assert acc.char_level.value == 0 assert acc.char_peak.value == 0 assert acc.char_detected.value == 0 hass.states.async_set(entity_id, STATE_UNKNOWN) await hass.async_block_till_done() - assert acc.char_co2.value == 0 + assert acc.char_level.value == 0 assert acc.char_peak.value == 0 assert acc.char_detected.value == 0 - hass.states.async_set(entity_id, '1100') + value = 1100 + assert value > THRESHOLD_CO2 + hass.states.async_set(entity_id, str(value)) await hass.async_block_till_done() - assert acc.char_co2.value == 1100 + assert acc.char_level.value == 1100 assert acc.char_peak.value == 1100 assert acc.char_detected.value == 1 - hass.states.async_set(entity_id, '800') + value = 800 + assert value < THRESHOLD_CO2 + hass.states.async_set(entity_id, str(value)) await hass.async_block_till_done() - assert acc.char_co2.value == 800 + assert acc.char_level.value == 800 assert acc.char_peak.value == 1100 assert acc.char_detected.value == 0