Skip to content

Commit 4690091

Browse files
315 sensor integrate ouster hardware (#346)
* Update launch file with lifecycle node for the ouster driver Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Add temporary placeholder for topic remappings for the ouster driver Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Update ouster launch file to match with ouster hardware, and update Lidar class to also bridge /scan topic for the ouster since the hardware itself does that as well Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Update ouster documentation Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Remove unused import Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Turn off scan method of the Ouster since it only takes a single row of the entire scan instead of merging the rows, and add the pointcloud_to_laserscan node Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Correctly update the documentation and make small adjustment to launch file to make the ip addresses slightly more dynamically adjustable when desired Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Generalize setting the ip address of the device a bit more Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> * Add missing line for linting Signed-off-by: Rosalie <rosalie.van.ark@alliander.com> --------- Signed-off-by: Rosalie <rosalie.van.ark@alliander.com>
1 parent 1c75051 commit 4690091

5 files changed

Lines changed: 120 additions & 17 deletions

File tree

docs/content/platforms.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ We created a fork with robot-specific enhancements: [https://github.com/alliande
3939

4040
![Panther](../img/panther/panther.png)
4141

42+
Regarding all vehicles, the general rule we apply for assigning IP addresses follows the following numbering order:
43+
44+
> 1 - Router \
45+
2 - Low-level computer (Raspberry Pi) \
46+
3 - High-level computer \
47+
4 - Additional component (e.g. Franka Arm) \
48+
5 - LiDAR
49+
4250
### Simulation Panther
4351

4452
A Panther vehicle can be launched in simulation by creating a configuration with an *Vehicle* of type *Panther*. Note that the E-Stop is triggered by default and needs to be released before driving is possible. This can be done by a service call:
@@ -126,7 +134,27 @@ An Ouster lidar can be launched in simulation by creating a configuration with a
126134

127135
### Hardware Ouster
128136

129-
*TODO: update when sensor has arrived.*
137+
**Network settings:**
138+
\
139+
When using the Ouster lidar, make sure that the IP-address of the host device (where the Ouster node is running) is set correctly in the settings of the Teltonika router. One can assign a static IP address to the Ouster via the Teltonika interface. In case of the Husarion vehicles, this interface is reachable via `http://10.15.20.1/`, and the static IP address should be set to `10.15.20.5` as number 5 is reserved for LiDARs.
140+
141+
Additionally, it is important to assign the correct UDP destination IP address for the Ouster LiDAR. This can both be done via the Ouster's configuration interface at `http://os-{serial_number}.local/`, or it can be done via the launch file of the Ouster (`ouster.launch.py` in the `rcdt_sensors` package).
142+
143+
_Note:_ If the firewall is enabled in Ubuntu, communication with the LiDAR is most likely blocked. Unblock it by allowing the IP-address of the LiDAR:
144+
145+
```bash
146+
sudo ufw allow to {IPv4_address}
147+
sudo ufw allow from {IPv4_address}
148+
```
149+
150+
**ROS2 setup:**
151+
\
152+
The [Ouster driver](https://github.com/ouster-lidar/ouster-ros/tree/ros2) runs as a [LifeCycle node](https://design.ros2.org/articles/node_lifecycle.html), meaning that once created, the node starts in an `Unconfigured` state. It needs to be `configured` and `activated` to start the driver.
153+
154+
Find all of the connected Ouster's information at `http://os-{serial_number}.local/`, where the following parameters for the driver node can be found:
155+
- `sensor_hostname`: Dashboard > System Information > IPv4 _(Remove the prefix length)_
156+
- `udp_dest`: Dashboard > System Status > Web Client Address
157+
130158

131159
## Velodyne
132160

ros2_ws/src/rcdt_launch/rcdt_launch/platforms/lidar.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def __init__( # noqa: PLR0913
3434
namespace: str | None = None,
3535
parent: Vehicle | None = None,
3636
parent_link: str = "",
37+
ip_address: str = "",
3738
):
3839
"""Initialize the Lidar platform.
3940
@@ -44,10 +45,12 @@ def __init__( # noqa: PLR0913
4445
namespace (str | None): The namespace of the lidar.
4546
parent (Vehicle | None): The parent platform.
4647
parent_link (str): The link of the parent to which the platform is attached. If empty, the base_link of the parent is used.
48+
ip_address (str): The IP address of the platform.
4749
"""
4850
super().__init__(
4951
platform, position, orientation, namespace, parent, parent_link
5052
)
53+
self.ip_address = ip_address
5154

5255
if parent:
5356
parent.lidar = self
@@ -120,6 +123,7 @@ def create_velodyne_launch(self) -> RegisteredLaunchDescription:
120123
"simulation": str(EnvironmentConfiguration.simulation),
121124
"namespace": self.namespace,
122125
"target_frame": target_frame,
126+
"ip_address": self.ip_address,
123127
},
124128
)
125129

@@ -140,5 +144,7 @@ def create_ouster_launch(self) -> RegisteredLaunchDescription:
140144
"simulation": str(EnvironmentConfiguration.simulation),
141145
"namespace": self.namespace,
142146
"target_frame": target_frame,
147+
"ip_device": self.ip_address,
148+
"ip_udp_destination": "10.15.20.3",
143149
},
144150
)

ros2_ws/src/rcdt_launch/rcdt_launch/predefined_configurations.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ def config_gps() -> None: # noqa: D103
8484

8585
@register_configuration("ouster")
8686
def config_ouster() -> None: # noqa: D103
87-
Lidar("ouster", [0, 0, 0.5])
87+
Lidar("ouster", [0, 0, 0.5], ip_address="10.15.20.5")
8888

8989

9090
@register_configuration("velodyne")
9191
def config_velodyne() -> None: # noqa: D103
92-
Lidar("velodyne", [0, 0, 0.5])
92+
Lidar("velodyne", [0, 0, 0.5], ip_address="10.15.20.5")
9393

9494

9595
@register_configuration("realsense")
@@ -156,31 +156,31 @@ def config_panther_zed() -> None: # noqa: D103
156156
@register_configuration("panther_velodyne")
157157
def config_panther_velodyne() -> None: # noqa: D103
158158
panther = Vehicle("panther", [0, 0, 0.2])
159-
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther)
159+
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
160160

161161

162162
@register_configuration("panther_ouster")
163163
def config_panther_ouster() -> None: # noqa: D103
164164
panther = Vehicle("panther", [0, 0, 0.2])
165-
Lidar("ouster", [0.13, -0.13, 0.35], parent=panther)
165+
Lidar("ouster", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
166166

167167

168168
@register_configuration("panther_collision_monitor")
169169
def config_panther_collision_monitor() -> None: # noqa: D103
170170
panther = Vehicle("panther", [0, 0, 0.2], collision_monitor=True)
171-
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther)
171+
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
172172

173173

174174
@register_configuration("panther_slam")
175175
def config_panther_slam() -> None: # noqa: D103
176176
panther = Vehicle("panther", [0, 0, 0.2], slam=True)
177-
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther)
177+
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
178178

179179

180180
@register_configuration("panther_lidar_navigation")
181181
def config_panther_lidar_navigation() -> None: # noqa: D103
182182
panther = Vehicle("panther", [0, 0, 0.2], navigation=True)
183-
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther)
183+
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
184184

185185

186186
@register_configuration("panther_gps_navigation")
@@ -190,7 +190,7 @@ def config_panther_gps() -> None: # noqa: D103
190190
panther = Vehicle(
191191
"panther", [0, 0, 0.2], navigation=True, use_gps=True, window_size=50
192192
)
193-
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther)
193+
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
194194
GPS("nmea", [0, 0, 0.2], parent=panther)
195195

196196

@@ -205,14 +205,14 @@ def config_mm() -> None: # noqa: D103
205205
def config_mm_velodyne() -> None: # noqa: D103
206206
panther = Vehicle("panther", [0, 0, 0.2], navigation=True)
207207
Arm("franka", [0, 0, 0.14], gripper=True, parent=panther, moveit=True)
208-
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther)
208+
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
209209

210210

211211
@register_configuration("mm_ouster")
212212
def config_mm_ouster() -> None: # noqa: D103
213213
panther = Vehicle("panther", [0, 0, 0.2], navigation=True)
214214
Arm("franka", [0, 0, 0.14], gripper=True, parent=panther, moveit=True)
215-
Lidar("ouster", [0.13, -0.13, 0.35], parent=panther)
215+
Lidar("ouster", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
216216

217217

218218
@register_configuration("mm_gps")
@@ -222,7 +222,7 @@ def config_mm_gps() -> None: # noqa: D103
222222
"panther", [0, 0, 0.2], navigation=True, use_gps=True, window_size=50
223223
)
224224
Arm("franka", [0, 0, 0.14], gripper=True, parent=panther, moveit=True)
225-
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther)
225+
Lidar("velodyne", [0.13, -0.13, 0.35], parent=panther, ip_address="10.15.20.5")
226226
GPS("nmea", [0, 0, 0.2], parent=panther)
227227

228228

ros2_ws/src/rcdt_sensors/launch/ouster.launch.py

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5-
65
from launch import LaunchContext, LaunchDescription
7-
from launch.actions import OpaqueFunction
8-
from launch_ros.actions import Node
6+
from launch.actions import ExecuteProcess, OpaqueFunction
7+
from launch_ros.actions import LifecycleNode, Node
98
from rcdt_utilities.launch_argument import LaunchArgument
9+
from rcdt_utilities.launch_utils import SKIP
1010
from rcdt_utilities.register import Register
1111

1212
use_sim_arg = LaunchArgument("simulation", True, [True, False])
1313
namespace_arg = LaunchArgument("namespace", "ouster")
1414
target_frame_arg = LaunchArgument("target_frame", "")
15+
ip_device_arg = LaunchArgument("ip_device", "") # IP address of the Ouster LiDAR
16+
ip_udp_destination_arg = LaunchArgument(
17+
"ip_udp_destination", ""
18+
) # Data destination address (host computer)
1519

1620

1721
def launch_setup(context: LaunchContext) -> list:
@@ -23,9 +27,64 @@ def launch_setup(context: LaunchContext) -> list:
2327
Returns:
2428
list: A list of actions to be executed.
2529
"""
26-
# Simulation-only setup
30+
use_sim = use_sim_arg.bool_value(context)
2731
namespace = namespace_arg.string_value(context)
2832
target_frame = target_frame_arg.string_value(context)
33+
ip_device = ip_device_arg.string_value(context)
34+
ip_udp_destination = ip_udp_destination_arg.string_value(context)
35+
driver_node_name = "ouster_driver"
36+
37+
sensor_frame = f"{namespace}/ouster"
38+
lidar_frame = f"{namespace}/os_lidar"
39+
imu_frame = f"{namespace}/os_imu"
40+
41+
ouster_driver_node = LifecycleNode(
42+
package="ouster_ros",
43+
executable="os_driver",
44+
namespace=namespace,
45+
name=driver_node_name,
46+
parameters=[
47+
{
48+
"sensor_hostname": ip_device,
49+
"udp_dest": ip_udp_destination,
50+
"lidar_port": 7502,
51+
"imu_port": 7503,
52+
"lidar_mode": "1024x10", # options: { 512x10, 512x20, 1024x10, 1024x20, 2048x10, 4096x5 }
53+
"sensor_frame": sensor_frame,
54+
"lidar_frame": lidar_frame,
55+
"imu_frame": imu_frame,
56+
"point_cloud_frame": lidar_frame,
57+
"proc_mask": "PCL", # options: IMU|PCL|SCAN|IMG|RAW|TLM
58+
"metadata": "/tmp/ouster_metadata.json", # Place the metadata in a temporary folder since we do not need it.
59+
}
60+
],
61+
remappings=[
62+
("points", "scan/points")
63+
], # Remap for the pointcloud_to_laserscan Node
64+
output="both",
65+
)
66+
67+
configure_ouster_driver = ExecuteProcess(
68+
cmd=[
69+
"ros2",
70+
"lifecycle",
71+
"set",
72+
f"/{namespace}/{driver_node_name}",
73+
"configure",
74+
],
75+
shell=False,
76+
)
77+
78+
activate_ouster_driver = ExecuteProcess(
79+
cmd=[
80+
"ros2",
81+
"lifecycle",
82+
"set",
83+
f"/{namespace}/{driver_node_name}",
84+
"activate",
85+
],
86+
shell=False,
87+
)
2988

3089
pointcloud_to_laserscan_node = Node(
3190
package="pointcloud_to_laserscan",
@@ -47,6 +106,9 @@ def launch_setup(context: LaunchContext) -> list:
47106
)
48107

49108
return [
109+
Register.on_start(ouster_driver_node, context) if not use_sim else SKIP,
110+
Register.on_exit(configure_ouster_driver, context) if not use_sim else SKIP,
111+
Register.on_exit(activate_ouster_driver, context) if not use_sim else SKIP,
50112
Register.on_start(pointcloud_to_laserscan_node, context),
51113
]
52114

@@ -61,6 +123,9 @@ def generate_launch_description() -> LaunchDescription:
61123
[
62124
use_sim_arg.declaration,
63125
namespace_arg.declaration,
126+
target_frame_arg.declaration,
127+
ip_device_arg.declaration,
128+
ip_udp_destination_arg.declaration,
64129
OpaqueFunction(function=launch_setup),
65130
]
66131
)

ros2_ws/src/rcdt_sensors/launch/velodyne.launch.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use_sim_arg = LaunchArgument("simulation", True, [True, False])
1515
namespace_arg = LaunchArgument("namespace", "velodyne")
1616
target_frame_arg = LaunchArgument("target_frame", "")
17+
ip_address_arg = LaunchArgument("ip_address", "")
1718

1819

1920
def launch_setup(context: LaunchContext) -> list:
@@ -28,6 +29,7 @@ def launch_setup(context: LaunchContext) -> list:
2829
use_sim = use_sim_arg.bool_value(context)
2930
namespace = namespace_arg.string_value(context)
3031
target_frame = target_frame_arg.string_value(context)
32+
ip_address = ip_address_arg.string_value(context)
3133

3234
frame_prefix = namespace + "/" if namespace else ""
3335

@@ -38,7 +40,7 @@ def launch_setup(context: LaunchContext) -> list:
3840
parameters=[
3941
{
4042
"model": "VLP16",
41-
"device_ip": "10.15.20.5",
43+
"device_ip": ip_address,
4244
"frame_id": frame_prefix + "velodyne",
4345
}
4446
],
@@ -99,6 +101,8 @@ def generate_launch_description() -> LaunchDescription:
99101
[
100102
use_sim_arg.declaration,
101103
namespace_arg.declaration,
104+
target_frame_arg.declaration,
105+
ip_address_arg.declaration,
102106
OpaqueFunction(function=launch_setup),
103107
]
104108
)

0 commit comments

Comments
 (0)