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.
- pymammotion/__init__.py +9 -6
- pymammotion/aliyun/client.py +235 -0
- pymammotion/aliyun/cloud_gateway.py +320 -69
- pymammotion/aliyun/model/aep_response.py +1 -2
- pymammotion/aliyun/model/dev_by_account_response.py +170 -23
- pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
- pymammotion/aliyun/model/regions_response.py +3 -3
- pymammotion/aliyun/model/session_by_authcode_response.py +2 -2
- pymammotion/aliyun/model/thing_response.py +12 -0
- pymammotion/aliyun/regions.py +62 -0
- pymammotion/aliyun/tea/core.py +297 -0
- pymammotion/bluetooth/ble.py +11 -15
- pymammotion/bluetooth/ble_message.py +389 -106
- pymammotion/bluetooth/model/atomic_integer.py +54 -0
- pymammotion/const.py +3 -0
- pymammotion/data/model/__init__.py +1 -2
- pymammotion/data/model/device.py +92 -240
- pymammotion/data/model/device_config.py +10 -24
- pymammotion/data/model/device_info.py +35 -0
- pymammotion/data/model/device_limits.py +49 -0
- pymammotion/data/model/enums.py +12 -2
- pymammotion/data/model/errors.py +12 -0
- pymammotion/data/model/events.py +14 -0
- pymammotion/data/model/generate_geojson.py +521 -0
- pymammotion/data/model/generate_route_information.py +3 -4
- pymammotion/data/model/hash_list.py +384 -48
- pymammotion/data/model/location.py +4 -4
- pymammotion/data/model/mowing_modes.py +24 -1
- pymammotion/data/model/raw_data.py +215 -0
- pymammotion/data/model/region_data.py +10 -11
- pymammotion/data/model/report_info.py +62 -6
- pymammotion/data/model/work.py +27 -0
- pymammotion/data/mower_state_manager.py +316 -0
- pymammotion/data/mqtt/event.py +73 -28
- pymammotion/data/mqtt/mammotion_properties.py +257 -0
- pymammotion/data/mqtt/properties.py +93 -78
- pymammotion/data/mqtt/status.py +18 -17
- pymammotion/event/event.py +32 -8
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +484 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/__init__.py +0 -0
- pymammotion/http/encryption.py +220 -0
- pymammotion/http/http.py +652 -44
- pymammotion/http/model/__init__.py +0 -0
- pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
- pymammotion/http/model/http.py +160 -9
- pymammotion/http/model/response_factory.py +61 -0
- pymammotion/http/model/rtk.py +16 -0
- pymammotion/mammotion/commands/abstract_message.py +7 -5
- pymammotion/mammotion/commands/mammotion_command.py +32 -3
- pymammotion/mammotion/commands/messages/basestation.py +43 -0
- pymammotion/mammotion/commands/messages/driver.py +61 -29
- pymammotion/mammotion/commands/messages/media.py +68 -15
- pymammotion/mammotion/commands/messages/navigation.py +61 -25
- pymammotion/mammotion/commands/messages/network.py +93 -100
- pymammotion/mammotion/commands/messages/ota.py +18 -18
- pymammotion/mammotion/commands/messages/system.py +97 -72
- pymammotion/mammotion/commands/messages/video.py +17 -12
- pymammotion/mammotion/devices/__init__.py +27 -3
- pymammotion/mammotion/devices/base.py +50 -127
- pymammotion/mammotion/devices/mammotion.py +447 -212
- pymammotion/mammotion/devices/mammotion_bluetooth.py +105 -60
- pymammotion/mammotion/devices/mammotion_cloud.py +157 -105
- pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
- pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
- pymammotion/mammotion/devices/managers/managers.py +81 -0
- pymammotion/mammotion/devices/mower_device.py +124 -0
- pymammotion/mammotion/devices/mower_manager.py +107 -0
- pymammotion/mammotion/devices/rtk_ble.py +89 -0
- pymammotion/mammotion/devices/rtk_cloud.py +113 -0
- pymammotion/mammotion/devices/rtk_device.py +50 -0
- pymammotion/mammotion/devices/rtk_manager.py +122 -0
- pymammotion/mqtt/__init__.py +2 -1
- pymammotion/mqtt/aliyun_mqtt.py +232 -0
- pymammotion/mqtt/linkkit/__init__.py +5 -0
- pymammotion/mqtt/linkkit/h2client.py +585 -0
- pymammotion/mqtt/linkkit/linkkit.py +3023 -0
- pymammotion/mqtt/mammotion_mqtt.py +176 -169
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +4839 -4
- pymammotion/proto/basestation.proto +8 -0
- pymammotion/proto/basestation_pb2.py +11 -9
- pymammotion/proto/basestation_pb2.pyi +16 -2
- pymammotion/proto/dev_net.proto +79 -55
- pymammotion/proto/dev_net_pb2.py +60 -56
- pymammotion/proto/dev_net_pb2.pyi +49 -6
- pymammotion/proto/luba_msg.proto +2 -1
- pymammotion/proto/luba_msg_pb2.py +6 -6
- pymammotion/proto/luba_msg_pb2.pyi +1 -0
- pymammotion/proto/luba_mul.proto +62 -1
- pymammotion/proto/luba_mul_pb2.py +38 -22
- pymammotion/proto/luba_mul_pb2.pyi +94 -7
- pymammotion/proto/mctrl_driver.proto +44 -4
- pymammotion/proto/mctrl_driver_pb2.py +26 -14
- pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
- pymammotion/proto/mctrl_nav.proto +97 -51
- pymammotion/proto/mctrl_nav_pb2.py +75 -67
- pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
- pymammotion/proto/mctrl_ota.proto +40 -2
- pymammotion/proto/mctrl_ota_pb2.py +23 -13
- pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
- pymammotion/proto/mctrl_pept.proto +8 -3
- pymammotion/proto/mctrl_pept_pb2.py +8 -6
- pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
- pymammotion/proto/mctrl_sys.proto +325 -86
- pymammotion/proto/mctrl_sys_pb2.py +162 -98
- pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
- pymammotion/proto/message_pool.py +3 -0
- pymammotion/proto/py.typed +0 -0
- pymammotion/utility/constant/device_constant.py +65 -21
- pymammotion/utility/datatype_converter.py +13 -12
- pymammotion/utility/device_config.py +755 -0
- pymammotion/utility/device_type.py +218 -21
- pymammotion/utility/map.py +238 -51
- pymammotion/utility/mur_mur_hash.py +159 -0
- {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/METADATA +27 -31
- pymammotion-0.5.51.dist-info/RECORD +152 -0
- {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
- pymammotion/aliyun/cloud_service.py +0 -65
- pymammotion/data/model/plan.py +0 -58
- pymammotion/data/state_manager.py +0 -130
- pymammotion/proto/basestation.py +0 -59
- pymammotion/proto/common.py +0 -12
- pymammotion/proto/dev_net.py +0 -381
- pymammotion/proto/luba_msg.py +0 -81
- pymammotion/proto/luba_mul.py +0 -76
- pymammotion/proto/mctrl_driver.py +0 -100
- pymammotion/proto/mctrl_nav.py +0 -660
- pymammotion/proto/mctrl_ota.py +0 -48
- pymammotion/proto/mctrl_pept.py +0 -41
- pymammotion/proto/mctrl_sys.py +0 -574
- pymammotion-0.2.62.dist-info/RECORD +0 -125
- /pymammotion/{http/_init_.py → bluetooth/model/__init__.py} +0 -0
- {pymammotion-0.2.62.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:
|
|
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
|
pymammotion/http/model/http.py
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Generic, Literal,
|
|
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
|
|
|
10
16
|
|
|
11
17
|
@dataclass
|
|
12
|
-
class ErrorInfo:
|
|
18
|
+
class ErrorInfo(DataClassDictMixin):
|
|
13
19
|
code: str
|
|
14
20
|
platform: str
|
|
15
21
|
module: str
|
|
@@ -38,18 +44,105 @@ class ErrorInfo:
|
|
|
38
44
|
nl_solution: str
|
|
39
45
|
da_implication: str
|
|
40
46
|
da_solution: str
|
|
47
|
+
sr_implication: str
|
|
48
|
+
sr_solution: str
|
|
41
49
|
sv_implication: str
|
|
42
50
|
sv_solution: str
|
|
43
51
|
sl_implication: str
|
|
44
52
|
sl_solution: str
|
|
45
53
|
pt_implication: str
|
|
46
54
|
pt_solution: str
|
|
55
|
+
hu_implication: str
|
|
56
|
+
hu_solution: str
|
|
57
|
+
hr_implication: str
|
|
58
|
+
hr_solution: str
|
|
59
|
+
no_implication: str
|
|
60
|
+
no_solution: str
|
|
61
|
+
fi_implication: str
|
|
62
|
+
fi_solution: str
|
|
63
|
+
ro_implication: str
|
|
64
|
+
ro_solution: str
|
|
65
|
+
bg_implication: str
|
|
66
|
+
bg_solution: str
|
|
67
|
+
et_implication: str
|
|
68
|
+
et_solution: str
|
|
69
|
+
lv_implication: str
|
|
70
|
+
lv_solution: str
|
|
71
|
+
lt_implication: str
|
|
72
|
+
lt_solution: str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
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
|
|
47
139
|
|
|
48
140
|
|
|
49
141
|
@dataclass
|
|
50
|
-
class Response(
|
|
142
|
+
class Response(DataClassORJSONMixin, Generic[DataT]):
|
|
51
143
|
code: int
|
|
52
144
|
msg: str
|
|
145
|
+
request_id: Annotated[str, Alias("requestId")] | None = None
|
|
53
146
|
data: DataT | None = None
|
|
54
147
|
|
|
55
148
|
class Config(BaseConfig):
|
|
@@ -60,20 +153,78 @@ class Response(DataClassDictMixin, Generic[DataT]):
|
|
|
60
153
|
class LoginResponseUserInformation(DataClassORJSONMixin):
|
|
61
154
|
areaCode: str
|
|
62
155
|
domainAbbreviation: str
|
|
63
|
-
email: Optional[str]
|
|
64
156
|
userId: str
|
|
65
157
|
userAccount: str
|
|
66
158
|
authType: str
|
|
159
|
+
email: str | None = None
|
|
160
|
+
|
|
161
|
+
class Config(BaseConfig):
|
|
162
|
+
omit_none = True
|
|
163
|
+
|
|
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
|
|
67
171
|
|
|
68
172
|
|
|
69
173
|
@dataclass
|
|
70
174
|
class LoginResponseData(DataClassORJSONMixin):
|
|
71
175
|
access_token: str
|
|
72
|
-
token_type: Literal["bearer"]
|
|
176
|
+
token_type: Literal["bearer", "Bearer"]
|
|
73
177
|
refresh_token: str
|
|
74
178
|
expires_in: int
|
|
75
|
-
scope: Literal["read"]
|
|
76
|
-
grant_type: Literal["password"]
|
|
77
179
|
authorization_code: str
|
|
78
180
|
userInformation: LoginResponseUserInformation
|
|
79
|
-
jti: str
|
|
181
|
+
jti: str = None
|
|
182
|
+
grant_type: Literal["password", "Password"] = None
|
|
183
|
+
scope: Literal["read", "Read"] = None
|
|
184
|
+
|
|
185
|
+
class Config(BaseConfig):
|
|
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.
|
|
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
|
|
|
@@ -42,11 +71,11 @@ class MammotionCommand(
|
|
|
42
71
|
def move_left(self, angular: float) -> bytes:
|
|
43
72
|
"""Move forward. values 0.0 1.0."""
|
|
44
73
|
angular_percent = get_percent(abs(angular * 100))
|
|
45
|
-
(linear_speed, angular_speed) = transform_both_speeds(0.0,
|
|
74
|
+
(linear_speed, angular_speed) = transform_both_speeds(0.0, 180.0, 0.0, angular_percent)
|
|
46
75
|
return self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed)
|
|
47
76
|
|
|
48
77
|
def move_right(self, angular: float) -> bytes:
|
|
49
78
|
"""Move back. values 0.0 1.0."""
|
|
50
79
|
angular_percent = get_percent(abs(angular * 100))
|
|
51
|
-
(linear_speed, angular_speed) = transform_both_speeds(0.0,
|
|
80
|
+
(linear_speed, angular_speed) = transform_both_speeds(0.0, 0.0, 0.0, angular_percent)
|
|
52
81
|
return self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed)
|
|
@@ -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
|
|
8
|
-
|
|
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.
|
|
30
|
+
msgtype=MsgCmdType.EMBED_DRIVER,
|
|
17
31
|
sender=MsgDevice.DEV_MOBILEAPP,
|
|
18
|
-
rcver=self.get_msg_device(MsgCmdType.
|
|
19
|
-
msgattr=MsgAttr.
|
|
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=
|
|
35
|
+
seqs=self.seqs.increment_and_get() & 255,
|
|
22
36
|
version=1,
|
|
23
|
-
subtype=
|
|
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 =
|
|
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 =
|
|
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
|
|
40
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
todev_devmotion_ctrl=
|
|
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
|
)
|