Add lider ROS node and move robot control into its own directory
This commit is contained in:
@@ -149,6 +149,44 @@ The sensor will activate automatically when this command runs and deactivate whe
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### RPLIDAR A1
|
||||||
|
|
||||||
|
Runs in a separate container built from [lidar/Dockerfile](lidar/Dockerfile).
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ sllidar_ros2 (rplidar_node) │
|
||||||
|
│ │
|
||||||
|
│ serial 115200 baud │
|
||||||
|
│ angle_compensate = true │
|
||||||
|
│ scan_mode = Sensitivity │
|
||||||
|
│ ▼ │
|
||||||
|
│ /dev/ttyUSB0 ──────> RPLIDAR A1 │
|
||||||
|
│ │
|
||||||
|
/scan <───────│ LaserScan @ ~10 Hz │
|
||||||
|
(sensor_msgs/ │ 360° scan, range 0.15–12 m │
|
||||||
|
LaserScan) │ │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Topics
|
||||||
|
|
||||||
|
| Topic | Direction | Type | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/scan` | Published | `sensor_msgs/LaserScan` | 360° laser scan in the `laser` frame |
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
The LIDAR container is configured via environment variables in `.env` or `docker-compose.yml`. See the [Launching](#launching) section for details.
|
||||||
|
|
||||||
|
#### Verifying LIDAR data
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 topic echo /scan
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Setting up the robot
|
## Setting up the robot
|
||||||
|
|
||||||
### 1. Flash Raspberry Pi OS
|
### 1. Flash Raspberry Pi OS
|
||||||
@@ -183,29 +221,51 @@ The [ansible/](ansible/) directory contains a playbook that handles the remainin
|
|||||||
docker run --rm --privileged tonistiigi/binfmt --install arm64
|
docker run --rm --privileged tonistiigi/binfmt --install arm64
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build the image
|
### Build with Docker Compose (recommended)
|
||||||
|
|
||||||
The Raspberry Pi is `arm64`, so the image must be built for that platform. On an amd64 host use `docker buildx`:
|
Both images are defined in `docker-compose.yml`. Build them together:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build --platform linux/arm64 -t raspbot_v2:latest .
|
docker compose build
|
||||||
```
|
```
|
||||||
|
|
||||||
`--load` exports the built image into the local Docker image store so it can be deployed with `docker save`.
|
Or build a single service:
|
||||||
|
|
||||||
The build is split into two stages:
|
```bash
|
||||||
|
docker compose build robot
|
||||||
|
docker compose build lidar
|
||||||
|
```
|
||||||
|
|
||||||
1. **builder** — installs the Raspbot hardware library, then compiles the ROS package with `colcon`
|
The builds are split into two stages each:
|
||||||
2. **runtime** — copies only the colcon install overlay and hardware library into a clean `ros:kilted` base; no build tools are included in the final image
|
|
||||||
|
1. **builder** — compiles the ROS package(s) with `colcon`; the lidar builder also clones `sllidar_ros2` from GitHub
|
||||||
|
2. **runtime** — copies only the install overlay into a clean `ros:kilted-ros-core` base; no build tools in the final image
|
||||||
|
|
||||||
|
### Build images individually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Robot controller
|
||||||
|
docker build --platform linux/arm64 -f robot/Dockerfile -t raspbot_v2:latest .
|
||||||
|
|
||||||
|
# LIDAR
|
||||||
|
docker build --platform linux/arm64 -f lidar/Dockerfile -t raspbot_v2_lidar:latest .
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Deploying
|
## Deploying
|
||||||
|
|
||||||
Once the image is built, pipe it directly to the target over SSH — no intermediate file or registry needed:
|
Pipe both images directly to the target over SSH — no intermediate file or registry needed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker save raspbot_v2:latest | ssh matt@raspbot-v2.local docker load
|
docker save raspbot_v2:latest raspbot_v2_lidar:latest \
|
||||||
|
| ssh matt@raspbot-v2.local docker load
|
||||||
|
```
|
||||||
|
|
||||||
|
Then copy the compose file to the target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp docker-compose.yml matt@raspbot-v2.local:~/
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `matt` with the username configured in [ansible/inventory.ini](ansible/inventory.ini).
|
Replace `matt` with the username configured in [ansible/inventory.ini](ansible/inventory.ini).
|
||||||
@@ -214,19 +274,55 @@ Replace `matt` with the username configured in [ansible/inventory.ini](ansible/i
|
|||||||
|
|
||||||
## Launching
|
## Launching
|
||||||
|
|
||||||
The default `CMD` starts both nodes together via the launch file. The container needs access to the I²C bus — pass only that device rather than running privileged:
|
### Start everything with Docker Compose (recommended)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
This starts both the robot controller and LIDAR containers. Logs from both are interleaved in the terminal, each line prefixed with the service name. To run in the background:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
docker compose logs -f # follow logs
|
||||||
|
docker compose down # stop and remove containers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
|
||||||
|
Create a `.env` file in the same directory as `docker-compose.yml` to override defaults:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ROS_DOMAIN_ID=0
|
||||||
|
LIDAR_PORT=/dev/ttyUSB0
|
||||||
|
LIDAR_FRAME_ID=laser
|
||||||
|
```
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `ROS_DOMAIN_ID` | `0` | ROS 2 domain — must match on all nodes |
|
||||||
|
| `LIDAR_PORT` | `/dev/ttyUSB0` | Host device node for the RPLIDAR |
|
||||||
|
| `LIDAR_FRAME_ID` | `laser` | `frame_id` in published `LaserScan` messages |
|
||||||
|
|
||||||
|
### Run containers individually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Robot controller
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--network=host \
|
--network=host \
|
||||||
--device /dev/i2c-1 \
|
--device /dev/i2c-1 \
|
||||||
--env ROS_DOMAIN_ID=0 \
|
--env ROS_DOMAIN_ID=0 \
|
||||||
raspbot_v2:latest
|
raspbot_v2:latest
|
||||||
|
|
||||||
|
# LIDAR
|
||||||
|
docker run --rm \
|
||||||
|
--network=host \
|
||||||
|
--device /dev/ttyUSB0 \
|
||||||
|
--env ROS_DOMAIN_ID=0 \
|
||||||
|
raspbot_v2_lidar:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
If your board exposes the controller on a different bus (check with `ls /dev/i2c-*` on the host), substitute the correct device node (e.g. `--device /dev/i2c-0`).
|
### Overriding robot launch parameters
|
||||||
|
|
||||||
### Overriding parameters at launch
|
|
||||||
|
|
||||||
Launch arguments can be appended after the image name:
|
Launch arguments can be appended after the image name:
|
||||||
|
|
||||||
@@ -250,6 +346,14 @@ Available launch arguments:
|
|||||||
| `tilt_center_deg` | `60.0` | Tilt angle at startup and shutdown (degrees) |
|
| `tilt_center_deg` | `60.0` | Tilt angle at startup and shutdown (degrees) |
|
||||||
| `ultrasonic_rate_hz` | `10.0` | Ultrasonic sensor publish rate (Hz) |
|
| `ultrasonic_rate_hz` | `10.0` | Ultrasonic sensor publish rate (Hz) |
|
||||||
|
|
||||||
|
### LIDAR device permissions
|
||||||
|
|
||||||
|
The RPLIDAR connects as a USB serial device. If the user running Docker is not in the `dialout` group, add them and log back in:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG dialout $USER
|
||||||
|
```
|
||||||
|
|
||||||
### Sending velocity commands from the host
|
### Sending velocity commands from the host
|
||||||
|
|
||||||
With the container running, publish from another terminal (requires ROS 2 on the host or a second container on the same network):
|
With the container running, publish from another terminal (requires ROS 2 on the host or a second container on the same network):
|
||||||
@@ -301,20 +405,24 @@ ros2 topic echo /joint_states
|
|||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── Dockerfile # Two-stage production image
|
├── docker-compose.yml # Launches robot and lidar containers together
|
||||||
├── docker-entrypoint.sh # Sources ROS overlays before exec
|
├── docker-entrypoint.sh # Sources ROS overlays before exec (shared by both images)
|
||||||
├── src/
|
├── robot/
|
||||||
│ └── raspbot_v2/
|
│ ├── Dockerfile # Robot controller image (two-stage)
|
||||||
│ ├── package.xml # ROS package manifest
|
│ ├── src/
|
||||||
│ ├── setup.py # ament_python build definition
|
│ │ └── raspbot_v2/
|
||||||
│ ├── launch/
|
│ │ ├── package.xml # ROS package manifest
|
||||||
│ │ └── robot.launch.py # Starts both nodes together
|
│ │ ├── setup.py # ament_python build definition
|
||||||
│ └── raspbot_v2/
|
│ │ ├── launch/
|
||||||
│ ├── __init__.py
|
│ │ │ └── robot.launch.py # Starts all robot nodes together
|
||||||
│ ├── motor_controller_node.py # Differential-drive motor control
|
│ │ └── raspbot_v2/
|
||||||
│ ├── camera_orientation_node.py # Pan/tilt servo control
|
│ │ ├── __init__.py
|
||||||
│ └── ultrasonic_node.py # HC-SR04 range sensor
|
│ │ ├── motor_controller_node.py # Differential-drive motor control
|
||||||
└── raspbot_v2_interface/ # Vendored Yahboom hardware library
|
│ │ ├── camera_orientation_node.py # Pan/tilt servo control
|
||||||
└── Raspbot_Lib/
|
│ │ └── ultrasonic_node.py # HC-SR04 range sensor
|
||||||
└── Raspbot_Lib.py # I²C driver (smbus, bus 1, addr 0x2B)
|
│ └── raspbot_v2_interface/ # Vendored Yahboom hardware library
|
||||||
|
│ └── Raspbot_Lib/
|
||||||
|
│ └── Raspbot_Lib.py # I²C driver (smbus, bus 1, addr 0x2B)
|
||||||
|
└── lidar/
|
||||||
|
└── Dockerfile # RPLIDAR A1 image (two-stage, clones sllidar_ros2)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Both containers share the host network so ROS 2 DDS discovery works without
|
||||||
|
# any extra multicast configuration. Each container is given access only to
|
||||||
|
# the specific device it needs rather than running in privileged mode.
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
robot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: robot/Dockerfile
|
||||||
|
platforms:
|
||||||
|
- linux/arm64
|
||||||
|
image: raspbot_v2:latest
|
||||||
|
network_mode: host
|
||||||
|
devices:
|
||||||
|
- /dev/i2c-1:/dev/i2c-1
|
||||||
|
environment:
|
||||||
|
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-0}
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
lidar:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: lidar/Dockerfile
|
||||||
|
platforms:
|
||||||
|
- linux/arm64
|
||||||
|
image: raspbot_v2_lidar:latest
|
||||||
|
network_mode: host
|
||||||
|
devices:
|
||||||
|
- ${LIDAR_PORT:-/dev/ttyUSB0}:${LIDAR_PORT:-/dev/ttyUSB0}
|
||||||
|
environment:
|
||||||
|
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-0}
|
||||||
|
command:
|
||||||
|
- ros2
|
||||||
|
- launch
|
||||||
|
- sllidar_ros2
|
||||||
|
- sllidar_a1_launch.py
|
||||||
|
- serial_port:=${LIDAR_PORT:-/dev/ttyUSB0}
|
||||||
|
- frame_id:=${LIDAR_FRAME_ID:-laser}
|
||||||
|
- serial_baudrate:=115200
|
||||||
|
- angle_compensate:=true
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ── Stage 1: build ────────────────────────────────────────────────────────────
|
||||||
|
FROM ros:kilted AS builder
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
python3-colcon-common-extensions \
|
||||||
|
git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /ws
|
||||||
|
|
||||||
|
RUN git clone --depth 1 https://github.com/slamtec/sllidar_ros2.git src/sllidar_ros2
|
||||||
|
|
||||||
|
RUN source /opt/ros/${ROS_DISTRO}/setup.bash && \
|
||||||
|
colcon build --packages-select sllidar_ros2
|
||||||
|
|
||||||
|
# ── Stage 2: runtime ──────────────────────────────────────────────────────────
|
||||||
|
FROM ros:kilted-ros-core
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ros-${ROS_DISTRO}-rclcpp \
|
||||||
|
ros-${ROS_DISTRO}-sensor-msgs \
|
||||||
|
ros-${ROS_DISTRO}-std-srvs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /ws/install /ws/install
|
||||||
|
|
||||||
|
# Source both ROS base and the workspace overlay on every shell/exec
|
||||||
|
RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> /etc/bash.bashrc && \
|
||||||
|
echo "source /ws/install/setup.bash" >> /etc/bash.bashrc
|
||||||
|
|
||||||
|
# Reuse the same entrypoint as the robot container (sources overlays, starts daemon)
|
||||||
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
CMD ["ros2", "launch", "sllidar_ros2", "sllidar_a1_launch.py"]
|
||||||
@@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
WORKDIR /ws
|
WORKDIR /ws
|
||||||
|
|
||||||
# Copy the ROS package into the standard colcon src/ layout and build it
|
# Copy the ROS package into the standard colcon src/ layout and build it
|
||||||
COPY src/raspbot_v2/ src/raspbot_v2/
|
COPY robot/src/raspbot_v2/ src/raspbot_v2/
|
||||||
|
|
||||||
RUN source /opt/ros/${ROS_DISTRO}/setup.bash && \
|
RUN source /opt/ros/${ROS_DISTRO}/setup.bash && \
|
||||||
colcon build --packages-select raspbot_v2
|
colcon build --packages-select raspbot_v2
|
||||||
@@ -30,7 +30,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install the Raspbot hardware library directly into site-packages
|
# Install the Raspbot hardware library directly into site-packages
|
||||||
COPY raspbot_v2_interface/ /usr/local/lib/python3.12/dist-packages/raspbot_v2_interface/
|
COPY robot/raspbot_v2_interface/ /usr/local/lib/python3.12/dist-packages/raspbot_v2_interface/
|
||||||
|
|
||||||
# Bring across the built ROS overlay
|
# Bring across the built ROS overlay
|
||||||
COPY --from=builder /ws/install /ws/install
|
COPY --from=builder /ws/install /ws/install
|
||||||
Reference in New Issue
Block a user