Skip to content

Commit fedae1d

Browse files
committed
add RTK devices and add relocate charging station
1 parent c59e3d5 commit fedae1d

17 files changed

Lines changed: 406 additions & 34 deletions

custom_components/mammotion/__init__.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
from pymammotion import CloudIOTGateway
1414
from pymammotion.aliyun.model.aep_response import AepResponse
1515
from pymammotion.aliyun.model.connect_response import ConnectResponse
16-
from pymammotion.aliyun.model.dev_by_account_response import ListingDevByAccountResponse
16+
from pymammotion.aliyun.model.dev_by_account_response import (
17+
Device,
18+
ListingDevByAccountResponse,
19+
)
1720
from pymammotion.aliyun.model.login_by_oauth_response import LoginByOAuthResponse
1821
from pymammotion.aliyun.model.regions_response import RegionResponse
1922
from pymammotion.aliyun.model.session_by_authcode_response import (
@@ -51,8 +54,9 @@
5154
MammotionMaintenanceUpdateCoordinator,
5255
MammotionMapUpdateCoordinator,
5356
MammotionReportUpdateCoordinator,
57+
MammotionRTKCoordinator,
5458
)
55-
from .models import MammotionMowerData
59+
from .models import MammotionDevices, MammotionMowerData, MammotionRTKData
5660

5761
PLATFORMS: list[Platform] = [
5862
Platform.BINARY_SENSOR,
@@ -67,7 +71,7 @@
6771
Platform.UPDATE,
6872
]
6973

70-
type MammotionConfigEntry = ConfigEntry[list[MammotionMowerData]]
74+
type MammotionConfigEntry = ConfigEntry[MammotionDevices]
7175

7276

7377
async def async_setup_entry(hass: HomeAssistant, entry: MammotionConfigEntry) -> bool:
@@ -90,7 +94,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: MammotionConfigEntry) ->
9094

9195
use_wifi = entry.data.get(CONF_USE_WIFI, True)
9296

93-
mammotion_devices: list[MammotionMowerData] = []
97+
mammotion_mowers: list[MammotionMowerData] = []
98+
mammotion_devices: MammotionDevices = MammotionDevices([], [])
99+
mammotion_rtk: list[MammotionRTKData] = []
100+
mammotion_rtk_devices: list[Device] = []
94101

95102
if account and password:
96103
credentials = Credentials()
@@ -121,6 +128,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: MammotionConfigEntry) ->
121128
device
122129
) in mqtt_client.cloud_client.devices_by_account_response.data.data:
123130
if not device.deviceName.startswith(DEVICE_SUPPORT):
131+
if device.categoryKey == "Tracker":
132+
mammotion_rtk_devices.append(device)
124133
continue
125134

126135
mammotion_device = mammotion.get_or_create_device_by_name(
@@ -178,7 +187,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MammotionConfigEntry) ->
178187
# not entirely sure this is a good idea
179188
mammotion_device.remove_cloud()
180189

181-
mammotion_devices.append(
190+
mammotion_mowers.append(
182191
MammotionMowerData(
183192
name=device.deviceName,
184193
device=device,
@@ -196,13 +205,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: MammotionConfigEntry) ->
196205
except:
197206
"""Do nothing for now."""
198207

208+
for rtk in mammotion_rtk_devices:
209+
rtk_coordinator = MammotionRTKCoordinator(hass, entry, rtk, mqtt_client)
210+
await rtk_coordinator.async_config_entry_first_refresh()
211+
mammotion_rtk.append(
212+
MammotionRTKData(
213+
name=rtk.deviceName,
214+
api=mammotion,
215+
device=rtk,
216+
coordinator=rtk_coordinator,
217+
)
218+
)
219+
199220
# if not any(mammotion.get_device_by_name(mammotion_device.device.deviceName).preference == ConnectionPreference.WIFI for mammotion_device in mammotion_devices):
200221
# for mammotion_device in mammotion_devices:
201222
# mower = mammotion.get_device_by_name(mammotion_device.device.deviceName)
202223
# await mower.cloud().stop()
203224
# mower.cloud().mqtt.disconnect() if mower.cloud().mqtt.is_connected() else None
204225
# mower.remove_cloud()
205-
226+
mammotion_devices.RTK = mammotion_rtk
227+
mammotion_devices.mowers = mammotion_mowers
206228
entry.runtime_data = mammotion_devices
207229
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
208230

@@ -343,7 +365,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: MammotionConfigEntry) -
343365
"""Unload a config entry."""
344366

345367
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
346-
for mower in entry.runtime_data:
368+
for mower in entry.runtime_data.mowers:
347369
try:
348370
await mower.api.remove_device(mower.name)
349371
except TimeoutError:
@@ -363,7 +385,12 @@ async def async_remove_config_entry_device(
363385
),
364386
)
365387
mower = next(
366-
(mower for mower in config_entry.runtime_data if mower.name == mower_name), None
388+
(
389+
mower
390+
for mower in config_entry.runtime_data.mowers
391+
if mower.name == mower_name
392+
),
393+
None,
367394
)
368395

369396
return not bool(mower)

custom_components/mammotion/binary_sensor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async def async_setup_entry(
4343
async_add_entities: AddEntitiesCallback,
4444
) -> None:
4545
"""Set up the Mammotion sensor entity."""
46-
mammotion_devices = entry.runtime_data
46+
mammotion_devices = entry.runtime_data.mowers
4747

4848
for mower in mammotion_devices:
4949
async_add_entities(

custom_components/mammotion/button.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ class MammotionTaskButtonSensorEntityDescription(ButtonEntityDescription):
7676
press_fn=lambda coordinator: coordinator.join_webrtc_channel(),
7777
entity_category=EntityCategory.CONFIG,
7878
),
79-
# TODO add delete and set charging station
79+
MammotionButtonSensorEntityDescription(
80+
key="relocate_charging_station",
81+
press_fn=lambda coordinator: coordinator.async_relocate_charging_station(),
82+
),
8083
# delete_charge_point
8184
)
8285

@@ -87,7 +90,7 @@ async def async_setup_entry(
8790
async_add_entities: AddEntitiesCallback,
8891
) -> None:
8992
"""Set up the Mammotion button sensor entity."""
90-
mammotion_devices = entry.runtime_data
93+
mammotion_devices = entry.runtime_data.mowers
9194

9295
for mower in mammotion_devices:
9396
added_tasks: set[int] = set()

custom_components/mammotion/camera.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async def async_setup_entry(
5555
async_add_entities: AddEntitiesCallback,
5656
) -> None:
5757
"""Set up the Mammotion camera entities."""
58-
mowers = entry.runtime_data
58+
mowers = entry.runtime_data.mowers
5959
entities = []
6060
for mower in mowers:
6161
if not DeviceType.is_luba1(mower.device.deviceName):
@@ -153,7 +153,11 @@ def _get_mower_by_entity_id(entity_id: str):
153153
state = hass.states.get(entity_id)
154154
name = state.attributes.get("model_name")
155155
return next(
156-
(mower for mower in entry.runtime_data if mower.device.deviceName == name),
156+
(
157+
mower
158+
for mower in entry.runtime_data.mowers
159+
if mower.device.deviceName == name
160+
),
157161
None,
158162
)
159163

custom_components/mammotion/coordinator.py

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
FailedRequestException,
2626
GatewayTimeoutException,
2727
NoConnectionException,
28+
SetupException,
2829
)
2930
from pymammotion.aliyun.model.dev_by_account_response import Device
3031
from pymammotion.data.model import GenerateRouteInformation, HashList
31-
from pymammotion.data.model.device import MowerInfo, MowingDevice
32+
from pymammotion.data.model.device import MowerInfo, MowingDevice, RTKDevice
3233
from pymammotion.data.model.device_config import OperationSettings, create_path_order
3334
from pymammotion.data.model.report_info import Maintain
3435
from pymammotion.data.mqtt.event import DeviceNotificationEventParams, ThingEventMessage
@@ -37,13 +38,15 @@
3738
from pymammotion.http.model.camera_stream import (
3839
StreamSubscriptionResponse,
3940
)
40-
from pymammotion.http.model.http import ErrorInfo, Response
41+
from pymammotion.http.model.http import CheckDeviceVersion, ErrorInfo, Response
42+
from pymammotion.http.model.rtk import RTK
4143
from pymammotion.mammotion.commands.mammotion_command import MammotionCommand
4244
from pymammotion.mammotion.devices.mammotion import (
4345
ConnectionPreference,
4446
Mammotion,
4547
MammotionMixedDeviceManager,
4648
)
49+
from pymammotion.mammotion.devices.mammotion_cloud import MammotionCloud
4750
from pymammotion.proto import RptAct, RptInfoType, SystemUpdateBufMsg
4851
from pymammotion.utility.constant import WorkMode
4952
from pymammotion.utility.device_type import DeviceType
@@ -75,6 +78,7 @@
7578
REPORT_INTERVAL = timedelta(minutes=1)
7679
DEVICE_VERSION_INTERVAL = timedelta(days=1)
7780
MAP_INTERVAL = timedelta(minutes=30)
81+
RTK_INTERVAL = timedelta(hours=5)
7882

7983

8084
class MammotionBaseUpdateCoordinator[DataT](DataUpdateCoordinator[DataT]):
@@ -170,8 +174,8 @@ async def async_refresh_login(self) -> None:
170174

171175
async def device_offline(self, device: MammotionMixedDeviceManager) -> None:
172176
device.state.online = False
173-
if cloud := device.cloud():
174-
await cloud.stop()
177+
# if cloud := device.cloud():
178+
# await cloud.stop()
175179

176180
loop = asyncio.get_running_loop()
177181
loop.call_later(900, lambda: asyncio.create_task(self.clear_update_failures()))
@@ -233,7 +237,7 @@ async def async_send_command(self, command: str, **kwargs: Any) -> bool | None:
233237
try:
234238
if ble := device.ble():
235239
# if we don't do this it will stay connected and no longer update over wifi
236-
ble.set_disconnect_strategy(True)
240+
ble.set_disconnect_strategy(disconnect=True)
237241
await ble.queue_command(command, **kwargs)
238242

239243
return True
@@ -407,6 +411,23 @@ async def async_get_area_list(self) -> None:
407411
"""Mowing area List."""
408412
await self.async_send_command("get_area_name_list", device_id=self.device.iotId)
409413

414+
async def async_relocate_charging_station(self):
415+
"""Reset charging station."""
416+
await self.async_send_command("delete_charge_point")
417+
# fetch charging location?
418+
"""
419+
nav {
420+
todev_get_commondata {
421+
pver: 1
422+
subCmd: 2
423+
action: 6
424+
type: 5
425+
totalFrame: 1
426+
currentFrame: 1
427+
}
428+
}
429+
"""
430+
410431
async def send_command_and_update(self, command_str: str, **kwargs: Any) -> None:
411432
"""Send command and update."""
412433
await self.async_send_command(command_str, **kwargs)
@@ -963,7 +984,6 @@ async def _async_update_data(self):
963984
if (
964985
len(device.state.map.hashlist) == 0
965986
or len(device.state.map.missing_hashlist()) > 0
966-
or len(device.state.map.plan) == 0
967987
):
968988
await self.manager.start_map_sync(self.device_name)
969989

@@ -1136,3 +1156,101 @@ async def _async_setup(self) -> None:
11361156
)
11371157
except DeviceOfflineException:
11381158
"""Device is offline bluetooth has been attempted."""
1159+
1160+
1161+
class MammotionRTKCoordinator(DataUpdateCoordinator[RTKDevice]):
1162+
"""Mammotion DataUpdateCoordinator."""
1163+
1164+
def __init__(
1165+
self,
1166+
hass: HomeAssistant,
1167+
config_entry: MammotionConfigEntry,
1168+
device: Device,
1169+
cloud: MammotionCloud,
1170+
) -> None:
1171+
"""Initialize global mammotion data updater."""
1172+
super().__init__(
1173+
hass=hass,
1174+
logger=LOGGER,
1175+
name=DOMAIN,
1176+
update_interval=RTK_INTERVAL,
1177+
config_entry=config_entry,
1178+
)
1179+
assert config_entry.unique_id
1180+
self.account = self.config_entry.data[CONF_ACCOUNTNAME]
1181+
self.password = self.config_entry.data[CONF_PASSWORD]
1182+
self.device: Device = device
1183+
self.device_name = device.deviceName
1184+
self.cloud: MammotionCloud = cloud
1185+
self.data: RTKDevice = RTKDevice(
1186+
name=self.device_name,
1187+
iot_id=self.device.iotId,
1188+
product_key=self.device.productKey,
1189+
)
1190+
1191+
async def _async_update_data(self):
1192+
"""Update RTK data."""
1193+
try:
1194+
response = await self.cloud.cloud_client.get_device_properties(
1195+
self.device.iotId
1196+
)
1197+
if response.code == 200:
1198+
data = response.data
1199+
if ota_progress := data.otaProgress:
1200+
self.data.update_check = CheckDeviceVersion.from_dict(
1201+
ota_progress.value
1202+
)
1203+
if network_info := data.networkInfo:
1204+
network = json.loads(network_info.value)
1205+
self.data.wifi_rssi = network["wifi_rssi"]
1206+
self.data.wifi_sta_mac = network["wifi_sta_mac"]
1207+
self.data.bt_mac = network["bt_mac"]
1208+
if coordinate := data.coordinate:
1209+
coord_val = json.loads(coordinate.value)
1210+
self.data.lat = coord_val["lat"]
1211+
self.data.lon = coord_val["lon"]
1212+
if device_version := data.deviceVersion:
1213+
self.data.device_version = device_version.value
1214+
self.data.online = True
1215+
1216+
ota_info = (
1217+
await self.cloud.cloud_client.mammotion_http.get_device_ota_firmware(
1218+
[self.data.iot_id]
1219+
)
1220+
)
1221+
if check_versions := ota_info.data:
1222+
for check_version in check_versions:
1223+
if check_version.device_id == self.data.iot_id:
1224+
self.data.update_check = check_version
1225+
return self.data
1226+
except SetupException:
1227+
"""Cloud IOT Gateway is not setup."""
1228+
return self.data
1229+
except DeviceOfflineException:
1230+
self.data.online = False
1231+
except GatewayTimeoutException:
1232+
"""Gateway is timing out again."""
1233+
return self.data
1234+
1235+
async def _async_setup(self) -> None:
1236+
"""Setup RTK data."""
1237+
1238+
rtk_response = await self.cloud.cloud_client.mammotion_http.get_rtk_devices()
1239+
if rtk_response.code == 0:
1240+
rtk_list = [
1241+
rtk
1242+
for rtk in rtk_response.data
1243+
if self.device.deviceName == rtk.device_name
1244+
]
1245+
try:
1246+
rtk_device: RTK = next(iter(rtk_list))
1247+
self.data.lora_version = rtk_device.lora
1248+
except StopIteration:
1249+
"""Failed to get RTK device."""
1250+
return
1251+
1252+
async def update_firmware(self, version: str) -> None:
1253+
"""Update firmware."""
1254+
await self.cloud.cloud_client.mammotion_http.start_ota_upgrade(
1255+
self.device.iotId, version
1256+
)

custom_components/mammotion/device_tracker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def async_setup_entry(
2222
async_add_entities: AddEntitiesCallback,
2323
) -> None:
2424
"""Set up the RTK tracker from config entry."""
25-
mammotion_devices = entry.runtime_data
25+
mammotion_devices = entry.runtime_data.mowers
2626

2727
for mower in mammotion_devices:
2828
async_add_entities([MammotionTracker(mower.reporting_coordinator)])

custom_components/mammotion/diagnostics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async def async_get_config_entry_diagnostics(
1818
entry: MammotionConfigEntry,
1919
) -> dict[str, Any]:
2020
"""Return diagnostics for a config entry."""
21-
mammotion_devices: list[MammotionMowerData] = entry.runtime_data
21+
mammotion_devices: list[MammotionMowerData] = entry.runtime_data.mowers
2222
data = {}
2323
for device in mammotion_devices:
2424
data[device.name] = asdict(device.reporting_coordinator.data)

0 commit comments

Comments
 (0)