Add support for homekit controller sensors (#21535)

Adds support for homekit devices with temperature, humidity, and
light level characteristics (such as the iHome iSS50)
This commit is contained in:
cpopp 2019-02-28 12:09:04 -06:00 committed by Paulus Schoutsen
parent 82bdd9568d
commit 84b84559a4
4 changed files with 240 additions and 2 deletions

View File

@ -25,6 +25,9 @@ HOMEKIT_ACCESSORY_DISPATCH = {
'window-covering': 'cover', 'window-covering': 'cover',
'lock-mechanism': 'lock', 'lock-mechanism': 'lock',
'motion': 'binary_sensor', 'motion': 'binary_sensor',
'humidity': 'sensor',
'light': 'sensor',
'temperature': 'sensor'
} }
HOMEKIT_IGNORE = [ HOMEKIT_IGNORE = [

View File

@ -0,0 +1,153 @@
"""Support for Homekit sensors."""
from homeassistant.components.homekit_controller import (
KNOWN_ACCESSORIES, HomeKitEntity)
from homeassistant.const import TEMP_CELSIUS
DEPENDENCIES = ['homekit_controller']
HUMIDITY_ICON = 'mdi-water-percent'
TEMP_C_ICON = "mdi-temperature-celsius"
BRIGHTNESS_ICON = "mdi-brightness-6"
UNIT_PERCENT = "%"
UNIT_LUX = "lux"
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Homekit sensor support."""
if discovery_info is not None:
accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']]
devtype = discovery_info['device-type']
if devtype == 'humidity':
add_entities(
[HomeKitHumiditySensor(accessory, discovery_info)], True)
elif devtype == 'temperature':
add_entities(
[HomeKitTemperatureSensor(accessory, discovery_info)], True)
elif devtype == 'light':
add_entities(
[HomeKitLightSensor(accessory, discovery_info)], True)
class HomeKitHumiditySensor(HomeKitEntity):
"""Representation of a Homekit humidity sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT
]
@property
def name(self):
"""Return the name of the device."""
return "{} {}".format(super().name, "Humidity")
@property
def icon(self):
"""Return the sensor icon."""
return HUMIDITY_ICON
@property
def unit_of_measurement(self):
"""Return units for the sensor."""
return UNIT_PERCENT
def _update_relative_humidity_current(self, value):
self._state = value
@property
def state(self):
"""Return the current humidity."""
return self._state
class HomeKitTemperatureSensor(HomeKitEntity):
"""Representation of a Homekit temperature sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.TEMPERATURE_CURRENT
]
@property
def name(self):
"""Return the name of the device."""
return "{} {}".format(super().name, "Temperature")
@property
def icon(self):
"""Return the sensor icon."""
return TEMP_C_ICON
@property
def unit_of_measurement(self):
"""Return units for the sensor."""
return TEMP_CELSIUS
def _update_temperature_current(self, value):
self._state = value
@property
def state(self):
"""Return the current temperature in Celsius."""
return self._state
class HomeKitLightSensor(HomeKitEntity):
"""Representation of a Homekit light level sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking."""
# pylint: disable=import-error
from homekit.model.characteristics import CharacteristicsTypes
return [
CharacteristicsTypes.LIGHT_LEVEL_CURRENT
]
@property
def name(self):
"""Return the name of the device."""
return "{} {}".format(super().name, "Light Level")
@property
def icon(self):
"""Return the sensor icon."""
return BRIGHTNESS_ICON
@property
def unit_of_measurement(self):
"""Return units for the sensor."""
return UNIT_LUX
def _update_light_level_current(self, value):
self._state = value
@property
def state(self):
"""Return the current light level in lux."""
return self._state

View File

@ -134,10 +134,12 @@ class FakeService(AbstractService):
return char return char
async def setup_test_component(hass, services, capitalize=False): async def setup_test_component(hass, services, capitalize=False, suffix=None):
"""Load a fake homekit accessory based on a homekit accessory model. """Load a fake homekit accessory based on a homekit accessory model.
If capitalize is True, property names will be in upper case. If capitalize is True, property names will be in upper case.
If suffix is set, entityId will include the suffix
""" """
domain = None domain = None
for service in services: for service in services:
@ -174,4 +176,5 @@ async def setup_test_component(hass, services, capitalize=False):
fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info) fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info)
await hass.async_block_till_done() await hass.async_block_till_done()
return Helper(hass, '.'.join((domain, 'testdevice')), pairing, accessory) entity = 'testdevice' if suffix is None else 'testdevice_{}'.format(suffix)
return Helper(hass, '.'.join((domain, entity)), pairing, accessory)

View File

@ -0,0 +1,79 @@
"""Basic checks for HomeKit sensor."""
from tests.components.homekit_controller.common import (
FakeService, setup_test_component)
TEMPERATURE = ('temperature', 'temperature.current')
HUMIDITY = ('humidity', 'relative-humidity.current')
LIGHT_LEVEL = ('light', 'light-level.current')
def create_temperature_sensor_service():
"""Define temperature characteristics."""
service = FakeService('public.hap.service.sensor.temperature')
cur_state = service.add_characteristic('temperature.current')
cur_state.value = 0
return service
def create_humidity_sensor_service():
"""Define humidity characteristics."""
service = FakeService('public.hap.service.sensor.humidity')
cur_state = service.add_characteristic('relative-humidity.current')
cur_state.value = 0
return service
def create_light_level_sensor_service():
"""Define light level characteristics."""
service = FakeService('public.hap.service.sensor.light')
cur_state = service.add_characteristic('light-level.current')
cur_state.value = 0
return service
async def test_temperature_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit temperature sensor accessory."""
sensor = create_temperature_sensor_service()
helper = await setup_test_component(hass, [sensor], suffix="temperature")
helper.characteristics[TEMPERATURE].value = 10
state = await helper.poll_and_get_state()
assert state.state == '10'
helper.characteristics[TEMPERATURE].value = 20
state = await helper.poll_and_get_state()
assert state.state == '20'
async def test_humidity_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit humidity sensor accessory."""
sensor = create_humidity_sensor_service()
helper = await setup_test_component(hass, [sensor], suffix="humidity")
helper.characteristics[HUMIDITY].value = 10
state = await helper.poll_and_get_state()
assert state.state == '10'
helper.characteristics[HUMIDITY].value = 20
state = await helper.poll_and_get_state()
assert state.state == '20'
async def test_light_level_sensor_read_state(hass, utcnow):
"""Test reading the state of a HomeKit temperature sensor accessory."""
sensor = create_light_level_sensor_service()
helper = await setup_test_component(hass, [sensor], suffix="light_level")
helper.characteristics[LIGHT_LEVEL].value = 10
state = await helper.poll_and_get_state()
assert state.state == '10'
helper.characteristics[LIGHT_LEVEL].value = 20
state = await helper.poll_and_get_state()
assert state.state == '20'