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.
- pymammotion/__init__.py +5 -4
- pymammotion/aliyun/client.py +235 -0
- pymammotion/aliyun/cloud_gateway.py +312 -64
- 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 +7 -9
- pymammotion/bluetooth/ble_message.py +10 -14
- pymammotion/const.py +3 -0
- pymammotion/data/model/__init__.py +1 -2
- pymammotion/data/model/device.py +95 -27
- pymammotion/data/model/device_config.py +4 -4
- pymammotion/data/model/device_info.py +35 -0
- pymammotion/data/model/device_limits.py +10 -10
- 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 +2 -2
- pymammotion/data/model/hash_list.py +370 -57
- pymammotion/data/model/location.py +4 -4
- pymammotion/data/model/mowing_modes.py +17 -1
- pymammotion/data/model/raw_data.py +2 -10
- pymammotion/data/model/region_data.py +10 -11
- pymammotion/data/model/report_info.py +31 -5
- 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 +27 -6
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +484 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/encryption.py +5 -6
- pymammotion/http/http.py +574 -28
- 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 +129 -4
- 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 +30 -1
- 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 +17 -23
- pymammotion/mammotion/commands/messages/ota.py +18 -18
- pymammotion/mammotion/commands/messages/system.py +32 -49
- pymammotion/mammotion/commands/messages/video.py +15 -16
- pymammotion/mammotion/devices/__init__.py +27 -3
- pymammotion/mammotion/devices/base.py +40 -131
- pymammotion/mammotion/devices/mammotion.py +436 -201
- pymammotion/mammotion/devices/mammotion_bluetooth.py +57 -47
- pymammotion/mammotion/devices/mammotion_cloud.py +134 -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 +93 -52
- 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 +29 -5
- pymammotion/utility/datatype_converter.py +13 -12
- pymammotion/utility/device_config.py +522 -130
- pymammotion/utility/device_type.py +218 -21
- pymammotion/utility/map.py +238 -51
- pymammotion/utility/mur_mur_hash.py +159 -0
- {pymammotion-0.4.0a2.dist-info → pymammotion-0.5.51.dist-info}/METADATA +26 -31
- pymammotion-0.5.51.dist-info/RECORD +152 -0
- {pymammotion-0.4.0a2.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 -129
- 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 -664
- pymammotion/proto/mctrl_ota.py +0 -48
- pymammotion/proto/mctrl_pept.py +0 -41
- pymammotion/proto/mctrl_sys.py +0 -574
- pymammotion-0.4.0a2.dist-info/RECORD +0 -131
- /pymammotion/http/{_init_.py → __init__.py} +0 -0
- {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:
|
|
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,9 +1,15 @@
|
|
|
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
|
|
|
@@ -67,9 +73,76 @@ class ErrorInfo(DataClassDictMixin):
|
|
|
67
73
|
|
|
68
74
|
|
|
69
75
|
@dataclass
|
|
70
|
-
class
|
|
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:
|
|
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.
|
|
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
|
|
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
|
)
|
|
@@ -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
|
|
6
|
-
|
|
7
|
-
|
|
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 =
|
|
13
|
-
msgtype=
|
|
14
|
-
sender=
|
|
15
|
-
rcver=self.get_msg_device(MsgCmdType.
|
|
16
|
-
msgattr=
|
|
17
|
-
seqs=
|
|
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=
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
34
|
-
|
|
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
|
+
)
|