Simplfy homekit_controller characteristic writes (#32683)

This commit is contained in:
Jc2k 2020-03-11 16:27:20 +00:00 committed by GitHub
parent c56530a712
commit ffe8b94d75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 123 additions and 233 deletions

View file

@ -1,6 +1,7 @@
"""Support for Homekit device discovery.""" """Support for Homekit device discovery."""
import logging import logging
import os import os
from typing import Any, Dict
import aiohomekit import aiohomekit
from aiohomekit.model import Accessory from aiohomekit.model import Accessory
@ -37,7 +38,6 @@ class HomeKitEntity(Entity):
self._aid = devinfo["aid"] self._aid = devinfo["aid"]
self._iid = devinfo["iid"] self._iid = devinfo["iid"]
self._features = 0 self._features = 0
self._chars = {}
self.setup() self.setup()
self._signals = [] self._signals = []
@ -79,6 +79,24 @@ class HomeKitEntity(Entity):
signal_remove() signal_remove()
self._signals.clear() 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 @property
def should_poll(self) -> bool: def should_poll(self) -> bool:
"""Return False. """Return False.
@ -91,8 +109,6 @@ class HomeKitEntity(Entity):
"""Configure an entity baed on its HomeKit characteristics metadata.""" """Configure an entity baed on its HomeKit characteristics metadata."""
self.pollable_characteristics = [] self.pollable_characteristics = []
self.watchable_characteristics = [] self.watchable_characteristics = []
self._chars = {}
self._char_names = {}
char_types = self.get_characteristic_types() char_types = self.get_characteristic_types()
@ -116,10 +132,6 @@ class HomeKitEntity(Entity):
if CharacteristicPermissions.events in char.perms: if CharacteristicPermissions.events in char.perms:
self.watchable_characteristics.append((self._aid, char.iid)) 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 # Callback to allow entity to configure itself based on this
# characteristics metadata (valid values, value ranges, features, etc) # characteristics metadata (valid values, value ranges, features, etc)
setup_fn_name = escape_characteristic_name(char.type_name) setup_fn_name = escape_characteristic_name(char.type_name)

View file

@ -103,14 +103,9 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
async def set_alarm_state(self, state, code=None): async def set_alarm_state(self, state, code=None):
"""Send state command.""" """Send state command."""
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET: TARGET_STATE_MAP[state]}
"aid": self._aid, )
"iid": self._chars["security-system-state.target"],
"value": TARGET_STATE_MAP[state],
}
]
await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View file

@ -127,32 +127,25 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
"""Set new target temperature.""" """Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE) temp = kwargs.get(ATTR_TEMPERATURE)
characteristics = [ await self.async_put_characteristics(
{"aid": self._aid, "iid": self._chars["temperature.target"], "value": temp} {CharacteristicsTypes.TEMPERATURE_TARGET: temp}
] )
await self._accessory.put_characteristics(characteristics)
async def async_set_humidity(self, humidity): async def async_set_humidity(self, humidity):
"""Set new target humidity.""" """Set new target humidity."""
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: humidity}
"aid": self._aid, )
"iid": self._chars["relative-humidity.target"],
"value": humidity,
}
]
await self._accessory.put_characteristics(characteristics)
async def async_set_hvac_mode(self, hvac_mode): async def async_set_hvac_mode(self, hvac_mode):
"""Set new target operation mode.""" """Set new target operation mode."""
characteristics = [ await self.async_put_characteristics(
{ {
"aid": self._aid, CharacteristicsTypes.HEATING_COOLING_TARGET: MODE_HASS_TO_HOMEKIT[
"iid": self._chars["heating-cooling.target"], hvac_mode
"value": MODE_HASS_TO_HOMEKIT[hvac_mode], ],
} }
] )
await self._accessory.put_characteristics(characteristics)
@property @property
def current_temperature(self): def current_temperature(self):

View file

@ -338,12 +338,8 @@ class HKDevice:
async def put_characteristics(self, characteristics): async def put_characteristics(self, characteristics):
"""Control a HomeKit device state from Home Assistant.""" """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: 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 # Feed characteristics back into HA and update the current state
# results will only contain failures, so anythin in characteristics # results will only contain failures, so anythin in characteristics
@ -351,8 +347,8 @@ class HKDevice:
# reflect the change immediately. # reflect the change immediately.
new_entity_state = {} new_entity_state = {}
for row in characteristics: for aid, iid, value in characteristics:
key = (row["aid"], row["iid"]) key = (aid, iid)
# If the key was returned by put_characteristics() then the # If the key was returned by put_characteristics() then the
# change didn't work # change didn't work
@ -361,7 +357,7 @@ class HKDevice:
# Otherwise it was accepted and we can apply the change to # Otherwise it was accepted and we can apply the change to
# our state # our state
new_entity_state[key] = {"value": row["value"]} new_entity_state[key] = {"value": value}
self.process_new_events(new_entity_state) self.process_new_events(new_entity_state)

View file

@ -110,14 +110,9 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
async def set_door_state(self, state): async def set_door_state(self, state):
"""Send state command.""" """Send state command."""
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.DOOR_STATE_TARGET: TARGET_GARAGE_STATE_MAP[state]}
"aid": self._aid, )
"iid": self._chars["door-state.target"],
"value": TARGET_GARAGE_STATE_MAP[state],
}
]
await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@ -198,6 +193,20 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
state = CURRENT_WINDOW_STATE_MAP[value] state = CURRENT_WINDOW_STATE_MAP[value]
return state == STATE_OPENING 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 @property
def current_cover_tilt_position(self): def current_cover_tilt_position(self):
"""Return current position of cover tilt.""" """Return current position of cover tilt."""
@ -210,10 +219,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
async def async_stop_cover(self, **kwargs): async def async_stop_cover(self, **kwargs):
"""Send hold command.""" """Send hold command."""
characteristics = [ await self.async_put_characteristics({CharacteristicsTypes.POSITION_HOLD: 1})
{"aid": self._aid, "iid": self._chars["position.hold"], "value": 1}
]
await self._accessory.put_characteristics(characteristics)
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Send open command.""" """Send open command."""
@ -226,32 +232,21 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
async def async_set_cover_position(self, **kwargs): async def async_set_cover_position(self, **kwargs):
"""Send position command.""" """Send position command."""
position = kwargs[ATTR_POSITION] position = kwargs[ATTR_POSITION]
characteristics = [ await self.async_put_characteristics(
{"aid": self._aid, "iid": self._chars["position.target"], "value": position} {CharacteristicsTypes.POSITION_TARGET: position}
] )
await self._accessory.put_characteristics(characteristics)
async def async_set_cover_tilt_position(self, **kwargs): async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position.""" """Move the cover tilt to a specific position."""
tilt_position = kwargs[ATTR_TILT_POSITION] tilt_position = kwargs[ATTR_TILT_POSITION]
if "vertical-tilt.target" in self._chars: if self.is_vertical_tilt:
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.VERTICAL_TILT_TARGET: tilt_position}
"aid": self._aid, )
"iid": self._chars["vertical-tilt.target"], elif self.is_horizontal_tilt:
"value": tilt_position, await self.async_put_characteristics(
} {CharacteristicsTypes.HORIZONTAL_TILT_TARGET: 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)
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View file

@ -55,6 +55,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
CharacteristicsTypes.SWING_MODE, CharacteristicsTypes.SWING_MODE,
CharacteristicsTypes.ROTATION_DIRECTION, CharacteristicsTypes.ROTATION_DIRECTION,
CharacteristicsTypes.ROTATION_SPEED, CharacteristicsTypes.ROTATION_SPEED,
self.on_characteristic,
] ]
def _setup_rotation_direction(self, char): def _setup_rotation_direction(self, char):
@ -66,6 +67,11 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
def _setup_swing_mode(self, char): def _setup_swing_mode(self, char):
self._features |= SUPPORT_OSCILLATE self._features |= SUPPORT_OSCILLATE
@property
def is_on(self):
"""Return true if device is on."""
return self.service.value(self.on_characteristic) == 1
@property @property
def speed(self): def speed(self):
"""Return the current speed.""" """Return the current speed."""
@ -111,14 +117,8 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
async def async_set_direction(self, direction): async def async_set_direction(self, direction):
"""Set the direction of the fan.""" """Set the direction of the fan."""
await self._accessory.put_characteristics( await self.async_put_characteristics(
[ {CharacteristicsTypes.ROTATION_DIRECTION: DIRECTION_TO_HK[direction]}
{
"aid": self._aid,
"iid": self._chars["rotation.direction"],
"value": DIRECTION_TO_HK[direction],
}
]
) )
async def async_set_speed(self, speed): async def async_set_speed(self, speed):
@ -126,96 +126,45 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
if speed == SPEED_OFF: if speed == SPEED_OFF:
return await self.async_turn_off() return await self.async_turn_off()
await self._accessory.put_characteristics( await self.async_put_characteristics(
[ {CharacteristicsTypes.ROTATION_SPEED: SPEED_TO_PCNT[speed]}
{
"aid": self._aid,
"iid": self._chars["rotation.speed"],
"value": SPEED_TO_PCNT[speed],
}
]
) )
async def async_oscillate(self, oscillating: bool): async def async_oscillate(self, oscillating: bool):
"""Oscillate the fan.""" """Oscillate the fan."""
await self._accessory.put_characteristics( await self.async_put_characteristics(
[ {CharacteristicsTypes.SWING_MODE: 1 if oscillating else 0}
{
"aid": self._aid,
"iid": self._chars["swing-mode"],
"value": 1 if oscillating else 0,
}
]
) )
async def async_turn_on(self, speed=None, **kwargs): async def async_turn_on(self, speed=None, **kwargs):
"""Turn the specified fan on.""" """Turn the specified fan on."""
characteristics = [] characteristics = {}
if not self.is_on: if not self.is_on:
characteristics.append( characteristics[self.on_characteristic] = True
{
"aid": self._aid,
"iid": self._chars[self.on_characteristic],
"value": True,
}
)
if self.supported_features & SUPPORT_SET_SPEED and speed: if self.supported_features & SUPPORT_SET_SPEED and speed:
characteristics.append( characteristics[CharacteristicsTypes.ROTATION_SPEED] = SPEED_TO_PCNT[speed]
{
"aid": self._aid,
"iid": self._chars["rotation.speed"],
"value": SPEED_TO_PCNT[speed],
},
)
if not characteristics: if characteristics:
return await self.async_put_characteristics(characteristics)
await self._accessory.put_characteristics(characteristics)
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn the specified fan off.""" """Turn the specified fan off."""
characteristics = [ await self.async_put_characteristics({self.on_characteristic: False})
{
"aid": self._aid,
"iid": self._chars[self.on_characteristic],
"value": False,
}
]
await self._accessory.put_characteristics(characteristics)
class HomeKitFanV1(BaseHomeKitFan): class HomeKitFanV1(BaseHomeKitFan):
"""Implement fan support for public.hap.service.fan.""" """Implement fan support for public.hap.service.fan."""
on_characteristic = "on" on_characteristic = CharacteristicsTypes.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
class HomeKitFanV2(BaseHomeKitFan): class HomeKitFanV2(BaseHomeKitFan):
"""Implement fan support for public.hap.service.fanv2.""" """Implement fan support for public.hap.service.fanv2."""
on_characteristic = "active" on_characteristic = CharacteristicsTypes.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
ENTITY_TYPES = { ENTITY_TYPES = {

View file

@ -94,41 +94,28 @@ class HomeKitLight(HomeKitEntity, Light):
temperature = kwargs.get(ATTR_COLOR_TEMP) temperature = kwargs.get(ATTR_COLOR_TEMP)
brightness = kwargs.get(ATTR_BRIGHTNESS) brightness = kwargs.get(ATTR_BRIGHTNESS)
characteristics = [] characteristics = {}
if hs_color is not None: if hs_color is not None:
characteristics.append( characteristics.update(
{"aid": self._aid, "iid": self._chars["hue"], "value": hs_color[0]}
)
characteristics.append(
{ {
"aid": self._aid, CharacteristicsTypes.HUE: hs_color[0],
"iid": self._chars["saturation"], CharacteristicsTypes.SATURATION: hs_color[1],
"value": hs_color[1],
} }
) )
if brightness is not None: if brightness is not None:
characteristics.append( characteristics[CharacteristicsTypes.BRIGHTNESS] = int(
{ brightness * 100 / 255
"aid": self._aid,
"iid": self._chars["brightness"],
"value": int(brightness * 100 / 255),
}
) )
if temperature is not None: if temperature is not None:
characteristics.append( characteristics[CharacteristicsTypes.COLOR_TEMPERATURE] = int(temperature)
{
"aid": self._aid, characteristics[CharacteristicsTypes.ON] = True
"iid": self._chars["color-temperature"],
"value": int(temperature), await self.async_put_characteristics(characteristics)
}
)
characteristics.append(
{"aid": self._aid, "iid": self._chars["on"], "value": True}
)
await self._accessory.put_characteristics(characteristics)
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn the specified light off.""" """Turn the specified light off."""
characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": False}] await self.async_put_characteristics({CharacteristicsTypes.ON: False})
await self._accessory.put_characteristics(characteristics)

View file

@ -61,14 +61,9 @@ class HomeKitLock(HomeKitEntity, LockDevice):
async def _set_lock_state(self, state): async def _set_lock_state(self, state):
"""Send state command.""" """Send state command."""
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE: TARGET_STATE_MAP[state]}
"aid": self._aid, )
"iid": self._chars["lock-mechanism.target-state"],
"value": TARGET_STATE_MAP[state],
}
]
await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View file

@ -165,23 +165,13 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
return return
if TargetMediaStateValues.PLAY in self._supported_target_media_state: if TargetMediaStateValues.PLAY in self._supported_target_media_state:
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PLAY}
"aid": self._aid, )
"iid": self._chars["target-media-state"],
"value": TargetMediaStateValues.PLAY,
}
]
await self._accessory.put_characteristics(characteristics)
elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key: elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key:
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE}
"aid": self._aid, )
"iid": self._chars["remote-key"],
"value": RemoteKeyValues.PLAY_PAUSE,
}
]
await self._accessory.put_characteristics(characteristics)
async def async_media_pause(self): async def async_media_pause(self):
"""Send pause command.""" """Send pause command."""
@ -190,23 +180,13 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
return return
if TargetMediaStateValues.PAUSE in self._supported_target_media_state: if TargetMediaStateValues.PAUSE in self._supported_target_media_state:
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PAUSE}
"aid": self._aid, )
"iid": self._chars["target-media-state"],
"value": TargetMediaStateValues.PAUSE,
}
]
await self._accessory.put_characteristics(characteristics)
elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key: elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key:
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE}
"aid": self._aid, )
"iid": self._chars["remote-key"],
"value": RemoteKeyValues.PLAY_PAUSE,
}
]
await self._accessory.put_characteristics(characteristics)
async def async_media_stop(self): async def async_media_stop(self):
"""Send stop command.""" """Send stop command."""
@ -215,14 +195,9 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
return return
if TargetMediaStateValues.STOP in self._supported_target_media_state: if TargetMediaStateValues.STOP in self._supported_target_media_state:
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.STOP}
"aid": self._aid, )
"iid": self._chars["target-media-state"],
"value": TargetMediaStateValues.STOP,
}
]
await self._accessory.put_characteristics(characteristics)
async def async_select_source(self, source): async def async_select_source(self, source):
"""Switch to a different media source.""" """Switch to a different media source."""
@ -240,11 +215,6 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
identifier = input_source[CharacteristicsTypes.IDENTIFIER] identifier = input_source[CharacteristicsTypes.IDENTIFIER]
characteristics = [ await self.async_put_characteristics(
{ {CharacteristicsTypes.ACTIVE_IDENTIFIER: identifier.value}
"aid": self._aid, )
"iid": self._chars["active-identifier"],
"value": identifier.value,
}
]
await self._accessory.put_characteristics(characteristics)

View file

@ -43,13 +43,11 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice):
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn the specified switch on.""" """Turn the specified switch on."""
characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": True}] await self.async_put_characteristics({CharacteristicsTypes.ON: True})
await self._accessory.put_characteristics(characteristics)
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn the specified switch off.""" """Turn the specified switch off."""
characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": False}] await self.async_put_characteristics({CharacteristicsTypes.ON: False})
await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):