Robot Namespaces¶
OLO uses robot namespaces to support both single-robot and multi-robot
deployments. A namespace identifies the robot that a handle should
operate on. In ROS terms, it is the prefix before robot topics and nodes, for
example /{namespace}/joint_states.
Most robot programs should start from Client.robot().
It resolves the namespace once and exposes the service-specific handles for that
robot.
from olo import Client
from olo.core import TopicInfo
with Client() as client:
# Resolve the default robot namespace when unambiguous.
robot = client.robot()
print(robot.namespace or "<global>")
joint_states = TopicInfo("joint_states", "sensor_msgs/msg/JointState")
print(robot.core.get_latest(joint_states))
# Handles always exist, but operations require runtime dependencies.
# Kinematics pose requires MoveIt; navigation pose requires robot TF.
print(robot.kinematics.pose())
print(robot.navigation.current_pose())
import { connect } from "olo/web";
import type { TopicInfo } from "olo/core";
const client = connect();
// Resolve the default robot namespace when unambiguous.
const robot = await client.robot();
console.log(robot.namespace || "<global>");
const jointStates: TopicInfo = {
name: "joint_states",
msgType: "sensor_msgs/msg/JointState",
};
console.log(await robot.core.getLatest(jointStates));
// Handles always exist, but operations require runtime dependencies.
// Kinematics pose requires MoveIt; navigation pose requires robot TF.
console.log(await robot.kinematics.pose());
console.log(await robot.navigation.currentPose());
Capability handles¶
robot.frames, robot.kinematics, robot.locomotion, and
robot.navigation are always present on the robot handle. Creating a robot
does not contact MoveIt, Nav2, or the locomotion controller; each operation
validates its own runtime dependencies. Locomotion validates its conventional
odometry and command topics when a command starts. Model-dependent kinematics
accessors fetch the robot model lazily on first use (and retry after a failed
fetch). Call robot.kinematics.model / await robot.kinematics.getModel()
or query robot.navigation.state() / await robot.navigation.state() and
inspect state.ready at startup if you want a fail-fast readiness check before
commanding motion. Use client.spatial, client.kinematics,
client.locomotion, or client.navigation when you need the low-level
service wrappers with an explicit namespace per call.
Default namespace resolution¶
When constructing a namespace-scoped handle without passing an argument, the SDK attempts to resolve the default namespace. If the namespace is ambiguous, the SDK raises an error rather than guessing. This keeps the common single-robot path short while making multi-robot code choose explicitly.
The same rules apply to client.platform.navigation.start /
stop / deployment when namespace is omitted (via
client.platform, which shares the appliance channel’s resolver).
Discovery result |
Default behavior |
|---|---|
Exactly one namespace |
Uses that namespace |
No explicit namespaces, but the appliance reports a global robot |
Uses the global namespace, represented by |
No namespace and no global robot |
Raises |
More than one namespace |
Raises |
Explicit namespace selection¶
Pass a namespace when the program targets a specific robot.
with Client() as client:
left = client.robot("left_arm")
right = client.robot("right_arm")
print(left.namespace)
print(right.namespace)
import { connect } from "olo/web";
const client = connect();
const left = await client.robot("left_arm");
const right = await client.robot("right_arm");
console.log(left.namespace);
console.log(right.namespace);
The empty string "" and "global" both mean the global namespace. Use
them only when the appliance intentionally exposes an un-namespaced robot.
Service-specific handles¶
Use client.<service>.handle(...) when a program needs only one service
surface or needs a low-level, explicit-namespace interface for fleet and
diagnostic use. Each handle follows the same namespace resolution rules as
client.robot(...).
with Client() as client:
core = client.core.handle("fr3")
frames = client.spatial.handle("fr3")
arm = client.kinematics.handle("fr3")
import { connect } from "olo/web";
const client = connect();
const core = await client.core.handle("fr3");
const frames = await client.spatial.handle("fr3");
const arm = await client.kinematics.handle("fr3");
Core topic and node names¶
olo.core.CoreHandle topic operations take a
olo.core.TopicInfo and resolve names using ROS conventions:
No leading
/: relative to the robot namespace, for examplestatuson afr3handle becomes/fr3/status.Leading
/: absolute and used as-is, for example/fleet/statusstays/fleet/status.
Use olo.core.CoreHandle.topic() when you need the resolved absolute form
for logging or comparison. Node names are prefixed without a leading slash,
matching ROS parameter service names such as fr3/controller_manager.
with Client() as client:
robot = client.robot("fr3")
status = TopicInfo("status", "std_msgs/msg/String")
fleet_status = TopicInfo("/fleet/status", "std_msgs/msg/String")
assert robot.core.topic(status.name, status.msg_type).name == "/fr3/status"
assert robot.core.topic(fleet_status.name, fleet_status.msg_type).name == "/fleet/status"
robot.core.publish(status, {"data": "local"})
robot.core.publish(fleet_status, {"data": "fleet-wide"})
params = robot.core.get_params("controller_manager", ["update_rate"])
import { connect } from "olo/web";
import type { TopicInfo } from "olo/core";
const client = connect();
const robot = await client.robot("fr3");
const status: TopicInfo = { name: "status", msgType: "std_msgs/msg/String" };
const fleetStatus: TopicInfo = { name: "/fleet/status", msgType: "std_msgs/msg/String" };
console.assert(robot.core.topic(status.name, status.msgType).name === "/fr3/status");
console.assert(robot.core.topic(fleetStatus.name, fleetStatus.msgType).name === "/fleet/status");
await robot.core.publish(status, { data: "local" });
await robot.core.publish(fleetStatus, { data: "fleet-wide" });
const params = await robot.core.getParams("controller_manager", ["update_rate"]);
Robot TF frames¶
olo.spatial.RobotFrames provides namespace-scoped TF access. Bare frame
names such as base_link are resolved server-side to
{namespace}/base_link; global frames such as world, map, and
odom are never prefixed. client.spatial.handle(...) and
robot.frames always return a scoped subtree, not the full TF tree. Use
olo.spatial.Spatial.tree() for the complete tree across all robots.
with Client() as client:
robot = client.robot("fr3")
base = robot.frames.base_frame()
world_to_base = robot.frames.lookup("world", "base_link")
print(robot.frames.tree())
print(client.spatial.tree()) # Full tree across all robots.
import { connect } from "olo/web";
const client = connect();
const robot = await client.robot("fr3");
const base = await robot.frames.baseFrame();
const worldTBase = await robot.frames.lookup("world", "base_link");
console.log(await robot.frames.tree());
console.log(await client.spatial.tree()); // Full tree across all robots.
Use olo.spatial.Spatial.lookup() directly when you need exact frame ids
without namespace resolution.
Low-level calls¶
Low-level service methods keep their wire-level defaults and do not resolve a default robot namespace. Use them when writing fleet tools that need to pass namespaces around directly or preserve exact wire-level behavior.
with Client() as client:
topics = client.core.list_topics()
model = client.kinematics.get_model(robot_namespace="fr3")
import { connect } from "olo/web";
const client = connect();
const topics = await client.core.listTopics();
const model = await client.kinematics.getModel("fr3");
Listing namespaces¶
Use olo.core.Core.list_robot_namespaces() when a program wants to present
a choice to a user or make its own policy decision. The returned
olo.core.RobotNamespaces also exposes resolve_default() for code
that wants the same resolve-or-raise policy used by the robot handle and service handle
factories.
with Client() as client:
available = client.core.list_robot_namespaces()
for namespace in available.namespaces:
print(namespace)
if available.has_global:
print("<global>")
import { connect } from "olo/web";
const client = connect();
const available = await client.core.listRobotNamespaces();
for (const namespace of available.namespaces) {
console.log(namespace);
}
if (available.hasGlobal) {
console.log("<global>");
}