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:
parent
4248893007
commit
647d137daa
12 changed files with 159 additions and 358 deletions
|
@ -4,10 +4,9 @@ import os
|
|||
|
||||
import aiohomekit
|
||||
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 homeassistant.core import callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
@ -60,7 +59,7 @@ class HomeKitEntity(Entity):
|
|||
"""Entity added to hass."""
|
||||
self._signals.append(
|
||||
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):
|
||||
"""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.watchable_characteristics = []
|
||||
self._chars = {}
|
||||
self._char_names = {}
|
||||
|
||||
char_types = self.get_characteristic_types()
|
||||
|
||||
# Setup events and/or polling for characteristics directly attached to this entity
|
||||
for char in self.service.characteristics:
|
||||
if char.type not in characteristic_types:
|
||||
continue
|
||||
self._setup_characteristic(char.to_accessory_and_service_list())
|
||||
for char in self.service.characteristics.filter(char_types=char_types):
|
||||
self._setup_characteristic(char)
|
||||
|
||||
# Setup events and/or polling for characteristics attached to sub-services of this
|
||||
# entity (like an INPUT_SOURCE).
|
||||
for service in self.accessory.services.filter(parent_service=self.service):
|
||||
for char in service.characteristics:
|
||||
if char.type not in characteristic_types:
|
||||
continue
|
||||
self._setup_characteristic(char.to_accessory_and_service_list())
|
||||
for char in service.characteristics.filter(char_types=char_types):
|
||||
self._setup_characteristic(char)
|
||||
|
||||
def _setup_characteristic(self, char):
|
||||
def _setup_characteristic(self, char: Characteristic):
|
||||
"""Configure an entity based on a HomeKit characteristics metadata."""
|
||||
# Build up a list of (aid, iid) tuples to poll on update()
|
||||
if "pr" in char["perms"]:
|
||||
self.pollable_characteristics.append((self._aid, char["iid"]))
|
||||
if "pr" in char.perms:
|
||||
self.pollable_characteristics.append((self._aid, char.iid))
|
||||
|
||||
# Build up a list of (aid, iid) tuples to subscribe to
|
||||
if "ev" in char["perms"]:
|
||||
self.watchable_characteristics.append((self._aid, char["iid"]))
|
||||
if "ev" in char.perms:
|
||||
self.watchable_characteristics.append((self._aid, char.iid))
|
||||
|
||||
# Build a map of ctype -> iid
|
||||
short_name = CharacteristicsTypes.get_short(char["type"])
|
||||
self._chars[short_name] = char["iid"]
|
||||
self._char_names[char["iid"]] = short_name
|
||||
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(short_name)
|
||||
setup_fn_name = escape_characteristic_name(char.type_name)
|
||||
setup_fn = getattr(self, f"_setup_{setup_fn_name}", None)
|
||||
if not setup_fn:
|
||||
return
|
||||
setup_fn(char)
|
||||
|
||||
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()
|
||||
setup_fn(char.to_accessory_and_service_list())
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
def unique_id(self) -> str:
|
||||
"""Return the ID of this device."""
|
||||
serial = self.accessory_info.value(CharacteristicsTypes.SERIAL_NUMBER)
|
||||
return f"homekit-{serial}-{self._iid}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the device if any."""
|
||||
return self.accessory_info.value(CharacteristicsTypes.NAME)
|
||||
|
||||
|
|
|
@ -34,38 +34,38 @@ class HomeAirQualitySensor(HomeKitEntity, AirQualityEntity):
|
|||
@property
|
||||
def particulate_matter_2_5(self):
|
||||
"""Return the particulate matter 2.5 level."""
|
||||
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_PM25)
|
||||
return self.service.value(CharacteristicsTypes.DENSITY_PM25)
|
||||
|
||||
@property
|
||||
def particulate_matter_10(self):
|
||||
"""Return the particulate matter 10 level."""
|
||||
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_PM10)
|
||||
return self.service.value(CharacteristicsTypes.DENSITY_PM10)
|
||||
|
||||
@property
|
||||
def ozone(self):
|
||||
"""Return the O3 (ozone) level."""
|
||||
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_OZONE)
|
||||
return self.service.value(CharacteristicsTypes.DENSITY_OZONE)
|
||||
|
||||
@property
|
||||
def sulphur_dioxide(self):
|
||||
"""Return the SO2 (sulphur dioxide) level."""
|
||||
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_SO2)
|
||||
return self.service.value(CharacteristicsTypes.DENSITY_SO2)
|
||||
|
||||
@property
|
||||
def nitrogen_dioxide(self):
|
||||
"""Return the NO2 (nitrogen dioxide) level."""
|
||||
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_NO2)
|
||||
return self.service.value(CharacteristicsTypes.DENSITY_NO2)
|
||||
|
||||
@property
|
||||
def air_quality_text(self):
|
||||
"""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")
|
||||
|
||||
@property
|
||||
def volatile_organic_compounds(self):
|
||||
"""Return the volatile organic compounds (VOC) level."""
|
||||
return self.get_hk_char_value(CharacteristicsTypes.DENSITY_VOC)
|
||||
return self.service.value(CharacteristicsTypes.DENSITY_VOC)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
|
|
@ -60,12 +60,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
|
||||
"""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):
|
||||
"""Define the homekit characteristics the entity cares about."""
|
||||
return [
|
||||
|
@ -74,12 +68,6 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
|
|||
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
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
|
@ -88,7 +76,9 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
|
|||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
return CURRENT_STATE_MAP[
|
||||
self.service.value(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT)
|
||||
]
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
|
@ -125,7 +115,10 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
if self._battery_level is None:
|
||||
return None
|
||||
attributes = {}
|
||||
|
||||
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
|
||||
|
|
|
@ -20,18 +20,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||
class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice):
|
||||
"""Representation of a Homekit motion sensor."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the entity."""
|
||||
super().__init__(*args)
|
||||
self._on = False
|
||||
|
||||
def get_characteristic_types(self):
|
||||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.MOTION_DETECTED]
|
||||
|
||||
def _update_motion_detected(self, value):
|
||||
self._on = value
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Define this binary_sensor as a motion sensor."""
|
||||
|
@ -40,24 +32,16 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice):
|
|||
@property
|
||||
def is_on(self):
|
||||
"""Has motion been detected."""
|
||||
return self._on
|
||||
return self.service.value(CharacteristicsTypes.MOTION_DETECTED)
|
||||
|
||||
|
||||
class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice):
|
||||
"""Representation of a Homekit contact sensor."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the entity."""
|
||||
super().__init__(*args)
|
||||
self._state = None
|
||||
|
||||
def get_characteristic_types(self):
|
||||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.CONTACT_STATE]
|
||||
|
||||
def _update_contact_state(self, value):
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Define this binary_sensor as a opening sensor."""
|
||||
|
@ -66,17 +50,12 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice):
|
|||
@property
|
||||
def is_on(self):
|
||||
"""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):
|
||||
"""Representation of a Homekit smoke sensor."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the entity."""
|
||||
super().__init__(*args)
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
"""Return the class of this sensor."""
|
||||
|
@ -86,22 +65,14 @@ class HomeKitSmokeSensor(HomeKitEntity, BinarySensorDevice):
|
|||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.SMOKE_DETECTED]
|
||||
|
||||
def _update_smoke_detected(self, value):
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if smoke is currently detected."""
|
||||
return self._state == 1
|
||||
return self.service.value(CharacteristicsTypes.SMOKE_DETECTED) == 1
|
||||
|
||||
|
||||
class HomeKitOccupancySensor(HomeKitEntity, BinarySensorDevice):
|
||||
"""Representation of a Homekit smoke sensor."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the entity."""
|
||||
super().__init__(*args)
|
||||
self._state = None
|
||||
"""Representation of a Homekit occupancy sensor."""
|
||||
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
|
@ -112,13 +83,10 @@ class HomeKitOccupancySensor(HomeKitEntity, BinarySensorDevice):
|
|||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.OCCUPANCY_DETECTED]
|
||||
|
||||
def _update_occupancy_detected(self, value):
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if smoke is currently detected."""
|
||||
return self._state == 1
|
||||
"""Return true if occupancy is currently detected."""
|
||||
return self.service.value(CharacteristicsTypes.OCCUPANCY_DETECTED) == 1
|
||||
|
||||
|
||||
ENTITY_TYPES = {
|
||||
|
|
|
@ -67,14 +67,7 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the device."""
|
||||
self._state = None
|
||||
self._target_mode = None
|
||||
self._current_mode = None
|
||||
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._max_target_temp = None
|
||||
self._min_target_humidity = DEFAULT_MIN_HUMIDITY
|
||||
|
@ -130,31 +123,6 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
if "maxValue" in characteristic:
|
||||
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):
|
||||
"""Set new target temperature."""
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
|
@ -189,12 +157,12 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temp
|
||||
return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temp
|
||||
return self.service.value(CharacteristicsTypes.TEMPERATURE_TARGET)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
|
@ -213,12 +181,12 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
@property
|
||||
def current_humidity(self):
|
||||
"""Return the current humidity."""
|
||||
return self._current_humidity
|
||||
return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
"""Return the humidity we try to reach."""
|
||||
return self._target_humidity
|
||||
return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET)
|
||||
|
||||
@property
|
||||
def min_humidity(self):
|
||||
|
@ -233,12 +201,21 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
|
|||
@property
|
||||
def hvac_action(self):
|
||||
"""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
|
||||
def hvac_mode(self):
|
||||
"""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
|
||||
def hvac_modes(self):
|
||||
|
|
|
@ -61,13 +61,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
|
||||
"""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
|
||||
def device_class(self):
|
||||
"""Define this cover as a garage door."""
|
||||
|
@ -81,31 +74,31 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
|
|||
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
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
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
|
||||
def is_closed(self):
|
||||
"""Return true if cover is closed, else False."""
|
||||
return self._state == STATE_CLOSED
|
||||
return self.state == STATE_CLOSED
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
"""Return if the cover is closing or not."""
|
||||
return self._state == STATE_CLOSING
|
||||
return self.state == STATE_CLOSING
|
||||
|
||||
@property
|
||||
def is_opening(self):
|
||||
"""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):
|
||||
"""Send open command."""
|
||||
|
@ -129,10 +122,15 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
if self._obstruction_detected is None:
|
||||
return None
|
||||
attributes = {}
|
||||
|
||||
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):
|
||||
|
@ -141,11 +139,7 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
|
|||
def __init__(self, accessory, discovery_info):
|
||||
"""Initialise the Cover."""
|
||||
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
|
||||
|
||||
def get_characteristic_types(self):
|
||||
|
@ -175,21 +169,6 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
|
|||
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
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
|
@ -198,22 +177,36 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
|
|||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current position of cover."""
|
||||
return self._position
|
||||
return self.service.value(CharacteristicsTypes.POSITION_CURRENT)
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return true if cover is closed, else False."""
|
||||
return self._position == 0
|
||||
return self.current_cover_position == 0
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
"""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
|
||||
def is_opening(self):
|
||||
"""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):
|
||||
"""Send hold command."""
|
||||
|
@ -238,11 +231,6 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
|
|||
]
|
||||
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):
|
||||
"""Move the cover tilt to a specific position."""
|
||||
tilt_position = kwargs[ATTR_TILT_POSITION]
|
||||
|
@ -268,8 +256,12 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
state_attributes = {}
|
||||
if self._obstruction_detected is not None:
|
||||
state_attributes["obstruction-detected"] = self._obstruction_detected
|
||||
attributes = {}
|
||||
|
||||
return state_attributes
|
||||
obstruction_detected = self.service.value(
|
||||
CharacteristicsTypes.OBSTRUCTION_DETECTED
|
||||
)
|
||||
if obstruction_detected:
|
||||
attributes["obstruction-detected"] = obstruction_detected
|
||||
|
||||
return attributes
|
||||
|
|
|
@ -46,12 +46,7 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the fan."""
|
||||
self._on = None
|
||||
self._features = 0
|
||||
self._rotation_direction = 0
|
||||
self._rotation_speed = 0
|
||||
self._swing_mode = 0
|
||||
|
||||
super().__init__(*args)
|
||||
|
||||
def get_characteristic_types(self):
|
||||
|
@ -71,31 +66,23 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||
def _setup_swing_mode(self, char):
|
||||
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
|
||||
def speed(self):
|
||||
"""Return the current speed."""
|
||||
if not self.is_on:
|
||||
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
|
||||
if self._rotation_speed > SPEED_TO_PCNT[SPEED_LOW]:
|
||||
|
||||
if rotation_speed > SPEED_TO_PCNT[SPEED_LOW]:
|
||||
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_OFF
|
||||
|
||||
@property
|
||||
|
@ -108,12 +95,14 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity):
|
|||
@property
|
||||
def current_direction(self):
|
||||
"""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
|
||||
def oscillating(self):
|
||||
"""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
|
||||
def supported_features(self):
|
||||
|
@ -208,8 +197,10 @@ class HomeKitFanV1(BaseHomeKitFan):
|
|||
"""Define the homekit characteristics the entity cares about."""
|
||||
return [CharacteristicsTypes.ON] + super().get_characteristic_types()
|
||||
|
||||
def _update_on(self, value):
|
||||
self._on = value == 1
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self.service.value(CharacteristicsTypes.ON) == 1
|
||||
|
||||
|
||||
class HomeKitFanV2(BaseHomeKitFan):
|
||||
|
@ -221,8 +212,10 @@ class HomeKitFanV2(BaseHomeKitFan):
|
|||
"""Define the homekit characteristics the entity cares about."""
|
||||
return [CharacteristicsTypes.ACTIVE] + super().get_characteristic_types()
|
||||
|
||||
def _update_active(self, value):
|
||||
self._on = value == 1
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self.service.value(CharacteristicsTypes.ACTIVE) == 1
|
||||
|
||||
|
||||
ENTITY_TYPES = {
|
||||
|
|
|
@ -38,15 +38,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
class HomeKitLight(HomeKitEntity, 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):
|
||||
"""Define the homekit characteristics the entity cares about."""
|
||||
return [
|
||||
|
@ -69,40 +60,28 @@ class HomeKitLight(HomeKitEntity, Light):
|
|||
def _setup_saturation(self, char):
|
||||
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
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._on
|
||||
return self.service.value(CharacteristicsTypes.ON)
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness * 255 / 100
|
||||
return self.service.value(CharacteristicsTypes.BRIGHTNESS) * 255 / 100
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the color property."""
|
||||
return (self._hue, self._saturation)
|
||||
return (
|
||||
self.service.value(CharacteristicsTypes.HUE),
|
||||
self.service.value(CharacteristicsTypes.SATURATION),
|
||||
)
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the color temperature."""
|
||||
return self._color_temperature
|
||||
return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
|
|
@ -37,12 +37,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
class HomeKitLock(HomeKitEntity, LockDevice):
|
||||
"""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):
|
||||
"""Define the homekit characteristics the entity cares about."""
|
||||
return [
|
||||
|
@ -51,16 +45,11 @@ class HomeKitLock(HomeKitEntity, LockDevice):
|
|||
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
|
||||
def is_locked(self):
|
||||
"""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):
|
||||
"""Lock the device."""
|
||||
|
@ -84,7 +73,10 @@ class HomeKitLock(HomeKitEntity, LockDevice):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
if self._battery_level is None:
|
||||
return None
|
||||
attributes = {}
|
||||
|
||||
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
|
||||
|
|
|
@ -130,9 +130,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
|
|||
@property
|
||||
def source(self):
|
||||
"""Name of the current input source."""
|
||||
active_identifier = self.get_hk_char_value(
|
||||
CharacteristicsTypes.ACTIVE_IDENTIFIER
|
||||
)
|
||||
active_identifier = self.service.value(CharacteristicsTypes.ACTIVE_IDENTIFIER)
|
||||
if not active_identifier:
|
||||
return None
|
||||
|
||||
|
@ -150,11 +148,11 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice):
|
|||
@property
|
||||
def state(self):
|
||||
"""State of the tv."""
|
||||
active = self.get_hk_char_value(CharacteristicsTypes.ACTIVE)
|
||||
active = self.service.value(CharacteristicsTypes.ACTIVE)
|
||||
if not active:
|
||||
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:
|
||||
return HK_TO_HA_STATE[homekit_state]
|
||||
|
||||
|
|
|
@ -25,11 +25,6 @@ UNIT_LUX = "lux"
|
|||
class HomeKitHumiditySensor(HomeKitEntity):
|
||||
"""Representation of a Homekit humidity sensor."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the entity."""
|
||||
super().__init__(*args)
|
||||
self._state = None
|
||||
|
||||
def get_characteristic_types(self):
|
||||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT]
|
||||
|
@ -54,23 +49,15 @@ class HomeKitHumiditySensor(HomeKitEntity):
|
|||
"""Return units for the sensor."""
|
||||
return UNIT_PERCENTAGE
|
||||
|
||||
def _update_relative_humidity_current(self, value):
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current humidity."""
|
||||
return self._state
|
||||
return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)
|
||||
|
||||
|
||||
class HomeKitTemperatureSensor(HomeKitEntity):
|
||||
"""Representation of a Homekit temperature sensor."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the entity."""
|
||||
super().__init__(*args)
|
||||
self._state = None
|
||||
|
||||
def get_characteristic_types(self):
|
||||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.TEMPERATURE_CURRENT]
|
||||
|
@ -95,23 +82,15 @@ class HomeKitTemperatureSensor(HomeKitEntity):
|
|||
"""Return units for the sensor."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
def _update_temperature_current(self, value):
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current temperature in Celsius."""
|
||||
return self._state
|
||||
return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)
|
||||
|
||||
|
||||
class HomeKitLightSensor(HomeKitEntity):
|
||||
"""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):
|
||||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT]
|
||||
|
@ -136,23 +115,15 @@ class HomeKitLightSensor(HomeKitEntity):
|
|||
"""Return units for the sensor."""
|
||||
return UNIT_LUX
|
||||
|
||||
def _update_light_level_current(self, value):
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current light level in lux."""
|
||||
return self._state
|
||||
return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT)
|
||||
|
||||
|
||||
class HomeKitCarbonDioxideSensor(HomeKitEntity):
|
||||
"""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):
|
||||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL]
|
||||
|
@ -172,25 +143,15 @@ class HomeKitCarbonDioxideSensor(HomeKitEntity):
|
|||
"""Return units for the sensor."""
|
||||
return CONCENTRATION_PARTS_PER_MILLION
|
||||
|
||||
def _update_carbon_dioxide_level(self, value):
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current CO2 level in ppm."""
|
||||
return self._state
|
||||
return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL)
|
||||
|
||||
|
||||
class HomeKitBatterySensor(HomeKitEntity):
|
||||
"""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):
|
||||
"""Define the homekit characteristics the entity is tracking."""
|
||||
return [
|
||||
|
@ -218,12 +179,12 @@ class HomeKitBatterySensor(HomeKitEntity):
|
|||
# This is similar to the logic in helpers.icon, but we have delegated the
|
||||
# decision about what mdi:battery-alert is to the device.
|
||||
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
|
||||
icon += f"-charging-{percentage}"
|
||||
elif self._charging:
|
||||
elif self.is_charging:
|
||||
icon += "-outline"
|
||||
elif self._low_battery:
|
||||
elif self.is_low_battery:
|
||||
icon += "-alert"
|
||||
elif self.state < 95:
|
||||
percentage = max(int(round(self.state / 10 - 0.01)) * 10, 10)
|
||||
|
@ -236,22 +197,23 @@ class HomeKitBatterySensor(HomeKitEntity):
|
|||
"""Return units for the sensor."""
|
||||
return UNIT_PERCENTAGE
|
||||
|
||||
def _update_battery_level(self, value):
|
||||
self._state = value
|
||||
@property
|
||||
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):
|
||||
self._low_battery = value == 1
|
||||
|
||||
def _update_charging_state(self, value):
|
||||
@property
|
||||
def is_charging(self):
|
||||
"""Return true if currently charing."""
|
||||
# 0 = not charging
|
||||
# 1 = charging
|
||||
# 2 = not chargeable
|
||||
self._charging = value == 1
|
||||
return self.service.value(CharacteristicsTypes.CHARGING_STATE) == 1
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current battery level percentage."""
|
||||
return self._state
|
||||
return self.service.value(CharacteristicsTypes.BATTERY_LEVEL)
|
||||
|
||||
|
||||
ENTITY_TYPES = {
|
||||
|
|
|
@ -32,30 +32,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
class HomeKitSwitch(HomeKitEntity, SwitchDevice):
|
||||
"""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):
|
||||
"""Define the homekit characteristics the entity cares about."""
|
||||
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
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._on
|
||||
return self.service.value(CharacteristicsTypes.ON)
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the specified switch on."""
|
||||
self._on = True
|
||||
characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": True}]
|
||||
await self._accessory.put_characteristics(characteristics)
|
||||
|
||||
|
@ -67,5 +54,6 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
if self._outlet_in_use is not None:
|
||||
return {OUTLET_IN_USE: self._outlet_in_use}
|
||||
outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE)
|
||||
if outlet_in_use is not None:
|
||||
return {OUTLET_IN_USE: outlet_in_use}
|
||||
|
|
Loading…
Add table
Reference in a new issue