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
File without changes
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import List
3
2
 
4
3
  from mashumaro.mixins.orjson import DataClassORJSONMixin
5
4
 
@@ -13,7 +12,20 @@ class Camera(DataClassORJSONMixin):
13
12
  @dataclass
14
13
  class StreamSubscriptionResponse(DataClassORJSONMixin):
15
14
  appid: str
16
- cameras: List[Camera]
15
+ cameras: list[Camera]
17
16
  channelName: str
18
17
  token: str
19
18
  uid: int
19
+ license: str | None = None
20
+ availableTime: int | None = None
21
+
22
+
23
+ @dataclass
24
+ class VideoResourceResponse(DataClassORJSONMixin):
25
+ id: str
26
+ deviceId: str
27
+ deviceName: str
28
+ cycleType: int
29
+ usageYearMonth: str
30
+ totalTime: int
31
+ availableTime: int
@@ -1,9 +1,15 @@
1
- from dataclasses import dataclass
2
- from typing import Generic, Literal, Optional, TypeVar
1
+ from dataclasses import dataclass, field
2
+ from typing import Annotated, Generic, Literal, TypeVar
3
3
 
4
4
  from mashumaro import DataClassDictMixin
5
5
  from mashumaro.config import BaseConfig
6
6
  from mashumaro.mixins.orjson import DataClassORJSONMixin
7
+ from mashumaro.types import Alias
8
+
9
+
10
+ class UnauthorizedException(Exception):
11
+ pass
12
+
7
13
 
8
14
  DataT = TypeVar("DataT")
9
15
 
@@ -67,9 +73,76 @@ class ErrorInfo(DataClassDictMixin):
67
73
 
68
74
 
69
75
  @dataclass
70
- class Response(DataClassDictMixin, Generic[DataT]):
76
+ class SettingVo(DataClassORJSONMixin):
77
+ """Device setting configuration."""
78
+
79
+ type: int = 0
80
+ is_switch: Annotated[int, Alias("isSwitch")] = 0
81
+
82
+
83
+ @dataclass
84
+ class LocationVo(DataClassORJSONMixin):
85
+ """Device location information."""
86
+
87
+ date_time: Annotated[str, Alias("dateTime")] = ""
88
+ date_timestamp: Annotated[int, Alias("dateTimestamp")] = 0
89
+ location: list[float] = field(default_factory=lambda: [0.0, 0.0])
90
+
91
+
92
+ @dataclass
93
+ class DeviceInfo:
94
+ """Complete device information."""
95
+
96
+ iot_id: Annotated[str, Alias("iotId")] = ""
97
+ device_id: Annotated[str, Alias("deviceId")] = ""
98
+ device_name: Annotated[str, Alias("deviceName")] = ""
99
+ device_type: Annotated[str, Alias("deviceType")] = ""
100
+ series: str = ""
101
+ product_series: Annotated[str, Alias("productSeries")] = ""
102
+ icon_code: Annotated[str, Alias("iconCode")] = ""
103
+ generation: int = 0
104
+ status: int = 0
105
+ is_subscribe: Annotated[int, Alias("isSubscribe")] = 0
106
+ setting_vos: Annotated[list[SettingVo], Alias("settingVos")] = field(default_factory=list)
107
+ active_time: Annotated[str, Alias("activeTime")] = ""
108
+ active_timestamp: Annotated[int, Alias("activeTimestamp")] = 0
109
+ location_vo: Annotated[LocationVo | None, Alias("locationVo")] = None
110
+
111
+
112
+ @dataclass
113
+ class DeviceRecord(DataClassORJSONMixin):
114
+ identity_id: Annotated[str, Alias("identityId")]
115
+ iot_id: Annotated[str, Alias("iotId")]
116
+ product_key: Annotated[str, Alias("productKey")]
117
+ device_name: Annotated[str, Alias("deviceName")]
118
+ owned: int
119
+ status: int
120
+ bind_time: Annotated[int, Alias("bindTime")]
121
+ create_time: Annotated[str, Alias("createTime")]
122
+
123
+
124
+ @dataclass
125
+ class DeviceRecords(DataClassORJSONMixin):
126
+ records: list[DeviceRecord]
127
+ total: int
128
+ size: int
129
+ current: int
130
+ pages: int
131
+
132
+
133
+ @dataclass
134
+ class MQTTConnection(DataClassORJSONMixin):
135
+ host: str
136
+ jwt: str
137
+ client_id: Annotated[str, Alias("clientId")]
138
+ username: str
139
+
140
+
141
+ @dataclass
142
+ class Response(DataClassORJSONMixin, Generic[DataT]):
71
143
  code: int
72
144
  msg: str
145
+ request_id: Annotated[str, Alias("requestId")] | None = None
73
146
  data: DataT | None = None
74
147
 
75
148
  class Config(BaseConfig):
@@ -83,12 +156,20 @@ class LoginResponseUserInformation(DataClassORJSONMixin):
83
156
  userId: str
84
157
  userAccount: str
85
158
  authType: str
86
- email: Optional[str] = None
159
+ email: str | None = None
87
160
 
88
161
  class Config(BaseConfig):
89
162
  omit_none = True
90
163
 
91
164
 
165
+ @dataclass
166
+ class JWTTokenInfo(DataClassORJSONMixin):
167
+ """specifically for newer devices and mqtt"""
168
+
169
+ iot: str # iot domain e.g api-iot-business-eu-dcdn.mammotion.com
170
+ robot: str # e.g api-robot-eu.mammotion.com
171
+
172
+
92
173
  @dataclass
93
174
  class LoginResponseData(DataClassORJSONMixin):
94
175
  access_token: str
@@ -103,3 +184,47 @@ class LoginResponseData(DataClassORJSONMixin):
103
184
 
104
185
  class Config(BaseConfig):
105
186
  omit_none = True
187
+
188
+
189
+ @dataclass
190
+ class FirmwareVersions(DataClassORJSONMixin):
191
+ firmware_version: Annotated[str, Alias("firmwareVersion")] = ""
192
+ firmware_code: Annotated[str, Alias("firmwareCode")] = ""
193
+ firmware_latest_version: Annotated[str, Alias("firmwareLatestVersion")] = ""
194
+ firmware_type: Annotated[str, Alias("firmwareType")] = ""
195
+
196
+
197
+ @dataclass
198
+ class ProductVersionInfo(DataClassORJSONMixin):
199
+ release_note: Annotated[str, Alias("releaseNote")] = ""
200
+ release_version: Annotated[str, Alias("releaseVersion")] = ""
201
+ data_location: str | None = None
202
+
203
+
204
+ @dataclass
205
+ class CheckDeviceVersion(DataClassORJSONMixin):
206
+ cause_code: Annotated[int, Alias("causeCode")] = 0
207
+ product_version_info_vo: Annotated[ProductVersionInfo | None, Alias("productVersionInfoVo")] = None
208
+ progress: int | None = 0
209
+ upgradeable: bool = False
210
+ device_id: Annotated[str, Alias("deviceId")] = ""
211
+ device_name: Annotated[str | None, Alias("deviceName")] = ""
212
+ current_version: Annotated[str, Alias("currentVersion")] = ""
213
+ isupgrading: bool | None = False
214
+ cause_msg: Annotated[str, Alias("causeMsg")] = ""
215
+
216
+ def __eq__(self, other):
217
+ if not isinstance(other, CheckDeviceVersion):
218
+ return NotImplemented
219
+
220
+ if self.device_id != other.device_id or self.current_version != other.current_version:
221
+ return False
222
+
223
+ if self.product_version_info_vo and other.product_version_info_vo:
224
+ if self.product_version_info_vo.release_version != other.product_version_info_vo.release_version:
225
+ return False
226
+ return True
227
+ elif self.product_version_info_vo is None and other.product_version_info_vo is None:
228
+ return False
229
+ else:
230
+ return True
@@ -0,0 +1,61 @@
1
+ from typing import TypeVar, Union, get_args, get_origin
2
+
3
+ from pymammotion.http.model.http import Response
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ def deserialize_data(value, target_type):
9
+ """Deserialize data into a specified target type.
10
+
11
+ The function handles deserialization of basic types, lists, and unions. It
12
+ recursively processes list elements and supports optional types by handling
13
+ Union[T, None]. For custom types with a `from_dict` method, it calls this
14
+ method for deserialization. If the target type is unknown or unsupported, it
15
+ returns the value unchanged.
16
+
17
+ Args:
18
+ value: The data to be deserialized.
19
+ target_type (type): The desired type into which the data should be deserialized.
20
+
21
+ Returns:
22
+ The deserialized data in the specified target type.
23
+
24
+ """
25
+ if value is None:
26
+ return None
27
+
28
+ origin = get_origin(target_type)
29
+ args = get_args(target_type)
30
+
31
+ if origin is list and args:
32
+ item_type = args[0]
33
+ return [deserialize_data(v, item_type) for v in value]
34
+
35
+ if origin is Union:
36
+ # Support Optional[T] = Union[T, None]
37
+ non_none_types = [t for t in args if t is not type(None)]
38
+ if len(non_none_types) == 1:
39
+ target = non_none_types[0]
40
+ # Handle Response[list[type]] case
41
+ if get_origin(target) is list and get_args(target):
42
+ item_type = get_args(target)[0]
43
+ return [deserialize_data(v, item_type) for v in value]
44
+ return deserialize_data(value, target)
45
+
46
+ if hasattr(target_type, "from_dict"):
47
+ return target_type.from_dict(value)
48
+
49
+ return value # fallback: unknown type, leave as-is
50
+
51
+
52
+ def response_factory(response_cls: type[Response[T]], raw_dict: dict) -> Response[T]:
53
+ # Extract the type of the generic `data` field
54
+ """Create a Response instance from a dictionary."""
55
+ data_type = get_args(response_cls)[0] if get_args(response_cls) else None
56
+
57
+ if data_type:
58
+ data_value = deserialize_data(raw_dict.get("data"), data_type)
59
+ return Response(code=raw_dict["code"], msg=raw_dict["msg"], data=data_value)
60
+ else:
61
+ return response_cls.from_dict(raw_dict)
@@ -0,0 +1,16 @@
1
+ """RTK device information."""
2
+ from dataclasses import dataclass, field
3
+
4
+ from mashumaro import field_options
5
+ from mashumaro.mixins.orjson import DataClassORJSONMixin
6
+
7
+
8
+ @dataclass
9
+ class RTK(DataClassORJSONMixin):
10
+ """RTK device information."""
11
+
12
+ device_id: str = field(metadata=field_options(alias="deviceId"))
13
+ device_name: str = field(metadata=field_options(alias="deviceName"))
14
+ product_key: str = field(metadata=field_options(alias="productKey"))
15
+ status: int
16
+ lora: str
@@ -1,22 +1,24 @@
1
1
  from abc import abstractmethod
2
2
 
3
- from pymammotion.proto.luba_msg import MsgCmdType, MsgDevice
3
+ from pymammotion.bluetooth.model.atomic_integer import AtomicInteger
4
+ from pymammotion.proto import MsgCmdType, MsgDevice
4
5
  from pymammotion.utility.device_type import DeviceType
5
6
 
6
7
 
7
8
  class AbstractMessage:
9
+ seqs = AtomicInteger(0)
10
+ user_account: int
11
+
8
12
  @abstractmethod
9
13
  def get_device_name(self) -> str:
10
14
  """Get device name."""
11
15
 
16
+ @abstractmethod
12
17
  def get_device_product_key(self) -> str:
13
18
  """Get device name."""
14
19
 
15
20
  def get_msg_device(self, msg_type: MsgCmdType, msg_device: MsgDevice) -> MsgDevice:
16
21
  """Changes the rcver name if it's not a luba1."""
17
- if (
18
- not DeviceType.is_luba1(self.get_device_name(), self.get_device_product_key())
19
- and msg_type == MsgCmdType.MSG_CMD_TYPE_NAV
20
- ):
22
+ if DeviceType.is_luba_pro(self.get_device_name(), self.get_device_product_key()) and msg_type == MsgCmdType.NAV:
21
23
  return MsgDevice.DEV_NAVIGATION
22
24
  return msg_device
@@ -5,6 +5,7 @@ from pymammotion.mammotion.commands.messages.network import MessageNetwork
5
5
  from pymammotion.mammotion.commands.messages.ota import MessageOta
6
6
  from pymammotion.mammotion.commands.messages.system import MessageSystem
7
7
  from pymammotion.mammotion.commands.messages.video import MessageVideo
8
+ from pymammotion.utility.device_type import DeviceType
8
9
  from pymammotion.utility.movement import get_percent, transform_both_speeds
9
10
 
10
11
 
@@ -13,14 +14,42 @@ class MammotionCommand(
13
14
  ):
14
15
  """MQTT commands for Luba."""
15
16
 
16
- def __init__(self, device_name: str) -> None:
17
+ def __init__(self, device_name: str, user_account: int) -> None:
17
18
  self._device_name = device_name
18
19
  self._product_key = ""
20
+ self.user_account = user_account
19
21
 
20
22
  def get_device_name(self) -> str:
21
23
  """Get device name."""
22
24
  return self._device_name
23
25
 
26
+ def read_write_device(self, rw_id: int, context: int, rw: int):
27
+ if (
28
+ rw_id == 6 or rw_id == 3 or rw_id == 7 or rw_id == 8 or rw_id == 10 or rw_id == 11
29
+ ) and DeviceType.is_luba_pro(self.get_device_name()):
30
+ return self.allpowerfull_rw_adapter_x3(rw_id, context, rw)
31
+ return self.allpowerfull_rw(rw_id, context, rw)
32
+
33
+ def traverse_mode(self, context: int) -> bytes:
34
+ """Sets the traversal mode back to charger."""
35
+ # setReChargeMode
36
+ # 0 direct
37
+ # 1 follow the perimeter
38
+ return self.read_write_device(7, context, 1)
39
+
40
+ def turning_mode(self, context: int) -> bytes:
41
+ """Sets the traversal mode back to charger."""
42
+ # setTurnAroundMode
43
+ # 0 zero turn
44
+ # 1 multipoint turn
45
+ return self.read_write_device(6, context, 1)
46
+
47
+ def get_error_code(self) -> bytes:
48
+ return self.read_write_device(5, 2, 1)
49
+
50
+ def get_error_timestamp(self) -> bytes:
51
+ return self.read_write_device(5, 3, 1)
52
+
24
53
  def get_device_product_key(self) -> str:
25
54
  return self._product_key
26
55
 
@@ -0,0 +1,43 @@
1
+ """RTK protobuf commands."""
2
+
3
+ from abc import ABC
4
+ from logging import getLogger
5
+ import time
6
+
7
+ from pymammotion.mammotion.commands.abstract_message import AbstractMessage
8
+ from pymammotion.proto import (
9
+ AppToBaseMqttRtkT,
10
+ BaseStation,
11
+ LubaMsg,
12
+ MsgAttr,
13
+ MsgCmdType,
14
+ MsgDevice,
15
+ RequestBasestationInfoT,
16
+ )
17
+
18
+ logger = getLogger(__name__)
19
+
20
+
21
+ class MessageBasestation(AbstractMessage, ABC):
22
+ def send_order_msg_basestation(self, driver) -> bytes:
23
+ return LubaMsg(
24
+ msgtype=MsgCmdType.BASESTATION,
25
+ sender=MsgDevice.DEV_MOBILEAPP,
26
+ rcver=self.get_msg_device(MsgCmdType.BASESTATION, MsgDevice.DEV_MAINCTL),
27
+ msgattr=MsgAttr.REQ,
28
+ timestamp=round(time.time() * 1000),
29
+ seqs=self.seqs.increment_and_get() & 255,
30
+ version=1,
31
+ subtype=self.user_account,
32
+ driver=driver,
33
+ ).SerializeToString()
34
+
35
+ def basestation_info(self) -> bytes:
36
+ """Build and send a request to get basestation info (request_type=1)."""
37
+ base = BaseStation(to_dev=RequestBasestationInfoT(request_type=1))
38
+ return self.send_order_msg_basestation(base)
39
+
40
+ def set_base_net_rtk_switch(self, rtk_switch: int) -> bytes:
41
+ """Set RTK switch via app_to_base_mqtt_rtk_t."""
42
+ base = BaseStation(app_to_base_mqtt_rtk_msg=AppToBaseMqttRtkT(rtk_switch=rtk_switch))
43
+ return self.send_order_msg_basestation(base)
@@ -1,58 +1,90 @@
1
1
  # === sendOrderMsg_Driver ===
2
- import time
3
2
  from abc import ABC
4
3
  from logging import getLogger
4
+ import time
5
5
 
6
6
  from pymammotion.mammotion.commands.abstract_message import AbstractMessage
7
- from pymammotion.proto import mctrl_driver
8
- from pymammotion.proto.luba_msg import LubaMsg, MsgAttr, MsgCmdType, MsgDevice
7
+ from pymammotion.proto import (
8
+ AppGetCutterWorkMode,
9
+ AppSetCutterWorkMode,
10
+ DrvKnifeHeight,
11
+ DrvMotionCtrl,
12
+ DrvMowCtrlByHand,
13
+ DrvSrSpeed,
14
+ LubaMsg,
15
+ MctlDriver,
16
+ MsgAttr,
17
+ MsgCmdType,
18
+ MsgDevice,
19
+ RtkCfgReqT,
20
+ RtkSysMaskQueryT,
21
+ )
9
22
 
10
23
  logger = getLogger(__name__)
11
24
 
12
25
 
13
26
  class MessageDriver(AbstractMessage, ABC):
14
27
  def send_order_msg_driver(self, driver) -> bytes:
28
+ """Build and serialize a driver command message."""
15
29
  return LubaMsg(
16
- msgtype=MsgCmdType.MSG_CMD_TYPE_EMBED_DRIVER,
30
+ msgtype=MsgCmdType.EMBED_DRIVER,
17
31
  sender=MsgDevice.DEV_MOBILEAPP,
18
- rcver=self.get_msg_device(MsgCmdType.MSG_CMD_TYPE_EMBED_DRIVER, MsgDevice.DEV_MAINCTL),
19
- msgattr=MsgAttr.MSG_ATTR_REQ,
32
+ rcver=self.get_msg_device(MsgCmdType.EMBED_DRIVER, MsgDevice.DEV_MAINCTL),
33
+ msgattr=MsgAttr.REQ,
20
34
  timestamp=round(time.time() * 1000),
21
- seqs=1,
35
+ seqs=self.seqs.increment_and_get() & 255,
22
36
  version=1,
23
- subtype=1,
37
+ subtype=self.user_account,
24
38
  driver=driver,
25
39
  ).SerializeToString()
26
40
 
27
- def set_blade_height(self, height: int):
41
+ def set_blade_height(self, height: int) -> bytes:
42
+ """Set mower blade height."""
28
43
  logger.debug(f"Send knife height height={height}")
29
- build = mctrl_driver.MctlDriver(todev_knife_height_set=mctrl_driver.DrvKnifeHeight(knife_height=height))
44
+ build = MctlDriver(todev_knife_height_set=DrvKnifeHeight(knife_height=height))
30
45
  logger.debug(f"Send command--Knife motor height setting height={height}")
31
46
  return self.send_order_msg_driver(build)
32
47
 
33
- def set_speed(self, speed: float):
48
+ def set_speed(self, speed: float) -> bytes:
49
+ """Set the device speed."""
34
50
  logger.debug(f"{self.get_device_name()} set speed, {speed}")
35
- build = mctrl_driver.MctlDriver(bidire_speed_read_set=mctrl_driver.DrvSrSpeed(speed=speed, rw=1))
51
+ build = MctlDriver(bidire_speed_read_set=DrvSrSpeed(speed=speed, rw=1))
36
52
  logger.debug(f"Send command--Speed setting speed={speed}")
37
53
  return self.send_order_msg_driver(build)
38
54
 
39
- def syn_nav_star_point_data(self, sat_system: int):
40
- build = mctrl_driver.MctlDriver(rtk_sys_mask_query=mctrl_driver.RtkSysMaskQueryT(sat_system=sat_system))
55
+ def get_cutter_mode(self) -> bytes:
56
+ """Request the current cutter mode."""
57
+ build = MctlDriver(current_cutter_mode=AppGetCutterWorkMode())
58
+ return self.send_order_msg_driver(build)
59
+
60
+ def set_cutter_mode(self, cutter_mode: int) -> bytes:
61
+ """Set blade speed."""
62
+ """
63
+ 1 slow
64
+ 0 normal
65
+ 2 fast
66
+ """
67
+ build = MctlDriver(cutter_mode_ctrl_by_hand=AppSetCutterWorkMode(cutter_mode=cutter_mode))
68
+ return self.send_order_msg_driver(build)
69
+
70
+ def syn_nav_star_point_data(self, sat_system: int) -> bytes:
71
+ """Synchronize navigation satellite frequency points."""
72
+ build = MctlDriver(rtk_sys_mask_query=RtkSysMaskQueryT(sat_system=sat_system))
41
73
  logger.debug(f"Send command--Navigation satellite frequency point synchronization={sat_system}")
42
74
  return self.send_order_msg_driver(build)
43
75
 
44
- def set_nav_star_point(self, cmd_req: str):
45
- build = mctrl_driver.MctlDriver(
46
- rtk_cfg_req=mctrl_driver.RtkCfgReqT(cmd_req=cmd_req, cmd_length=len(cmd_req) - 1)
47
- )
76
+ def set_nav_star_point(self, cmd_req: str) -> bytes:
77
+ """Configure navigation satellite frequency points."""
78
+ build = MctlDriver(rtk_cfg_req=RtkCfgReqT(cmd_req=cmd_req, cmd_length=len(cmd_req) - 1))
48
79
  logger.debug(f"Send command--Navigation satellite frequency point setting={cmd_req}")
49
80
  logger.debug(
50
81
  f"Navigation satellite setting, Send command--Navigation satellite frequency point setting={cmd_req}"
51
82
  )
52
83
  return self.send_order_msg_driver(build)
53
84
 
54
- def get_speed(self):
55
- build = mctrl_driver.MctlDriver(bidire_speed_read_set=mctrl_driver.DrvSrSpeed(rw=0))
85
+ def get_speed(self) -> bytes:
86
+ """Request the current speed value."""
87
+ build = MctlDriver(bidire_speed_read_set=DrvSrSpeed(rw=0))
56
88
  logger.debug("Send command--Get speed value")
57
89
  return self.send_order_msg_driver(build)
58
90
 
@@ -62,13 +94,14 @@ class MessageDriver(AbstractMessage, ABC):
62
94
  cut_knife_ctrl: int,
63
95
  cut_knife_height: int,
64
96
  max_run_speed: float,
65
- ):
66
- build = mctrl_driver.MctlDriver(
67
- mow_ctrl_by_hand=mctrl_driver.DrvMowCtrlByHand(
97
+ ) -> bytes:
98
+ """Send manual mowing control command."""
99
+ build = MctlDriver(
100
+ mow_ctrl_by_hand=DrvMowCtrlByHand(
68
101
  main_ctrl=main_ctrl,
69
102
  cut_knife_ctrl=cut_knife_ctrl,
70
103
  cut_knife_height=cut_knife_height,
71
- max_run__speed=max_run_speed,
104
+ max_run_speed=max_run_speed,
72
105
  )
73
106
  )
74
107
  logger.debug(
@@ -78,13 +111,12 @@ class MessageDriver(AbstractMessage, ABC):
78
111
 
79
112
  return self.send_order_msg_driver(build)
80
113
 
81
- def send_movement(self, linear_speed: int, angular_speed: int):
114
+ def send_movement(self, linear_speed: int, angular_speed: int) -> bytes:
115
+ """Send motion command with linear and angular speeds."""
82
116
  logger.debug(f"Control command print, linearSpeed={
83
117
  linear_speed} // angularSpeed={angular_speed}")
84
118
  return self.send_order_msg_driver(
85
- mctrl_driver.MctlDriver(
86
- todev_devmotion_ctrl=mctrl_driver.DrvMotionCtrl(
87
- set_linear_speed=linear_speed, set_angular_speed=angular_speed
88
- )
119
+ MctlDriver(
120
+ todev_devmotion_ctrl=DrvMotionCtrl(set_linear_speed=linear_speed, set_angular_speed=angular_speed)
89
121
  )
90
122
  )
@@ -2,33 +2,86 @@
2
2
  from abc import ABC
3
3
 
4
4
  from pymammotion.mammotion.commands.abstract_message import AbstractMessage
5
- from pymammotion.proto import luba_msg_pb2, luba_mul_pb2
6
- from pymammotion.proto.luba_msg import MsgCmdType, MsgDevice
7
- from pymammotion.proto.luba_mul import MUL_LANGUAGE
5
+ from pymammotion.proto import (
6
+ GetHeadlamp,
7
+ LampCtrlSta,
8
+ LampManualCtrlSta,
9
+ LubaMsg,
10
+ MsgAttr,
11
+ MsgCmdType,
12
+ MsgDevice,
13
+ MulLanguage,
14
+ MulSetAudio,
15
+ MulSetWiper,
16
+ MulSex,
17
+ SetHeadlamp,
18
+ SocMul,
19
+ )
8
20
 
9
21
 
10
22
  class MessageMedia(AbstractMessage, ABC):
11
23
  def send_order_msg_media(self, mul):
12
- luba_msg = luba_msg_pb2.LubaMsg(
13
- msgtype=luba_msg_pb2.MSG_CMD_TYPE_MUL,
14
- sender=luba_msg_pb2.DEV_MOBILEAPP,
15
- rcver=self.get_msg_device(MsgCmdType.MSG_CMD_TYPE_MUL, MsgDevice.SOC_MODULE_MULTIMEDIA),
16
- msgattr=luba_msg_pb2.MSG_ATTR_REQ,
17
- seqs=1,
24
+ luba_msg = LubaMsg(
25
+ msgtype=MsgCmdType.MUL,
26
+ sender=MsgDevice.DEV_MOBILEAPP,
27
+ rcver=self.get_msg_device(MsgCmdType.MUL, MsgDevice.SOC_MODULE_MULTIMEDIA),
28
+ msgattr=MsgAttr.REQ,
29
+ seqs=self.seqs.increment_and_get() & 255,
18
30
  version=1,
19
- subtype=1,
31
+ subtype=self.user_account,
20
32
  mul=mul,
21
33
  )
22
34
 
23
35
  return luba_msg.SerializeToString()
24
36
 
25
37
  def set_car_volume(self, volume: int):
26
- return self.send_order_msg_media(luba_mul_pb2.SocMul(set_audio=luba_mul_pb2.MulSetAudio(at_switch=volume)))
38
+ """Set the car volume. 0 - 100"""
39
+ return self.send_order_msg_media(SocMul(set_audio=MulSetAudio(at_switch=volume)))
27
40
 
28
- def set_car_voice_language(self, language_type: MUL_LANGUAGE | str | None):
41
+ def set_car_voice_language(self, language_type: MulLanguage | str | None):
42
+ return self.send_order_msg_media(SocMul(set_audio=MulSetAudio(au_language=language_type)))
43
+
44
+ def set_car_volume_sex(self, sex: MulSex):
45
+ return self.send_order_msg_media(SocMul(set_audio=MulSetAudio(sex=sex)))
46
+
47
+ def set_car_wiper(self, round_num: int):
48
+ """Set mower wiper."""
49
+ # 2
50
+ return self.send_order_msg_media(SocMul(set_wiper=MulSetWiper(round=round_num)))
51
+
52
+ def get_car_light(self, ids: int):
53
+ """Get mower light settings.
54
+ 1126 for manual
55
+ 1123 for night time settings
56
+ """
57
+ return self.send_order_msg_media(SocMul(get_lamp=GetHeadlamp(get_ids=ids)))
58
+
59
+ def set_car_light(self, on_off: bool = False):
60
+ """Set mower light.
61
+
62
+ set whether light is on during the night during mowing
63
+ auto night on true, id=1121, power_ctrl=1
64
+ auto night off false, id=1121, power_ctrl=1
65
+ """
66
+
67
+ ctrl_state = LampCtrlSta.power_ctrl_on if on_off else LampCtrlSta.power_off
29
68
  return self.send_order_msg_media(
30
- luba_mul_pb2.SocMul(set_audio=luba_mul_pb2.MulSetAudio(au_language=language_type))
69
+ SocMul(
70
+ set_lamp=SetHeadlamp(
71
+ set_ids=1121, lamp_power_ctrl=1, lamp_ctrl=ctrl_state, ctrl_lamp_bright=False, lamp_bright=0
72
+ )
73
+ )
31
74
  )
32
75
 
33
- def set_car_wiper(self, round_num: int):
34
- return self.send_order_msg_media(luba_mul_pb2.SocMul(set_wiper=luba_mul_pb2.MulSetWiper(round=round_num)))
76
+ def set_car_manual_light(self, manual_ctrl: bool = False):
77
+ """Set mower light.
78
+
79
+ set whether light is on manually
80
+ manual on: true, id=1125, power_ctrl=2
81
+ manual off: false, id=1127, power_ctrl=2
82
+ """
83
+ ids = 1125 if manual_ctrl else 1127
84
+ manual_light_ctrl = LampManualCtrlSta.manual_power_on if manual_ctrl else LampManualCtrlSta.manual_power_off
85
+ return self.send_order_msg_media(
86
+ SocMul(set_lamp=SetHeadlamp(set_ids=ids, lamp_power_ctrl=2, lamp_manual_ctrl=manual_light_ctrl))
87
+ )