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

# Safety model

> How nao-rs stops the motors: a latched death switch checked before every frame, a guard process that takes over a hung runtime, the triggers, and the chest board's own hard power-off.

nao-rs has one rule for the motors: **when the death switch is tripped, every
frame to the chest board is all-off, whatever the controller asked for.**
All-off means stiffness 0 on every joint. The switch is latched: once tripped,
it stays tripped until a person re-arms it on purpose.

## Principles

These rules bind every part of nao-rs:

1. **The board firmware is never touched.** The chest board, motor board and
   foot board firmware stays Aldebaran's. nao-rs flashes nothing and writes
   no configuration or EEPROM data stored on the boards. It only reads what
   HAL reads and sends what HAL sends.
2. **Everything above the firmware is rewritten and proven offline first.**
   Frames must be byte-identical to HAL's, against Aldebaran's original
   libraries and against recordings of the real robot, before they reach a
   board. See [Verification](https://nao-rs.lukaloehr.com/verification.md).
3. **No motion without a person present during bring-up.** The first
   movements happen with someone at the robot, the robot lying down, one
   joint at a time and at low stiffness.
4. **No command bypasses the death switch.** No API, lease or controller can
   override a trip or re-arm the switch; only a person on the robot can.

## Four layers

Four layers make the all-off rule hold even when parts of the software fail.

| Layer | What it does | When it acts |
| --- | --- | --- |
| 1. The latch in the runtime | One atomic load right before every frame. Tripped means the frame is all-off | Every cycle, about every 12 ms |
| 2. The guard process | Watches the runtime's heartbeat. Kills a hung runtime, takes the chest board over and sends all-off itself | Heartbeat older than 60 ms, or a trip not confirmed within 40 ms |
| 3. The triggers | Chest button, all three head pads, overtemperature, cycle overrun, the UDP stop, the API, the CLI | When they fire, they trip the latch |
| 4. The chest board's firmware | Holding the chest button for 8 seconds switches the whole robot off | Always: it doesn't depend on the head computer |

> **Warning:** All-off means limp
>
> A tripped switch removes all stiffness at once. A standing robot falls. The
> death switch protects people and the robot from motors that keep pushing; it
> is not a way to sit the robot down.

## Layer 1: the latch

The switch is a small shared memory page at `/run/nao-rs/deathswitch`. Every
process on the robot that can stop the motors maps it: the runtime inside
`hulk`, the guard and `naoctl`. The page holds only atomic values, so no reader
ever sees half a write and a crashed writer can't leave a lock behind.

- **Fresh pages start tripped.** `/run` is cleared at every boot, so every boot
  starts with the motors off until someone re-arms.
- **The first reason wins.** A second trip doesn't overwrite why the switch was
  tripped.
- **Stopping the runtime trips it.** When the runtime shuts down, it trips the
  switch before its cycle ends, so no motor stays stiff without a cycle.
- **A damaged page is a stop.** If the page file is truncated, zeroed,
  overwritten, deleted or replaced, the runtime sends all-off from that frame
  on and the switch trips with `PageLost`
  ([details](https://nao-rs.lukaloehr.com/safety/death-switch.md#page-integrity)).
- **Re-arming is a person's decision.** Only `naoctl rearm` re-arms. No timeout,
  no controller and no API message does.
- **Re-arming never resumes motion.** Whatever tripped the switch (a gesture,
  the app, an API, UDP, `naoctl`, heat or a fault), the robot program drops to
  its unstiff state while the switch is tripped. After a re-arm the robot stays
  limp until the app or an API wakes it.

### HAL's safety layer in every frame

Below the death switch, the runtime applies HAL's own stiffness safety to
every frame, ported 1:1 and checked against Aldebaran's original libraries.
It only ever lowers stiffness:

- **Controller lost.** When the motion code hasn't sent a command for 300 ms
  (25 cycles), the stiffness of every joint ramps down to 0 in 500 cycles
  (6 s at the runtime's 12 ms; HAL's 10 ms cycle made it 5 s), and back up in
  200 cycles once commands arrive again.
- **Cycle stall.** When more than 100 ms pass between two cycles, every
  stiffness goes to 0 at once and stays there for 1 s, then ramps back up.
- **Battery over-current.** While the battery current is above 4.5 A, the
  stiffness is lowered step by step, down to 24 %, and recovers once the
  current is back under the limit.

The page is not in `/dev/shm`: logind deletes a user's files there when the
user's last session ends, and a process opening the path afterwards would get a
new page the running runtime never sees.

## Layer 2: the guard

`nao-rs-guard` is a separate process, started by systemd before `hulk`, at
real-time priority 40 (the runtime's cycle runs at 30). It allocates nothing in
its loop and looks at the page every 2 ms. It takes over when:

- **the heartbeat stops**: the runtime's last beat is more than 60 ms old
  (hang, crash, deadlock); or
- **a trip is not obeyed**: the switch is tripped and the runtime hasn't
  confirmed an all-off frame within 40 ms.

Taking over means, in this order:

1. trip the switch, so a runtime that wakes up again sends all-off as well;
2. kill the runtime's process with `SIGKILL`, which makes the kernel release
   the chest board;
3. open the chest board itself and send stiffness 0 to both motor buses from
   the first frame, without the start-up handshake;
4. keep sending all-off every cycle until a person re-arms, or until a
   restarted runtime registers.

**Restarts need no re-arm.** systemd restarts `hulk` one second after it
fails, also after the guard killed it. The new runtime registers in the page
and waits up to 5 s for the chest board; the guard hands the board back,
forgets the old heartbeat and watches the new runtime as before. The switch
stays tripped through the handover, so the new runtime sends all-off too until
a person re-arms. After a re-arm with no runtime running, the guard releases
the board as well.

On the robot, a frozen `hulk` was detected and taken over in 62 ms, and the
guard's first all-off frame went out 9 ms after the kill. That the motor
boards obey the guard's frames is checked in the motor session (**planned**),
because nao-rs doesn't drive the motors yet.

The guard also receives the UDP stop (see below). It does nothing while no
runtime has ever beaten since boot or since its last release: then nothing
sends frames, so no motor can be stiff.

## Layer 3: the triggers

| Trigger | Where it is seen | Reason recorded |
| --- | --- | --- |
| Chest button pressed | Runtime, every cycle. It fires on the press, so a button held at start-up doesn't hide a later press | `ChestButton` |
| All three head pads touched at once | Runtime, from the head bus. Two pads don't trip | `HeadTouch` |
| A joint at 75 °C or more | Runtime, from the motor boards' temperatures | `Overtemperature` |
| CPU above 95 °C | Fan thread, before it halts the system | `Overtemperature` |
| Cycle overrun: more than 3 periods missed | Runtime | `DriverFault` |
| A failed exchange with the chest board | Runtime | `DriverFault` |
| The runtime stops | Runtime | `DriverFault` |
| UDP datagram starting with `NAO-RS STOP` on port 7701 | Guard | `Udp` |
| REST API `POST /v1/safety/stop`, the WebSocket `stop`, the MessagePack API's `Stop` and the brain's `stop` tool | The page directly, and the UDP stop | `Api` (`Udp` only if the page could not be written) |
| `naoctl stop` | The page directly | `Cli` |
| The page file truncated, zeroed, overwritten, deleted or replaced | Runtime before every frame, guard before every poll | `PageLost` |
| Heartbeat lost | Guard | `GuardHeartbeatLost` |
| Trip not confirmed | Guard | `GuardNoConfirmation` |

### Gestures

| Gesture | Effect |
| --- | --- |
| Chest button, one press | **Stop**: the death switch trips and latches, all motors limp. The robot then says its IP address and how to connect, in English or German ([What the robot says](https://nao-rs.lukaloehr.com/get-started/voice.md)) |
| Chest button, held 8 s | Hard power-off by the chest board's firmware (layer 4) |
| All three head pads at once | Stop, like the chest button |
| Waking the robot | Only from the app or an API (`Power` wake), never by a gesture, and never by a re-arm |
| Re-arming after a stop | Only `naoctl rearm` on the robot, after a person has checked it. The robot stays limp until it is woken |

The UDP stop has **no authentication on purpose**: anyone on the robot's
network must be able to stop it, even when the API or the runtime is stuck.
From a computer with a checkout of the nao-rs repository:

```bash
cargo nao-rs stop <robot-address>
```

It sends the datagram ten times, 5 ms apart; one is enough. Any tool that can
send UDP works too, for example `printf 'NAO-RS STOP' | nc -u -w1 <robot-address> 7701`.

## Layer 4: the hard power-off

Holding the chest button for **8 seconds** switches the robot off completely:
motors, LEDs, fan and head. The chest board's firmware does this, so it works
whatever runs or hangs on the head computer. It was tested on 2026-11-06 with no
software reading the chest board. Pressing the button also trips the death
switch at once, so in practice the motors are off long before the 8 seconds
are up.

## Read and re-arm

On the robot:

```bash
naoctl status   # armed or tripped, why, since when; the runtime's heartbeat
naoctl stop     # trip it
naoctl rearm    # re-arm: the robot may be woken again
```

Re-arm only after you have checked the robot and its surroundings. The robot
stays limp after the re-arm; wake it from the app or an API when you want it to
move. A gesture never wakes it. [The death switch in detail](https://nao-rs.lukaloehr.com/safety/death-switch.md) covers
the page layout, the heartbeat and the guard's rules.

## Related topics

- [The death switch in detail](https://nao-rs.lukaloehr.com/safety/death-switch.md)
- [Temperatures, fan and thermal protection](https://nao-rs.lukaloehr.com/safety/thermal.md)
- Previous: [What the robot says](https://nao-rs.lukaloehr.com/get-started/voice.md)
- Next: [The death switch in detail](https://nao-rs.lukaloehr.com/safety/death-switch.md)
