move_detect/docs/hardware-bringup.md
유창욱 c3227c179c fix(security,edge,test): close the confirmed audit backlog; add the guards that prevent recurrence
감사에서 확정된 잔여 항목을 일괄 처리. 이전 커밋이 증상(스테일 번들)을 고쳤다면 이번에는
그것이 조용히 지나가게 만든 구조를 고친다.

보안
- CSP 도입. 인라인 2블록에 per-request nonce, unsafe-inline 없음. 빌드가 없다는 제약이
  nonce를 막지는 않는다. 보안 헤더를 .htaccess 단독에서 PHP로 옮겨 개발/납품이 같아진다.
- /logout에 CSRF. SameSite=Lax가 막아주지만, 사고 대응 중 운영자를 콘솔에서 튕겨내는 건
  이 제품에선 실제 가용성 공격이라 예외로 둘 이유가 없다.
- 잠긴 계정 423 → 401. 아무나 이름을 추측해 5회 실패로 잠근 뒤 423을 계정 실재 확인으로
  쓸 수 있었다. 잠금 사실은 감사 로그에 남고, 익명 호출자는 못 읽는다.

엣지
- 큐 flush에 회당 상한. 정전 복구 직후 2000건을 비우는 동안 감지가 멈추던 구간을 없앤다.
- 타이머 env 전수 검증. COOLDOWN_S=nan은 dedup을 무력화해 매 틱 발보시켰다.
- T2 프레임 스킵(INFER_EVERY). 스킵 틱은 프레임을 읽어 버퍼만 비우고 링버퍼에 넣지 않는다 —
  추론이 없으면 얼굴 박스도 없어 그대로 넣으면 증거 스냅샷에 미블러 얼굴이 실린다.
  T3(모션 게이트)는 의도적으로 미구현: 게이트가 틀리면 '움직이지 않는 쓰러진 작업자'에게
  추론이 안 돈다. 실영상 없이 임계값을 정할 수 없다.
- systemd 워치독(sd_notify) + 메모리 상한. Restart=always는 죽은 프로세스만 잡고
  '살아 있는데 멈춘' 상태는 못 잡는다. UVC 해상도·버퍼 고정(링버퍼 RSS가 웹캠 기본값에 좌우).

테스트 — 감사의 최대 갭
- PDO 통합 테스트가 pdo_sqlite 부재로 통째로 스킵되고 CI도 없어 사실상 한 번도 안 돌았다.
  `-d extension=pdo_sqlite`로 되살리고 리포트·히트맵·익스포트 집계 SQL을 최초로 실행한다.
- 얼굴 블러 테스트가 out is not None만 봤다 — blur()가 무동작이어도 통과. PIPA 대응의
  유일한 실행 가능한 증거였는데 아무것도 증명하지 못했다. 디테일 파괴를 단언하도록 교체.
- index.php 라우트 테이블 대조(프런트 호출 15개 교차검증), dist↔소스 패리티 테스트,
  CI 신설(스킵이 하나라도 있으면 실패).
- ArraySessionStore.regenerate()가 데이터를 지워 실제 session_regenerate_id(true)와
  달랐다. 더블이 실구현과 어긋난 자리 하나를 실제로 잡았다.

프런트
- 이벤트 목록 시그니처 재렌더 스킵 + 포커스 복원. aria-live 영역을 5초마다 통째로 다시
  만들어 스크린리더가 200행을 재낭독하고 키보드 포커스가 body로 날아갔다. 같은 패턴이
  device-tree.js에는 이미 있었다.
- 폴링 중첩 가드(느린 공유호스팅에서 응답 역전), 콜드스타트 실패 표시.

납품
- VERSION 신설, 두 산출물에 version/git_commit/built_at 스탬프. 스테일 번들의 SHA256SUMS는
  자기정합적이라 통과한다 — provenance 없이는 하류에서 탐지할 방법이 없다.
- deploy/README·env.example을 Hailo 단독 번들 실제와 일치(템플릿이 없는 ONNX를 가리켰다).
- 이벤트 보존 정책은 기본 0(영구). 산업안전 기록을 시스템이 임의로 지우지 않는다.

검증: pytest 251 / PHPUnit 162, 스킵 0. CSP nonce 결합은 실제 렌더 경로로 확인.
브라우저 실렌더는 MySQL이 필요해 이 환경에서 미수행.
2026-08-07 23:31:53 +09:00

157 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Hardware Bring-up & Benchmark Procedure (Raspberry Pi 5 + Hailo-8L)
This document describes the validation procedure for the edge detection core on actual hardware (Pi 5 + Hailo accelerator). Development PC testing is covered in the main test suite; this procedure applies to the Pi deployment phase.
## 1. PCIe Gen3 Activation
Enable PCIe Gen3 for Hailo-8L optimal throughput:
1. SSH to Pi 5 and edit `/boot/firmware/config.txt`:
```bash
sudo nano /boot/firmware/config.txt
```
2. Add or update the line:
```
dtparam=pciex1_gen=3
```
3. Reboot:
```bash
sudo reboot
```
4. Verify PCIe Gen3 negotiation:
```bash
lspci -vv | grep -i speed
```
Expected output: `LnkSta: Speed 8GT/s` (Gen3) or higher, not 2.5GT/s (Gen1).
---
## 2. HEF Benchmark: Single, 2-Stream, 4-Stream
Benchmark `yolov8s_pose.hef` (fall detection backbone) under different load scenarios:
1. **Single-stream baseline:**
- Load HEF via HailoRT VDevice, run 1 stream continuously (640×640 RGB input).
- Measure: FPS, CPU usage, Hailo die temperature.
- Target: ≥ 20 FPS @ <60°C (baseline headroom).
2. **2-stream round-robin:**
- Process 2 video streams (CSI + USB UVC) round-robin into Hailo.
- Measure: Combined FPS per stream, CPU, temperature.
- Target: ≥ 10 FPS per stream (full fall detection at 2 streams).
3. **4-stream round-robin (stress test):**
- Process 4 USB UVC streams into Hailo round-robin.
- Measure: FPS per stream, CPU, temperature, memory.
- Target: ≥ 7–8 FPS per stream (performance envelope; may require USB controller separation).
**Benchmark tooling:**
- Use `hailo-benchmark` or custom Python loop with HailoRT + OpenCV frame capture.
- Log FPS, CPU (top/mpstat), temperature (vcgencmd measure_temp).
- Store results in `docs/benchmarks/YYYY-MM-DD-pi5-hef-results.txt`.
---
## 3. FPS 3-Tier Validation
Measure frame processing under three operational modes:
| Tier | Mode | Description | Target FPS | Notes |
|------|------|-------------|-----------|-------|
| **T1: Full** | Detection on all frames | Hailo inference on every frame; maximum load. | ≥ 12 fps (crucial: fall detection responsiveness) | For 1–2 primary cameras (fall risk zones). Limits concurrent streams. |
| **T2: Detection** | Inference every 2–3 frames | Trade-off: skip frames to reduce Hailo load. | ≥ 8 fps | For additional context cameras; acceptable for non-critical zones. |
| **T3: Motion** | Hailo only on motion keyframes | Inference triggered by motion (v4l2 MJPEG keyframe or frame diff). | ≥ 5 fps avg | For background monitoring; many cameras, low overhead. |
1. Tier selection is `INFER_EVERY` in `/etc/move-detect/edge.env` (per device):
`1` = T1 (every tick, the default), `2`–`3` = T2. On a skipped tick the frame is still read
and discarded — the camera buffer stays current — but no pose runs and nothing enters the
evidence ring, so no unblurred frame can reach a snapshot.
**T3 (motion-gated inference) is deliberately NOT implemented.** A motion gate that misjudges
would stop inferring on a *motionless fallen worker* — the exact case this product exists to
catch — and its threshold cannot be tuned without real footage. Decide it during bring-up
with measured video, not in advance.
2. Run each tier with realistic camera setup (e.g., T1 on main fall cam, T2 on perimeter).
3. Log FPS, E2E latency (frame capture → alert), CPU, Hailo temp for 30 min per tier.
4. Confirm T1 ≥ 12 fps for the target scene (well-lit, medium activity).
---
## 4. Multi-Camera Stability (Long-run Test)
Validate sustained operation and resource cleanup:
1. **Setup:**
- 1 CSI camera (picamera2 via PiCameraSource) + 2–4 USB UVC cameras (UvcSource).
- Route USB cameras to 2 separate USB3 controllers (Pi 5 has 2 controllers; check with `lsusb -t`).
2. **Test duration:** 4–8 hours continuous frame capture and optional Hailo inference.
3. **Monitor:**
- FPS stability (no drift or sudden drops).
- Memory usage (RSS, no unbounded growth; confirm video buffer cleanup).
- CPU distribution (no single core maxed; picamera2 process isolation working).
- Hailo temperature (sustained < 75°C).
- Error logs (no hang or exception cascades).
4. **Pass criterion:** No crashes, memory leaks, or FPS degradation after 4 hours.
---
## 5. Reflect Results in Thresholds & Camera Roles
After benchmark completion, update configuration:
Every knob below lives in `/etc/move-detect/edge.env` (read by `edge/config.py::load_config`),
**not** in a code file — no redeploy is needed to retune a site. All are validated at startup:
a non-finite or non-positive value refuses to boot rather than silently disabling a detector.
The full list is in `docs/pilot-runbook.md` §3.
1. **Detector thresholds:**
- `MOTION_THR` (default 0.25) — per-second stillness threshold shared by fall no-motion and
immobility. This is the primary re-tuning knob on real video: it is frame-rate normalised,
so a device running at a different measured fps needs it re-checked.
- `FALL_PRONE_S` (3.0) / `FALL_NO_MOTION_S` (10.0) — how long prone before a fall confirms,
and before it escalates to the 위급 no-motion event. Raise if real footage over-triggers.
- `IMMOBILE_S` (120.0), `GHOST_REAPPEAR_S` (10.0), `ZONE_DEBOUNCE_S` (0.75), `COOLDOWN_S` (30.0).
2. **Per-camera load:**
- `INFER_EVERY` (1) — 1 for the fall-critical camera, 2–3 for context cameras that missed
the T1 target. Set per device; each camera on a device shares it today.
- `TARGET_FPS` (12) — loop pacing. It only slows the loop down; it cannot buy throughput.
3. **Benchmark report:**
- Document actual Pi hardware, kernel version, HailoRT version, Hailo FW version.
- Archive FPS/latency/temp results.
- Flag any anomalies (thermal throttling, USB contention, CPU saturation).
---
## Troubleshooting
**PCIe speed not Gen3:**
- Confirm cable seating and power to Hailo.
- Check firmware update: `sudo rpi-update` (experimental; use with caution).
**HEF FPS < target:**
- Verify HailoRT version matches Hailo FW.
- Check for CPU threads contention (picamera2 process + Python inference + OS).
- Monitor Hailo die temperature; throttle if > 85°C.
**Memory leak in USB capture:**
- Ensure OpenCV's `VideoCapture.release()` is called on cleanup.
- Check `v4l2-ctl -D` for buffer count and memory mapping.
**USB controller contention:**
- Separate high-bandwidth cameras (USB3 MJPEG) across 2 controllers.
- Use `lsusb -t` and `/sys/bus/usb/devices/` to map.
---
## References
- [hailo-rpi5-examples](https://github.com/hailo-ai/hailo-rpi5-examples) — Hailo + Pi 5 reference (MIT).
- [picamera2 docs](https://datasheets.raspberrypi.com/camera/picamera2-manual.pdf).
- [Raspberry Pi 5 datasheet](https://datasheets.raspberrypi.com/rpi5/raspberry-pi-5-datasheet.pdf).