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
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
|
+
LubaProductKey = [
|
|
4
|
+
"a1UBFdq6nNz",
|
|
5
|
+
"a1x0zHD3Xop",
|
|
6
|
+
"a1pvCnb3PPu",
|
|
7
|
+
"a1kweSOPylG",
|
|
8
|
+
"a1JFpmAV5Ur",
|
|
9
|
+
"a1BmXWlsdbA",
|
|
10
|
+
"a1jOhAYOIG8",
|
|
11
|
+
"a1K4Ki2L5rK",
|
|
12
|
+
"a1ae1QnXZGf",
|
|
13
|
+
"a1nf9kRBWoH",
|
|
14
|
+
"a1ZU6bdGjaM",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
LubaVProductKey = ["a1iMygIwxFC", "a1LLmy1zc0j", "a1LLmy1zc0j"]
|
|
18
|
+
|
|
19
|
+
LubaVProProductKey = ["a1mb8v6tnAa", "a1pHsTqyoPR"]
|
|
20
|
+
|
|
21
|
+
Luba2MiniProductKey = ["a1L5ZfJIxGl", "a1dCWYFLROK"]
|
|
22
|
+
|
|
23
|
+
YukaProductKey = ["a1kT0TlYEza", "a1IQV0BrnXb"]
|
|
24
|
+
|
|
25
|
+
YukaPlusProductKey = ["a1lNESu9VST", "a1zAEzmvWDa"]
|
|
26
|
+
|
|
27
|
+
YukaMiniProductKey = ["a1BqmEWMRbX", "a1biqVGvxrE"]
|
|
28
|
+
|
|
29
|
+
RTKProductKey = ["a1qXkZ5P39W", "a1Nc68bGZzX"]
|
|
30
|
+
|
|
31
|
+
YukaMVProductKey = ["a1jFe8HzcDb", "a16cz0iXgUJ", "USpE46bNTC7", "pdA6uJrBfjz"]
|
|
32
|
+
|
|
33
|
+
LubaLDProductKey = ["a1jDMfG2Fgj", "a1vtZq9LUFS"]
|
|
34
|
+
|
|
35
|
+
LubaVAProductKey = ["a1Ce85210Be", "a1BBOJnnjb9"]
|
|
36
|
+
|
|
37
|
+
YukaMLProductKey = ["a1OWGO8WXbh", "a1s6znKxGvI"]
|
|
38
|
+
|
|
39
|
+
LubaMDProductKey = ["a1T6VTFTc0C", "a14iRDqMepW"]
|
|
40
|
+
|
|
41
|
+
LubaMBProductKey = ["a1pb9toor70"]
|
|
42
|
+
|
|
43
|
+
RTKNBProductKey = ["a1NfZqdSREf", "a1ZuQVL7UiN"]
|
|
44
|
+
|
|
45
|
+
LubaLAProductKey = ["CDYuKXTYrSP"]
|
|
46
|
+
|
|
47
|
+
YukaMN100ProductKey = ["NnbeYtaEUGE"]
|
|
48
|
+
|
|
49
|
+
Cm900ProductKey = ["zkRuTK9KsXG", "6DbgVh2Qs5m"]
|
|
50
|
+
|
|
3
51
|
|
|
4
52
|
class DeviceType(Enum):
|
|
5
53
|
UNKNOWN = (-1, "UNKNOWN", "Unknown")
|
|
@@ -10,6 +58,24 @@ class DeviceType(Enum):
|
|
|
10
58
|
YUKA_MINI = (4, "Yuka-MN", "Yuka Mini")
|
|
11
59
|
YUKA_MINI2 = (5, "Yuka-YM", "Yuka Mini 2")
|
|
12
60
|
LUBA_VP = (6, "Luba-VP", "Luba VP")
|
|
61
|
+
LUBA_MN = (7, "Luba-MN", "HM430")
|
|
62
|
+
YUKA_VP = (8, "Yuka-VP", "MN241")
|
|
63
|
+
SPINO = (9, "Spino", "Spino")
|
|
64
|
+
RTK3A1 = (10, "RBSA1", "RBS03A1")
|
|
65
|
+
LUBA_LD = (11, "Luba-LD", "HM431")
|
|
66
|
+
RTK3A0 = (12, "RBSA0", "RBS03A0")
|
|
67
|
+
RTK3A2 = (13, "RBSA2", "RBS03A2")
|
|
68
|
+
YUKA_MINIV = (14, "Yuka-MV", "MN231")
|
|
69
|
+
LUBA_VA = (15, "Luba-VA", "HM442")
|
|
70
|
+
YUKA_ML = (16, "Yuka-ML", "MN232")
|
|
71
|
+
LUBA_MD = (17, "Luba-MD", "HM433")
|
|
72
|
+
LUBA_LA = (18, "Luba-LA", "HM432")
|
|
73
|
+
SWIMMINGPOOL_S1 = (19, "Spino-S1", "Spino-S1")
|
|
74
|
+
SWIMMINGPOOL_E1 = (20, "Spino-E1", "Spino-E1")
|
|
75
|
+
YUKA_MN100 = (21, "Ezy-VT", "MN100")
|
|
76
|
+
RTKNB = (22, "NB", "NB")
|
|
77
|
+
LUBA_MB = (23, "Luba-MB", "HM434")
|
|
78
|
+
CM900 = (24, "Kumar-MK", "KM01")
|
|
13
79
|
|
|
14
80
|
def __init__(self, value: int, name: str, model: str) -> None:
|
|
15
81
|
self._value = value
|
|
@@ -58,6 +124,44 @@ class DeviceType(Enum):
|
|
|
58
124
|
return DeviceType.YUKA_MINI
|
|
59
125
|
elif value == 5:
|
|
60
126
|
return DeviceType.YUKA_MINI2
|
|
127
|
+
elif value == 6:
|
|
128
|
+
return DeviceType.LUBA_VP
|
|
129
|
+
elif value == 7:
|
|
130
|
+
return DeviceType.LUBA_MN
|
|
131
|
+
elif value == 8:
|
|
132
|
+
return DeviceType.YUKA_VP
|
|
133
|
+
elif value == 9:
|
|
134
|
+
return DeviceType.SPINO
|
|
135
|
+
elif value == 10:
|
|
136
|
+
return DeviceType.RTK3A1
|
|
137
|
+
elif value == 11:
|
|
138
|
+
return DeviceType.LUBA_LD
|
|
139
|
+
elif value == 12:
|
|
140
|
+
return DeviceType.RTK3A0
|
|
141
|
+
elif value == 13:
|
|
142
|
+
return DeviceType.RTK3A2
|
|
143
|
+
elif value == 14:
|
|
144
|
+
return DeviceType.YUKA_MINIV
|
|
145
|
+
elif value == 15:
|
|
146
|
+
return DeviceType.LUBA_VA
|
|
147
|
+
elif value == 16:
|
|
148
|
+
return DeviceType.YUKA_ML
|
|
149
|
+
elif value == 17:
|
|
150
|
+
return DeviceType.LUBA_MD
|
|
151
|
+
elif value == 18:
|
|
152
|
+
return DeviceType.LUBA_LA
|
|
153
|
+
elif value == 19:
|
|
154
|
+
return DeviceType.SWIMMINGPOOL_S1
|
|
155
|
+
elif value == 20:
|
|
156
|
+
return DeviceType.SWIMMINGPOOL_E1
|
|
157
|
+
elif value == 21:
|
|
158
|
+
return DeviceType.YUKA_MN100
|
|
159
|
+
elif value == 22:
|
|
160
|
+
return DeviceType.RTKNB
|
|
161
|
+
elif value == 23:
|
|
162
|
+
return DeviceType.LUBA_MB
|
|
163
|
+
elif value == 24:
|
|
164
|
+
return DeviceType.CM900
|
|
61
165
|
else:
|
|
62
166
|
return DeviceType.UNKNOWN
|
|
63
167
|
|
|
@@ -86,14 +190,52 @@ class DeviceType(Enum):
|
|
|
86
190
|
return DeviceType.RTK
|
|
87
191
|
elif DeviceType.LUBA_2.get_name() in substring2 or DeviceType.contain_luba_2_product_key(product_key):
|
|
88
192
|
return DeviceType.LUBA_2
|
|
89
|
-
elif DeviceType.
|
|
90
|
-
return DeviceType.
|
|
193
|
+
elif DeviceType.LUBA_LD.get_name() in substring2:
|
|
194
|
+
return DeviceType.LUBA_LD
|
|
195
|
+
elif DeviceType.LUBA_VP.get_name() in substring2:
|
|
196
|
+
return DeviceType.LUBA_VP
|
|
197
|
+
elif DeviceType.LUBA_MN.get_name() in substring2:
|
|
198
|
+
return DeviceType.LUBA_MN
|
|
199
|
+
elif DeviceType.YUKA_VP.get_name() in substring2:
|
|
200
|
+
return DeviceType.YUKA_VP
|
|
91
201
|
elif DeviceType.YUKA_MINI.get_name() in substring2:
|
|
92
202
|
return DeviceType.YUKA_MINI
|
|
93
203
|
elif DeviceType.YUKA_MINI2.get_name() in substring2:
|
|
94
204
|
return DeviceType.YUKA_MINI2
|
|
205
|
+
elif DeviceType.LUBA_YUKA.get_name() in substring2:
|
|
206
|
+
return DeviceType.LUBA_YUKA
|
|
95
207
|
elif DeviceType.LUBA.get_name() in substring2 or DeviceType.contain_luba_product_key(product_key):
|
|
96
208
|
return DeviceType.LUBA
|
|
209
|
+
elif DeviceType.SPINO.get_name() in substring2:
|
|
210
|
+
return DeviceType.SPINO
|
|
211
|
+
elif DeviceType.RTK3A1.get_name() in substring2:
|
|
212
|
+
return DeviceType.RTK3A1
|
|
213
|
+
elif DeviceType.RTK3A0.get_name() in substring2:
|
|
214
|
+
return DeviceType.RTK3A0
|
|
215
|
+
elif DeviceType.RTK3A2.get_name() in substring2:
|
|
216
|
+
return DeviceType.RTK3A2
|
|
217
|
+
elif DeviceType.YUKA_MINIV.get_name() in substring2:
|
|
218
|
+
return DeviceType.YUKA_MINIV
|
|
219
|
+
elif DeviceType.LUBA_VA.get_name() in substring2:
|
|
220
|
+
return DeviceType.LUBA_VA
|
|
221
|
+
elif DeviceType.YUKA_ML.get_name() in substring2:
|
|
222
|
+
return DeviceType.YUKA_ML
|
|
223
|
+
elif DeviceType.LUBA_MD.get_name() in substring2:
|
|
224
|
+
return DeviceType.LUBA_MD
|
|
225
|
+
elif DeviceType.LUBA_LA.get_name() in substring2:
|
|
226
|
+
return DeviceType.LUBA_LA
|
|
227
|
+
elif DeviceType.SWIMMINGPOOL_S1.get_name() in substring2:
|
|
228
|
+
return DeviceType.SWIMMINGPOOL_S1
|
|
229
|
+
elif DeviceType.SWIMMINGPOOL_E1.get_name() in substring2:
|
|
230
|
+
return DeviceType.SWIMMINGPOOL_E1
|
|
231
|
+
elif DeviceType.YUKA_MN100.get_name() in substring2:
|
|
232
|
+
return DeviceType.YUKA_MN100
|
|
233
|
+
elif DeviceType.RTKNB.get_name() in substring2:
|
|
234
|
+
return DeviceType.RTKNB
|
|
235
|
+
elif DeviceType.LUBA_MB.get_name() in substring2:
|
|
236
|
+
return DeviceType.LUBA_MB
|
|
237
|
+
elif DeviceType.CM900.get_name() in substring2:
|
|
238
|
+
return DeviceType.CM900
|
|
97
239
|
else:
|
|
98
240
|
return DeviceType.UNKNOWN
|
|
99
241
|
except Exception:
|
|
@@ -149,7 +291,7 @@ class DeviceType(Enum):
|
|
|
149
291
|
return device_type.get_value() == DeviceType.LUBA.get_value()
|
|
150
292
|
|
|
151
293
|
@staticmethod
|
|
152
|
-
def
|
|
294
|
+
def is_luba_pro(device_name: str, product_key: str = ""):
|
|
153
295
|
"""Check if the device type is LUBA 2 or higher based on the device name
|
|
154
296
|
and optional product key.
|
|
155
297
|
|
|
@@ -167,7 +309,11 @@ class DeviceType(Enum):
|
|
|
167
309
|
else:
|
|
168
310
|
device_type = DeviceType.value_of_str(device_name, product_key)
|
|
169
311
|
|
|
170
|
-
return
|
|
312
|
+
return (
|
|
313
|
+
device_type.get_value() >= DeviceType.LUBA_2.get_value()
|
|
314
|
+
and device_type.get_value() != DeviceType.SPINO.get_value()
|
|
315
|
+
and not DeviceType.is_rtk(device_name, product_key)
|
|
316
|
+
)
|
|
171
317
|
|
|
172
318
|
@staticmethod
|
|
173
319
|
def is_yuka(device_name: str):
|
|
@@ -183,8 +329,33 @@ class DeviceType(Enum):
|
|
|
183
329
|
|
|
184
330
|
return (
|
|
185
331
|
DeviceType.value_of_str(device_name).get_value() == DeviceType.LUBA_YUKA.get_value()
|
|
332
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_VP.get_value()
|
|
186
333
|
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI.get_value()
|
|
187
334
|
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI2.get_value()
|
|
335
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINIV.get_value()
|
|
336
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_ML.get_value()
|
|
337
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MN100.get_value()
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def is_yuka_mini(device_name: str):
|
|
342
|
+
return (
|
|
343
|
+
DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI.get_value()
|
|
344
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI2.get_value()
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
@staticmethod
|
|
348
|
+
def is_mini_or_x_series(device_name: str):
|
|
349
|
+
"""IsNewDeviceType returns if a device is part of the x or mini series."""
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI.get_value()
|
|
353
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI2.get_value()
|
|
354
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINIV.get_value()
|
|
355
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_VP.get_value()
|
|
356
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.LUBA_MN.get_value()
|
|
357
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.LUBA_VP.get_value()
|
|
358
|
+
or DeviceType.value_of_str(device_name).get_value() == DeviceType.LUBA_LD.get_value()
|
|
188
359
|
)
|
|
189
360
|
|
|
190
361
|
@staticmethod
|
|
@@ -209,7 +380,13 @@ class DeviceType(Enum):
|
|
|
209
380
|
else:
|
|
210
381
|
device_type = DeviceType.value_of_str(device_name, product_key)
|
|
211
382
|
|
|
212
|
-
return
|
|
383
|
+
return (
|
|
384
|
+
DeviceType.RTK.get_value() == device_type.get_value()
|
|
385
|
+
or DeviceType.RTK3A0.get_value() == device_type.get_value()
|
|
386
|
+
or DeviceType.RTK3A1.get_value() == device_type.get_value()
|
|
387
|
+
or DeviceType.RTK3A2.get_value() == device_type.get_value()
|
|
388
|
+
or DeviceType.RTKNB.get_value() == device_type.get_value()
|
|
389
|
+
)
|
|
213
390
|
|
|
214
391
|
@staticmethod
|
|
215
392
|
def contain_rtk_product_key(product_key) -> bool:
|
|
@@ -226,7 +403,7 @@ class DeviceType(Enum):
|
|
|
226
403
|
|
|
227
404
|
if not product_key:
|
|
228
405
|
return False
|
|
229
|
-
return product_key in
|
|
406
|
+
return product_key in RTKProductKey
|
|
230
407
|
|
|
231
408
|
@staticmethod
|
|
232
409
|
def contain_luba_product_key(product_key) -> bool:
|
|
@@ -242,19 +419,7 @@ class DeviceType(Enum):
|
|
|
242
419
|
|
|
243
420
|
if not product_key:
|
|
244
421
|
return False
|
|
245
|
-
return product_key in
|
|
246
|
-
"a1UBFdq6nNz",
|
|
247
|
-
"a1x0zHD3Xop",
|
|
248
|
-
"a1pvCnb3PPu",
|
|
249
|
-
"a1kweSOPylG",
|
|
250
|
-
"a1JFpmAV5Ur",
|
|
251
|
-
"a1BmXWlsdbA",
|
|
252
|
-
"a1jOhAYOIG8",
|
|
253
|
-
"a1K4Ki2L5rK",
|
|
254
|
-
"a1ae1QnXZGf",
|
|
255
|
-
"a1nf9kRBWoH",
|
|
256
|
-
"a1ZU6bdGjaM",
|
|
257
|
-
]
|
|
422
|
+
return product_key in LubaProductKey
|
|
258
423
|
|
|
259
424
|
@staticmethod
|
|
260
425
|
def contain_luba_2_product_key(product_key) -> bool:
|
|
@@ -270,7 +435,7 @@ class DeviceType(Enum):
|
|
|
270
435
|
|
|
271
436
|
if not product_key:
|
|
272
437
|
return False
|
|
273
|
-
return product_key in
|
|
438
|
+
return product_key in LubaVProductKey
|
|
274
439
|
|
|
275
440
|
@staticmethod
|
|
276
441
|
def contain_yuka_product_key(product_key) -> bool:
|
|
@@ -286,7 +451,39 @@ class DeviceType(Enum):
|
|
|
286
451
|
|
|
287
452
|
if not product_key:
|
|
288
453
|
return False
|
|
289
|
-
return product_key in
|
|
454
|
+
return product_key in YukaProductKey
|
|
455
|
+
|
|
456
|
+
@staticmethod
|
|
457
|
+
def contain_yuka_mini_product_key(product_key) -> bool:
|
|
458
|
+
"""Check if the given product key is present in a predefined list.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
product_key (str): The product key to be checked.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
bool: True if the product key is in the predefined list, False otherwise.
|
|
465
|
+
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
if not product_key:
|
|
469
|
+
return False
|
|
470
|
+
return product_key in YukaMiniProductKey
|
|
471
|
+
|
|
472
|
+
@staticmethod
|
|
473
|
+
def contain_yuka_plus_product_key(product_key) -> bool:
|
|
474
|
+
"""Check if the given product key is present in a predefined list.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
product_key (str): The product key to be checked.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
bool: True if the product key is in the predefined list, False otherwise.
|
|
481
|
+
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
if not product_key:
|
|
485
|
+
return False
|
|
486
|
+
return product_key in YukaPlusProductKey
|
|
290
487
|
|
|
291
488
|
def is_support_video(self):
|
|
292
489
|
return self != DeviceType.LUBA
|
pymammotion/utility/map.py
CHANGED
|
@@ -1,72 +1,259 @@
|
|
|
1
1
|
import math
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
from numpy.typing import NDArray
|
|
4
5
|
|
|
5
|
-
from pymammotion.data.model.location import
|
|
6
|
+
from pymammotion.data.model.location import LocationPoint
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class CoordinateConverter:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
"""Converts between ENU (East-North-Up) and LLA (Latitude-Longitude-Altitude) coordinate systems.
|
|
11
|
+
|
|
12
|
+
Uses WGS84 ellipsoid model for Earth coordinate transformations.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# WGS84 ellipsoid constants
|
|
16
|
+
WGS84A: float = 6378137.0 # Semi-major axis (equatorial radius) in meters
|
|
17
|
+
FLATTENING: float = 0.0033528106647474805 # WGS84 flattening factor
|
|
18
|
+
|
|
19
|
+
def __init__(self, latitude_rad: float, longitude_rad: float, yaw_rad: float = 0.0) -> None:
|
|
20
|
+
"""Initialize the coordinate converter.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
latitude_rad: Reference latitude in radians
|
|
24
|
+
longitude_rad: Reference longitude in radians
|
|
25
|
+
yaw_rad: Reference yaw angle in radians (default: 0.0)
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
# WGS84 ellipsoid parameters
|
|
29
|
+
self.semi_major_axis: float = self.WGS84A
|
|
30
|
+
self.flattening: float = self.FLATTENING
|
|
31
|
+
self.semi_minor_axis: float = (1.0 - self.flattening) * self.WGS84A
|
|
32
|
+
|
|
33
|
+
# Eccentricity parameters
|
|
34
|
+
self.first_eccentricity_squared: float = (2.0 - self.flattening) * self.flattening
|
|
35
|
+
self.eccentricity_ratio_squared: float = (1.0 - self.flattening) * (1.0 - self.flattening)
|
|
36
|
+
self.second_eccentricity_squared: float = (self.semi_major_axis**2 - self.semi_minor_axis**2) / (
|
|
37
|
+
self.semi_minor_axis**2
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Rotation matrix for coordinate transformation
|
|
41
|
+
self.rotation_matrix: NDArray[np.float64] = np.zeros((3, 3), dtype=np.float64)
|
|
42
|
+
|
|
43
|
+
# ECEF origin coordinates
|
|
44
|
+
self.x0: float = 0.0
|
|
45
|
+
self.y0: float = 0.0
|
|
46
|
+
self.z0: float = 0.0
|
|
47
|
+
|
|
48
|
+
# Yaw angle
|
|
49
|
+
self.yaw: float = yaw_rad
|
|
50
|
+
|
|
51
|
+
# Initialize with provided reference point
|
|
52
|
+
self.set_init_lla(latitude_rad, longitude_rad, yaw_rad)
|
|
53
|
+
|
|
54
|
+
def set_init_lla(self, latitude_rad: float, longitude_rad: float, yaw_rad: float = 0.0) -> None:
|
|
55
|
+
"""Set the initial LLA reference point and yaw angle.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
latitude_rad: Reference latitude in radians
|
|
59
|
+
longitude_rad: Reference longitude in radians
|
|
60
|
+
yaw_rad: Reference yaw angle in radians (default: 0.0)
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
self.yaw = yaw_rad
|
|
64
|
+
|
|
65
|
+
sin_lat = math.sin(latitude_rad)
|
|
66
|
+
cos_lat = math.cos(latitude_rad)
|
|
67
|
+
sin_lon = math.sin(longitude_rad)
|
|
68
|
+
cos_lon = math.cos(longitude_rad)
|
|
69
|
+
|
|
70
|
+
# Radius of curvature in the prime vertical
|
|
71
|
+
N = self.semi_major_axis / math.sqrt(1.0 - self.first_eccentricity_squared * (sin_lat**2))
|
|
72
|
+
|
|
73
|
+
# Calculate ECEF origin coordinates (altitude = 0)
|
|
74
|
+
horizontal_distance = N * cos_lat
|
|
75
|
+
self.x0 = horizontal_distance * cos_lon
|
|
76
|
+
self.y0 = horizontal_distance * sin_lon
|
|
77
|
+
self.z0 = self.eccentricity_ratio_squared * N * sin_lat
|
|
78
|
+
|
|
79
|
+
# Build rotation matrix (ECEF to ENU)
|
|
80
|
+
self.rotation_matrix[0, 0] = -sin_lon
|
|
81
|
+
self.rotation_matrix[0, 1] = cos_lon
|
|
82
|
+
self.rotation_matrix[0, 2] = 0.0
|
|
83
|
+
|
|
84
|
+
self.rotation_matrix[1, 0] = -cos_lon * sin_lat
|
|
85
|
+
self.rotation_matrix[1, 1] = -sin_lon * sin_lat
|
|
86
|
+
self.rotation_matrix[1, 2] = cos_lat
|
|
87
|
+
|
|
88
|
+
self.rotation_matrix[2, 0] = cos_lon * cos_lat
|
|
89
|
+
self.rotation_matrix[2, 1] = sin_lon * cos_lat
|
|
90
|
+
self.rotation_matrix[2, 2] = sin_lat
|
|
91
|
+
|
|
92
|
+
def enu_to_lla(self, east: float, north: float) -> LocationPoint:
|
|
93
|
+
"""Convert ENU (East-North-Up) coordinates to LLA (Latitude-Longitude-Altitude).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
east: East coordinate in meters
|
|
97
|
+
north: North coordinate in meters
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Point with latitude and longitude in degrees
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
# Transform ENU to ECEF (Earth-Centered, Earth-Fixed) coordinates
|
|
104
|
+
# using rotation matrix and origin offset
|
|
105
|
+
ecef_x = self.rotation_matrix[0, 0] * north + self.rotation_matrix[1, 0] * east + self.x0
|
|
106
|
+
ecef_y = self.rotation_matrix[0, 1] * north + self.rotation_matrix[1, 1] * east + self.y0
|
|
107
|
+
ecef_z = self.rotation_matrix[0, 2] * north + self.rotation_matrix[1, 2] * east + self.z0
|
|
108
|
+
|
|
109
|
+
# Calculate horizontal distance from Earth's axis
|
|
110
|
+
horizontal_distance = math.hypot(ecef_x, ecef_y)
|
|
111
|
+
|
|
112
|
+
# Initial latitude estimate using simplified formula
|
|
113
|
+
initial_latitude = math.atan2(self.semi_major_axis * ecef_z, self.semi_minor_axis * horizontal_distance)
|
|
114
|
+
|
|
115
|
+
sin_initial_lat = math.sin(initial_latitude)
|
|
116
|
+
cos_initial_lat = math.cos(initial_latitude)
|
|
117
|
+
|
|
118
|
+
# Calculate longitude (straightforward conversion from ECEF)
|
|
119
|
+
longitude_deg = math.degrees(math.atan2(ecef_y, ecef_x))
|
|
120
|
+
|
|
121
|
+
# Calculate precise latitude using iterative correction
|
|
122
|
+
# This accounts for Earth's ellipsoidal shape
|
|
123
|
+
latitude_rad = math.atan2(
|
|
124
|
+
ecef_z + self.second_eccentricity_squared * self.semi_minor_axis * (sin_initial_lat**3),
|
|
125
|
+
horizontal_distance - self.first_eccentricity_squared * self.semi_major_axis * (cos_initial_lat**3),
|
|
126
|
+
)
|
|
127
|
+
latitude_deg = math.degrees(latitude_rad)
|
|
128
|
+
|
|
129
|
+
return LocationPoint(latitude=latitude_deg, longitude=longitude_deg)
|
|
130
|
+
|
|
131
|
+
def lla_to_enu(self, longitude_deg: float, latitude_deg: float) -> list[float]:
|
|
132
|
+
"""Convert LLA (Latitude-Longitude-Altitude) to ENU (East-North-Up) coordinates.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
longitude_deg: Longitude in degrees
|
|
136
|
+
latitude_deg: Latitude in degrees
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of [east, north] coordinates in meters
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
# Convert to radians
|
|
143
|
+
lat_rad = math.radians(latitude_deg)
|
|
144
|
+
lon_rad = math.radians(longitude_deg)
|
|
145
|
+
|
|
30
146
|
sin_lat = math.sin(lat_rad)
|
|
31
147
|
cos_lat = math.cos(lat_rad)
|
|
32
148
|
sin_lon = math.sin(lon_rad)
|
|
33
149
|
cos_lon = math.cos(lon_rad)
|
|
34
150
|
|
|
35
|
-
|
|
36
|
-
|
|
151
|
+
# Calculate radius of curvature
|
|
152
|
+
N = self.semi_major_axis / math.sqrt(1.0 - self.first_eccentricity_squared * (sin_lat**2))
|
|
37
153
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
154
|
+
# Convert to ECEF (altitude = 0)
|
|
155
|
+
horizontal = N * cos_lat
|
|
156
|
+
ecef_x = horizontal * cos_lon
|
|
157
|
+
ecef_y = horizontal * sin_lon
|
|
158
|
+
ecef_z = self.eccentricity_ratio_squared * N * sin_lat
|
|
41
159
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
160
|
+
# Translate to origin
|
|
161
|
+
dx = ecef_x - self.x0
|
|
162
|
+
dy = ecef_y - self.y0
|
|
163
|
+
dz = ecef_z - self.z0
|
|
45
164
|
|
|
46
|
-
|
|
47
|
-
self.
|
|
48
|
-
self.
|
|
165
|
+
# Rotate to ENU frame
|
|
166
|
+
east = self.rotation_matrix[0, 0] * dx + self.rotation_matrix[0, 1] * dy + self.rotation_matrix[0, 2] * dz
|
|
167
|
+
north = self.rotation_matrix[1, 0] * dx + self.rotation_matrix[1, 1] * dy + self.rotation_matrix[1, 2] * dz
|
|
49
168
|
|
|
50
|
-
|
|
51
|
-
self.
|
|
52
|
-
self.
|
|
169
|
+
# Apply yaw rotation (inverse)
|
|
170
|
+
rotated_east = math.cos(-self.yaw) * east - math.sin(-self.yaw) * north
|
|
171
|
+
rotated_north = math.sin(-self.yaw) * east + math.cos(-self.yaw) * north
|
|
53
172
|
|
|
54
|
-
|
|
55
|
-
d3 = self.R_[0][0] * n + self.R_[1][0] * e + self.x0_
|
|
56
|
-
d4 = self.R_[0][1] * n + self.R_[1][1] * e + self.y0_
|
|
57
|
-
d5 = self.R_[0][2] * n + self.R_[1][2] * e + self.z0_
|
|
173
|
+
return [round(rotated_east, 3), round(rotated_north, 3)]
|
|
58
174
|
|
|
59
|
-
|
|
60
|
-
|
|
175
|
+
def get_transform_yaw_with_yaw(self, yaw_degrees: float) -> float:
|
|
176
|
+
"""Transform a yaw angle from global coordinates to local coordinates.
|
|
61
177
|
|
|
62
|
-
|
|
63
|
-
|
|
178
|
+
This applies the inverse of the reference yaw rotation to convert a global
|
|
179
|
+
heading angle into the local coordinate frame.
|
|
64
180
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
181
|
+
Args:
|
|
182
|
+
yaw_degrees: Input yaw angle in degrees
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Transformed yaw angle in degrees
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
# Convert input angle to radians
|
|
189
|
+
yaw_rad = math.radians(yaw_degrees)
|
|
190
|
+
|
|
191
|
+
# Apply inverse rotation: -self.yaw
|
|
192
|
+
inverse_yaw = -self.yaw
|
|
193
|
+
|
|
194
|
+
# Using angle addition formula: atan2(sin(a+b), cos(a+b))
|
|
195
|
+
# where a = inverse_yaw, b = yaw_rad
|
|
196
|
+
sin_sum = math.sin(inverse_yaw) * math.cos(yaw_rad) + math.cos(inverse_yaw) * math.sin(yaw_rad)
|
|
197
|
+
cos_sum = math.cos(inverse_yaw) * math.cos(yaw_rad) - math.sin(inverse_yaw) * math.sin(yaw_rad)
|
|
198
|
+
|
|
199
|
+
# Calculate resulting angle and convert back to degrees
|
|
200
|
+
result_rad = math.atan2(sin_sum, cos_sum)
|
|
201
|
+
result_degrees = math.degrees(result_rad)
|
|
202
|
+
|
|
203
|
+
return result_degrees
|
|
204
|
+
|
|
205
|
+
def get_angle_yaw(self) -> float:
|
|
206
|
+
"""Get the current yaw angle in degrees.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Yaw angle in degrees
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
return math.degrees(self.yaw)
|
|
213
|
+
|
|
214
|
+
def get_yaw(self) -> float:
|
|
215
|
+
"""Get the current yaw angle in radians.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Yaw angle in radians
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
return self.yaw
|
|
222
|
+
|
|
223
|
+
def set_yaw(self, yaw_rad: float) -> None:
|
|
224
|
+
"""Set the yaw angle.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
yaw_rad: Yaw angle in radians
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
self.yaw = yaw_rad
|
|
231
|
+
|
|
232
|
+
def set_yaw_degrees(self, yaw_degrees: float) -> None:
|
|
233
|
+
"""Set the yaw angle from degrees.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
yaw_degrees: Yaw angle in degrees
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
self.yaw = math.radians(yaw_degrees)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Usage example
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
# Initialize converter with reference point
|
|
245
|
+
converter = CoordinateConverter(
|
|
246
|
+
latitude_rad=math.radians(40.0), longitude_rad=math.radians(-105.0), yaw_rad=math.radians(45.0)
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Convert ENU to LLA
|
|
250
|
+
point = converter.enu_to_lla(east=100.0, north=200.0)
|
|
251
|
+
print(f"Latitude: {point.latitude}, Longitude: {point.longitude}")
|
|
252
|
+
|
|
253
|
+
# Convert LLA to ENU
|
|
254
|
+
enu = converter.lla_to_enu(longitude_deg=-105.0, latitude_deg=40.0)
|
|
255
|
+
print(f"East: {enu[0]}, North: {enu[1]}")
|
|
71
256
|
|
|
72
|
-
|
|
257
|
+
# Transform yaw angle
|
|
258
|
+
transformed_yaw = converter.get_transform_yaw_with_yaw(90.0)
|
|
259
|
+
print(f"Transformed yaw: {transformed_yaw}°")
|