Subsystem SDK API Guide
The subsystem SDK API is the subsystem-author API for Python and C++.
Use this page when writing code that subclasses or wraps a R2Kernel subsystem. Do not use this API from browser clients or outside-runtime tools; those clients should use the Host Control REST API or Host Control WebSocket API.
This page explains the subsystem-author flow. For the table-oriented public class and method catalog, see Subsystem SDK Reference.
Scope
The SDK API owns the following.
- subsystem construction and lifecycle hooks
- Event publish/subscribe registration and publishing
- Control endpoint registration and timed exchange calls
- Procedure registration/calls
- Operation registration/start/cancel/query
- typed payload refs and endpoint timing policy
API Contract, component, and message definitions live under Spec. This page shows how subsystem code uses those contracts.
Construction
Use the role-specific subclass that matches subsystem.role in
subsystem.yaml.
| Role | Python/C++ subclass |
|---|---|
gateway |
GatewaySubsystem |
component |
ComponentSubsystem |
compound |
CompoundSubsystem |
app |
AppSubsystem |
Subsystem remains the common base class. In Python, the role-specific classes
check the loaded manifest role during construction.
Python example.
ComponentSubsystem(
name="controller",
log_level="INFO",
user_data_path="./userdata",
manifest_path="./subsystem.yaml",
heartbeat_port=40000,
)
C++ example.
r2kernel::wrapper::SubsystemOptions options;
options.name = "controller";
options.log_level = "INFO";
options.user_data_path = "./userdata";
options.manifest_path = "./subsystem.yaml";
options.heartbeat_port = 40000;
MyComponentSubsystem subsystem(std::move(options));
When a subsystem is started with r2kernel launch run or r2kernel deploy up, the
package launcher also sets R2KERNEL_SUBSYSTEM_MANIFEST and R2KERNEL_BINDINGS.
That lets C++ subsystems report the same manifest, interfaces, and
bindings status shape as Python subsystems.
Lifecycle
A subsystem implements required startup hooks and may implement runtime lifecycle hooks. Runtime lifecycle commands are requests; user code does not set the subsystem state directly.
Python example.
from r2kernel.wrapper import ComponentSubsystem
class MySubsystem(ComponentSubsystem):
def on_initialize(self) -> bool:
return True
def on_start(self) -> bool:
return True
def on_terminate(self) -> bool:
return True
def on_reset(self) -> bool:
return True
def on_pause(self) -> bool:
return True
def on_resume(self) -> bool:
return True
def on_fault(self, reason: str | None = None) -> bool:
return True
C++ example.
class MySubsystem final : public r2kernel::wrapper::ComponentSubsystem {
protected:
bool on_initialize() override { return true; }
bool on_start() override { return true; }
bool on_terminate() override { return true; }
bool on_reset() override { return true; }
bool on_pause() override { return true; }
bool on_resume() override { return true; }
bool on_fault(const std::string& reason) override {
(void)reason;
return true;
}
};
Canonical subsystem lifecycle states are documented in Subsystem Lifecycle. The common operator commands are these.
r2kernel subsystem pause <subsystem>
r2kernel subsystem resume <subsystem>
r2kernel subsystem reset <subsystem>
r2kernel subsystem fault <subsystem> --reason "fault text"
Event API
For Event methods, topic means the Event channel key.
Python methods.
register_event_publish(...)register_event_subscribe(...)publish_event(...)start_event_transport()stop_event_transport()
C++ methods use the same conceptual operations with EventPublishSpec, EventSubscribeSpec, and EventPayload.
register_event_publish accepts an optional Event queue_depth in Python, and
EventPublishSpec::queue_depth in C++. A value of 0 or omission selects the
release default. Set it explicitly for reproducible Event streaming or telemetry
workloads where short bursts must be absorbed before samples are dropped.
For the method catalog, see Subsystem SDK Reference.
Control API
Control can be raw, typed, or typed/timed. Use the control method names in
subsystem code. Low-level wire protocol details are reserved for internal
protocol documentation.
The public SDK argument is named topic. For Control calls, treat this value as
the Control endpoint key, not as an Event channel.
Python example.
target = self.binding_target("gateway")
response = self.transact_control(
target=target,
topic="timed.request",
type="bytes",
value={"value": 1},
request_type="msg.core.common.primitive.v1/Int32Value",
response_type="msg.core.common.primitive.v1/Int32Value",
timing={"execute_after_ns": 500_000},
period_ms=0.0,
deadline_ms=1000.0,
)
For the method catalog, see Subsystem SDK Reference.
C++ example.
r2kernel::wrapper::ControlTransactOptions options;
options.request_type = "msg.core.common.primitive.v1/Int32Value";
options.response_type = "msg.core.common.primitive.v1/Int32Value";
options.timing = r2kernel::wrapper::ControlTimingOptions{};
options.timing->execute_after_ns = 500'000;
const auto response = transact_control(
"gateway",
"timed.request",
"{\"value\":1}",
options
);
Procedure API
Basic procedure calls remain available. Typed/timed calls add request and response message refs.
Python example.
response = self.call_bound_procedure(
binding="io_gateway",
procedure="set_output",
params={"index": 1, "value": True},
request_type="msg.core.common.primitive.v1/IndexedBoolValue",
response_type="msg.core.common.primitive.v1/BoolValue",
timing={"execute_after_ns": 500_000},
)
C++ example.
r2kernel::wrapper::ProcedureCallOptions options;
options.request_type = "msg.core.common.primitive.v1/IndexedBoolValue";
options.response_type = "msg.core.common.primitive.v1/BoolValue";
options.timing = r2kernel::wrapper::ProcedureTimingOptions{};
options.timing->execute_after_ns = 500'000;
auto response = call_procedure(
"io_gateway",
"set_output",
"{\"index\":1,\"value\":true}",
options
);
For the method catalog, see Subsystem SDK Reference.
Operation API
Operation uses the control exchange path for start/cancel/query and Event for state/feedback/result.
Python example.
operation = self.start_bound_operation(
binding="arm",
operation="move_j",
params={"positions": [0.0, 0.1, 0.2]},
request_type="msg.motion.articulated.v1/JointWayPoint",
timing={"execute_after_ns": 1_000_000},
)
C++ example.
r2kernel::wrapper::OperationStartOptions options;
options.request_type = "msg.motion.articulated.v1/JointWayPoint";
options.timing = r2kernel::wrapper::OperationTimingOptions{};
options.timing->execute_after_ns = 1'000'000;
auto response = start_operation("arm", "move_j", "{...}", options);
For the method catalog, see Subsystem SDK Reference.
Endpoint Timing Policy
Endpoint registration accepts the following.
timing_support = "none"timing_support = "optional"timing_support = "required"
This applies to Control endpoints, Procedures, and Operations.
For the option fields used by Python and C++, see Subsystem SDK Reference.
Spatial Frame API
Spatial ports are declared in subsystem.yaml. SDK methods report the dynamic
relationships owned by the implementation; they do not create Event endpoints.
Python.
self.set_frame_transform(
parent_frame="odom",
child_frame="base_link",
translation=(x, y, z),
rotation_xyzw=(qx, qy, qz, qw),
observed_at_sync_ns=self.now_ns(),
stale_after_ms=500,
)
result = self.lookup_frame_transform(
"component.mobile/odom",
"component.arm/tool0",
)
Use clear_frame_transform(child_frame) when this subsystem no longer owns a
valid edge. frame_transforms() returns the current local snapshot.
C++ uses FrameTransform, Subsystem::set_frame_transform(),
Subsystem::clear_frame_transform(), and Subsystem::frame_transforms(). Host
code can perform lookup through r2kernel frame lookup or Host Control REST.
Dynamic updates use a lightweight control-backend command. Heartbeats carry the
complete snapshot for recovery. stale_after_ms is required for dynamic edges;
static edges use is_static=true and do not expire.
See Spatial Model And Frame Graph.
Managed Resource Handles
Packages launched through R2Kernel receive R2KERNEL_RESOURCE_LEASE_ID and can ask
the host resource service for approved handles. Hardware-facing code uses the
public helpers such as open_udp_socket, open_tcp_socket,
open_can_socket, and open_device.
Managed package frontend backends use a narrower helper. If package.yaml declares
frontend.context.api or frontend.context.ws, launch resolves that context
into a loopback listener grant. The package process opens that listener through
the broker.
from r2kernel.core import ResourceBrokerClient
api_listener = ResourceBrokerClient().open_frontend_tcp_listener(kind="api").socket
ws_listener = ResourceBrokerClient().open_frontend_tcp_listener(kind="ws").socket
The browser still connects to the control backend same-origin proxy. The package backend serves the accepted listener socket; it must not bind a separate frontend port directly under brokered network sandboxing.
For resource helper methods, see Subsystem SDK Reference.