Skip to main content
AirGen simulation quadruped implementation. This class provides an interface to control simulated quadruped robots in AirGen using the CVMoverClient from the grid_sim_client.airgen client.

Constructor

Signature
Initialize a simulated AirGen quadruped client.
str
default:"'127.0.0.1'"
IP address of the AirGen server. (default: “127.0.0.1”)
int
default:"41481"
Port number for the AirGen server. (default: 41481)
int
default:"3600"
Timeout value for the connection in seconds. (default: 3600)
str
default:"''"
Name of the robot in the simulation. (default: "")

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.

cleanup()

Signature
Release AirGen transport resources and unregister base cleanup.

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.

getBarometerData()

Signature

getDistanceSensorData()

Signature

getGpsData()

Signature

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
Get an image from the specified camera.
str
default:"''"
Name of the camera in the AirGen scene. (default: “front_center”)
str
default:"'rgb'"
Image type, one of (“rgb”|“depth”). (default: “rgb”)
Returns: Image: Captured image wrapped in the project’s Image type.

getImuData()

Signature

getJointAngles()

Signature
Get current joint angles in radians. CVMoverClient doesn’t provide direct joint angle access in the standard API. This would require accessing lower-level state information. Returns: list[float]: Current joint angles in radians.

getLidarData()

Signature

getLidarPointCloud()

Signature
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

getMagnetometerData()

Signature

getOrientation()

Signature
Get the orientation of the quadruped in the world frame. Returns: Optional[Orientation]: Current orientation as a quaternion (x, y, z, w).

getPosition()

Signature
Get the position of the quadruped in the world frame. Returns: Optional[Position]: Current position of the quadruped in world coordinates.

getState()

Signature
Get the current state of the quadruped. Returns: dict: Current state including position, orientation, and velocity.

lieDown()

Signature
Command the robot to lie down. CVMoverClient doesn’t have an explicit lieDown command. Users can stop the robot and it will settle into a stable pose. Returns: None

moveByVelocity()

Signature
Move the quadruped with a twist command (linear + angular velocity). In body frame, the velocity is continuously re-applied in the robot’s instantaneous body frame, so a non-zero yaw rate produces curved motion.
Velocity
required
Linear velocity in m/s (x, y, z).
Velocity
required
Angular velocity in rad/s; only the z component (yaw rate) is used.
str
default:"'body'"
Reference frame for the linear velocity (“body” or “world”). (default: “body”)
Returns: None

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).

setDefaultJointAngles()

Signature
Set the default joint position for the robot.
required
Value of the joint position.

setDefaultJointVelocities()

Signature
Set the default joint velocity for the robot.
required
Value of the joint velocity.

setJointAngles()

Signature
Set joint angles in radians. CVMoverClient doesn’t provide direct joint angle control in the standard API. The robot is controlled via velocity and position commands at a higher level.
list
required
List of target joint angles in radians.
bool
default:"True"
Wait for movement to complete. (default: True)
Optional[float]
Time to complete the movement in seconds.
Optional[float]
Time to accelerate/decelerate in seconds.
Returns: None

setup_shutdown_handlers()

Signature
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.

standUp()

Signature
Command the robot to stand up. This is typically handled automatically when the robot starts moving. For explicit gait control, use the underlying CVMoverClient methods. Returns: None

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
Soft e-stop for the robot. Stops the robot from moving gracefully by sending zero velocity commands. The API control is toggled off and on to reset the robot’s internal controller state and ensure all movement commands are cleared. Returns: None