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 Librux 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.
librux::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 librux launch run or lbx deploy up, the
package launcher also sets LIBRUX_SUBSYSTEM_MANIFEST and LIBRUX_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 librux.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 librux::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.
lbx subsystem pause <subsystem>
lbx subsystem resume <subsystem>
lbx subsystem reset <subsystem>
lbx 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 uses the SDK
default heuristic. Use this for Event streaming/telemetry workloads where short
bursts should 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 SDK argument is still named topic for wire compatibility. In user-facing
terms, 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.
librux::wrapper::ControlTransactOptions options;
options.request_type = "msg.core.common.primitive.v1/Int32Value";
options.response_type = "msg.core.common.primitive.v1/Int32Value";
options.timing = librux::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.
librux::wrapper::ProcedureCallOptions options;
options.request_type = "msg.core.common.primitive.v1/IndexedBoolValue";
options.response_type = "msg.core.common.primitive.v1/BoolValue";
options.timing = librux::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.
librux::wrapper::OperationStartOptions options;
options.request_type = "msg.motion.articulated.v1/JointWayPoint";
options.timing = librux::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.
Managed Resource Handles
Packages launched through Librux receive LIBRUX_RESOURCE_LEASE_ID and can ask
librux-resourced for approved file descriptors. Hardware-facing handles use
the normal broker 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 librux.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.