Skip to content

Commit 2b5fd93

Browse files
author
pool man
committed
Merged with upstream
2 parents fc351d6 + b57be91 commit 2b5fd93

File tree

329 files changed

+21595
-1087
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

329 files changed

+21595
-1087
lines changed

.gitmodules

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,30 @@
2929
path = drivers/nmea_navsat_driver
3030
url = [email protected]:smarc-project/nmea_navsat_driver.git
3131
branch = ros2
32+
[submodule "drivers/stim300"]
33+
path = drivers/stim300
34+
url = [email protected]:smarc-project/stim300.git
35+
branch = humble
36+
[submodule "drivers/dvl-a50-ros-driver"]
37+
path = drivers/dvl-a50-ros-driver
38+
url = [email protected]:smarc-project/dvl-a50-ros-driver.git
39+
branch = humble
40+
[submodule "drivers/ublox"]
41+
path = drivers/ublox
42+
url = https://github.com/KumarRobotics/ublox.git
43+
branch = ros2
3244
[submodule "external_packages/smarc_modelling"]
3345
path = external_packages/smarc_modelling
3446
url = https://github.com/smarc-project/smarc_modelling.git
35-
branch = master
47+
[submodule "vehicles/hardware/dji/psdk_ros2"]
48+
path = vehicles/hardware/dji/psdk_ros2
49+
url = https://gitr.sys.kth.se/smarc-project/dji_psdk_wrapper.git
50+
branch = smarc
51+
52+
[submodule "utilities/serial_ping_pkg"]
53+
path = utilities/serial_ping_pkg
54+
url = https://github.com/NinjaTuna007/serial_ping_pkg.git
55+
[submodule "external_equipment/PlayStation-JoyInterface-ROS2"]
56+
path = external_equipment/PlayStation-JoyInterface-ROS2
57+
url = https://github.com/HarvestX/PlayStation-JoyInterface-ROS2
58+
branch = humble

All Topics.msg.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@
108108
- TELEOP_ENABLE : 'teleop/enable'
109109
- ASSIST_ENABLE : 'teleop/drive_assist_enable'
110110
- ELEVATOR_PID_CTRL : 'ctrl/elevator_pid/ctrl_effort'
111+
- CONTROL_YAW_TOPIC : "ctrl/yaw"
112+
- CONTROL_YAW_RATE_TOPIC : "ctrl/yawrate"
113+
- CONTROL_PITCH_TOPIC : "ctrl/pitch"
114+
- CONTROL_PITCH_RATE_TOPIC : "ctrl/pitchrate"
115+
- CONTROL_ROLL_TOPIC : "ctrl/roll"
116+
- CONTROL_ROLL_RATE_TOPIC : "ctrl/rollrate"
117+
- CONTROL_SURGE_RATE_TOPIC : "ctrl/surgerate"
111118
## Topics in file: [messages/lolo_msgs/msg/Topics.msg](messages/lolo_msgs/msg/Topics.msg)
112119

113120
- RUDDER_CMD : "actuators/rudder_cmd"
@@ -128,7 +135,7 @@
128135
- THRUSTER_STRB_FB : "actuators/thruster_strb_fb"
129136
- VERTICAL_THRUSTER_BACK_PORT_FB : "actuators/vertical_thruster_back_port_fb"
130137
- VERTICAL_THRUSTER_BACK_STRB_FB : "actuators/vertical_thruster_back_strb_fb"
131-
- VERTICAL_THRUSTER_FRONT_PORT_FB : "actuators/vertical_thruster_front_strb_fb"
138+
- VERTICAL_THRUSTER_FRONT_PORT_FB : "actuators/vertical_thruster_front_port_fb"
132139
- VERTICAL_THRUSTER_FRONT_STRB_FB : "actuators/vertical_thruster_front_strb_fb"
133140
- HEARTBEAT_TOPIC : "heartbeat"
134141
- LEAK_TOPIC : "leak"
@@ -151,6 +158,28 @@
151158
- SATELITE_RECEIVED_TOPIC : "communication/satelite_received"
152159
- USBL_RECEIVED_TOPIC : "communication/usbl/received"
153160
- USBL_RECEIVED_CHR_TOPIC : "communication/usbl/received/chr"
161+
- INS_ODOM_TOPIC : "ins_odom"
162+
- INS_RAW_TOPIC : "/ix/ins"
163+
- INS_IMU_TOPIC : "/standard/imu"
164+
- GPS_RAW_TOPIC : sensors/gps/raw
165+
- GPS_TOPIC : sensors/gps
166+
- SVS_RAW_TOPIC : sensors/svs/raw
167+
- SVS_TOPIC : sensors/svs
168+
- CTD_RAW_TOPIC : sensors/ctd/raw
169+
- CTD_TOPIC : sensors/ctd
170+
- DO_RAW_TOPIC : sensors/do/raw
171+
- DO_TOPIC : sensors/do
172+
- TURB_RAW_TOPIC : sensors/turbidity/raw
173+
- TURB_TOPIC : sensors/turbidity
174+
- FLUORESCENCE_RAW_TOPIC : sensors/fluorescence/raw
175+
- FLUORESCENCE_TOPIC : sensors/fluorescence
176+
- DVL_TOPIC : sensors/dvl
177+
- FLS_WC_TOPIC : sensors/fls/watercolumn
178+
- FLS_IMAGE_TOPIC : sensors/fls/watercolumn/image
179+
- MBES_WC_TOPIC : sensors/fls/watercolumn
180+
- MBES_IMAGE_TOPIC : sensors/fls/watercolumn/image
181+
- MBES_BATHY_TOPIC : sensors/fls/bathymetry
182+
- MBES_BATHY_POINTCLOUD_TOPIC : sensors/fls/bathymetry/points
154183
## Topics in file: [messages/smarc_msgs/msg/Topics.msg](messages/smarc_msgs/msg/Topics.msg)
155184

156185
- GPS_TOPIC : 'core/gps'
@@ -209,9 +238,12 @@
209238
- 'core/imu':
210239
- [ STIM_IMU_TOPIC ](messages/sam_msgs/msg/Topics.msg)
211240
- [ IMU_TOPIC ](messages/drone_msgs/msg/Topics.msg)
212-
- "actuators/vertical_thruster_front_strb_fb":
213-
- [ VERTICAL_THRUSTER_FRONT_PORT_FB ](messages/lolo_msgs/msg/Topics.msg)
214-
- [ VERTICAL_THRUSTER_FRONT_STRB_FB ](messages/lolo_msgs/msg/Topics.msg)
241+
- sensors/fls/watercolumn:
242+
- [ FLS_WC_TOPIC ](messages/lolo_msgs/msg/Topics.msg)
243+
- [ MBES_WC_TOPIC ](messages/lolo_msgs/msg/Topics.msg)
244+
- sensors/fls/watercolumn/image:
245+
- [ FLS_IMAGE_TOPIC ](messages/lolo_msgs/msg/Topics.msg)
246+
- [ MBES_IMAGE_TOPIC ](messages/lolo_msgs/msg/Topics.msg)
215247
- 'core/gps':
216248
- [ GPS_TOPIC ](messages/smarc_msgs/msg/Topics.msg)
217249
- [ GPS_TOPIC ](messages/drone_msgs/msg/Topics.msg)

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Each of the following directories also include more detailed readmes within.
66
[This document](/documentation/Installing%20ROS2.md) has step-by-step instructions to get things running.
77

88

9+
## [Making a new Action for a robot? Check this example out](/behaviours/alars/alars/SuperSimpleActionServer.py)
10+
[This example](/behaviours/alars/alars/SuperSimpleActionServer.py) implements an action server that hides ALL of the ROS from your beautiful control implementations.
11+
It works with [the bt](./behaviours/wasp_bt/launch/wasp_bt.launch) out of the box.
12+
913
## Stuff in this repo
1014

1115
> Add more directories as needed.
File renamed without changes.
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import rclpy, math
2+
3+
4+
from rclpy.node import Node
5+
from rclpy.executors import MultiThreadedExecutor
6+
from rclpy.time import Time, Duration
7+
8+
from tf2_ros import Buffer, TransformListener
9+
from tf2_geometry_msgs import do_transform_pose_stamped
10+
from tf_transformations import euler_from_quaternion, quaternion_from_euler
11+
12+
13+
from smarc_action_base.gentler_action_server import GentlerActionServer
14+
from smarc_utilities.georef_utils import convert_latlon_to_utm
15+
16+
from smarc_msgs.msg import Topics as SmarcTopics
17+
18+
from geographic_msgs.msg import GeoPoint
19+
from geometry_msgs.msg import PointStamped, PoseStamped
20+
21+
class SearchAndTrackAuvAction():
22+
def __init__(self,
23+
node: Node,
24+
action_name: str):
25+
self._node = node
26+
27+
self.log = node.get_logger().info
28+
29+
self.ODOM_FRAME = "Quadrotor/odom"
30+
self.BASE_FRAME = "Quadrotor/base_link"
31+
self.SETPOINT_TOPIC = "move_to_setpoint"
32+
self.POINT_REACHED_DISTANCE = 0.3
33+
self.SPIRAL_ARM_DISTANCE = 0.5 # Distance between spiral arms in meters
34+
35+
self._as = GentlerActionServer(
36+
node,
37+
action_name,
38+
self._on_goal_received,
39+
self._on_cancel_received,
40+
self._prepare_loop,
41+
self._loop_inner,
42+
self._give_feedback,
43+
loop_frequency=10.0
44+
)
45+
46+
self._search_center_odom : PointStamped|None = None
47+
self._search_radius : float = 0.0
48+
self._keep_following : bool = False
49+
self._spiral_position : float|None = None
50+
51+
52+
self._tf_buffer = Buffer()
53+
self._tf_listener = TransformListener(self._tf_buffer, self._node, spin_thread=True)
54+
55+
self._feedback_str = "No feedback yet"
56+
57+
self._setpoint_pub = self._node.create_publisher(
58+
PoseStamped,
59+
self.SETPOINT_TOPIC,
60+
10)
61+
62+
self._auv_detection_sub = self._node.create_subscription(
63+
PointStamped,
64+
"alars_detection/auv",
65+
self._auv_detection_callback,
66+
10)
67+
68+
@property
69+
def now_stamp(self):
70+
return self._node.get_clock().now().to_msg()
71+
72+
73+
def _auv_detection_callback(self, msg: PointStamped):
74+
#TODO
75+
pass
76+
77+
78+
def _on_goal_received(self, goal_request: dict) -> bool:
79+
self.log(f"Received goal request: {goal_request}")
80+
gp = goal_request['search_center']
81+
alt : float = gp['altitude']
82+
radius : float = gp['tolerance']
83+
84+
try:
85+
if alt < 2.0:
86+
self._node.get_logger().error("Search center altitude must be > 2.0 meters")
87+
return False
88+
if alt > 10.0:
89+
self._node.get_logger().error("Search center altitude must be < 10.0 meters")
90+
return False
91+
if radius > 100.0:
92+
self._node.get_logger().error("Search tolerance must be < 100.0 meters")
93+
return False
94+
if radius < 0.0:
95+
self._node.get_logger().error("Search tolerance must be >= 0.0 meters")
96+
return False
97+
except:
98+
self._node.get_logger().error("Invalid goal request format!")
99+
return False
100+
101+
center_gp = GeoPoint()
102+
center_gp.latitude = float(gp['latitude'])
103+
center_gp.longitude = float(gp['longitude'])
104+
center_gp.altitude = alt
105+
center_utm : PointStamped = convert_latlon_to_utm(center_gp)
106+
107+
try:
108+
tf = self._tf_buffer.lookup_transform(
109+
self.ODOM_FRAME,
110+
center_utm.header.frame_id,
111+
Time(seconds=0),
112+
timeout=Duration(seconds=1)
113+
)
114+
ps = PoseStamped()
115+
ps.header = center_utm.header
116+
ps.pose.position = center_utm.point
117+
ps.pose.orientation.w = 1.0 # Set orientation to identity
118+
119+
pnt = do_transform_pose_stamped(ps, tf)
120+
self._search_center_odom = PointStamped()
121+
self._search_center_odom.header = pnt.header
122+
self._search_center_odom.point = pnt.pose.position
123+
124+
except Exception as e:
125+
self.log(f"Failed to transform search center from {center_utm.header.frame_id} to {self.ODOM_FRAME}: {e}")
126+
return False
127+
128+
self._search_radius = radius
129+
self._keep_following = bool(goal_request['keep_following'])
130+
self._spiral_position = None
131+
132+
self.log(f"Search center set to: {format_point_stamped(self._search_center_odom)}")
133+
self.log(f"Search radius set to: {self._search_radius} meters")
134+
return True
135+
136+
137+
138+
139+
def _on_cancel_received(self) -> bool:
140+
self._node.get_logger().info("Received cancel request")
141+
return True
142+
143+
144+
def _prepare_loop(self) -> None:
145+
if self._search_center_odom is None:
146+
self._node.get_logger().error("Search center not set, cannot start action")
147+
return
148+
self._current_setpoint_odom: PoseStamped = PoseStamped()
149+
self._current_setpoint_odom.pose.position.x = self._search_center_odom.point.x
150+
self._current_setpoint_odom.pose.position.y = self._search_center_odom.point.y
151+
self._current_setpoint_odom.pose.position.z = self._search_center_odom.point.z
152+
self._current_setpoint_odom.header = self._search_center_odom.header
153+
154+
155+
def _update_current_setpoint_on_spiral(self):
156+
if self._spiral_position is None:
157+
self._spiral_position = 0
158+
159+
if self._search_center_odom is None:
160+
self.log("Search center not set, cannot update spiral position")
161+
return
162+
163+
spiral_x, spiral_y = point_on_spiral(self._spiral_position, base_radius=self.POINT_REACHED_DISTANCE, arm_distance=self.SPIRAL_ARM_DISTANCE)
164+
self._current_setpoint_odom.pose.position.x = spiral_x + self._search_center_odom.point.x
165+
self._current_setpoint_odom.pose.position.y = spiral_y + self._search_center_odom.point.y
166+
167+
168+
def _loop_inner(self) -> bool | None:
169+
if self._search_center_odom is None:
170+
self.log("Search center not set, cannot continue action")
171+
return False
172+
173+
tf = self._tf_buffer.lookup_transform(
174+
self.BASE_FRAME,
175+
self._current_setpoint_odom.header.frame_id,
176+
Time(seconds=0),
177+
timeout=Duration(seconds=1)
178+
)
179+
current_setpoint_in_base = do_transform_pose_stamped(self._current_setpoint_odom, tf)
180+
# Calculate the magnitude (distance from origin) of the setpoint in the base frame
181+
pos = current_setpoint_in_base.pose.position
182+
dist_to_current_setpoint = math.sqrt(pos.x ** 2 + pos.y ** 2 + pos.z ** 2)
183+
if dist_to_current_setpoint < self.POINT_REACHED_DISTANCE:
184+
if self._spiral_position is not None:
185+
self._spiral_position += math.pi / 16
186+
self._update_current_setpoint_on_spiral()
187+
else:
188+
self.log("Reached search center, starting search")
189+
self._spiral_position = 0
190+
self._update_current_setpoint_on_spiral()
191+
192+
# Check distance between current_setpoint_odom and search_center
193+
dx = self._current_setpoint_odom.pose.position.x - self._search_center_odom.point.x
194+
dy = self._current_setpoint_odom.pose.position.y - self._search_center_odom.point.y
195+
dz = self._current_setpoint_odom.pose.position.z - self._search_center_odom.point.z
196+
dist_from_center = math.sqrt(dx**2 + dy**2 + dz**2)
197+
if dist_from_center > self._search_radius:
198+
self.log(f"Current setpoint is outside search radius of {self._search_radius}m, search failed.")
199+
return False
200+
201+
self._current_setpoint_odom.header.stamp = self.now_stamp
202+
self._setpoint_pub.publish(self._current_setpoint_odom)
203+
self._feedback_str = f"Remaining: {dist_to_current_setpoint:.2f}m"
204+
return None
205+
206+
207+
208+
def _give_feedback(self) -> str:
209+
return self._feedback_str
210+
211+
212+
def point_on_spiral(T_rad:float, base_radius:float=0, arm_distance:float=1) -> tuple[float, float]:
213+
r = base_radius + arm_distance * T_rad
214+
x = r * math.cos(T_rad)
215+
y = r * math.sin(T_rad)
216+
return x, y
217+
218+
219+
def main():
220+
rclpy.init()
221+
node = Node("search_auv_action_node")
222+
223+
SearchAndTrackAuvAction(node, "alars_search_and_follow")
224+
225+
executor = MultiThreadedExecutor()
226+
executor.add_node(node)
227+
try:
228+
executor.spin()
229+
except KeyboardInterrupt:
230+
node.get_logger().info("Shutting down Search AUV Action server")
231+
finally:
232+
executor.shutdown()
233+
node.destroy_node()
234+
rclpy.shutdown()
235+
236+
237+
def format_point_stamped(point: PointStamped|None) -> str:
238+
if( point is None):
239+
return "None"
240+
return f"(x={point.point.x:.3f}, y={point.point.y:.3f}, z={point.point.z:.3f}, frame_id={point.header.frame_id})"
241+
242+
def format_pose_stamped(pose: PoseStamped|None) -> str:
243+
if( pose is None):
244+
return "None"
245+
rpy = euler_from_quaternion([
246+
pose.pose.orientation.x,
247+
pose.pose.orientation.y,
248+
pose.pose.orientation.z,
249+
pose.pose.orientation.w
250+
])
251+
return f"(x={pose.pose.position.x:.3f}, y={pose.pose.position.y:.3f}, z={pose.pose.position.z:.3f}, " \
252+
f"roll={math.degrees(rpy[0]):.3f}, pitch={math.degrees(rpy[1]):.3f}, yaw={math.degrees(rpy[2]):.3f}, " \
253+
f"frame_id={pose.header.frame_id})"

0 commit comments

Comments
 (0)