mirror of
https://github.com/Lash-L/RoborockCustomMap.git
synced 2026-07-26 16:18:32 +02:00
Add per-map rotation support with select entity and calibrated overlays (#37)
* Add map rotation constants Introduce rotation configuration constants for map image handling. Adds rotation options (0, 90, 180, 270) and dispatcher signal name. * Add per-map rotation select entity Add SelectEntity to control map rotation per map_flag. Rotation value is persisted via RestoreEntity and stored in hass.data. Dispatcher signal notifies image entities when rotation changes. * Enable rotation select platform and initialize storage Register SELECT platform and initialize rotation storage in hass.data. Add proper unload cleanup and reload behavior. * Add backend map rotation with executor offloading Implement backend image rotation using Pillow. Rotation is applied in async_add_executor_job to avoid blocking the event loop. Includes defensive validation and fallback handling. * Add translations for rotation select entity Add English and German translations for map rotation select entity. Includes user-friendly labels for rotation options. * Document map rotation select entity in README Add documentation for the per-map rotation select entity. Explains: - How to rotate maps (0/90/180/270) - Where to find the rotation select entity - That calibration points are rotated as well - That no reload is required Also clarifies usage with Xiaomi Vacuum Map Card. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Luke Lashley <conway220@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
Luke Lashley
parent
1128d40ee8
commit
0531b6d120
@@ -0,0 +1,110 @@
|
||||
"""Support for Roborock map rotation select entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.roborock.coordinator import RoborockDataUpdateCoordinator
|
||||
from homeassistant.components.roborock.entity import RoborockCoordinatedEntityV1
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .const import (
|
||||
CONF_MAP_ROTATION,
|
||||
DEFAULT_MAP_ROTATION,
|
||||
DOMAIN,
|
||||
MAP_ROTATION_OPTIONS,
|
||||
SIGNAL_ROTATION_CHANGED,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up rotation Select entities (one per map)."""
|
||||
async_add_entities(
|
||||
RoborockMapRotationSelect(
|
||||
config_entry=config_entry,
|
||||
unique_id=f"{coord.duid_slug}_map_rotation_{map_info.map_flag}",
|
||||
coordinator=coord,
|
||||
map_flag=map_info.map_flag,
|
||||
map_name=map_info.name,
|
||||
)
|
||||
for coord in config_entry.runtime_data
|
||||
if coord.properties_api.home is not None
|
||||
for map_info in (coord.properties_api.home.home_map_info or {}).values()
|
||||
)
|
||||
|
||||
|
||||
class RoborockMapRotationSelect(RoborockCoordinatedEntityV1, RestoreEntity, SelectEntity):
|
||||
"""Select entity to control map rotation."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_entry: ConfigEntry,
|
||||
unique_id: str,
|
||||
coordinator: RoborockDataUpdateCoordinator,
|
||||
map_flag: int,
|
||||
map_name: str,
|
||||
) -> None:
|
||||
"""Initialize rotation select."""
|
||||
RoborockCoordinatedEntityV1.__init__(self, unique_id, coordinator)
|
||||
|
||||
self.config_entry = config_entry
|
||||
self.map_flag = map_flag
|
||||
|
||||
if not map_name:
|
||||
map_name = f"Map {map_flag}"
|
||||
|
||||
self._attr_name = f"{map_name} rotation"
|
||||
self._attr_options = [str(v) for v in MAP_ROTATION_OPTIONS]
|
||||
self._attr_current_option = str(DEFAULT_MAP_ROTATION)
|
||||
self._attr_translation_key = "rotation"
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore previous rotation setting and store in hass.data."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
if (last := await self.async_get_last_state()) is not None:
|
||||
if last.state in self._attr_options:
|
||||
self._attr_current_option = last.state
|
||||
|
||||
# Persist selection for the image entity to read
|
||||
self.hass.data[DOMAIN][self.config_entry.entry_id][CONF_MAP_ROTATION][
|
||||
self.map_flag
|
||||
] = int(self._attr_current_option)
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Handle user selecting a rotation option."""
|
||||
if option not in self._attr_options:
|
||||
return
|
||||
|
||||
self._attr_current_option = option
|
||||
|
||||
self.hass.data[DOMAIN][self.config_entry.entry_id][CONF_MAP_ROTATION][
|
||||
self.map_flag
|
||||
] = int(option)
|
||||
|
||||
# Notify the image entity to bust the cache via image_last_updated bump
|
||||
async_dispatcher_send(
|
||||
self.hass,
|
||||
f"{SIGNAL_ROTATION_CHANGED}_{self.config_entry.entry_id}_{self.map_flag}",
|
||||
)
|
||||
|
||||
self.async_write_ha_state()
|
||||
Reference in New Issue
Block a user