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,369 @@
1
+ """Manage state from notifications into MowingDevice."""
2
+
3
+ from collections.abc import Awaitable, Callable
4
+ from datetime import UTC, datetime
5
+ import logging
6
+ from typing import Any
7
+
8
+ import betterproto2
9
+ from shapely import Point
10
+
11
+ from pymammotion.data.model.device import MowingDevice
12
+ from pymammotion.data.model.device_info import SideLight
13
+ from pymammotion.data.model.generate_geojson import GeojsonGenerator
14
+ from pymammotion.data.model.hash_list import (
15
+ AreaHashNameList,
16
+ MowPath,
17
+ NavGetCommData,
18
+ NavGetHashListData,
19
+ Plan,
20
+ SvgMessage,
21
+ )
22
+ from pymammotion.data.model.location import Dock, LocationPoint
23
+ from pymammotion.data.model.work import CurrentTaskSettings
24
+ from pymammotion.data.mqtt.event import ThingEventMessage
25
+ from pymammotion.data.mqtt.properties import ThingPropertiesMessage
26
+ from pymammotion.data.mqtt.status import ThingStatusMessage
27
+ from pymammotion.event.event import DataEvent
28
+ from pymammotion.proto import (
29
+ AppGetAllAreaHashName,
30
+ AppGetCutterWorkMode,
31
+ AppSetCutterWorkMode,
32
+ CoverPathUploadT,
33
+ DeviceFwInfo,
34
+ DeviceProductTypeInfoT,
35
+ DrvDevInfoResp,
36
+ DrvDevInfoResult,
37
+ Getlamprsp,
38
+ GetNetworkInfoRsp,
39
+ LubaMsg,
40
+ NavGetCommDataAck,
41
+ NavGetHashListAck,
42
+ NavPlanJobSet,
43
+ NavReqCoverPath,
44
+ NavSysParamMsg,
45
+ NavUnableTimeSet,
46
+ SvgMessageAckT,
47
+ TimeCtrlLight,
48
+ WifiIotStatusReport,
49
+ )
50
+ from pymammotion.utility.map import CoordinateConverter
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ class MowerStateManager:
56
+ """Manage state."""
57
+
58
+ def __init__(self, device: MowingDevice) -> None:
59
+ """Initialize state manager with a device."""
60
+ self._device: MowingDevice = device
61
+ self.last_updated_at = datetime.now(UTC)
62
+ self.cloud_gethash_ack_callback: Callable[[NavGetHashListAck], Awaitable[None]] | None = None
63
+ self.cloud_get_commondata_ack_callback: (
64
+ Callable[[NavGetCommDataAck | SvgMessageAckT], Awaitable[None]] | None
65
+ ) = None
66
+ self.cloud_on_notification_callback = DataEvent()
67
+ self.cloud_queue_command_callback = DataEvent()
68
+
69
+ self.cloud_get_plan_callback: Callable[[NavPlanJobSet], Awaitable[None]] | None = None
70
+ self.ble_gethash_ack_callback: Callable[[NavGetHashListAck], Awaitable[None]] | None = None
71
+ self.ble_get_commondata_ack_callback: Callable[[NavGetCommDataAck | SvgMessageAckT], Awaitable[None]] | None = (
72
+ None
73
+ )
74
+ self.ble_get_plan_callback: Callable[[NavPlanJobSet], Awaitable[None]] | None = None
75
+ self.ble_on_notification_callback = DataEvent()
76
+ self.ble_queue_command_callback = DataEvent()
77
+
78
+ self.properties_callback = DataEvent()
79
+ self.status_callback = DataEvent()
80
+ self.device_event_callback = DataEvent()
81
+
82
+ def get_device(self) -> MowingDevice:
83
+ """Get device."""
84
+ return self._device
85
+
86
+ def set_device(self, device: MowingDevice) -> None:
87
+ """Set device."""
88
+ self._device = device
89
+
90
+ async def properties(self, thing_properties: ThingPropertiesMessage) -> None:
91
+ """Update device properties and invoke callback."""
92
+ # TODO update device based off thing properties
93
+ self._device.mqtt_properties = thing_properties
94
+ await self.on_properties_callback(thing_properties)
95
+
96
+ async def status(self, thing_status: ThingStatusMessage) -> None:
97
+ """Update device status and invoke callback."""
98
+ if not self._device.online:
99
+ self._device.online = True
100
+ self._device.status_properties = thing_status
101
+ if self._device.mower_state.product_key == "":
102
+ self._device.mower_state.product_key = thing_status.params.product_key
103
+ await self.on_status_callback(thing_status)
104
+
105
+ async def device_event(self, device_event: ThingEventMessage) -> None:
106
+ """Sets MQTT event and calls callback."""
107
+ self._device.mqtt_device_event = device_event
108
+ await self.on_device_event_callback(device_event)
109
+
110
+ @property
111
+ def online(self) -> bool:
112
+ """Return online status."""
113
+ return self._device.online
114
+
115
+ @online.setter
116
+ def online(self, value: bool) -> None:
117
+ """Set online status."""
118
+ self._device.online = value
119
+
120
+ async def gethash_ack_callback(self, msg: NavGetHashListAck) -> None:
121
+ """Dispatch hash list acknowledgment to available callback."""
122
+ if self.cloud_gethash_ack_callback:
123
+ await self.cloud_gethash_ack_callback(msg)
124
+ elif self.ble_gethash_ack_callback:
125
+ await self.ble_gethash_ack_callback(msg)
126
+
127
+ async def on_notification_callback(self, res: tuple[str, Any | None]) -> None:
128
+ """Dispatch notification to available callback."""
129
+ if self.cloud_on_notification_callback:
130
+ await self.cloud_on_notification_callback.data_event(res)
131
+ elif self.ble_on_notification_callback:
132
+ await self.ble_on_notification_callback.data_event(res)
133
+
134
+ async def on_properties_callback(self, thing_properties: ThingPropertiesMessage) -> None:
135
+ """Call properties callback if it exists."""
136
+ if self.properties_callback:
137
+ await self.properties_callback.data_event(thing_properties)
138
+
139
+ async def on_status_callback(self, thing_status: ThingStatusMessage) -> None:
140
+ """Execute the status callback if it is set."""
141
+ if self.status_callback:
142
+ await self.status_callback.data_event(thing_status)
143
+
144
+ async def on_device_event_callback(self, device_event: ThingEventMessage) -> None:
145
+ """Executes the event callback if it is set."""
146
+ if self.device_event_callback:
147
+ await self.device_event_callback.data_event(device_event)
148
+
149
+ async def get_commondata_ack_callback(self, comm_data: NavGetCommDataAck | SvgMessageAckT) -> None:
150
+ """Asynchronously calls the appropriate callback based on available handlers."""
151
+ if self.cloud_get_commondata_ack_callback:
152
+ await self.cloud_get_commondata_ack_callback(comm_data)
153
+ elif self.ble_get_commondata_ack_callback:
154
+ await self.ble_get_commondata_ack_callback(comm_data)
155
+
156
+ async def get_plan_callback(self, planjob: NavPlanJobSet) -> None:
157
+ """Dispatch plan job to available callback."""
158
+ if self.cloud_get_plan_callback:
159
+ await self.cloud_get_plan_callback(planjob)
160
+ elif self.ble_get_plan_callback:
161
+ await self.ble_get_plan_callback(planjob)
162
+
163
+ async def queue_command_callback(self, **kwargs: Any) -> None:
164
+ """Queue command to available callback."""
165
+ if self.cloud_queue_command_callback:
166
+ await self.cloud_queue_command_callback.data_event(**kwargs)
167
+ elif self.ble_queue_command_callback:
168
+ await self.ble_queue_command_callback.data_event(**kwargs)
169
+
170
+ async def notification(self, message: LubaMsg) -> None:
171
+ """Handle protobuf notifications."""
172
+ res = betterproto2.which_one_of(message, "LubaSubMsg")
173
+ self.last_updated_at = datetime.now(UTC)
174
+ # additional catch all if we don't get a status update
175
+ if not self._device.online:
176
+ self._device.online = True
177
+
178
+ match res[0]:
179
+ case "nav":
180
+ await self._update_nav_data(message)
181
+ case "sys":
182
+ self._update_sys_data(message)
183
+ case "driver":
184
+ self._update_driver_data(message)
185
+ case "net":
186
+ self._update_net_data(message)
187
+ case "mul":
188
+ self._update_mul_data(message)
189
+ case "ota":
190
+ self._update_ota_data(message)
191
+
192
+ await self.on_notification_callback(res)
193
+
194
+ async def _update_nav_data(self, message: LubaMsg) -> None:
195
+ """Update nav data."""
196
+ nav_msg = betterproto2.which_one_of(message.nav, "SubNavMsg")
197
+ match nav_msg[0]:
198
+ case "toapp_gethash_ack":
199
+ hashlist_ack: NavGetHashListAck = nav_msg[1]
200
+ self._device.map.update_root_hash_list(
201
+ NavGetHashListData.from_dict(hashlist_ack.to_dict(casing=betterproto2.Casing.SNAKE))
202
+ )
203
+ await self.gethash_ack_callback(nav_msg[1])
204
+ case "toapp_get_commondata_ack":
205
+ common_data: NavGetCommDataAck = nav_msg[1]
206
+ updated = self._device.map.update(
207
+ NavGetCommData.from_dict(common_data.to_dict(casing=betterproto2.Casing.SNAKE))
208
+ )
209
+ if updated:
210
+ if len(self._device.map.missing_hashlist(0)) == 0:
211
+ self.generate_geojson(self._device.location.RTK, self._device.location.dock)
212
+
213
+ await self.get_commondata_ack_callback(common_data)
214
+ case "cover_path_upload":
215
+ mow_path: CoverPathUploadT = nav_msg[1]
216
+ self._device.map.update_mow_path(MowPath.from_dict(mow_path.to_dict(casing=betterproto2.Casing.SNAKE)))
217
+ if len(self._device.map.find_missing_mow_path_frames()) == 0:
218
+ self.generate_mowing_geojson(self._device.location.RTK)
219
+
220
+ case "todev_planjob_set":
221
+ planjob: NavPlanJobSet = nav_msg[1]
222
+ self._device.map.update_plan(Plan.from_dict(planjob.to_dict(casing=betterproto2.Casing.SNAKE)))
223
+ await self.get_plan_callback(planjob)
224
+
225
+ case "toapp_svg_msg":
226
+ common_svg_data: SvgMessageAckT = nav_msg[1]
227
+ updated = self._device.map.update(
228
+ SvgMessage.from_dict(common_svg_data.to_dict(casing=betterproto2.Casing.SNAKE))
229
+ )
230
+ if updated:
231
+ await self.get_commondata_ack_callback(common_svg_data)
232
+
233
+ case "toapp_all_hash_name":
234
+ hash_names: AppGetAllAreaHashName = nav_msg[1]
235
+ converted_list = [AreaHashNameList(name=item.name, hash=item.hash) for item in hash_names.hashnames]
236
+ self._device.map.area_name = converted_list
237
+
238
+ case "bidire_reqconver_path":
239
+ work_settings: NavReqCoverPath = nav_msg[1]
240
+
241
+ current_task = CurrentTaskSettings.from_dict(work_settings.to_dict(casing=betterproto2.Casing.SNAKE))
242
+
243
+ if current_task.path_hash == 0:
244
+ self._device.map.current_mow_path = {}
245
+
246
+ if work_settings.sub_cmd == 0:
247
+ await self.queue_command_callback(key="get_all_boundary_hash_list", sub_cmd=3)
248
+
249
+ self._device.work = current_task
250
+
251
+ case "nav_sys_param_cmd":
252
+ settings: NavSysParamMsg = nav_msg[1]
253
+ match settings.id:
254
+ case 3:
255
+ self._device.mower_state.rain_detection = bool(settings.context)
256
+ case 6:
257
+ self._device.mower_state.turning_mode = settings.context
258
+ case 7:
259
+ self._device.mower_state.traversal_mode = settings.context
260
+ case "todev_unable_time_set":
261
+ nav_non_work_time: NavUnableTimeSet = nav_msg[1]
262
+ self._device.non_work_hours.non_work_sub_cmd = nav_non_work_time.sub_cmd
263
+ self._device.non_work_hours.start_time = nav_non_work_time.unable_start_time
264
+ self._device.non_work_hours.end_time = nav_non_work_time.unable_end_time
265
+
266
+ def _update_sys_data(self, message) -> None:
267
+ """Update system."""
268
+ sys_msg = betterproto2.which_one_of(message.sys, "SubSysMsg")
269
+ match sys_msg[0]:
270
+ case "system_update_buf":
271
+ self._device.buffer(sys_msg[1])
272
+ case "toapp_report_data":
273
+ self._device.update_report_data(sys_msg[1])
274
+ case "mow_to_app_info":
275
+ self._device.mow_info(sys_msg[1])
276
+ case "system_tard_state_tunnel":
277
+ self._device.run_state_update(sys_msg[1])
278
+ case "todev_time_ctrl_light":
279
+ ctrl_light: TimeCtrlLight = sys_msg[1]
280
+ side_led: SideLight = SideLight.from_dict(ctrl_light.to_dict(casing=betterproto2.Casing.SNAKE))
281
+ self._device.mower_state.side_led = side_led
282
+ case "device_product_type_info":
283
+ device_product_type: DeviceProductTypeInfoT = sys_msg[1]
284
+ if device_product_type.main_product_type != "" or device_product_type.sub_product_type != "":
285
+ self._device.mower_state.model_id = device_product_type.main_product_type
286
+ self._device.mower_state.sub_model_id = device_product_type.sub_product_type
287
+ case "toapp_dev_fw_info":
288
+ device_fw_info: DeviceFwInfo = sys_msg[1]
289
+ self._device.device_firmwares.device_version = device_fw_info.version
290
+ self._device.mower_state.swversion = device_fw_info.version
291
+
292
+ def _update_driver_data(self, message) -> None:
293
+ """Update driver data."""
294
+ driver_msg = betterproto2.which_one_of(message.driver, "SubDrvMsg")
295
+ match driver_msg[0]:
296
+ case "current_cutter_mode":
297
+ cutter_work_mode: AppGetCutterWorkMode = driver_msg[1]
298
+ self._device.mower_state.cutter_mode = cutter_work_mode.current_cutter_mode
299
+ self._device.mower_state.cutter_rpm = cutter_work_mode.current_cutter_rpm
300
+ case "cutter_mode_ctrl_by_hand":
301
+ cutter_work_mode_set: AppSetCutterWorkMode = driver_msg[1]
302
+ self._device.mower_state.cutter_mode = cutter_work_mode_set.cutter_mode
303
+
304
+ def _update_net_data(self, message) -> None:
305
+ """Update network data."""
306
+ net_msg = betterproto2.which_one_of(message.net, "NetSubType")
307
+ match net_msg[0]:
308
+ case "toapp_wifi_iot_status":
309
+ wifi_iot_status: WifiIotStatusReport = net_msg[1]
310
+ self._device.mower_state.product_key = wifi_iot_status.product_key
311
+ case "toapp_devinfo_resp":
312
+ toapp_devinfo_resp: DrvDevInfoResp = net_msg[1]
313
+ for resp in toapp_devinfo_resp.resp_ids:
314
+ if resp.res == DrvDevInfoResult.DRV_RESULT_SUC and resp.id == 1 and resp.type == 6:
315
+ self._device.mower_state.swversion = resp.info
316
+ self._device.device_firmwares.device_version = resp.info
317
+ case "toapp_networkinfo_rsp":
318
+ get_network_info_resp: GetNetworkInfoRsp = net_msg[1]
319
+ self._device.mower_state.wifi_mac = get_network_info_resp.wifi_mac
320
+
321
+ def _update_mul_data(self, message) -> None:
322
+ """Media and video states."""
323
+ mul_msg = betterproto2.which_one_of(message.mul, "SubMul")
324
+ match mul_msg[0]:
325
+ case "get_lamp_rsp":
326
+ lamp_resp: Getlamprsp = mul_msg[1]
327
+ self._device.mower_state.lamp_info.lamp_bright = lamp_resp.lamp_bright
328
+ if lamp_resp.get_ids in (1126, 1127):
329
+ self._device.mower_state.lamp_info.lamp_bright = lamp_resp.lamp_bright
330
+ self._device.mower_state.lamp_info.manual_light = bool(lamp_resp.lamp_manual_ctrl.value) or bool(
331
+ lamp_resp.lamp_bright
332
+ )
333
+ if lamp_resp.get_ids == 1123:
334
+ self._device.mower_state.lamp_info.lamp_bright = lamp_resp.lamp_bright
335
+ self._device.mower_state.lamp_info.night_light = bool(lamp_resp.lamp_ctrl.value) or bool(
336
+ lamp_resp.lamp_bright
337
+ )
338
+
339
+ def _update_ota_data(self, message) -> None:
340
+ """Update OTA data."""
341
+
342
+ def generate_geojson(self, rtk: LocationPoint, dock: Dock) -> Any:
343
+ """Generate geojson from frames."""
344
+ coordinator_converter = CoordinateConverter(rtk.latitude, rtk.longitude)
345
+ RTK_real_loc = coordinator_converter.enu_to_lla(0, 0)
346
+
347
+ dock_location = coordinator_converter.enu_to_lla(dock.latitude, dock.longitude)
348
+ dock_rotation = coordinator_converter.get_transform_yaw_with_yaw(dock.rotation) + 180
349
+
350
+ self._device.map.generated_geojson = GeojsonGenerator.generate_geojson(
351
+ self._device.map,
352
+ Point(RTK_real_loc.latitude, RTK_real_loc.longitude),
353
+ Point(dock_location.latitude, dock_location.longitude),
354
+ int(dock_rotation),
355
+ )
356
+
357
+ return self._device.map.generated_geojson
358
+
359
+ def generate_mowing_geojson(self, rtk: LocationPoint) -> Any:
360
+ """Generate geojson from frames."""
361
+ coordinator_converter = CoordinateConverter(rtk.latitude, rtk.longitude)
362
+ RTK_real_loc = coordinator_converter.enu_to_lla(0, 0)
363
+
364
+ self._device.map.generated_mow_path_geojson = GeojsonGenerator.generate_mow_path_geojson(
365
+ self._device.map,
366
+ Point(RTK_real_loc.latitude, RTK_real_loc.longitude),
367
+ )
368
+
369
+ return self._device.map.generated_mow_path_geojson
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,227 @@
1
+ from base64 import b64decode
2
+ from dataclasses import dataclass
3
+ from typing import Annotated, Any, Literal
4
+
5
+ from google.protobuf import json_format
6
+ from mashumaro.config import BaseConfig
7
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
8
+ from mashumaro.types import Alias, SerializableType
9
+
10
+ from pymammotion.proto import luba_msg_pb2
11
+
12
+
13
+ class Base64EncodedProtobuf(SerializableType):
14
+ def __init__(self, proto: str) -> None:
15
+ self.proto = proto
16
+
17
+ def _serialize(self):
18
+ return self.proto
19
+
20
+ @classmethod
21
+ def _deserialize(cls, value):
22
+ return cls(*value)
23
+
24
+ @classmethod
25
+ def __get_validators__(cls):
26
+ yield cls.validate
27
+
28
+ @classmethod
29
+ def validate(cls, v: str):
30
+ if not isinstance(v, str):
31
+ raise TypeError("string required")
32
+ binary = b64decode(v, validate=True)
33
+ data = luba_msg_pb2.LubaMsg()
34
+ data.ParseFromString(binary)
35
+ return json_format.MessageToDict(data)
36
+
37
+
38
+ @dataclass
39
+ class DeviceProtobufMsgEventValue(DataClassORJSONMixin):
40
+ content: str
41
+
42
+
43
+ @dataclass
44
+ class DeviceWarningEventValue(DataClassORJSONMixin):
45
+ # TODO: enum for error codes
46
+ # (see resources/res/values-en-rUS/strings.xml in APK)
47
+ code: int
48
+
49
+
50
+ @dataclass
51
+ class DeviceConfigurationRequestValue(DataClassORJSONMixin):
52
+ code: int
53
+ bizId: str
54
+ params: str
55
+
56
+
57
+ @dataclass
58
+ class DeviceNotificationEventCode(DataClassORJSONMixin):
59
+ localTime: int
60
+ code: str
61
+
62
+
63
+ @dataclass
64
+ class DeviceNotificationEventValue(DataClassORJSONMixin):
65
+ data: str # parsed to DeviceNotificationEventCode
66
+
67
+
68
+ @dataclass
69
+ class DeviceBizReqEventValue(DataClassORJSONMixin):
70
+ bizType: str
71
+ bizId: str
72
+ params: str
73
+
74
+
75
+ @dataclass
76
+ class GeneralParams(DataClassORJSONMixin):
77
+ group_id_list: Annotated[list[str], Alias("groupIdList")]
78
+ group_id: Annotated[str, Alias("groupId")]
79
+ category_key: Annotated[Literal["LawnMower", "Tracker"], Alias("categoryKey")]
80
+ batch_id: Annotated[str, Alias("batchId")]
81
+ gmt_create: Annotated[int, Alias("gmtCreate")]
82
+ product_key: Annotated[str, Alias("productKey")]
83
+ type: str
84
+ device_name: Annotated[str, Alias("deviceName")]
85
+ iot_id: Annotated[str, Alias("iotId")]
86
+ check_level: Annotated[int, Alias("checkLevel")]
87
+ namespace: str
88
+ tenant_id: Annotated[str, Alias("tenantId")]
89
+ name: str
90
+ thing_type: Annotated[Literal["DEVICE"], Alias("thingType")]
91
+ time: int
92
+ tenant_instance_id: Annotated[str, Alias("tenantInstanceId")]
93
+ value: Any
94
+
95
+ # Optional fields
96
+ identifier: str | None = None
97
+ check_failed_data: Annotated[dict | None, Alias("checkFailedData")] = None
98
+ _tenant_id: Annotated[str | None, Alias("_tenantId")] = None
99
+ generate_time: Annotated[int | None, Alias("generateTime")] = None
100
+ jmsx_delivery_count: Annotated[int | None, Alias("JMSXDeliveryCount")] = None
101
+ qos: int | None = None
102
+ request_id: Annotated[str | None, Alias("requestId")] = None
103
+ _category_key: Annotated[str | None, Alias("_categoryKey")] = None
104
+ device_type: Annotated[str | None, Alias("deviceType")] = None
105
+ _trace_id: Annotated[str | None, Alias("_traceId")] = None
106
+
107
+ class Config(BaseConfig):
108
+ allow_deserialization_not_by_alias = True
109
+
110
+
111
+ @dataclass
112
+ class DeviceProtobufMsgEventParams(GeneralParams):
113
+ identifier: Literal["device_protobuf_msg_event"]
114
+ type: Literal["info"]
115
+ value: DeviceProtobufMsgEventValue
116
+
117
+
118
+ @dataclass
119
+ class DeviceNotificationEventParams(GeneralParams):
120
+ """Device notification event.
121
+
122
+ {'data': '{"localTime":1725159492000,"code":"1002"}'},
123
+ """
124
+
125
+ identifier: Literal["device_notification_event", "device_information_event", "device_warning_code_event"]
126
+ type: Literal["info"]
127
+ value: DeviceNotificationEventValue
128
+
129
+
130
+ @dataclass
131
+ class DeviceBizReqEventParams(GeneralParams):
132
+ identifier: Literal["device_biz_req_event"]
133
+ type: Literal["info"]
134
+ value: DeviceBizReqEventValue
135
+
136
+
137
+ @dataclass
138
+ class DeviceWarningEventParams(GeneralParams):
139
+ identifier: Literal["device_warning_event"]
140
+ type: Literal["alert"]
141
+ value: DeviceWarningEventValue
142
+
143
+
144
+ @dataclass
145
+ class DeviceConfigurationRequestEvent(GeneralParams):
146
+ type: Literal["info"]
147
+ value: DeviceConfigurationRequestValue
148
+
149
+
150
+ @dataclass
151
+ class DeviceLogProgressEventParams(GeneralParams):
152
+ identifier: Literal["device_log_progress_event"]
153
+ type: Literal["info"]
154
+ value: DeviceNotificationEventValue
155
+
156
+
157
+ @dataclass
158
+ class ThingEventMessage(DataClassORJSONMixin):
159
+ method: Literal["thing.events", "thing.properties"]
160
+ id: str
161
+ params: (
162
+ DeviceProtobufMsgEventParams
163
+ | DeviceWarningEventParams
164
+ | DeviceNotificationEventParams
165
+ | DeviceLogProgressEventParams
166
+ | DeviceBizReqEventParams
167
+ | DeviceConfigurationRequestEvent
168
+ | dict
169
+ )
170
+ version: Literal["1.0"]
171
+
172
+ @classmethod
173
+ def from_dicts(cls, payload: dict) -> "ThingEventMessage":
174
+ """Deserialize payload JSON ThingEventMessage."""
175
+ method = payload.get("method")
176
+ event_id = payload.get("id")
177
+ params_dict = payload.get("params", {})
178
+ version = payload.get("version")
179
+
180
+ identifier = params_dict.get("identifier")
181
+ if identifier is None:
182
+ """Request configuration event."""
183
+ params_obj = DeviceConfigurationRequestEvent.from_dict(params_dict)
184
+ elif identifier == "device_protobuf_msg_event":
185
+ params_obj = DeviceProtobufMsgEventParams.from_dict(params_dict)
186
+ elif identifier == "device_warning_event":
187
+ params_obj = DeviceWarningEventParams.from_dict(params_dict)
188
+ elif identifier == "device_biz_req_event":
189
+ params_obj = DeviceBizReqEventParams.from_dict(params_dict)
190
+ elif identifier == "device_log_progress_event":
191
+ params_obj = DeviceLogProgressEventParams.from_dict(params_dict)
192
+ elif identifier == "device_config_req_event":
193
+ params_obj = payload.get("params", {})
194
+ elif (
195
+ identifier == "device_notification_event"
196
+ or identifier == "device_warning_code_event"
197
+ or identifier == "device_information_event"
198
+ ):
199
+ params_obj = DeviceNotificationEventParams.from_dict(params_dict)
200
+ else:
201
+ raise ValueError(f"Unknown identifier: {identifier} {params_dict}")
202
+
203
+ return cls(method=method, id=event_id, params=params_obj, version=version)
204
+
205
+
206
+ @dataclass
207
+ class MammotionProtoMsgParams(DataClassORJSONMixin, SerializableType):
208
+ value: DeviceProtobufMsgEventValue
209
+ iot_id: str = ""
210
+ product_key: str = ""
211
+ device_name: str = ""
212
+
213
+ @classmethod
214
+ def _deserialize(cls, d: dict[str, Any]) -> "MammotionProtoMsgParams":
215
+ """Override from_dict to allow dict manipulation before conversion."""
216
+ proto: str = d["content"]
217
+
218
+ return cls(value=DeviceProtobufMsgEventValue(content=proto))
219
+
220
+
221
+ @dataclass
222
+ class MammotionEventMessage(DataClassORJSONMixin):
223
+ id: str
224
+ version: str
225
+ sys: dict
226
+ params: MammotionProtoMsgParams
227
+ method: str