mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 00:27:19 +00:00
Move Roborock MapParser to coordinator (#140750)
Move MapParser to coordinator
This commit is contained in:
parent
784381a25f
commit
b0db7b432e
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from propcache.api import cached_property
|
from propcache.api import cached_property
|
||||||
@ -25,6 +26,10 @@ from roborock.version_1_apis.roborock_local_client_v1 import RoborockLocalClient
|
|||||||
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
|
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
|
||||||
from roborock.version_a01_apis import RoborockClientA01
|
from roborock.version_a01_apis import RoborockClientA01
|
||||||
from roborock.web_api import RoborockApiClient
|
from roborock.web_api import RoborockApiClient
|
||||||
|
from vacuum_map_parser_base.config.color import ColorsPalette
|
||||||
|
from vacuum_map_parser_base.config.image_config import ImageConfig
|
||||||
|
from vacuum_map_parser_base.config.size import Sizes
|
||||||
|
from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_CONNECTIONS
|
from homeassistant.const import ATTR_CONNECTIONS
|
||||||
@ -38,7 +43,11 @@ from homeassistant.util import slugify
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
A01_UPDATE_INTERVAL,
|
A01_UPDATE_INTERVAL,
|
||||||
|
DEFAULT_DRAWABLES,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
DRAWABLES,
|
||||||
|
MAP_FILE_FORMAT,
|
||||||
|
MAP_SCALE,
|
||||||
V1_CLOUD_IN_CLEANING_INTERVAL,
|
V1_CLOUD_IN_CLEANING_INTERVAL,
|
||||||
V1_CLOUD_NOT_CLEANING_INTERVAL,
|
V1_CLOUD_NOT_CLEANING_INTERVAL,
|
||||||
V1_LOCAL_IN_CLEANING_INTERVAL,
|
V1_LOCAL_IN_CLEANING_INTERVAL,
|
||||||
@ -127,6 +136,18 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
self._user_data = user_data
|
self._user_data = user_data
|
||||||
self._api_client = api_client
|
self._api_client = api_client
|
||||||
self._is_cloud_api = False
|
self._is_cloud_api = False
|
||||||
|
drawables = [
|
||||||
|
drawable
|
||||||
|
for drawable, default_value in DEFAULT_DRAWABLES.items()
|
||||||
|
if config_entry.options.get(DRAWABLES, {}).get(drawable, default_value)
|
||||||
|
]
|
||||||
|
self.map_parser = RoborockMapDataParser(
|
||||||
|
ColorsPalette(),
|
||||||
|
Sizes({k: v * MAP_SCALE for k, v in Sizes.SIZES.items()}),
|
||||||
|
drawables,
|
||||||
|
ImageConfig(scale=MAP_SCALE),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def dock_device_info(self) -> DeviceInfo:
|
def dock_device_info(self) -> DeviceInfo:
|
||||||
@ -145,6 +166,19 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
sw_version=self.roborock_device_info.device.fv,
|
sw_version=self.roborock_device_info.device.fv,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def parse_image(self, map_bytes: bytes) -> bytes | None:
|
||||||
|
"""Parse map_bytes and store it as image bytes."""
|
||||||
|
try:
|
||||||
|
parsed_map = self.map_parser.parse(map_bytes)
|
||||||
|
except (IndexError, ValueError) as err:
|
||||||
|
_LOGGER.debug("Exception when parsing map contents: %s", err)
|
||||||
|
return None
|
||||||
|
if parsed_map.image is None:
|
||||||
|
return None
|
||||||
|
img_byte_arr = io.BytesIO()
|
||||||
|
parsed_map.image.data.save(img_byte_arr, format=MAP_FILE_FORMAT)
|
||||||
|
return img_byte_arr.getvalue()
|
||||||
|
|
||||||
async def _async_setup(self) -> None:
|
async def _async_setup(self) -> None:
|
||||||
"""Set up the coordinator."""
|
"""Set up the coordinator."""
|
||||||
# Verify we can communicate locally - if we can't, switch to cloud api
|
# Verify we can communicate locally - if we can't, switch to cloud api
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
"""Support for Roborock image."""
|
"""Support for Roborock image."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import io
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from roborock import RoborockCommand
|
from roborock import RoborockCommand
|
||||||
from vacuum_map_parser_base.config.color import ColorsPalette
|
|
||||||
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
||||||
from vacuum_map_parser_base.config.size import Sizes
|
|
||||||
from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser
|
|
||||||
|
|
||||||
from homeassistant.components.image import ImageEntity
|
from homeassistant.components.image import ImageEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -20,15 +14,7 @@ from homeassistant.exceptions import HomeAssistantError
|
|||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import DOMAIN, IMAGE_CACHE_INTERVAL, MAP_SLEEP
|
||||||
DEFAULT_DRAWABLES,
|
|
||||||
DOMAIN,
|
|
||||||
DRAWABLES,
|
|
||||||
IMAGE_CACHE_INTERVAL,
|
|
||||||
MAP_FILE_FORMAT,
|
|
||||||
MAP_SCALE,
|
|
||||||
MAP_SLEEP,
|
|
||||||
)
|
|
||||||
from .coordinator import RoborockConfigEntry, RoborockDataUpdateCoordinator
|
from .coordinator import RoborockConfigEntry, RoborockDataUpdateCoordinator
|
||||||
from .entity import RoborockCoordinatedEntityV1
|
from .entity import RoborockCoordinatedEntityV1
|
||||||
|
|
||||||
@ -42,31 +28,6 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Roborock image platform."""
|
"""Set up Roborock image platform."""
|
||||||
|
|
||||||
drawables = [
|
|
||||||
drawable
|
|
||||||
for drawable, default_value in DEFAULT_DRAWABLES.items()
|
|
||||||
if config_entry.options.get(DRAWABLES, {}).get(drawable, default_value)
|
|
||||||
]
|
|
||||||
parser = RoborockMapDataParser(
|
|
||||||
ColorsPalette(),
|
|
||||||
Sizes({k: v * MAP_SCALE for k, v in Sizes.SIZES.items()}),
|
|
||||||
drawables,
|
|
||||||
ImageConfig(scale=MAP_SCALE),
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
def parse_image(map_bytes: bytes) -> bytes | None:
|
|
||||||
try:
|
|
||||||
parsed_map = parser.parse(map_bytes)
|
|
||||||
except (IndexError, ValueError) as err:
|
|
||||||
_LOGGER.debug("Exception when parsing map contents: %s", err)
|
|
||||||
return None
|
|
||||||
if parsed_map.image is None:
|
|
||||||
return None
|
|
||||||
img_byte_arr = io.BytesIO()
|
|
||||||
parsed_map.image.data.save(img_byte_arr, format=MAP_FILE_FORMAT)
|
|
||||||
return img_byte_arr.getvalue()
|
|
||||||
|
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*(refresh_coordinators(hass, coord) for coord in config_entry.runtime_data.v1)
|
*(refresh_coordinators(hass, coord) for coord in config_entry.runtime_data.v1)
|
||||||
)
|
)
|
||||||
@ -78,7 +39,6 @@ async def async_setup_entry(
|
|||||||
coord,
|
coord,
|
||||||
map_info.flag,
|
map_info.flag,
|
||||||
map_info.name,
|
map_info.name,
|
||||||
parse_image,
|
|
||||||
)
|
)
|
||||||
for coord in config_entry.runtime_data.v1
|
for coord in config_entry.runtime_data.v1
|
||||||
for map_info in coord.maps.values()
|
for map_info in coord.maps.values()
|
||||||
@ -100,14 +60,12 @@ class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity):
|
|||||||
coordinator: RoborockDataUpdateCoordinator,
|
coordinator: RoborockDataUpdateCoordinator,
|
||||||
map_flag: int,
|
map_flag: int,
|
||||||
map_name: str,
|
map_name: str,
|
||||||
parser: Callable[[bytes], bytes | None],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a Roborock map."""
|
"""Initialize a Roborock map."""
|
||||||
RoborockCoordinatedEntityV1.__init__(self, unique_id, coordinator)
|
RoborockCoordinatedEntityV1.__init__(self, unique_id, coordinator)
|
||||||
ImageEntity.__init__(self, coordinator.hass)
|
ImageEntity.__init__(self, coordinator.hass)
|
||||||
self.config_entry = config_entry
|
self.config_entry = config_entry
|
||||||
self._attr_name = map_name
|
self._attr_name = map_name
|
||||||
self.parser = parser
|
|
||||||
self.map_flag = map_flag
|
self.map_flag = map_flag
|
||||||
self.cached_map = b""
|
self.cached_map = b""
|
||||||
self._attr_entity_category = EntityCategory.DIAGNOSTIC
|
self._attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
@ -154,7 +112,7 @@ class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity):
|
|||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
not isinstance(response[0], bytes)
|
not isinstance(response[0], bytes)
|
||||||
or (content := self.parser(response[0])) is None
|
or (content := self.coordinator.parse_image(response[0])) is None
|
||||||
):
|
):
|
||||||
_LOGGER.debug("Failed to parse map contents: %s", response[0])
|
_LOGGER.debug("Failed to parse map contents: %s", response[0])
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
|
@ -6,6 +6,10 @@ from typing import Any
|
|||||||
from roborock.code_mappings import RoborockStateCode
|
from roborock.code_mappings import RoborockStateCode
|
||||||
from roborock.roborock_message import RoborockDataProtocol
|
from roborock.roborock_message import RoborockDataProtocol
|
||||||
from roborock.roborock_typing import RoborockCommand
|
from roborock.roborock_typing import RoborockCommand
|
||||||
|
from vacuum_map_parser_base.config.color import ColorsPalette
|
||||||
|
from vacuum_map_parser_base.config.image_config import ImageConfig
|
||||||
|
from vacuum_map_parser_base.config.size import Sizes
|
||||||
|
from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.vacuum import (
|
from homeassistant.components.vacuum import (
|
||||||
@ -26,7 +30,6 @@ from .const import (
|
|||||||
)
|
)
|
||||||
from .coordinator import RoborockConfigEntry, RoborockDataUpdateCoordinator
|
from .coordinator import RoborockConfigEntry, RoborockDataUpdateCoordinator
|
||||||
from .entity import RoborockCoordinatedEntityV1
|
from .entity import RoborockCoordinatedEntityV1
|
||||||
from .image import ColorsPalette, ImageConfig, RoborockMapDataParser, Sizes
|
|
||||||
|
|
||||||
STATE_CODE_TO_STATE = {
|
STATE_CODE_TO_STATE = {
|
||||||
RoborockStateCode.starting: VacuumActivity.IDLE, # "Starting"
|
RoborockStateCode.starting: VacuumActivity.IDLE, # "Starting"
|
||||||
|
@ -110,7 +110,7 @@ def bypass_api_fixture(bypass_api_client_fixture: Any) -> None:
|
|||||||
return_value=MULTI_MAP_LIST,
|
return_value=MULTI_MAP_LIST,
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
return_value=MAP_DATA,
|
return_value=MAP_DATA,
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
|
@ -65,7 +65,7 @@ async def test_floorplan_image(
|
|||||||
"homeassistant.components.roborock.image.dt_util.utcnow", return_value=now
|
"homeassistant.components.roborock.image.dt_util.utcnow", return_value=now
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
return_value=new_map_data,
|
return_value=new_map_data,
|
||||||
) as parse_map,
|
) as parse_map,
|
||||||
):
|
):
|
||||||
@ -94,7 +94,7 @@ async def test_floorplan_image_failed_parse(
|
|||||||
# Update image, but get none for parse image.
|
# Update image, but get none for parse image.
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
return_value=map_data,
|
return_value=map_data,
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
@ -148,7 +148,7 @@ async def test_fail_to_load_image(
|
|||||||
"""Test that we gracefully handle failing to load an image."""
|
"""Test that we gracefully handle failing to load an image."""
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
) as parse_map,
|
) as parse_map,
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.roborock_storage.Path.exists",
|
"homeassistant.components.roborock.roborock_storage.Path.exists",
|
||||||
@ -178,7 +178,7 @@ async def test_fail_parse_on_startup(
|
|||||||
map_data = copy.deepcopy(MAP_DATA)
|
map_data = copy.deepcopy(MAP_DATA)
|
||||||
map_data.image = None
|
map_data.image = None
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
return_value=map_data,
|
return_value=map_data,
|
||||||
):
|
):
|
||||||
await async_setup_component(hass, DOMAIN, {})
|
await async_setup_component(hass, DOMAIN, {})
|
||||||
@ -226,7 +226,7 @@ async def test_fail_updating_image(
|
|||||||
# Update image, but get none for parse image.
|
# Update image, but get none for parse image.
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
return_value=map_data,
|
return_value=map_data,
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
|
@ -261,7 +261,7 @@ async def test_get_current_position(
|
|||||||
return_value=b"",
|
return_value=b"",
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
return_value=map_data,
|
return_value=map_data,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
@ -316,7 +316,7 @@ async def test_get_current_position_no_robot_position(
|
|||||||
return_value=b"",
|
return_value=b"",
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.roborock.image.RoborockMapDataParser.parse",
|
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
|
||||||
return_value=map_data,
|
return_value=map_data,
|
||||||
),
|
),
|
||||||
pytest.raises(HomeAssistantError, match="Robot position not found"),
|
pytest.raises(HomeAssistantError, match="Robot position not found"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user