Reference ROS 2 provider (Rust / r2r) for the husarion_asset_msgs package:// asset standard. Serves a component's meshes/URDF resources over a typed GetAsset service and announces which packages it owns on AssetProviderInfo. Two bins: asset_server (the node) + asset_conformance (an untyped GetAsset client = the provider conformance suite).
The repo is one ament_cargo package (package.xml at the root), mirroring husarion_rosbridge's pattern:
- The cargo package name MUST equal the
package.xml<name>(husarion_asset_server, no dashes) — ament_cargo installs tolib/<cargo-name>/andros2 run <pkg> <exe>resolveslib/<ament-name>/<exe>. The binaries keep their names (asset_server,asset_conformance), so the rosbot snap's fetch-by-version is unaffected by the crate rename. --ros-argshandling (src/ros_args.rs, pure, unit-tested): the launch block is stripped before clap parses, and the__node/__nsremaps are mirrored into the r2r node identity (launchname=/namespace=win over--node-name/--namespace/ env).- Every knob is a ROS 2 parameter (
src/params.rsKNOBSregistry:owned_packages,description_topic,providers_topic,heartbeat,max_chunk; param name = env var minusASSET_SERVER_, lowercased). Precedence flag > param > env > default via the env-overlay trick: startup overrides are read offnode.paramsand written into their env vars before the second clap parse. The node serves the standard param services (make_parameter_handler, spawned on the sameLocalPoolas the handlers) and publishes resolved values back, soros2 param list/get/describeare accurate. Config is startup-only — a runtimeros2 param setlogs "restart to apply". Examples inexamples/(launch YAML + params YAML).
r2r needs a sourced ROS 2 Jazzy env + libclang + the colcon-built husarion_asset_msgs contract — there is no host-cargo path (the dev host has no ROS, no cargo). Everything builds in Docker:
just check— the full gate in a cachedhas-builder:jazzytoolchain container: fmt, clippy-D warnings, tests, the ament_cargo colcon build, andros2 pkgresolution. Needs../husarion_asset_msgs.--platform linux/amd64is forced (this host caches arm64 ros images; QEMU would take hours).just image— the provider image (husarion/asset-server:dev).
INVARIANT (build/runtime symmetry): r2r binds and links the typesupport of every rosidl package on AMENT_PREFIX_PATH at build time, so the build and runtime environments must expose the same rosidl package set. Both Dockerfile stages and the check gate therefore use ros:jazzy-ros-core (building on ros-base links rosbag2_interfaces + tf2_msgs — the measured base-vs-core delta — and the binary then fails to load on a core runtime). ci.yaml runs in ros:jazzy-ros-core for the same reason. Exception: release.yml builds the standalone binaries in ros-base deliberately — the rosbot snap's ros2-extension runtime provides that superset.
Dockerfile: build stage (ros-core + tooling) colcon-builds the ament package; runtime stage = ros-core + all three RMWs (cyclonedds / fastrtps / zenoh) + the msgs install + the ament prefix (entrypoint sources it, so ros2 run / ros2 launch work inside the container). No robot descriptions baked in — the deploy layers the driver's share/<desc-pkg> on top and extends AMENT_PREFIX_PATH (see husarion-cockpit deploy/husarion-ugv/asset-server/). One image serves any robot, independent of driver version. Published multi-arch by .github/workflows/image.yml (native runners, digest → manifest) as husarion/asset-server:X.Y.Z + :latest.
- Ownership —
--owned-packages a,b(explicit) OR auto-derived from a co-located latchedrobot_description(parse itspackage://URIs). Run one per published robot_description, co-located with the description publisher so its packages are onAMENT_PREFIX_PATH. - Security (don't weaken):
package://only · no..traversal · owned-set only · resolved realpath confined to the package share dir. - The service is
{node_fqn}/get_asset(namespaced via launchnamespace=/-r __ns:=/ROS_NAMESPACE); the announce is/asset_providers(latched). The bridge reads the real provider name from the announce, not a guess. - Identity must be unique per provider — a duplicate FQN collides the service and merges the announces. The main loop counts announce-topic publishers sharing the node FQN (
get_publishers_info_by_topic; r2r 0.9.5 has noget_node_names) once per heartbeat and warns loudly while a duplicate exists.
just release (gated on just check) bumps Cargo.toml + Cargo.lock + package.xml + CHANGELOG in lockstep (.release/apply-release.py; the package.xml <version> drifting is the bug husarion_rosbridge hit at 0.8.1), folds an ## [Unreleased] CHANGELOG section into the release section automatically, tags vX.Y.Z, pushes. The tag triggers two workflows:
release.yml— prebuiltasset_serverbinaries (amd64 + arm64, native runners, ros-base) on the GitHub Release; the rosbot snap fetches these (no in-snap compile; no RPATH — the snap'sLD_LIBRARY_PATHresolves rcl/rmw and the typesupport). Cut a release before rebuilding the snap.image.yml— the universal provider image to Docker Hub (DOCKERHUB_USERNAME/DOCKERHUB_TOKENsecrets; also manually runnable via workflow_dispatch with an explicit version).
Commit as the operator with no Co-Authored-By Claude trailer; never push. just check is the local gate (Docker); CI re-runs the same gate per push, and the image build on tag is the final arbiter. Markdown is mdformat-gated (.mdformat.toml, wrap = "no" — single-line paragraphs, no hard wraps; pre-commit install to enable), same style as husarion-cockpit.