> ## Documentation Index
> Fetch the complete documentation index at: https://docs.generalrobotics.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Unitree Go2 Quadruped

> The Unitree Go2 Quadruped as GRID drives it

The Unitree Go2 Quadruped as GRID drives it — what this robot actually implements, with the signatures it actually accepts.

`make_robot("<name>")` returns a `RemoteRobot`. Its attributes are the methods and subcomponents below, and every call runs on the robot; the driver behind it is `Go2`. Connecting neither starts nor moves the robot — it attaches to one that is already up. A method that fails on the robot arrives as `RuntimeError` naming the original error in its message. The client packages this page uses are preinstalled in every GRID session workspace and in the Python environment the GRID CLI prepares when you run a program with `skill run`; there is nothing to install.

```python theme={null}
from grid_nexus_client import make_robot

robot = make_robot("<go2-name>")

# `front` is a built-in camera: reach it as an attribute.
frame = robot.front.getImage()
rgb = frame.decode()                    # -> H x W x 3 RGB ndarray
```

## Methods on the robot

Called as `robot.<method>(...)`.

### `commandJointsPD()`

```python Signature theme={null}
commandJointsPD(
    joint_indices: np.ndarray,
    q: np.ndarray,
    kp: np.ndarray,
    kd: np.ndarray,
) -> None
```

Drive the given motors with per-joint PD gains; leave all others un-actuated.

Direct low-level position control for a fixed-rate policy loop:
non-blocking (no sleep) and gain-per-joint, unlike `setJointAngles`.
Motors named by `joint_indices` (SDK order) get the position PD
command; every other motor is parked at the firmware's position and
velocity stop values with zero gains, so it is not actuated.

**joint\_indices** *required*

SDK motor indices to drive.

**q** *required*

Target joint positions (rad), aligned to `joint_indices`.

**kp** *required*

Per-joint position gains, aligned to `joint_indices`.

**kd** *required*

Per-joint damping gains, aligned to `joint_indices`.

### `getBatteryState()`

```python Signature theme={null}
getBatteryState()
```

Get the current battery state when low-level control enabled.

**Returns:**

Battery voltage and current

### `getControlState()`

```python Signature theme={null}
getControlState() -> ControlState
```

Return the latest control-loop state without consuming it.

Base angular velocity, orientation, and per-motor position/velocity in
Unitree SDK index order. Non-blocking; the RL control loop reads it each
tick. The Go2 runs no state estimator, so `base_lin_vel` is absent and
a policy consuming that term is rejected at driver construction.

**Returns:**

The latest control-loop state.

**Raises:**

RuntimeError: If no low-level state has been received yet.

### `getFootForce()`

```python Signature theme={null}
getFootForce()
```

Get the current foot force readings when low-level control enabled.

**Returns:**

Current foot force readings

### `getIMU()`

```python Signature theme={null}
getIMU()
```

Get the current IMU state when low-level control enabled.

**Returns:**

Current IMU state

### `getImage()`

```python Signature theme={null}
getImage(camera_name: str = '', image_type: str = '') -> Image | None
```

Get an image from the specified camera.

**camera\_name** `str` (default: '')

name of the camera. May be omitted when the robot has exactly one camera.

**image\_type** `str` (default: '')

supported image types depend on camera, for the built-in `front` camera only "rgb" is supported

**Returns:**

Image: requested image

### `getLidarPointCloud()`

```python Signature theme={null}
getLidarPointCloud(lidar_name: str = '') -> Optional[PointCloud]
```

Get a point cloud from the named LiDAR sensor.

**lidar\_name** `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

### `getLowLevelState()`

```python Signature theme={null}
getLowLevelState()
```

Get the complete low level state.

**Returns:**

Complete low level robot state
[https://github.com/unitreerobotics/unitree\_sdk2\_python/blob/3af1610228977f449e22c8fba868bb8516d41ec3/unitree\_sdk2py/idl/unitree\_go/msg/dds\_/\_LowState\_.py#L34](https://github.com/unitreerobotics/unitree_sdk2_python/blob/3af1610228977f449e22c8fba868bb8516d41ec3/unitree_sdk2py/idl/unitree_go/msg/dds_/_LowState_.py#L34)

### `getMotorState()`

```python Signature theme={null}
getMotorState()
```

Get the current state of all motors when low-level control enabled.

**Returns:**

Current state of all motors
[https://github.com/unitreerobotics/unitree\_sdk2\_python/blob/3af1610228977f449e22c8fba868bb8516d41ec3/unitree\_sdk2py/idl/unitree\_go/msg/dds\_/\_MotorState\_.py](https://github.com/unitreerobotics/unitree_sdk2_python/blob/3af1610228977f449e22c8fba868bb8516d41ec3/unitree_sdk2py/idl/unitree_go/msg/dds_/_MotorState_.py)

### `getOrientation()`

```python Signature theme={null}
getOrientation() -> Orientation | None
```

Get the current orientation of the unitree go2 agent in radians.

**name** `optional, str`

unused

**Returns:**

Orientation: current orientation of the unitree go2 agent in radians

### `getPosition()`

```python Signature theme={null}
getPosition() -> Position | None
```

Get the current position of the unitree go2 agent with respect to the world coordinate.

**name** `optional, str`

unused

**Returns:**

Position: unitree go2 agent's current position (x,y,z) with respect to the world
coordinate

### `getState()`

```python Signature theme={null}
getState(keys: Optional[Union[str, List[str]]] = None) -> Dict[str, Any]
```

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 `&#123;"left_arm": &#123;"joint_positions": ...&#125;,
"right_arm": &#123;...&#125;&#125;`, 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 — `&#123;"error": "&lt;ExceptionType>: &lt;message>"&#125;`,
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 `&#123;"error": "..."&#125;` 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 `Robot` with
registered state getters) 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.

**keys**

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.

### `getWirelessRemoteState()`

```python Signature theme={null}
getWirelessRemoteState() -> Sequence[int]
```

Return the latest wireless-remote bytes (Unitree teleop), non-consuming.

Unitree-specific operator input, deliberately kept off the generic
low-level control contract.

**Returns:**

The raw `wireless_remote` byte buffer from the latest `LowState_`.

**Raises:**

RuntimeError: If no low-level state has been received yet.

### `lieDown()`

```python Signature theme={null}
lieDown(duration_s: float = 4.0) -> None
```

Ease the robot down to a folded resting pose.

Under high-level control this asks the built-in controller to stand
down. Under low-level control — where that controller has been released
and `self.client` does not exist — it interpolates every motor from
its measured position to `GO2_LIEDOWN_POSE` at the hold gains, so
the robot settles rather than collapsing.

**duration\_s** (default: 4.0)

Seconds to spend on the descent (low-level only). Slower is gentler.

**Returns:**

None

**Raises:**

RuntimeError: If no low-level state has been received yet, or the
last one is stale (low-level only).

### `moveByVelocity()`

```python Signature theme={null}
moveByVelocity(
    linear_velocity: Velocity,
    angular_velocity: Velocity,
    frame: str = 'body',
) -> None
```

Command a body-frame walking velocity.

Each command is honoured for roughly one second; without re-issue
the controller decays the command and the robot stops. The
controller also ramps each command up and down, so a single
command followed by a long wait covers significantly less ground
than `v × t` would predict (observed \~40–60% over a one-second
window). Re-issue at high rate (e.g. 10 Hz) for sustained motion
past about a second.

Body-frame z linear and x/y angular components are discarded —
the Go2 cannot translate vertically nor roll/pitch on demand.

**linear\_velocity** `Velocity` *required*

Body-frame linear velocity. `x_vel` (forward, m/s), `y_vel` (left, m/s); `z_vel` is discarded.

**angular\_velocity** `Velocity` *required*

Body-frame angular velocity. `z_vel` (yaw rate, rad/s); `x_vel` and `y_vel` are discarded.

**frame** `optional, str` (default: 'body')

Reference frame of the command. Defaults to `"body"`. `"world"` is not supported.

**Returns:**

None

With `rl_locomotion` configured the command is latched for the
policy instead of sent to the built-in controller, so a caller — a
teleop script or the nav stack — drives a learned gait through this
same contract without knowing a policy is running. The policy tracks
the command continuously, so the decay and ramping described above do
not apply.

**Raises:**

NotImplementedError: If `low_level_control` was enabled on
construction without an RL policy (only the high-level
controller implements this), or if `frame="world"` is
requested.

### `setDamping()`

```python Signature theme={null}
setDamping(kd: float = 8.0) -> None
```

Publish a damping brake — zero position gain, given damping.

The Go2's braking safe state: the legs resist motion and the robot
settles onto them rather than collapsing.

**kd** (default: 8.0)

Damping gain applied to every leg motor.

### `setDefaultJointAngles()`

```python Signature theme={null}
setDefaultJointAngles(joint_pos: list) -> None
```

Set the default joint position for the robot.

**joint\_pos** *required*

Value of the joint position.

### `setDefaultJointVelocities()`

```python Signature theme={null}
setDefaultJointVelocities(joint_vel: list) -> None
```

Set the default joint velocity for the robot.

**joint\_vel** *required*

Value of the joint velocity.

### `setJointAngles()`

```python Signature theme={null}
setJointAngles(joint_position: dict) -> None
```

Set the position of the robot's joints when low-level control enabled.

**joint\_position** `dict` *required*

Dictionary mapping joint names to target positions

**Returns:**

None

### `setJointTorques()`

```python Signature theme={null}
setJointTorques(joint_torque: dict) -> None
```

Set the torque of the robot's joints when low-level control enabled.

**joint\_torque** `dict` *required*

Dictionary mapping joint names to target torques

**Returns:**

None

### `setJointVelocities()`

```python Signature theme={null}
setJointVelocities(joint_velocity: dict) -> None
```

Set the velocity of the robot's joints when low-level control enabled.

**joint\_velocity** `dict` *required*

Dictionary mapping joint names to target velocities

**Returns:**

None

### `setZeroTorque()`

```python Signature theme={null}
setZeroTorque() -> None
```

Publish a zero-torque command to all motors (non-blocking).

### `standUp()`

```python Signature theme={null}
standUp() -> None
```

Make the robot stand up from a lying position.

**Returns:**

None

### `stop()`

```python Signature theme={null}
stop() -> None
```

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.

## Properties on the robot

Read as `robot.<property>`; each read runs the getter on the robot.

**policy\_drivers** `Dict[str, PolicyDriver]`

The robot's registered policy drivers, keyed by name.

Empty on a robot constructed without RL. Lazily created, so the mixin
needs no cooperation from the host robot's `__init__`.

## robot.front — camera

Reached as `robot.front`; its methods are `Go2Camera`'s and run on the robot.

The Go2's front camera, read over the Unitree DDS video client.

Methods inherited from the component base are shown in brief here; their full descriptions are under *Methods on the robot* above.

### `front.getCameraExtrinsics()`

```python Signature theme={null}
front.getCameraExtrinsics() -> Optional[np.ndarray]
```

Get the extrinsic parameters for the camera.

### `front.getCameraIntrinsics()`

```python Signature theme={null}
front.getCameraIntrinsics() -> Optional[Tuple[float, float, float, float]]
```

Get the intrinsic parameters for the camera.

### `front.getCameraSettings()`

```python Signature theme={null}
front.getCameraSettings() -> CameraSettings
```

Get the camera settings.

### `front.getData()`

```python Signature theme={null}
front.getData() -> Image
```

Get the image from the camera.

### `front.getImage()`

```python Signature theme={null}
front.getImage(
    image_type: str | None = None,
    *,
    compressed: bool = True,
) -> Image | None
```

Get image from Go2 camera.

VideoClient natively returns JPEG (\~200 KB). By default we pass
those bytes through without decoding (\~30x bandwidth saving on
the wire). Pass `compressed=False` to get a decoded RGB ndarray
instead — pays a JPEG decode on every call, but matches the
legacy API for callers that expect ndarrays.

**image\_type** `str | None, optional`

no effect for Go2Camera — only JPEG is supported. Kept for API compatibility.

**compressed** `bool, optional` (default: True)

when True (default) return the raw JPEG bytes via `Image(..., encoding_format="jpeg")`. When False, decode to an RGB ndarray and return `Image(rgb_ndarray)`.

**Returns:**

Image | None: encoded JPEG or decoded RGB ndarray depending
on `compressed`. None if the SDK call fails.

### `front.getState()`

```python Signature theme={null}
front.getState(keys: Optional[Union[str, List[str]]] = None) -> Dict[str, Any]
```

Get a live state snapshot of this component's tree.

### `front.recordCalibration()`

```python Signature theme={null}
front.recordCalibration(calibration: dict) -> None
```

Install a recorded extrinsic calibration on this camera in place.

### `front.reloadCalibration()`

```python Signature theme={null}
front.reloadCalibration() -> dict
```

Re-read this camera's calibration from the robot's config and install it.

### `front.setCameraExtrinsics()`

```python Signature theme={null}
front.setCameraExtrinsics(transform: np.ndarray) -> None
```

Set the extrinsic parameters for the camera.

### `front.setCameraIntrinsics()`

```python Signature theme={null}
front.setCameraIntrinsics(fx: float, fy: float, cx: float, cy: float) -> None
```

Set the intrinsic parameters for the camera.

### `front.setCameraSettings()`

```python Signature theme={null}
front.setCameraSettings(settings: CameraSettings) -> None
```

Set the camera settings.

### `front.stop()`

```python Signature theme={null}
front.stop() -> None
```

Halt all motion across this component's tree (soft e-stop).

## robot.go2\_lidar — lidar

Reached as `robot.go2_lidar`; its methods are `Go2Lidar`'s and run on the robot.

Bridge the Go2's onboard LiDAR into the GRID `Lidar` interface.

Methods inherited from the component base are shown in brief here; their full descriptions are under *Methods on the robot* above.

### `go2_lidar.getData()`

```python Signature theme={null}
go2_lidar.getData() -> PointCloud
```

### `go2_lidar.getPointCloud()`

```python Signature theme={null}
go2_lidar.getPointCloud(
    *,
    source: str = LIDAR_SOURCE_HEIGHT_MAP,
) -> Optional[PointCloud]
```

Return the latest point cloud from the selected source, or `None`.

**source** (default: LIDAR\_SOURCE\_HEIGHT\_MAP)

Which LiDAR product to return. `"height_map"` (default) converts Unitree's fused elevation grid to one point per observed cell, robot-centred with `+x` forward. `"cloud"` returns the raw LiDAR returns of the most recent message, in the sensor's own frame. The class docstring sets out how the two differ in dimensionality, extent, freshness and frame.

**Returns:**

The latest `PointCloud`, or `None` if the messages it
needs have not arrived yet or hold no usable points. The
`"height_map"` source needs both a height map and a robot
pose, each no older than `HEIGHT_MAP_MAX_AGE_S`, so it
returns `None` until each has arrived and again once either
stops — which is what the Go2 does with the height map the
moment it sits or lies down. Each distinct reason is logged
once rather than once per call.

**Raises:**

ValueError: If `source` is neither `"height_map"` nor
`"cloud"`.

### `go2_lidar.getState()`

```python Signature theme={null}
go2_lidar.getState(keys: Optional[Union[str, List[str]]] = None) -> Dict[str, Any]
```

Get a live state snapshot of this component's tree.

### `go2_lidar.stop()`

```python Signature theme={null}
go2_lidar.stop() -> None
```

Halt all motion across this component's tree (soft e-stop).

## Not implemented on this robot

Declared by the interface, raises `NotImplementedError` here: `getJointAngles`.

## Lifecycle and configuration

The driver's own bring-up and configuration hooks. The edge runs them when the robot comes up; do not call them from a program. Note that `robot.shutdown()` on the proxy is not the method below — it is `RemoteRobot.shutdown()`, which closes your connection and leaves the robot as it was.

* `addPolicy()` — Register a policy driver under a name.
* `addSensor()` — Add an external sensor to the robot.
* `addSubcomponent()` — Attach a child component under a name.
* `builtin_subcomponents()` — Declare the `front` camera every Go2 ships with.
* `configHash()` — Hash this component's serialized config subtree.
* `config_schema()`
* `from_config()` — Construct this component and its config-declared subtree.
* `getIdentity()` — Get this component's own hardware identity (serial, model, version, MAC).
* `getRobotId()` — Stable, readable identifier for a robot or rig, for a database key.
* `isPolicyAlive()` — Return whether a policy control loop is currently running.
* `raiseForPolicyError()` — Re-raise the exception that killed a policy control loop, if any.
* `serialize()` — Serialize this component tree back to its config envelope.
* `setup_shutdown_handlers()` — Register the process-wide atexit and signal handlers for safe teardown.
* `shutdown()` — Shut down this component's tree, halting motion and releasing resources.
* `start()` — Bring this component's tree online (connect, enable, arm).
* `startPolicy()` — Run the policy startup handshake, then begin closed-loop control.
* `stopAllPolicies()` — Stop every registered policy control loop.
* `stopPolicy()` — Stop a policy control loop.

The contract these methods implement is **Robot interface**; values returned are **grid-types** (camera reads return `Image` — call [`decode()`](/v2.2/python-api/grid-types/image) for an ndarray). You reach the robot through [`make_robot`](/v2.2/python-api/grid-nexus-client/make_robot).


## Related topics

- [Python APIs](/v2.2/python-api/overview.md)
- [Robots](/v2.2/simulation/airgen/robots.md)
- [Quadruped](/v2.2/python-api/robot-interface/quadruped.md)
- [Environment Config](/v2.2/simulation/isaac/session_configuration/env.md)
- [MDP Config](/v2.2/simulation/isaac/session_configuration/mdp.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.