pymammotion 0.5.69__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.
Files changed (154) hide show
  1. pymammotion/__init__.py +53 -0
  2. pymammotion/agora/__init__.py +0 -0
  3. pymammotion/agora/agora_api.py +755 -0
  4. pymammotion/agora/agora_rtc_capabilities.py +748 -0
  5. pymammotion/agora/agora_websockets.py +1175 -0
  6. pymammotion/aliyun/__init__.py +1 -0
  7. pymammotion/aliyun/client.py +235 -0
  8. pymammotion/aliyun/cloud_gateway.py +982 -0
  9. pymammotion/aliyun/model/aep_response.py +21 -0
  10. pymammotion/aliyun/model/connect_response.py +51 -0
  11. pymammotion/aliyun/model/dev_by_account_response.py +195 -0
  12. pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
  13. pymammotion/aliyun/model/regions_response.py +29 -0
  14. pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
  15. pymammotion/aliyun/model/thing_response.py +12 -0
  16. pymammotion/aliyun/regions.py +62 -0
  17. pymammotion/aliyun/tea/core.py +297 -0
  18. pymammotion/aliyun/tmp_constant.py +171 -0
  19. pymammotion/bluetooth/__init__.py +1 -0
  20. pymammotion/bluetooth/ble.py +62 -0
  21. pymammotion/bluetooth/ble_message.py +676 -0
  22. pymammotion/bluetooth/const.py +27 -0
  23. pymammotion/bluetooth/data/__init__.py +0 -0
  24. pymammotion/bluetooth/data/convert.py +25 -0
  25. pymammotion/bluetooth/data/framectrldata.py +40 -0
  26. pymammotion/bluetooth/data/notifydata.py +62 -0
  27. pymammotion/bluetooth/model/__init__.py +0 -0
  28. pymammotion/bluetooth/model/atomic_integer.py +54 -0
  29. pymammotion/const.py +13 -0
  30. pymammotion/data/__init__.py +0 -0
  31. pymammotion/data/model/__init__.py +8 -0
  32. pymammotion/data/model/account.py +8 -0
  33. pymammotion/data/model/device.py +192 -0
  34. pymammotion/data/model/device_config.py +72 -0
  35. pymammotion/data/model/device_info.py +60 -0
  36. pymammotion/data/model/device_limits.py +49 -0
  37. pymammotion/data/model/enums.py +77 -0
  38. pymammotion/data/model/errors.py +12 -0
  39. pymammotion/data/model/events.py +14 -0
  40. pymammotion/data/model/generate_geojson.py +565 -0
  41. pymammotion/data/model/generate_route_information.py +26 -0
  42. pymammotion/data/model/hash_list.py +475 -0
  43. pymammotion/data/model/location.py +36 -0
  44. pymammotion/data/model/mowing_modes.py +77 -0
  45. pymammotion/data/model/rapid_state.py +45 -0
  46. pymammotion/data/model/raw_data.py +215 -0
  47. pymammotion/data/model/region_data.py +102 -0
  48. pymammotion/data/model/report_info.py +182 -0
  49. pymammotion/data/model/work.py +27 -0
  50. pymammotion/data/mower_state_manager.py +369 -0
  51. pymammotion/data/mqtt/__init__.py +1 -0
  52. pymammotion/data/mqtt/event.py +227 -0
  53. pymammotion/data/mqtt/mammotion_properties.py +276 -0
  54. pymammotion/data/mqtt/properties.py +203 -0
  55. pymammotion/data/mqtt/status.py +57 -0
  56. pymammotion/event/__init__.py +6 -0
  57. pymammotion/event/event.py +96 -0
  58. pymammotion/homeassistant/__init__.py +3 -0
  59. pymammotion/homeassistant/mower_api.py +514 -0
  60. pymammotion/homeassistant/rtk_api.py +54 -0
  61. pymammotion/http/__init__.py +0 -0
  62. pymammotion/http/encryption.py +220 -0
  63. pymammotion/http/http.py +673 -0
  64. pymammotion/http/model/__init__.py +0 -0
  65. pymammotion/http/model/camera_stream.py +31 -0
  66. pymammotion/http/model/http.py +249 -0
  67. pymammotion/http/model/response_factory.py +61 -0
  68. pymammotion/http/model/rtk.py +16 -0
  69. pymammotion/mammotion/__init__.py +0 -0
  70. pymammotion/mammotion/commands/__init__.py +0 -0
  71. pymammotion/mammotion/commands/abstract_message.py +24 -0
  72. pymammotion/mammotion/commands/mammotion_command.py +81 -0
  73. pymammotion/mammotion/commands/messages/__init__.py +0 -0
  74. pymammotion/mammotion/commands/messages/basestation.py +43 -0
  75. pymammotion/mammotion/commands/messages/driver.py +122 -0
  76. pymammotion/mammotion/commands/messages/media.py +87 -0
  77. pymammotion/mammotion/commands/messages/navigation.py +564 -0
  78. pymammotion/mammotion/commands/messages/network.py +205 -0
  79. pymammotion/mammotion/commands/messages/ota.py +38 -0
  80. pymammotion/mammotion/commands/messages/system.py +330 -0
  81. pymammotion/mammotion/commands/messages/video.py +33 -0
  82. pymammotion/mammotion/control/__init__.py +0 -0
  83. pymammotion/mammotion/control/joystick.py +145 -0
  84. pymammotion/mammotion/devices/__init__.py +29 -0
  85. pymammotion/mammotion/devices/base.py +163 -0
  86. pymammotion/mammotion/devices/mammotion.py +571 -0
  87. pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
  88. pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
  89. pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
  90. pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
  91. pymammotion/mammotion/devices/managers/managers.py +81 -0
  92. pymammotion/mammotion/devices/mower_device.py +120 -0
  93. pymammotion/mammotion/devices/mower_manager.py +107 -0
  94. pymammotion/mammotion/devices/rtk_ble.py +89 -0
  95. pymammotion/mammotion/devices/rtk_cloud.py +115 -0
  96. pymammotion/mammotion/devices/rtk_device.py +50 -0
  97. pymammotion/mammotion/devices/rtk_manager.py +125 -0
  98. pymammotion/mqtt/__init__.py +6 -0
  99. pymammotion/mqtt/aliyun_mqtt.py +237 -0
  100. pymammotion/mqtt/linkkit/__init__.py +5 -0
  101. pymammotion/mqtt/linkkit/h2client.py +585 -0
  102. pymammotion/mqtt/linkkit/linkkit.py +3025 -0
  103. pymammotion/mqtt/mammotion_future.py +26 -0
  104. pymammotion/mqtt/mammotion_mqtt.py +214 -0
  105. pymammotion/mqtt/mqtt_models.py +66 -0
  106. pymammotion/proto/__init__.py +4841 -0
  107. pymammotion/proto/basestation.proto +51 -0
  108. pymammotion/proto/basestation_pb2.py +35 -0
  109. pymammotion/proto/basestation_pb2.pyi +89 -0
  110. pymammotion/proto/common.proto +7 -0
  111. pymammotion/proto/common_pb2.py +25 -0
  112. pymammotion/proto/common_pb2.pyi +13 -0
  113. pymammotion/proto/dev_net.proto +321 -0
  114. pymammotion/proto/dev_net_pb2.py +111 -0
  115. pymammotion/proto/dev_net_pb2.pyi +515 -0
  116. pymammotion/proto/luba_msg.proto +76 -0
  117. pymammotion/proto/luba_msg_pb2.py +41 -0
  118. pymammotion/proto/luba_msg_pb2.pyi +97 -0
  119. pymammotion/proto/luba_mul.proto +129 -0
  120. pymammotion/proto/luba_mul_pb2.py +61 -0
  121. pymammotion/proto/luba_mul_pb2.pyi +178 -0
  122. pymammotion/proto/mctrl_driver.proto +107 -0
  123. pymammotion/proto/mctrl_driver_pb2.py +57 -0
  124. pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
  125. pymammotion/proto/mctrl_nav.proto +591 -0
  126. pymammotion/proto/mctrl_nav_pb2.py +136 -0
  127. pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
  128. pymammotion/proto/mctrl_ota.proto +80 -0
  129. pymammotion/proto/mctrl_ota_pb2.py +45 -0
  130. pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
  131. pymammotion/proto/mctrl_pept.proto +34 -0
  132. pymammotion/proto/mctrl_pept_pb2.py +33 -0
  133. pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
  134. pymammotion/proto/mctrl_sys.proto +741 -0
  135. pymammotion/proto/mctrl_sys_pb2.py +206 -0
  136. pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
  137. pymammotion/proto/message_pool.py +3 -0
  138. pymammotion/proto/py.typed +0 -0
  139. pymammotion/py.typed +0 -0
  140. pymammotion/utility/constant/__init__.py +3 -0
  141. pymammotion/utility/constant/device_constant.py +315 -0
  142. pymammotion/utility/conversions.py +5 -0
  143. pymammotion/utility/datatype_converter.py +124 -0
  144. pymammotion/utility/device_config.py +755 -0
  145. pymammotion/utility/device_type.py +489 -0
  146. pymammotion/utility/map.py +259 -0
  147. pymammotion/utility/movement.py +18 -0
  148. pymammotion/utility/mur_mur_hash.py +159 -0
  149. pymammotion/utility/periodic.py +106 -0
  150. pymammotion/utility/rocker_util.py +194 -0
  151. pymammotion-0.5.69.dist-info/METADATA +93 -0
  152. pymammotion-0.5.69.dist-info/RECORD +154 -0
  153. pymammotion-0.5.69.dist-info/WHEEL +4 -0
  154. pymammotion-0.5.69.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,259 @@
1
+ import math
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+
6
+ from pymammotion.data.model.location import LocationPoint
7
+
8
+
9
+ class CoordinateConverter:
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
+
146
+ sin_lat = math.sin(lat_rad)
147
+ cos_lat = math.cos(lat_rad)
148
+ sin_lon = math.sin(lon_rad)
149
+ cos_lon = math.cos(lon_rad)
150
+
151
+ # Calculate radius of curvature
152
+ N = self.semi_major_axis / math.sqrt(1.0 - self.first_eccentricity_squared * (sin_lat**2))
153
+
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
159
+
160
+ # Translate to origin
161
+ dx = ecef_x - self.x0
162
+ dy = ecef_y - self.y0
163
+ dz = ecef_z - self.z0
164
+
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
168
+
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
172
+
173
+ return [round(rotated_east, 3), round(rotated_north, 3)]
174
+
175
+ def get_transform_yaw_with_yaw(self, yaw_degrees: float) -> float:
176
+ """Transform a yaw angle from global coordinates to local coordinates.
177
+
178
+ This applies the inverse of the reference yaw rotation to convert a global
179
+ heading angle into the local coordinate frame.
180
+
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]}")
256
+
257
+ # Transform yaw angle
258
+ transformed_yaw = converter.get_transform_yaw_with_yaw(90.0)
259
+ print(f"Transformed yaw: {transformed_yaw}°")
@@ -0,0 +1,18 @@
1
+ from .rocker_util import RockerControlUtil
2
+
3
+
4
+ def transform_both_speeds(linear: float, angular: float, linear_percent: float, angular_percent: float):
5
+ transfrom3 = RockerControlUtil.getInstance().transfrom3(linear, linear_percent)
6
+ transform4 = RockerControlUtil.getInstance().transfrom3(angular, angular_percent)
7
+
8
+ if transfrom3 is not None and len(transfrom3) > 0:
9
+ linear_speed = transfrom3[0] * 10
10
+ angular_speed = int(transform4[1] * 4.5)
11
+ return linear_speed, angular_speed
12
+
13
+
14
+ def get_percent(percent: float):
15
+ if percent <= 15.0:
16
+ return 0.0
17
+
18
+ return percent - 15.0
@@ -0,0 +1,159 @@
1
+ import struct
2
+
3
+
4
+ class MurMurHashUtil:
5
+ MASK_32 = 0xFFFFFFFF
6
+ MULTIPLIER = 1540483477
7
+
8
+ @staticmethod
9
+ def get_unsigned_int(i: int) -> int:
10
+ """Convert signed int to unsigned (32-bit)"""
11
+ return i & MurMurHashUtil.MASK_32
12
+
13
+ @staticmethod
14
+ def hash(data: bytes) -> int:
15
+ """MurmurHash2 64-bit implementation"""
16
+ pos = 0
17
+ data_len = len(data)
18
+
19
+ remaining = data_len ^ 97
20
+ j = 0
21
+
22
+ # Process 8 bytes at a time
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
45
+
46
+ # Process remaining 4 bytes if available
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
102
+ j8 = j7 << 32
103
+
104
+ low = j6 ^ (j7 >> 19)
105
+ low = (low & MurMurHashUtil.MASK_32) * j4
106
+ low = low & MurMurHashUtil.MASK_32
107
+
108
+ result = j8 | low
109
+
110
+ # Convert to signed 64-bit
111
+ if result > 0x7FFFFFFFFFFFFFFF:
112
+ result = result - 0x10000000000000000
113
+
114
+ return result
115
+
116
+ @staticmethod
117
+ def hash_string(s: str) -> int:
118
+ """Hash a string using UTF-8 encoding"""
119
+ return MurMurHashUtil.hash(s.encode("utf-8"))
120
+
121
+ @staticmethod
122
+ def read_unsigned_long(value: int) -> int:
123
+ """Convert to unsigned by masking with Long.MAX_VALUE"""
124
+ return value & 0x7FFFFFFFFFFFFFFF
125
+
126
+ @staticmethod
127
+ def hash_unsigned(data: str | bytes) -> 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)
135
+
136
+ return MurMurHashUtil.read_unsigned_long(hash_val)
137
+
138
+ @staticmethod
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)
@@ -0,0 +1,106 @@
1
+ import asyncio
2
+ from contextlib import suppress
3
+
4
+
5
+ class Periodic:
6
+ def __init__(self, func, time) -> None:
7
+ self.func = func
8
+ self.time = time
9
+ self.is_started = False
10
+ self._task = None
11
+
12
+ def start(self) -> None:
13
+ """Start the task if it is not already started.
14
+
15
+ If the task is not already started, it sets the 'is_started' flag to
16
+ True and initiates a task to call a function periodically using asyncio.
17
+ """
18
+
19
+ if not self.is_started:
20
+ self.is_started = True
21
+ # Start task to call func periodically:
22
+ self._task = asyncio.ensure_future(self._run())
23
+
24
+ async def stop(self) -> None:
25
+ """Stop the task if it is currently running.
26
+
27
+ If the task is currently running, it will be cancelled and awaited until
28
+ it stops.
29
+ """
30
+
31
+ if self.is_started:
32
+ self.is_started = False
33
+ # Stop task and await it stopped:
34
+ self._task.cancel()
35
+ with suppress(asyncio.CancelledError):
36
+ await self._task
37
+
38
+ async def _run(self) -> None:
39
+ """Run the specified function at regular intervals using asyncio.
40
+
41
+ This method runs the specified function at regular intervals based on
42
+ the time provided.
43
+
44
+ Args:
45
+ self: The instance of the class.
46
+
47
+ """
48
+
49
+ while True:
50
+ await asyncio.sleep(self.time)
51
+ await self.func()
52
+
53
+
54
+ def periodic(period):
55
+ """Schedule a function to run periodically at a specified time interval.
56
+
57
+ This decorator function takes a period (in seconds) as input and returns
58
+ a scheduler function. The scheduler function, when applied as a
59
+ decorator to another function, will run the decorated function
60
+ periodically at the specified time interval.
61
+
62
+ Args:
63
+ period (int): Time interval in seconds at which the decorated function should run
64
+ periodically.
65
+
66
+ Returns:
67
+ function: A scheduler function that can be used as a decorator to run a function
68
+ periodically.
69
+
70
+ """
71
+
72
+ def scheduler(fcn):
73
+ """Schedule the execution of a given async function periodically.
74
+
75
+ This function takes an async function as input and returns a new async
76
+ function that will execute the input function periodically at a
77
+ specified interval.
78
+
79
+ Args:
80
+ fcn (function): The async function to be scheduled.
81
+
82
+ Returns:
83
+ function: An async function that will execute the input function periodically.
84
+
85
+ """
86
+
87
+ async def wrapper(*args, **kwargs) -> None:
88
+ """Execute the given function periodically using asyncio tasks.
89
+
90
+ This function continuously creates an asyncio task to execute the
91
+ provided function with the given arguments and keyword arguments. It
92
+ then waits for a specified period of time before creating the next task.
93
+
94
+ Args:
95
+ *args: Variable length argument list to be passed to the function.
96
+ **kwargs: Arbitrary keyword arguments to be passed to the function.
97
+
98
+ """
99
+
100
+ while True:
101
+ asyncio.create_task(fcn(*args, **kwargs))
102
+ await asyncio.sleep(period)
103
+
104
+ return wrapper
105
+
106
+ return scheduler