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
str
required
Unique identifier for the sensor
Sensor
required
Sensor object to add (e.g. Camera, IMU, Lidar)
addSubcomponent()
Signature
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.
name is not a non-empty string, or contains
a path syntax character.
builtin_subcomponents()
Signature
__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
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."<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
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.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
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
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.getLidarPointCloud()
Signature
str
default:"''"
Name of the lidar. If empty, uses the first available lidar.
getOrientation()
Signature
getPosition()
Signature
getState()
Signature
_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.
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.
land()
Signature
moveByVelocity()
Signature
required
Linear velocity in m/s.
required
Angular velocity in rad/s.
default:"'body'"
Reference frame for the velocity command —
"body" (drone-relative) or "world". Defaults to "body".serialize()
Signature
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 undersubcomponents, the type-omitted overlay form that inherits the declaration’stype. The stub exists only in this output; it is never a phantom entry insubcomponents,getState, or introspection. - declared
enabled: falsebut 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 omitsenabledkeeps the declaration’s value).
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).
setup_shutdown_handlers()
Signature
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
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
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
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.
takeoff()
Signature