감사에서 확정된 잔여 항목을 일괄 처리. 이전 커밋이 증상(스테일 번들)을 고쳤다면 이번에는 그것이 조용히 지나가게 만든 구조를 고친다. 보안 - 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이 필요해 이 환경에서 미수행.
6.8 KiB
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:
-
SSH to Pi 5 and edit
/boot/firmware/config.txt:sudo nano /boot/firmware/config.txt -
Add or update the line:
dtparam=pciex1_gen=3 -
Reboot:
sudo reboot -
Verify PCIe Gen3 negotiation:
lspci -vv | grep -i speedExpected 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:
-
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-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).
-
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-benchmarkor 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. |
- Tier selection is
INFER_EVERYin/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. - Run each tier with realistic camera setup (e.g., T1 on main fall cam, T2 on perimeter).
- Log FPS, E2E latency (frame capture → alert), CPU, Hailo temp for 30 min per tier.
- 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:
-
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).
-
Test duration: 4–8 hours continuous frame capture and optional Hailo inference.
-
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).
-
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.
-
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).
-
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.
-
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 -Dfor buffer count and memory mapping.
USB controller contention:
- Separate high-bandwidth cameras (USB3 MJPEG) across 2 controllers.
- Use
lsusb -tand/sys/bus/usb/devices/to map.
References
- hailo-rpi5-examples — Hailo + Pi 5 reference (MIT).
- picamera2 docs.
- Raspberry Pi 5 datasheet.