Skip to main content

rosp-sdk

Python SDK for the Robot Open Specification Protocol (ROSP) — base classes, data models, plugin registry, and tools for building robot adapters.

Install

Pick the transports your project actually needs; the base install stays lean.

# Base install — protocol models + ROSPAdapter base class + registry.
# Small, no transport-specific deps.
pip install rosp-sdk

# With one or more transport adapters (see "Transports" below).
pip install rosp-sdk[ros2]        # + rosp-adapter-ros2  (needs system ROS 2)
pip install rosp-sdk[grpc]        # + rosp-adapter-grpc  (Spot, Viam, custom)
pip install rosp-sdk[vda5050]     # + rosp-adapter-vda5050 (MQTT AGVs)
pip install rosp-sdk[all]         # every adapter above

# With CLI tools:
pip install rosp-sdk[cli]

The plugin model

Adapters are ordinary Python packages that register themselves under the rosp.adapters entry-point group. rosp-sdk discovers them at runtime via importlib.metadata — no imports until you ask for one, no heavyweight transitive dependencies unless a transport is installed.

from rosp_sdk import available_adapters, load_adapter

available_adapters()
# {'ros2': 'rosp_adapter_ros2:ROS2Adapter',
#  'grpc': 'rosp_adapter_grpc:GRPCAdapter',
#  'vda5050': 'rosp_adapter_vda5050.rosp_adapter:VDA5050ROSPAdapter'}

Adapter = load_adapter("ros2")           # returns the class
adapter = Adapter(config={"robot_id": "urn:rosp:robot:..."})
async with adapter:
    card = await adapter.describe()
    async for msg in adapter.stream(["/sensor/lidar/scan"]):
        print(msg.data)

If an adapter isn't installed, load_adapter raises ROSPAdapterNotInstalled with the exact pip install command needed to fix it.

Transports

Transport Package Extra System requirement
ROS 2 (DDS) rosp-adapter-ros2 pip install rosp-sdk[ros2] ROS 2 Humble installed + sourced (rclpy comes from ROS, not pip)
gRPC rosp-adapter-grpc pip install rosp-sdk[grpc] none (grpcio is pip-installable)
VDA 5050 rosp-adapter-vda5050 pip install rosp-sdk[vda5050] MQTT broker reachable from the process

Quick Start

Build an Adapter

from rosp_sdk import ROSPAdapter, RobotCard, StreamMessage, StreamQoS

class MyRobotAdapter(ROSPAdapter):
    async def describe(self, depth="full") -> RobotCard:
        return RobotCard(
            rosp_version="0.1",
            id="urn:rosp:robot:mycompany:myrobot:001",
            identity={"manufacturer": "MyCompany", "model": "MyRobot", "type": "mobile_robot"},
            hardware={"weight_kg": 25.0, "payload_kg": 10.0},
            sensors=[{"sensor_id": "lidar", "type": "lidar_2d"}],
            actuators=[{"actuator_id": "drive", "type": "wheel"}],
            capabilities=[{"capability_id": "navigation", "type": "navigation"}],
            integration={"supported_protocols": [{"protocol": "ros2"}]},
            safety={"e_stop_available": True},
        )

    async def stream(self, topics, qos=None):
        # Subscribe to robot sensors and yield StreamMessage objects
        ...

    async def discover(self):
        # Return robot identity for network discovery
        ...

    async def health_check(self):
        # Return adapter and robot health
        ...

    async def connect(self):
        # Connect to robot's native interface
        ...

    async def disconnect(self):
        # Clean shutdown
        ...

Validate a Robot Card

from rosp_sdk import validate_robot_card

errors = validate_robot_card(card_dict)
if not errors:
    print("Valid!")

CLI

rosp validate my-robot.json        # Validate against schema
rosp export-card my-robot.json     # Pretty-print
rosp inspect my-robot.json         # Show sensors, actuators, integration
rosp version                       # Show SDK and spec version

New here? Connect a robot walks you from your robot's API to a conformant ROSP robot in about 30 minutes (tested: our suite follows it literally).

ROSP 0.3: components, schema catalog, events, health

Spec: docs/spec/ROSP-v0.3-stream-contract.md. Every stream names the part it belongs to, carries its payload definition, and parts declare what "unhealthy" means; the robot pushes events and health.

from rosp_sdk import Event, HealthStatus, AdapterStatus, RobotStatus, streams

# In describe(): streams name their component and a catalog schema (29 shipped: pose3d, twist,
# geolocation, battery, esc_report, joint_report, flight_state, imu_status, gps_status, ...).
card = RobotCard(..., actuators=[{"actuator_id": "rotor-4", "type": "propeller", "health": {"indicators": [
            {"id": "rpm_deviation", "field": "rpm", "compare": "siblings_pct",
             "warn": {"op": "gt", "value": 8}, "critical": {"op": "gt", "value": 15}, "for_s": 2}]}}],
       components=[{"component_id": "battery", "type": "battery"}],
       streams=[{"stream": "actuators/rotor-4/esc", "schema": "esc_report", "component_id": "rotor-4", "rate_hz": 2},
                {"stream": "robot/battery", "schema": "battery", "component_id": "battery", "rate_hz": 1}])

# Anywhere in the adapter: report what happened. Platforms that negotiated "event" get it at once.
self.emit(Event("motor.failure", "critical", "ESC 4 failure flags 0x04", component_id="rotor-4", data={"flags": 4}))

# health_check() is called by the session (every 10 s, and sent immediately when it changes).
HealthStatus(AdapterStatus.HEALTHY, RobotStatus.ERROR, latency_ms=12, uptime_seconds=300,
             components={"rotor-4": {"status": "error", "message": "ESC failure flags 0x04"}})

# Consumer side: evaluate a card's health indicators from the streams (no transport needed)
from rosp_sdk.health import HealthEvaluator      # the card's indicators, evaluated from the streams
ev = HealthEvaluator(card)
for change in ev.observe("actuators/rotor-4/esc", {"rpm": 5400}):
    print(change.message())                       # "rotor-4: rpm deviation from siblings 9.2% (ok -> warning)"

rosp_sdk.streams gives consumers the catalog (catalog(), schema_def(), fields()), the parts of a card in one shape (components()), the resolved stream declarations (resolve_card()), stream naming (stream_name()) and payload validation. validate_robot_card() now treats unknown part types as warnings (check_robot_card() returns both errors and warnings) and reports dangling component or indicator references as errors.

Security: identity, roles, audit (spec 8)

ROSP connections use mutual TLS 1.3: each side's certificate carries its identity as a SAN URI — rosp://robot/<robot-id> or rosp://platform/<platform-id> — and the robot grants each platform a role from an access policy. rosp-sdk ships the certificate tooling and the identity/role/audit primitives; the live session and WebSocket transport that drive them are a separate component.

Issue a local CA and certificates with the CLI (pip install 'rosp-sdk[cli,certs]'):

rosp certs init --dir certs                          # local CA: ca.crt (+ ca.key, keep private)
rosp certs issue robot acme-amr-007 --dir certs      # robot-id as derived from the card (spec 4.2)
rosp certs issue platform my-fleet-manager --dir certs
echo '{"platform/my-fleet-manager": "controller", "*": "observer"}' > certs/access.json
from rosp_sdk.security import AccessPolicy, JsonlAuditLog, TLSConfig

tls = TLSConfig.from_dir("certs", "robot-acme-amr-007")   # SSLContext for the transport layer
access = AccessPolicy.load("certs/access.json")           # map a peer's cert identity to a role
audit = JsonlAuditLog("audit.jsonl")                      # append-only record of who did what

A platform the policy doesn't list, or whose platform_id differs from its certificate, is refused (4001); a robot announcing a robot-id other than its certificate's is refused. These primitives are consumed by the ROSP session/transport, which lives outside this package.

Conformance

A conformance harness exercises any running adapter against its Robot Card — handshake and TLS identity, card validity, the robot-id rule, every declared stream (data matches its schema), refusals (unknown topic 2006, unknown action 3003, bad params 2005, expired 5002), and the command lifecycle. Because it needs the live session and transport, it ships separately from this package.

Spec

License

Apache 2.0

Metadata

Release files for rosp-sdk 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rosp-sdk 0.3.1
File Size Uploaded
rosp_sdk-0.3.1.tar.gz 82.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rosp-sdk 0.3.1
File Interpreter ABI Platform
rosp_sdk-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 172.7 kB

Release files / rosp_sdk-0.3.1.tar.gz

Download URL rosp_sdk-0.3.1.tar.gz
Size 82.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7b715d19b534a5f5e229f3f3da6e0cd044df288f60b543aee9614bae5fec0071
BLAKE2b-256 checksum
How to use checksums
3f5ea911e91984384b31e5b38073646f090a4c600be5ca53f38f1e2ad507d69d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Release files / rosp_sdk-0.3.1-py3-none-any.whl

Download URL rosp_sdk-0.3.1-py3-none-any.whl
Size 89.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e6daf58b231da1354a7e742b99b9947d3f9594a7006daa0b8fb589d631ca1d0d
BLAKE2b-256 checksum
How to use checksums
fb46a274c214e26bdf3ab1790cee23ed38433ec9b7cc6839e1979564a2f5d1b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page