pymammotion 0.5.69__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. pymammotion/__init__.py +53 -0
  2. pymammotion/agora/__init__.py +0 -0
  3. pymammotion/agora/agora_api.py +755 -0
  4. pymammotion/agora/agora_rtc_capabilities.py +748 -0
  5. pymammotion/agora/agora_websockets.py +1175 -0
  6. pymammotion/aliyun/__init__.py +1 -0
  7. pymammotion/aliyun/client.py +235 -0
  8. pymammotion/aliyun/cloud_gateway.py +982 -0
  9. pymammotion/aliyun/model/aep_response.py +21 -0
  10. pymammotion/aliyun/model/connect_response.py +51 -0
  11. pymammotion/aliyun/model/dev_by_account_response.py +195 -0
  12. pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
  13. pymammotion/aliyun/model/regions_response.py +29 -0
  14. pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
  15. pymammotion/aliyun/model/thing_response.py +12 -0
  16. pymammotion/aliyun/regions.py +62 -0
  17. pymammotion/aliyun/tea/core.py +297 -0
  18. pymammotion/aliyun/tmp_constant.py +171 -0
  19. pymammotion/bluetooth/__init__.py +1 -0
  20. pymammotion/bluetooth/ble.py +62 -0
  21. pymammotion/bluetooth/ble_message.py +676 -0
  22. pymammotion/bluetooth/const.py +27 -0
  23. pymammotion/bluetooth/data/__init__.py +0 -0
  24. pymammotion/bluetooth/data/convert.py +25 -0
  25. pymammotion/bluetooth/data/framectrldata.py +40 -0
  26. pymammotion/bluetooth/data/notifydata.py +62 -0
  27. pymammotion/bluetooth/model/__init__.py +0 -0
  28. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  29. pymammotion/const.py +13 -0
  30. pymammotion/data/__init__.py +0 -0
  31. pymammotion/data/model/__init__.py +8 -0
  32. pymammotion/data/model/account.py +8 -0
  33. pymammotion/data/model/device.py +192 -0
  34. pymammotion/data/model/device_config.py +72 -0
  35. pymammotion/data/model/device_info.py +60 -0
  36. pymammotion/data/model/device_limits.py +49 -0
  37. pymammotion/data/model/enums.py +77 -0
  38. pymammotion/data/model/errors.py +12 -0
  39. pymammotion/data/model/events.py +14 -0
  40. pymammotion/data/model/generate_geojson.py +565 -0
  41. pymammotion/data/model/generate_route_information.py +26 -0
  42. pymammotion/data/model/hash_list.py +475 -0
  43. pymammotion/data/model/location.py +36 -0
  44. pymammotion/data/model/mowing_modes.py +77 -0
  45. pymammotion/data/model/rapid_state.py +45 -0
  46. pymammotion/data/model/raw_data.py +215 -0
  47. pymammotion/data/model/region_data.py +102 -0
  48. pymammotion/data/model/report_info.py +182 -0
  49. pymammotion/data/model/work.py +27 -0
  50. pymammotion/data/mower_state_manager.py +369 -0
  51. pymammotion/data/mqtt/__init__.py +1 -0
  52. pymammotion/data/mqtt/event.py +227 -0
  53. pymammotion/data/mqtt/mammotion_properties.py +276 -0
  54. pymammotion/data/mqtt/properties.py +203 -0
  55. pymammotion/data/mqtt/status.py +57 -0
  56. pymammotion/event/__init__.py +6 -0
  57. pymammotion/event/event.py +96 -0
  58. pymammotion/homeassistant/__init__.py +3 -0
  59. pymammotion/homeassistant/mower_api.py +514 -0
  60. pymammotion/homeassistant/rtk_api.py +54 -0
  61. pymammotion/http/__init__.py +0 -0
  62. pymammotion/http/encryption.py +220 -0
  63. pymammotion/http/http.py +673 -0
  64. pymammotion/http/model/__init__.py +0 -0
  65. pymammotion/http/model/camera_stream.py +31 -0
  66. pymammotion/http/model/http.py +249 -0
  67. pymammotion/http/model/response_factory.py +61 -0
  68. pymammotion/http/model/rtk.py +16 -0
  69. pymammotion/mammotion/__init__.py +0 -0
  70. pymammotion/mammotion/commands/__init__.py +0 -0
  71. pymammotion/mammotion/commands/abstract_message.py +24 -0
  72. pymammotion/mammotion/commands/mammotion_command.py +81 -0
  73. pymammotion/mammotion/commands/messages/__init__.py +0 -0
  74. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  75. pymammotion/mammotion/commands/messages/driver.py +122 -0
  76. pymammotion/mammotion/commands/messages/media.py +87 -0
  77. pymammotion/mammotion/commands/messages/navigation.py +564 -0
  78. pymammotion/mammotion/commands/messages/network.py +205 -0
  79. pymammotion/mammotion/commands/messages/ota.py +38 -0
  80. pymammotion/mammotion/commands/messages/system.py +330 -0
  81. pymammotion/mammotion/commands/messages/video.py +33 -0
  82. pymammotion/mammotion/control/__init__.py +0 -0
  83. pymammotion/mammotion/control/joystick.py +145 -0
  84. pymammotion/mammotion/devices/__init__.py +29 -0
  85. pymammotion/mammotion/devices/base.py +163 -0
  86. pymammotion/mammotion/devices/mammotion.py +571 -0
  87. pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
  88. pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
  89. pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
  90. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  91. pymammotion/mammotion/devices/managers/managers.py +81 -0
  92. pymammotion/mammotion/devices/mower_device.py +120 -0
  93. pymammotion/mammotion/devices/mower_manager.py +107 -0
  94. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  95. pymammotion/mammotion/devices/rtk_cloud.py +115 -0
  96. pymammotion/mammotion/devices/rtk_device.py +50 -0
  97. pymammotion/mammotion/devices/rtk_manager.py +125 -0
  98. pymammotion/mqtt/__init__.py +6 -0
  99. pymammotion/mqtt/aliyun_mqtt.py +237 -0
  100. pymammotion/mqtt/linkkit/__init__.py +5 -0
  101. pymammotion/mqtt/linkkit/h2client.py +585 -0
  102. pymammotion/mqtt/linkkit/linkkit.py +3025 -0
  103. pymammotion/mqtt/mammotion_future.py +26 -0
  104. pymammotion/mqtt/mammotion_mqtt.py +214 -0
  105. pymammotion/mqtt/mqtt_models.py +66 -0
  106. pymammotion/proto/__init__.py +4841 -0
  107. pymammotion/proto/basestation.proto +51 -0
  108. pymammotion/proto/basestation_pb2.py +35 -0
  109. pymammotion/proto/basestation_pb2.pyi +89 -0
  110. pymammotion/proto/common.proto +7 -0
  111. pymammotion/proto/common_pb2.py +25 -0
  112. pymammotion/proto/common_pb2.pyi +13 -0
  113. pymammotion/proto/dev_net.proto +321 -0
  114. pymammotion/proto/dev_net_pb2.py +111 -0
  115. pymammotion/proto/dev_net_pb2.pyi +515 -0
  116. pymammotion/proto/luba_msg.proto +76 -0
  117. pymammotion/proto/luba_msg_pb2.py +41 -0
  118. pymammotion/proto/luba_msg_pb2.pyi +97 -0
  119. pymammotion/proto/luba_mul.proto +129 -0
  120. pymammotion/proto/luba_mul_pb2.py +61 -0
  121. pymammotion/proto/luba_mul_pb2.pyi +178 -0
  122. pymammotion/proto/mctrl_driver.proto +107 -0
  123. pymammotion/proto/mctrl_driver_pb2.py +57 -0
  124. pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
  125. pymammotion/proto/mctrl_nav.proto +591 -0
  126. pymammotion/proto/mctrl_nav_pb2.py +136 -0
  127. pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
  128. pymammotion/proto/mctrl_ota.proto +80 -0
  129. pymammotion/proto/mctrl_ota_pb2.py +45 -0
  130. pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
  131. pymammotion/proto/mctrl_pept.proto +34 -0
  132. pymammotion/proto/mctrl_pept_pb2.py +33 -0
  133. pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
  134. pymammotion/proto/mctrl_sys.proto +741 -0
  135. pymammotion/proto/mctrl_sys_pb2.py +206 -0
  136. pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
  137. pymammotion/proto/message_pool.py +3 -0
  138. pymammotion/proto/py.typed +0 -0
  139. pymammotion/py.typed +0 -0
  140. pymammotion/utility/constant/__init__.py +3 -0
  141. pymammotion/utility/constant/device_constant.py +315 -0
  142. pymammotion/utility/conversions.py +5 -0
  143. pymammotion/utility/datatype_converter.py +124 -0
  144. pymammotion/utility/device_config.py +755 -0
  145. pymammotion/utility/device_type.py +489 -0
  146. pymammotion/utility/map.py +259 -0
  147. pymammotion/utility/movement.py +18 -0
  148. pymammotion/utility/mur_mur_hash.py +159 -0
  149. pymammotion/utility/periodic.py +106 -0
  150. pymammotion/utility/rocker_util.py +194 -0
  151. pymammotion-0.5.69.dist-info/METADATA +93 -0
  152. pymammotion-0.5.69.dist-info/RECORD +154 -0
  153. pymammotion-0.5.69.dist-info/WHEEL +4 -0
  154. pymammotion-0.5.69.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,514 @@
1
+ """Thin api layer between home assistant and pymammotion."""
2
+
3
+ import asyncio
4
+ from datetime import datetime, timedelta
5
+ from logging import getLogger
6
+ from typing import Any
7
+
8
+ from aiohttp import ClientSession
9
+
10
+ from pymammotion.aliyun.cloud_gateway import (
11
+ EXPIRED_CREDENTIAL_EXCEPTIONS,
12
+ DeviceOfflineException,
13
+ FailedRequestException,
14
+ GatewayTimeoutException,
15
+ NoConnectionException,
16
+ )
17
+ from pymammotion.data.model import GenerateRouteInformation
18
+ from pymammotion.data.model.device import MowingDevice
19
+ from pymammotion.data.model.device_config import OperationSettings, create_path_order
20
+ from pymammotion.mammotion.devices import MammotionMowerDeviceManager
21
+ from pymammotion.mammotion.devices.mammotion import Mammotion
22
+ from pymammotion.proto import RptAct, RptInfoType
23
+ from pymammotion.utility.device_type import DeviceType
24
+
25
+ logger = getLogger(__name__)
26
+
27
+
28
+ class HomeAssistantMowerApi:
29
+ """API for interacting with Mammotion Mowers for Home Assistant."""
30
+
31
+ def __init__(self, session: ClientSession | None = None) -> None:
32
+ self._plan_lock = asyncio.Lock()
33
+ self.update_failures = 0
34
+ self._mammotion = Mammotion(session)
35
+ self._map_lock = asyncio.Lock()
36
+ self._last_call_times: dict[str, datetime] = {}
37
+ self._call_intervals = {
38
+ "check_maps": timedelta(minutes=5),
39
+ "read_plan": timedelta(minutes=30),
40
+ "read_settings": timedelta(minutes=5),
41
+ "get_errors": timedelta(minutes=1),
42
+ "get_report_cfg": timedelta(seconds=5),
43
+ "get_maintenance": timedelta(minutes=30),
44
+ "device_version_upgrade": timedelta(hours=24),
45
+ "device_info": timedelta(hours=24),
46
+ }
47
+
48
+ @property
49
+ def mammotion(self) -> Mammotion:
50
+ return self._mammotion
51
+
52
+ def _should_call_api(self, api_name: str, device: MowingDevice | None = None) -> bool:
53
+ """Check if API should be called based on time or criteria."""
54
+ # Time-based check
55
+ if api_name not in self._last_call_times:
56
+ return True
57
+
58
+ last_call = self._last_call_times[api_name]
59
+ interval = self._call_intervals.get(api_name, timedelta(seconds=10))
60
+
61
+ # Criteria-based checks
62
+ if api_name == "check_maps" and device:
63
+ # Call immediately if map data is incomplete
64
+ if len(device.map.area) == 0 or device.map.missing_hashlist():
65
+ return True
66
+
67
+ return datetime.now() - last_call >= interval
68
+
69
+ def _mark_api_called(self, api_name: str) -> None:
70
+ """Mark an API as called with the current timestamp."""
71
+ self._last_call_times[api_name] = datetime.now()
72
+
73
+ async def update(self, device_name: str) -> MowingDevice:
74
+ device = self._mammotion.get_device_by_name(device_name)
75
+
76
+ if device.has_queued_commands():
77
+ return device.state
78
+
79
+ if self._map_lock.locked():
80
+ # if maps is not complete kick off the map sync process again
81
+ if len(device.state.map.missing_hashlist()) > 0:
82
+ await self._mammotion.start_map_sync(device_name)
83
+ return device.state
84
+ # if maps complete
85
+ else:
86
+ self._map_lock.release()
87
+
88
+ # Check maps periodically
89
+ if self._should_call_api("check_maps") and not self._map_lock.locked():
90
+ await self._map_lock.acquire()
91
+ await self.mammotion.start_map_sync(device_name)
92
+ self._mark_api_called("check_maps")
93
+ return device.state
94
+
95
+ if self._should_call_api("read_plan"):
96
+ if len(device.state.map.plan) == 0 or list(device.state.map.plan.values())[0].total_plan_num != len(
97
+ device.state.map.plan
98
+ ):
99
+ await self.async_send_command(device_name, "read_plan", sub_cmd=2, plan_index=0)
100
+ self._mark_api_called("read_plan")
101
+ return device.state
102
+
103
+ # Read settings less frequently
104
+ if self._should_call_api("read_settings"):
105
+ # await device.async_read_settings()
106
+ self._mark_api_called("read_settings")
107
+
108
+ # Check for errors periodically
109
+ if self._should_call_api("get_errors"):
110
+ await self.async_send_command(device_name, "get_error_code")
111
+ await self.async_send_command(device_name, "get_error_timestamp")
112
+ self._mark_api_called("get_errors")
113
+
114
+ if self._should_call_api("get_report_cfg"):
115
+ await self.async_send_command(device_name, "get_report_cfg")
116
+ self._mark_api_called("get_report_cfg")
117
+
118
+ if self._should_call_api("get_maintenance"):
119
+ await self.async_send_command(device_name, "get_maintenance")
120
+ self._mark_api_called("get_maintenance")
121
+
122
+ if self._should_call_api("device_version_upgrade"):
123
+ await self.async_check_firmware_version(device_name)
124
+ self._mark_api_called("device_version_upgrade")
125
+
126
+ if self._should_call_api("device_info"):
127
+ await self.async_device_info(device_name)
128
+
129
+ return device.state
130
+
131
+ async def async_send_command(self, device_name: str, command: str, **kwargs: Any) -> bool | None:
132
+ """Send command."""
133
+ device = self._mammotion.get_device_by_name(device_name)
134
+
135
+ try:
136
+ # TODO check preference
137
+ if device.cloud:
138
+ return await self.async_send_cloud_command(device, command, **kwargs)
139
+ elif device.ble:
140
+ return await self.async_send_bluetooth_command(device, command, **kwargs)
141
+ except (DeviceOfflineException, NoConnectionException) as ex:
142
+ """Device is offline try bluetooth if we have it."""
143
+ logger.error(f"Device offline: {ex.iot_id}")
144
+ if ble := device.ble:
145
+ # if we don't do this, it will stay connected and no longer update over Wi-Fi
146
+ ble.set_disconnect_strategy(disconnect=True)
147
+ await ble.queue_command(command, **kwargs)
148
+
149
+ return True
150
+ raise DeviceOfflineException(ex.args[0], device.iot_id)
151
+ return False
152
+
153
+ async def async_send_cloud_command(
154
+ self, device: MammotionMowerDeviceManager, key: str, **kwargs: Any
155
+ ) -> bool | None:
156
+ """Send command."""
157
+ if cloud := device.cloud:
158
+ if not device.state.online:
159
+ return False
160
+
161
+ try:
162
+ await cloud.command(key, **kwargs)
163
+ self.update_failures = 0
164
+ return True
165
+ except FailedRequestException:
166
+ self.update_failures += 1
167
+ if self.update_failures < 5:
168
+ await cloud.command(key, **kwargs)
169
+ return True
170
+ return False
171
+ except EXPIRED_CREDENTIAL_EXCEPTIONS:
172
+ self.update_failures += 1
173
+ await self._mammotion.refresh_login(device.mammotion_http.account)
174
+ # TODO tell home assistant the credentials have changed
175
+ if self.update_failures < 5:
176
+ await cloud.command(key, **kwargs)
177
+ return True
178
+ return False
179
+ except GatewayTimeoutException as ex:
180
+ logger.error(f"Gateway timeout exception: {ex.iot_id}")
181
+ self.update_failures = 0
182
+ return False
183
+ except (DeviceOfflineException, NoConnectionException) as ex:
184
+ """Device is offline try bluetooth if we have it."""
185
+ logger.error(f"Device offline: {ex.iot_id}")
186
+ return False
187
+
188
+ @staticmethod
189
+ async def async_send_bluetooth_command(device: MammotionMowerDeviceManager, key: str, **kwargs: Any) -> bool | None:
190
+ """Send command."""
191
+ if ble := device.ble:
192
+ await ble.command(key, **kwargs)
193
+
194
+ return True
195
+ raise DeviceOfflineException("bluetooth command failed", device.iot_id)
196
+
197
+ async def set_scheduled_updates(self, device_name: str, enabled: bool) -> None:
198
+ device = self.mammotion.get_device_by_name(device_name)
199
+ device.state.enabled = enabled
200
+ if device.state.enabled:
201
+ self.update_failures = 0
202
+ if not device.state.online:
203
+ device.state.online = True
204
+ if device.cloud and device.cloud.stopped:
205
+ await device.cloud.start()
206
+ else:
207
+ if device.cloud:
208
+ await device.cloud.stop()
209
+ if device.cloud.mqtt.is_connected():
210
+ device.cloud.mqtt.disconnect()
211
+ if device.ble:
212
+ await device.ble.stop()
213
+
214
+ def is_online(self, device_name: str) -> bool:
215
+ if device := self.mammotion.get_device_by_name(device_name):
216
+ if ble := device.ble:
217
+ return device.state.online or ble is not None and ble.client.is_connected
218
+ return False
219
+
220
+ async def update_firmware(self, device_name: str, version: str) -> None:
221
+ """Update firmware."""
222
+ device = self.mammotion.get_device_by_name(device_name)
223
+ await device.mammotion_http.start_ota_upgrade(device.iot_id, version)
224
+
225
+ async def async_start_stop_blades(self, device_name: str, start_stop: bool, blade_height: int = 60) -> None:
226
+ """Start stop blades."""
227
+ if DeviceType.is_luba1(device_name):
228
+ if start_stop:
229
+ await self.async_send_command(device_name, "set_blade_control", on_off=1)
230
+ else:
231
+ await self.async_send_command(device_name, "set_blade_control", on_off=0)
232
+ elif start_stop:
233
+ if DeviceType.is_yuka(device_name) or DeviceType.is_yuka_mini(device_name):
234
+ blade_height = 0
235
+
236
+ await self.async_send_command(
237
+ device_name,
238
+ "operate_on_device",
239
+ main_ctrl=1,
240
+ cut_knife_ctrl=1,
241
+ cut_knife_height=blade_height,
242
+ max_run_speed=1.2,
243
+ )
244
+ else:
245
+ await self.async_send_command(
246
+ device_name,
247
+ "operate_on_device",
248
+ main_ctrl=0,
249
+ cut_knife_ctrl=0,
250
+ cut_knife_height=blade_height,
251
+ max_run_speed=1.2,
252
+ )
253
+
254
+ async def async_set_rain_detection(self, device_name: str, on_off: bool) -> None:
255
+ """Set rain detection."""
256
+ await self.async_send_command(device_name, "read_write_device", rw_id=3, context=int(on_off), rw=1)
257
+
258
+ async def async_read_rain_detection(self, device_name: str) -> None:
259
+ """Set rain detection."""
260
+ await self.async_send_command(device_name, "read_write_device", rw_id=3, context=1, rw=0)
261
+
262
+ async def async_set_sidelight(self, device_name: str, on_off: int) -> None:
263
+ """Set Sidelight."""
264
+ await self.async_send_command(device_name, "read_and_set_sidelight", is_sidelight=bool(on_off), operate=0)
265
+ await self.async_read_sidelight(device_name)
266
+
267
+ async def async_read_sidelight(self, device_name: str) -> None:
268
+ """Set Sidelight."""
269
+ await self.async_send_command(device_name, "read_and_set_sidelight", is_sidelight=False, operate=1)
270
+
271
+ async def async_set_manual_light(self, device_name: str, manual_ctrl: bool) -> None:
272
+ """Set manual night light."""
273
+ await self.async_send_command(device_name, "set_car_manual_light", manual_ctrl=manual_ctrl)
274
+ await self.async_send_command(device_name, "get_car_light", ids=1126)
275
+
276
+ async def async_set_night_light(self, device_name: str, night_light: bool) -> None:
277
+ """Set night light."""
278
+ await self.async_send_command(device_name, "set_car_light", on_off=night_light)
279
+ await self.async_send_command(device_name, "get_car_light", ids=1123)
280
+
281
+ async def async_set_traversal_mode(self, device_name: str, context: int) -> None:
282
+ """Set traversal mode."""
283
+ await self.async_send_command(device_name, "traverse_mode", context=context)
284
+
285
+ async def async_set_turning_mode(self, device_name: str, context: int) -> None:
286
+ """Set turning mode."""
287
+ await self.async_send_command(device_name, "turning_mode", context=context)
288
+
289
+ async def async_blade_height(self, device_name: str, height: int) -> int:
290
+ """Set blade height."""
291
+ await self.async_send_command(device_name, "set_blade_height", height=height)
292
+ return height
293
+
294
+ async def async_set_cutter_speed(self, device_name: str, mode: int) -> None:
295
+ """Set cutter speed."""
296
+ await self.async_send_command(device_name, "set_cutter_mode", cutter_mode=mode)
297
+
298
+ async def async_set_speed(self, device_name: str, speed: float) -> None:
299
+ """Set working speed."""
300
+ await self.async_send_command(device_name, "set_speed", speed=speed)
301
+
302
+ async def async_leave_dock(self, device_name: str) -> None:
303
+ """Leave dock."""
304
+ await self.send_command_and_update(device_name, "leave_dock")
305
+
306
+ async def async_cancel_task(self, device_name: str) -> None:
307
+ """Cancel task."""
308
+ await self.send_command_and_update(device_name, "cancel_job")
309
+
310
+ async def async_move_forward(self, device_name: str, speed: float) -> None:
311
+ """Move forward."""
312
+ device = self.mammotion.get_device_by_name(device_name)
313
+ await self.async_send_bluetooth_command(device, "move_forward", linear=speed)
314
+
315
+ async def async_move_left(self, device_name: str, speed: float) -> None:
316
+ """Move left."""
317
+ device = self.mammotion.get_device_by_name(device_name)
318
+ await self.async_send_bluetooth_command(device, "move_left", angular=speed)
319
+
320
+ async def async_move_right(self, device_name: str, speed: float) -> None:
321
+ """Move right."""
322
+ device = self.mammotion.get_device_by_name(device_name)
323
+ await self.async_send_bluetooth_command(device, "move_right", angular=speed)
324
+
325
+ async def async_move_back(self, device_name: str, speed: float) -> None:
326
+ """Move back."""
327
+ device = self.mammotion.get_device_by_name(device_name)
328
+ await self.async_send_bluetooth_command(device, "move_back", linear=speed)
329
+
330
+ async def async_rtk_dock_location(self, device_name: str) -> None:
331
+ """RTK and dock location."""
332
+ await self.async_send_command(device_name, "read_write_device", rw_id=5, rw=1, context=1)
333
+
334
+ async def async_get_area_list(self, device_name: str, iot_id: str) -> None:
335
+ """Mowing area List."""
336
+ await self.async_send_command(device_name, "get_area_name_list", device_id=iot_id)
337
+
338
+ async def async_relocate_charging_station(self, device_name: str) -> None:
339
+ """Reset charging station."""
340
+ await self.async_send_command(device_name, "delete_charge_point")
341
+ # fetch charging location?
342
+ """
343
+ nav {
344
+ todev_get_commondata {
345
+ pver: 1
346
+ subCmd: 2
347
+ action: 6
348
+ type: 5
349
+ totalFrame: 1
350
+ currentFrame: 1
351
+ }
352
+ }
353
+ """
354
+
355
+ async def async_set_non_work_hours(self, device_name: str, start_time: str, end_time: str) -> None:
356
+ """Set non work hours l1?."""
357
+ device = self._mammotion.get_device_by_name(device_name)
358
+ await self.async_send_command(
359
+ device_name,
360
+ "set_plan_unable_time",
361
+ sub_cmd=device.state.non_work_hours.sub_cmd,
362
+ device_id=device.iot_id,
363
+ unable_end_time=end_time,
364
+ unable_start_time=start_time,
365
+ )
366
+
367
+ async def async_set_job_dnd(self, device_name: str, start_time: str, end_time: str) -> None:
368
+ """Set non work hours."""
369
+ await self.async_send_command(
370
+ device_name,
371
+ "job_do_not_disturb",
372
+ sub_cmd=1,
373
+ trigger=1,
374
+ unable_end_time=end_time,
375
+ unable_start_time=start_time,
376
+ )
377
+
378
+ async def async_del_job_dnd(self, device_name: str) -> None:
379
+ """Del non work hours."""
380
+ await self.async_send_command(device_name, "job_do_not_disturb", sub_cmd=1, trigger=0)
381
+
382
+ async def send_command_and_update(self, device_name: str, command_str: str, **kwargs: Any) -> None:
383
+ """Send command and update."""
384
+ await self.async_send_command(device_name, command_str, **kwargs)
385
+ await self.async_request_iot_sync(device_name)
386
+
387
+ async def async_request_iot_sync(self, device_name: str, stop: bool = False) -> None:
388
+ """Sync specific info from device."""
389
+ await self.async_send_command(
390
+ device_name,
391
+ "request_iot_sys",
392
+ rpt_act=RptAct.RPT_STOP if stop else RptAct.RPT_START,
393
+ rpt_info_type=[
394
+ RptInfoType.RIT_DEV_STA,
395
+ RptInfoType.RIT_DEV_LOCAL,
396
+ RptInfoType.RIT_WORK,
397
+ RptInfoType.RIT_MAINTAIN,
398
+ RptInfoType.RIT_BASESTATION_INFO,
399
+ RptInfoType.RIT_VIO,
400
+ ],
401
+ timeout=10000,
402
+ period=3000,
403
+ no_change_period=4000,
404
+ count=0,
405
+ )
406
+
407
+ def generate_route_information(
408
+ self, device_name: str, operation_settings: OperationSettings
409
+ ) -> GenerateRouteInformation:
410
+ """Generate route information."""
411
+ device = self.mammotion.get_device_by_name(device_name)
412
+ if device.state.report_data.dev:
413
+ dev = device.state.report_data.dev
414
+ if dev.collector_status.collector_installation_status == 0:
415
+ operation_settings.is_dump = False
416
+
417
+ if DeviceType.is_yuka(device_name):
418
+ operation_settings.blade_height = -10
419
+
420
+ route_information = GenerateRouteInformation(
421
+ one_hashs=list(operation_settings.areas),
422
+ rain_tactics=operation_settings.rain_tactics,
423
+ speed=operation_settings.speed,
424
+ ultra_wave=operation_settings.ultra_wave, # touch no touch etc
425
+ toward=operation_settings.toward, # is just angle (route angle)
426
+ toward_included_angle=operation_settings.toward_included_angle # demond_angle
427
+ if operation_settings.channel_mode == 1
428
+ else 0, # crossing angle relative to grid
429
+ toward_mode=operation_settings.toward_mode,
430
+ blade_height=operation_settings.blade_height,
431
+ channel_mode=operation_settings.channel_mode, # single, double, segment or none (route mode)
432
+ channel_width=operation_settings.channel_width, # path space
433
+ job_mode=operation_settings.job_mode, # taskMode grid or border first
434
+ edge_mode=operation_settings.mowing_laps, # perimeter/mowing laps
435
+ path_order=create_path_order(operation_settings, device_name),
436
+ obstacle_laps=operation_settings.obstacle_laps,
437
+ )
438
+
439
+ if DeviceType.is_luba1(device_name):
440
+ route_information.toward_mode = 0
441
+ route_information.toward_included_angle = 0
442
+ return route_information
443
+
444
+ async def async_plan_route(self, device_name: str, operation_settings: OperationSettings) -> bool | None:
445
+ """Plan mow."""
446
+ route_information = self.generate_route_information(device_name, operation_settings)
447
+
448
+ # not sure if this is artificial limit
449
+ # if (
450
+ # DeviceType.is_mini_or_x_series(device_name)
451
+ # and route_information.toward_mode == 0
452
+ # ):
453
+ # route_information.toward = 0
454
+
455
+ return await self.async_send_command(
456
+ device_name, "generate_route_information", generate_route_information=route_information
457
+ )
458
+
459
+ async def async_modify_plan_route(self, device_name: str, operation_settings: OperationSettings) -> bool | None:
460
+ """Modify plan mow."""
461
+ device = self.mammotion.get_device_by_name(device_name)
462
+
463
+ if work := device.state.work:
464
+ operation_settings.areas = set(work.zone_hashs)
465
+ operation_settings.toward = work.toward
466
+ operation_settings.toward_mode = work.toward_mode
467
+ operation_settings.toward_included_angle = work.toward_included_angle
468
+ operation_settings.mowing_laps = work.edge_mode
469
+ operation_settings.job_mode = work.job_mode
470
+ operation_settings.job_id = work.job_id
471
+ operation_settings.job_version = work.job_ver
472
+
473
+ route_information = self.generate_route_information(device_name, operation_settings)
474
+ if route_information.toward_mode == 0:
475
+ route_information.toward = 0
476
+
477
+ return await self.async_send_command(
478
+ device_name, "modify_route_information", generate_route_information=route_information
479
+ )
480
+
481
+ async def start_task(self, device_name: str, plan_id: str) -> None:
482
+ """Start task."""
483
+ await self.async_send_command(device_name, "single_schedule", plan_id=plan_id)
484
+
485
+ async def clear_update_failures(self, device_name: str) -> None:
486
+ """Clear update failures."""
487
+ self.update_failures = 0
488
+ device = self.mammotion.get_device_by_name(device_name)
489
+ if not device.state.online:
490
+ device.state.online = True
491
+ if cloud := device.cloud:
492
+ if cloud.stopped:
493
+ await cloud.start()
494
+
495
+ async def async_check_firmware_version(self, device_name: str) -> None:
496
+ """Checks firmware version."""
497
+ device = self.mammotion.get_device_by_name(device_name)
498
+ ota_info = await device.mammotion_http.get_device_ota_firmware([device.iot_id])
499
+ logger.debug("OTA info: %s", ota_info.data)
500
+ if check_versions := ota_info.data:
501
+ for check_version in check_versions:
502
+ if check_version.device_id == device.iot_id:
503
+ device.state.update_check = check_version
504
+
505
+ async def async_device_info(self, device_name) -> None:
506
+ """Get device info."""
507
+ command_list = [
508
+ "get_device_version_main",
509
+ "get_device_version_info",
510
+ "get_device_base_info",
511
+ "get_device_product_model",
512
+ ]
513
+ for command in command_list:
514
+ await self.async_send_command(device_name, command)
@@ -0,0 +1,54 @@
1
+ import json
2
+
3
+ from pymammotion.aliyun.cloud_gateway import DeviceOfflineException, GatewayTimeoutException, SetupException
4
+ from pymammotion.data.model.device import RTKDevice
5
+ from pymammotion.http.model.http import CheckDeviceVersion
6
+ from pymammotion.mammotion.devices.mammotion import Mammotion
7
+
8
+
9
+ class HomeAssistantRTKApi:
10
+ def __init__(self) -> None:
11
+ self._mammotion = Mammotion()
12
+
13
+ @property
14
+ def mammotion(self) -> Mammotion:
15
+ return self._mammotion
16
+
17
+ async def update(self, device_name: str) -> RTKDevice:
18
+ """Update RTK data."""
19
+ device = self.mammotion.get_rtk_device_by_name(device_name)
20
+ try:
21
+ response = await device.cloud_client.get_device_properties(device.iot_id)
22
+ if response.code == 200:
23
+ if data := response.data:
24
+ if ota_progress := data.otaProgress:
25
+ device.state.update_check = CheckDeviceVersion.from_dict(ota_progress.value)
26
+ if network_info := data.networkInfo:
27
+ network = json.loads(network_info.value)
28
+ device.state.wifi_rssi = network["wifi_rssi"]
29
+ device.state.wifi_sta_mac = network["wifi_sta_mac"]
30
+ device.state.bt_mac = network["bt_mac"]
31
+ if coordinate := data.coordinate:
32
+ coord_val = json.loads(coordinate.value)
33
+ if device.state.lat == 0:
34
+ device.state.lat = coord_val["lat"]
35
+ if device.state.lon == 0:
36
+ device.state.lon = coord_val["lon"]
37
+ if device_version := data.deviceVersion:
38
+ device.state.device_version = device_version.value
39
+ device.state.online = True
40
+
41
+ ota_info = await device.cloud_client.mammotion_http.get_device_ota_firmware([device.state.iot_id])
42
+ if check_versions := ota_info.data:
43
+ for check_version in check_versions:
44
+ if check_version.device_id == device.state.iot_id:
45
+ device.state.update_check = check_version
46
+ return device.state
47
+ except SetupException:
48
+ """Cloud IOT Gateway is not setup."""
49
+ return device.state
50
+ except DeviceOfflineException:
51
+ device.state.online = False
52
+ except GatewayTimeoutException:
53
+ """Gateway is timing out again."""
54
+ return device.state
File without changes