From aa8de420e286474124a1248142f3ae2f48097bde Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 12 Apr 2018 10:21:53 +0800 Subject: [PATCH 1/3] Support co2/light/air sensor in HomeKit --- homeassistant/components/homekit/__init__.py | 17 +++- homeassistant/components/homekit/const.py | 9 +++ .../components/homekit/type_sensors.py | 79 ++++++++++++++++++- homeassistant/components/homekit/util.py | 13 +++ 4 files changed, 115 insertions(+), 3 deletions(-) mode change 100755 => 100644 homeassistant/components/homekit/type_sensors.py diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 02d21889f6b799..6499c4d2aa025e 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -11,7 +11,7 @@ from homeassistant.components.cover import SUPPORT_SET_POSITION from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, - CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT, + ATTR_DEVICE_CLASS, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA @@ -19,7 +19,8 @@ from homeassistant.util.decorator import Registry from .const import ( DOMAIN, HOMEKIT_FILE, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FILTER, - DEFAULT_PORT, DEFAULT_AUTO_START, SERVICE_HOMEKIT_START) + DEFAULT_PORT, DEFAULT_AUTO_START, SERVICE_HOMEKIT_START, + DEVICE_CLASS_PM25, DEVICE_CLASS_CO2, DEVICE_CLASS_LUX) from .util import ( validate_entity_config, show_setup_message) @@ -108,6 +109,18 @@ def get_accessory(hass, state, aid, config): elif unit == '%': a_type = 'HumiditySensor' + device_class = state.attributes.get(ATTR_DEVICE_CLASS) + if device_class == DEVICE_CLASS_PM25 \ + or DEVICE_CLASS_PM25 in state.entity_id: + a_type = 'AirQualitySensor' + + elif device_class == DEVICE_CLASS_CO2 \ + or DEVICE_CLASS_CO2 in state.entity_id: + a_type = 'CarbonDioxideSensor' + + elif device_class == DEVICE_CLASS_LUX or unit == 'lm' or unit == 'lux': + a_type = 'LightSensor' + elif state.domain == 'switch' or state.domain == 'remote' \ or state.domain == 'input_boolean' or state.domain == 'script': a_type = 'Switch' diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 37ee9722bc4f9f..6cf288676916f8 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -34,6 +34,7 @@ # #### Services #### SERV_ACCESSORY_INFO = 'AccessoryInformation' +SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor' SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor' SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor' SERV_CONTACT_SENSOR = 'ContactSensor' @@ -41,6 +42,7 @@ # CurrentRelativeHumidity | StatusActive, StatusFault, StatusTampered, # StatusLowBattery, Name SERV_LEAK_SENSOR = 'LeakSensor' +SERV_LIGHT_SENSOR = 'LightSensor' SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name SERV_LOCK = 'LockMechanism' SERV_MOTION_SENSOR = 'MotionSensor' @@ -55,12 +57,17 @@ # #### Characteristics #### +CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity' +CHAR_AIR_QUALITY = 'AirQuality' CHAR_BRIGHTNESS = 'Brightness' # Int | [0, 100] CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected' +CHAR_CARBON_DIOXIDE_LEVEL = 'CarbonDioxideLevel' +CHAR_CARBON_DIOXIDE_PEAK_LEVEL = 'CarbonDioxidePeakLevel' CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected' CHAR_COLOR_TEMPERATURE = 'ColorTemperature' CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState' CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature' +CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = 'CurrentAmbientLightLevel' CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState' CHAR_CURRENT_POSITION = 'CurrentPosition' # Int | [0, 100] CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity' # percent @@ -93,8 +100,10 @@ # #### Device Class #### DEVICE_CLASS_CO2 = 'co2' DEVICE_CLASS_GAS = 'gas' +DEVICE_CLASS_LUX = 'lux' DEVICE_CLASS_MOISTURE = 'moisture' DEVICE_CLASS_MOTION = 'motion' DEVICE_CLASS_OCCUPANCY = 'occupancy' DEVICE_CLASS_OPENING = 'opening' +DEVICE_CLASS_PM25 = 'pm25' DEVICE_CLASS_SMOKE = 'smoke' diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py old mode 100755 new mode 100644 index 790f0de61033eb..a9ea9c2d346f2a --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -10,6 +10,9 @@ from .const import ( CATEGORY_SENSOR, SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR, CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS, + SERV_AIR_QUALITY_SENSOR, CHAR_AIR_QUALITY, CHAR_AIR_PARTICULATE_DENSITY, + CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL, + SERV_LIGHT_SENSOR, CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, DEVICE_CLASS_CO2, SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED, DEVICE_CLASS_GAS, SERV_CARBON_MONOXIDE_SENSOR, CHAR_CARBON_MONOXIDE_DETECTED, @@ -18,7 +21,8 @@ DEVICE_CLASS_OCCUPANCY, SERV_OCCUPANCY_SENSOR, CHAR_OCCUPANCY_DETECTED, DEVICE_CLASS_OPENING, SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, DEVICE_CLASS_SMOKE, SERV_SMOKE_SENSOR, CHAR_SMOKE_DETECTED) -from .util import convert_to_float, temperature_to_homekit +from .util import ( + convert_to_float, temperature_to_homekit, density_to_air_quality) _LOGGER = logging.getLogger(__name__) @@ -81,6 +85,79 @@ def update_state(self, new_state): self.entity_id, humidity) +@TYPES.register('AirQualitySensor') +class AirQualitySensor(HomeAccessory): + """Generate a AirQualitySensor accessory as air quality sensor.""" + + def __init__(self, *args, config): + """Initialize a AirQualitySensor accessory object.""" + super().__init__(*args, category=CATEGORY_SENSOR) + + serv_air_quality = add_preload_service(self, SERV_AIR_QUALITY_SENSOR, + [CHAR_AIR_PARTICULATE_DENSITY]) + self.char_quality = setup_char( + CHAR_AIR_QUALITY, serv_air_quality, value=0) + self.char_density = setup_char( + CHAR_AIR_PARTICULATE_DENSITY, serv_air_quality, value=0) + + def update_state(self, new_state): + """Update accessory after state change.""" + density = convert_to_float(new_state.state) + if density is not None: + self.char_density.set_value(density) + self.char_quality.set_value(density_to_air_quality(density)) + _LOGGER.debug('%s: Set to %d', self.entity_id, density) + + +@TYPES.register('CarbonDioxideSensor') +class CarbonDioxideSensor(HomeAccessory): + """Generate a CarbonDioxideSensor accessory as CO2 sensor.""" + + def __init__(self, *args, config): + """Initialize a CarbonDioxideSensor accessory object.""" + super().__init__(*args, category=CATEGORY_SENSOR) + + serv_co2 = add_preload_service(self, SERV_CARBON_DIOXIDE_SENSOR, + [CHAR_CARBON_DIOXIDE_LEVEL, + CHAR_CARBON_DIOXIDE_PEAK_LEVEL]) + self.char_co2 = setup_char( + CHAR_CARBON_DIOXIDE_LEVEL, serv_co2, value=0) + self.char_peak = setup_char( + CHAR_CARBON_DIOXIDE_PEAK_LEVEL, serv_co2, value=0) + self.char_detected = setup_char( + CHAR_CARBON_DIOXIDE_DETECTED, serv_co2, value=0) + + 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) + + +@TYPES.register('LightSensor') +class LightSensor(HomeAccessory): + """Generate a LightSensor accessory as light sensor.""" + + def __init__(self, *args, config): + """Initialize a LightSensor accessory object.""" + super().__init__(*args, category=CATEGORY_SENSOR) + + serv_light = add_preload_service(self, SERV_LIGHT_SENSOR) + self.char_light = setup_char( + CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, serv_light, value=0) + + def update_state(self, new_state): + """Update accessory after state change.""" + luminance = convert_to_float(new_state.state) + if luminance is not None: + self.char_light.set_value(luminance) + _LOGGER.debug('%s: Set to %d', self.entity_id, luminance) + + @TYPES.register('BinarySensor') class BinarySensor(HomeAccessory): """Generate a BinarySensor accessory as binary sensor.""" diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index e14b6c47bc884e..29fe3c8f265673 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -64,3 +64,16 @@ def temperature_to_homekit(temperature, unit): def temperature_to_states(temperature, unit): """Convert temperature back from Celsius to Home Assistant unit.""" return round(temp_util.convert(temperature, TEMP_CELSIUS, unit), 1) + + +def density_to_air_quality(density): + """Map PM2.5 density to HomeKit AirQuality level.""" + if density <= 35: + return 1 + elif density <= 75: + return 2 + elif density <= 115: + return 3 + elif density <= 150: + return 4 + return 5 From 00e7d5e419c55e2fb940686bfab4aa10a79cbd81 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 12 Apr 2018 10:31:44 +0800 Subject: [PATCH 2/3] Add test case --- .../homekit/test_get_accessories.py | 20 +++++ tests/components/homekit/test_type_sensors.py | 86 ++++++++++++++++++- tests/components/homekit/test_util.py | 10 ++- 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 6f2521fc4e5fee..8337ee6f717ca2 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -62,6 +62,26 @@ def test_sensor_humidity(self): {ATTR_UNIT_OF_MEASUREMENT: '%'}) get_accessory(None, state, 2, {}) + def test_air_quality_sensor(self): + """Test air quality sensor with pm25 class.""" + with patch.dict(TYPES, {'AirQualitySensor': self.mock_type}): + state = State('sensor.air_quality', '40', + {ATTR_DEVICE_CLASS: 'pm25'}) + get_accessory(None, state, 2, {}) + + def test_co2_sensor(self): + """Test co2 sensor with entity_id contains co2.""" + with patch.dict(TYPES, {'CarbonDioxideSensor': self.mock_type}): + state = State('sensor.airmeter_co2', '500', {}) + get_accessory(None, state, 2, {}) + + def test_light_sensor(self): + """Test light sensor with lm as unit.""" + with patch.dict(TYPES, {'LightSensor': self.mock_type}): + state = State('sensor.light', '900', + {ATTR_UNIT_OF_MEASUREMENT: 'lm'}) + get_accessory(None, state, 2, {}) + def test_binary_sensor(self): """Test binary sensor with opening class.""" with patch.dict(TYPES, {'BinarySensor': self.mock_type}): diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index f9dfb04b37c83e..2454bd014981f2 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -3,7 +3,8 @@ from homeassistant.components.homekit.const import PROP_CELSIUS from homeassistant.components.homekit.type_sensors import ( - TemperatureSensor, HumiditySensor, BinarySensor, BINARY_SENSOR_SERVICE_MAP) + TemperatureSensor, HumiditySensor, AirQualitySensor, CarbonDioxideSensor, + LightSensor, BinarySensor, BINARY_SENSOR_SERVICE_MAP) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, ATTR_DEVICE_CLASS, STATE_UNKNOWN, STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME, TEMP_CELSIUS, TEMP_FAHRENHEIT) @@ -71,6 +72,89 @@ def test_humidity(self): self.hass.block_till_done() self.assertEqual(acc.char_humidity.value, 20) + def test_air_quality(self): + """Test if accessory is updated after state change.""" + entity_id = 'sensor.air_quality' + + acc = AirQualitySensor(self.hass, 'Air Quality', entity_id, + 2, config=None) + acc.run() + + self.assertEqual(acc.aid, 2) + self.assertEqual(acc.category, 10) # Sensor + + self.assertEqual(acc.char_density.value, 0) + self.assertEqual(acc.char_quality.value, 0) + + self.hass.states.set(entity_id, STATE_UNKNOWN, + {ATTR_UNIT_OF_MEASUREMENT: "μg/m³"}) + self.hass.block_till_done() + + self.hass.states.set(entity_id, '34', + {ATTR_UNIT_OF_MEASUREMENT: "μg/m³"}) + self.hass.block_till_done() + self.assertEqual(acc.char_density.value, 34) + self.assertEqual(acc.char_quality.value, 1) + + self.hass.states.set(entity_id, '200', + {ATTR_UNIT_OF_MEASUREMENT: "μg/m³"}) + self.hass.block_till_done() + self.assertEqual(acc.char_density.value, 200) + self.assertEqual(acc.char_quality.value, 5) + + def test_co2(self): + """Test if accessory is updated after state change.""" + entity_id = 'sensor.co2' + + acc = CarbonDioxideSensor(self.hass, 'CO2', entity_id, 2, config=None) + acc.run() + + self.assertEqual(acc.aid, 2) + self.assertEqual(acc.category, 10) # Sensor + + self.assertEqual(acc.char_co2.value, 0) + self.assertEqual(acc.char_peak.value, 0) + self.assertEqual(acc.char_detected.value, 0) + + self.hass.states.set(entity_id, STATE_UNKNOWN, + {ATTR_UNIT_OF_MEASUREMENT: "ppm"}) + self.hass.block_till_done() + + self.hass.states.set(entity_id, '1100', + {ATTR_UNIT_OF_MEASUREMENT: "ppm"}) + self.hass.block_till_done() + self.assertEqual(acc.char_co2.value, 1100) + self.assertEqual(acc.char_peak.value, 1100) + self.assertEqual(acc.char_detected.value, 1) + + self.hass.states.set(entity_id, '800', + {ATTR_UNIT_OF_MEASUREMENT: "ppm"}) + self.hass.block_till_done() + self.assertEqual(acc.char_co2.value, 800) + self.assertEqual(acc.char_peak.value, 1100) + self.assertEqual(acc.char_detected.value, 0) + + def test_light(self): + """Test if accessory is updated after state change.""" + entity_id = 'sensor.light' + + acc = LightSensor(self.hass, 'Light', entity_id, 2, config=None) + acc.run() + + self.assertEqual(acc.aid, 2) + self.assertEqual(acc.category, 10) # Sensor + + self.assertEqual(acc.char_light.value, 0.0001) + + self.hass.states.set(entity_id, STATE_UNKNOWN, + {ATTR_UNIT_OF_MEASUREMENT: "lm³"}) + self.hass.block_till_done() + + self.hass.states.set(entity_id, '300', + {ATTR_UNIT_OF_MEASUREMENT: "lm"}) + self.hass.block_till_done() + self.assertEqual(acc.char_light.value, 300) + def test_binary(self): """Test if accessory is updated after state change.""" entity_id = 'binary_sensor.opening' diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 7465e9affab9d8..126a97bfb45bf6 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -8,7 +8,8 @@ from homeassistant.components.homekit.const import HOMEKIT_NOTIFY_ID from homeassistant.components.homekit.util import ( show_setup_message, dismiss_setup_message, convert_to_float, - temperature_to_homekit, temperature_to_states, ATTR_CODE) + temperature_to_homekit, temperature_to_states, ATTR_CODE, + density_to_air_quality) from homeassistant.components.homekit.util import validate_entity_config \ as vec from homeassistant.components.persistent_notification import ( @@ -100,3 +101,10 @@ def test_temperature_to_states(self): """Test temperature conversion from HomeKit to HA.""" self.assertEqual(temperature_to_states(20, TEMP_CELSIUS), 20.0) self.assertEqual(temperature_to_states(20.2, TEMP_FAHRENHEIT), 68.4) + + def test_density_to_air_quality(self): + """Test map PM2.5 density to HomeKit AirQuality level.""" + self.assertEqual(density_to_air_quality(0), 1) + self.assertEqual(density_to_air_quality(20), 1) + self.assertEqual(density_to_air_quality(35.1), 2) + self.assertEqual(density_to_air_quality(300), 5) From ab0330383473f394a10445765b08981a342c0687 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Thu, 12 Apr 2018 12:47:13 +0200 Subject: [PATCH 3/3] Small changes * Added tests * changed device_class lux to light --- homeassistant/components/homekit/__init__.py | 19 ++-- homeassistant/components/homekit/const.py | 11 ++- .../components/homekit/type_sensors.py | 7 +- .../homekit/test_get_accessories.py | 41 +++++++++ tests/components/homekit/test_type_sensors.py | 37 ++++---- tests/components/homekit/test_util.py | 86 ++++++++++--------- 6 files changed, 124 insertions(+), 77 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 6499c4d2aa025e..1092cea0c6e74a 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -20,7 +20,8 @@ from .const import ( DOMAIN, HOMEKIT_FILE, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FILTER, DEFAULT_PORT, DEFAULT_AUTO_START, SERVICE_HOMEKIT_START, - DEVICE_CLASS_PM25, DEVICE_CLASS_CO2, DEVICE_CLASS_LUX) + DEVICE_CLASS_CO2, DEVICE_CLASS_LIGHT, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PM25, DEVICE_CLASS_TEMPERATURE) from .util import ( validate_entity_config, show_setup_message) @@ -104,21 +105,21 @@ def get_accessory(hass, state, aid, config): elif state.domain == 'sensor': unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - if unit == TEMP_CELSIUS or unit == TEMP_FAHRENHEIT: + device_class = state.attributes.get(ATTR_DEVICE_CLASS) + + if device_class == DEVICE_CLASS_TEMPERATURE or unit == TEMP_CELSIUS \ + or unit == TEMP_FAHRENHEIT: a_type = 'TemperatureSensor' - elif unit == '%': + elif device_class == DEVICE_CLASS_HUMIDITY or unit == '%': a_type = 'HumiditySensor' - - device_class = state.attributes.get(ATTR_DEVICE_CLASS) - if device_class == DEVICE_CLASS_PM25 \ + elif device_class == DEVICE_CLASS_PM25 \ or DEVICE_CLASS_PM25 in state.entity_id: a_type = 'AirQualitySensor' - elif device_class == DEVICE_CLASS_CO2 \ or DEVICE_CLASS_CO2 in state.entity_id: a_type = 'CarbonDioxideSensor' - - elif device_class == DEVICE_CLASS_LUX or unit == 'lm' or unit == 'lux': + elif device_class == DEVICE_CLASS_LIGHT or unit == 'lm' or \ + unit == 'lux': a_type = 'LightSensor' elif state.domain == 'switch' or state.domain == 'remote' \ diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 6cf288676916f8..7cde51b541685f 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -38,9 +38,7 @@ SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor' SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor' SERV_CONTACT_SENSOR = 'ContactSensor' -SERV_HUMIDITY_SENSOR = 'HumiditySensor' -# CurrentRelativeHumidity | StatusActive, StatusFault, StatusTampered, -# StatusLowBattery, Name +SERV_HUMIDITY_SENSOR = 'HumiditySensor' # CurrentRelativeHumidity SERV_LEAK_SENSOR = 'LeakSensor' SERV_LIGHT_SENSOR = 'LightSensor' SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name @@ -52,8 +50,7 @@ SERV_SWITCH = 'Switch' SERV_TEMPERATURE_SENSOR = 'TemperatureSensor' SERV_THERMOSTAT = 'Thermostat' -SERV_WINDOW_COVERING = 'WindowCovering' -# CurrentPosition, TargetPosition, PositionState +SERV_WINDOW_COVERING = 'WindowCovering' # CurrentPosition, TargetPosition # #### Characteristics #### @@ -100,10 +97,12 @@ # #### Device Class #### DEVICE_CLASS_CO2 = 'co2' DEVICE_CLASS_GAS = 'gas' -DEVICE_CLASS_LUX = 'lux' +DEVICE_CLASS_HUMIDITY = 'humidity' +DEVICE_CLASS_LIGHT = 'light' DEVICE_CLASS_MOISTURE = 'moisture' DEVICE_CLASS_MOTION = 'motion' DEVICE_CLASS_OCCUPANCY = 'occupancy' DEVICE_CLASS_OPENING = 'opening' DEVICE_CLASS_PM25 = 'pm25' DEVICE_CLASS_SMOKE = 'smoke' +DEVICE_CLASS_TEMPERATURE = 'temperature' diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index a9ea9c2d346f2a..6aa8d92c0afc29 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -117,9 +117,8 @@ def __init__(self, *args, config): """Initialize a CarbonDioxideSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_co2 = add_preload_service(self, SERV_CARBON_DIOXIDE_SENSOR, - [CHAR_CARBON_DIOXIDE_LEVEL, - CHAR_CARBON_DIOXIDE_PEAK_LEVEL]) + serv_co2 = add_preload_service(self, SERV_CARBON_DIOXIDE_SENSOR, [ + CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL]) self.char_co2 = setup_char( CHAR_CARBON_DIOXIDE_LEVEL, serv_co2, value=0) self.char_peak = setup_char( @@ -130,7 +129,7 @@ def __init__(self, *args, config): def update_state(self, new_state): """Update accessory after state change.""" co2 = convert_to_float(new_state.state) - if co2: + if co2 is not None: self.char_co2.set_value(co2) if co2 > self.char_peak.value: self.char_peak.set_value(co2) diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 8337ee6f717ca2..052b7557c11785 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -41,6 +41,13 @@ def tearDown(self): """Test if mock type was called.""" self.assertTrue(self.mock_type.called) + def test_sensor_temperature(self): + """Test temperature sensor with device class temperature.""" + with patch.dict(TYPES, {'TemperatureSensor': self.mock_type}): + state = State('sensor.temperature', '23', + {ATTR_DEVICE_CLASS: 'temperature'}) + get_accessory(None, state, 2, {}) + def test_sensor_temperature_celsius(self): """Test temperature sensor with Celsius as unit.""" with patch.dict(TYPES, {'TemperatureSensor': self.mock_type}): @@ -56,6 +63,13 @@ def test_sensor_temperature_fahrenheit(self): get_accessory(None, state, 2, {}) def test_sensor_humidity(self): + """Test humidity sensor with device class humidity.""" + with patch.dict(TYPES, {'HumiditySensor': self.mock_type}): + state = State('sensor.humidity', '20', + {ATTR_DEVICE_CLASS: 'humidity'}) + get_accessory(None, state, 2, {}) + + def test_sensor_humidity_unit(self): """Test humidity sensor with % as unit.""" with patch.dict(TYPES, {'HumiditySensor': self.mock_type}): state = State('sensor.humidity', '20', @@ -69,19 +83,46 @@ def test_air_quality_sensor(self): {ATTR_DEVICE_CLASS: 'pm25'}) get_accessory(None, state, 2, {}) + def test_air_quality_sensor_entity_id(self): + """Test air quality sensor with entity_id contains pm25.""" + with patch.dict(TYPES, {'AirQualitySensor': self.mock_type}): + state = State('sensor.air_quality_pm25', '40', {}) + get_accessory(None, state, 2, {}) + def test_co2_sensor(self): + """Test co2 sensor with device class co2.""" + with patch.dict(TYPES, {'CarbonDioxideSensor': self.mock_type}): + state = State('sensor.airmeter', '500', + {ATTR_DEVICE_CLASS: 'co2'}) + get_accessory(None, state, 2, {}) + + def test_co2_sensor_entity_id(self): """Test co2 sensor with entity_id contains co2.""" with patch.dict(TYPES, {'CarbonDioxideSensor': self.mock_type}): state = State('sensor.airmeter_co2', '500', {}) get_accessory(None, state, 2, {}) def test_light_sensor(self): + """Test light sensor with device class lux.""" + with patch.dict(TYPES, {'LightSensor': self.mock_type}): + state = State('sensor.light', '900', + {ATTR_DEVICE_CLASS: 'light'}) + get_accessory(None, state, 2, {}) + + def test_light_sensor_unit_lm(self): """Test light sensor with lm as unit.""" with patch.dict(TYPES, {'LightSensor': self.mock_type}): state = State('sensor.light', '900', {ATTR_UNIT_OF_MEASUREMENT: 'lm'}) get_accessory(None, state, 2, {}) + def test_light_sensor_unit_lux(self): + """Test light sensor with lux as unit.""" + with patch.dict(TYPES, {'LightSensor': self.mock_type}): + state = State('sensor.light', '900', + {ATTR_UNIT_OF_MEASUREMENT: 'lux'}) + get_accessory(None, state, 2, {}) + def test_binary_sensor(self): """Test binary sensor with opening class.""" with patch.dict(TYPES, {'BinarySensor': self.mock_type}): diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index 2454bd014981f2..77bfc0c890129b 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -41,6 +41,7 @@ def test_temperature(self): self.hass.states.set(entity_id, STATE_UNKNOWN, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) self.hass.block_till_done() + self.assertEqual(acc.char_temp.value, 0.0) self.hass.states.set(entity_id, '20', {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) @@ -64,11 +65,11 @@ def test_humidity(self): self.assertEqual(acc.char_humidity.value, 0) - self.hass.states.set(entity_id, STATE_UNKNOWN, - {ATTR_UNIT_OF_MEASUREMENT: "%"}) + self.hass.states.set(entity_id, STATE_UNKNOWN) self.hass.block_till_done() + self.assertEqual(acc.char_humidity.value, 0) - self.hass.states.set(entity_id, '20', {ATTR_UNIT_OF_MEASUREMENT: "%"}) + self.hass.states.set(entity_id, '20') self.hass.block_till_done() self.assertEqual(acc.char_humidity.value, 20) @@ -86,18 +87,17 @@ def test_air_quality(self): self.assertEqual(acc.char_density.value, 0) self.assertEqual(acc.char_quality.value, 0) - self.hass.states.set(entity_id, STATE_UNKNOWN, - {ATTR_UNIT_OF_MEASUREMENT: "μg/m³"}) + self.hass.states.set(entity_id, STATE_UNKNOWN) self.hass.block_till_done() + self.assertEqual(acc.char_density.value, 0) + self.assertEqual(acc.char_quality.value, 0) - self.hass.states.set(entity_id, '34', - {ATTR_UNIT_OF_MEASUREMENT: "μg/m³"}) + self.hass.states.set(entity_id, '34') self.hass.block_till_done() self.assertEqual(acc.char_density.value, 34) self.assertEqual(acc.char_quality.value, 1) - self.hass.states.set(entity_id, '200', - {ATTR_UNIT_OF_MEASUREMENT: "μg/m³"}) + self.hass.states.set(entity_id, '200') self.hass.block_till_done() self.assertEqual(acc.char_density.value, 200) self.assertEqual(acc.char_quality.value, 5) @@ -116,19 +116,19 @@ def test_co2(self): self.assertEqual(acc.char_peak.value, 0) self.assertEqual(acc.char_detected.value, 0) - self.hass.states.set(entity_id, STATE_UNKNOWN, - {ATTR_UNIT_OF_MEASUREMENT: "ppm"}) + self.hass.states.set(entity_id, STATE_UNKNOWN) self.hass.block_till_done() + self.assertEqual(acc.char_co2.value, 0) + self.assertEqual(acc.char_peak.value, 0) + self.assertEqual(acc.char_detected.value, 0) - self.hass.states.set(entity_id, '1100', - {ATTR_UNIT_OF_MEASUREMENT: "ppm"}) + self.hass.states.set(entity_id, '1100') self.hass.block_till_done() self.assertEqual(acc.char_co2.value, 1100) self.assertEqual(acc.char_peak.value, 1100) self.assertEqual(acc.char_detected.value, 1) - self.hass.states.set(entity_id, '800', - {ATTR_UNIT_OF_MEASUREMENT: "ppm"}) + self.hass.states.set(entity_id, '800') self.hass.block_till_done() self.assertEqual(acc.char_co2.value, 800) self.assertEqual(acc.char_peak.value, 1100) @@ -146,12 +146,11 @@ def test_light(self): self.assertEqual(acc.char_light.value, 0.0001) - self.hass.states.set(entity_id, STATE_UNKNOWN, - {ATTR_UNIT_OF_MEASUREMENT: "lm³"}) + self.hass.states.set(entity_id, STATE_UNKNOWN) self.hass.block_till_done() + self.assertEqual(acc.char_light.value, 0.0001) - self.hass.states.set(entity_id, '300', - {ATTR_UNIT_OF_MEASUREMENT: "lm"}) + self.hass.states.set(entity_id, '300') self.hass.block_till_done() self.assertEqual(acc.char_light.value, 300) diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 126a97bfb45bf6..4a9521384bd553 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -2,6 +2,7 @@ import unittest import voluptuous as vol +import pytest from homeassistant.core import callback from homeassistant.components.homekit.accessories import HomeBridge @@ -21,6 +22,52 @@ from tests.common import get_test_home_assistant +def test_validate_entity_config(): + """Test validate entities.""" + configs = [{'invalid_entity_id': {}}, {'demo.test': 1}, + {'demo.test': 'test'}, {'demo.test': [1, 2]}, + {'demo.test': None}] + + for conf in configs: + with pytest.raises(vol.Invalid): + vec(conf) + + assert vec({}) == {} + assert vec({'alarm_control_panel.demo': {ATTR_CODE: '1234'}}) == \ + {'alarm_control_panel.demo': {ATTR_CODE: '1234'}} + + +def test_convert_to_float(): + """Test convert_to_float method.""" + assert convert_to_float(12) == 12 + assert convert_to_float(12.4) == 12.4 + assert convert_to_float(STATE_UNKNOWN) is None + assert convert_to_float(None) is None + + +def test_temperature_to_homekit(): + """Test temperature conversion from HA to HomeKit.""" + assert temperature_to_homekit(20.46, TEMP_CELSIUS) == 20.5 + assert temperature_to_homekit(92.1, TEMP_FAHRENHEIT) == 33.4 + + +def test_temperature_to_states(): + """Test temperature conversion from HomeKit to HA.""" + assert temperature_to_states(20, TEMP_CELSIUS) == 20.0 + assert temperature_to_states(20.2, TEMP_FAHRENHEIT) == 68.4 + + +def test_density_to_air_quality(): + """Test map PM2.5 density to HomeKit AirQuality level.""" + assert density_to_air_quality(0) == 1 + assert density_to_air_quality(35) == 1 + assert density_to_air_quality(35.1) == 2 + assert density_to_air_quality(75) == 2 + assert density_to_air_quality(115) == 3 + assert density_to_air_quality(150) == 4 + assert density_to_air_quality(300) == 5 + + class TestUtil(unittest.TestCase): """Test all HomeKit util methods.""" @@ -40,21 +87,6 @@ def tearDown(self): """Stop down everything that was started.""" self.hass.stop() - def test_validate_entity_config(self): - """Test validate entities.""" - configs = [{'invalid_entity_id': {}}, {'demo.test': 1}, - {'demo.test': 'test'}, {'demo.test': [1, 2]}, - {'demo.test': None}] - - for conf in configs: - with self.assertRaises(vol.Invalid): - vec(conf) - - self.assertEqual(vec({}), {}) - self.assertEqual( - vec({'alarm_control_panel.demo': {ATTR_CODE: '1234'}}), - {'alarm_control_panel.demo': {ATTR_CODE: '1234'}}) - def test_show_setup_msg(self): """Test show setup message as persistence notification.""" bridge = HomeBridge(self.hass) @@ -84,27 +116,3 @@ def test_dismiss_setup_msg(self): self.assertEqual( data[ATTR_SERVICE_DATA].get(ATTR_NOTIFICATION_ID, None), HOMEKIT_NOTIFY_ID) - - def test_convert_to_float(self): - """Test convert_to_float method.""" - self.assertEqual(convert_to_float(12), 12) - self.assertEqual(convert_to_float(12.4), 12.4) - self.assertIsNone(convert_to_float(STATE_UNKNOWN)) - self.assertIsNone(convert_to_float(None)) - - def test_temperature_to_homekit(self): - """Test temperature conversion from HA to HomeKit.""" - self.assertEqual(temperature_to_homekit(20.46, TEMP_CELSIUS), 20.5) - self.assertEqual(temperature_to_homekit(92.1, TEMP_FAHRENHEIT), 33.4) - - def test_temperature_to_states(self): - """Test temperature conversion from HomeKit to HA.""" - self.assertEqual(temperature_to_states(20, TEMP_CELSIUS), 20.0) - self.assertEqual(temperature_to_states(20.2, TEMP_FAHRENHEIT), 68.4) - - def test_density_to_air_quality(self): - """Test map PM2.5 density to HomeKit AirQuality level.""" - self.assertEqual(density_to_air_quality(0), 1) - self.assertEqual(density_to_air_quality(20), 1) - self.assertEqual(density_to_air_quality(35.1), 2) - self.assertEqual(density_to_air_quality(300), 5)