# TurboPi architecture — repo-to-Pi file mapping

> **One-line rule:** the repo is the source of truth. Never edit files on the Pi directly. Run `./scripts/deploy-turbopi.sh` to push changes.

## Why this doc exists

The TurboPi ships with Hiwonder's vendor tree at `/home/pi/TurboPi/` (Python libs, `lab_config.yaml`, Camera.py, ColorTracking, etc.). During initial bring-up that tree was copied into this repo at `vendor/turbopi/` as a full clone of [Hiwonder's upstream repo](https://github.com/Hiwonder/TurboPi.git). Since then, the two copies have drifted — calibrations were edited on the Pi and never made it back to the repo; the repo's `vendor/` directory is gitignored so nothing under it gets tracked by default.

This doc documents the clean mapping so future sessions don't have to reverse-engineer it.

## Why `vendor/turbopi/` is LOCKED (no `.git/` subdirectory)

During session 53 we discovered that `vendor/turbopi/` was a live clone of Hiwonder's upstream repo — complete with a working `origin` remote pointing at `https://github.com/Hiwonder/TurboPi.git`. That's dangerous in two ways:

1. A stray `git pull` inside `vendor/turbopi/` could fetch new upstream commits and silently overwrite files we care about (like `lab_config.yaml`, which used to live here).
2. The nested repo formed a submodule boundary — the outer repo physically could not track individual files inside `vendor/turbopi/`, because Git treats any directory with its own `.git/` as foreign territory.

**Resolution (session 53):** `rm -rf vendor/turbopi/.git`. The file contents are untouched, but the directory is no longer a git repo. No remotes, no branches, no possibility of a cross-repo pull bleeding upstream changes back in. The directory is a frozen reference snapshot.

If you ever need a fresh copy of Hiwonder's code, `git clone https://github.com/Hiwonder/TurboPi.git` into `/tmp/` or elsewhere — do NOT re-clone into `vendor/turbopi/`. The canonical location for OUR calibration is now `services/turbopi-server/lab_config.yaml`, not `vendor/turbopi/lab_config.yaml`.

## File mapping

| Repo path | Pi path | Tracked in git? | Deploy mechanism |
|---|---|---|---|
| `services/turbopi-server/lab_config.yaml` | `/home/pi/TurboPi/lab_config.yaml` | **Yes** — co-located with turbopi-server, not in vendor tree | `./scripts/deploy-turbopi.sh` |
| `vendor/turbopi/*.py` (Hiwonder libs) | `/home/pi/TurboPi/*.py` | No (vendor code, ignored by `vendor/` rule) | Initial bring-up only; frozen |
| `scripts/lab_calibrate_pi.py` | `/home/pi/bin/lab_calibrate_pi.py` | Yes | `./scripts/deploy-turbopi.sh` |
| `services/turbopi-server/*.py` | `/home/pi/her-os-turbopi-server/` (or wherever turbopi-server.service points) | Yes | (separate turbopi-server deploy, not covered here) |

## Why `lab_config.yaml` lives with `services/turbopi-server/`, not in `vendor/turbopi/`

The obvious-looking choice would be to put `lab_config.yaml` inside `vendor/turbopi/` to mirror its deploy location on the Pi (`/home/pi/TurboPi/lab_config.yaml`). We tried this first in session 53. Two problems blocked it:

1. **`vendor/` is in `.gitignore`** so nothing under it is tracked by default. We tried a negation rule (`!vendor/turbopi/lab_config.yaml`) — it didn't work because of the nested repo issue (see above).
2. **Even after removing the nested `.git/`, keeping the file in `vendor/turbopi/` was conceptually wrong** — that's *vendor* code (frozen Hiwonder reference), and our *calibration* is not. Mixing our live config with their frozen reference creates exactly the kind of drift that got us here in the first place.

**Decision:** our config lives under `services/turbopi-server/lab_config.yaml` (co-located with the server code that cares about it). The deploy script knows how to copy it to the Pi-side path. The vendor tree stays pure reference material.

## Deploy flow

```bash
# After editing vendor/turbopi/lab_config.yaml or scripts/lab_calibrate_pi.py:
./scripts/deploy-turbopi.sh

# Override Pi host (default: pi):
PI_HOST=pi-car ./scripts/deploy-turbopi.sh
```

The script:
1. Backs up the existing `/home/pi/TurboPi/lab_config.yaml` to `lab_config.yaml.<epoch>.bak` before overwrite
2. Rsyncs `vendor/turbopi/lab_config.yaml` → `pi:/home/pi/TurboPi/lab_config.yaml`
3. Ensures `/home/pi/bin/` exists
4. Rsyncs `scripts/lab_calibrate_pi.py` → `pi:/home/pi/bin/lab_calibrate_pi.py` (+ chmod +x)

Note: **turbopi-server itself has a separate deploy path** and is not covered by this script. Editing its code requires a restart: `ssh pi "sudo systemctl restart turbopi-server"`.

## Calibration workflow

```bash
# 1. Stop turbopi-server so /dev/video0 is free
ssh pi "sudo systemctl stop turbopi-server"

# 2. Run the calibration tool on the Pi (now at a durable path)
ssh pi "/home/pi/bin/lab_calibrate_pi.py --grab-only --grab-path /tmp/frame.jpg"

# 3. Pull the frame, run the web UI, user drags bbox, compute stats
#    (see scripts/lab_calibrate_webui.py)

# 4. Sample LAB stats from bbox, update vendor/turbopi/lab_config.yaml in the repo
#    (do NOT edit /home/pi/TurboPi/lab_config.yaml on the Pi directly)

# 5. Deploy the updated yaml back to the Pi
./scripts/deploy-turbopi.sh

# 6. Restart the server
ssh pi "sudo systemctl start turbopi-server"
```

## Known hardware gotchas

See memory file `project_turbopi_camera_gotchas.md` for:
- OpenCV `CAP_PROP_SATURATION` clamps to max on the UVC backend (don't use OpenCV to set v4l2 controls; use `v4l2-ctl` via subprocess)
- Ultrasonic sensor's blue power LED biases auto white balance (calibrate under runtime conditions, don't try to "fix" the resulting pink cast with WB tweaks)
- Pi `/tmp` wipes on reboot (moot now that deploy-turbopi.sh installs to `/home/pi/bin/`)
