Skip to main content

Write a Service Application

A service application uses a client SDK to talk to the gateway. This guide shows the JavaScript SDK, @openrois/sdk, for browsers and Node.js, and the Python SDK, openrois-sdk, for asyncio. The Unity SDK has the same methods in Pascal case with an Async suffix, listed in the SDK reference. See installation to add an SDK to a project, and the quick start for a gateway to test against.

The Inspector shows the SDK calls for anything you do in it, which is a quick way to find the calls an application needs.

Connect​

import { RoISClient, componentRef, componentType } from "@openrois/sdk";

const client = await RoISClient.connect("ws://localhost:8765");

connect opens the WebSocket and performs rois.system.connect. Call client.disconnect() when you are done, which also releases what you bound. In Python, async with RoISClient.connect(url) as client: disconnects at the end of the block.

One client is one session. Bindings and subscriptions end with it, and the SDKs do not reconnect on their own: after the connection closes, connect again, then bind and subscribe again.

Discover Components​

Component references have the form engine_id/name, for example mock/navigation. Do not hardcode them. Find them with a condition instead:

// Every component of a type, across every connected robot.
const [nav] = await client.search(componentType({ authority: "OMG", code: "Navigation" }));
const target = componentRef(nav);

// The engine profile, and the profile of every component it lists.
const { profile, component_profiles } = await client.getProfile();

Building your interface from the profile is what lets one application work with any platform. A component that does not list suspend among its commands should not get a suspend button.

The gateway notifies you when components join or leave. Listen for rois.system.profile_changed with client.on, and read the profile again.

Query State​

Queries do not require a reservation.

const results = await client.query("component_status", target);

RoIS results are lists of { name, data_type_ref, value } entries, with every value encoded as a string. decodeValue from @openrois/interfaces, and decode_value from openrois.interfaces, read a value by its type.

Subscribe to Events​

client.on("rois.event.notify_event", ({ event_type, results }) => {
console.log(event_type, results);
});

const subscribeId = await client.subscribe("reached_target", target);

// Later:
await client.unsubscribe(subscribeId);

The listener receives every event of every subscription, so filter by subscribe_id or event_type when you hold several.

Command a Component​

Actuation components, such as Navigation, follow the RoIS reservation lifecycle. Bind the component, configure it, execute, and release it when done. While you hold the reservation, other applications receive OUT_OF_RESOURCES if they try to bind it.

execute returns the command ids as soon as the engine accepts the commands. Each command ends later, with a rois.command.completed notification. A command can end before the reply reaches your code, so add the listener first and record each status by id:

const statuses = new Map<string, string>();
const waiters = new Map<string, () => void>();
client.on("rois.command.completed", ({ command_id, status }) => {
statuses.set(command_id, status);
waiters.get(command_id)?.();
});

await client.bind(nav);
await client.setParameter(nav, [
{ name: "target_positions", data_type_ref: "string[]", value: '["kitchen"]' },
]);
const [commandId] = await client.execute([{ component_ref: nav, command_type: "start" }]);

// Wait before release, which aborts a running command.
if (!statuses.has(commandId)) {
await new Promise<void>((resolve) => waiters.set(commandId, resolve));
}
console.log(statuses.get(commandId)); // "OK"

await client.release(nav);

Sensing components, such as PersonDetection, can be started and queried without an exclusive reservation.

Run a Sequence​

One execute can carry several commands. They run in order, and a group with a branch_list runs its branches at the same time, each branch in order. The next step waits for every branch. Here a robot drives to a place, then speaks and reacts together, half a second later:

const commandIds = await client.execute([
{ component_ref: nav, command_type: "start" },
{
branch_list: [
{ command_list: [{ component_ref: speech, command_type: "start" }] },
{ command_list: [{ component_ref: reaction, command_type: "start" }] },
],
delay_time: 500,
},
]);

The engine assigns one id to each command and returns them in order, branch by branch. Every component in the sequence must be bound first. When a command does not end OK, the rest of its branch and every later step end with ABORT and never start. A set_parameter command in the sequence configures a component right before the command that reads the parameter.

Handle Errors​

Operations that return a RoIS return code other than OK raise a RoISError carrying the code and the method. Transport problems raise subclasses of TransportError.

import { RoISError } from "@openrois/sdk";

try {
await client.bind(nav);
} catch (err) {
if (err instanceof RoISError && err.returnCode === "OUT_OF_RESOURCES") {
// Another application holds the reservation.
} else {
throw err;
}
}
ErrorRaised when
RoISErrorThe engine returns ERROR, BAD_PARAMETER, UNSUPPORTED, OUT_OF_RESOURCES, or TIMEOUT
ConnectionError (RoISConnectionError in Python)The WebSocket cannot be opened, or closes while a request waits
RequestTimeoutErrorNo response arrives within the request timeout, 30 seconds by default
RpcErrorThe gateway returns a JSON-RPC error object

A command that fails after it was accepted does not raise: it ends with a status other than OK in rois.command.completed. An engine also reports faults of its components with rois.system.notify_error, and getErrorDetail (get_error_detail) reads the detail.

Security​

The gateway does not authenticate connections yet, so use it on trusted networks only. See deployment.

Other Languages​

Any language with a WebSocket client can use OpenRoIS directly through the wire protocol.