Skip to content

Commit 37ef5ca

Browse files
Add Follow Person exercise and polished O3DE exercises
1 parent f72e4b0 commit 37ef5ca

29 files changed

Lines changed: 451 additions & 19 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from rclpy.node import Node
2+
import sensor_msgs.msg
3+
from math import pi as PI
4+
import rclpy
5+
import numpy as np
6+
from sensor_msgs_py import point_cloud2
7+
from threading import Lock
8+
9+
10+
class LidarData:
11+
def __init__(self):
12+
self.points = []
13+
self.intensities = []
14+
self.timeStamp = 0.0
15+
self.min_range = 0.1
16+
self.max_range = 15.0
17+
self.field_of_view = (2 * PI / 3, PI / 18)
18+
self.is_dense = True
19+
20+
def __str__(self):
21+
return (
22+
f"LiDARData:\n"
23+
f" timestamp: {self.timeStamp}\n"
24+
f" points: {len(self.points)}\n"
25+
f" range: [{self.min_range}, {self.max_range}]\n"
26+
f" FOV: {self.field_of_view}\n"
27+
f" is_dense: {self.is_dense}"
28+
)
29+
30+
31+
def pointCloud2LidarData(cloud):
32+
lidar = LidarData()
33+
if not cloud or cloud.width * cloud.height == 0:
34+
return lidar
35+
36+
# Read XYZ points
37+
lidar.points = list(
38+
point_cloud2.read_points(cloud, field_names=("x", "y", "z"), skip_nans=False)
39+
)
40+
41+
# Read intensities if available
42+
if "intensity" in [f.name for f in cloud.fields]:
43+
intensities = point_cloud2.read_points(
44+
cloud, field_names=("intensity",), skip_nans=False
45+
)
46+
lidar.intensities = [i[0] for i in intensities]
47+
48+
# Timestamp (ROS 2 Time -> seconds)
49+
lidar.timeStamp = cloud.header.stamp.sec + (cloud.header.stamp.nanosec * 1e-9)
50+
51+
# Validate point cloud
52+
lidar.is_dense = all(
53+
all(np.isfinite(coord) for coord in point) for point in lidar.points
54+
)
55+
56+
return lidar
57+
58+
59+
class LidarNode(Node):
60+
def __init__(self, topic):
61+
super().__init__("lidar_node")
62+
self._lock = Lock()
63+
self.last_cloud_ = None
64+
self.sub = self.create_subscription(
65+
sensor_msgs.msg.PointCloud2, topic, self.pointcloud_callback, 10
66+
)
67+
68+
def pointcloud_callback(self, cloud):
69+
with self._lock:
70+
self.last_cloud_ = cloud
71+
72+
def getLidarData(self):
73+
with self._lock:
74+
return pointCloud2LidarData(self.last_cloud_)
75+
76+
def get_point_cloud(self):
77+
with self._lock:
78+
return self.last_cloud_

database/exercises/db.sql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,9 @@ COPY public.exercises (id, exercise_id, name, description, tags, status, url) FR
138138
24 car_junction Car Junction Autonomous Navigation through traffic at road junction. ["AUTONOMOUS DRIVING","ROS2"] PROTOTYPE https://jderobot.github.io/RoboticsAcademy/exercises/AutonomousCars/car_junction
139139
25 machine_vision Machine Vision Machine Vision exercise ["ROS2"] ACTIVE https://jderobot.github.io/RoboticsAcademy/exercises/IndustrialRobots/machine_vision
140140
26 labyrinth_escape Labyrinth Escape Labyrinth Escape exercise ["ROS2","Drones"] ACTIVE https://jderobot.github.io/RoboticsAcademy/exercises/Drones/labyrinth_escape
141-
27 o3de_warehouse O3DE Warehouse Control an O3DE robot to organize a warehouse ["ROS2"] PROTOTYPE https://jderobot.github.io/RoboticsAcademy/exercises
141+
27 o3de_laser_mapping O3DE LaserMapping Control an O3DE robot to organize a warehouse ["ROS2"] PROTOTYPE https://jderobot.github.io/RoboticsAcademy/exercises
142142
28 o3de_follow_line O3DE FollowLine Control an O3DE robot to follow a line ["ROS2"] PROTOTYPE https://jderobot.github.io/RoboticsAcademy/exercises
143+
29 o3de_follow_person O3DE FollowPerson Control an O3DE robot to follow a person ["ROS2"] PROTOTYPE https://jderobot.github.io/RoboticsAcademy/exercises
143144
\.
144145

145146
--
@@ -191,6 +192,7 @@ COPY public.exercises_universes (id, exercise_id, universe_id) FROM stdin;
191192
41 26 39
192193
42 27 40
193194
43 28 41
195+
44 29 42
194196
\.
195197
-- 30 16 3
196198

@@ -282,6 +284,9 @@ COPY public.exercises_tools (id, exercise_id, tool_id) FROM stdin;
282284
81 28 console
283285
82 28 simulator
284286
83 28 web_gui
287+
84 29 console
288+
85 29 simulator
289+
86 29 web_gui
285290
\.
286291

287292
--
203 KB
Loading
182 KB
Loading
305 KB
Loading
-416 KB
Binary file not shown.

exercises/static/exercises/o3de_follow_line/python_template/ros2_humble/HAL.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def __auto_spin() -> None:
3939
rclpy.init(args=None)
4040

4141
### HAL INIT ###
42-
motor_node = AckerMotorsNode("/chassis_link/cmd_vel", 4, 0.3, 2)
43-
camera_node = CameraNode("/chassis_link/color/image_color")
42+
motor_node = AckerMotorsNode("/cmd_vel", 4, 0.3, 2)
43+
camera_node = CameraNode("/cam_f1_left/image_raw")
4444

4545
executor = rclpy.executors.MultiThreadedExecutor()
4646
executor.add_node(camera_node)

exercises/static/exercises/o3de_follow_line/python_template/ros2_humble/WebGUI.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def __init__(self, host="ws://127.0.0.1:2303"):
4747
self.payload = {"image": "", "lap": "", "map": ""}
4848
self.bridge = CvBridge()
4949

50-
self.pose3d_object = OdometryNode("/chassis_link/odom")
50+
self.pose3d_object = OdometryNode("/odom")
5151

5252
self.executor = rclpy.executors.MultiThreadedExecutor()
5353
self.executor.add_node(self.webgui_publisher)
@@ -66,6 +66,10 @@ def __init__(self, host="ws://127.0.0.1:2303"):
6666
self.auto_image_thread.start()
6767

6868
self.start()
69+
70+
def get_real_time_factor(self):
71+
"""Anula la función de la clase padre para que no busque 'world_name' ni use Gazebo."""
72+
pass
6973

7074
def _setup_auto_mode(self):
7175
"""Set up automatic image subscription"""

exercises/static/exercises/o3de_follow_line/react-components/WebGUI.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const WebGUI = () => {
3535

3636
const updateCircuit = (universe) => {
3737
switch (universe) {
38+
case "O3DE FollowLine":
39+
circuitName = "o3de default";
40+
setCircuitImg(defaultCircuit);
41+
break;
3842
case "Simple Circuit":
3943
circuitName = "default";
4044
setCircuitImg(defaultCircuit);

0 commit comments

Comments
 (0)