An open ROS 2 interface for serving package:// assets — meshes, textures, URDF resources — over the ROS graph. This README is the specification: implement these two interfaces and the announce/routing rules below, and any conforming client (or the husarion_rosbridge router) can fetch your robot's assets with no baked-in mesh bundles.
It exists because the common web path bakes every mesh into the bridge image. Instead, meshes stay where the robot/sensor is defined and cross the container/snap boundary only as ordinary DDS service payloads — the same ROS graph the topics already use.
# Request
string uri # e.g. "package://rosbot_description/meshes/rosbot_xl/body.dae"
uint64 offset # byte offset to start at (0 = from the beginning)
uint32 length # max bytes to return (0 = as many as fit in one response)
---
# Response
bool success
string error # human-readable; empty on success
string media_type # optional, e.g. "model/stl", "model/vnd.collada+xml"
uint64 total_size # full asset size in bytes — lets the client drive chunking
string content_hash # "sha256:..." of the FULL asset; stable across chunks (ETag/caching)
uint8[] data # the requested byte range (empty on failure)
Chunking is part of the contract. DDS service responses have RMW-dependent size limits and meshes run to hundreds of KB, so the client requests [offset, offset+length) ranges sized under the RMW limit and stitches them. total_size + content_hash describe the whole asset, so the client knows when it is done and can cache across sessions. Small assets degenerate to a single offset:0, length:0 call.
string node_name # for diagnostics
string service_name # the GetAsset service this provider hosts (unique)
string[] packages # package names this provider can resolve
builtin_interfaces/Time stamp # re-stamped on every heartbeat re-announce
float32 heartbeat_period_sec # provider's re-announce period
- Each provider hosts a uniquely-named
GetAssetservice (two servers on one name is illegal in ROS 2). - Each provider publishes
AssetProviderInfoon a well-known topic — default/asset_providers— TRANSIENT_LOCAL + keep-last, so a late-started router and late providers converge on the current set. - Liveness via heartbeat: providers re-announce periodically (re-stamping
stamp); a router drops a provider after ~2×heartbeat_period_secwith no refresh, so a crashed provider's packages stop resolving rather than lingering. (Heartbeat, not DDS liveliness, which varies by RMW.) - On a request for
package://PKG/…, the router looks upPKG, calls that provider's service (chunking as needed), and caches bytes bycontent_hash— invalidating when a provider re-announces a changed asset. - Transitive references (a
.daereferencing textures/sub-meshes) are resolved by the client, oneget_assetper URI. Providers stay bytes-only — no file-format parsing. - Overlap: if two providers announce the same package, the router dedups and picks one deterministically (e.g. first-registered).
- Unknown package / not found: the provider returns
success:false; the router relays a clean failure and the renderer applies its own fallback.
A conforming provider MUST:
- accept only
package://URIs; reject absolute paths and any..traversal; - serve only packages in its announced owned set;
- resolve
package://PKG/RELvia the ament index /resource_retrieveragainst its ownAMENT_PREFIX_PATH.
The recommended owned set is auto-derived: subscribe to the co-located robot_description, scrape every package://PKG/… out of the URDF, and announce exactly those PKGs. That makes overlap structurally impossible and needs no per-deployment config. See the reference husarion_asset_server.
No primitive/geometry/box semantics. The contract resolves URIs → bytes, full stop. Geometry fallback (e.g. a <collision> primitive when a mesh is missing) is URDF + renderer territory. Keeping the standard pure is deliberate.
A conformance suite ships with the reference server (husarion_asset_server, the asset_conformance binary) so third-party providers can self-validate against this standard:
asset_conformance --service /your_provider/get_asset \
--uri package://your_pkg/some/asset.daeIt checks full + chunked fetch, content_hash stability/determinism, and the security rules (unknown-package + .. traversal rejection), exiting non-zero on any failure.
colcon build --packages-select husarion_asset_msgsApache-2.0.