> Documentation index: https://nao-rs.lukaloehr.com/llms.txt, a list of every page in this documentation.

# LEDs

> Every LED group of the NAO6, every single LED's index from the robot API down to the driver chip's channel, the colour order, the value conversion and how to set them.

A NAO6 has 2 × 8 RGB eye segments, 2 × 10 ear LEDs, 12 skull LEDs, an RGB
chest LED and an RGB LED in each foot. The head LEDs sit on the head's I2C bus,
the chest and foot LEDs behind the chest board.

## The groups

| Group | LEDs | Colour | Driven by | Robot API field | Runtime command |
| --- | --- | --- | --- | --- | --- |
| Left eye | 8 segments | RGB | 3 LED driver chips, one per colour | `left_eye`: 8 × `Rgb` | `left_eye: [f32; 24]` |
| Right eye | 8 segments | RGB | the same 3 chips | `right_eye`: 8 × `Rgb` | `right_eye: [f32; 24]` |
| Left ear | 10 | one colour | LED driver at `0x61` | `left_ear`: 10 × brightness | `left_ear: [f32; 10]` |
| Right ear | 10 | one colour | LED driver at `0x60` | `right_ear`: 10 × brightness | `right_ear: [f32; 10]` |
| Skull | 12 | one colour | LED driver at `0x65` | not settable | `skull: [f32; 12]` |
| Chest | 1 | RGB | chest board | `chest`: `Rgb` | `chest: [f32; 3]` |
| Left foot, right foot | 1 each | RGB | foot boards | `left_foot`, `right_foot`: `Rgb` | `left_foot`, `right_foot: [f32; 3]` |

The runtime's commands use LoLA's arrays, so software written for LoLA sets
the LEDs unchanged. The robot API sits on top of them and converts.

## Values and colour order

| Where | Value | Colour order |
| --- | --- | --- |
| Robot API, `Rgb` | three bytes, 0..255 | red, green, blue |
| Robot API, ears | 0..1, limited to that range | brightness only |
| Runtime, eyes | 0..1 per LED | **planar**: all 8 reds (indices 0..7), then all 8 greens (8..15), then all 8 blues (16..23) |
| Runtime, chest and feet | 0..1 per channel | red, green, blue |
| Runtime, ears and skull | 0..1 per LED | brightness only |

A runtime value becomes one byte without a gamma curve and without clamping,
exactly as LoLA and HAL do it: the head LEDs take `value × 255` truncated
toward zero, the chest and foot LEDs `value × 255` rounded to the nearest
whole number. 1.0 gives 255 either way. Values above 1 or below 0 wrap around
in the byte (1.5 gives 126 on a head LED), so keep runtime values within 0..1.
The robot API can't produce such values: it sends bytes divided by 255 and
limits ear values to 0..1.

## Eyes

Each eye has 8 segments around it. In the robot API, segment *i* is at
*i* × 45°: segment 0 at the top, then clockwise as seen from the front of the
robot.

The three eye chips each drive one colour of both eyes: the chip at `0x62`
red, `0x64` green, `0x66` blue. Channels 0 to 7 belong to the left eye,
8 to 15 to the right eye.

**Left eye**

| API segment | Angle | Runtime index (red, green, blue) | Chip channel |
| --- | --- | --- | --- |
| 0 | 0° | 1, 9, 17 | 0 |
| 1 | 45° | 0, 8, 16 | 1 |
| 2 | 90° | 7, 15, 23 | 2 |
| 3 | 135° | 6, 14, 22 | 3 |
| 4 | 180° | 5, 13, 21 | 4 |
| 5 | 225° | 4, 12, 20 | 5 |
| 6 | 270° | 3, 11, 19 | 6 |
| 7 | 315° | 2, 10, 18 | 7 |

**Right eye**

| API segment | Angle | Runtime index (red, green, blue) | Chip channel |
| --- | --- | --- | --- |
| 0 | 0° | 0, 8, 16 | 8 |
| 1 | 45° | 1, 9, 17 | 9 |
| 2 | 90° | 2, 10, 18 | 10 |
| 3 | 135° | 3, 11, 19 | 11 |
| 4 | 180° | 4, 12, 20 | 12 |
| 5 | 225° | 5, 13, 21 | 13 |
| 6 | 270° | 6, 14, 22 | 14 |
| 7 | 315° | 7, 15, 23 | 15 |

The left eye's runtime array is ordered differently from the right eye's (it
comes from LoLA's own key order), but on the chips both run in angle order.

## Ears

Each ear has 10 LEDs in a ring, one every 36°. In the robot API, LED *i* of
either ear is the one at *i* × 36°.

| Angle | API index | Left ear runtime index | Left ear chip channel | Right ear runtime index | Right ear chip channel |
| --- | --- | --- | --- | --- | --- |
| 0° | 0 | 0 | 5 | 9 | 9 |
| 36° | 1 | 1 | 6 | 8 | 8 |
| 72° | 2 | 2 | 7 | 7 | 7 |
| 108° | 3 | 3 | 8 | 6 | 6 |
| 144° | 4 | 4 | 9 | 5 | 5 |
| 180° | 5 | 5 | 0 | 4 | 4 |
| 216° | 6 | 6 | 1 | 3 | 3 |
| 252° | 7 | 7 | 2 | 2 | 2 |
| 288° | 8 | 8 | 3 | 1 | 1 |
| 324° | 9 | 9 | 4 | 0 | 0 |

The right ear's runtime array runs backwards relative to the API. Channels 10 to 15 of the
ear chips are unused and stay off.

## Skull

| Runtime index | Chip channel |
| --- | --- |
| 0, 1, 2, 3, 4, 5, 6, 7, 8 | 8, 7, 6, 5, 4, 3, 2, 1, 0 |
| 9, 10, 11 | 11, 10, 9 |

The robot API can't set the skull today. `hulk` shows the battery on it: the
LEDs light up to the charge level and run an animation while the robot
charges.

## Chest and feet

The chest LED goes to the chest board every cycle as one message (red, green,
blue), except in a cycle that changes the chest board's client state; the foot
LEDs go to the two foot boards (red, green, blue each).

## Who sets them

| Situation | Eyes | Ears | Skull | Chest | Feet |
| --- | --- | --- | --- | --- | --- |
| The runtime before its controller sends anything | white (as LoLA without a client) | on | off | off | off |
| `hulk`'s own status, robot unstiff | rainbow | hottest motor (full below 65 °C, dark from 74 °C) | battery | blinking blue |  |
| `hulk`'s own status, playing | white | hottest motor | battery | green |  |
| `hulk`'s own status, robot API link |  |  |  |  | dark before a client connected, green while connected, red after it was lost |
| Robot API `Leds` message | per group: a group set to `None` keeps `hulk`'s status |  | not settable |  |  |

A `Leds` message with every group `None`, or `Leds(None)`, hands all LEDs back
to `hulk`.

## Setting them

**From the robot API**, send a `Leds` message (MessagePack, see [Robot
API](https://nao-rs.lukaloehr.com/reference/api.md)). Example, as JSON for readability: a red left eye and a
green chest, everything else left to `hulk`:

```json
{
  "Leds": {
    "left_eye": [[255,0,0],[255,0,0],[255,0,0],[255,0,0],[255,0,0],[255,0,0],[255,0,0],[255,0,0]],
    "right_eye": null,
    "left_ear": null,
    "right_ear": null,
    "chest": [0, 255, 0],
    "left_foot": null,
    "right_foot": null
  }
}
```

**From Rust, in the runtime**, set the arrays of `robot::Commands` and pass
them to `Robot::command`. The runtime forwards the head LEDs to the head
thread and the chest and foot LEDs to the chest board:

```text
let mut commands = robot::Commands::default();
commands.chest = [0.0, 0.0, 1.0];          // blue
for i in 8..16 {                            // green, every segment
    commands.left_eye[i] = 1.0;
    commands.right_eye[i] = 1.0;
}
robot.command(&commands);
```

**To test the head LEDs** on the robot, with `hulk` stopped:
`naoctl head 10` cycles the eyes through red, green and blue and turns the ears
and the skull on for 10 seconds.

**From the REST API**, with a lease (see [REST API](https://nao-rs.lukaloehr.com/reference/rest-api.md)):

```bash
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "Nao-Lease: $LEASE" \
  -H 'Content-Type: application/json' -d '{"color": "#0055ff"}' \
  http://$ROBOT:7702/v1/leds/eyes
```

It knows the same groups plus `eyes`, `ears` and `feet` for both sides at
once, single LEDs (`/v1/leds/left_eye/0`), presets, and animations computed on
the robot at 20 Hz: `breathe`, `blink`, `rainbow` (not on the ears) and
`progress`. Any LED write to a group cancels its animation, animations end with
the lease, and their frames don't count as a heartbeat. Colours are
`"#rrggbb"`. See [LEDs in the REST API](https://nao-rs.lukaloehr.com/reference/rest-api/leds.md).

## Related topics

- [Hardware reference](https://nao-rs.lukaloehr.com/hardware.md)
- [Chest board link](https://nao-rs.lukaloehr.com/hardware/chest-board.md)
- [Body sensors and units](https://nao-rs.lukaloehr.com/hardware/sensors.md)
- [Head devices](https://nao-rs.lukaloehr.com/hardware/head.md)
- [Head fan](https://nao-rs.lukaloehr.com/hardware/fan.md)
- Previous: [Head devices](https://nao-rs.lukaloehr.com/hardware/head.md)
- Next: [Head fan](https://nao-rs.lukaloehr.com/hardware/fan.md)
