File size: 7,550 Bytes
f8430d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
---
title: Reachy_Mini_Teleop
emoji: 🎮
colorFrom: blue
colorTo: indigo
sdk: static
pinned: false
short_description: Local gamepad puppeteering with independent antenna control.
tags:
- reachy_mini
- reachy_mini_python_app
---

# Reachy Mini Teleop

Local, expressive gamepad control for Reachy Mini Lite. Control the head, body,
height, and both antennas independently. A browser panel shows app/controller
status, switches Precision/Turbo mode, and provides Stop.

The controller connects to this Windows computer. The browser panel is local;
it does not provide remote robot access or camera/audio streaming.

## Start here

1. Power/connect Reachy Mini and open **Reachy Mini Control**. Connect the robot
   and wait until it is ready. Close other apps that command its movements.
2. Connect your Xbox or DualSense controller by USB or Bluetooth. Center the
   sticks and release the triggers before starting.
3. Double-click **start-local.cmd** in this folder. It checks the locked Python
   environment, starts Teleop, and opens **http://127.0.0.1:8042**.
4. Keep the terminal open. Use the gamepad to move the robot.
5. Click **Stop** in the browser, or press **Ctrl+C** in the terminal. Allow about
   three seconds for the neutral return. Motors remain enabled to support the head.

Closing the browser tab does **not** stop Teleop. Force-closing the terminal,
losing power, or a broken daemon connection cannot guarantee a neutral return.
The browser Stop button is a normal software stop, not an emergency hardware stop.

## First-time setup

Requires Windows, Python **3.12**, and [uv](https://docs.astral.sh/uv/getting-started/installation/).
The launcher creates/updates the environment using the committed lock file.
Initial setup needs internet access; a prepared environment can run offline.
No Hugging Face login is required for this local app.

From PowerShell in this folder:

```powershell
uv sync --locked --python 3.12
uv run --no-sync python -m teleop.main --check
```

The check connects to the daemon's WebSocket and reads telemetry. It sends no
motion commands and does not initialize camera/audio. For normal operation:

```powershell
.\start-local.cmd
```

For offline operation after setup, bypass the launcher's environment check:

```powershell
.\.venv\Scripts\python.exe -m teleop.main --open-browser
```

The app also retains its `reachy_mini_apps` entry point for discovery by Reachy
Mini Control. The launcher is the supported Windows workflow tested here.

## Controls

| Input | Action |
|---|---|
| Left stick | Head yaw and pitch relative to the body |
| Right stick horizontal | Body rotation; head follows while preserving relative yaw |
| Right stick vertical | Head height |
| L2 / LT | Left antenna outward |
| R2 / RT | Right antenna outward |
| L1 / LB | Left antenna inward |
| R1 / RB | Right antenna inward |
| D-pad up | Turbo |
| D-pad down | Precision |
| Browser mode switch | Same Precision/Turbo state |
| Browser Stop / terminal Ctrl+C | Smooth neutral return, then exit |

Both antenna channels remain enabled. The SDK receives antennas in **right, left**
order. SDL's controller mapping handles Xbox/DualSense layout differences. Unknown
controllers are rejected rather than assigned guessed button indices; configure
`SDL_GAMECONTROLLERCONFIG` if your controller needs a custom mapping.

The app streams one atomic head/body/antenna target at approximately 60 Hz, with
elapsed-time integration, smoothing, and per-channel speed limits. Turbo doubles
rates but retains pose bounds. Conservative limits are head/body relative yaw
+/-45 degrees, pitch +/-20 degrees, body +/-90 degrees, height -20 to +30 mm, and
antennas +/-60 degrees. Height-dependent yaw/pitch limits narrow near either
height extreme. These are conservative heuristics, **not collision detection**;
the daemon also applies its own limits. Startup begins at measured pose, then
converges gradually into app limits if necessary. Sticks hold the last position
when released; they do not automatically recenter the head.

Controller loss stops the loop and requests a three-second neutral return. A lost
daemon connection stops commands and logs an error; it cannot send a return move.

## Pollen Telepresence or this app?

Pollen's current [Telepresence app](https://huggingface.co/spaces/pollen-robotics/telepresence)
is the easier choice for remote viewing, phone/touch control, robot camera,
and two-way audio. It also supports physical gamepads: its current source reads
whichever stick has the larger deflection and uses that to steer head yaw/pitch,
with body yaw taking over beyond the head's relative yaw limit.

This app remains useful for **local expressive puppeteering**: separate sticks
for head versus body/height, independent trigger/shoulder antenna control,
Precision/Turbo modes, and holding poses when controls are released. The reviewed
Pollen Telepresence code does not provide those antenna/height controls. Its motion
loop is 20 Hz; ours targets 60 Hz with explicit smoothing, but that alone does not
prove smoother hardware movement. An attended comparison is still needed.

We recommend Pollen Telepresence for general remote teleoperation and keeping
this app for the richer local performance controls. There is no need to rebuild
Pollen's camera/audio stack here.

Sources reviewed October 24, 2026:

- [Pollen Telepresence README](https://huggingface.co/spaces/pollen-robotics/telepresence/blob/main/README.md)
- [Gamepad input](https://huggingface.co/spaces/pollen-robotics/telepresence/blob/main/src/components/joystick/useGamepadDeflection.ts)
- [Motion controller](https://huggingface.co/spaces/pollen-robotics/telepresence/blob/main/src/components/joystick/useHeadVelocityControl.ts)
- [Reachy Mini Python SDK](https://huggingface.co/docs/reachy_mini/en/SDK/python-sdk)

## Troubleshooting

- **Zenoh / port 7447:** an old Python environment is being used. This app pins
  SDK 1.11.0 to match the installed daemon. Run the launcher from this folder;
  there is another Teleop copy at `Desktop\teleop` that was not updated.
- **Port 8000 / connection refused:** open Reachy Mini Control and connect the robot.
  The new `--check` command explains missing/unsupported daemon readiness.
- **GStreamer 1.28.8 / cp312 installation error:** Windows media components are
  constrained to the Desktop-compatible 1.28.7 wheels. Use the committed lock
  and Python 3.12; do not install GStreamer independently into this environment.
- **`exclude-newer = "7 days"` warning:** this project's own uv settings isolate
  it from the parent SDK checkout's newer uv configuration format.
- **Waiting for controller:** ensure the pad is connected to the same computer,
  not just the robot or another Bluetooth host. Restart after pairing if needed.
- **Port 8042 already in use:** stop the existing Teleop instance before starting
  another. Do not run two robot-control apps simultaneously.
- **Antenna motion looks choppy:** the earlier hardware issue may still need
  attention. Software keeps both channels available; do not force the mechanism.

The browser status and extension bar show commanded app state, not independently
verified physical movement. Watch the terminal for errors.

## Development checks

```powershell
uv run --no-sync python -m unittest discover -s tests -v
uv run --no-sync python -m teleop.main --check
```

Tests use fake robot/controller input. Physical controls, direction, feel, antenna
behavior, and Stop/disconnect acceptance still require an attended hardware trial.