mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Support binary_sensor and device_tracker in HomeKit (#13735)
* Support binary_sensor and device_tracker for HomeKit * Add test for get_accessory and binary sensor * Test service.display_name and char_detected.display_name * Split test to improve speed
This commit is contained in:
parent
8beb9c2b28
commit
cb51553c2d
@ -92,6 +92,11 @@ def get_accessory(hass, state, aid, config):
|
||||
return TYPES['HumiditySensor'](hass, state.entity_id, state.name,
|
||||
aid=aid)
|
||||
|
||||
elif state.domain == 'binary_sensor' or state.domain == 'device_tracker':
|
||||
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'BinarySensor')
|
||||
return TYPES['BinarySensor'](hass, state.entity_id,
|
||||
state.name, aid=aid)
|
||||
|
||||
elif state.domain == 'cover':
|
||||
# Only add covers that support set_cover_position
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
@ -35,11 +35,18 @@ CATEGORY_WINDOW_COVERING = 'WINDOW_COVERING'
|
||||
|
||||
# #### Services ####
|
||||
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
||||
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_LEAK_SENSOR = 'LeakSensor'
|
||||
SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name
|
||||
SERV_MOTION_SENSOR = 'MotionSensor'
|
||||
SERV_OCCUPANCY_SENSOR = 'OccupancySensor'
|
||||
SERV_SECURITY_SYSTEM = 'SecuritySystem'
|
||||
SERV_SMOKE_SENSOR = 'SmokeSensor'
|
||||
SERV_SWITCH = 'Switch'
|
||||
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
|
||||
SERV_THERMOSTAT = 'Thermostat'
|
||||
@ -48,7 +55,10 @@ SERV_WINDOW_COVERING = 'WindowCovering'
|
||||
|
||||
# #### Characteristics ####
|
||||
CHAR_BRIGHTNESS = 'Brightness' # Int | [0, 100]
|
||||
CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected'
|
||||
CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected'
|
||||
CHAR_COLOR_TEMPERATURE = 'ColorTemperature'
|
||||
CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState'
|
||||
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
|
||||
CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState'
|
||||
CHAR_CURRENT_POSITION = 'CurrentPosition'
|
||||
@ -57,13 +67,17 @@ CHAR_CURRENT_SECURITY_STATE = 'SecuritySystemCurrentState'
|
||||
CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
|
||||
CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
|
||||
CHAR_HUE = 'Hue' # arcdegress | [0, 360]
|
||||
CHAR_LEAK_DETECTED = 'LeakDetected'
|
||||
CHAR_MANUFACTURER = 'Manufacturer'
|
||||
CHAR_MODEL = 'Model'
|
||||
CHAR_MOTION_DETECTED = 'MotionDetected'
|
||||
CHAR_NAME = 'Name'
|
||||
CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected'
|
||||
CHAR_ON = 'On' # boolean
|
||||
CHAR_POSITION_STATE = 'PositionState'
|
||||
CHAR_SATURATION = 'Saturation' # percent
|
||||
CHAR_SERIAL_NUMBER = 'SerialNumber'
|
||||
CHAR_SMOKE_DETECTED = 'SmokeDetected'
|
||||
CHAR_TARGET_HEATING_COOLING = 'TargetHeatingCoolingState'
|
||||
CHAR_TARGET_POSITION = 'TargetPosition'
|
||||
CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState'
|
||||
@ -72,3 +86,12 @@ CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits'
|
||||
|
||||
# #### Properties ####
|
||||
PROP_CELSIUS = {'minValue': -273, 'maxValue': 999}
|
||||
|
||||
# #### Device Class ####
|
||||
DEVICE_CLASS_CO2 = 'co2'
|
||||
DEVICE_CLASS_GAS = 'gas'
|
||||
DEVICE_CLASS_MOISTURE = 'moisture'
|
||||
DEVICE_CLASS_MOTION = 'motion'
|
||||
DEVICE_CLASS_OCCUPANCY = 'occupancy'
|
||||
DEVICE_CLASS_OPENING = 'opening'
|
||||
DEVICE_CLASS_SMOKE = 'smoke'
|
||||
|
57
homeassistant/components/homekit/type_sensors.py
Normal file → Executable file
57
homeassistant/components/homekit/type_sensors.py
Normal file → Executable file
@ -2,19 +2,40 @@
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS)
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS,
|
||||
ATTR_DEVICE_CLASS, STATE_ON, STATE_HOME)
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
from .const import (
|
||||
CATEGORY_SENSOR, SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR,
|
||||
CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS)
|
||||
CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS,
|
||||
DEVICE_CLASS_CO2, SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED,
|
||||
DEVICE_CLASS_GAS, SERV_CARBON_MONOXIDE_SENSOR,
|
||||
CHAR_CARBON_MONOXIDE_DETECTED,
|
||||
DEVICE_CLASS_MOISTURE, SERV_LEAK_SENSOR, CHAR_LEAK_DETECTED,
|
||||
DEVICE_CLASS_MOTION, SERV_MOTION_SENSOR, CHAR_MOTION_DETECTED,
|
||||
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
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
BINARY_SENSOR_SERVICE_MAP = {
|
||||
DEVICE_CLASS_CO2: (SERV_CARBON_DIOXIDE_SENSOR,
|
||||
CHAR_CARBON_DIOXIDE_DETECTED),
|
||||
DEVICE_CLASS_GAS: (SERV_CARBON_MONOXIDE_SENSOR,
|
||||
CHAR_CARBON_MONOXIDE_DETECTED),
|
||||
DEVICE_CLASS_MOISTURE: (SERV_LEAK_SENSOR, CHAR_LEAK_DETECTED),
|
||||
DEVICE_CLASS_MOTION: (SERV_MOTION_SENSOR, CHAR_MOTION_DETECTED),
|
||||
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)}
|
||||
|
||||
|
||||
@TYPES.register('TemperatureSensor')
|
||||
class TemperatureSensor(HomeAccessory):
|
||||
"""Generate a TemperatureSensor accessory for a temperature sensor.
|
||||
@ -75,3 +96,35 @@ class HumiditySensor(HomeAccessory):
|
||||
self.char_humidity.set_value(humidity)
|
||||
_LOGGER.debug('%s: Percent set to %d%%',
|
||||
self.entity_id, humidity)
|
||||
|
||||
|
||||
@TYPES.register('BinarySensor')
|
||||
class BinarySensor(HomeAccessory):
|
||||
"""Generate a BinarySensor accessory as binary sensor."""
|
||||
|
||||
def __init__(self, hass, entity_id, name, **kwargs):
|
||||
"""Initialize a BinarySensor accessory object."""
|
||||
super().__init__(name, entity_id, CATEGORY_SENSOR, **kwargs)
|
||||
|
||||
self.hass = hass
|
||||
self.entity_id = entity_id
|
||||
|
||||
device_class = hass.states.get(entity_id).attributes \
|
||||
.get(ATTR_DEVICE_CLASS)
|
||||
service_char = BINARY_SENSOR_SERVICE_MAP[device_class] \
|
||||
if device_class in BINARY_SENSOR_SERVICE_MAP \
|
||||
else BINARY_SENSOR_SERVICE_MAP[DEVICE_CLASS_OCCUPANCY]
|
||||
|
||||
service = add_preload_service(self, service_char[0])
|
||||
self.char_detected = service.get_characteristic(service_char[1])
|
||||
self.char_detected.value = 0
|
||||
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update accessory after state change."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
state = new_state.state
|
||||
detected = (state == STATE_ON) or (state == STATE_HOME)
|
||||
self.char_detected.set_value(detected)
|
||||
_LOGGER.debug('%s: Set to %d', self.entity_id, detected)
|
||||
|
@ -9,7 +9,7 @@ from homeassistant.components.climate import (
|
||||
from homeassistant.components.homekit import get_accessory, TYPES
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_UNIT_OF_MEASUREMENT, ATTR_SUPPORTED_FEATURES,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_DEVICE_CLASS)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -63,6 +63,19 @@ class TestGetAccessories(unittest.TestCase):
|
||||
{ATTR_UNIT_OF_MEASUREMENT: '%'})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_binary_sensor(self):
|
||||
"""Test binary sensor with opening class."""
|
||||
with patch.dict(TYPES, {'BinarySensor': self.mock_type}):
|
||||
state = State('binary_sensor.opening', 'on',
|
||||
{ATTR_DEVICE_CLASS: 'opening'})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_device_tracker(self):
|
||||
"""Test binary sensor with opening class."""
|
||||
with patch.dict(TYPES, {'BinarySensor': self.mock_type}):
|
||||
state = State('device_tracker.someone', 'not_home', {})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_cover_set_position(self):
|
||||
"""Test cover with support for set_cover_position."""
|
||||
with patch.dict(TYPES, {'WindowCovering': self.mock_type}):
|
||||
|
@ -3,9 +3,10 @@ import unittest
|
||||
|
||||
from homeassistant.components.homekit.const import PROP_CELSIUS
|
||||
from homeassistant.components.homekit.type_sensors import (
|
||||
TemperatureSensor, HumiditySensor)
|
||||
TemperatureSensor, HumiditySensor, BinarySensor, BINARY_SENSOR_SERVICE_MAP)
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
ATTR_UNIT_OF_MEASUREMENT, ATTR_DEVICE_CLASS, STATE_UNKNOWN, STATE_ON,
|
||||
STATE_OFF, STATE_HOME, STATE_NOT_HOME, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
@ -68,3 +69,55 @@ class TestHomekitSensors(unittest.TestCase):
|
||||
self.hass.states.set(entity_id, '20', {ATTR_UNIT_OF_MEASUREMENT: "%"})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_humidity.value, 20)
|
||||
|
||||
def test_binary(self):
|
||||
"""Test if accessory is updated after state change."""
|
||||
entity_id = 'binary_sensor.opening'
|
||||
|
||||
self.hass.states.set(entity_id, STATE_UNKNOWN,
|
||||
{ATTR_DEVICE_CLASS: "opening"})
|
||||
self.hass.block_till_done()
|
||||
|
||||
acc = BinarySensor(self.hass, entity_id, 'Window Opening', aid=2)
|
||||
acc.run()
|
||||
|
||||
self.assertEqual(acc.aid, 2)
|
||||
self.assertEqual(acc.category, 10) # Sensor
|
||||
|
||||
self.assertEqual(acc.char_detected.value, 0)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_ON,
|
||||
{ATTR_DEVICE_CLASS: "opening"})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_detected.value, 1)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_OFF,
|
||||
{ATTR_DEVICE_CLASS: "opening"})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_detected.value, 0)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_HOME,
|
||||
{ATTR_DEVICE_CLASS: "opening"})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_detected.value, 1)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_NOT_HOME,
|
||||
{ATTR_DEVICE_CLASS: "opening"})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_detected.value, 0)
|
||||
|
||||
self.hass.states.remove(entity_id)
|
||||
self.hass.block_till_done()
|
||||
|
||||
def test_binary_device_classes(self):
|
||||
"""Test if services and characteristics are assigned correctly."""
|
||||
entity_id = 'binary_sensor.demo'
|
||||
|
||||
for device_class, (service, char) in BINARY_SENSOR_SERVICE_MAP.items():
|
||||
self.hass.states.set(entity_id, STATE_OFF,
|
||||
{ATTR_DEVICE_CLASS: device_class})
|
||||
self.hass.block_till_done()
|
||||
|
||||
acc = BinarySensor(self.hass, entity_id, 'Binary Sensor', aid=2)
|
||||
self.assertEqual(acc.get_service(service).display_name, service)
|
||||
self.assertEqual(acc.char_detected.display_name, char)
|
||||
|
Loading…
x
Reference in New Issue
Block a user