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 :meth:`Client.robot() `. It resolves the namespace once and exposes the service-specific handles for that robot. .. tab-set:: :sync-group: sdk-language .. tab-item:: Python :sync: python .. code-block:: python 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 "") 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()) .. tab-item:: TypeScript :sync: typescript .. code-block:: typescript 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 || ""); 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). .. list-table:: :header-rows: 1 * - 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 :class:`olo.OloNotFound` * - More than one namespace - Raises :class:`olo.OloAmbiguousNamespace` Explicit namespace selection ---------------------------- Pass a namespace when the program targets a specific robot. .. tab-set:: :sync-group: sdk-language .. tab-item:: Python :sync: python .. code-block:: python with Client() as client: left = client.robot("left_arm") right = client.robot("right_arm") print(left.namespace) print(right.namespace) .. tab-item:: TypeScript :sync: typescript .. code-block:: typescript 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..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(...)``. .. tab-set:: :sync-group: sdk-language .. tab-item:: Python :sync: python .. code-block:: python with Client() as client: core = client.core.handle("fr3") frames = client.spatial.handle("fr3") arm = client.kinematics.handle("fr3") .. tab-item:: TypeScript :sync: typescript .. code-block:: typescript 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 ------------------------- :class:`olo.core.CoreHandle` topic operations take a :class:`olo.core.TopicInfo` and resolve names using ROS conventions: - No leading ``/``: relative to the robot namespace, for example ``status`` on a ``fr3`` handle becomes ``/fr3/status``. - Leading ``/``: absolute and used as-is, for example ``/fleet/status`` stays ``/fleet/status``. Use :meth:`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``. .. tab-set:: :sync-group: sdk-language .. tab-item:: Python :sync: python .. code-block:: python 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"]) .. tab-item:: TypeScript :sync: typescript .. code-block:: typescript 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 --------------- :class:`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 :meth:`olo.spatial.Spatial.tree` for the complete tree across all robots. .. tab-set:: :sync-group: sdk-language .. tab-item:: Python :sync: python .. code-block:: python 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. .. tab-item:: TypeScript :sync: typescript .. code-block:: typescript 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 :meth:`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. .. tab-set:: :sync-group: sdk-language .. tab-item:: Python :sync: python .. code-block:: python with Client() as client: topics = client.core.list_topics() model = client.kinematics.get_model(robot_namespace="fr3") .. tab-item:: TypeScript :sync: typescript .. code-block:: typescript import { connect } from "olo/web"; const client = connect(); const topics = await client.core.listTopics(); const model = await client.kinematics.getModel("fr3"); Listing namespaces ------------------ Use :meth:`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 :class:`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. .. tab-set:: :sync-group: sdk-language .. tab-item:: Python :sync: python .. code-block:: python with Client() as client: available = client.core.list_robot_namespaces() for namespace in available.namespaces: print(namespace) if available.has_global: print("") .. tab-item:: TypeScript :sync: typescript .. code-block:: typescript 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(""); }