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.
- pymammotion/__init__.py +53 -0
- pymammotion/agora/__init__.py +0 -0
- pymammotion/agora/agora_api.py +755 -0
- pymammotion/agora/agora_rtc_capabilities.py +748 -0
- pymammotion/agora/agora_websockets.py +1175 -0
- pymammotion/aliyun/__init__.py +1 -0
- pymammotion/aliyun/client.py +235 -0
- pymammotion/aliyun/cloud_gateway.py +982 -0
- pymammotion/aliyun/model/aep_response.py +21 -0
- pymammotion/aliyun/model/connect_response.py +51 -0
- pymammotion/aliyun/model/dev_by_account_response.py +195 -0
- pymammotion/aliyun/model/login_by_oauth_response.py +64 -0
- pymammotion/aliyun/model/regions_response.py +29 -0
- pymammotion/aliyun/model/session_by_authcode_response.py +19 -0
- pymammotion/aliyun/model/thing_response.py +12 -0
- pymammotion/aliyun/regions.py +62 -0
- pymammotion/aliyun/tea/core.py +297 -0
- pymammotion/aliyun/tmp_constant.py +171 -0
- pymammotion/bluetooth/__init__.py +1 -0
- pymammotion/bluetooth/ble.py +62 -0
- pymammotion/bluetooth/ble_message.py +676 -0
- pymammotion/bluetooth/const.py +27 -0
- pymammotion/bluetooth/data/__init__.py +0 -0
- pymammotion/bluetooth/data/convert.py +25 -0
- pymammotion/bluetooth/data/framectrldata.py +40 -0
- pymammotion/bluetooth/data/notifydata.py +62 -0
- pymammotion/bluetooth/model/__init__.py +0 -0
- pymammotion/bluetooth/model/atomic_integer.py +54 -0
- pymammotion/const.py +13 -0
- pymammotion/data/__init__.py +0 -0
- pymammotion/data/model/__init__.py +8 -0
- pymammotion/data/model/account.py +8 -0
- pymammotion/data/model/device.py +192 -0
- pymammotion/data/model/device_config.py +72 -0
- pymammotion/data/model/device_info.py +60 -0
- pymammotion/data/model/device_limits.py +49 -0
- pymammotion/data/model/enums.py +77 -0
- pymammotion/data/model/errors.py +12 -0
- pymammotion/data/model/events.py +14 -0
- pymammotion/data/model/generate_geojson.py +565 -0
- pymammotion/data/model/generate_route_information.py +26 -0
- pymammotion/data/model/hash_list.py +475 -0
- pymammotion/data/model/location.py +36 -0
- pymammotion/data/model/mowing_modes.py +77 -0
- pymammotion/data/model/rapid_state.py +45 -0
- pymammotion/data/model/raw_data.py +215 -0
- pymammotion/data/model/region_data.py +102 -0
- pymammotion/data/model/report_info.py +182 -0
- pymammotion/data/model/work.py +27 -0
- pymammotion/data/mower_state_manager.py +369 -0
- pymammotion/data/mqtt/__init__.py +1 -0
- pymammotion/data/mqtt/event.py +227 -0
- pymammotion/data/mqtt/mammotion_properties.py +276 -0
- pymammotion/data/mqtt/properties.py +203 -0
- pymammotion/data/mqtt/status.py +57 -0
- pymammotion/event/__init__.py +6 -0
- pymammotion/event/event.py +96 -0
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +514 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/__init__.py +0 -0
- pymammotion/http/encryption.py +220 -0
- pymammotion/http/http.py +673 -0
- pymammotion/http/model/__init__.py +0 -0
- pymammotion/http/model/camera_stream.py +31 -0
- pymammotion/http/model/http.py +249 -0
- pymammotion/http/model/response_factory.py +61 -0
- pymammotion/http/model/rtk.py +16 -0
- pymammotion/mammotion/__init__.py +0 -0
- pymammotion/mammotion/commands/__init__.py +0 -0
- pymammotion/mammotion/commands/abstract_message.py +24 -0
- pymammotion/mammotion/commands/mammotion_command.py +81 -0
- pymammotion/mammotion/commands/messages/__init__.py +0 -0
- pymammotion/mammotion/commands/messages/basestation.py +43 -0
- pymammotion/mammotion/commands/messages/driver.py +122 -0
- pymammotion/mammotion/commands/messages/media.py +87 -0
- pymammotion/mammotion/commands/messages/navigation.py +564 -0
- pymammotion/mammotion/commands/messages/network.py +205 -0
- pymammotion/mammotion/commands/messages/ota.py +38 -0
- pymammotion/mammotion/commands/messages/system.py +330 -0
- pymammotion/mammotion/commands/messages/video.py +33 -0
- pymammotion/mammotion/control/__init__.py +0 -0
- pymammotion/mammotion/control/joystick.py +145 -0
- pymammotion/mammotion/devices/__init__.py +29 -0
- pymammotion/mammotion/devices/base.py +163 -0
- pymammotion/mammotion/devices/mammotion.py +571 -0
- pymammotion/mammotion/devices/mammotion_bluetooth.py +496 -0
- pymammotion/mammotion/devices/mammotion_cloud.py +355 -0
- pymammotion/mammotion/devices/mammotion_mower_ble.py +48 -0
- pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
- pymammotion/mammotion/devices/managers/managers.py +81 -0
- pymammotion/mammotion/devices/mower_device.py +120 -0
- pymammotion/mammotion/devices/mower_manager.py +107 -0
- pymammotion/mammotion/devices/rtk_ble.py +89 -0
- pymammotion/mammotion/devices/rtk_cloud.py +115 -0
- pymammotion/mammotion/devices/rtk_device.py +50 -0
- pymammotion/mammotion/devices/rtk_manager.py +125 -0
- pymammotion/mqtt/__init__.py +6 -0
- pymammotion/mqtt/aliyun_mqtt.py +237 -0
- pymammotion/mqtt/linkkit/__init__.py +5 -0
- pymammotion/mqtt/linkkit/h2client.py +585 -0
- pymammotion/mqtt/linkkit/linkkit.py +3025 -0
- pymammotion/mqtt/mammotion_future.py +26 -0
- pymammotion/mqtt/mammotion_mqtt.py +214 -0
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +4841 -0
- pymammotion/proto/basestation.proto +51 -0
- pymammotion/proto/basestation_pb2.py +35 -0
- pymammotion/proto/basestation_pb2.pyi +89 -0
- pymammotion/proto/common.proto +7 -0
- pymammotion/proto/common_pb2.py +25 -0
- pymammotion/proto/common_pb2.pyi +13 -0
- pymammotion/proto/dev_net.proto +321 -0
- pymammotion/proto/dev_net_pb2.py +111 -0
- pymammotion/proto/dev_net_pb2.pyi +515 -0
- pymammotion/proto/luba_msg.proto +76 -0
- pymammotion/proto/luba_msg_pb2.py +41 -0
- pymammotion/proto/luba_msg_pb2.pyi +97 -0
- pymammotion/proto/luba_mul.proto +129 -0
- pymammotion/proto/luba_mul_pb2.py +61 -0
- pymammotion/proto/luba_mul_pb2.pyi +178 -0
- pymammotion/proto/mctrl_driver.proto +107 -0
- pymammotion/proto/mctrl_driver_pb2.py +57 -0
- pymammotion/proto/mctrl_driver_pb2.pyi +167 -0
- pymammotion/proto/mctrl_nav.proto +591 -0
- pymammotion/proto/mctrl_nav_pb2.py +136 -0
- pymammotion/proto/mctrl_nav_pb2.pyi +1067 -0
- pymammotion/proto/mctrl_ota.proto +80 -0
- pymammotion/proto/mctrl_ota_pb2.py +45 -0
- pymammotion/proto/mctrl_ota_pb2.pyi +128 -0
- pymammotion/proto/mctrl_pept.proto +34 -0
- pymammotion/proto/mctrl_pept_pb2.py +33 -0
- pymammotion/proto/mctrl_pept_pb2.pyi +58 -0
- pymammotion/proto/mctrl_sys.proto +741 -0
- pymammotion/proto/mctrl_sys_pb2.py +206 -0
- pymammotion/proto/mctrl_sys_pb2.pyi +1213 -0
- pymammotion/proto/message_pool.py +3 -0
- pymammotion/proto/py.typed +0 -0
- pymammotion/py.typed +0 -0
- pymammotion/utility/constant/__init__.py +3 -0
- pymammotion/utility/constant/device_constant.py +315 -0
- pymammotion/utility/conversions.py +5 -0
- pymammotion/utility/datatype_converter.py +124 -0
- pymammotion/utility/device_config.py +755 -0
- pymammotion/utility/device_type.py +489 -0
- pymammotion/utility/map.py +259 -0
- pymammotion/utility/movement.py +18 -0
- pymammotion/utility/mur_mur_hash.py +159 -0
- pymammotion/utility/periodic.py +106 -0
- pymammotion/utility/rocker_util.py +194 -0
- pymammotion-0.5.69.dist-info/METADATA +93 -0
- pymammotion-0.5.69.dist-info/RECORD +154 -0
- pymammotion-0.5.69.dist-info/WHEEL +4 -0
- 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
|