From a8042c814eaaf8538fec3a846cb10ab3156a7237 Mon Sep 17 00:00:00 2001 From: Matt Spencer Date: Thu, 16 Apr 2026 10:49:49 +0000 Subject: [PATCH] Added Dockerfile to build robot controoler container --- .vscode/settings.json | 4 + CMakeLists.txt | 9 +++ Dockerfile | 48 +++++++++++ README.md | 179 ++++++++++++++++++++++++++++++++++++++++++ docker-entrypoint.sh | 7 ++ package.xml | 3 +- setup.cfg | 13 +++ 7 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-entrypoint.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index c578b33..fba2659 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { "python.analysis.extraPaths": [ "/opt/ros/kilted/lib/python3.12/site-packages" + ], + "ROS2.distro": "kilted", + "python.autoComplete.extraPaths": [ + "/opt/ros/kilted/lib/python3.12/site-packages" ] } diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6da01b4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.8) +project(my_robot) + +find_package(ament_cmake REQUIRED) +find_package(ament_cmake_python REQUIRED) + +ament_python_install_package(${PROJECT_NAME}) + +ament_package() diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..51cbf14 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +# syntax=docker/dockerfile:1 + +# ── Stage 1: build ──────────────────────────────────────────────────────────── +FROM ros:kilted AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3-colcon-common-extensions \ + python3-pip \ + ros-${ROS_DISTRO}-ament-cmake-python \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /ws + +# Install the vendored Raspbot hardware library +COPY raspbot_v2_interface/ raspbot_v2_interface/ +# PEP 668: newer Debian/Ubuntu marks the system Python as externally managed and blocks pip +# by default. --break-system-packages overrides this; safe here as the container is isolated. +RUN pip3 install --no-cache-dir --break-system-packages ./raspbot_v2_interface + +# Copy the ROS package into the standard colcon src/ layout and build it +COPY package.xml setup.py setup.cfg CMakeLists.txt src/my_robot/ +COPY my_robot/ src/my_robot/my_robot/ + +RUN . /opt/ros/${ROS_DISTRO}/setup.sh && \ + colcon build --packages-select my_robot + +# ── Stage 2: runtime ────────────────────────────────────────────────────────── +FROM ros:kilted-ros-core + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ros-${ROS_DISTRO}-rclpy \ + ros-${ROS_DISTRO}-geometry-msgs \ + ros-${ROS_DISTRO}-std-msgs \ + && rm -rf /var/lib/apt/lists/* + +# Bring across the installed Raspbot library and the built ROS overlay +COPY --from=builder /usr/local/lib /usr/local/lib +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 + +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["ros2", "run", "my_robot", "motor_controller"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..4de0221 --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +# my_robot — Motor Controller Node + +ROS 2 package for differential-drive motor control on the Yahboom Raspbot V2 platform. + +--- + +## Architecture + +``` + ┌──────────────────────────────────┐ + │ MotorControllerNode │ + │ │ + /cmd_vel ──────────>│ Twist → differential kinematics │ + (geometry_msgs/Twist)│ left = linear − (angular × wb/2)│ + │ right = linear + (angular × wb/2)│ + │ │ + /wheel_speeds ──────>│ Direct per-wheel override │ + (Float32MultiArray │ [FL, FR, RL, RR] │ + 4 × float32) │ │ + │ ▼ │ + │ raspbot_v2_interface │ + │ I²C bus 1, addr 0x2B │ + │ ▼ │ + │ /dev/i2c-1 ─────────> Motors │ + │ │ + /current_wheel_speeds│<─ telemetry @ 10 Hz │ + (Float32MultiArray) │ [FL, FR, RL, RR] │ + └──────────────────────────────────┘ +``` + +### Topics + +| Topic | Direction | Type | Description | +|---|---|---|---| +| `/cmd_vel` | Subscribed | `geometry_msgs/Twist` | Velocity command — `linear.x` (m/s) and `angular.z` (rad/s) | +| `/wheel_speeds` | Subscribed | `std_msgs/Float32MultiArray` | Direct per-wheel speed override `[FL, FR, RL, RR]` in library units (0–255) | +| `/current_wheel_speeds` | Published | `std_msgs/Float32MultiArray` | Current wheel speeds read from hardware, published at 10 Hz | + +### Parameters + +| Parameter | Default | Description | +|---|---|---| +| `wheel_base` | `0.3` | Distance between left and right wheels in metres | +| `max_speed` | `1.0` | Maximum motor speed in library units | + +### Hardware interface + +The node drives the Yahboom Raspbot V2 motor controller over **I²C bus 1** (device address `0x2B`) using the bundled `raspbot_v2_interface` library. The only host device required is `/dev/i2c-1`. + +--- + +## Setting up the robot + +### 1. Flash Raspberry Pi OS + +Use the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) to write Raspberry Pi OS (64-bit, Lite recommended) to a microSD card. + +Before writing, open the imager's **Advanced options** (⚙) and configure: + +| Setting | Value | +|---|---| +| Hostname | `raspbot-v2.local` | +| SSH | Enabled | +| Username / Password | Your preferred credentials | +| Wi-Fi | Your network SSID and password (if not using Ethernet) | + +Write the image, insert the card, and power on the Pi. Once it has booted and is reachable on the network (test with `ping raspbot-v2.local`), proceed to the next step. + +### 2. Provision with Ansible + +The [ansible/](ansible/) directory contains a playbook that handles the remaining setup (enabling SPI, installing Docker). See [ansible/README.md](ansible/README.md) for full instructions. + +--- + +## Deploying + +Once the image is built, pipe it directly to the target over SSH — no intermediate file or registry needed: + +```bash +docker save my_robot:latest | ssh matt@raspbot-v2.local docker load +``` + +Replace `matt` with the username configured in [ansible/inventory.ini](ansible/inventory.ini). + +--- + +## Building + +### Prerequisites + +- Docker (with BuildKit enabled) +- For cross-compilation from an amd64 host, QEMU user-space emulation must be registered with the kernel. If you haven't done this before, run once: + + ```bash + docker run --rm --privileged tonistiigi/binfmt --install arm64 + ``` + +### Build the image + +The Raspberry Pi is `arm64`, so the image must be built for that platform. On an amd64 host use `docker buildx`: + +```bash +docker build --platform linux/arm64 -t my_robot:latest . +``` + +`--load` exports the built image into the local Docker image store so it can be deployed with `docker save`. + +The build is split into two stages: + +1. **builder** — installs the Raspbot hardware library, then compiles the ROS package with `colcon` +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 + +--- + +## Launching + +The container needs access to the I²C bus that the motor controller is wired to. Pass only that device rather than running the container in privileged mode: + +```bash +docker run --rm \ + --device /dev/i2c-1 \ + my_robot:latest +``` + +If your board exposes the motor 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 parameters at launch + +ROS 2 parameters can be passed through `--ros-args`: + +```bash +docker run --rm \ + --device /dev/i2c-1 \ + my_robot:latest \ + ros2 run my_robot motor_controller \ + --ros-args -p wheel_base:=0.25 -p max_speed:=0.8 +``` + +### Sending velocity commands from the host + +With the container running, publish a `cmd_vel` message from another terminal (requires ROS 2 installed on the host or a second container on the same network): + +```bash +# Drive forward at 0.2 m/s +ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist \ + "{linear: {x: 0.2}, angular: {z: 0.0}}" + +# Turn on the spot +ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist \ + "{linear: {x: 0.0}, angular: {z: 0.5}}" + +# Stop +ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist \ + "{linear: {x: 0.0}, angular: {z: 0.0}}" +``` + +### Verifying telemetry + +```bash +ros2 topic echo /current_wheel_speeds +``` + +--- + +## Project layout + +``` +. +├── Dockerfile # Two-stage production image +├── docker-entrypoint.sh # Sources ROS overlays before exec +├── package.xml # ROS package manifest +├── setup.py # ament_python build definition +├── my_robot/ +│ ├── __init__.py +│ └── motor_controller_node.py +└── raspbot_v2_interface/ # Vendored Yahboom hardware library + └── Raspbot_Lib/ + └── Raspbot_Lib.py # I²C driver (smbus, bus 1, addr 0x2B) +``` diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..e357a52 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +source /opt/ros/${ROS_DISTRO}/setup.bash +source /ws/install/setup.bash + +exec "$@" diff --git a/package.xml b/package.xml index 96e4b03..9ab40c3 100644 --- a/package.xml +++ b/package.xml @@ -10,7 +10,8 @@ geometry_msgs std_msgs - ament_python + ament_cmake + ament_cmake_python ament_copyright ament_flake8 ament_pep257 diff --git a/setup.cfg b/setup.cfg index e69de29..284e957 100644 --- a/setup.cfg +++ b/setup.cfg @@ -0,0 +1,13 @@ +[metadata] +name = my_robot +version = 0.0.1 + +[options] +packages = find: +install_requires = + setuptools + +[develop] +script_dir=$base/lib/my_robot +[install] +install_scripts=$base/lib/my_robot