Refactor homekit_controller entity update to work more like update coordinator (#32670)

* Clean up use of get_characteristic_types

* Get rid of get_hk_char_value helper

* Get rid of _update_fn callbacks

* Call async_write_has_state directly as async_state_changed doesnt do anything any more
This commit is contained in:
Jc2k 2020-03-11 11:40:47 +00:00 committed by GitHub
parent 4248893007
commit 647d137daa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 159 additions and 358 deletions

View file

@ -4,10 +4,9 @@ import os
import aiohomekit import aiohomekit
from aiohomekit.model import Accessory from aiohomekit.model import Accessory
from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -60,7 +59,7 @@ class HomeKitEntity(Entity):
"""Entity added to hass.""" """Entity added to hass."""
self._signals.append( self._signals.append(
self.hass.helpers.dispatcher.async_dispatcher_connect( self.hass.helpers.dispatcher.async_dispatcher_connect(
self._accessory.signal_state_updated, self.async_state_changed self._accessory.signal_state_updated, self.async_write_ha_state
) )
) )
@ -86,93 +85,53 @@ class HomeKitEntity(Entity):
def setup(self): def setup(self):
"""Configure an entity baed on its HomeKit characteristics metadata.""" """Configure an entity baed on its HomeKit characteristics metadata."""
get_uuid = CharacteristicsTypes.get_uuid
characteristic_types = [get_uuid(c) for c in self.get_characteristic_types()]
self.pollable_characteristics = [] self.pollable_characteristics = []
self.watchable_characteristics = [] self.watchable_characteristics = []
self._chars = {} self._chars = {}
self._char_names = {} self._char_names = {}
char_types = self.get_characteristic_types()
# Setup events and/or polling for characteristics directly attached to this entity # Setup events and/or polling for characteristics directly attached to this entity
for char in self.service.characteristics: for char in self.service.characteristics.filter(char_types=char_types):
if char.type not in characteristic_types: self._setup_characteristic(char)
continue
self._setup_characteristic(char.to_accessory_and_service_list())
# Setup events and/or polling for characteristics attached to sub-services of this # Setup events and/or polling for characteristics attached to sub-services of this
# entity (like an INPUT_SOURCE). # entity (like an INPUT_SOURCE).
for service in self.accessory.services.filter(parent_service=self.service): for service in self.accessory.services.filter(parent_service=self.service):
for char in service.characteristics: for char in service.characteristics.filter(char_types=char_types):
if char.type not in characteristic_types: self._setup_characteristic(char)
continue
self._setup_characteristic(char.to_accessory_and_service_list())
def _setup_characteristic(self, char): def _setup_characteristic(self, char: Characteristic):
"""Configure an entity based on a HomeKit characteristics metadata.""" """Configure an entity based on a HomeKit characteristics metadata."""
# Build up a list of (aid, iid) tuples to poll on update() # Build up a list of (aid, iid) tuples to poll on update()
if "pr" in char["perms"]: if "pr" in char.perms:
self.pollable_characteristics.append((self._aid, char["iid"])) self.pollable_characteristics.append((self._aid, char.iid))
# Build up a list of (aid, iid) tuples to subscribe to # Build up a list of (aid, iid) tuples to subscribe to
if "ev" in char["perms"]: if "ev" 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 # Build a map of ctype -> iid
short_name = CharacteristicsTypes.get_short(char["type"]) self._chars[char.type_name] = char.iid
self._chars[short_name] = char["iid"] self._char_names[char.iid] = char.type_name
self._char_names[char["iid"]] = short_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(short_name) setup_fn_name = escape_characteristic_name(char.type_name)
setup_fn = getattr(self, f"_setup_{setup_fn_name}", None) setup_fn = getattr(self, f"_setup_{setup_fn_name}", None)
if not setup_fn: if not setup_fn:
return return
setup_fn(char) setup_fn(char.to_accessory_and_service_list())
def get_hk_char_value(self, characteristic_type):
"""Return the value for a given characteristic type enum."""
state = self._accessory.current_state.get(self._aid)
if not state:
return None
char = self._chars.get(CharacteristicsTypes.get_short(characteristic_type))
if not char:
return None
return state.get(char, {}).get("value")
@callback
def async_state_changed(self):
"""Collect new data from bridge and update the entity state in hass."""
accessory_state = self._accessory.current_state.get(self._aid, {})
for iid, result in accessory_state.items():
# No value so don't process this result
if "value" not in result:
continue
# Unknown iid - this is probably for a sibling service that is part
# of the same physical accessory. Ignore it.
if iid not in self._char_names:
continue
# Callback to update the entity with this characteristic value
char_name = escape_characteristic_name(self._char_names[iid])
update_fn = getattr(self, f"_update_{char_name}", None)
if not update_fn:
continue
update_fn(result["value"])
self.async_write_ha_state()
@property @property
def unique_id(self): def unique_id(self) -> str:
"""Return the ID of this device.""" """Return the ID of this device."""
serial = self.accessory_info.value(CharacteristicsTypes.SERIAL_NUMBER) serial = self.accessory_info.value(CharacteristicsTypes.SERIAL_NUMBER)
return f"homekit-{serial}-{self._iid}" return f"homekit-{serial}-{self._iid}"
@property @property
def name(self): def name(self) -> str:
"""Return the name of the device if any.""" """Return the name of the device if any."""
return self.accessory_info.value(CharacteristicsTypes.NAME) return self.accessory_info.value(CharacteristicsTypes.NAME)

View file

@ -34,38 +34,38 @@ class HomeAirQualitySensor(HomeKitEntity, AirQualityEntity):
@property @property
def particulate_matter_2_5(self): def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level.""" """Return the particulate matter 2.5 level."""
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_PM25) return self.service.value(CharacteristicsTypes.DENSITY_PM25)
@property @property
def particulate_matter_10(self): def particulate_matter_10(self):
"""Return the particulate matter 10 level.""" """Return the particulate matter 10 level."""
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_PM10) return self.service.value(CharacteristicsTypes.DENSITY_PM10)
@property @property
def ozone(self): def ozone(self):
"""Return the O3 (ozone) level.""" """Return the O3 (ozone) level."""
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_OZONE) return self.service.value(CharacteristicsTypes.DENSITY_OZONE)
@property @property
def sulphur_dioxide(self): def sulphur_dioxide(self):
"""Return the SO2 (sulphur dioxide) level.""" """Return the SO2 (sulphur dioxide) level."""
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_SO2) return self.service.value(CharacteristicsTypes.DENSITY_SO2)
@property @property
def nitrogen_dioxide(self): def nitrogen_dioxide(self):
"""Return the NO2 (nitrogen dioxide) level.""" """Return the NO2 (nitrogen dioxide) level."""
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_NO2) return self.service.value(CharacteristicsTypes.DENSITY_NO2)
@property @property
def air_quality_text(self): def air_quality_text(self):
"""Return the Air Quality Index (AQI).""" """Return the Air Quality Index (AQI)."""
air_quality = self.get_hk_char_value(CharacteristicsTypes.AIR_QUALITY) air_quality = self.service.value(CharacteristicsTypes.AIR_QUALITY)
return AIR_QUALITY_TEXT.get(air_quality, "unknown") return AIR_QUALITY_TEXT.get(air_quality, "unknown")
@property @property
def volatile_organic_compounds(self): def volatile_organic_compounds(self):
"""Return the volatile organic compounds (VOC) level.""" """Return the volatile organic compounds (VOC) level."""
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_VOC) return self.service.value(CharacteristicsTypes.DENSITY_VOC)
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View file

@ -60,12 +60,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel): class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
"""Representation of a Homekit Alarm Control Panel.""" """Representation of a Homekit Alarm Control Panel."""
def __init__(self, *args):
"""Initialise the Alarm Control Panel."""
super().__init__(*args)
self._state = None
self._battery_level = None
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
return [ return [
@ -74,12 +68,6 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
CharacteristicsTypes.BATTERY_LEVEL, CharacteristicsTypes.BATTERY_LEVEL,
] ]
def _update_security_system_state_current(self, value):
self._state = CURRENT_STATE_MAP[value]
def _update_battery_level(self, value):
self._battery_level = value
@property @property
def icon(self): def icon(self):
"""Return icon.""" """Return icon."""
@ -88,7 +76,9 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
@property @property
def state(self): def state(self):
"""Return the state of the device.""" """Return the state of the device."""
return self._state return CURRENT_STATE_MAP[
self.service.value(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT)
]
@property @property
def supported_features(self) -> int: def supported_features(self) -> int:
@ -125,7 +115,10 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional state attributes."""
if self._battery_level is None: attributes = {}
return None
return {ATTR_BATTERY_LEVEL: self._battery_level} battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL)
if battery_level:
attributes[ATTR_BATTERY_LEVEL] = battery_level
return attributes

View file

@ -20,18 +20,10 @@ _LOGGER = logging.getLogger(__name__)
class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice):
"""Representation of a Homekit motion sensor.""" """Representation of a Homekit motion sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._on = False
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.MOTION_DETECTED] return [CharacteristicsTypes.MOTION_DETECTED]
def _update_motion_detected(self, value):
self._on = value
@property @property
def device_class(self): def device_class(self):
"""Define this binary_sensor as a motion sensor.""" """Define this binary_sensor as a motion sensor."""
@ -40,24 +32,16 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice):
@property @property
def is_on(self): def is_on(self):
"""Has motion been detected.""" """Has motion been detected."""
return self._on return self.service.value(CharacteristicsTypes.MOTION_DETECTED)
class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice): class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice):
"""Representation of a Homekit contact sensor.""" """Representation of a Homekit contact sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.CONTACT_STATE] return [CharacteristicsTypes.CONTACT_STATE]
def _update_contact_state(self, value):
self._state = value
@property @property
def device_class(self): def device_class(self):
"""Define this binary_sensor as a opening sensor.""" """Define this binary_sensor as a opening sensor."""
@ -66,17 +50,12 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice):
@property @property
def is_on(self): def is_on(self):
"""Return true if the binary sensor is on/open.""" """Return true if the binary sensor is on/open."""
return self._state == 1 return self.service.value(CharacteristicsTypes.CONTACT_STATE) == 1
class HomeKitSmokeSensor(HomeKitEntity, BinarySensorDevice): class HomeKitSmokeSensor(HomeKitEntity, BinarySensorDevice):
"""Representation of a Homekit smoke sensor.""" """Representation of a Homekit smoke sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
@property @property
def device_class(self) -> str: def device_class(self) -> str:
"""Return the class of this sensor.""" """Return the class of this sensor."""
@ -86,22 +65,14 @@ class HomeKitSmokeSensor(HomeKitEntity, BinarySensorDevice):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.SMOKE_DETECTED] return [CharacteristicsTypes.SMOKE_DETECTED]
def _update_smoke_detected(self, value):
self._state = value
@property @property
def is_on(self): def is_on(self):
"""Return true if smoke is currently detected.""" """Return true if smoke is currently detected."""
return self._state == 1 return self.service.value(CharacteristicsTypes.SMOKE_DETECTED) == 1
class HomeKitOccupancySensor(HomeKitEntity, BinarySensorDevice): class HomeKitOccupancySensor(HomeKitEntity, BinarySensorDevice):
"""Representation of a Homekit smoke sensor.""" """Representation of a Homekit occupancy sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
@property @property
def device_class(self) -> str: def device_class(self) -> str:
@ -112,13 +83,10 @@ class HomeKitOccupancySensor(HomeKitEntity, BinarySensorDevice):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.OCCUPANCY_DETECTED] return [CharacteristicsTypes.OCCUPANCY_DETECTED]
def _update_occupancy_detected(self, value):
self._state = value
@property @property
def is_on(self): def is_on(self):
"""Return true if smoke is currently detected.""" """Return true if occupancy is currently detected."""
return self._state == 1 return self.service.value(CharacteristicsTypes.OCCUPANCY_DETECTED) == 1
ENTITY_TYPES = { ENTITY_TYPES = {

View file

@ -67,14 +67,7 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
def __init__(self, *args): def __init__(self, *args):
"""Initialise the device.""" """Initialise the device."""
self._state = None
self._target_mode = None
self._current_mode = None
self._valid_modes = [] self._valid_modes = []
self._current_temp = None
self._target_temp = None
self._current_humidity = None
self._target_humidity = None
self._min_target_temp = None self._min_target_temp = None
self._max_target_temp = None self._max_target_temp = None
self._min_target_humidity = DEFAULT_MIN_HUMIDITY self._min_target_humidity = DEFAULT_MIN_HUMIDITY
@ -130,31 +123,6 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
if "maxValue" in characteristic: if "maxValue" in characteristic:
self._max_target_humidity = characteristic["maxValue"] self._max_target_humidity = characteristic["maxValue"]
def _update_heating_cooling_current(self, value):
# This characteristic describes the current mode of a device,
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
# Can be 0 - 2 (Off, Heat, Cool)
self._current_mode = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
def _update_heating_cooling_target(self, value):
# This characteristic describes the target mode
# E.g. should the device start heating a room if the temperature
# falls below the target temperature.
# Can be 0 - 3 (Off, Heat, Cool, Auto)
self._target_mode = MODE_HOMEKIT_TO_HASS.get(value)
def _update_temperature_current(self, value):
self._current_temp = value
def _update_temperature_target(self, value):
self._target_temp = value
def _update_relative_humidity_current(self, value):
self._current_humidity = value
def _update_relative_humidity_target(self, value):
self._target_humidity = value
async def async_set_temperature(self, **kwargs): async def async_set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE) temp = kwargs.get(ATTR_TEMPERATURE)
@ -189,12 +157,12 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
@property @property
def current_temperature(self): def current_temperature(self):
"""Return the current temperature.""" """Return the current temperature."""
return self._current_temp return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
@property @property
def target_temperature(self): def target_temperature(self):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self._target_temp return self.service.value(CharacteristicsTypes.TEMPERATURE_TARGET)
@property @property
def min_temp(self): def min_temp(self):
@ -213,12 +181,12 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
@property @property
def current_humidity(self): def current_humidity(self):
"""Return the current humidity.""" """Return the current humidity."""
return self._current_humidity return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
@property @property
def target_humidity(self): def target_humidity(self):
"""Return the humidity we try to reach.""" """Return the humidity we try to reach."""
return self._target_humidity return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET)
@property @property
def min_humidity(self): def min_humidity(self):
@ -233,12 +201,21 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
@property @property
def hvac_action(self): def hvac_action(self):
"""Return the current running hvac operation.""" """Return the current running hvac operation."""
return self._current_mode # This characteristic describes the current mode of a device,
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
# Can be 0 - 2 (Off, Heat, Cool)
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_CURRENT)
return CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
@property @property
def hvac_mode(self): def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode.""" """Return hvac operation ie. heat, cool mode."""
return self._target_mode # This characteristic describes the target mode
# E.g. should the device start heating a room if the temperature
# falls below the target temperature.
# Can be 0 - 3 (Off, Heat, Cool, Auto)
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
return MODE_HOMEKIT_TO_HASS.get(value)
@property @property
def hvac_modes(self): def hvac_modes(self):

View file

@ -61,13 +61,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
"""Representation of a HomeKit Garage Door.""" """Representation of a HomeKit Garage Door."""
def __init__(self, accessory, discovery_info):
"""Initialise the Cover."""
super().__init__(accessory, discovery_info)
self._state = None
self._obstruction_detected = None
self.lock_state = None
@property @property
def device_class(self): def device_class(self):
"""Define this cover as a garage door.""" """Define this cover as a garage door."""
@ -81,31 +74,31 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
CharacteristicsTypes.OBSTRUCTION_DETECTED, CharacteristicsTypes.OBSTRUCTION_DETECTED,
] ]
def _update_door_state_current(self, value):
self._state = CURRENT_GARAGE_STATE_MAP[value]
def _update_obstruction_detected(self, value):
self._obstruction_detected = value
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE return SUPPORT_OPEN | SUPPORT_CLOSE
@property
def state(self):
"""Return the current state of the garage door."""
value = self.service.value(CharacteristicsTypes.DOOR_STATE_CURRENT)
return CURRENT_GARAGE_STATE_MAP[value]
@property @property
def is_closed(self): def is_closed(self):
"""Return true if cover is closed, else False.""" """Return true if cover is closed, else False."""
return self._state == STATE_CLOSED return self.state == STATE_CLOSED
@property @property
def is_closing(self): def is_closing(self):
"""Return if the cover is closing or not.""" """Return if the cover is closing or not."""
return self._state == STATE_CLOSING return self.state == STATE_CLOSING
@property @property
def is_opening(self): def is_opening(self):
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
return self._state == STATE_OPENING return self.state == STATE_OPENING
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Send open command.""" """Send open command."""
@ -129,10 +122,15 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional state attributes."""
if self._obstruction_detected is None: attributes = {}
return None
return {"obstruction-detected": self._obstruction_detected} obstruction_detected = self.service.value(
CharacteristicsTypes.OBSTRUCTION_DETECTED
)
if obstruction_detected:
attributes["obstruction-detected"] = obstruction_detected
return attributes
class HomeKitWindowCover(HomeKitEntity, CoverDevice): class HomeKitWindowCover(HomeKitEntity, CoverDevice):
@ -141,11 +139,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
def __init__(self, accessory, discovery_info): def __init__(self, accessory, discovery_info):
"""Initialise the Cover.""" """Initialise the Cover."""
super().__init__(accessory, discovery_info) super().__init__(accessory, discovery_info)
self._state = None
self._position = None
self._tilt_position = None
self._obstruction_detected = None
self.lock_state = None
self._features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION self._features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
def get_characteristic_types(self): def get_characteristic_types(self):
@ -175,21 +169,6 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION
) )
def _update_position_state(self, value):
self._state = CURRENT_WINDOW_STATE_MAP[value]
def _update_position_current(self, value):
self._position = value
def _update_vertical_tilt_current(self, value):
self._tilt_position = value
def _update_horizontal_tilt_current(self, value):
self._tilt_position = value
def _update_obstruction_detected(self, value):
self._obstruction_detected = value
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
@ -198,22 +177,36 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
@property @property
def current_cover_position(self): def current_cover_position(self):
"""Return the current position of cover.""" """Return the current position of cover."""
return self._position return self.service.value(CharacteristicsTypes.POSITION_CURRENT)
@property @property
def is_closed(self): def is_closed(self):
"""Return true if cover is closed, else False.""" """Return true if cover is closed, else False."""
return self._position == 0 return self.current_cover_position == 0
@property @property
def is_closing(self): def is_closing(self):
"""Return if the cover is closing or not.""" """Return if the cover is closing or not."""
return self._state == STATE_CLOSING value = self.service.value(CharacteristicsTypes.POSITION_STATE)
state = CURRENT_WINDOW_STATE_MAP[value]
return state == STATE_CLOSING
@property @property
def is_opening(self): def is_opening(self):
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
return self._state == STATE_OPENING value = self.service.value(CharacteristicsTypes.POSITION_STATE)
state = CURRENT_WINDOW_STATE_MAP[value]
return state == STATE_OPENING
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt."""
tilt_position = self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT)
if not tilt_position:
tilt_position = self.service.value(
CharacteristicsTypes.HORIZONTAL_TILT_CURRENT
)
return tilt_position
async def async_stop_cover(self, **kwargs): async def async_stop_cover(self, **kwargs):
"""Send hold command.""" """Send hold command."""
@ -238,11 +231,6 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
] ]
await self._accessory.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt."""
return self._tilt_position
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]
@ -268,8 +256,12 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional state attributes."""
state_attributes = {} attributes = {}
if self._obstruction_detected is not None:
state_attributes["obstruction-detected"] = self._obstruction_detected
return state_attributes obstruction_detected = self.service.value(
CharacteristicsTypes.OBSTRUCTION_DETECTED
)
if obstruction_detected:
attributes["obstruction-detected"] = obstruction_detected
return attributes

View file

@ -46,12 +46,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
def __init__(self, *args): def __init__(self, *args):
"""Initialise the fan.""" """Initialise the fan."""
self._on = None
self._features = 0 self._features = 0
self._rotation_direction = 0
self._rotation_speed = 0
self._swing_mode = 0
super().__init__(*args) super().__init__(*args)
def get_characteristic_types(self): def get_characteristic_types(self):
@ -71,31 +66,23 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
def _setup_swing_mode(self, char): def _setup_swing_mode(self, char):
self._features |= SUPPORT_OSCILLATE self._features |= SUPPORT_OSCILLATE
def _update_rotation_direction(self, value):
self._rotation_direction = value
def _update_rotation_speed(self, value):
self._rotation_speed = value
def _update_swing_mode(self, value):
self._swing_mode = value
@property
def is_on(self):
"""Return true if device is on."""
return self._on
@property @property
def speed(self): def speed(self):
"""Return the current speed.""" """Return the current speed."""
if not self.is_on: if not self.is_on:
return SPEED_OFF return SPEED_OFF
if self._rotation_speed > SPEED_TO_PCNT[SPEED_MEDIUM]:
rotation_speed = self.service.value(CharacteristicsTypes.ROTATION_SPEED)
if rotation_speed > SPEED_TO_PCNT[SPEED_MEDIUM]:
return SPEED_HIGH return SPEED_HIGH
if self._rotation_speed > SPEED_TO_PCNT[SPEED_LOW]:
if rotation_speed > SPEED_TO_PCNT[SPEED_LOW]:
return SPEED_MEDIUM return SPEED_MEDIUM
if self._rotation_speed > SPEED_TO_PCNT[SPEED_OFF]:
if rotation_speed > SPEED_TO_PCNT[SPEED_OFF]:
return SPEED_LOW return SPEED_LOW
return SPEED_OFF return SPEED_OFF
@property @property
@ -108,12 +95,14 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
@property @property
def current_direction(self): def current_direction(self):
"""Return the current direction of the fan.""" """Return the current direction of the fan."""
return HK_DIRECTION_TO_HA[self._rotation_direction] direction = self.service.value(CharacteristicsTypes.ROTATION_DIRECTION)
return HK_DIRECTION_TO_HA[direction]
@property @property
def oscillating(self): def oscillating(self):
"""Return whether or not the fan is currently oscillating.""" """Return whether or not the fan is currently oscillating."""
return self._swing_mode == 1 oscillating = self.service.value(CharacteristicsTypes.SWING_MODE)
return oscillating == 1
@property @property
def supported_features(self): def supported_features(self):
@ -208,8 +197,10 @@ class HomeKitFanV1(BaseHomeKitFan):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
return [CharacteristicsTypes.ON] + super().get_characteristic_types() return [CharacteristicsTypes.ON] + super().get_characteristic_types()
def _update_on(self, value): @property
self._on = value == 1 def is_on(self):
"""Return true if device is on."""
return self.service.value(CharacteristicsTypes.ON) == 1
class HomeKitFanV2(BaseHomeKitFan): class HomeKitFanV2(BaseHomeKitFan):
@ -221,8 +212,10 @@ class HomeKitFanV2(BaseHomeKitFan):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
return [CharacteristicsTypes.ACTIVE] + super().get_characteristic_types() return [CharacteristicsTypes.ACTIVE] + super().get_characteristic_types()
def _update_active(self, value): @property
self._on = value == 1 def is_on(self):
"""Return true if device is on."""
return self.service.value(CharacteristicsTypes.ACTIVE) == 1
ENTITY_TYPES = { ENTITY_TYPES = {

View file

@ -38,15 +38,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class HomeKitLight(HomeKitEntity, Light): class HomeKitLight(HomeKitEntity, Light):
"""Representation of a Homekit light.""" """Representation of a Homekit light."""
def __init__(self, *args):
"""Initialise the light."""
super().__init__(*args)
self._on = False
self._brightness = 0
self._color_temperature = 0
self._hue = 0
self._saturation = 0
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
return [ return [
@ -69,40 +60,28 @@ class HomeKitLight(HomeKitEntity, Light):
def _setup_saturation(self, char): def _setup_saturation(self, char):
self._features |= SUPPORT_COLOR self._features |= SUPPORT_COLOR
def _update_on(self, value):
self._on = value
def _update_brightness(self, value):
self._brightness = value
def _update_color_temperature(self, value):
self._color_temperature = value
def _update_hue(self, value):
self._hue = value
def _update_saturation(self, value):
self._saturation = value
@property @property
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if device is on."""
return self._on return self.service.value(CharacteristicsTypes.ON)
@property @property
def brightness(self): def brightness(self):
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
return self._brightness * 255 / 100 return self.service.value(CharacteristicsTypes.BRIGHTNESS) * 255 / 100
@property @property
def hs_color(self): def hs_color(self):
"""Return the color property.""" """Return the color property."""
return (self._hue, self._saturation) return (
self.service.value(CharacteristicsTypes.HUE),
self.service.value(CharacteristicsTypes.SATURATION),
)
@property @property
def color_temp(self): def color_temp(self):
"""Return the color temperature.""" """Return the color temperature."""
return self._color_temperature return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE)
@property @property
def supported_features(self): def supported_features(self):

View file

@ -37,12 +37,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class HomeKitLock(HomeKitEntity, LockDevice): class HomeKitLock(HomeKitEntity, LockDevice):
"""Representation of a HomeKit Controller Lock.""" """Representation of a HomeKit Controller Lock."""
def __init__(self, accessory, discovery_info):
"""Initialise the Lock."""
super().__init__(accessory, discovery_info)
self._state = None
self._battery_level = None
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
return [ return [
@ -51,16 +45,11 @@ class HomeKitLock(HomeKitEntity, LockDevice):
CharacteristicsTypes.BATTERY_LEVEL, CharacteristicsTypes.BATTERY_LEVEL,
] ]
def _update_lock_mechanism_current_state(self, value):
self._state = CURRENT_STATE_MAP[value]
def _update_battery_level(self, value):
self._battery_level = value
@property @property
def is_locked(self): def is_locked(self):
"""Return true if device is locked.""" """Return true if device is locked."""
return self._state == STATE_LOCKED value = self.service.value(CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE)
return CURRENT_STATE_MAP[value] == STATE_LOCKED
async def async_lock(self, **kwargs): async def async_lock(self, **kwargs):
"""Lock the device.""" """Lock the device."""
@ -84,7 +73,10 @@ class HomeKitLock(HomeKitEntity, LockDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional state attributes."""
if self._battery_level is None: attributes = {}
return None
return {ATTR_BATTERY_LEVEL: self._battery_level} battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL)
if battery_level:
attributes[ATTR_BATTERY_LEVEL] = battery_level
return attributes

View file

@ -130,9 +130,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
@property @property
def source(self): def source(self):
"""Name of the current input source.""" """Name of the current input source."""
active_identifier = self.get_hk_char_value( active_identifier = self.service.value(CharacteristicsTypes.ACTIVE_IDENTIFIER)
CharacteristicsTypes.ACTIVE_IDENTIFIER
)
if not active_identifier: if not active_identifier:
return None return None
@ -150,11 +148,11 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
@property @property
def state(self): def state(self):
"""State of the tv.""" """State of the tv."""
active = self.get_hk_char_value(CharacteristicsTypes.ACTIVE) active = self.service.value(CharacteristicsTypes.ACTIVE)
if not active: if not active:
return STATE_PROBLEM return STATE_PROBLEM
homekit_state = self.get_hk_char_value(CharacteristicsTypes.CURRENT_MEDIA_STATE) homekit_state = self.service.value(CharacteristicsTypes.CURRENT_MEDIA_STATE)
if homekit_state is not None: if homekit_state is not None:
return HK_TO_HA_STATE[homekit_state] return HK_TO_HA_STATE[homekit_state]

View file

@ -25,11 +25,6 @@ UNIT_LUX = "lux"
class HomeKitHumiditySensor(HomeKitEntity): class HomeKitHumiditySensor(HomeKitEntity):
"""Representation of a Homekit humidity sensor.""" """Representation of a Homekit humidity sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT] return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT]
@ -54,23 +49,15 @@ class HomeKitHumiditySensor(HomeKitEntity):
"""Return units for the sensor.""" """Return units for the sensor."""
return UNIT_PERCENTAGE return UNIT_PERCENTAGE
def _update_relative_humidity_current(self, value):
self._state = value
@property @property
def state(self): def state(self):
"""Return the current humidity.""" """Return the current humidity."""
return self._state return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
class HomeKitTemperatureSensor(HomeKitEntity): class HomeKitTemperatureSensor(HomeKitEntity):
"""Representation of a Homekit temperature sensor.""" """Representation of a Homekit temperature sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.TEMPERATURE_CURRENT] return [CharacteristicsTypes.TEMPERATURE_CURRENT]
@ -95,23 +82,15 @@ class HomeKitTemperatureSensor(HomeKitEntity):
"""Return units for the sensor.""" """Return units for the sensor."""
return TEMP_CELSIUS return TEMP_CELSIUS
def _update_temperature_current(self, value):
self._state = value
@property @property
def state(self): def state(self):
"""Return the current temperature in Celsius.""" """Return the current temperature in Celsius."""
return self._state return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
class HomeKitLightSensor(HomeKitEntity): class HomeKitLightSensor(HomeKitEntity):
"""Representation of a Homekit light level sensor.""" """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): def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT] return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT]
@ -136,23 +115,15 @@ class HomeKitLightSensor(HomeKitEntity):
"""Return units for the sensor.""" """Return units for the sensor."""
return UNIT_LUX return UNIT_LUX
def _update_light_level_current(self, value):
self._state = value
@property @property
def state(self): def state(self):
"""Return the current light level in lux.""" """Return the current light level in lux."""
return self._state return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT)
class HomeKitCarbonDioxideSensor(HomeKitEntity): class HomeKitCarbonDioxideSensor(HomeKitEntity):
"""Representation of a Homekit Carbon Dioxide sensor.""" """Representation of a Homekit Carbon Dioxide sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL] return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL]
@ -172,25 +143,15 @@ class HomeKitCarbonDioxideSensor(HomeKitEntity):
"""Return units for the sensor.""" """Return units for the sensor."""
return CONCENTRATION_PARTS_PER_MILLION return CONCENTRATION_PARTS_PER_MILLION
def _update_carbon_dioxide_level(self, value):
self._state = value
@property @property
def state(self): def state(self):
"""Return the current CO2 level in ppm.""" """Return the current CO2 level in ppm."""
return self._state return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL)
class HomeKitBatterySensor(HomeKitEntity): class HomeKitBatterySensor(HomeKitEntity):
"""Representation of a Homekit battery sensor.""" """Representation of a Homekit battery sensor."""
def __init__(self, *args):
"""Initialise the entity."""
super().__init__(*args)
self._state = None
self._low_battery = False
self._charging = False
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity is tracking.""" """Define the homekit characteristics the entity is tracking."""
return [ return [
@ -218,12 +179,12 @@ class HomeKitBatterySensor(HomeKitEntity):
# This is similar to the logic in helpers.icon, but we have delegated the # This is similar to the logic in helpers.icon, but we have delegated the
# decision about what mdi:battery-alert is to the device. # decision about what mdi:battery-alert is to the device.
icon = "mdi:battery" icon = "mdi:battery"
if self._charging and self.state > 10: if self.is_charging and self.state > 10:
percentage = int(round(self.state / 20 - 0.01)) * 20 percentage = int(round(self.state / 20 - 0.01)) * 20
icon += f"-charging-{percentage}" icon += f"-charging-{percentage}"
elif self._charging: elif self.is_charging:
icon += "-outline" icon += "-outline"
elif self._low_battery: elif self.is_low_battery:
icon += "-alert" icon += "-alert"
elif self.state < 95: elif self.state < 95:
percentage = max(int(round(self.state / 10 - 0.01)) * 10, 10) percentage = max(int(round(self.state / 10 - 0.01)) * 10, 10)
@ -236,22 +197,23 @@ class HomeKitBatterySensor(HomeKitEntity):
"""Return units for the sensor.""" """Return units for the sensor."""
return UNIT_PERCENTAGE return UNIT_PERCENTAGE
def _update_battery_level(self, value): @property
self._state = value def is_low_battery(self):
"""Return true if battery level is low."""
return self.service.value(CharacteristicsTypes.STATUS_LO_BATT) == 1
def _update_status_lo_batt(self, value): @property
self._low_battery = value == 1 def is_charging(self):
"""Return true if currently charing."""
def _update_charging_state(self, value):
# 0 = not charging # 0 = not charging
# 1 = charging # 1 = charging
# 2 = not chargeable # 2 = not chargeable
self._charging = value == 1 return self.service.value(CharacteristicsTypes.CHARGING_STATE) == 1
@property @property
def state(self): def state(self):
"""Return the current battery level percentage.""" """Return the current battery level percentage."""
return self._state return self.service.value(CharacteristicsTypes.BATTERY_LEVEL)
ENTITY_TYPES = { ENTITY_TYPES = {

View file

@ -32,30 +32,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class HomeKitSwitch(HomeKitEntity, SwitchDevice): class HomeKitSwitch(HomeKitEntity, SwitchDevice):
"""Representation of a Homekit switch.""" """Representation of a Homekit switch."""
def __init__(self, *args):
"""Initialise the switch."""
super().__init__(*args)
self._on = None
self._outlet_in_use = None
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
return [CharacteristicsTypes.ON, CharacteristicsTypes.OUTLET_IN_USE] return [CharacteristicsTypes.ON, CharacteristicsTypes.OUTLET_IN_USE]
def _update_on(self, value):
self._on = value
def _update_outlet_in_use(self, value):
self._outlet_in_use = value
@property @property
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if device is on."""
return self._on return self.service.value(CharacteristicsTypes.ON)
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn the specified switch on.""" """Turn the specified switch on."""
self._on = True
characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": True}] characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": True}]
await self._accessory.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@ -67,5 +54,6 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional state attributes."""
if self._outlet_in_use is not None: outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE)
return {OUTLET_IN_USE: self._outlet_in_use} if outlet_in_use is not None:
return {OUTLET_IN_USE: outlet_in_use}