pymammotion 0.4.0a2__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 (133) hide show
  1. pymammotion/__init__.py +5 -4
  2. pymammotion/aliyun/client.py +235 -0
  3. pymammotion/aliyun/cloud_gateway.py +312 -64
  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 +7 -9
  13. pymammotion/bluetooth/ble_message.py +10 -14
  14. pymammotion/const.py +3 -0
  15. pymammotion/data/model/__init__.py +1 -2
  16. pymammotion/data/model/device.py +95 -27
  17. pymammotion/data/model/device_config.py +4 -4
  18. pymammotion/data/model/device_info.py +35 -0
  19. pymammotion/data/model/device_limits.py +10 -10
  20. pymammotion/data/model/enums.py +12 -2
  21. pymammotion/data/model/errors.py +12 -0
  22. pymammotion/data/model/events.py +14 -0
  23. pymammotion/data/model/generate_geojson.py +521 -0
  24. pymammotion/data/model/generate_route_information.py +2 -2
  25. pymammotion/data/model/hash_list.py +370 -57
  26. pymammotion/data/model/location.py +4 -4
  27. pymammotion/data/model/mowing_modes.py +17 -1
  28. pymammotion/data/model/raw_data.py +2 -10
  29. pymammotion/data/model/region_data.py +10 -11
  30. pymammotion/data/model/report_info.py +31 -5
  31. pymammotion/data/model/work.py +27 -0
  32. pymammotion/data/mower_state_manager.py +316 -0
  33. pymammotion/data/mqtt/event.py +73 -28
  34. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  35. pymammotion/data/mqtt/properties.py +93 -78
  36. pymammotion/data/mqtt/status.py +18 -17
  37. pymammotion/event/event.py +27 -6
  38. pymammotion/homeassistant/__init__.py +3 -0
  39. pymammotion/homeassistant/mower_api.py +484 -0
  40. pymammotion/homeassistant/rtk_api.py +54 -0
  41. pymammotion/http/encryption.py +5 -6
  42. pymammotion/http/http.py +574 -28
  43. pymammotion/http/model/__init__.py +0 -0
  44. pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
  45. pymammotion/http/model/http.py +129 -4
  46. pymammotion/http/model/response_factory.py +61 -0
  47. pymammotion/http/model/rtk.py +16 -0
  48. pymammotion/mammotion/commands/abstract_message.py +7 -5
  49. pymammotion/mammotion/commands/mammotion_command.py +30 -1
  50. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  51. pymammotion/mammotion/commands/messages/driver.py +61 -29
  52. pymammotion/mammotion/commands/messages/media.py +68 -15
  53. pymammotion/mammotion/commands/messages/navigation.py +61 -25
  54. pymammotion/mammotion/commands/messages/network.py +17 -23
  55. pymammotion/mammotion/commands/messages/ota.py +18 -18
  56. pymammotion/mammotion/commands/messages/system.py +32 -49
  57. pymammotion/mammotion/commands/messages/video.py +15 -16
  58. pymammotion/mammotion/devices/__init__.py +27 -3
  59. pymammotion/mammotion/devices/base.py +40 -131
  60. pymammotion/mammotion/devices/mammotion.py +436 -201
  61. pymammotion/mammotion/devices/mammotion_bluetooth.py +57 -47
  62. pymammotion/mammotion/devices/mammotion_cloud.py +134 -105
  63. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  64. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  65. pymammotion/mammotion/devices/managers/managers.py +81 -0
  66. pymammotion/mammotion/devices/mower_device.py +124 -0
  67. pymammotion/mammotion/devices/mower_manager.py +107 -0
  68. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  69. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  70. pymammotion/mammotion/devices/rtk_device.py +50 -0
  71. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  72. pymammotion/mqtt/__init__.py +2 -1
  73. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  74. pymammotion/mqtt/linkkit/__init__.py +5 -0
  75. pymammotion/mqtt/linkkit/h2client.py +585 -0
  76. pymammotion/mqtt/linkkit/linkkit.py +3023 -0
  77. pymammotion/mqtt/mammotion_mqtt.py +176 -169
  78. pymammotion/mqtt/mqtt_models.py +66 -0
  79. pymammotion/proto/__init__.py +4839 -4
  80. pymammotion/proto/basestation.proto +8 -0
  81. pymammotion/proto/basestation_pb2.py +11 -9
  82. pymammotion/proto/basestation_pb2.pyi +16 -2
  83. pymammotion/proto/dev_net.proto +79 -55
  84. pymammotion/proto/dev_net_pb2.py +60 -56
  85. pymammotion/proto/dev_net_pb2.pyi +49 -6
  86. pymammotion/proto/luba_msg.proto +2 -1
  87. pymammotion/proto/luba_msg_pb2.py +6 -6
  88. pymammotion/proto/luba_msg_pb2.pyi +1 -0
  89. pymammotion/proto/luba_mul.proto +62 -1
  90. pymammotion/proto/luba_mul_pb2.py +38 -22
  91. pymammotion/proto/luba_mul_pb2.pyi +94 -7
  92. pymammotion/proto/mctrl_driver.proto +44 -4
  93. pymammotion/proto/mctrl_driver_pb2.py +26 -14
  94. pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
  95. pymammotion/proto/mctrl_nav.proto +93 -52
  96. pymammotion/proto/mctrl_nav_pb2.py +75 -67
  97. pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
  98. pymammotion/proto/mctrl_ota.proto +40 -2
  99. pymammotion/proto/mctrl_ota_pb2.py +23 -13
  100. pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
  101. pymammotion/proto/mctrl_pept.proto +8 -3
  102. pymammotion/proto/mctrl_pept_pb2.py +8 -6
  103. pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
  104. pymammotion/proto/mctrl_sys.proto +325 -86
  105. pymammotion/proto/mctrl_sys_pb2.py +162 -98
  106. pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
  107. pymammotion/proto/message_pool.py +3 -0
  108. pymammotion/proto/py.typed +0 -0
  109. pymammotion/utility/constant/device_constant.py +29 -5
  110. pymammotion/utility/datatype_converter.py +13 -12
  111. pymammotion/utility/device_config.py +522 -130
  112. pymammotion/utility/device_type.py +218 -21
  113. pymammotion/utility/map.py +238 -51
  114. pymammotion/utility/mur_mur_hash.py +159 -0
  115. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info}/METADATA +26 -31
  116. pymammotion-0.5.51.dist-info/RECORD +152 -0
  117. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
  118. pymammotion/aliyun/cloud_service.py +0 -65
  119. pymammotion/data/model/plan.py +0 -58
  120. pymammotion/data/state_manager.py +0 -129
  121. pymammotion/proto/basestation.py +0 -59
  122. pymammotion/proto/common.py +0 -12
  123. pymammotion/proto/dev_net.py +0 -381
  124. pymammotion/proto/luba_msg.py +0 -81
  125. pymammotion/proto/luba_mul.py +0 -76
  126. pymammotion/proto/mctrl_driver.py +0 -100
  127. pymammotion/proto/mctrl_nav.py +0 -664
  128. pymammotion/proto/mctrl_ota.py +0 -48
  129. pymammotion/proto/mctrl_pept.py +0 -41
  130. pymammotion/proto/mctrl_sys.py +0 -574
  131. pymammotion-0.4.0a2.dist-info/RECORD +0 -131
  132. /pymammotion/http/{_init_.py → __init__.py} +0 -0
  133. {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info/licenses}/LICENSE +0 -0
@@ -1,14 +1,27 @@
1
1
  from dataclasses import dataclass, field
2
+ from enum import StrEnum
2
3
 
3
4
  from mashumaro.mixins.orjson import DataClassORJSONMixin
4
5
 
5
6
 
7
+ class NetUsedType(StrEnum):
8
+ NONE = "NONE"
9
+ WIFI = "WIFI"
10
+ MNET = "MNET"
11
+
12
+
6
13
  @dataclass
7
14
  class ConnectData(DataClassORJSONMixin):
8
15
  connect_type: int = 0
9
16
  ble_rssi: int = 0
10
17
  wifi_rssi: int = 0
11
- used_net: str = ""
18
+ link_type: int = 0
19
+ mnet_rssi: int = 0
20
+ mnet_inet: int = 0
21
+ used_net: str = "NONE"
22
+
23
+
24
+ # mnet_cfg:
12
25
 
13
26
 
14
27
  @dataclass
@@ -28,6 +41,11 @@ class LockStateT(DataClassORJSONMixin):
28
41
  lock_state: int = 0
29
42
 
30
43
 
44
+ @dataclass
45
+ class VioSurvivalInfo(DataClassORJSONMixin):
46
+ vio_survival_distance: float = 0.0
47
+
48
+
31
49
  @dataclass
32
50
  class DeviceData(DataClassORJSONMixin):
33
51
  sys_status: int = 0
@@ -35,10 +53,11 @@ class DeviceData(DataClassORJSONMixin):
35
53
  battery_val: int = 0
36
54
  sensor_status: int = 0
37
55
  last_status: int = 0
38
- vslam_status: int = 0
39
56
  sys_time_stamp: str = ""
40
- collector_status: CollectorStatus = field(default_factory=CollectorStatus)
57
+ vslam_status: int = 0
41
58
  mnet_info: MnetInfo = field(default_factory=MnetInfo)
59
+ vio_survival_info: VioSurvivalInfo = field(default_factory=VioSurvivalInfo)
60
+ collector_status: CollectorStatus = field(default_factory=CollectorStatus)
42
61
  lock_state: LockStateT = field(default_factory=LockStateT)
43
62
 
44
63
 
@@ -60,8 +79,15 @@ class LocationData(DataClassORJSONMixin):
60
79
  bol_hash: str = ""
61
80
 
62
81
 
82
+ @dataclass
83
+ class BladeUsed(DataClassORJSONMixin):
84
+ blade_used_time: int = 0
85
+ blade_used_warn_time: int = 0
86
+
87
+
63
88
  @dataclass
64
89
  class Maintain(DataClassORJSONMixin):
90
+ blade_used_time: BladeUsed = field(default_factory=BladeUsed)
65
91
  mileage: int = 0
66
92
  work_time: int = 0
67
93
  bat_cycles: int = 0
@@ -73,7 +99,7 @@ class VisionInfo(DataClassORJSONMixin):
73
99
  y: float = 0.0
74
100
  heading: float = 0.0
75
101
  vio_state: int = 0
76
- brightness: int = 1
102
+ brightness: int = 0
77
103
  detect_feature_num: int = 0
78
104
  track_feature_num: int = 0
79
105
 
@@ -121,6 +147,6 @@ class ReportData(DataClassORJSONMixin):
121
147
  self.dev = DeviceData.from_dict(data.get("dev", self.dev.to_dict()))
122
148
  self.rtk = RTKData.from_dict(data.get("rtk", self.rtk.to_dict()))
123
149
  self.maintenance = Maintain.from_dict(data.get("maintain", self.maintenance.to_dict()))
124
- self.vision_info = VisionInfo.from_dict(data.get("vio_to_app_info", self.vision_info.to_dict()))
150
+ self.vision_info = VisionInfo.from_dict(data.get("vio_to_app_info", VisionInfo().to_dict()))
125
151
  self.locations = locations
126
152
  self.work = WorkData.from_dict(data.get("work", self.work.to_dict()))
@@ -0,0 +1,27 @@
1
+ """bidire_reqconver_path as a model."""
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
6
+
7
+
8
+ @dataclass
9
+ class CurrentTaskSettings(DataClassORJSONMixin):
10
+ pver: int = 0
11
+ job_id: int = 0
12
+ job_ver: int = 0
13
+ job_mode: int = 0
14
+ sub_cmd: int = 0
15
+ edge_mode: int = 0
16
+ knife_height: int = 0
17
+ channel_width: int = 0
18
+ ultra_wave: int = 0
19
+ channel_mode: int = 0
20
+ toward: int = 0
21
+ speed: float = 0.0
22
+ zone_hashs: list[int] = field(default_factory=list)
23
+ path_hash: int = 0
24
+ reserved: str = ""
25
+ result: int = 0
26
+ toward_mode: int = 0
27
+ toward_included_angle: int = 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", "Tracker"]
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