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."""
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)

View file

@ -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):

View file

@ -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):

View file

@ -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)

View file

@ -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):

View file

@ -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 = {

View file

@ -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})

View file

@ -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):

View file

@ -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}
)

View file

@ -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):