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.

Files changed (135) hide show
  1. pymammotion/__init__.py +9 -6
  2. pymammotion/aliyun/client.py +235 -0
  3. pymammotion/aliyun/cloud_gateway.py +320 -69
  4. pymammotion/aliyun/model/aep_response.py +1 -2
  5. pymammotion/aliyun/model/dev_by_account_response.py +170 -23
  6. pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
  7. pymammotion/aliyun/model/regions_response.py +3 -3
  8. pymammotion/aliyun/model/session_by_authcode_response.py +2 -2
  9. pymammotion/aliyun/model/thing_response.py +12 -0
  10. pymammotion/aliyun/regions.py +62 -0
  11. pymammotion/aliyun/tea/core.py +297 -0
  12. pymammotion/bluetooth/ble.py +11 -15
  13. pymammotion/bluetooth/ble_message.py +389 -106
  14. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  15. pymammotion/const.py +3 -0
  16. pymammotion/data/model/__init__.py +1 -2
  17. pymammotion/data/model/device.py +92 -240
  18. pymammotion/data/model/device_config.py +10 -24
  19. pymammotion/data/model/device_info.py +35 -0
  20. pymammotion/data/model/device_limits.py +49 -0
  21. pymammotion/data/model/enums.py +12 -2
  22. pymammotion/data/model/errors.py +12 -0
  23. pymammotion/data/model/events.py +14 -0
  24. pymammotion/data/model/generate_geojson.py +521 -0
  25. pymammotion/data/model/generate_route_information.py +3 -4
  26. pymammotion/data/model/hash_list.py +384 -48
  27. pymammotion/data/model/location.py +4 -4
  28. pymammotion/data/model/mowing_modes.py +24 -1
  29. pymammotion/data/model/raw_data.py +215 -0
  30. pymammotion/data/model/region_data.py +10 -11
  31. pymammotion/data/model/report_info.py +62 -6
  32. pymammotion/data/model/work.py +27 -0
  33. pymammotion/data/mower_state_manager.py +316 -0
  34. pymammotion/data/mqtt/event.py +73 -28
  35. pymammotion/data/mqtt/mammotion_properties.py +257 -0
  36. pymammotion/data/mqtt/properties.py +93 -78
  37. pymammotion/data/mqtt/status.py +18 -17
  38. pymammotion/event/event.py +32 -8
  39. pymammotion/homeassistant/__init__.py +3 -0
  40. pymammotion/homeassistant/mower_api.py +484 -0
  41. pymammotion/homeassistant/rtk_api.py +54 -0
  42. pymammotion/http/__init__.py +0 -0
  43. pymammotion/http/encryption.py +220 -0
  44. pymammotion/http/http.py +652 -44
  45. pymammotion/http/model/__init__.py +0 -0
  46. pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
  47. pymammotion/http/model/http.py +160 -9
  48. pymammotion/http/model/response_factory.py +61 -0
  49. pymammotion/http/model/rtk.py +16 -0
  50. pymammotion/mammotion/commands/abstract_message.py +7 -5
  51. pymammotion/mammotion/commands/mammotion_command.py +32 -3
  52. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  53. pymammotion/mammotion/commands/messages/driver.py +61 -29
  54. pymammotion/mammotion/commands/messages/media.py +68 -15
  55. pymammotion/mammotion/commands/messages/navigation.py +61 -25
  56. pymammotion/mammotion/commands/messages/network.py +93 -100
  57. pymammotion/mammotion/commands/messages/ota.py +18 -18
  58. pymammotion/mammotion/commands/messages/system.py +97 -72
  59. pymammotion/mammotion/commands/messages/video.py +17 -12
  60. pymammotion/mammotion/devices/__init__.py +27 -3
  61. pymammotion/mammotion/devices/base.py +50 -127
  62. pymammotion/mammotion/devices/mammotion.py +447 -212
  63. pymammotion/mammotion/devices/mammotion_bluetooth.py +105 -60
  64. pymammotion/mammotion/devices/mammotion_cloud.py +157 -105
  65. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  66. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  67. pymammotion/mammotion/devices/managers/managers.py +81 -0
  68. pymammotion/mammotion/devices/mower_device.py +124 -0
  69. pymammotion/mammotion/devices/mower_manager.py +107 -0
  70. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  71. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  72. pymammotion/mammotion/devices/rtk_device.py +50 -0
  73. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  74. pymammotion/mqtt/__init__.py +2 -1
  75. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  76. pymammotion/mqtt/linkkit/__init__.py +5 -0
  77. pymammotion/mqtt/linkkit/h2client.py +585 -0
  78. pymammotion/mqtt/linkkit/linkkit.py +3023 -0
  79. pymammotion/mqtt/mammotion_mqtt.py +176 -169
  80. pymammotion/mqtt/mqtt_models.py +66 -0
  81. pymammotion/proto/__init__.py +4839 -4
  82. pymammotion/proto/basestation.proto +8 -0
  83. pymammotion/proto/basestation_pb2.py +11 -9
  84. pymammotion/proto/basestation_pb2.pyi +16 -2
  85. pymammotion/proto/dev_net.proto +79 -55
  86. pymammotion/proto/dev_net_pb2.py +60 -56
  87. pymammotion/proto/dev_net_pb2.pyi +49 -6
  88. pymammotion/proto/luba_msg.proto +2 -1
  89. pymammotion/proto/luba_msg_pb2.py +6 -6
  90. pymammotion/proto/luba_msg_pb2.pyi +1 -0
  91. pymammotion/proto/luba_mul.proto +62 -1
  92. pymammotion/proto/luba_mul_pb2.py +38 -22
  93. pymammotion/proto/luba_mul_pb2.pyi +94 -7
  94. pymammotion/proto/mctrl_driver.proto +44 -4
  95. pymammotion/proto/mctrl_driver_pb2.py +26 -14
  96. pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
  97. pymammotion/proto/mctrl_nav.proto +97 -51
  98. pymammotion/proto/mctrl_nav_pb2.py +75 -67
  99. pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
  100. pymammotion/proto/mctrl_ota.proto +40 -2
  101. pymammotion/proto/mctrl_ota_pb2.py +23 -13
  102. pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
  103. pymammotion/proto/mctrl_pept.proto +8 -3
  104. pymammotion/proto/mctrl_pept_pb2.py +8 -6
  105. pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
  106. pymammotion/proto/mctrl_sys.proto +325 -86
  107. pymammotion/proto/mctrl_sys_pb2.py +162 -98
  108. pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
  109. pymammotion/proto/message_pool.py +3 -0
  110. pymammotion/proto/py.typed +0 -0
  111. pymammotion/utility/constant/device_constant.py +65 -21
  112. pymammotion/utility/datatype_converter.py +13 -12
  113. pymammotion/utility/device_config.py +755 -0
  114. pymammotion/utility/device_type.py +218 -21
  115. pymammotion/utility/map.py +238 -51
  116. pymammotion/utility/mur_mur_hash.py +159 -0
  117. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/METADATA +27 -31
  118. pymammotion-0.5.51.dist-info/RECORD +152 -0
  119. {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
  120. pymammotion/aliyun/cloud_service.py +0 -65
  121. pymammotion/data/model/plan.py +0 -58
  122. pymammotion/data/state_manager.py +0 -130
  123. pymammotion/proto/basestation.py +0 -59
  124. pymammotion/proto/common.py +0 -12
  125. pymammotion/proto/dev_net.py +0 -381
  126. pymammotion/proto/luba_msg.py +0 -81
  127. pymammotion/proto/luba_mul.py +0 -76
  128. pymammotion/proto/mctrl_driver.py +0 -100
  129. pymammotion/proto/mctrl_nav.py +0 -660
  130. pymammotion/proto/mctrl_ota.py +0 -48
  131. pymammotion/proto/mctrl_pept.py +0 -41
  132. pymammotion/proto/mctrl_sys.py +0 -574
  133. pymammotion-0.2.62.dist-info/RECORD +0 -125
  134. /pymammotion/{http/_init_.py → bluetooth/model/__init__.py} +0 -0
  135. {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.LUBA_YUKA.get_name() in substring2:
90
- return DeviceType.LUBA_YUKA
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 is_luba_2(device_name: str, product_key: str = ""):
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 device_type.get_value() >= DeviceType.LUBA_2.get_value()
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 DeviceType.RTK.get_value() <= device_type.get_value() < DeviceType.LUBA.get_value()
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 ["a1qXkZ5P39W", "a1Nc68bGZzX"]
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 ["a1iMygIwxFC", "a1LLmy1zc0j", "a1LLmy1zc0j"]
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 ["a1IQV0BrnXb"]
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
@@ -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 Point
6
+ from pymammotion.data.model.location import LocationPoint
6
7
 
7
8
 
8
9
  class CoordinateConverter:
9
- def __init__(self, latitude_rad: float, longitude_rad: float) -> None:
10
- # Initialize constants
11
- self.WGS84A = 6378137.0
12
- self.f_ = 3.3528106647474805e-21
13
- self.b_ = (1.0 - self.f_) * self.WGS84A
14
- self.e2_ = (2.0 - self.f_) * self.f_
15
- self.e2m_ = (1.0 - self.f_) * (1.0 - self.f_)
16
- self.ep2_ = ((self.WGS84A**2) - (self.b_**2)) / (self.b_**2)
17
-
18
- # Initialize rotation matrix
19
- self.R_ = np.zeros((3, 3))
20
-
21
- # Variables to store initial LLA
22
- self.x0_ = 0.0
23
- self.y0_ = 0.0
24
- self.z0_ = 0.0
25
-
26
- # Call set_init_lla with provided lat/lon
27
- self.set_init_lla(latitude_rad, longitude_rad)
28
-
29
- def set_init_lla(self, lat_rad, lon_rad) -> None:
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
- sqrt = self.WGS84A / math.sqrt(1.0 - (self.e2_ * (sin_lat**2)))
36
- d3 = sqrt * cos_lat
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
- self.x0_ = d3 * cos_lon
39
- self.y0_ = d3 * sin_lon
40
- self.z0_ = self.e2m_ * sqrt * sin_lat
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
- self.R_[0][0] = -sin_lon
43
- self.R_[0][1] = cos_lon
44
- self.R_[0][2] = 0.0
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
- self.R_[1][0] = -cos_lon * sin_lat
47
- self.R_[1][1] = -sin_lon * sin_lat
48
- self.R_[1][2] = cos_lat
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
- self.R_[2][0] = cos_lon * cos_lat
51
- self.R_[2][1] = sin_lon * cos_lat
52
- self.R_[2][2] = sin_lat
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
- def enu_to_lla(self, e, n) -> Point:
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
- hypot = math.hypot(d3, d4)
60
- atan2_lat = math.atan2(self.WGS84A * d5, self.b_ * hypot)
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
- sin_lat = math.sin(atan2_lat)
63
- cos_lat = math.cos(atan2_lat)
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
- lon = math.atan2(d4, d3) * 180.0 / math.pi
66
- lat = (
67
- math.atan2(d5 + self.ep2_ * self.b_ * (sin_lat**3), hypot - self.e2_ * self.WGS84A * (cos_lat**3))
68
- * 180.0
69
- / math.pi
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
- return Point(latitude=lat, longitude=lon)
257
+ # Transform yaw angle
258
+ transformed_yaw = converter.get_transform_yaw_with_yaw(90.0)
259
+ print(f"Transformed yaw: {transformed_yaw}°")