Skip to content

Commit 00ccaab

Browse files
committed
adjustments to area names, dynamics line fetching for ble only right now
1 parent 183f15b commit 00ccaab

2 files changed

Lines changed: 148 additions & 99 deletions

File tree

custom_components/mammotion/coordinator.py

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from homeassistant.helpers import device_registry as dr
2929
from homeassistant.helpers import entity_registry as er
3030
from homeassistant.helpers.debounce import Debouncer
31-
from homeassistant.helpers.event import async_call_later
31+
from homeassistant.helpers.event import async_call_later, async_track_time_interval
3232
from homeassistant.helpers.storage import Store
3333
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
3434
from mashumaro.exceptions import InvalidFieldValue
@@ -50,7 +50,7 @@
5050
RTKBaseStationDevice,
5151
)
5252
from pymammotion.data.model.device_config import OperationSettings, create_path_order
53-
from pymammotion.data.model.hash_list import AreaHashNameList, Plan, SvgMessage
53+
from pymammotion.data.model.hash_list import Plan, SvgMessage
5454
from pymammotion.data.model.pool_state import PoolPlan, SpinoToggle
5555
from pymammotion.data.model.report_info import Maintain, NetUsedType
5656
from pymammotion.data.mqtt.event import DeviceNotificationEventParams, ThingEventMessage
@@ -101,6 +101,7 @@
101101
MAINTENANCE_INTERVAL = timedelta(minutes=60)
102102
DEFAULT_INTERVAL = timedelta(minutes=30)
103103
REPORT_INTERVAL = timedelta(minutes=5)
104+
DYNAMICS_LINE_INTERVAL = timedelta(seconds=10)
104105
DEVICE_VERSION_INTERVAL = timedelta(weeks=1)
105106
MAP_INTERVAL = timedelta(minutes=60)
106107
RTK_INTERVAL = timedelta(hours=5)
@@ -1457,24 +1458,22 @@ def get_area_entity_name(self, area_hash: int) -> str | None:
14571458
if area_hash == 0:
14581459
return None
14591460

1460-
name = None
14611461
_mower_data = cast(MowingDevice, self.data)
1462-
if area_data := _mower_data.map.area.get(area_hash):
1463-
area_frame = area_data.data[0] if len(area_data.data) > 0 else None
1464-
if area_frame is not None:
1465-
area_name: AreaHashNameList | None = next(
1466-
(
1467-
area
1468-
for area in _mower_data.map.area_name
1469-
if area.hash == area_frame.hash
1470-
),
1471-
None,
1472-
)
1473-
name = area_name.name if area_name is not None else None
1474-
else:
1462+
if area_hash not in _mower_data.map.area:
14751463
return "path"
14761464

1477-
return name if name else f"area {area_hash}"
1465+
# Prefer the user's HA-level entity name over the device-assigned name.
1466+
entity_reg = er.async_get(self.hass)
1467+
unique_id = f"{self.unique_name}_{area_hash}"
1468+
entity_id = entity_reg.async_get_entity_id("switch", DOMAIN, unique_id)
1469+
if entity_id and (entry := entity_reg.async_get(entity_id)) and entry.name:
1470+
return entry.name
1471+
1472+
for area in _mower_data.map.computed_areas:
1473+
if area.hash == area_hash:
1474+
return area.name
1475+
1476+
return f"area {area_hash}"
14781477

14791478
@property
14801479
def map_sync_status(self) -> str:
@@ -1978,6 +1977,8 @@ async def _async_setup(self) -> None:
19781977
class MammotionMapUpdateCoordinator(MammotionBaseUpdateCoordinator[MowerInfo]):
19791978
"""Class to manage fetching mammotion data."""
19801979

1980+
_dynamics_line_cancel: CALLBACK_TYPE | None = None
1981+
19811982
def __init__(
19821983
self,
19831984
hass: HomeAssistant,
@@ -2046,13 +2047,72 @@ async def _async_update_data(self) -> MowerInfo:
20462047
assert _d is not None
20472048
return _d.mower_state
20482049

2050+
def _device_supports_dynamics_line(self) -> bool:
2051+
"""Return True if this device supports the dynamics-line mow-progress stream."""
2052+
device = self.manager.get_device_by_name(self.device_name)
2053+
firmware = device.device_firmwares.main_controller if device else None
2054+
return DeviceType.value_of_str(self.device_name).is_support_dynamics_line(
2055+
firmware
2056+
)
2057+
2058+
def _ble_is_connected(self) -> bool:
2059+
"""Return True if BLE transport exists and is currently connected."""
2060+
if handle := self.manager.mower(self.device_name):
2061+
if ble := handle.get_transport(TransportType.BLE):
2062+
return ble.is_connected
2063+
return False
2064+
2065+
def _stop_dynamics_line_poll(self) -> None:
2066+
if self._dynamics_line_cancel is not None:
2067+
self._dynamics_line_cancel()
2068+
self._dynamics_line_cancel = None
2069+
2070+
async def _on_sys_status_changed_dynamics(self, sys_status: int) -> None:
2071+
"""Start the dynamics-line poll when mowing over BLE; stop it otherwise."""
2072+
if (
2073+
sys_status in MOWING_ACTIVE_MODES
2074+
and self._device_supports_dynamics_line()
2075+
and self._ble_is_connected()
2076+
):
2077+
if self._dynamics_line_cancel is None:
2078+
self._dynamics_line_cancel = async_track_time_interval(
2079+
self.hass,
2080+
self._fetch_dynamics_line,
2081+
DYNAMICS_LINE_INTERVAL,
2082+
)
2083+
else:
2084+
self._stop_dynamics_line_poll()
2085+
2086+
async def _fetch_dynamics_line(self, _now: datetime.datetime) -> None:
2087+
"""Fetch the dynamics line; self-cancels if BLE has disconnected."""
2088+
if not self._ble_is_connected():
2089+
self._stop_dynamics_line_poll()
2090+
return
2091+
try:
2092+
await self.manager.get_dynamics_line(self.device_name)
2093+
device = self.manager.get_device_by_name(self.device_name)
2094+
if device is not None:
2095+
self.async_set_updated_data(device.mower_state)
2096+
except (
2097+
DeviceOfflineException,
2098+
NoTransportAvailableError,
2099+
GatewayTimeoutException,
2100+
):
2101+
pass
2102+
20492103
async def _async_setup(self) -> None:
20502104
"""Set up coordinator with initial call to get map data."""
20512105
await super()._async_setup()
20522106
device = self.manager.get_device_by_name(self.device_name)
20532107
if device is None:
20542108
return
20552109

2110+
if handle := self.manager.mower(self.device_name):
2111+
handle.watch_field(
2112+
lambda s: s.raw.report_data.dev.sys_status,
2113+
self._on_sys_status_changed_dynamics,
2114+
)
2115+
20562116
if not device.enabled or not device.online:
20572117
return
20582118
try:
@@ -2417,6 +2477,14 @@ async def _async_setup(self) -> None:
24172477
context=0,
24182478
rw=0,
24192479
)
2480+
# Fetch pool geometry once at startup. Responses arrive as unsolicited
2481+
# APP_DOWNLINK_CMD frames and are reassembled by PoolStateReducer.
2482+
for fetch in (self.async_fetch_pool_map, self.async_fetch_pool_line):
2483+
with contextlib.suppress(
2484+
GatewayTimeoutException,
2485+
NoTransportAvailableError,
2486+
):
2487+
await fetch()
24202488
except DeviceOfflineException:
24212489
self.device.online = False
24222490

0 commit comments

Comments
 (0)