pymammotion 0.5.33__py3-none-any.whl → 0.5.40__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 (47) hide show
  1. pymammotion/__init__.py +3 -3
  2. pymammotion/aliyun/cloud_gateway.py +106 -18
  3. pymammotion/aliyun/model/dev_by_account_response.py +198 -20
  4. pymammotion/data/model/device.py +1 -0
  5. pymammotion/data/model/device_config.py +1 -1
  6. pymammotion/data/model/enums.py +3 -1
  7. pymammotion/data/model/generate_route_information.py +2 -2
  8. pymammotion/data/model/hash_list.py +105 -33
  9. pymammotion/data/model/region_data.py +4 -4
  10. pymammotion/data/{state_manager.py → mower_state_manager.py} +17 -7
  11. pymammotion/homeassistant/__init__.py +3 -0
  12. pymammotion/homeassistant/mower_api.py +446 -0
  13. pymammotion/homeassistant/rtk_api.py +54 -0
  14. pymammotion/http/http.py +118 -7
  15. pymammotion/http/model/http.py +77 -2
  16. pymammotion/http/model/response_factory.py +10 -4
  17. pymammotion/mammotion/commands/mammotion_command.py +6 -0
  18. pymammotion/mammotion/commands/messages/navigation.py +10 -6
  19. pymammotion/mammotion/devices/__init__.py +27 -3
  20. pymammotion/mammotion/devices/base.py +16 -138
  21. pymammotion/mammotion/devices/mammotion.py +361 -204
  22. pymammotion/mammotion/devices/mammotion_bluetooth.py +7 -5
  23. pymammotion/mammotion/devices/mammotion_cloud.py +22 -74
  24. pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
  25. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  26. pymammotion/mammotion/devices/managers/managers.py +81 -0
  27. pymammotion/mammotion/devices/mower_device.py +121 -0
  28. pymammotion/mammotion/devices/mower_manager.py +107 -0
  29. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  30. pymammotion/mammotion/devices/rtk_cloud.py +113 -0
  31. pymammotion/mammotion/devices/rtk_device.py +50 -0
  32. pymammotion/mammotion/devices/rtk_manager.py +122 -0
  33. pymammotion/mqtt/__init__.py +2 -1
  34. pymammotion/mqtt/aliyun_mqtt.py +232 -0
  35. pymammotion/mqtt/mammotion_mqtt.py +132 -194
  36. pymammotion/mqtt/mqtt_models.py +66 -0
  37. pymammotion/proto/__init__.py +1 -1
  38. pymammotion/proto/mctrl_nav.proto +1 -1
  39. pymammotion/proto/mctrl_nav_pb2.py +1 -1
  40. pymammotion/proto/mctrl_nav_pb2.pyi +4 -4
  41. pymammotion/proto/mctrl_sys.proto +1 -1
  42. pymammotion/utility/device_type.py +88 -3
  43. pymammotion/utility/mur_mur_hash.py +132 -87
  44. {pymammotion-0.5.33.dist-info → pymammotion-0.5.40.dist-info}/METADATA +25 -31
  45. {pymammotion-0.5.33.dist-info → pymammotion-0.5.40.dist-info}/RECORD +54 -40
  46. {pymammotion-0.5.33.dist-info → pymammotion-0.5.40.dist-info}/WHEEL +1 -1
  47. {pymammotion-0.5.33.dist-info → pymammotion-0.5.40.dist-info/licenses}/LICENSE +0 -0
@@ -22,13 +22,31 @@ Luba2MiniProductKey = ["a1L5ZfJIxGl", "a1dCWYFLROK"]
22
22
 
23
23
  YukaProductKey = ["a1kT0TlYEza", "a1IQV0BrnXb"]
24
24
 
25
- YukaPlusProductKey = ["a1lNESu9VST"]
25
+ YukaPlusProductKey = ["a1lNESu9VST", "a1zAEzmvWDa"]
26
26
 
27
27
  YukaMiniProductKey = ["a1BqmEWMRbX", "a1biqVGvxrE"]
28
28
 
29
29
  RTKProductKey = ["a1qXkZ5P39W", "a1Nc68bGZzX"]
30
30
 
31
- YukaMVProductKey = ["a1jFe8HzcDb"]
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"]
32
50
 
33
51
 
34
52
  class DeviceType(Enum):
@@ -47,7 +65,17 @@ class DeviceType(Enum):
47
65
  LUBA_LD = (11, "Luba-LD", "HM431")
48
66
  RTK3A0 = (12, "RBSA0", "RBS03A0")
49
67
  RTK3A2 = (13, "RBSA2", "RBS03A2")
50
- YUKA_MINIVP = (14, "Yuka-MV", "MN231")
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")
51
79
 
52
80
  def __init__(self, value: int, name: str, model: str) -> None:
53
81
  self._value = value
@@ -112,6 +140,28 @@ class DeviceType(Enum):
112
140
  return DeviceType.RTK3A0
113
141
  elif value == 13:
114
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
115
165
  else:
116
166
  return DeviceType.UNKNOWN
117
167
 
@@ -156,6 +206,36 @@ class DeviceType(Enum):
156
206
  return DeviceType.LUBA_YUKA
157
207
  elif DeviceType.LUBA.get_name() in substring2 or DeviceType.contain_luba_product_key(product_key):
158
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
159
239
  else:
160
240
  return DeviceType.UNKNOWN
161
241
  except Exception:
@@ -252,6 +332,9 @@ class DeviceType(Enum):
252
332
  or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_VP.get_value()
253
333
  or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI.get_value()
254
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()
255
338
  )
256
339
 
257
340
  @staticmethod
@@ -268,6 +351,7 @@ class DeviceType(Enum):
268
351
  return (
269
352
  DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_MINI.get_value()
270
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()
271
355
  or DeviceType.value_of_str(device_name).get_value() == DeviceType.YUKA_VP.get_value()
272
356
  or DeviceType.value_of_str(device_name).get_value() == DeviceType.LUBA_MN.get_value()
273
357
  or DeviceType.value_of_str(device_name).get_value() == DeviceType.LUBA_VP.get_value()
@@ -301,6 +385,7 @@ class DeviceType(Enum):
301
385
  or DeviceType.RTK3A0.get_value() == device_type.get_value()
302
386
  or DeviceType.RTK3A1.get_value() == device_type.get_value()
303
387
  or DeviceType.RTK3A2.get_value() == device_type.get_value()
388
+ or DeviceType.RTKNB.get_value() == device_type.get_value()
304
389
  )
305
390
 
306
391
  @staticmethod
@@ -2,113 +2,158 @@ import struct
2
2
 
3
3
 
4
4
  class MurMurHashUtil:
5
+ MASK_32 = 0xFFFFFFFF
6
+ MULTIPLIER = 1540483477
7
+
5
8
  @staticmethod
6
- def get_unsigned_int(i):
7
- return i & 0xFFFFFFFF
9
+ def get_unsigned_int(i: int) -> int:
10
+ """Convert signed int to unsigned (32-bit)"""
11
+ return i & MurMurHashUtil.MASK_32
8
12
 
9
13
  @staticmethod
10
- def hash(byte_arr: bytes):
11
- # Create a bytearray view with little endian order
12
- position = 0
13
- remaining_bytes = len(byte_arr)
14
+ def hash(data: bytes) -> int:
15
+ """MurmurHash2 64-bit implementation"""
16
+ pos = 0
17
+ data_len = len(data)
14
18
 
15
- # Initial values
16
- remaining = remaining_bytes ^ 97
19
+ remaining = data_len ^ 97
17
20
  j = 0
18
21
 
19
22
  # Process 8 bytes at a time
20
- while remaining_bytes >= 8:
21
- multiplier = 1540483477
22
-
23
- # First 4 bytes
24
- unsigned_int = struct.unpack_from("<I", byte_arr, position)[0]
25
- position += 4
26
- unsigned_int = (MurMurHashUtil.get_unsigned_int(unsigned_int) * multiplier) & 0xFFFFFFFF
27
- remaining = (
28
- ((remaining * multiplier) & 0xFFFFFFFF)
29
- ^ (((unsigned_int ^ (unsigned_int >> 24)) & 0xFFFFFFFF) * multiplier) & 0xFFFFFFFF
30
- ) & 0xFFFFFFFF
31
-
32
- # Next 4 bytes
33
- unsigned_int2 = struct.unpack_from("<I", byte_arr, position)[0]
34
- position += 4
35
- unsigned_int2 = (MurMurHashUtil.get_unsigned_int(unsigned_int2) * multiplier) & 0xFFFFFFFF
36
- j = (
37
- ((j * multiplier) & 0xFFFFFFFF)
38
- ^ ((((unsigned_int2 ^ (unsigned_int2 >> 24)) & 0xFFFFFFFF) * multiplier) & 0xFFFFFFFF)
39
- ) & 0xFFFFFFFF
40
-
41
- remaining_bytes -= 8
23
+ while (data_len - pos) >= 8:
24
+ val1 = struct.unpack_from("<i", data, pos)[0]
25
+ pos += 4
26
+
27
+ unsigned_int_1 = MurMurHashUtil.get_unsigned_int(val1)
28
+ temp1 = (unsigned_int_1 * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
29
+ temp2 = (temp1 ^ (temp1 >> 24)) & MurMurHashUtil.MASK_32
30
+ temp3 = (temp2 * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
31
+
32
+ remaining = ((remaining * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32) ^ temp3
33
+ remaining = remaining & MurMurHashUtil.MASK_32
34
+
35
+ val2 = struct.unpack_from("<i", data, pos)[0]
36
+ pos += 4
37
+
38
+ unsigned_int_2 = MurMurHashUtil.get_unsigned_int(val2)
39
+ temp1 = (unsigned_int_2 * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
40
+ temp2 = (temp1 ^ (temp1 >> 24)) & MurMurHashUtil.MASK_32
41
+ temp3 = (temp2 * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
42
+
43
+ j = ((j * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32) ^ temp3
44
+ j = j & MurMurHashUtil.MASK_32
42
45
 
43
46
  # Process remaining 4 bytes if available
44
- if remaining_bytes >= 4:
45
- multiplier = 1540483477
46
- unsigned_int3 = struct.unpack_from("<I", byte_arr, position)[0]
47
- position += 4
48
- unsigned_int3 = (MurMurHashUtil.get_unsigned_int(unsigned_int3) * multiplier) & 0xFFFFFFFF
49
- remaining = (
50
- ((remaining * multiplier) & 0xFFFFFFFF)
51
- ^ ((((unsigned_int3 ^ (unsigned_int3 >> 24)) & 0xFFFFFFFF) * multiplier) & 0xFFFFFFFF)
52
- ) & 0xFFFFFFFF
53
- remaining_bytes -= 4
54
-
55
- # Process final 1-3 bytes if available
56
- if remaining_bytes == 1:
57
- j = (
58
- ((j ^ (MurMurHashUtil.get_unsigned_int(byte_arr[position]) & 0xFF)) & 0xFFFFFFFF) * 1540483477
59
- ) & 0xFFFFFFFF
60
- elif remaining_bytes == 2:
61
- j = (j ^ ((MurMurHashUtil.get_unsigned_int(byte_arr[position + 1]) & 0xFF) << 8)) & 0xFFFFFFFF
62
- j = (
63
- ((j ^ (MurMurHashUtil.get_unsigned_int(byte_arr[position]) & 0xFF)) & 0xFFFFFFFF) * 1540483477
64
- ) & 0xFFFFFFFF
65
- elif remaining_bytes == 3:
66
- j = (j ^ ((MurMurHashUtil.get_unsigned_int(byte_arr[position + 2]) & 0xFF) << 16)) & 0xFFFFFFFF
67
- j = (j ^ ((MurMurHashUtil.get_unsigned_int(byte_arr[position + 1]) & 0xFF) << 8)) & 0xFFFFFFFF
68
- j = (
69
- ((j ^ (MurMurHashUtil.get_unsigned_int(byte_arr[position]) & 0xFF)) & 0xFFFFFFFF) * 1540483477
70
- ) & 0xFFFFFFFF
71
-
72
- # Final mixing
73
- multiplier = 1540483477
74
- j5 = (((remaining ^ (j >> 18)) & 0xFFFFFFFF) * multiplier) & 0xFFFFFFFF
75
- j6 = (((j ^ (j5 >> 22)) & 0xFFFFFFFF) * multiplier) & 0xFFFFFFFF
76
- j7 = (((j5 ^ (j6 >> 17)) & 0xFFFFFFFF) * multiplier) & 0xFFFFFFFF
47
+ if (data_len - pos) >= 4:
48
+ val = struct.unpack_from("<i", data, pos)[0]
49
+ pos += 4
50
+
51
+ unsigned_int_3 = MurMurHashUtil.get_unsigned_int(val)
52
+ temp1 = (unsigned_int_3 * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
53
+ temp2 = (temp1 ^ (temp1 >> 24)) & MurMurHashUtil.MASK_32
54
+ temp3 = (temp2 * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
55
+
56
+ remaining = ((remaining * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32) ^ temp3
57
+ remaining = remaining & MurMurHashUtil.MASK_32
58
+
59
+ # Process tail bytes (1-3 bytes)
60
+ bytes_remaining = data_len - pos
61
+
62
+ if bytes_remaining == 1:
63
+ byte_val = data[pos] if data[pos] < 128 else data[pos] - 256
64
+ j = (j ^ (MurMurHashUtil.get_unsigned_int(byte_val) & 255)) & MurMurHashUtil.MASK_32
65
+ j = (j * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
66
+
67
+ elif bytes_remaining == 2:
68
+ byte_val1 = data[pos + 1] if data[pos + 1] < 128 else data[pos + 1] - 256
69
+ j = (j ^ ((MurMurHashUtil.get_unsigned_int(byte_val1) & 255) << 8)) & MurMurHashUtil.MASK_32
70
+
71
+ byte_val0 = data[pos] if data[pos] < 128 else data[pos] - 256
72
+ j = (j ^ (MurMurHashUtil.get_unsigned_int(byte_val0) & 255)) & MurMurHashUtil.MASK_32
73
+ j = (j * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
74
+
75
+ elif bytes_remaining == 3:
76
+ byte_val2 = data[pos + 2] if data[pos + 2] < 128 else data[pos + 2] - 256
77
+ j = (j ^ ((MurMurHashUtil.get_unsigned_int(byte_val2) & 255) << 16)) & MurMurHashUtil.MASK_32
78
+
79
+ byte_val1 = data[pos + 1] if data[pos + 1] < 128 else data[pos + 1] - 256
80
+ j = (j ^ ((MurMurHashUtil.get_unsigned_int(byte_val1) & 255) << 8)) & MurMurHashUtil.MASK_32
81
+
82
+ byte_val0 = data[pos] if data[pos] < 128 else data[pos] - 256
83
+ j = (j ^ (MurMurHashUtil.get_unsigned_int(byte_val0) & 255)) & MurMurHashUtil.MASK_32
84
+ j = (j * MurMurHashUtil.MULTIPLIER) & MurMurHashUtil.MASK_32
85
+
86
+ # Final avalanche
87
+ j4 = MurMurHashUtil.MULTIPLIER
88
+
89
+ j5 = remaining ^ (j >> 18)
90
+ j5 = (j5 & MurMurHashUtil.MASK_32) * j4
91
+ j5 = j5 & MurMurHashUtil.MASK_32
92
+
93
+ j6 = j ^ (j5 >> 22)
94
+ j6 = (j6 & MurMurHashUtil.MASK_32) * j4
95
+ j6 = j6 & MurMurHashUtil.MASK_32
96
+
97
+ j7 = j5 ^ (j6 >> 17)
98
+ j7 = (j7 & MurMurHashUtil.MASK_32) * j4
99
+ j7 = j7 & MurMurHashUtil.MASK_32
100
+
101
+ # Combine high and low parts
77
102
  j8 = j7 << 32
78
103
 
79
- return j8 | ((((j6 ^ (j7 >> 19)) & 0xFFFFFFFF) * multiplier) & 0xFFFFFFFF)
104
+ low = j6 ^ (j7 >> 19)
105
+ low = (low & MurMurHashUtil.MASK_32) * j4
106
+ low = low & MurMurHashUtil.MASK_32
80
107
 
81
- @staticmethod
82
- def hash_unsigned(s: str | bytes) -> None:
83
- if isinstance(s, str):
84
- return MurMurHashUtil.read_unsigned_long(MurMurHashUtil.hash(s.encode()))
85
- elif isinstance(s, bytes) or isinstance(s, bytearray):
86
- return MurMurHashUtil.read_unsigned_long(MurMurHashUtil.hash(s))
87
- return None
108
+ result = j8 | low
109
+
110
+ # Convert to signed 64-bit
111
+ if result > 0x7FFFFFFFFFFFFFFF:
112
+ result = result - 0x10000000000000000
113
+
114
+ return result
88
115
 
89
116
  @staticmethod
90
- def long2bytes(value: int) -> bytes:
91
- # Convert long to 8 bytes in little-endian order
92
- result = bytearray(8)
93
- for i in range(8):
94
- result[7 - i] = (value >> (i * 8)) & 0xFF
95
- return bytes(result)
117
+ def hash_string(s: str) -> int:
118
+ """Hash a string using UTF-8 encoding"""
119
+ return MurMurHashUtil.hash(s.encode("utf-8"))
96
120
 
97
121
  @staticmethod
98
122
  def read_unsigned_long(value: int) -> int:
123
+ """Convert to unsigned by masking with Long.MAX_VALUE"""
99
124
  return value & 0x7FFFFFFFFFFFFFFF
100
125
 
101
126
  @staticmethod
102
- def hash_unsigned_list(long_list: list[int]):
103
- byte_arr = b""
104
- for i in range(len(long_list)):
105
- if i == 0:
106
- byte_arr = MurMurHashUtil.long2bytes(long_list[i])
107
- else:
108
- byte_arr += MurMurHashUtil.long2bytes(long_list[i])
127
+ def hash_unsigned(data) -> int:
128
+ """Get unsigned hash value
129
+ Can accept bytes or string
130
+ """
131
+ if isinstance(data, str):
132
+ hash_val = MurMurHashUtil.hash_string(data)
133
+ else:
134
+ hash_val = MurMurHashUtil.hash(data)
109
135
 
110
- return MurMurHashUtil.read_unsigned_long(MurMurHashUtil.hash(byte_arr))
136
+ return MurMurHashUtil.read_unsigned_long(hash_val)
111
137
 
112
138
  @staticmethod
113
- def hash_string(s: str):
114
- return MurMurHashUtil.hash(s.encode())
139
+ def long_to_bytes(value: int) -> bytes:
140
+ """Convert long to bytes exactly as Java does:
141
+ 1. Pack as big-endian (ByteBuffer default)
142
+ 2. Reverse all bytes
143
+ """
144
+ if value < 0:
145
+ value = value & 0xFFFFFFFFFFFFFFFF
146
+
147
+ big_endian = struct.pack(">Q", value)
148
+ return big_endian[::-1]
149
+
150
+ @staticmethod
151
+ def hash_unsigned_list(values: list[int]) -> int:
152
+ """Hash a list of long values"""
153
+ data = b""
154
+
155
+ for val in values:
156
+ data += MurMurHashUtil.long_to_bytes(val)
157
+
158
+ hash_val = MurMurHashUtil.hash(data)
159
+ return MurMurHashUtil.read_unsigned_long(hash_val)
@@ -1,34 +1,29 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: pymammotion
3
- Version: 0.5.33
4
- Summary:
5
- License: GPL-3.0
6
- Author: Michael Arthur
7
- Author-email: michael@jumblesoft.co.nz
8
- Requires-Python: >=3.10
9
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Programming Language :: Python :: 3.13
14
- Requires-Dist: aiohttp (>=3.9.1)
15
- Requires-Dist: alibabacloud-apigateway-util (>=0.0.2,<0.0.3)
16
- Requires-Dist: alibabacloud-iot-api-gateway (>=0.0.4,<0.0.5)
17
- Requires-Dist: alicloud-gateway-iot (>=1.0.0,<2.0.0)
18
- Requires-Dist: async-timeout (>=4.0.3,<5.0.0)
19
- Requires-Dist: betterproto2 (>=0.8.0,<0.9.0)
20
- Requires-Dist: bleak (>=0.21.0)
21
- Requires-Dist: bleak-retry-connector (>=3.5.0)
22
- Requires-Dist: crcmod (>=1.7,<2.0)
23
- Requires-Dist: cryptography (>=43.0.1)
24
- Requires-Dist: grpclib (>=0.4.8,<0.5.0)
25
- Requires-Dist: jsonic (>=1.0.0,<2.0.0)
26
- Requires-Dist: mashumaro (>=3.13,<4.0)
27
- Requires-Dist: numpy (>=1.26.0)
28
- Requires-Dist: orjson (>=3.9.15,<4.0.0)
29
- Requires-Dist: paho-mqtt (>=2.1.0,<3.0.0)
30
- Requires-Dist: protobuf (>=4.23.1)
31
- Requires-Dist: py-jsonic (>=0.0.2,<0.0.3)
3
+ Version: 0.5.40
4
+ Author: jLynx
5
+ Author-email: Michael Arthur <michael@jumblesoft.co.nz>
6
+ License-Expression: GPL-3.0
7
+ License-File: LICENSE
8
+ Requires-Python: <4.0,>=3.12
9
+ Requires-Dist: aiohttp>=3.9.1
10
+ Requires-Dist: alibabacloud-apigateway-util<0.0.3,>=0.0.2
11
+ Requires-Dist: alibabacloud-iot-api-gateway<0.0.5,>=0.0.4
12
+ Requires-Dist: alicloud-gateway-iot<2,>=1.0.0
13
+ Requires-Dist: async-timeout<5,>=4.0.3
14
+ Requires-Dist: betterproto2<0.9,>=0.8.0
15
+ Requires-Dist: bleak-retry-connector>=3.5.0
16
+ Requires-Dist: bleak>=0.21.0
17
+ Requires-Dist: crcmod~=1.7
18
+ Requires-Dist: cryptography>=43.0.1
19
+ Requires-Dist: jsonic<2,>=1.0.0
20
+ Requires-Dist: mashumaro~=3.13
21
+ Requires-Dist: numpy>=1.26.0
22
+ Requires-Dist: orjson<4,>=3.9.15
23
+ Requires-Dist: paho-mqtt<3,>=2.1.0
24
+ Requires-Dist: protobuf>=4.23.1
25
+ Requires-Dist: py-jsonic<0.0.3,>=0.0.2
26
+ Requires-Dist: pyjwt>=2.10.1
32
27
  Description-Content-Type: text/markdown
33
28
 
34
29
  # PyMammotion - Python API for Mammotion Mowers [![Discord](https://img.shields.io/discord/1247286396297678879)](https://discord.gg/vpZdWhJX8x)
@@ -97,4 +92,3 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
97
92
 
98
93
  The trademarks "Mammotion," "Luba," and "Yuka" referenced herein are registered trademarks of their respective owners. The author of this software repository is not affiliated with, endorsed by, or connected to these trademark owners in any way.
99
94
 
100
-