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"}})

# Platform side
session = await ws.connect_to_adapter(url, platform_id="brain", on_event=print, on_health=print)
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. rosp check tests event-kind streams, payloads against their definitions, and the health push.

Security: mutual TLS, roles, audit (spec 8)

Every ROSP connection uses 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 gives each platform a role from an access policy. Set it up with the CLI (pip install 'rosp-sdk[cli,certs,ws]'):

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 import ws
from rosp_sdk.security import AccessPolicy, JsonlAuditLog, TLSConfig

# Robot side (here dialing out to the platform; serve_adapter(..., ssl=tls.server()) to listen instead)
tls = TLSConfig.from_dir("certs", "robot-acme-amr-007")
await ws.run_reverse(adapter, "wss://platform.example:8791/rosp/adapter", ssl=tls.client(),
                     access=AccessPolicy.load("certs/access.json"), audit=JsonlAuditLog("audit.jsonl"))

# Platform side
tls = TLSConfig.from_dir("certs", "platform-my-fleet-manager")
await ws.serve_platform(on_session, port=8791, ssl=tls.server(), platform_id="my-fleet-manager")

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 dropped by the platform. Plain ws:// needs insecure=True and logs a warning — local development only.

Check your adapter: rosp check

Point the conformance kit at any running adapter. It acts as a platform and uses only what the robot declares (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.

rosp check wss://robot:8765/rosp --certs certs --as rosp-check        # the robot listens
rosp check --serve 8792 --certs certs --as rosp-check                 # the robot dials in
rosp check ... --motion --watchdog       # also command it — it WILL move a few metres

--motion adds goto lifecycle, cancel, preemption (aircraft take off to 2 m first and land after); --watchdog silences the platform to check the robot ends the command with 5005 and applies its link-loss behaviour. The platform you run it as (--as) needs a role on the robot (access policy). Exit code 0 means conformant; --json for CI.

Spec

License

Apache 2.0

Metadata

Release files for rosp-sdk 0.3.0

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.0
File Size Uploaded
rosp_sdk-0.3.0.tar.gz 83.2 kB Details

Built distribution (wheel)

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

Total release size: 173.4 kB

Release files / rosp_sdk-0.3.0.tar.gz

Download URL rosp_sdk-0.3.0.tar.gz
Size 83.2 kB
Tags Source
SHA-256 checksum
How to use checksums
8da8601e4c28c61316f556c14682d9c07b885610fe204ac28aeae77954e10a18
BLAKE2b-256 checksum
How to use checksums
9058dfe7782b32a5e9851dcc635f3b2413d9943ef0b1af866b3b1c2323cda8eb
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.0-py3-none-any.whl

Download URL rosp_sdk-0.3.0-py3-none-any.whl
Size 90.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e8b0481fb3cb5a4c8ebda87e198e2ef9fe3756421b9eddeeb5ca155510d710b8
BLAKE2b-256 checksum
How to use checksums
d6811f0c0687259b6ca96a70fbac84efb0127cb2136cdb28f9459ec60c46292d
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

0.3.1

2 release files

This release

0.3.0 This release

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