Skip to main content
Abstract base class for mobile manipulator robots (mobile base + arm(s)). A mobile manipulator combines a mobile base (wheeled, legged, etc.) with one or more arms and other joint groups. Subclasses populate self.subcomponents during __init__ with a mapping of joint-group name to the controller for that group (e.g. "left_arm" -> arm, "base" -> mobile base). Sensors attached via addSensor share the same subcomponents mapping; the joint accessors below operate on the joint groups only (every subcomponent that is not a sensor or end effector). The base class provides getJointAngles / setJointAngles that dispatch to joint groups by name. When called without a group argument they operate on all joint groups via a dict; when called with a group they operate on a single joint group and use the same list[float] signature as Arm, Humanoid, etc.

Properties

Dict[str, 'Component']
Child components that are arms (derived, read-only).Filtered from subcomponents by Arm role; the returned dict is a fresh snapshot.
Optional['Component']
The attached end effector, or None (derived, read-only).Returns the child named "end_effector" when it is an EndEffector, otherwise the first EndEffector child in insertion order. Attach one via addSubcomponent("end_effector", ...).
ComponentIdentity
Hardware identity this component’s config declared it should have.Set from the identity block of the config envelope by from_config, and empty for a component built any other way. Recording it acquires no hardware, and what it means splits by transport. For network-addressed hardware (an arm at a configured IP) it is a declaration to verify against: whether a mismatch warns or fails belongs to the caller that owns that policy, not to the driver. For bus-enumerated hardware (USB cameras), whose enumeration order is not a stable address, the declared serial_number is instead a selector: bring-up opens exactly that unit and fails loudly when it is absent, never falling back to a different device.Compare it against getIdentity, which is what the hardware reports.
Dict[str, 'Component']
Child components that are sensors (derived, read-only).Filtered from subcomponents by Sensor role; attach sensors via addSensor/addSubcomponent rather than writing to the returned dict, which is a fresh snapshot.
Dict[str, 'Component']
Mapping of child-component name to child component.The returned dict is the live backing store: mutating it mutates the component tree. Prefer addSubcomponent for inserts.

Methods

addSensor()

Signature
Add an external sensor to the robot.
str
required
Unique identifier for the sensor
Sensor
required
Sensor object to add (e.g. Camera, IMU, Lidar)
Returns: None

addSubcomponent()

Signature
Attach a child component under a name. An existing child with the same name is replaced, matching dict assignment semantics (addSensor has always overwritten).
required
Non-empty name for the child, unique within this component’s subcomponents. Names are addressable as getState path chunks, so the path syntax characters / * ? [ ] are not allowed.
required
The child component to attach.
Raises: ValueError: If name is not a non-empty string, or contains a path syntax character.

builtin_subcomponents()

Signature
Declare the children this class always ships with (customization point). Hardware that is part of the component itself — a humanoid’s head camera, an arm’s wrist camera, a base built into a mobile manipulator — is declared here rather than constructed in __init__, so one description drives both the runtime tree and the static config tooling. The declaration is pure data in the same shape an authored subcomponents entry uses: {"<child name>": {"type": "<registry name>", "args": {...}, "subcomponents": {...}, "enabled": True}}, where type names a robot- or sensor-registry key, args are that class’s config args, subcomponents declares grandchildren by the same rules, and enabled (default True) can declare a child off by default. An override must be callable on the bare class, without instantiation and without importing a driver or touching hardware: the setup-time stream resolver reads it off the registry class to resolve a robot’s camera streams on a machine that has none of the hardware attached. The base class materializes the declaration on every construction path — a direct constructor call attaches the children once the constructor returns, and from_config attaches them merged with the authored subcomponents block (authored keys win, so an entry with the same name retunes args or drops the child with enabled: false — and may omit type, inheriting the one declared here — while an entry naming a different type is an error). Attached builtin children are ordinary subcomponents: they appear in serialize, in getState, and in the lifecycle walks, and a builtin Camera child is skipped when the tree is built with enable_cameras=False. Returns: Mapping of child name to its config declaration. The default is empty — a class with no builtin children.

configHash()

Signature
Hash this component’s serialized config subtree. A SHA-256 hex digest over the serialize output — the config topology and args, not object identity and not getState telemetry. Deterministic: the same tree with the same options always produces the same digest, regardless of dict insertion order. Touches no hardware; with include_identity it hashes whatever identity is already recorded (declared or previously read), triggering no new read. Two hashes are comparable only when computed with the same options; equality across differently-parameterized calls is meaningless. Note that with include_identity the digest changes as identity fields become known — a component reports fields monotonically (some only once started), so hash at a consistent point in the lifecycle when comparing across runs.
When given, only tree nodes whose component is an instance of one of these classes are hashed; a non-matching node is pruned together with its entire subtree. None keeps every node.
default:"False"
When True, each kept node’s identity block (as serialize records it — hardware-read overlaid on declared) is included in the hash. When False the hash covers configuration only.
default:"frozenset()"
Config args keys dropped from every kept node before hashing (e.g. an IP address that changes across networks).
default:"False"
When True, prepend this component’s registry type and an underscore to the digest (e.g. "go2_<digest>", "rig_<digest>" for a composite root) for a human-readable, greppable label. The type is already part of the hashed payload, so this only labels the same digest — the suffix after _ is identical to the unprefixed call with the same options.
Returns: A 64-character lowercase SHA-256 hex digest, or "<type>_<digest>" when type_prefix is True. Raises: RuntimeError: If this component was not constructed from a config, so it cannot be serialized. ValueError: If this component itself matches none of include_types — the hash would cover an empty tree and compare equal for unrelated components.

config_schema()

Signature

from_config()

Signature
Construct this component and its config-declared subtree. Recursive walk owned by the base class, the inverse of serialize: the config envelope ({"type": ..., "args": {...}, "subcomponents": {...}}) is validated, this component is constructed from args via _from_config_args, and each declared child is resolved through the robot/sensor registries, constructed by its own class’s from_config, and attached. type may be omitted when calling on a concrete class — it is then taken from the class’s registry entry. The declared children are this class’s builtin_subcomponents overlaid by the config’s subcomponents block: builtin children first in declaration order, then authored-only children. An authored entry whose name matches a builtin one overlays it — its args merge over the builtin args and enabled: false drops the child — rather than colliding with it, and it may omit type entirely ({"chassis_rear": {"enabled": false}} is a complete entry), inheriting the builtin’s; an entry naming a different type than the builtin declaration is an error, as is an entry that omits type while overlaying no builtin child. Any entry with enabled: false is skipped, builtin or authored. A node’s optional identity block is recorded on the built component as expected_identity — the hardware this entry is declared to be. It is never checked against the device here: construction acquires no hardware, and deciding what a mismatch means belongs to the caller. Component configs nested inside args (an arm’s end_effector, for example) are the constructor’s business and are built in _from_config_args; the recursion here covers the subcomponents block only. The returned tree is constructed but not started — use make_robot/ make_sensor, a with block, or an explicit start call to bring it online.
required
Config envelope for this component. Extra keys are rejected; args and subcomponents may be omitted.
default:"True"
When True, camera components declared in the config are constructed and attached for local getImage use. When False, config-declared cameras are skipped at every depth because deploy streaming owns those devices; the flag is also passed to _from_config_args so a driver can skip cameras it builds itself.
Returns: The constructed component with its declared subcomponents attached and its config recorded — including, per builtin child, whether the authored config moved it off its declaration’s enabled state — so type(c).from_config(c.serialize()) rebuilds an equivalent tree. Raises: ValueError: If config is not a mapping, a declared subcomponent type is in neither registry, or an authored entry overlays a builtin child with a different type. ConfigValidationError: If the config tree does not match the envelope shape; the message carries full config-tree paths.

getIdentity()

Signature
Get this component’s own hardware identity (serial, model, version, MAC). Local, not recursive: it answers for this component alone. The tree-wide view is serialize, which carries each node’s identity alongside its topology; identity is deliberately absent from getState, which is live telemetry rather than constants burned into a device. Never raises and never blocks the caller’s real work. A driver read that fails — controller unreachable, dashboard refused, SDK error — is logged and degrades to whatever fields are already known, down to an empty mapping. Absence means unknown, never different: see ComponentIdentity for what consumers may conclude from a missing field. Callable in every lifecycle state — before start(), while started, after stop(), and after shutdown(). Where the transport allows it (the UR dashboard server needs only the controller IP, ZED serials are enumerable from sysfs before the device is opened) a constructed-but-unstarted component already answers, which is what lets a caller de-duplicate a robot before bringing it up. Drivers that can only read identity from a live device handle return nothing until then, so a call after start() may carry more fields than one before it. Monotonic per component. Every field ever read successfully is cached, so repeated calls only ever gain fields — a later failure never drops one that was already known. A field whose value is re-read is updated to the fresh value. The field set is closed. Only the fields ComponentIdentity declares are accepted; anything else a driver reports is logged and dropped, exactly like a non-string value, so what a component reports always round-trips through the config envelope. Returns: A fresh ComponentIdentity mapping holding only the fields known for this component; {} when nothing is known. Mutating the result does not affect the component.

getImage()

Signature
Return the image of camera. Which camera a no-argument call reads is decided by cardinality alone — no robot, config, or class declares a default. A robot with exactly one camera needs no name; a robot with several requires one, so adding a second camera never silently changes which one a bare getImage() returns.
str
default:"''"
Name of the camera to get image from — either a directly attached camera’s name or a /-separated path through subcomponents to a nested one (left_arm/wrist). May be omitted when the robot has exactly one camera.
Forwarded to the underlying sensor’s getImage. Lets callers pass camera-specific options (e.g. image_type="depth", compressed=False) without the base class having to enumerate them.
Returns: Image: The captured image. Raises: RuntimeError: If no cameras are configured, or the name is omitted on a robot with more than one camera; the message lists every configured camera path. KeyError: If camera_name is given but names no camera on this robot.

getJointAngles()

Signature
Get joint angles.
If provided, return joint angles for that subcomponent only (as a flat list[float]). If None (default), return a dict mapping every subcomponent name to its joint angles.
Returns: list[float] when group is given, otherwise dict[str, list[float]]. Raises: KeyError: If group is not a known subcomponent.
Get a point cloud from the named LiDAR sensor.
str
default:"''"
Name of the lidar. If empty, uses the first available lidar.
Returns: Optional[PointCloud]: The point cloud, or None if lidar not found

getOrientation()

Signature
Get the orientation of the robot in the world frame. Returns: Optional[Orientation]: Current orientation of the robot in world coordinates, or None if orientation is unavailable Raises: NotImplementedError: Must be implemented by subclasses

getPosition()

Signature
Get the position of the robot in the world frame, in meters. Returns: Optional[Position]: Current position of the robot in world coordinates, or None if position is unavailable Raises: NotImplementedError: Must be implemented by subclasses

getState()

Signature
Get a live state snapshot of this component’s tree. Recursive walk owned by the base class, following the lifecycle template: each class contributes only the protected local hook _local_state, and the walk composes the per-component results into one nested mapping. Subclasses must not replace this method — per-component state belongs in _local_state. With no keys, returns this component’s local state merged with one nested node per subcomponent name — the tree structure is the nesting itself, and the names mirror subcomponents (and therefore serialize() and edge introspection): a two-arm rig returns {"left_arm": {"joint_positions": ...}, "right_arm": {...}}, and a leaf component returns its local state alone. A component whose state could not be read (dead telemetry, not yet started, already shut down) carries an "error" entry — {"error": "<ExceptionType>: <message>"}, with its children still nested alongside — instead of its state keys. Errors are contained per node: one failing component never loses the rest of the snapshot, and the walk still descends into the failing component’s children. "error" is reserved for that purpose, and local state keys must not collide with subcomponent names; a hook violating either is reported as that node’s error. With keys, returns a flat mapping with one entry per match. Each key is a /-separated path through the component tree: every segment names a subcomponent, and the final segment may instead name an entry in that component’s local state. Because structure is the nesting, a literal path is the same as plain indexing — getState(["left_arm/joint_positions"]) returns the value of getState()["left_arm"]["joint_positions"] — and a path ending on a subcomponent yields that component’s full nested node. Segments may use shell-style wildcards over subcomponent names — * matches within one chunk and ** matches any chain of chunks — so "*_arm/joint_positions" fans out to one entry per arm, keyed by the concrete matched path. Wildcards never match state entries; only a literal final segment does. A key that matches nothing raises, so a typo is loud rather than silently absent. A matched component whose state read failed yields {"error": "..."} at its path. Keyed reads are lazy: a hook runs only where a key lands — a literal path executes the final component’s hook alone (not the nodes traversed on the way), a glob executes only the matched nodes, and each component’s hook runs at most once per call however many keys reach it. Components with a selective-read hook (_local_state_entries — every state_getters robot) go further and execute only the getters a key names, so a ** glob probes their registries without touching hardware. The caller pays only for the state actually requested; ** and the no-keys form visit the whole tree because that is the request. State is cheap by convention: local state carries kinematics, status, and health — never bulk sensor payloads (camera frames, point clouds, scans), which stay in their dedicated methods (getImage, getPointCloud, …). Callable on a stopped tree — reading state after a soft e-stop is when it matters most — and on a partially started one, where unstarted components report a per-node error instead of failing the call.
State paths to read, or None for the full nested snapshot. A single string is shorthand for a one-element list.
Returns: The nested state snapshot when keys is None, otherwise a flat mapping of matched path to state value or nested node. Raises: TypeError: If keys is neither None, a string, nor a list/tuple of strings. ValueError: If a key is empty, has an empty path segment, or matches no subcomponent path or state entry.

moveByVelocity()

Signature
Command base velocity.
required
Linear velocity of the base.
required
Angular velocity of the base.
default:"'body'"
Reference frame for the velocity command.

serialize()

Signature
Serialize this component tree back to its config envelope. The exact inverse of from_config, producing the recursive config schema it consumes: {"type": ..., "args": {...}, "subcomponents": {name: <node>}}, minimized via model_dump(exclude_defaults=True) so empty args/subcomponents are omitted. Only config-declared children (those constructed through from_config) are emitted; parts a driver constructs internally are implied by the parent’s args and reappear on reconstruction. A child a class ships with (builtin_subcomponents) is normally implied by the parent’s type and needs no enabled key. It gets an explicit one exactly when its attached state diverges from what the bare class declaration would produce, because reconstruction would otherwise flip it:
  • declared enabled but switched off by the config (enabled: false) — emitted as a bare {"enabled": false} stub under subcomponents, the type-omitted overlay form that inherits the declaration’s type. The stub exists only in this output; it is never a phantom entry in subcomponents, getState, or introspection.
  • declared enabled: false but switched on by the config (enabled: true) — its normal serialized entry additionally carries "enabled": true, which an omitted key would not preserve (an authored entry that omits enabled keeps the declaration’s value).
A child whose state matches its declaration is emitted exactly as before, with no enabled key and no stub. The asymmetry to know: children skipped because the tree was built with enable_cameras=False get no stub. That flag is factory-only camera-ownership semantics — deploy streaming owns those devices for this process, the config never said to drop them — so the serialized config keeps describing the robot’s cameras, and passing the flag again is what skips them again. A node also carries an identity block — {"serial_number": ..., "model": ..., "controller_version": ..., "mac_address": ...}, each key present only when known — whenever this component has a hardware identity to record, so the serialized tree says which physical units it was built from and not just which types. It is what getIdentity reports overlaid on the identity the config declared (expected_identity), so hardware that has identified itself wins over the declaration while an unread declaration survives the round-trip. Omitted entirely when nothing is known — never an empty block. Returns: The minimal config mapping such that make_robot(component.serialize()) reconstructs an equivalent component tree. Raises: RuntimeError: If this component was not constructed from a config, so no config type is recorded. ConfigValidationError: If the recorded config does not fit the envelope (e.g. a recorded arg key is not a string).

setJointAngles()

Signature
Set joint angles. Can be called in two ways:
  • Multi-group (default): pass a dict mapping group names to angle lists. velocities may also be a dict.
  • Single-group: pass a flat list of angles together with group="<name>". velocities may also be a flat list. This matches the Arm / Humanoid / Quadruped signature.
required
Target joint angles in radians — a dict for multi-group or a list for single-group.
Optional joint velocities. In single-group mode, a list or scalar. In multi-group mode, a dict mapping group names to velocity lists/scalars (partial dicts OK — omitted groups get no velocity), a single scalar (broadcast to every group being commanded), or None.
Subcomponent name. Required when angles is a list.
Raises: KeyError: If a group name is not a known subcomponent. TypeError: If angles is a list but group is not provided, or if velocities is a list in multi-group mode.
Register the process-wide atexit and signal handlers for safe teardown. Installs an atexit hook and SIGINT/SIGTERM (and SIGHUP where available) handlers that shut down every enrolled bring-up entry point, newest first, so hardware is released on normal termination and on Ctrl+C. Registration normally happens automatically the first time a component is enrolled from the main thread; call this explicitly from the main thread when components are only ever brought up from background threads, where Python forbids installing signal handlers — enrollment there defers the atexit hook together with the signal handlers.

shutdown()

Signature
Shut down this component’s tree, halting motion and releasing resources. Two phases, both owned by the base class. First a tree-wide stop: motion is halted everywhere before anything is torn down, so no part of the robot is still commandable while another part is being released. Then the teardown walk — every subcomponent is shut down first (children before their parent, leaf-to-root; siblings in subcomponents insertion order), then this component’s own _shutdown_self hook runs. Children-first ordering is a contract for shared resources: a parent that owns a resource its children borrow (e.g. one ROS node shared by both arms) releases it in its own hook, after all children have shut down — a child must never release a resource it does not own. Idempotent: the first call latches, and every later call is a no-op; the tree-wide stop also runs once, so a nested shutdown() reached by the walk does not re-stop its subtree. Best-effort: the stop phase and every hook failure are logged and the walk continues, so this method never raises — it can run safely from __exit__, signal handlers, and interpreter-exit hooks. Blocks until every hook has returned; the component is unusable afterwards. Shutting down also drops the component from process-wide exit cleanup (the enrollment made by the public start), so the atexit/signal handlers only ever touch components that are still live. Subclasses must not replace this method — per-component cleanup belongs in _shutdown_self. An override may only extend the walk and must delegate to super().shutdown(); it must never re-implement the recursion.

start()

Signature
Bring this component’s tree online (connect, enable, arm). Recursive walk owned by the base class and the mirror of shutdown. Per node the walk runs _start_self, then _provision_children, then descends into every subcomponent (parents before their children, root-to-leaf; siblings in subcomponents insertion order). That ordering is the resource-handoff contract: a parent creates the runtime resources its children borrow (an RTDE connection, a ROS node) in its own bring-up hook and hands them to its children in _provision_children, before any child hook runs. Warning: bring-up hooks move hardware. start() is not a passive connect. Driver hooks power on, enable, home, calibrate, or stand a robot up — a Robotiq gripper strokes its jaws to auto-calibrate, a WidowX homes, a Flexiv enables and homes its gripper, a Go2 stands up. Restarting a tree re-runs those hooks, so stop() then start() repeats that motion. Clear the workspace before starting or restarting a physical robot; each driver’s _start_self documents the motion it commands. Idempotent: the walk always descends the whole tree, but a component whose bring-up hook already ran — and whose _still_live health hook still reports it live — is skipped, so calling start() twice re-runs no bring-up and calling it after attaching a new subcomponent brings up only the newcomer. A started component whose health hook reports its resources dead (a power-cycled controller, a dropped connection) gets its bring-up re-run, so start() alone is the recovery call after such a fault. _provision_children runs on every descent through a node regardless of that mark, so a newcomer is provisioned by its parent before its own hook runs. stop re-arms every component it visits, so stop() then start() re-runs the bring-up hooks (reconnect-after-fault) — while leaving the stopped tree callable throughout. Blocks until every hook has returned. Fail-fast: the first failing hook aborts the walk and ComponentStartError is raised — a half-started component is never handed back to the caller. Cleanup is scoped to this call: the components this walk brought up for the first time are shut down (best-effort) and the failing subtree is released, while components that were already live before the call keep running and the tree is not latched. On an initial bring-up nothing was live, so that scope is the whole tree. Components this walk was restarting (started before, re-armed by stop) are only stopped, never shut down — a failed reconnect (e.g. a robot still in an emergency stop) stays retryable with another start(). A subtree that was already shut down is skipped with a warning rather than resurrected. Calling this method also enrolls the component in process-wide exit cleanup: the atexit/signal handlers shut down every component whose public start() was called (a tree root, or a subtree driven directly) and that has not been shut down yet, so hardware is released on normal termination and Ctrl+C once the handlers are installed — automatic on the first main-thread enrollment; a process that only brings components up from background threads must call setup_shutdown_handlers from its main thread. shutdown unenrolls. Subclasses must not replace this method — per-component bring-up belongs in _start_self. An override may only extend the walk by delegating to super().start(); it must never re-implement the recursion. Raises: RuntimeError: If this component has already been shut down; shutdown() is final, so a new object is required. ComponentStartError: If a bring-up hook raised. Whatever this call brought up has been released by the time it propagates, and the original failure is the exception’s __cause__.

stop()

Signature
Halt all motion across this component’s tree (soft e-stop). Recursive walk owned by the base class: every subcomponent is stopped first (children before their parent, leaf-to-root; siblings in subcomponents insertion order), then this component’s own _stop_self hook runs. The walk is best-effort — a failing component never prevents the rest of the tree from being stopped; failures are collected and raised together after the walk completes. Components (and their subtrees) that have already been shut down are skipped. Safe to call repeatedly: stopping an already-stopped tree re-runs the hooks, which must tolerate that. Blocks until every hook has returned. Stops motion only; resources stay live and the component remains usable — telemetry and every other public method keep working on a stopped tree. Use shutdown to release resources. Stopping re-arms each visited component, so a later start re-runs the bring-up hooks — that is the stop-then-start restart path — but it does not revoke callability: only shutdown does that. Subclasses must not replace this method — per-component stop behavior belongs in _stop_self. An override may only extend the walk (adding a driver-specific mode, as UR5e does with its immediate escape hatch) and must delegate to super().stop() for the normal path; it must never re-implement the recursion. Raises: ComponentStopError: If one or more stop hooks raised. The walk still visited every component; the exception carries every (component_path, exception) pair.

Related topics

Overview