Bump python-roborock to 0.36.0 (#103465)

This commit is contained in:
Luke Lashley 2023-11-08 06:48:05 -05:00 committed by GitHub
parent a78ef60773
commit 44fe704f49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 77 additions and 43 deletions

View file

@ -26,7 +26,7 @@ from .device import RoborockCoordinatedEntity
class RoborockBinarySensorDescriptionMixin: class RoborockBinarySensorDescriptionMixin:
"""A class that describes binary sensor entities.""" """A class that describes binary sensor entities."""
value_fn: Callable[[DeviceProp], bool] value_fn: Callable[[DeviceProp], bool | int | None]
@dataclass @dataclass

View file

@ -33,7 +33,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
device: HomeDataDevice, device: HomeDataDevice,
device_networking: NetworkInfo, device_networking: NetworkInfo,
product_info: HomeDataProduct, product_info: HomeDataProduct,
cloud_api: RoborockMqttClient | None = None, cloud_api: RoborockMqttClient,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
@ -44,7 +44,9 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
DeviceProp(), DeviceProp(),
) )
device_data = DeviceData(device, product_info.model, device_networking.ip) device_data = DeviceData(device, product_info.model, device_networking.ip)
self.api = RoborockLocalClient(device_data) self.api: RoborockLocalClient | RoborockMqttClient = RoborockLocalClient(
device_data
)
self.cloud_api = cloud_api self.cloud_api = cloud_api
self.device_info = DeviceInfo( self.device_info = DeviceInfo(
name=self.roborock_device_info.device.name, name=self.roborock_device_info.device.name,
@ -59,10 +61,10 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
async def verify_api(self) -> None: async def verify_api(self) -> None:
"""Verify that the api is reachable. If it is not, switch clients.""" """Verify that the api is reachable. If it is not, switch clients."""
if isinstance(self.api, RoborockLocalClient):
try: try:
await self.api.ping() await self.api.ping()
except RoborockException: except RoborockException:
if isinstance(self.api, RoborockLocalClient):
_LOGGER.warning( _LOGGER.warning(
"Using the cloud API for device %s. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance", "Using the cloud API for device %s. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance",
self.roborock_device_info.device.duid, self.roborock_device_info.device.duid,

View file

@ -36,7 +36,7 @@ class RoborockEntity(Entity):
def get_cache(self, attribute: CacheableAttribute) -> AttributeCache: def get_cache(self, attribute: CacheableAttribute) -> AttributeCache:
"""Get an item from the api cache.""" """Get an item from the api cache."""
return self._api.cache.get(attribute) return self._api.cache[attribute]
async def send( async def send(
self, self,
@ -45,7 +45,7 @@ class RoborockEntity(Entity):
) -> dict: ) -> dict:
"""Send a command to a vacuum cleaner.""" """Send a command to a vacuum cleaner."""
try: try:
response = await self._api.send_command(command, params) response: dict = await self._api.send_command(command, params)
except RoborockException as err: except RoborockException as err:
raise HomeAssistantError( raise HomeAssistantError(
f"Error while calling {command.name if isinstance(command, RoborockCommand) else command} with {params}" f"Error while calling {command.name if isinstance(command, RoborockCommand) else command} with {params}"
@ -80,15 +80,11 @@ class RoborockCoordinatedEntity(
def _device_status(self) -> Status: def _device_status(self) -> Status:
"""Return the status of the device.""" """Return the status of the device."""
data = self.coordinator.data data = self.coordinator.data
if data: return data.status
status = data.status
if status:
return status
return Status({})
async def send( async def send(
self, self,
command: RoborockCommand, command: RoborockCommand | str,
params: dict[str, Any] | list[Any] | int | None = None, params: dict[str, Any] | list[Any] | int | None = None,
) -> dict: ) -> dict:
"""Overloads normal send command but refreshes coordinator.""" """Overloads normal send command but refreshes coordinator."""

View file

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/roborock", "documentation": "https://www.home-assistant.io/integrations/roborock",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["roborock"], "loggers": ["roborock"],
"requirements": ["python-roborock==0.35.0"] "requirements": ["python-roborock==0.36.0"]
} }

View file

@ -74,7 +74,7 @@ async def async_setup_entry(
# We need to check if this function is supported by the device. # We need to check if this function is supported by the device.
results = await asyncio.gather( results = await asyncio.gather(
*( *(
coordinator.api.cache.get(description.cache_key).async_value() coordinator.api.get_from_cache(description.cache_key)
for coordinator, description in possible_entities for coordinator, description in possible_entities
), ),
return_exceptions=True, return_exceptions=True,

View file

@ -24,9 +24,9 @@ class RoborockSelectDescriptionMixin:
# The command that the select entity will send to the api. # The command that the select entity will send to the api.
api_command: RoborockCommand api_command: RoborockCommand
# Gets the current value of the select entity. # Gets the current value of the select entity.
value_fn: Callable[[Status], str] value_fn: Callable[[Status], str | None]
# Gets all options of the select entity. # Gets all options of the select entity.
options_lambda: Callable[[Status], list[str]] options_lambda: Callable[[Status], list[str] | None]
# Takes the value from the select entiy and converts it for the api. # Takes the value from the select entiy and converts it for the api.
parameter_lambda: Callable[[str, Status], list[int]] parameter_lambda: Callable[[str, Status], list[int]]
@ -43,21 +43,23 @@ SELECT_DESCRIPTIONS: list[RoborockSelectDescription] = [
key="water_box_mode", key="water_box_mode",
translation_key="mop_intensity", translation_key="mop_intensity",
api_command=RoborockCommand.SET_WATER_BOX_CUSTOM_MODE, api_command=RoborockCommand.SET_WATER_BOX_CUSTOM_MODE,
value_fn=lambda data: data.water_box_mode.name, value_fn=lambda data: data.water_box_mode_name,
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
options_lambda=lambda data: data.water_box_mode.keys() options_lambda=lambda data: data.water_box_mode.keys()
if data.water_box_mode if data.water_box_mode is not None
else None, else None,
parameter_lambda=lambda key, status: [status.water_box_mode.as_dict().get(key)], parameter_lambda=lambda key, status: [status.get_mop_intensity_code(key)],
), ),
RoborockSelectDescription( RoborockSelectDescription(
key="mop_mode", key="mop_mode",
translation_key="mop_mode", translation_key="mop_mode",
api_command=RoborockCommand.SET_MOP_MODE, api_command=RoborockCommand.SET_MOP_MODE,
value_fn=lambda data: data.mop_mode.name, value_fn=lambda data: data.mop_mode_name,
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
options_lambda=lambda data: data.mop_mode.keys() if data.mop_mode else None, options_lambda=lambda data: data.mop_mode.keys()
parameter_lambda=lambda key, status: [status.mop_mode.as_dict().get(key)], if data.mop_mode is not None
else None,
parameter_lambda=lambda key, status: [status.get_mop_mode_code(key)],
), ),
] ]
@ -74,13 +76,15 @@ async def async_setup_entry(
] ]
async_add_entities( async_add_entities(
RoborockSelectEntity( RoborockSelectEntity(
f"{description.key}_{slugify(device_id)}", f"{description.key}_{slugify(device_id)}", coordinator, description, options
coordinator,
description,
) )
for device_id, coordinator in coordinators.items() for device_id, coordinator in coordinators.items()
for description in SELECT_DESCRIPTIONS for description in SELECT_DESCRIPTIONS
if description.options_lambda(coordinator.roborock_device_info.props.status) if (
options := description.options_lambda(
coordinator.roborock_device_info.props.status
)
)
is not None is not None
) )
@ -95,11 +99,12 @@ class RoborockSelectEntity(RoborockCoordinatedEntity, SelectEntity):
unique_id: str, unique_id: str,
coordinator: RoborockDataUpdateCoordinator, coordinator: RoborockDataUpdateCoordinator,
entity_description: RoborockSelectDescription, entity_description: RoborockSelectDescription,
options: list[str],
) -> None: ) -> None:
"""Create a select entity.""" """Create a select entity."""
self.entity_description = entity_description self.entity_description = entity_description
super().__init__(unique_id, coordinator) super().__init__(unique_id, coordinator)
self._attr_options = self.entity_description.options_lambda(self._device_status) self._attr_options = options
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Set the option.""" """Set the option."""

View file

@ -117,7 +117,7 @@ SENSOR_DESCRIPTIONS = [
icon="mdi:information-outline", icon="mdi:information-outline",
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
translation_key="status", translation_key="status",
value_fn=lambda data: data.status.state.name, value_fn=lambda data: data.status.state_name,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
options=RoborockStateCode.keys(), options=RoborockStateCode.keys(),
), ),
@ -142,7 +142,7 @@ SENSOR_DESCRIPTIONS = [
icon="mdi:alert-circle", icon="mdi:alert-circle",
translation_key="vacuum_error", translation_key="vacuum_error",
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
value_fn=lambda data: data.status.error_code.name, value_fn=lambda data: data.status.error_code_name,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
options=RoborockErrorCode.keys(), options=RoborockErrorCode.keys(),
), ),
@ -157,7 +157,9 @@ SENSOR_DESCRIPTIONS = [
key="last_clean_start", key="last_clean_start",
translation_key="last_clean_start", translation_key="last_clean_start",
icon="mdi:clock-time-twelve", icon="mdi:clock-time-twelve",
value_fn=lambda data: data.last_clean_record.begin_datetime, value_fn=lambda data: data.last_clean_record.begin_datetime
if data.last_clean_record is not None
else None,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
), ),
@ -165,7 +167,9 @@ SENSOR_DESCRIPTIONS = [
key="last_clean_end", key="last_clean_end",
translation_key="last_clean_end", translation_key="last_clean_end",
icon="mdi:clock-time-twelve", icon="mdi:clock-time-twelve",
value_fn=lambda data: data.last_clean_record.end_datetime, value_fn=lambda data: data.last_clean_record.end_datetime
if data.last_clean_record is not None
else None,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
), ),

View file

@ -125,7 +125,7 @@ async def async_setup_entry(
# We need to check if this function is supported by the device. # We need to check if this function is supported by the device.
results = await asyncio.gather( results = await asyncio.gather(
*( *(
coordinator.api.cache.get(description.cache_key).async_value() coordinator.api.get_from_cache(description.cache_key)
for coordinator, description in possible_entities for coordinator, description in possible_entities
), ),
return_exceptions=True, return_exceptions=True,

View file

@ -139,7 +139,7 @@ async def async_setup_entry(
# We need to check if this function is supported by the device. # We need to check if this function is supported by the device.
results = await asyncio.gather( results = await asyncio.gather(
*( *(
coordinator.api.cache.get(description.cache_key).async_value() coordinator.api.get_from_cache(description.cache_key)
for coordinator, description in possible_entities for coordinator, description in possible_entities
), ),
return_exceptions=True, return_exceptions=True,

View file

@ -93,11 +93,12 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity):
"""Initialize a vacuum.""" """Initialize a vacuum."""
StateVacuumEntity.__init__(self) StateVacuumEntity.__init__(self)
RoborockCoordinatedEntity.__init__(self, unique_id, coordinator) RoborockCoordinatedEntity.__init__(self, unique_id, coordinator)
self._attr_fan_speed_list = self._device_status.fan_power.keys() self._attr_fan_speed_list = self._device_status.fan_power_options
@property @property
def state(self) -> str | None: def state(self) -> str | None:
"""Return the status of the vacuum cleaner.""" """Return the status of the vacuum cleaner."""
assert self._device_status.state is not None
return STATE_CODE_TO_STATE.get(self._device_status.state) return STATE_CODE_TO_STATE.get(self._device_status.state)
@property @property
@ -108,7 +109,7 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity):
@property @property
def fan_speed(self) -> str | None: def fan_speed(self) -> str | None:
"""Return the fan speed of the vacuum cleaner.""" """Return the fan speed of the vacuum cleaner."""
return self._device_status.fan_power.name return self._device_status.fan_power_name
async def async_start(self) -> None: async def async_start(self) -> None:
"""Start the vacuum.""" """Start the vacuum."""
@ -138,7 +139,7 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity):
"""Set vacuum fan speed.""" """Set vacuum fan speed."""
await self.send( await self.send(
RoborockCommand.SET_CUSTOM_MODE, RoborockCommand.SET_CUSTOM_MODE,
[self._device_status.fan_power.as_dict().get(fan_speed)], [self._device_status.get_fan_speed_code(fan_speed)],
) )
async def async_start_pause(self) -> None: async def async_start_pause(self) -> None:

View file

@ -2184,7 +2184,7 @@ python-qbittorrent==0.4.3
python-ripple-api==0.0.3 python-ripple-api==0.0.3
# homeassistant.components.roborock # homeassistant.components.roborock
python-roborock==0.35.0 python-roborock==0.36.0
# homeassistant.components.smarttub # homeassistant.components.smarttub
python-smarttub==0.0.35 python-smarttub==0.0.35

View file

@ -1628,7 +1628,7 @@ python-picnic-api==1.1.0
python-qbittorrent==0.4.3 python-qbittorrent==0.4.3
# homeassistant.components.roborock # homeassistant.components.roborock
python-roborock==0.35.0 python-roborock==0.36.0
# homeassistant.components.smarttub # homeassistant.components.smarttub
python-smarttub==0.0.35 python-smarttub==0.0.35

View file

@ -260,7 +260,17 @@
'dockType': 3, 'dockType': 3,
'dustCollectionStatus': 0, 'dustCollectionStatus': 0,
'errorCode': 0, 'errorCode': 0,
'errorCodeName': 'none',
'fanPower': 102, 'fanPower': 102,
'fanPowerName': 'balanced',
'fanPowerOptions': list([
'off',
'quiet',
'balanced',
'turbo',
'max',
'custom',
]),
'homeSecEnablePassword': 0, 'homeSecEnablePassword': 0,
'homeSecStatus': 0, 'homeSecStatus': 0,
'inCleaning': 0, 'inCleaning': 0,
@ -274,10 +284,12 @@
'mapStatus': 3, 'mapStatus': 3,
'mopForbiddenEnable': 1, 'mopForbiddenEnable': 1,
'mopMode': 300, 'mopMode': 300,
'mopModeName': 'standard',
'msgSeq': 458, 'msgSeq': 458,
'msgVer': 2, 'msgVer': 2,
'squareMeterCleanArea': 21.0, 'squareMeterCleanArea': 21.0,
'state': 8, 'state': 8,
'stateName': 'charging',
'switchMapMode': 0, 'switchMapMode': 0,
'unsaveMapFlag': 0, 'unsaveMapFlag': 0,
'unsaveMapReason': 0, 'unsaveMapReason': 0,
@ -285,6 +297,7 @@
'washReady': 0, 'washReady': 0,
'waterBoxCarriageStatus': 1, 'waterBoxCarriageStatus': 1,
'waterBoxMode': 203, 'waterBoxMode': 203,
'waterBoxModeName': 'intense',
'waterBoxStatus': 1, 'waterBoxStatus': 1,
'waterShortageStatus': 0, 'waterShortageStatus': 0,
}), }),
@ -521,7 +534,17 @@
'dockType': 3, 'dockType': 3,
'dustCollectionStatus': 0, 'dustCollectionStatus': 0,
'errorCode': 0, 'errorCode': 0,
'errorCodeName': 'none',
'fanPower': 102, 'fanPower': 102,
'fanPowerName': 'balanced',
'fanPowerOptions': list([
'off',
'quiet',
'balanced',
'turbo',
'max',
'custom',
]),
'homeSecEnablePassword': 0, 'homeSecEnablePassword': 0,
'homeSecStatus': 0, 'homeSecStatus': 0,
'inCleaning': 0, 'inCleaning': 0,
@ -535,10 +558,12 @@
'mapStatus': 3, 'mapStatus': 3,
'mopForbiddenEnable': 1, 'mopForbiddenEnable': 1,
'mopMode': 300, 'mopMode': 300,
'mopModeName': 'standard',
'msgSeq': 458, 'msgSeq': 458,
'msgVer': 2, 'msgVer': 2,
'squareMeterCleanArea': 21.0, 'squareMeterCleanArea': 21.0,
'state': 8, 'state': 8,
'stateName': 'charging',
'switchMapMode': 0, 'switchMapMode': 0,
'unsaveMapFlag': 0, 'unsaveMapFlag': 0,
'unsaveMapReason': 0, 'unsaveMapReason': 0,
@ -546,6 +571,7 @@
'washReady': 0, 'washReady': 0,
'waterBoxCarriageStatus': 1, 'waterBoxCarriageStatus': 1,
'waterBoxMode': 203, 'waterBoxMode': 203,
'waterBoxModeName': 'intense',
'waterBoxStatus': 1, 'waterBoxStatus': 1,
'waterShortageStatus': 0, 'waterShortageStatus': 0,
}), }),