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

# LowLevelControllable

> Interface for robots controllable by direct low-level joint commands

```python theme={null}
from grid_robot_api.base.robot import LowLevelControllable
```

Interface for robots controllable by direct low-level joint commands.

## Methods

### `commandJointsPD()`

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

Command a subset of joints to position targets with per-joint gains.

Deliberately **not** `setJointAngles`. The two look similar and are
not interchangeable, so per this package's "shared names mean shared
behavior" rule they carry different names:

* **Non-blocking.** `setJointAngles` may block — its base signature
  takes `blocking` / `moving_time`, and some implementations sleep
  for a control period. This is called every tick from a fixed-rate
  control thread that owns its own pacing, so it must return at once.
* **A subset, not the whole robot.** `setJointAngles` drives every
  joint and rejects a partial list. This drives exactly
  `joint_indices`, which is how a lower-body policy runs while a
  separate controller owns the arms.
* **Caller-supplied gains.** `setJointAngles` applies the robot's own
  stiffness. Here the gains arrive per call, because a policy's gains
  are part of its deployment contract and travel with its artifact.
  Driving at the robot's gains instead is a real failure and a quiet
  one — it shows up as a degraded gait, not an error.

Every joint NOT in `joint_indices` is left un-actuated per the robot's
own convention (zero gains, or the vendor's position/velocity-stop
sentinels), so a separate controller may own it. The four arrays are
positionally aligned (one entry per driven joint).

<ParamField body="joint_indices" required>
  Motor indices to drive.
</ParamField>

<ParamField body="q" required>
  Target joint positions (rad), aligned to `joint_indices`.
</ParamField>

<ParamField body="kp" required>
  Per-joint position gains, aligned to `joint_indices`.
</ParamField>

<ParamField body="kd" required>
  Per-joint damping gains, aligned to `joint_indices`.
</ParamField>

### `getControlState()`

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

Return the latest control-loop state without consuming it.

Non-consuming and non-blocking: repeated calls return the most recent
snapshot, suitable for a fixed-rate control loop. Distinct from the
base `getState` registry
(a keyed dict for slow proprioception reads) — this is the fast,
fixed-shape accessor for closed-loop control.

**Returns:**

The latest control-loop state.

### `setDamping()`

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

Command a damping brake: zero position gain, the given damping gain.

<ParamField body="kd" default="8.0">
  Damping gain applied to every motor.
</ParamField>

### `setZeroTorque()`

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

Command zero torque to all motors.


## Related topics

- [Customizing RL Training](/simulation/isaac/reinforcement-learning/customizing-training.md)
- [MDP Config](/simulation/isaac/session_configuration/mdp.md)
- [Workflow Config](/simulation/isaac/session_configuration/workflow.md)
- [AsyncCortexClient](/python-api/grid-cortex-client/asynccortexclient.md)
- [CortexClient](/python-api/grid-cortex-client/cortexclient.md)
