pymammotion 0.2.62__py3-none-any.whl → 0.5.51__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.

Potentially problematic release.


This version of pymammotion might be problematic. Click here for more details.

Files changed (135) hide show
  1. pymammotion/__init__.py +9 -6
  2. pymammotion/aliyun/client.py +235 -0
  3. pymammotion/aliyun/cloud_gateway.py +320 -69
  4. pymammotion/aliyun/model/aep_response.py +1 -2
  5. pymammotion/aliyun/model/dev_by_account_response.py +170 -23
  6. pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
  7. pymammotion/aliyun/model/regions_response.py +3 -3
  8. pymammotion/aliyun/model/session_by_authcode_response.py +2 -2
  9. pymammotion/aliyun/model/thing_response.py +12 -0
  10. pymammotion/aliyun/regions.py +62 -0
  11. pymammotion/aliyun/tea/core.py +297 -0
  12. pymammotion/bluetooth/ble.py +11 -15
  13. pymammotion/bluetooth/ble_message.py +389 -106
  14. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  15. pymammotion/const.py +3 -0
  16. pymammotion/data/model/__init__.py +1 -2
  17. pymammotion/data/model/device.py +92 -240
  18. pymammotion/data/model/device_config.py +10 -24
  19. pymammotion/data/model/device_info.py +35 -0
  20. pymammotion/data/model/device_limits.py +49 -0
  21. pymammotion/data/model/enums.py +12 -2
  22. pymammotion/data/model/errors.py +12 -0
  23. pymammotion/data/model/events.py +14 -0
  24. pymammotion/data/model/generate_geojson.py +521 -0
  25. pymammotion/data/model/generate_route_information.py +3 -4
  26. pymammotion/data/model/hash_list.py +384 -48
  27. pymammotion/data/model/location.py +4 -4
  28. pymammotion/data/model/mowing_modes.py +24 -1
  29. pymammotion/data/model/raw_data.py +215 -0
  30. pymammotion/data/model/region_data.py +10 -11
  31. pymammotion/data/model/report_info.py +62 -6
  32. pymammotion/data/model/work.py +27 -0
  33. pymammotion/data/mower_state_manager.py +316 -0
  34. pymammotion/data/mqtt/event.py +73 -28
  35. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  36. pymammotion/data/mqtt/properties.py +93 -78
  37. pymammotion/data/mqtt/status.py +18 -17
  38. pymammotion/event/event.py +32 -8
  39. pymammotion/homeassistant/__init__.py +3 -0
  40. pymammotion/homeassistant/mower_api.py +484 -0
  41. pymammotion/homeassistant/rtk_api.py +54 -0
  42. pymammotion/http/__init__.py +0 -0
  43. pymammotion/http/encryption.py +220 -0
  44. pymammotion/http/http.py +652 -44
  45. pymammotion/http/model/__init__.py +0 -0
  46. pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
  47. pymammotion/http/model/http.py +160 -9
  48. pymammotion/http/model/response_factory.py +61 -0
  49. pymammotion/http/model/rtk.py +16 -0
  50. pymammotion/mammotion/commands/abstract_message.py +7 -5
  51. pymammotion/mammotion/commands/mammotion_command.py +32 -3
  52. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  53. pymammotion/mammotion/commands/messages/driver.py +61 -29
  54. pymammotion/mammotion/commands/messages/media.py +68 -15
  55. pymammotion/mammotion/commands/messages/navigation.py +61 -25
  56. pymammotion/mammotion/commands/messages/network.py +93 -100
  57. pymammotion/mammotion/commands/messages/ota.py +18 -18
  58. pymammotion/mammotion/commands/messages/system.py +97 -72
  59. pymammotion/mammotion/commands/messages/video.py +17 -12
  60. pymammotion/mammotion/devices/__init__.py +27 -3
  61. pymammotion/mammotion/devices/base.py +50 -127
  62. pymammotion/mammotion/devices/mammotion.py +447 -212
  63. pymammotion/mammotion/devices/mammotion_bluetooth.py +105 -60
  64. pymammotion/mammotion/devices/mammotion_cloud.py +157 -105
  65. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  66. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  67. pymammotion/mammotion/devices/managers/managers.py +81 -0
  68. pymammotion/mammotion/devices/mower_device.py +124 -0
  69. pymammotion/mammotion/devices/mower_manager.py +107 -0
  70. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  71. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  72. pymammotion/mammotion/devices/rtk_device.py +50 -0
  73. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  74. pymammotion/mqtt/__init__.py +2 -1
  75. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  76. pymammotion/mqtt/linkkit/__init__.py +5 -0
  77. pymammotion/mqtt/linkkit/h2client.py +585 -0
  78. pymammotion/mqtt/linkkit/linkkit.py +3023 -0
  79. pymammotion/mqtt/mammotion_mqtt.py +176 -169
  80. pymammotion/mqtt/mqtt_models.py +66 -0
  81. pymammotion/proto/__init__.py +4839 -4
  82. pymammotion/proto/basestation.proto +8 -0
  83. pymammotion/proto/basestation_pb2.py +11 -9
  84. pymammotion/proto/basestation_pb2.pyi +16 -2
  85. pymammotion/proto/dev_net.proto +79 -55
  86. pymammotion/proto/dev_net_pb2.py +60 -56
  87. pymammotion/proto/dev_net_pb2.pyi +49 -6
  88. pymammotion/proto/luba_msg.proto +2 -1
  89. pymammotion/proto/luba_msg_pb2.py +6 -6
  90. pymammotion/proto/luba_msg_pb2.pyi +1 -0
  91. pymammotion/proto/luba_mul.proto +62 -1
  92. pymammotion/proto/luba_mul_pb2.py +38 -22
  93. pymammotion/proto/luba_mul_pb2.pyi +94 -7
  94. pymammotion/proto/mctrl_driver.proto +44 -4
  95. pymammotion/proto/mctrl_driver_pb2.py +26 -14
  96. pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
  97. pymammotion/proto/mctrl_nav.proto +97 -51
  98. pymammotion/proto/mctrl_nav_pb2.py +75 -67
  99. pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
  100. pymammotion/proto/mctrl_ota.proto +40 -2
  101. pymammotion/proto/mctrl_ota_pb2.py +23 -13
  102. pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
  103. pymammotion/proto/mctrl_pept.proto +8 -3
  104. pymammotion/proto/mctrl_pept_pb2.py +8 -6
  105. pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
  106. pymammotion/proto/mctrl_sys.proto +325 -86
  107. pymammotion/proto/mctrl_sys_pb2.py +162 -98
  108. pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
  109. pymammotion/proto/message_pool.py +3 -0
  110. pymammotion/proto/py.typed +0 -0
  111. pymammotion/utility/constant/device_constant.py +65 -21
  112. pymammotion/utility/datatype_converter.py +13 -12
  113. pymammotion/utility/device_config.py +755 -0
  114. pymammotion/utility/device_type.py +218 -21
  115. pymammotion/utility/map.py +238 -51
  116. pymammotion/utility/mur_mur_hash.py +159 -0
  117. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/METADATA +27 -31
  118. pymammotion-0.5.51.dist-info/RECORD +152 -0
  119. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
  120. pymammotion/aliyun/cloud_service.py +0 -65
  121. pymammotion/data/model/plan.py +0 -58
  122. pymammotion/data/state_manager.py +0 -130
  123. pymammotion/proto/basestation.py +0 -59
  124. pymammotion/proto/common.py +0 -12
  125. pymammotion/proto/dev_net.py +0 -381
  126. pymammotion/proto/luba_msg.py +0 -81
  127. pymammotion/proto/luba_mul.py +0 -76
  128. pymammotion/proto/mctrl_driver.py +0 -100
  129. pymammotion/proto/mctrl_nav.py +0 -660
  130. pymammotion/proto/mctrl_ota.py +0 -48
  131. pymammotion/proto/mctrl_pept.py +0 -41
  132. pymammotion/proto/mctrl_sys.py +0 -574
  133. pymammotion-0.2.62.dist-info/RECORD +0 -125
  134. /pymammotion/{http/_init_.py → bluetooth/model/__init__.py} +0 -0
  135. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,316 @@
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
+
10
+ from pymammotion.data.model.device import MowingDevice
11
+ from pymammotion.data.model.device_info import SideLight
12
+ from pymammotion.data.model.hash_list import (
13
+ AreaHashNameList,
14
+ MowPath,
15
+ NavGetCommData,
16
+ NavGetHashListData,
17
+ Plan,
18
+ SvgMessage,
19
+ )
20
+ from pymammotion.data.model.work import CurrentTaskSettings
21
+ from pymammotion.data.mqtt.event import ThingEventMessage
22
+ from pymammotion.data.mqtt.properties import ThingPropertiesMessage
23
+ from pymammotion.data.mqtt.status import ThingStatusMessage
24
+ from pymammotion.event.event import DataEvent
25
+ from pymammotion.proto import (
26
+ AppGetAllAreaHashName,
27
+ AppGetCutterWorkMode,
28
+ AppSetCutterWorkMode,
29
+ CoverPathUploadT,
30
+ DeviceFwInfo,
31
+ DeviceProductTypeInfoT,
32
+ DrvDevInfoResp,
33
+ DrvDevInfoResult,
34
+ Getlamprsp,
35
+ GetNetworkInfoRsp,
36
+ LubaMsg,
37
+ NavGetCommDataAck,
38
+ NavGetHashListAck,
39
+ NavPlanJobSet,
40
+ NavReqCoverPath,
41
+ NavSysParamMsg,
42
+ NavUnableTimeSet,
43
+ SvgMessageAckT,
44
+ TimeCtrlLight,
45
+ WifiIotStatusReport,
46
+ )
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ class MowerStateManager:
52
+ """Manage state."""
53
+
54
+ def __init__(self, device: MowingDevice) -> None:
55
+ """Initialize state manager with a device."""
56
+ self._device: MowingDevice = device
57
+ self.last_updated_at = datetime.now(UTC)
58
+ self.cloud_gethash_ack_callback: Callable[[NavGetHashListAck], Awaitable[None]] | None = None
59
+ self.cloud_get_commondata_ack_callback: (
60
+ Callable[[NavGetCommDataAck | SvgMessageAckT], Awaitable[None]] | None
61
+ ) = None
62
+ self.cloud_on_notification_callback = DataEvent()
63
+ self.cloud_queue_command_callback = DataEvent()
64
+
65
+ self.cloud_get_plan_callback: Callable[[NavPlanJobSet], Awaitable[None]] | None = None
66
+ self.ble_gethash_ack_callback: Callable[[NavGetHashListAck], Awaitable[None]] | None = None
67
+ self.ble_get_commondata_ack_callback: Callable[[NavGetCommDataAck | SvgMessageAckT], Awaitable[None]] | None = (
68
+ None
69
+ )
70
+ self.ble_get_plan_callback: Callable[[NavPlanJobSet], Awaitable[None]] | None = None
71
+ self.ble_on_notification_callback = DataEvent()
72
+ self.ble_queue_command_callback = DataEvent()
73
+
74
+ self.properties_callback = DataEvent()
75
+ self.status_callback = DataEvent()
76
+ self.device_event_callback = DataEvent()
77
+
78
+ def get_device(self) -> MowingDevice:
79
+ """Get device."""
80
+ return self._device
81
+
82
+ def set_device(self, device: MowingDevice) -> None:
83
+ """Set device."""
84
+ self._device = device
85
+
86
+ async def properties(self, thing_properties: ThingPropertiesMessage) -> None:
87
+ """Update device properties and invoke callback."""
88
+ # TODO update device based off thing properties
89
+ self._device.mqtt_properties = thing_properties
90
+ await self.on_properties_callback(thing_properties)
91
+
92
+ async def status(self, thing_status: ThingStatusMessage) -> None:
93
+ """Update device status and invoke callback."""
94
+ if not self._device.online:
95
+ self._device.online = True
96
+ self._device.status_properties = thing_status
97
+ if self._device.mower_state.product_key == "":
98
+ self._device.mower_state.product_key = thing_status.params.productKey
99
+ await self.on_status_callback(thing_status)
100
+
101
+ async def device_event(self, device_event: ThingEventMessage) -> None:
102
+ """Sets MQTT event and calls callback."""
103
+ self._device.mqtt_device_event = device_event
104
+ await self.on_device_event_callback(device_event)
105
+
106
+ @property
107
+ def online(self) -> bool:
108
+ """Return online status."""
109
+ return self._device.online
110
+
111
+ @online.setter
112
+ def online(self, value: bool) -> None:
113
+ """Set online status."""
114
+ self._device.online = value
115
+
116
+ async def gethash_ack_callback(self, msg: NavGetHashListAck) -> None:
117
+ """Dispatch hash list acknowledgment to available callback."""
118
+ if self.cloud_gethash_ack_callback:
119
+ await self.cloud_gethash_ack_callback(msg)
120
+ elif self.ble_gethash_ack_callback:
121
+ await self.ble_gethash_ack_callback(msg)
122
+
123
+ async def on_notification_callback(self, res: tuple[str, Any | None]) -> None:
124
+ """Dispatch notification to available callback."""
125
+ if self.cloud_on_notification_callback:
126
+ await self.cloud_on_notification_callback.data_event(res)
127
+ elif self.ble_on_notification_callback:
128
+ await self.ble_on_notification_callback.data_event(res)
129
+
130
+ async def on_properties_callback(self, thing_properties: ThingPropertiesMessage) -> None:
131
+ """Call properties callback if it exists."""
132
+ if self.properties_callback:
133
+ await self.properties_callback.data_event(thing_properties)
134
+
135
+ async def on_status_callback(self, thing_status: ThingStatusMessage) -> None:
136
+ """Execute the status callback if it is set."""
137
+ if self.status_callback:
138
+ await self.status_callback.data_event(thing_status)
139
+
140
+ async def on_device_event_callback(self, device_event: ThingEventMessage) -> None:
141
+ """Executes the event callback if it is set."""
142
+ if self.device_event_callback:
143
+ await self.device_event_callback.data_event(device_event)
144
+
145
+ async def get_commondata_ack_callback(self, comm_data: NavGetCommDataAck | SvgMessageAckT) -> None:
146
+ """Asynchronously calls the appropriate callback based on available handlers."""
147
+ if self.cloud_get_commondata_ack_callback:
148
+ await self.cloud_get_commondata_ack_callback(comm_data)
149
+ elif self.ble_get_commondata_ack_callback:
150
+ await self.ble_get_commondata_ack_callback(comm_data)
151
+
152
+ async def get_plan_callback(self, planjob: NavPlanJobSet) -> None:
153
+ """Dispatch plan job to available callback."""
154
+ if self.cloud_get_plan_callback:
155
+ await self.cloud_get_plan_callback(planjob)
156
+ elif self.ble_get_plan_callback:
157
+ await self.ble_get_plan_callback(planjob)
158
+
159
+ async def notification(self, message: LubaMsg) -> None:
160
+ """Handle protobuf notifications."""
161
+ res = betterproto2.which_one_of(message, "LubaSubMsg")
162
+ self.last_updated_at = datetime.now(UTC)
163
+ # additional catch all if we don't get a status update
164
+ if not self._device.online:
165
+ self._device.online = True
166
+
167
+ match res[0]:
168
+ case "nav":
169
+ await self._update_nav_data(message)
170
+ case "sys":
171
+ self._update_sys_data(message)
172
+ case "driver":
173
+ self._update_driver_data(message)
174
+ case "net":
175
+ self._update_net_data(message)
176
+ case "mul":
177
+ self._update_mul_data(message)
178
+ case "ota":
179
+ self._update_ota_data(message)
180
+
181
+ await self.on_notification_callback(res)
182
+
183
+ async def _update_nav_data(self, message: LubaMsg) -> None:
184
+ """Update nav data."""
185
+ nav_msg = betterproto2.which_one_of(message.nav, "SubNavMsg")
186
+ match nav_msg[0]:
187
+ case "toapp_gethash_ack":
188
+ hashlist_ack: NavGetHashListAck = nav_msg[1]
189
+ self._device.map.update_root_hash_list(
190
+ NavGetHashListData.from_dict(hashlist_ack.to_dict(casing=betterproto2.Casing.SNAKE))
191
+ )
192
+ await self.gethash_ack_callback(nav_msg[1])
193
+ case "toapp_get_commondata_ack":
194
+ common_data: NavGetCommDataAck = nav_msg[1]
195
+ updated = self._device.map.update(
196
+ NavGetCommData.from_dict(common_data.to_dict(casing=betterproto2.Casing.SNAKE))
197
+ )
198
+ if updated:
199
+ await self.get_commondata_ack_callback(common_data)
200
+ case "cover_path_upload":
201
+ mow_path: CoverPathUploadT = nav_msg[1]
202
+ self._device.map.update_mow_path(MowPath.from_dict(mow_path.to_dict(casing=betterproto2.Casing.SNAKE)))
203
+
204
+ case "todev_planjob_set":
205
+ planjob: NavPlanJobSet = nav_msg[1]
206
+ self._device.map.update_plan(Plan.from_dict(planjob.to_dict(casing=betterproto2.Casing.SNAKE)))
207
+ await self.get_plan_callback(planjob)
208
+
209
+ case "toapp_svg_msg":
210
+ common_svg_data: SvgMessageAckT = nav_msg[1]
211
+ updated = self._device.map.update(
212
+ SvgMessage.from_dict(common_svg_data.to_dict(casing=betterproto2.Casing.SNAKE))
213
+ )
214
+ if updated:
215
+ await self.get_commondata_ack_callback(common_svg_data)
216
+
217
+ case "toapp_all_hash_name":
218
+ hash_names: AppGetAllAreaHashName = nav_msg[1]
219
+ converted_list = [AreaHashNameList(name=item.name, hash=item.hash) for item in hash_names.hashnames]
220
+ self._device.map.area_name = converted_list
221
+
222
+ case "bidire_reqconver_path":
223
+ work_settings: NavReqCoverPath = nav_msg[1]
224
+ self._device.work = CurrentTaskSettings.from_dict(
225
+ work_settings.to_dict(casing=betterproto2.Casing.SNAKE)
226
+ )
227
+ case "nav_sys_param_cmd":
228
+ settings: NavSysParamMsg = nav_msg[1]
229
+ match settings.id:
230
+ case 3:
231
+ self._device.mower_state.rain_detection = bool(settings.context)
232
+ case 6:
233
+ self._device.mower_state.turning_mode = settings.context
234
+ case 7:
235
+ self._device.mower_state.traversal_mode = settings.context
236
+ case "todev_unable_time_set":
237
+ nav_non_work_time: NavUnableTimeSet = nav_msg[1]
238
+ self._device.non_work_hours.non_work_sub_cmd = nav_non_work_time.sub_cmd
239
+ self._device.non_work_hours.start_time = nav_non_work_time.unable_start_time
240
+ self._device.non_work_hours.end_time = nav_non_work_time.unable_end_time
241
+
242
+ def _update_sys_data(self, message) -> None:
243
+ """Update system."""
244
+ sys_msg = betterproto2.which_one_of(message.sys, "SubSysMsg")
245
+ match sys_msg[0]:
246
+ case "system_update_buf":
247
+ self._device.buffer(sys_msg[1])
248
+ case "toapp_report_data":
249
+ self._device.update_report_data(sys_msg[1])
250
+ case "mow_to_app_info":
251
+ self._device.mow_info(sys_msg[1])
252
+ case "system_tard_state_tunnel":
253
+ self._device.run_state_update(sys_msg[1])
254
+ case "todev_time_ctrl_light":
255
+ ctrl_light: TimeCtrlLight = sys_msg[1]
256
+ side_led: SideLight = SideLight.from_dict(ctrl_light.to_dict(casing=betterproto2.Casing.SNAKE))
257
+ self._device.mower_state.side_led = side_led
258
+ case "device_product_type_info":
259
+ device_product_type: DeviceProductTypeInfoT = sys_msg[1]
260
+ if device_product_type.main_product_type != "" or device_product_type.sub_product_type != "":
261
+ self._device.mower_state.model_id = device_product_type.main_product_type
262
+ self._device.mower_state.sub_model_id = device_product_type.sub_product_type
263
+ case "toapp_dev_fw_info":
264
+ device_fw_info: DeviceFwInfo = sys_msg[1]
265
+ self._device.device_firmwares.device_version = device_fw_info.version
266
+ self._device.mower_state.swversion = device_fw_info.version
267
+
268
+ def _update_driver_data(self, message) -> None:
269
+ """Update driver data."""
270
+ driver_msg = betterproto2.which_one_of(message.driver, "SubDrvMsg")
271
+ match driver_msg[0]:
272
+ case "current_cutter_mode":
273
+ cutter_work_mode: AppGetCutterWorkMode = driver_msg[1]
274
+ self._device.mower_state.cutter_mode = cutter_work_mode.current_cutter_mode
275
+ self._device.mower_state.cutter_rpm = cutter_work_mode.current_cutter_rpm
276
+ case "cutter_mode_ctrl_by_hand":
277
+ cutter_work_mode_set: AppSetCutterWorkMode = driver_msg[1]
278
+ self._device.mower_state.cutter_mode = cutter_work_mode_set.cutter_mode
279
+
280
+ def _update_net_data(self, message) -> None:
281
+ """Update network data."""
282
+ net_msg = betterproto2.which_one_of(message.net, "NetSubType")
283
+ match net_msg[0]:
284
+ case "toapp_wifi_iot_status":
285
+ wifi_iot_status: WifiIotStatusReport = net_msg[1]
286
+ self._device.mower_state.product_key = wifi_iot_status.productkey
287
+ case "toapp_devinfo_resp":
288
+ toapp_devinfo_resp: DrvDevInfoResp = net_msg[1]
289
+ for resp in toapp_devinfo_resp.resp_ids:
290
+ if resp.res == DrvDevInfoResult.DRV_RESULT_SUC and resp.id == 1 and resp.type == 6:
291
+ self._device.mower_state.swversion = resp.info
292
+ self._device.device_firmwares.device_version = resp.info
293
+ case "toapp_networkinfo_rsp":
294
+ get_network_info_resp: GetNetworkInfoRsp = net_msg[1]
295
+ self._device.mower_state.wifi_mac = get_network_info_resp.wifi_mac
296
+
297
+ def _update_mul_data(self, message) -> None:
298
+ """Media and video states."""
299
+ mul_msg = betterproto2.which_one_of(message.mul, "SubMul")
300
+ match mul_msg[0]:
301
+ case "get_lamp_rsp":
302
+ lamp_resp: Getlamprsp = mul_msg[1]
303
+ self._device.mower_state.lamp_info.lamp_bright = lamp_resp.lamp_bright
304
+ if lamp_resp.get_ids in (1126, 1127):
305
+ self._device.mower_state.lamp_info.lamp_bright = lamp_resp.lamp_bright
306
+ self._device.mower_state.lamp_info.manual_light = bool(lamp_resp.lamp_manual_ctrl.value) or bool(
307
+ lamp_resp.lamp_bright
308
+ )
309
+ if lamp_resp.get_ids == 1123:
310
+ self._device.mower_state.lamp_info.lamp_bright = lamp_resp.lamp_bright
311
+ self._device.mower_state.lamp_info.night_light = bool(lamp_resp.lamp_ctrl.value) or bool(
312
+ lamp_resp.lamp_bright
313
+ )
314
+
315
+ def _update_ota_data(self, message) -> None:
316
+ """Update OTA data."""
@@ -1,10 +1,10 @@
1
1
  from base64 import b64decode
2
2
  from dataclasses import dataclass
3
- from typing import Any, Literal, Optional, Union
3
+ from typing import Annotated, Any, Literal
4
4
 
5
5
  from google.protobuf import json_format
6
6
  from mashumaro.mixins.orjson import DataClassORJSONMixin
7
- from mashumaro.types import SerializableType
7
+ from mashumaro.types import Alias, SerializableType
8
8
 
9
9
  from pymammotion.proto import luba_msg_pb2
10
10
 
@@ -73,34 +73,35 @@ class DeviceBizReqEventValue(DataClassORJSONMixin):
73
73
 
74
74
  @dataclass
75
75
  class GeneralParams(DataClassORJSONMixin):
76
- groupIdList: list[str]
77
- groupId: str
78
- categoryKey: Literal["LawnMower"]
79
- batchId: str
80
- gmtCreate: int
81
- productKey: str
76
+ group_id_list: Annotated[list[str], Alias("groupIdList")]
77
+ group_id: Annotated[str, Alias("groupId")]
78
+ category_key: Annotated[Literal["LawnMower", "Tracker"], Alias("categoryKey")]
79
+ batch_id: Annotated[str, Alias("batchId")]
80
+ gmt_create: Annotated[int, Alias("gmtCreate")]
81
+ product_key: Annotated[str, Alias("productKey")]
82
82
  type: str
83
- deviceName: str
84
- iotId: str
85
- checkLevel: int
83
+ device_name: Annotated[str, Alias("deviceName")]
84
+ iot_id: Annotated[str, Alias("iotId")]
85
+ check_level: Annotated[int, Alias("checkLevel")]
86
86
  namespace: str
87
- tenantId: str
87
+ tenant_id: Annotated[str, Alias("tenantId")]
88
88
  name: str
89
- thingType: Literal["DEVICE"]
89
+ thing_type: Annotated[Literal["DEVICE"], Alias("thingType")]
90
90
  time: int
91
- tenantInstanceId: str
91
+ tenant_instance_id: Annotated[str, Alias("tenantInstanceId")]
92
92
  value: Any
93
93
 
94
- identifier: Optional[str] = None
95
- checkFailedData: Optional[dict] = None
96
- _tenantId: Optional[str] = None
97
- generateTime: Optional[int] = None
98
- JMSXDeliveryCount: Optional[int] = None
99
- qos: Optional[int] = None
100
- requestId: Optional[str] = None
101
- _categoryKey: Optional[str] = None
102
- deviceType: Optional[str] = None
103
- _traceId: Optional[str] = None
94
+ # Optional fields
95
+ identifier: str | None = None
96
+ check_failed_data: Annotated[dict | None, Alias("checkFailedData")] = None
97
+ _tenant_id: Annotated[str | None, Alias("_tenantId")] = None
98
+ generate_time: Annotated[int | None, Alias("generateTime")] = None
99
+ jmsx_delivery_count: Annotated[int | None, Alias("JMSXDeliveryCount")] = None
100
+ qos: int | None = None
101
+ request_id: Annotated[str | None, Alias("requestId")] = None
102
+ _category_key: Annotated[str | None, Alias("_categoryKey")] = None
103
+ device_type: Annotated[str | None, Alias("deviceType")] = None
104
+ _trace_id: Annotated[str | None, Alias("_traceId")] = None
104
105
 
105
106
 
106
107
  @dataclass
@@ -117,7 +118,7 @@ class DeviceNotificationEventParams(GeneralParams):
117
118
  {'data': '{"localTime":1725159492000,"code":"1002"}'},
118
119
  """
119
120
 
120
- identifier: Literal["device_notification_event", "device_warning_code_event"]
121
+ identifier: Literal["device_notification_event", "device_information_event", "device_warning_code_event"]
121
122
  type: Literal["info"]
122
123
  value: DeviceNotificationEventValue
123
124
 
@@ -142,11 +143,26 @@ class DeviceConfigurationRequestEvent(GeneralParams):
142
143
  value: DeviceConfigurationRequestValue
143
144
 
144
145
 
146
+ @dataclass
147
+ class DeviceLogProgressEventParams(GeneralParams):
148
+ identifier: Literal["device_log_progress_event"]
149
+ type: Literal["info"]
150
+ value: DeviceNotificationEventValue
151
+
152
+
145
153
  @dataclass
146
154
  class ThingEventMessage(DataClassORJSONMixin):
147
155
  method: Literal["thing.events", "thing.properties"]
148
156
  id: str
149
- params: Union[DeviceProtobufMsgEventParams, DeviceWarningEventParams, dict]
157
+ params: (
158
+ DeviceProtobufMsgEventParams
159
+ | DeviceWarningEventParams
160
+ | DeviceNotificationEventParams
161
+ | DeviceLogProgressEventParams
162
+ | DeviceBizReqEventParams
163
+ | DeviceConfigurationRequestEvent
164
+ | dict
165
+ )
150
166
  version: Literal["1.0"]
151
167
 
152
168
  @classmethod
@@ -157,7 +173,6 @@ class ThingEventMessage(DataClassORJSONMixin):
157
173
  params_dict = payload.get("params", {})
158
174
  version = payload.get("version")
159
175
 
160
- # Determina quale classe usare per i parametri
161
176
  identifier = params_dict.get("identifier")
162
177
  if identifier is None:
163
178
  """Request configuration event."""
@@ -168,11 +183,41 @@ class ThingEventMessage(DataClassORJSONMixin):
168
183
  params_obj = DeviceWarningEventParams.from_dict(params_dict)
169
184
  elif identifier == "device_biz_req_event":
170
185
  params_obj = DeviceBizReqEventParams.from_dict(params_dict)
186
+ elif identifier == "device_log_progress_event":
187
+ params_obj = DeviceLogProgressEventParams.from_dict(params_dict)
171
188
  elif identifier == "device_config_req_event":
172
189
  params_obj = payload.get("params", {})
173
- elif identifier == "device_notification_event" or identifier == "device_warning_code_event":
190
+ elif (
191
+ identifier == "device_notification_event"
192
+ or identifier == "device_warning_code_event"
193
+ or identifier == "device_information_event"
194
+ ):
174
195
  params_obj = DeviceNotificationEventParams.from_dict(params_dict)
175
196
  else:
176
197
  raise ValueError(f"Unknown identifier: {identifier} {params_dict}")
177
198
 
178
199
  return cls(method=method, id=event_id, params=params_obj, version=version)
200
+
201
+
202
+ @dataclass
203
+ class MammotionProtoMsgParams(DataClassORJSONMixin, SerializableType):
204
+ value: DeviceProtobufMsgEventValue
205
+ iot_id: str = ""
206
+ product_key: str = ""
207
+ device_name: str = ""
208
+
209
+ @classmethod
210
+ def _deserialize(cls, d: dict[str, Any]) -> "MammotionProtoMsgParams":
211
+ """Override from_dict to allow dict manipulation before conversion."""
212
+ proto: str = d["content"]
213
+
214
+ return cls(value=DeviceProtobufMsgEventValue(content=proto))
215
+
216
+
217
+ @dataclass
218
+ class MammotionEventMessage(DataClassORJSONMixin):
219
+ id: str
220
+ version: str
221
+ sys: dict
222
+ params: MammotionProtoMsgParams
223
+ method: str