# naowalk

A keyboard teleop client for the NAO robot: live video, walking, head control,
two-way audio (hear the robot's mics, make it speak via TTS), and a YouTube
music player that downloads a track with `yt-dlp` and plays it through the
robot's own speakers.

## Requirements

**System packages** (Debian/Ubuntu names — adjust for your OS):

```bash
sudo apt install portaudio19-dev ffmpeg sshpass openssh-client
```

- `portaudio19-dev` — needed to build/install `pyaudio`
- `ffmpeg` — needed by `yt-dlp` to extract audio to mp3
- `sshpass` + `openssh-client` — used to `scp` downloaded tracks onto the
  robot and to run `mkdir` over `ssh` (the script assumes the default
  `nao`/`nao` credentials)

**NAOqi Python SDK (`qi`)** — not on PyPI. Download the `pynaoqi-python`
wheel that matches your OS/Python version from the Aldebaran/SoftBank
Robotics developer site and install it:

```bash
pip install ./pynaoqi-*.whl
```

**Python packages:**

```bash
pip install -r requirements.txt
```

## Usage

```bash
python3 naowalk.py --ip <robot-ip> [options]
```

| Flag | Default | Description |
|---|---|---|
| `--ip` | `127.0.0.1` | Robot's IP address |
| `--port` | `9559` | NAOqi port |
| `--audio-device-index` | auto | Output device index for hearing the robot's mic on your machine |
| `--mic-channel` | `left` | Which robot mic to stream (`left`, `right`, `front`, `rear`) — pick a working one, see Troubleshooting |
| `--mic-gain` | `8.0` | Amplification applied to the streamed mic audio |
| `--video-scale` | `2.0` | Display window scale (base feed is 320x240) |

## Controls

| Key | Action |
|---|---|
| `W`/`A`/`S`/`D` or arrows | Walk forward/turn/back |
| `I`/`K`/`J`/`L` | Head pitch/yaw |
| `Space` | Reset head to center |
| `1` | Wave gesture |
| `M` | Open music search (type query, `Enter` to search, `↑↓` to select, `Enter`/`1`-`5` to play) |
| `P` | Pause / resume music (resume restarts the track from the beginning — see Notes) |
| `X` | Stop music |
| `-` / `=` | Volume down/up |
| `T` | Type a message for the robot to speak (TTS) |
| `Esc` | Quit (or cancel current mode) |

## How it works

- **`SoundReceiver`** — registered as a NAOqi service that receives the
  robot's mic stream and plays it out your local speakers via `pyaudio`, with
  gain and a prebuffer to smooth out network jitter.
- **`NaoMusicPlayer`** — searches YouTube with `yt-dlp`, downloads and
  converts the top match to mp3, `scp`s it to the robot, and plays it with
  `ALAudioPlayer`.
- **`NaoTeleop`** — the main loop: pygame window for video/input, keyboard
  handling, head/walk control, and wiring everything above together.

## Notes / design decisions

- **No seek.** Seeking was removed entirely — it added the most complexity
  for the least-used feature. `P` is a simple play/stop toggle; resuming
  restarts the track from the beginning rather than tracking a playback
  position.
- **`moveInit()` on startup.** The robot used to need its walk buttons
  jittered for a few seconds before its first walk would work. `run()` now
  calls `ALMotionProxy::moveInit()` once at startup, which settles the walk
  engine into the right pose before any real move command — this is the
  documented NAOqi fix for exactly this symptom.
- **`_safe()` helper.** Most one-off NAOqi calls (setting angles, volume,
  stopping movement, unsubscribing services) are wrapped in a small
  `self._safe(label, fn)` helper instead of a repeated `try/except` block, so
  one failed call logs and moves on without crashing the teleop loop or the
  shutdown sequence.

## Troubleshooting

- **Mic sounds dead / silent on one channel:** some robots have one or more
  physical mics that produce no signal. Test all four with a quick energy
  check, then pick a working channel with `--mic-channel`.
- **Robot falls over on the very first walk command:** make sure you're on a
  build that includes the `moveInit()` startup call described above; if it's
  still happening, increase the `time.sleep(1.0)` after `goToPosture` to give
  the posture more time to settle first.
- **`scp`/`ssh` failures for music:** confirm the robot is reachable at
  `--ip` and that `nao`/`nao` are still the correct credentials; the script
  hardcodes them via `sshpass`.
