diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 2089471f288..cf1cf28fc32 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -132,14 +132,6 @@ class HomeKitEntity(Entity): if CharacteristicPermissions.events in char.perms: self.watchable_characteristics.append((self._aid, char.iid)) - # 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) - setup_fn = getattr(self, f"_setup_{setup_fn_name}", None) - if not setup_fn: - return - setup_fn(char.to_accessory_and_service_list()) - @property def unique_id(self) -> str: """Return the ID of this device.""" diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 133c100b125..2262fa54770 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -1,7 +1,12 @@ """Support for Homekit climate devices.""" import logging -from aiohomekit.model.characteristics import CharacteristicsTypes +from aiohomekit.model.characteristics import ( + CharacteristicsTypes, + HeatingCoolingCurrentValues, + HeatingCoolingTargetValues, +) +from aiohomekit.utils import clamp_enum_to_char from homeassistant.components.climate import ( DEFAULT_MAX_HUMIDITY, @@ -28,21 +33,19 @@ _LOGGER = logging.getLogger(__name__) # Map of Homekit operation modes to hass modes MODE_HOMEKIT_TO_HASS = { - 0: HVAC_MODE_OFF, - 1: HVAC_MODE_HEAT, - 2: HVAC_MODE_COOL, - 3: HVAC_MODE_HEAT_COOL, + HeatingCoolingTargetValues.OFF: HVAC_MODE_OFF, + HeatingCoolingTargetValues.HEAT: HVAC_MODE_HEAT, + HeatingCoolingTargetValues.COOL: HVAC_MODE_COOL, + HeatingCoolingTargetValues.AUTO: HVAC_MODE_HEAT_COOL, } # Map of hass operation modes to homekit modes MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()} -DEFAULT_VALID_MODES = list(MODE_HOMEKIT_TO_HASS) - CURRENT_MODE_HOMEKIT_TO_HASS = { - 0: CURRENT_HVAC_IDLE, - 1: CURRENT_HVAC_HEAT, - 2: CURRENT_HVAC_COOL, + HeatingCoolingCurrentValues.IDLE: CURRENT_HVAC_IDLE, + HeatingCoolingCurrentValues.HEATING: CURRENT_HVAC_HEAT, + HeatingCoolingCurrentValues.COOLING: CURRENT_HVAC_COOL, } @@ -65,15 +68,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): """Representation of a Homekit climate device.""" - def __init__(self, *args): - """Initialise the device.""" - self._valid_modes = [] - self._min_target_temp = None - self._max_target_temp = None - self._min_target_humidity = DEFAULT_MIN_HUMIDITY - self._max_target_humidity = DEFAULT_MAX_HUMIDITY - super().__init__(*args) - def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [ @@ -85,44 +79,6 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET, ] - def _setup_heating_cooling_target(self, characteristic): - if "valid-values" in characteristic: - valid_values = [ - val - for val in DEFAULT_VALID_MODES - if val in characteristic["valid-values"] - ] - else: - valid_values = DEFAULT_VALID_MODES - if "minValue" in characteristic: - valid_values = [ - val for val in valid_values if val >= characteristic["minValue"] - ] - if "maxValue" in characteristic: - valid_values = [ - val for val in valid_values if val <= characteristic["maxValue"] - ] - - self._valid_modes = [MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values] - - def _setup_temperature_target(self, characteristic): - self._features |= SUPPORT_TARGET_TEMPERATURE - - if "minValue" in characteristic: - self._min_target_temp = characteristic["minValue"] - - if "maxValue" in characteristic: - self._max_target_temp = characteristic["maxValue"] - - def _setup_relative_humidity_target(self, characteristic): - self._features |= SUPPORT_TARGET_HUMIDITY - - if "minValue" in characteristic: - self._min_target_humidity = characteristic["minValue"] - - if "maxValue" in characteristic: - self._max_target_humidity = characteristic["maxValue"] - async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) @@ -160,15 +116,17 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): @property def min_temp(self): """Return the minimum target temp.""" - if self._max_target_temp: - return self._min_target_temp + if self.service.has(CharacteristicsTypes.TEMPERATURE_TARGET): + char = self.service[CharacteristicsTypes.TEMPERATURE_TARGET] + return char.minValue return super().min_temp @property def max_temp(self): """Return the maximum target temp.""" - if self._max_target_temp: - return self._max_target_temp + if self.service.has(CharacteristicsTypes.TEMPERATURE_TARGET): + char = self.service[CharacteristicsTypes.TEMPERATURE_TARGET] + return char.maxValue return super().max_temp @property @@ -184,12 +142,14 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): @property def min_humidity(self): """Return the minimum humidity.""" - return self._min_target_humidity + char = self.service[CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET] + return char.minValue or DEFAULT_MIN_HUMIDITY @property def max_humidity(self): """Return the maximum humidity.""" - return self._max_target_humidity + char = self.service[CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET] + return char.maxValue or DEFAULT_MAX_HUMIDITY @property def hvac_action(self): @@ -213,12 +173,24 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): @property def hvac_modes(self): """Return the list of available hvac operation modes.""" - return self._valid_modes + valid_values = clamp_enum_to_char( + HeatingCoolingTargetValues, + self.service[CharacteristicsTypes.HEATING_COOLING_TARGET], + ) + return [MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values] @property def supported_features(self): """Return the list of supported features.""" - return self._features + features = 0 + + if self.service.has(CharacteristicsTypes.TEMPERATURE_TARGET): + features |= SUPPORT_TARGET_TEMPERATURE + + if self.service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET): + features |= SUPPORT_TARGET_HUMIDITY + + return features @property def temperature_unit(self): diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 9b73846d6a7..88885d49b8e 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -131,12 +131,6 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): class HomeKitWindowCover(HomeKitEntity, CoverDevice): """Representation of a HomeKit Window or Window Covering.""" - def __init__(self, accessory, discovery_info): - """Initialise the Cover.""" - super().__init__(accessory, discovery_info) - - self._features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [ @@ -151,23 +145,27 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): CharacteristicsTypes.OBSTRUCTION_DETECTED, ] - def _setup_position_hold(self, char): - self._features |= SUPPORT_STOP - - def _setup_vertical_tilt_current(self, char): - self._features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION - ) - - def _setup_horizontal_tilt_current(self, char): - self._features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION - ) - @property def supported_features(self): """Flag supported features.""" - return self._features + features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + + if self.service.has(CharacteristicsTypes.POSITION_HOLD): + features |= SUPPORT_STOP + + supports_tilt = any( + ( + self.service.has(CharacteristicsTypes.VERTICAL_TILT_CURRENT), + self.service.has(CharacteristicsTypes.HORIZONTAL_TILT_CURRENT), + ) + ) + + if supports_tilt: + features |= ( + SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION + ) + + return features @property def current_cover_position(self): diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index f0d6967684c..e3b392ea107 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -44,11 +44,6 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): # that controls whether the fan is on or off. on_characteristic = None - def __init__(self, *args): - """Initialise the fan.""" - self._features = 0 - super().__init__(*args) - def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [ @@ -58,15 +53,6 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): self.on_characteristic, ] - def _setup_rotation_direction(self, char): - self._features |= SUPPORT_DIRECTION - - def _setup_rotation_speed(self, char): - self._features |= SUPPORT_SET_SPEED - - def _setup_swing_mode(self, char): - self._features |= SUPPORT_OSCILLATE - @property def is_on(self): """Return true if device is on.""" @@ -113,7 +99,18 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): @property def supported_features(self): """Flag supported features.""" - return self._features + features = 0 + + if self.service.has(CharacteristicsTypes.ROTATION_DIRECTION): + features |= SUPPORT_DIRECTION + + if self.service.has(CharacteristicsTypes.ROTATION_SPEED): + features |= SUPPORT_SET_SPEED + + if self.service.has(CharacteristicsTypes.SWING_MODE): + features |= SUPPORT_OSCILLATE + + return features async def async_set_direction(self, direction): """Set the direction of the fan.""" diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 14ed74cc085..e78ed48ad0c 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -48,18 +48,6 @@ class HomeKitLight(HomeKitEntity, Light): CharacteristicsTypes.SATURATION, ] - def _setup_brightness(self, char): - self._features |= SUPPORT_BRIGHTNESS - - def _setup_color_temperature(self, char): - self._features |= SUPPORT_COLOR_TEMP - - def _setup_hue(self, char): - self._features |= SUPPORT_COLOR - - def _setup_saturation(self, char): - self._features |= SUPPORT_COLOR - @property def is_on(self): """Return true if device is on.""" @@ -86,7 +74,21 @@ class HomeKitLight(HomeKitEntity, Light): @property def supported_features(self): """Flag supported features.""" - return self._features + features = 0 + + if self.service.has(CharacteristicsTypes.BRIGHTNESS): + features |= SUPPORT_BRIGHTNESS + + if self.service.has(CharacteristicsTypes.COLOR_TEMPERATURE): + features |= SUPPORT_COLOR_TEMP + + if self.service.has(CharacteristicsTypes.HUE): + features |= SUPPORT_COLOR + + if self.service.has(CharacteristicsTypes.SATURATION): + features |= SUPPORT_COLOR + + return features async def async_turn_on(self, **kwargs): """Turn the specified light on.""" diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index a73d68227c7..8d669174c24 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit[IP]==0.2.29.1"], + "requirements": ["aiohomekit[IP]==0.2.34"], "dependencies": [], "zeroconf": ["_hap._tcp.local."], "codeowners": ["@Jc2k"] diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 3a1a7359e13..3d5e194ed94 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -57,14 +57,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): """Representation of a HomeKit Controller Television.""" - def __init__(self, accessory, discovery_info): - """Initialise the TV.""" - self._state = None - self._features = 0 - self._supported_target_media_state = set() - self._supported_remote_key = set() - super().__init__(accessory, discovery_info) - def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" return [ @@ -78,28 +70,6 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): CharacteristicsTypes.IDENTIFIER, ] - def _setup_active_identifier(self, char): - self._features |= SUPPORT_SELECT_SOURCE - - def _setup_target_media_state(self, char): - self._supported_target_media_state = clamp_enum_to_char( - TargetMediaStateValues, char - ) - - if TargetMediaStateValues.PAUSE in self._supported_target_media_state: - self._features |= SUPPORT_PAUSE - - if TargetMediaStateValues.PLAY in self._supported_target_media_state: - self._features |= SUPPORT_PLAY - - if TargetMediaStateValues.STOP in self._supported_target_media_state: - self._features |= SUPPORT_STOP - - def _setup_remote_key(self, char): - self._supported_remote_key = clamp_enum_to_char(RemoteKeyValues, char) - if RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key: - self._features |= SUPPORT_PAUSE | SUPPORT_PLAY - @property def device_class(self): """Define the device class for a HomeKit enabled TV.""" @@ -108,7 +78,47 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): @property def supported_features(self): """Flag media player features that are supported.""" - return self._features + features = 0 + + if self.service.has(CharacteristicsTypes.ACTIVE_IDENTIFIER): + features |= SUPPORT_SELECT_SOURCE + + if self.service.has(CharacteristicsTypes.TARGET_MEDIA_STATE): + if TargetMediaStateValues.PAUSE in self.supported_media_states: + features |= SUPPORT_PAUSE + + if TargetMediaStateValues.PLAY in self.supported_media_states: + features |= SUPPORT_PLAY + + if TargetMediaStateValues.STOP in self.supported_media_states: + features |= SUPPORT_STOP + + if self.service.has(CharacteristicsTypes.REMOTE_KEY): + if RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys: + features |= SUPPORT_PAUSE | SUPPORT_PLAY + + return features + + @property + def supported_media_states(self): + """Mediate state flags that are supported.""" + if not self.service.has(CharacteristicsTypes.TARGET_MEDIA_STATE): + return frozenset() + + return clamp_enum_to_char( + TargetMediaStateValues, + self.service[CharacteristicsTypes.TARGET_MEDIA_STATE], + ) + + @property + def supported_remote_keys(self): + """Remote key buttons that are supported.""" + if not self.service.has(CharacteristicsTypes.REMOTE_KEY): + return frozenset() + + return clamp_enum_to_char( + RemoteKeyValues, self.service[CharacteristicsTypes.REMOTE_KEY] + ) @property def source_list(self): @@ -164,11 +174,11 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): _LOGGER.debug("Cannot play while already playing") return - if TargetMediaStateValues.PLAY in self._supported_target_media_state: + if TargetMediaStateValues.PLAY in self.supported_media_states: await self.async_put_characteristics( {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PLAY} ) - elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key: + elif RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys: await self.async_put_characteristics( {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} ) @@ -179,11 +189,11 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): _LOGGER.debug("Cannot pause while already paused") return - if TargetMediaStateValues.PAUSE in self._supported_target_media_state: + if TargetMediaStateValues.PAUSE in self.supported_media_states: await self.async_put_characteristics( {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PAUSE} ) - elif RemoteKeyValues.PLAY_PAUSE in self._supported_remote_key: + elif RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys: await self.async_put_characteristics( {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE} ) @@ -194,7 +204,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): _LOGGER.debug("Cannot stop when already idle") return - if TargetMediaStateValues.STOP in self._supported_target_media_state: + if TargetMediaStateValues.STOP in self.supported_media_states: await self.async_put_characteristics( {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.STOP} ) diff --git a/requirements_all.txt b/requirements_all.txt index 962e5d2bc45..235558575e1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -163,7 +163,7 @@ aioftp==0.12.0 aioharmony==0.1.13 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.29.1 +aiohomekit[IP]==0.2.34 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 297f3d8f173..e71929dadbd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -65,7 +65,7 @@ aioesphomeapi==2.6.1 aiofreepybox==0.0.8 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.29.1 +aiohomekit[IP]==0.2.34 # homeassistant.components.emulated_hue # homeassistant.components.http