A web application for controlling ROS 2 robots, featuring a React frontend, ROS 2 integration via rosbridge, and secure local development setup with Caddy and HTTPS. Inspired by retro handheld consoles.
- Responsive design for desktop and mobile
- ROS 2 connection (via rosbridge)
- Camera stream display (via web_video_server)
- User-created control pads with reusable starter templates
- 3D visualization support
- Behavior tree editing with searchable nodes and ROS resources
- Customizable themes with user-created color palettes
roboboy_1080p.webm
Prerequisites
- Docker and Docker Compose installed.
- mkcert for local HTTPS setup.
Local Development Setup
-
Clone the Repository:
git clone git@github.com:tessel-la/robo-boy.git cd robo-boy -
Setup Local HTTPS with mkcert:
- Install mkcert's CA: Run this once per machine to make browsers trust local certificates.
(You might need
mkcert -install
sudoor administrator privileges) - Create Certificates Directory:
mkdir certs
- Generate Certificate: Replace
YOUR_HOST_IPwith your computer's actual local network IP address (e.g.,192.168.1.67). This certificate will be valid forlocalhostand your IP.mkcert -key-file certs/local-key.pem -cert-file certs/local-cert.pem localhost 127.0.0.1 ::1 YOUR_HOST_IP
- Install mkcert's CA: Run this once per machine to make browsers trust local certificates.
-
Build and Run Services: This command builds the Docker images (if they don't exist or need updating) and starts the
app(React Vite dev server),ros-stack(rosbridge and web_video_server), andcaddy(reverse proxy) containers.docker compose up -d --build
-
Access the Application: On your development machine, open
https://localhost. From another device on the same network, openhttps://YOUR_HOST_IPusing the same IP you used formkcert.
Simulation Workspace Overlays
The ros-stack service runs with network_mode: host so it can discover ROS 2 nodes from host-networked simulation containers such as aerial_sim_cont, and manipulator_sim_cont. To let rosbridge understand custom message, service, and action types from those simulations, mount each built ROS 2 workspace install directory under /overlay_ws/<name>.
ros-stack automatically sources every install workspace mounted under /overlay_ws, and still supports the legacy single-workspace mount at /overlay_ws. It also supports ROBOT_WORKSPACE_SETUP=/path/to/install/setup.bash for workspaces whose generated hooks expect their original path.
Robo-Boy keeps robot-specific mounts in Compose override files. Choose one setup by copying an example .env; after that, regular docker compose up uses the selected files automatically.
# No robot-specific overlay
cp .env.no-overlay.example .env
# Aerostack overlay
cp .env.aerostack.example .env
# Manipulator overlay
cp .env.manipulator.example .envWhen switching overlays, recreate the ROS service so the mounts and sourced workspaces change:
docker compose up -d --build --force-recreate ros-stackThe Aerostack override mounts ~/.aerostack2_install at /overlay_ws/aerostack2. After the Aerostack simulation workspace has been built, copy its install directory from the simulation container and select the Aerostack .env:
docker cp aerial_sim_cont:/root/aerostack2_ws/install ~/.aerostack2_install
cp .env.aerostack.example .env
docker compose up -d --buildThe manipulator simulation stores its Jazzy build cache in Docker volumes named manipulator-sim_colcon_install_jazzy and manipulator-sim_colcon_build_jazzy, mounted in manipulator_sim_cont at /home/rosuser/moveit_ws/install and /home/rosuser/moveit_ws/build. Because that workspace uses symlinked install files, the Robo-Boy override also mounts the simulation build volume at both /overlay_ws/build and /home/rosuser/moveit_ws/build, plus the source packages read-only, so custom action definitions resolve correctly.
Start or build the manipulator simulation first, then start Robo-Boy with the optional override:
cd /home/riccardo/code/manipulator-sim
docker compose up -d --build
cd /home/riccardo/code/robo-boy
cp .env.manipulator.example .env
docker compose up -d --buildThe override assumes manipulator-sim is next to robo-boy at ../manipulator-sim. If it is somewhere else, set MANIPULATOR_SIM_DIR:
cp .env.manipulator.example .env
printf '\nMANIPULATOR_SIM_DIR=/path/to/manipulator-sim\n' >> .env
docker compose up -d --buildIf Robo-Boy is already running, recreate only the ROS service after selecting the manipulator .env:
docker compose up -d --build --force-recreate ros-stackIf your simulator Compose project or volume names differ, set these overrides in .env:
MANIPULATOR_SIM_INSTALL_VOLUME=manipulator-sim_colcon_install_jazzy
MANIPULATOR_SIM_BUILD_VOLUME=manipulator-sim_colcon_build_jazzyYou should see overlay activation messages in the ROS stack logs:
docker compose logs ros-stackFor a custom workspace that has an install directory on the host, add a Compose override that mounts it under /overlay_ws/<name>:
services:
ros-stack:
volumes:
- /path/to/custom_ws/install:/overlay_ws/custom:roFor a workspace stored in a Docker volume, mark the volume as external:
services:
ros-stack:
volumes:
- custom_ws_install:/overlay_ws/custom:ro
volumes:
custom_ws_install:
external: trueThen run Robo-Boy with both Compose files:
COMPOSE_FILE=docker-compose.yml:docker-compose.custom.yml docker compose up -d --buildMake sure the simulation container and ros-stack use compatible ROS settings, especially ROS_DISTRO and ROS_DOMAIN_ID, and that the workspace has been built before mounting its install directory.
If the custom workspace was built with colcon build --symlink-install, also mount any build/source paths referenced by generated hooks or symlinks. The manipulator override is the reference pattern for that case.
Stopping the Services
# Stop and remove containers
docker compose down
# Stop, remove containers, AND remove Caddy's data volumes (useful for a clean restart)
docker compose down -vThe application starts with an empty control area and lets each user create the pads they need. Pads can be built from scratch or cloned from a starter template.
Starter Template
A generic starter pad with two joysticks publishing one four-axis sensor_msgs/msg/Joy message on /joy, plus a pulse heartbeat monitor on /heartbeat. Selecting the template creates an editable user-owned copy.
Custom Gamepad Creator
Create your own control interfaces directly in the app! The Custom Gamepad Creator allows you to design personalized control layouts using a drag-and-drop interface.
Features:
- Component Library: Choose from joysticks, buttons, D-pads, toggles, and sliders
- Grid-based Design: Drag and drop components on a customizable grid
- Real-time Preview: Test your gamepad while designing
- ROS Integration: Configure each component to publish to specific ROS topics
- Save & Share: Store layouts locally and export/import via JSON files
Getting Started:
- Click the "+" button in the control panel tabs
- Select "Create Custom Gamepad" or choose a template
- Drag components from the palette to design your layout
- Configure each component's ROS topic and behavior
- Save your custom gamepad for future use
Saved pads can be exported individually as versioned JSON files and imported on another device or browser. Imports are added to the custom layout library without opening a control tab.
Perfect for creating specialized control interfaces tailored to your specific robot's needs!
Tab Management
You can open multiple user-created control panels and switch between them with tabs.
Custom Theme Creator
theme_custom.webm
The application supports multiple themes, including user-created custom themes. Themes define the color palette for the UI elements.
- Access Theme Menu: Click the theme icon button (usually in the bottom-right corner). This opens a popup menu displaying available themes (default and custom).
- Create New Theme: Click the "Create New Theme..." button in the popup. This opens the Theme Creator modal.
- Define Theme:
- Enter a unique Name for your theme.
- Select the base Colors (Primary, Secondary, Background) using the color pickers. Optional colors (Text, Border, etc.) can also be set.
- Choose an Icon to represent your theme in the selector menu.
- Click Save Theme.
- Editing/Deleting: Custom themes will have Edit (pencil) and Delete (trash) icons next to them in the theme selector popup. Clicking Edit opens the Theme Creator pre-filled with that theme's settings. Clicking Delete prompts for confirmation before removing the theme.
How Theme System Works
- Default themes (
light,dark,solarized) have their CSS variables defined directly insrc/index.cssusing[data-theme="themename"]selectors. - Custom themes are stored in the browser's
localStorage. - When a custom theme is selected, JavaScript dynamically generates a
<style>tag containing CSS variable overrides based on the saved colors and injects it into the document head. The<body>element also gets adata-theme="custom-theme-id"attribute. - UI components should primarily use the defined CSS variables (e.g.,
var(--primary-color),var(--background-color)) for styling to ensure they adapt correctly to the selected theme.
Development Tips
- Changes to frontend code (in
/src) should trigger hot-reloading in the browser. - If you modify
Dockerfile,docker-compose.yml, orCaddyfile, you'll need to rebuild and restart the services (docker compose up -d --build --force-recreate). - Caddy logs can be viewed with
docker compose logs caddy. - ROS stack logs can be viewed with
docker compose logs ros-stack.
The project is enforcing high code quality standards with strict coverage thresholds (>20% for Statements, Branches, Functions, and Lines).
Unit Tests
Unit tests are built with Vitest. They are co-located with source files (e.g., src/hooks/useRos.ts -> src/hooks/useRos.test.ts).
# Run unit tests in watch mode (for development)
npm run test
# Run unit tests once (for CI/CD)
npm run test:run
# Generate coverage report
npm run test:coverageEnd-to-End (E2E) Tests
End-to-End tests are built with Playwright. They verify the full application flow in a real browser environment.
# Run all E2E tests (headless)
npm run e2e
# Run against the Docker/Caddy stack that is already up on localhost
npm run e2e:stack
# Run E2E tests with UI runner (interactive debugging)
npm run e2e:ui
# View the last E2E test report
npm run e2e:reportProject Structure
The codebase follows a component-based architecture:
/e2e- End-to-End tests (Playwright)app-navigation.spec.ts- Navigation and routing testsentry-page.spec.ts- Entry page interaction tests
/src/components- Main UI components and layouts- Core UI components:
MainControlView,VisualizationPanel,Navbar,SettingsPopup, etc. /gamepads/custom- Runtime wrapper for user-created gamepads/visualizers- React wrappers for 3D visualizationPointCloudViz.tsx- Point cloud visualization componentLaserScanViz.tsx- Laser scan visualization componentPoseStampedViz.tsx- Pose/Odometry visualization componentUrdfViz.tsx- URDF model visualization componentCameraInfoViz.tsx- Camera information display
- Core UI components:
/src/features- Feature-specific code organized by functionality/theme- Theme system with custom color palette creation/customGamepad- Custom Gamepad Creator Systemtypes.ts- TypeScript interfaces and component definitionsdefaultLayouts.ts- Pre-built layouts and component librarygamepadStorage.ts- Local storage management for custom layouts/components- Custom gamepad editor and component implementationsGamepadEditor.tsx- Main drag-and-drop editor interfaceCustomGamepadLayout.tsx- Runtime layout rendererGamepadComponent.tsx- Generic component wrapperJoystickComponent.tsx,ButtonComponent.tsx,DPadComponent.tsxToggleComponent.tsx,SliderComponent.tsx- Input components
/src/hooks- Custom React hooks (useRos.ts,useRos3d.ts, etc.)/src/utils- Utility functions and helpers/ros3d- Core ROS 3D logic and primitive definitions/visualizers- Pure logic for PointCloud, LaserScan, etc.
/src/types- TypeScript type definitionsvitest.config.ts- Unit test configurationplaywright.config.ts- E2E test configuration
Adding Custom Gamepad Layouts
Use the built-in Custom Gamepad Creator accessible through the "+" button in control panel tabs. No coding required!
- Drag-and-drop interface with pre-built components
- Perfect for most use cases and quick prototyping
- Supports joysticks, buttons, D-pads, toggles, and sliders
- Real-time preview and easy ROS topic configuration
When the in-app system isn't enough for your specific needs, you can implement custom gamepads via code:
- Advanced Components: Create custom component types not available in the drag-and-drop editor
- Complex Logic: Implement sophisticated control algorithms and state management
- Performance Optimization: Fine-tune for high-frequency or specialized operations
- Custom ROS Integration: Support for complex message types, actions, and services
Implementation Paths:
- Starter Templates: Add reusable layouts to
defaultLayouts.ts - Extend Custom System: Add new component types to the custom gamepad library
- Comprehensive Guide: See the detailed README in
/src/features/customGamepadfor the architecture overview, component system, new component types, ROS message support, and storage format.
