# Saqr PPE Detection — Deployment Guide ## Unitree G1 Robot + Intel RealSense D435I --- ## Robot Details | Item | Value | |------|-------| | Robot | Unitree G1 Humanoid | | IP | `192.168.123.164` | | User | `unitree` | | OS | Ubuntu 20.04 (aarch64 / Jetson) | | Python | 3.10 (conda env: `marcus`, legacy: `teleimager`) | | Camera | Intel RealSense D435I | | Serial | `243622073459` | | Port | USB 3.2 @ `/dev/video0` | --- ## Repo layout ``` saqr/ # python package (core/apps/gui/robot/utils) scripts/ # deploy.sh, start_saqr.sh, run_local.sh, run_robot.sh, saqr-bridge.service config/ # logging.json data/ # dataset/, models/ (gitignored) runtime/ # captures/, logs/, runs/ (gitignored) docs/ # this file, start.md, use_case_catalogue.pdf pyproject.toml # installs the `saqr`, `saqr-bridge`, `saqr-gui`, ... scripts ``` --- ## Step 1: Train the Model (Dev Machine) ```bash cd ~/Robotics_workspace/yslootahtech/Project/Saqr conda activate AI_MSI_yolo pip install -e . saqr-train --dataset data/dataset --epochs 100 --batch 16 ``` Verify model exists: ```bash ls -lh data/models/saqr_best.pt # Expected: ~5.3 MB ``` --- ## Step 2: Deploy to Robot (Dev Machine) ```bash # From the project root: scripts/deploy.sh # rsync the tree + pip install -e scripts/deploy.sh --run # ...and start the bridge scripts/deploy.sh --ip 10.0.0.5 # custom robot IP ``` The script rsyncs `saqr/`, `scripts/`, `config/`, `docs/`, `pyproject.toml`, `requirements.txt`, and `README.md` to `~/Saqr` on the robot, then runs `pip install -e .` inside the target conda env (default `marcus`). --- ## Step 3: Install Dependencies (Robot, one-time) If `scripts/deploy.sh` ran `pip install -e .` successfully, you're done. Otherwise, on the robot: ```bash ssh unitree@192.168.123.164 source ~/miniconda3/etc/profile.d/conda.sh conda activate marcus cd ~/Saqr pip install -e . ``` ### Jetson GPU PyTorch (JetPack 5.1 / CUDA 11.4) ```bash pip uninstall torch torchvision -y pip install --no-cache-dir \ https://developer.download.nvidia.com/compute/redist/jp/v51/pytorch/torch-2.1.0a0+41361538.nv23.06-cp310-cp310-linux_aarch64.whl pip install --no-cache-dir \ https://developer.download.nvidia.com/compute/redist/jp/v51/pytorch/torchvision-0.16.1a0+5e8e2f1-cp310-cp310-linux_aarch64.whl ``` ### System clock (pip/SSL need a correct date) ```bash sudo date -s "2026-04-10 15:00:00" ``` ### Verify ```bash python -c "from ultralytics import YOLO; print('ultralytics OK')" python -c "import torch; print('CUDA:', torch.cuda.is_available())" python -c "import cv2; print('opencv OK')" python -c "import saqr; print('saqr', saqr.__version__)" ``` --- ## Step 4: Run Saqr (Robot) ### Production: bridge with R2+X / R2+Y The bridge owns the DDS clients and spawns `saqr` on demand. On the robot: ```bash cd ~/Saqr scripts/start_saqr.sh # manual launch sudo systemctl restart saqr-bridge # systemd-managed (see start.md) ``` Or without the helper script: ```bash conda activate marcus python -m saqr.robot.bridge --iface eth0 --source realsense --headless -- --stream 8080 ``` ### Plain saqr (no bridge) ```bash # With display scripts/run_robot.sh --stream 8080 # Headless scripts/run_robot.sh --headless --stream 8080 # V4L2 fallback if RealSense SDK won't enumerate scripts/run_robot.sh --source /dev/video2 --headless ``` Equivalent `python -m` forms: ```bash python -m saqr.apps.saqr_cli --source realsense --model saqr_best.pt --headless python -m saqr.apps.detect_cli --source /dev/video2 --model saqr_best.pt python -m saqr.apps.manager_cli --export ``` ### Dev machine GUI ```bash pip install -e ".[gui]" python -m saqr.gui.app --source 0 ``` --- ## Bridge behavior (R2+X / R2+Y) | Press | Action | |-------|--------| | **R2 + X** | Start saqr subprocess. Robot stays silent (no "Saqr activated." — see note below). | | **R2 + Y** | Stop saqr, robot says **"Saqr deactivated."** Bridge stays ready. | | **Ctrl+C** in terminal | Stop saqr (if running) and exit the bridge. | ### Per-detection behavior (while saqr is running) | Transition | TTS (speaker_id=2, English) | Arm action | |------------|-----------------------------|------------| | → UNSAFE | "Please stop. Wear your proper safety equipment. You are missing **{items}**." | `reject` (id=13) + auto `release arm` | | → SAFE | "Safe to enter. Have a good day." | — | | → PARTIAL | — | — | Bridge startup announces **"Saqr is running. Press R2 plus X to start."**. > **Note on speaker_id=2:** TTS speaker_ids are locked to a language on the > current G1 firmware. `speaker_id=0` is Chinese regardless of input text. > `speaker_id=2` was confirmed English via `Project/Sanad/voice_example.py 6`. > **Note on SIGINT traceback:** when R2+Y stops saqr, Python may print a > `KeyboardInterrupt` traceback from inside YOLO. The bridge catches the exit, > announces "Saqr deactivated.", and stays alive. --- ## Bridge CLI flags | Flag | Default | Description | |------|---------|-------------| | `--iface` | *(default DDS)* | DDS network interface, e.g. `eth0` | | `--timeout` | `10.0` | Arm/Audio/LowState client timeout (seconds) | | `--cooldown` | `8.0` | Per-(track_id, status) seconds before re-triggering TTS/arm | | `--release-after` | `2.0` | Seconds before auto `release arm` (0 = never) | | `--speaker-id` | `2` | G1 `TtsMaker` speaker_id | | `--dry-run` | off | Parse events but never call the SDK; implies `--no-trigger` | | `--no-trigger` | off | Skip the R2+X/R2+Y loop and start saqr immediately | | `--source` / `--headless` / `--saqr-conf` / `--imgsz` / `--device` | — | Passed through to saqr | | `-- ` | — | Everything after `--` is forwarded raw to saqr (e.g. `-- --stream 8080`) | --- ## Step 5: Check Results ```bash cat runtime/captures/result.csv # current state per tracked person cat runtime/captures/events.csv # audit log ls runtime/captures/{SAFE,PARTIAL,UNSAFE}/ saqr-manager --export # quick CSV export # Download to dev machine scp -r unitree@192.168.123.164:~/Saqr/runtime/captures ./captures_from_robot ``` --- ## Camera sources | Source | Flag | Notes | |--------|------|-------| | RealSense SDK | `--source realsense` | Uses `pyrealsense2`; preferred | | RealSense by serial | `--source realsense:243622073459` | | | V4L2 RGB | `--source /dev/video2` | OpenCV fallback; pure MJPG | | First OpenCV camera | `--source 0` | | | Video file | `--source path.mp4` | | G1 V4L2 map for the D435I: ``` /dev/video0 - Stereo module (infrared) — won't open with OpenCV /dev/video1 - Stereo metadata /dev/video2 - RGB camera (640x480) ← USE THIS /dev/video3 - RGB metadata /dev/video4 - RGB camera (secondary stream) ``` --- ## Tuning parameters | Parameter | Flag | Default | |-----------|------|---------| | Confidence | `--conf` | 0.35 | | Max missing frames | `--max-missing` | 90 | | Match distance (px) | `--match-distance` | 250 | | Confirm frames | `--status-confirm-frames` | 5 | Recommended for G1 patrol: ```bash saqr --source realsense --headless \ --conf 0.30 --max-missing 120 --match-distance 300 --status-confirm-frames 7 ``` --- ## Compliance rules | Status | Condition | Colour | |--------|-----------|--------| | SAFE | Helmet AND vest, no violations | Green | | PARTIAL | Only helmet OR only vest | Yellow | | UNSAFE | `no-helmet` or `no-vest`, or nothing detected | Red | --- ## Output files | File | Location | Description | |------|----------|-------------| | `result.csv` | `runtime/captures/` | Current state of tracked persons | | `events.csv` | `runtime/captures/` | Audit log (NEW / STATUS_CHANGE) | | Person crops | `runtime/captures/{SAFE,PARTIAL,UNSAFE}/*.jpg` | Latest crop per track | | Logs | `runtime/logs/Inference/*.log` | Module log output | --- ## Source map | Path | Purpose | |------|---------| | [saqr/apps/saqr_cli.py](../saqr/apps/saqr_cli.py) | Main PPE tracking entry (`saqr`) | | [saqr/robot/bridge.py](../saqr/robot/bridge.py) | Saqr → G1 bridge (R2+X/R2+Y) | | [saqr/robot/robot_controller.py](../saqr/robot/robot_controller.py) | G1 arm + audio + TTS worker | | [saqr/robot/controller.py](../saqr/robot/controller.py) | G1 wireless-remote DDS reader | | [saqr/core/pipeline.py](../saqr/core/pipeline.py) | Per-frame detect + track + emit | | [saqr/core/tracking.py](../saqr/core/tracking.py) | `PersonTracker`, `Track` | | [saqr/core/events.py](../saqr/core/events.py) | Event-line format (contract with bridge) | | [saqr/apps/detect_cli.py](../saqr/apps/detect_cli.py) | Simple detection without tracking | | [saqr/apps/train_cli.py](../saqr/apps/train_cli.py) | YOLO11n training | | [saqr/apps/manager_cli.py](../saqr/apps/manager_cli.py) | Capture CRUD + CSV export | | [saqr/gui/app.py](../saqr/gui/app.py) | PySide6 desktop GUI | --- ## Troubleshooting ### RealSense not detected ```bash lsusb | grep Intel rs-enumerate-devices | head -10 sudo usbreset /dev/bus/usb/002/002 # if the USB is stuck ``` ### ModuleNotFoundError: saqr ```bash # Make sure you're in the right env and the package is installed which python pip install -e ~/Saqr ``` ### System clock wrong (SSL errors) ```bash sudo date -s "2026-04-10 15:00:00" ``` ### Model not found ```bash ls ~/Saqr/data/models/ # should list saqr_best.pt ``` ### Too many duplicate track IDs ```bash saqr --source realsense --max-missing 150 --match-distance 300 --headless ```