SLAM mapping service for Robonix. It
turns a robot’s lidar / camera / odom streams into a live 2D occupancy
grid, a 3D point cloud, and a SLAM-corrected pose, published under a
fixed, engine-agnostic capability surface (robonix/service/map/*), and
persists named maps so a robot can re-localize across restarts.
It is a Robonix service package: it registers with atlas, discovers its
sensor inputs by capability contract (never hardcoded topics), and is brought
up by rbnx boot. Consumers (scene, nav) bind the contracts, not the
SLAM engine.
Capability surface, config schema, and persistence layout: CAPABILITY.md.
The launch branches on the provider roles bound by the deployment, so the same
service supports 2D lidar, 3D lidar, RGB-D, and external odometry without
robot-specific branches.
How to integrate it on your robot
Register your sensors as Robonix primitives under the standard
contracts (robonix/primitive/lidar/lidar3d, .../camera/depth,
.../chassis/odom, …). mapping discovers them via atlas.
Pick a deployment target and reference the matching package manifest
from your deploy robonix_manifest.yaml:
rbnx build -f robonix_manifest.yaml then rbnx boot -f robonix_manifest.yaml.
Consume the map: subscribe to robonix/service/map/occupancy_grid /
.../pointcloud / .../pose (resolve them via atlas).
config/rtabmap_params.template.yaml is
only a starting template. Copy it into the robot deployment repository and set
params_file; Mapping never loads the upstream template at runtime. Inline
rtabmap_params applies after the deploy-owned file.
With external odometry, deskew_lidar compensates each PointCloud2 point in
the odom frame before SLAM. Bind only the sensor roles Mapping should consume.
Separate localization and navigation odometry
Some robots need accurate ICP/RGB-D odometry for RTAB-Map localization but a
lower-latency chassis odometry stream for navigation. Enable the optional
split-odometry bridge for this setup:
service:
- name: mapping
url: https://github.com/syswonder/service-map-rbnx
config:
algo: rtabmap
base_frame: base_link
# Private RTAB-Map odometry frame.
odom_frame: odom_icp
# Public chassis odometry used by navigation.
navigation_odom_bridge: true
navigation_odom_topic: /odom
navigation_odom_frame: odom
sensor_providers:
lidar3d: roof_lidar
# Do not bind odom in split-odometry mode.
In this mode, RTAB-Map uses an internal message-only odometry trajectory in
odom_icp, the chassis owns odom -> base_link, and Mapping publishes the
correction required by navigation:
map -> odom -> base_link
The bridge computes map -> odom from RTAB-Map localization and the two
timestamp-aligned odometry poses. odom_frame and navigation_odom_frame
must be different, and sensor_providers.odom must not be configured.
The feature defaults to false; existing external- and internal-odometry
deployments are unchanged. See config.spec for the complete
field definitions.
Deployment targets
One package, three targets (selected by the deploy manifest: field — see
CAPABILITY.md):
target
manifest
runtime
x86_64 + docker
package_manifest.yaml
docker (docker/Dockerfile)
arm64 Jetson + docker
package_manifest.jetson-docker.yaml
docker (docker/Dockerfile.jetson, L4T)
arm64 Jetson + native
package_manifest.jetson-native.yaml
host ROS2 (scripts/start_native.sh)
Add a target by adding a package_manifest.<target>.yaml plus a case branch
in scripts/build.sh — the rest of the package is unchanged.
The generated ROS 2 overlay intentionally builds only Robonix’s custom map
interface package. Standard interfaces such as sensor_msgs continue to come
from the target’s ROS 2 Humble installation, preserving its support libraries
and CMake exports for consumers such as cv_bridge.
Saving & re-using a map
Mapping starts with a fresh runtime database. Call save_map(map_id) after
coverage is complete; the named map lives under {MAPPING_MAPS_DIR}/{map_id}/
(default: the package’s maps/ dir, which survives container restarts):
Build a map:map_mode: mapping. Drive the robot around, then call
save_map(map_id) to publish an immutable database and previews.
Re-use a map: set map_id plus map_mode: localization. Mapping copies
the saved db to a private runtime path and re-localizes against it; the
map frame is stable across restarts, so Scene can load semantic state for
the same id.
Start fresh:map_mode: mapping (the default). It never writes an
existing saved map.
Localization-mode persistence only re-anchors correctly because the map
frame is loaded from the saved db. Without map_mode: localization the
map origin resets to the robot’s boot pose each run.
Web UI (live map + runtime map ops)
A dependency-light operator page (stdlib http.server + Pillow) is enabled on
port 8091 by default; set deployment config webui_port: 0 to disable it.
It binds 127.0.0.1 by default because the map controls are unauthenticated.
An authenticated overlay deployment may explicitly set webui_host (or
MAPPING_WEBUI_HOST); otherwise use the local browser or an SSH tunnel.
It runs inside the mapping bridge process, so its buttons call the same
map_ops impls the gRPC/MCP capabilities use — no extra round trip — and it
reads the live /map + pose straight off the bridge’s rclpy node.
Live map canvas — occupancy grid + robot pose, with drag-to-pan,
wheel-zoom, a 1 m grid, and double-click-to-fit. Same world-centered
view model as scene’s web UI (canvas backing-store pinned to display size,
so click coordinates are exact).
Save — snapshot the live map under a map_id (writes
rtabmap.db + occupancy.png/pgm/yaml + meta.yaml).
Library — every saved map with a thumbnail; Load re-localizes onto
it, Del removes it from disk.
Mode — flip Mapping ⇄ Localization at runtime; a badge + button
highlight shows the current mode.
Reset map — wipe the live SLAM session and rebuild from scratch (for
when mapping diverges). Note: the origin resets to the robot’s current
pose, so the rebuilt frame won’t match the old map (origin drift).
Click the map → pose estimate — seeds /initialpose so rtabmap
re-localizes; the activity log records the seeded pose and, a few
seconds later, where it converged + the distance from your estimate.
These are the same operations exposed as runtime RPC + MCP capabilities
(so Pilot can drive them too): save_map, load_map, pose_estimate,
switch_mode (the webui adds reset + delete on top). All work on the
running rtabmap without a redeploy — load/switch_mode call rtabmap’s
runtime services and fall back to a restart with the config’s map_mode /
map_id when those services aren’t reachable.
The web UI has no auth — it’s a LAN debug tool. Don’t expose the port to an
untrusted network.
/map never populates — a provider binding is missing or points to the
wrong provider. Check the [start_engine] rtabmap scan2d=… scan3d=… line.
map_mode=localization errors “no saved map” — run a mapping session
with that map_id first, and confirm MAPPING_MAPS_DIR is the same path
(mounted) across runs.
Map origin drifts between runs — you’re in mapping mode (origin =
boot pose). Use localization to re-anchor to the saved map.
mapping_rbnx
SLAM mapping service for Robonix. It turns a robot’s lidar / camera / odom streams into a live 2D occupancy grid, a 3D point cloud, and a SLAM-corrected pose, published under a fixed, engine-agnostic capability surface (
robonix/service/map/*), and persists named maps so a robot can re-localize across restarts.It is a Robonix service package: it registers with
atlas, discovers its sensor inputs by capability contract (never hardcoded topics), and is brought up byrbnx boot. Consumers (scene,nav) bind the contracts, not the SLAM engine.SLAM engines (
algo)rtabmap(default, recommended)dlio/ws/installfastlio2The launch branches on the provider roles bound by the deployment, so the same service supports 2D lidar, 3D lidar, RGB-D, and external odometry without robot-specific branches.
How to integrate it on your robot
Register your sensors as Robonix primitives under the standard contracts (
robonix/primitive/lidar/lidar3d,.../camera/depth,.../chassis/odom, …). mapping discovers them via atlas.Pick a deployment target and reference the matching package manifest from your deploy
robonix_manifest.yaml:rbnx build -f robonix_manifest.yamlthenrbnx boot -f robonix_manifest.yaml.Consume the map: subscribe to
robonix/service/map/occupancy_grid/.../pointcloud/.../pose(resolve them via atlas).config/rtabmap_params.template.yamlis only a starting template. Copy it into the robot deployment repository and setparams_file; Mapping never loads the upstream template at runtime. Inlinertabmap_paramsapplies after the deploy-owned file.With external odometry,
deskew_lidarcompensates each PointCloud2 point in the odom frame before SLAM. Bind only the sensor roles Mapping should consume.Separate localization and navigation odometry
Some robots need accurate ICP/RGB-D odometry for RTAB-Map localization but a lower-latency chassis odometry stream for navigation. Enable the optional split-odometry bridge for this setup:
In this mode, RTAB-Map uses an internal message-only odometry trajectory in
odom_icp, the chassis ownsodom -> base_link, and Mapping publishes the correction required by navigation:The bridge computes
map -> odomfrom RTAB-Map localization and the two timestamp-aligned odometry poses.odom_frameandnavigation_odom_framemust be different, andsensor_providers.odommust not be configured.The feature defaults to
false; existing external- and internal-odometry deployments are unchanged. See config.spec for the complete field definitions.Deployment targets
One package, three targets (selected by the deploy
manifest:field — see CAPABILITY.md):package_manifest.yamldocker/Dockerfile)package_manifest.jetson-docker.yamldocker/Dockerfile.jetson, L4T)package_manifest.jetson-native.yamlscripts/start_native.sh)Add a target by adding a
package_manifest.<target>.yamlplus a case branch inscripts/build.sh— the rest of the package is unchanged.The generated ROS 2 overlay intentionally builds only Robonix’s custom
mapinterface package. Standard interfaces such assensor_msgscontinue to come from the target’s ROS 2 Humble installation, preserving its support libraries and CMake exports for consumers such ascv_bridge.Saving & re-using a map
Mapping starts with a fresh runtime database. Call
save_map(map_id)after coverage is complete; the named map lives under{MAPPING_MAPS_DIR}/{map_id}/(default: the package’smaps/dir, which survives container restarts):map_mode: mapping. Drive the robot around, then callsave_map(map_id)to publish an immutable database and previews.map_idplusmap_mode: localization. Mapping copies the saved db to a private runtime path and re-localizes against it; the map frame is stable across restarts, so Scene can load semantic state for the same id.map_mode: mapping(the default). It never writes an existing saved map.Web UI (live map + runtime map ops)
A dependency-light operator page (stdlib
http.server+ Pillow) is enabled on port8091by default; set deployment configwebui_port: 0to disable it. It binds127.0.0.1by default because the map controls are unauthenticated. An authenticated overlay deployment may explicitly setwebui_host(orMAPPING_WEBUI_HOST); otherwise use the local browser or an SSH tunnel.It runs inside the mapping bridge process, so its buttons call the same
map_opsimpls the gRPC/MCP capabilities use — no extra round trip — and it reads the live/map+ pose straight off the bridge’s rclpy node.map_id(writesrtabmap.db+occupancy.png/pgm/yaml+meta.yaml)./initialposeso rtabmap re-localizes; the activity log records the seeded pose and, a few seconds later, where it converged + the distance from your estimate.These are the same operations exposed as runtime RPC + MCP capabilities (so Pilot can drive them too):
save_map,load_map,pose_estimate,switch_mode(the webui addsreset+deleteon top). All work on the running rtabmap without a redeploy —load/switch_modecall rtabmap’s runtime services and fall back to a restart with the config’smap_mode/map_idwhen those services aren’t reachable.Layout
Troubleshooting
/mapnever populates — a provider binding is missing or points to the wrong provider. Check the[start_engine] rtabmap scan2d=… scan3d=…line.map_mode=localizationerrors “no saved map” — run amappingsession with thatmap_idfirst, and confirmMAPPING_MAPS_DIRis the same path (mounted) across runs.mappingmode (origin = boot pose). Uselocalizationto re-anchor to the saved map.License: MulanPSL-2.0