diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 572c2a047f3..2089471f288 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -1,6 +1,7 @@ """Support for Homekit device discovery.""" import logging import os +from typing import Any, Dict import aiohomekit from aiohomekit.model import Accessory @@ -37,7 +38,6 @@ class HomeKitEntity(Entity): self._aid = devinfo["aid"] self._iid = devinfo["iid"] self._features = 0 - self._chars = {} self.setup() self._signals = [] @@ -79,6 +79,24 @@ class HomeKitEntity(Entity): signal_remove() self._signals.clear() + async def async_put_characteristics(self, characteristics: Dict[str, Any]): + """ + Write characteristics to the device. + + A characteristic type is unique within a service, but in order to write + to a named characteristic on a bridge we need to turn its type into + an aid and iid, and send it as a list of tuples, which is what this + helper does. + + E.g. you can do: + + await entity.async_put_characteristics({ + CharacteristicsTypes.ON: True + }) + """ + payload = self.service.build_update(characteristics) + return await self._accessory.put_characteristics(payload) + @property def should_poll(self) -> bool: """Return False. @@ -91,8 +109,6 @@ class HomeKitEntity(Entity): """Configure an entity baed on its HomeKit characteristics metadata.""" self.pollable_characteristics = [] self.watchable_characteristics = [] - self._chars = {} - self._char_names = {} char_types = self.get_characteristic_types() @@ -116,10 +132,6 @@ class HomeKitEntity(Entity): if CharacteristicPermissions.events in char.perms: self.watchable_characteristics.append((self._aid, char.iid)) - # Build a map of ctype -> iid - self._chars[char.type_name] = char.iid - self._char_names[char.iid] = char.type_name - # Callback to allow entity to configure itself based on this # characteristics metadata (valid values, value ranges, features, etc) setup_fn_name = escape_characteristic_name(char.type_name) diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 0a96d716c78..9e712b4127f 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -103,14 +103,9 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel): async def set_alarm_state(self, state, code=None): """Send state command.""" - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["security-system-state.target"], - "value": TARGET_STATE_MAP[state], - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: TARGET_STATE_MAP[state]} + ) @property def device_state_attributes(self): diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index e748ea430e5..133c100b125 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -127,32 +127,25 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) - characteristics = [ - {"aid": self._aid, "iid": self._chars["temperature.target"], "value": temp} - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.TEMPERATURE_TARGET: temp} + ) async def async_set_humidity(self, humidity): """Set new target humidity.""" - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["relative-humidity.target"], - "value": humidity, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: humidity} + ) async def async_set_hvac_mode(self, hvac_mode): """Set new target operation mode.""" - characteristics = [ + await self.async_put_characteristics( { - "aid": self._aid, - "iid": self._chars["heating-cooling.target"], - "value": MODE_HASS_TO_HOMEKIT[hvac_mode], + CharacteristicsTypes.HEATING_COOLING_TARGET: MODE_HASS_TO_HOMEKIT[ + hvac_mode + ], } - ] - await self._accessory.put_characteristics(characteristics) + ) @property def current_temperature(self): diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 06f2830d5f8..605253e6235 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -338,12 +338,8 @@ class HKDevice: async def put_characteristics(self, characteristics): """Control a HomeKit device state from Home Assistant.""" - chars = [] - for row in characteristics: - chars.append((row["aid"], row["iid"], row["value"])) - async with self.pairing_lock: - results = await self.pairing.put_characteristics(chars) + results = await self.pairing.put_characteristics(characteristics) # Feed characteristics back into HA and update the current state # results will only contain failures, so anythin in characteristics @@ -351,8 +347,8 @@ class HKDevice: # reflect the change immediately. new_entity_state = {} - for row in characteristics: - key = (row["aid"], row["iid"]) + for aid, iid, value in characteristics: + key = (aid, iid) # If the key was returned by put_characteristics() then the # change didn't work @@ -361,7 +357,7 @@ class HKDevice: # Otherwise it was accepted and we can apply the change to # our state - new_entity_state[key] = {"value": row["value"]} + new_entity_state[key] = {"value": value} self.process_new_events(new_entity_state) diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 79682970496..9b73846d6a7 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -110,14 +110,9 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): async def set_door_state(self, state): """Send state command.""" - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["door-state.target"], - "value": TARGET_GARAGE_STATE_MAP[state], - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.DOOR_STATE_TARGET: TARGET_GARAGE_STATE_MAP[state]} + ) @property def device_state_attributes(self): @@ -198,6 +193,20 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): state = CURRENT_WINDOW_STATE_MAP[value] return state == STATE_OPENING + @property + def is_horizontal_tilt(self): + """Return True if the service has a horizontal tilt characteristic.""" + return ( + self.service.value(CharacteristicsTypes.HORIZONTAL_TILT_CURRENT) is not None + ) + + @property + def is_vertical_tilt(self): + """Return True if the service has a vertical tilt characteristic.""" + return ( + self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) is not None + ) + @property def current_cover_tilt_position(self): """Return current position of cover tilt.""" @@ -210,10 +219,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): async def async_stop_cover(self, **kwargs): """Send hold command.""" - characteristics = [ - {"aid": self._aid, "iid": self._chars["position.hold"], "value": 1} - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics({CharacteristicsTypes.POSITION_HOLD: 1}) async def async_open_cover(self, **kwargs): """Send open command.""" @@ -226,32 +232,21 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): async def async_set_cover_position(self, **kwargs): """Send position command.""" position = kwargs[ATTR_POSITION] - characteristics = [ - {"aid": self._aid, "iid": self._chars["position.target"], "value": position} - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.POSITION_TARGET: position} + ) async def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" tilt_position = kwargs[ATTR_TILT_POSITION] - if "vertical-tilt.target" in self._chars: - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["vertical-tilt.target"], - "value": tilt_position, - } - ] - await self._accessory.put_characteristics(characteristics) - elif "horizontal-tilt.target" in self._chars: - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["horizontal-tilt.target"], - "value": tilt_position, - } - ] - await self._accessory.put_characteristics(characteristics) + if self.is_vertical_tilt: + await self.async_put_characteristics( + {CharacteristicsTypes.VERTICAL_TILT_TARGET: tilt_position} + ) + elif self.is_horizontal_tilt: + await self.async_put_characteristics( + {CharacteristicsTypes.HORIZONTAL_TILT_TARGET: tilt_position} + ) @property def device_state_attributes(self): diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index 25cfee73fb8..f0d6967684c 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -55,6 +55,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): CharacteristicsTypes.SWING_MODE, CharacteristicsTypes.ROTATION_DIRECTION, CharacteristicsTypes.ROTATION_SPEED, + self.on_characteristic, ] def _setup_rotation_direction(self, char): @@ -66,6 +67,11 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): def _setup_swing_mode(self, char): self._features |= SUPPORT_OSCILLATE + @property + def is_on(self): + """Return true if device is on.""" + return self.service.value(self.on_characteristic) == 1 + @property def speed(self): """Return the current speed.""" @@ -111,14 +117,8 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): async def async_set_direction(self, direction): """Set the direction of the fan.""" - await self._accessory.put_characteristics( - [ - { - "aid": self._aid, - "iid": self._chars["rotation.direction"], - "value": DIRECTION_TO_HK[direction], - } - ] + await self.async_put_characteristics( + {CharacteristicsTypes.ROTATION_DIRECTION: DIRECTION_TO_HK[direction]} ) async def async_set_speed(self, speed): @@ -126,96 +126,45 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): if speed == SPEED_OFF: return await self.async_turn_off() - await self._accessory.put_characteristics( - [ - { - "aid": self._aid, - "iid": self._chars["rotation.speed"], - "value": SPEED_TO_PCNT[speed], - } - ] + await self.async_put_characteristics( + {CharacteristicsTypes.ROTATION_SPEED: SPEED_TO_PCNT[speed]} ) async def async_oscillate(self, oscillating: bool): """Oscillate the fan.""" - await self._accessory.put_characteristics( - [ - { - "aid": self._aid, - "iid": self._chars["swing-mode"], - "value": 1 if oscillating else 0, - } - ] + await self.async_put_characteristics( + {CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0} ) async def async_turn_on(self, speed=None, **kwargs): """Turn the specified fan on.""" - characteristics = [] + characteristics = {} if not self.is_on: - characteristics.append( - { - "aid": self._aid, - "iid": self._chars[self.on_characteristic], - "value": True, - } - ) + characteristics[self.on_characteristic] = True if self.supported_features & SUPPORT_SET_SPEED and speed: - characteristics.append( - { - "aid": self._aid, - "iid": self._chars["rotation.speed"], - "value": SPEED_TO_PCNT[speed], - }, - ) + characteristics[CharacteristicsTypes.ROTATION_SPEED] = SPEED_TO_PCNT[speed] - if not characteristics: - return - - await self._accessory.put_characteristics(characteristics) + if characteristics: + await self.async_put_characteristics(characteristics) async def async_turn_off(self, **kwargs): """Turn the specified fan off.""" - characteristics = [ - { - "aid": self._aid, - "iid": self._chars[self.on_characteristic], - "value": False, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics({self.on_characteristic: False}) class HomeKitFanV1(BaseHomeKitFan): """Implement fan support for public.hap.service.fan.""" - on_characteristic = "on" - - def get_characteristic_types(self): - """Define the homekit characteristics the entity cares about.""" - return [CharacteristicsTypes.ON] + super().get_characteristic_types() - - @property - def is_on(self): - """Return true if device is on.""" - return self.service.value(CharacteristicsTypes.ON) == 1 + on_characteristic = CharacteristicsTypes.ON class HomeKitFanV2(BaseHomeKitFan): """Implement fan support for public.hap.service.fanv2.""" - on_characteristic = "active" - - def get_characteristic_types(self): - """Define the homekit characteristics the entity cares about.""" - return [CharacteristicsTypes.ACTIVE] + super().get_characteristic_types() - - @property - def is_on(self): - """Return true if device is on.""" - return self.service.value(CharacteristicsTypes.ACTIVE) == 1 + on_characteristic = CharacteristicsTypes.ACTIVE ENTITY_TYPES = { diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 962cfcc466c..14ed74cc085 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -94,41 +94,28 @@ class HomeKitLight(HomeKitEntity, Light): temperature = kwargs.get(ATTR_COLOR_TEMP) brightness = kwargs.get(ATTR_BRIGHTNESS) - characteristics = [] + characteristics = {} + if hs_color is not None: - characteristics.append( - {"aid": self._aid, "iid": self._chars["hue"], "value": hs_color[0]} - ) - characteristics.append( + characteristics.update( { - "aid": self._aid, - "iid": self._chars["saturation"], - "value": hs_color[1], + CharacteristicsTypes.HUE: hs_color[0], + CharacteristicsTypes.SATURATION: hs_color[1], } ) + if brightness is not None: - characteristics.append( - { - "aid": self._aid, - "iid": self._chars["brightness"], - "value": int(brightness * 100 / 255), - } + characteristics[CharacteristicsTypes.BRIGHTNESS] = int( + brightness * 100 / 255 ) if temperature is not None: - characteristics.append( - { - "aid": self._aid, - "iid": self._chars["color-temperature"], - "value": int(temperature), - } - ) - characteristics.append( - {"aid": self._aid, "iid": self._chars["on"], "value": True} - ) - await self._accessory.put_characteristics(characteristics) + characteristics[CharacteristicsTypes.COLOR_TEMPERATURE] = int(temperature) + + characteristics[CharacteristicsTypes.ON] = True + + await self.async_put_characteristics(characteristics) async def async_turn_off(self, **kwargs): """Turn the specified light off.""" - characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": False}] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics({CharacteristicsTypes.ON: False}) diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index b79c10e2ae0..c07f85fb50f 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -61,14 +61,9 @@ class HomeKitLock(HomeKitEntity, LockDevice): async def _set_lock_state(self, state): """Send state command.""" - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["lock-mechanism.target-state"], - "value": TARGET_STATE_MAP[state], - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]} + ) @property def device_state_attributes(self): diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 798931e2cb4..2e4b05817bb 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -165,23 +165,13 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): return if TargetMediaStateValues.PLAY in self._supported_target_media_state: - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["target-media-state"], - "value": TargetMediaStateValues.PLAY, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PLAY} + ) elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key: - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["remote-key"], - "value": RemoteKeyValues.PLAY_PAUSE, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} + ) async def async_media_pause(self): """Send pause command.""" @@ -190,23 +180,13 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): return if TargetMediaStateValues.PAUSE in self._supported_target_media_state: - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["target-media-state"], - "value": TargetMediaStateValues.PAUSE, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PAUSE} + ) elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key: - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["remote-key"], - "value": RemoteKeyValues.PLAY_PAUSE, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} + ) async def async_media_stop(self): """Send stop command.""" @@ -215,14 +195,9 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): return if TargetMediaStateValues.STOP in self._supported_target_media_state: - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["target-media-state"], - "value": TargetMediaStateValues.STOP, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.STOP} + ) async def async_select_source(self, source): """Switch to a different media source.""" @@ -240,11 +215,6 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): identifier = input_source[CharacteristicsTypes.IDENTIFIER] - characteristics = [ - { - "aid": self._aid, - "iid": self._chars["active-identifier"], - "value": identifier.value, - } - ] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics( + {CharacteristicsTypes.ACTIVE_IDENTIFIER: identifier.value} + ) diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index 2251062d99c..5897bbb7b3f 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -43,13 +43,11 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn the specified switch on.""" - characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": True}] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics({CharacteristicsTypes.ON: True}) async def async_turn_off(self, **kwargs): """Turn the specified switch off.""" - characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": False}] - await self._accessory.put_characteristics(characteristics) + await self.async_put_characteristics({CharacteristicsTypes.ON: False}) @property def device_state_attributes(self):