Support CO2/PM2.5/Light sensors in HomeKit (#13804)

* Support co2/light/air sensor in HomeKit
* Add tests
* Added tests
* changed device_class lux to light
This commit is contained in:
Yonsm 2018-04-12 21:01:41 +08:00 committed by cdce8p
parent f47572d3c0
commit c863b9614c
7 changed files with 318 additions and 47 deletions

View File

@ -11,7 +11,7 @@ import voluptuous as vol
from homeassistant.components.cover import SUPPORT_SET_POSITION from homeassistant.components.cover import SUPPORT_SET_POSITION
from homeassistant.const import ( from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, 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) EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.helpers.entityfilter import FILTER_SCHEMA
@ -19,7 +19,9 @@ from homeassistant.util import get_local_ip
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
from .const import ( from .const import (
DOMAIN, HOMEKIT_FILE, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FILTER, 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_CO2, DEVICE_CLASS_LIGHT, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM25, DEVICE_CLASS_TEMPERATURE)
from .util import ( from .util import (
validate_entity_config, show_setup_message) validate_entity_config, show_setup_message)
@ -103,10 +105,22 @@ def get_accessory(hass, state, aid, config):
elif state.domain == 'sensor': elif state.domain == 'sensor':
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) 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' a_type = 'TemperatureSensor'
elif unit == '%': elif device_class == DEVICE_CLASS_HUMIDITY or unit == '%':
a_type = 'HumiditySensor' a_type = 'HumiditySensor'
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_LIGHT or unit == 'lm' or \
unit == 'lux':
a_type = 'LightSensor'
elif state.domain == 'switch' or state.domain == 'remote' \ elif state.domain == 'switch' or state.domain == 'remote' \
or state.domain == 'input_boolean' or state.domain == 'script': or state.domain == 'input_boolean' or state.domain == 'script':

View File

@ -34,13 +34,13 @@ CATEGORY_WINDOW_COVERING = 'WINDOW_COVERING'
# #### Services #### # #### Services ####
SERV_ACCESSORY_INFO = 'AccessoryInformation' SERV_ACCESSORY_INFO = 'AccessoryInformation'
SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor'
SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor' SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor'
SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor' SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor'
SERV_CONTACT_SENSOR = 'ContactSensor' SERV_CONTACT_SENSOR = 'ContactSensor'
SERV_HUMIDITY_SENSOR = 'HumiditySensor' SERV_HUMIDITY_SENSOR = 'HumiditySensor' # CurrentRelativeHumidity
# CurrentRelativeHumidity | StatusActive, StatusFault, StatusTampered,
# StatusLowBattery, Name
SERV_LEAK_SENSOR = 'LeakSensor' SERV_LEAK_SENSOR = 'LeakSensor'
SERV_LIGHT_SENSOR = 'LightSensor'
SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name
SERV_LOCK = 'LockMechanism' SERV_LOCK = 'LockMechanism'
SERV_MOTION_SENSOR = 'MotionSensor' SERV_MOTION_SENSOR = 'MotionSensor'
@ -50,17 +50,21 @@ SERV_SMOKE_SENSOR = 'SmokeSensor'
SERV_SWITCH = 'Switch' SERV_SWITCH = 'Switch'
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor' SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
SERV_THERMOSTAT = 'Thermostat' SERV_THERMOSTAT = 'Thermostat'
SERV_WINDOW_COVERING = 'WindowCovering' SERV_WINDOW_COVERING = 'WindowCovering' # CurrentPosition, TargetPosition
# CurrentPosition, TargetPosition, PositionState
# #### Characteristics #### # #### Characteristics ####
CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity'
CHAR_AIR_QUALITY = 'AirQuality'
CHAR_BRIGHTNESS = 'Brightness' # Int | [0, 100] CHAR_BRIGHTNESS = 'Brightness' # Int | [0, 100]
CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected' CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected'
CHAR_CARBON_DIOXIDE_LEVEL = 'CarbonDioxideLevel'
CHAR_CARBON_DIOXIDE_PEAK_LEVEL = 'CarbonDioxidePeakLevel'
CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected' CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected'
CHAR_COLOR_TEMPERATURE = 'ColorTemperature' CHAR_COLOR_TEMPERATURE = 'ColorTemperature'
CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState' CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState'
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature' CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = 'CurrentAmbientLightLevel'
CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState' CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState'
CHAR_CURRENT_POSITION = 'CurrentPosition' # Int | [0, 100] CHAR_CURRENT_POSITION = 'CurrentPosition' # Int | [0, 100]
CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity' # percent CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity' # percent
@ -93,8 +97,12 @@ PROP_CELSIUS = {'minValue': -273, 'maxValue': 999}
# #### Device Class #### # #### Device Class ####
DEVICE_CLASS_CO2 = 'co2' DEVICE_CLASS_CO2 = 'co2'
DEVICE_CLASS_GAS = 'gas' DEVICE_CLASS_GAS = 'gas'
DEVICE_CLASS_HUMIDITY = 'humidity'
DEVICE_CLASS_LIGHT = 'light'
DEVICE_CLASS_MOISTURE = 'moisture' DEVICE_CLASS_MOISTURE = 'moisture'
DEVICE_CLASS_MOTION = 'motion' DEVICE_CLASS_MOTION = 'motion'
DEVICE_CLASS_OCCUPANCY = 'occupancy' DEVICE_CLASS_OCCUPANCY = 'occupancy'
DEVICE_CLASS_OPENING = 'opening' DEVICE_CLASS_OPENING = 'opening'
DEVICE_CLASS_PM25 = 'pm25'
DEVICE_CLASS_SMOKE = 'smoke' DEVICE_CLASS_SMOKE = 'smoke'
DEVICE_CLASS_TEMPERATURE = 'temperature'

78
homeassistant/components/homekit/type_sensors.py Executable file → Normal file
View File

@ -10,6 +10,9 @@ from .accessories import HomeAccessory, add_preload_service, setup_char
from .const import ( from .const import (
CATEGORY_SENSOR, SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR, CATEGORY_SENSOR, SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR,
CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS, 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_CO2, SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED,
DEVICE_CLASS_GAS, SERV_CARBON_MONOXIDE_SENSOR, DEVICE_CLASS_GAS, SERV_CARBON_MONOXIDE_SENSOR,
CHAR_CARBON_MONOXIDE_DETECTED, CHAR_CARBON_MONOXIDE_DETECTED,
@ -18,7 +21,8 @@ from .const import (
DEVICE_CLASS_OCCUPANCY, SERV_OCCUPANCY_SENSOR, CHAR_OCCUPANCY_DETECTED, DEVICE_CLASS_OCCUPANCY, SERV_OCCUPANCY_SENSOR, CHAR_OCCUPANCY_DETECTED,
DEVICE_CLASS_OPENING, SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, DEVICE_CLASS_OPENING, SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE,
DEVICE_CLASS_SMOKE, SERV_SMOKE_SENSOR, CHAR_SMOKE_DETECTED) 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__) _LOGGER = logging.getLogger(__name__)
@ -81,6 +85,78 @@ class HumiditySensor(HomeAccessory):
self.entity_id, humidity) 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 is not None:
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') @TYPES.register('BinarySensor')
class BinarySensor(HomeAccessory): class BinarySensor(HomeAccessory):
"""Generate a BinarySensor accessory as binary sensor.""" """Generate a BinarySensor accessory as binary sensor."""

View File

@ -64,3 +64,16 @@ def temperature_to_homekit(temperature, unit):
def temperature_to_states(temperature, unit): def temperature_to_states(temperature, unit):
"""Convert temperature back from Celsius to Home Assistant unit.""" """Convert temperature back from Celsius to Home Assistant unit."""
return round(temp_util.convert(temperature, TEMP_CELSIUS, unit), 1) 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

View File

@ -41,6 +41,13 @@ class TestGetAccessories(unittest.TestCase):
"""Test if mock type was called.""" """Test if mock type was called."""
self.assertTrue(self.mock_type.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): def test_sensor_temperature_celsius(self):
"""Test temperature sensor with Celsius as unit.""" """Test temperature sensor with Celsius as unit."""
with patch.dict(TYPES, {'TemperatureSensor': self.mock_type}): with patch.dict(TYPES, {'TemperatureSensor': self.mock_type}):
@ -56,12 +63,66 @@ class TestGetAccessories(unittest.TestCase):
get_accessory(None, state, 2, {}) get_accessory(None, state, 2, {})
def test_sensor_humidity(self): 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.""" """Test humidity sensor with % as unit."""
with patch.dict(TYPES, {'HumiditySensor': self.mock_type}): with patch.dict(TYPES, {'HumiditySensor': self.mock_type}):
state = State('sensor.humidity', '20', state = State('sensor.humidity', '20',
{ATTR_UNIT_OF_MEASUREMENT: '%'}) {ATTR_UNIT_OF_MEASUREMENT: '%'})
get_accessory(None, state, 2, {}) 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_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): def test_binary_sensor(self):
"""Test binary sensor with opening class.""" """Test binary sensor with opening class."""
with patch.dict(TYPES, {'BinarySensor': self.mock_type}): with patch.dict(TYPES, {'BinarySensor': self.mock_type}):

View File

@ -3,7 +3,8 @@ import unittest
from homeassistant.components.homekit.const import PROP_CELSIUS from homeassistant.components.homekit.const import PROP_CELSIUS
from homeassistant.components.homekit.type_sensors import ( 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 ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, ATTR_DEVICE_CLASS, STATE_UNKNOWN, STATE_ON, ATTR_UNIT_OF_MEASUREMENT, ATTR_DEVICE_CLASS, STATE_UNKNOWN, STATE_ON,
STATE_OFF, STATE_HOME, STATE_NOT_HOME, TEMP_CELSIUS, TEMP_FAHRENHEIT) STATE_OFF, STATE_HOME, STATE_NOT_HOME, TEMP_CELSIUS, TEMP_FAHRENHEIT)
@ -40,6 +41,7 @@ class TestHomekitSensors(unittest.TestCase):
self.hass.states.set(entity_id, STATE_UNKNOWN, self.hass.states.set(entity_id, STATE_UNKNOWN,
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(acc.char_temp.value, 0.0)
self.hass.states.set(entity_id, '20', self.hass.states.set(entity_id, '20',
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
@ -63,14 +65,95 @@ class TestHomekitSensors(unittest.TestCase):
self.assertEqual(acc.char_humidity.value, 0) self.assertEqual(acc.char_humidity.value, 0)
self.hass.states.set(entity_id, STATE_UNKNOWN, self.hass.states.set(entity_id, STATE_UNKNOWN)
{ATTR_UNIT_OF_MEASUREMENT: "%"})
self.hass.block_till_done() 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.hass.block_till_done()
self.assertEqual(acc.char_humidity.value, 20) 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)
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')
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')
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)
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')
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')
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)
self.hass.block_till_done()
self.assertEqual(acc.char_light.value, 0.0001)
self.hass.states.set(entity_id, '300')
self.hass.block_till_done()
self.assertEqual(acc.char_light.value, 300)
def test_binary(self): def test_binary(self):
"""Test if accessory is updated after state change.""" """Test if accessory is updated after state change."""
entity_id = 'binary_sensor.opening' entity_id = 'binary_sensor.opening'

View File

@ -2,13 +2,15 @@
import unittest import unittest
import voluptuous as vol import voluptuous as vol
import pytest
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.accessories import HomeBridge
from homeassistant.components.homekit.const import HOMEKIT_NOTIFY_ID from homeassistant.components.homekit.const import HOMEKIT_NOTIFY_ID
from homeassistant.components.homekit.util import ( from homeassistant.components.homekit.util import (
show_setup_message, dismiss_setup_message, convert_to_float, 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 \ from homeassistant.components.homekit.util import validate_entity_config \
as vec as vec
from homeassistant.components.persistent_notification import ( from homeassistant.components.persistent_notification import (
@ -20,6 +22,52 @@ from homeassistant.const import (
from tests.common import get_test_home_assistant 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): class TestUtil(unittest.TestCase):
"""Test all HomeKit util methods.""" """Test all HomeKit util methods."""
@ -39,21 +87,6 @@ class TestUtil(unittest.TestCase):
"""Stop down everything that was started.""" """Stop down everything that was started."""
self.hass.stop() 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): def test_show_setup_msg(self):
"""Test show setup message as persistence notification.""" """Test show setup message as persistence notification."""
bridge = HomeBridge(self.hass) bridge = HomeBridge(self.hass)
@ -83,20 +116,3 @@ class TestUtil(unittest.TestCase):
self.assertEqual( self.assertEqual(
data[ATTR_SERVICE_DATA].get(ATTR_NOTIFICATION_ID, None), data[ATTR_SERVICE_DATA].get(ATTR_NOTIFICATION_ID, None),
HOMEKIT_NOTIFY_ID) 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)