- server-deploy.md: state MySQL 8.0.13+ / MariaDB 10.2+ is required (expression defaults), not "5.7+" — an import against 5.7 is a day-1 ERROR 1064 blocker. - pilot-runbook: add the air-gapped Pi path and mark pip/fetch as build-machine only; stop the shipped doc from telling a Pi operator the bundle "does not exist". - install_offline.sh: instruct the camera probe before enable (mirrors setup_online). - setup_online.sh: chmod 600 device.env on read; README documents delete + rotate. - systemd unit: order After/Wants=time-sync.target so occurred_at is real wall time.
147 lines
9.3 KiB
Markdown
147 lines
9.3 KiB
Markdown
# 파일럿 · 운영 런북
|
|
|
|
이 문서는 두 경로를 다룬다. **자신의 상황에 맞는 절에서 시작하라:**
|
|
|
|
- **§0 폐쇄망 Pi 현장 설치·운영** — Hailo·Pi5 실장비를 오프라인 번들로 설치한 경우. systemd 유닛의
|
|
`Documentation=`이 이 문서를 가리키므로 현장 운영자는 **§0만** 보면 된다.
|
|
- **§1~§6 노트북 웹캠 개발 파일럿** — 개발/데모 머신에서 CPU MoveNet로 엔드투엔드를 돌리는 최소 경로.
|
|
**이 절들의 `pip install`·모델 `fetch` 명령은 개발/빌드 머신 전용이다 — 폐쇄망 Pi에서 실행하지 말 것.**
|
|
|
|
- 엣지: `python -m edge` (또는 `move-detect-edge`) — pose → 감지 → HMAC 서명 POST.
|
|
- 서버: 기존 `server/` PHP 앱 — ingest + 대시보드 + 사고 상세 + 데스크 Web Audio 알림.
|
|
|
|
---
|
|
|
|
## 0. 폐쇄망 Pi 현장 설치·운영
|
|
|
|
실장비 Hailo·Pi5는 **오프라인 전체 번들**로 설치한다. 런타임 휠·pose 모델(HEF)·소스·systemd 유닛이
|
|
모두 번들에 동봉되어 있으므로 **Pi에서 인터넷을 받지 않는다** (아래 §1의 `pip install`/`fetch`는 절대
|
|
실행 금지 — 빌드 머신 전용).
|
|
|
|
1. **설치:** 번들 절차는 [deploy/README.md](../deploy/README.md)를 따른다. 빌드 머신에서
|
|
`deploy/bundle/build_offline_bundle.sh`(aarch64)로 번들을 만들어 Pi로 옮기고,
|
|
`sudo ./install_offline.sh`가 SHA256SUMS 검증 → 오프라인 휠 설치 → HEF 배치 → 유닛 설치를 수행한다.
|
|
(온라인 프로비저닝은 `sudo deploy/setup_online.sh` — 카메라 프로브까지 자동.)
|
|
2. **멀티카메라:** `/etc/move-detect/edge.env`의 `CAMERAS=csi:0:cam-01,uvc:0:cam-02`
|
|
형식으로 1 Pi에 2~4대를 배선한다(형식 `backend:device:camera_id`).
|
|
3. **enable 전 카메라 프로브** (설치 스크립트가 출력하는 명령) — 잘못된 인덱스/미체결 리본이 만드는
|
|
"초록불인데 한 대 덜 감시" 상태를 현장에서 잡는다.
|
|
4. **기동·확인:** `sudo systemctl enable --now move-detect-edge` → `journalctl -u move-detect-edge -f`.
|
|
5. **시크릿 위생:** 프로비저닝에 `deploy/device.env`를 썼다면 설치 후 삭제·필요시 로테이션
|
|
(deploy/README.md "Secret handling").
|
|
|
|
> 시크릿·서버 URL·모델 경로 등 env 키는 §3 표와 동일하다. 감지 임계값은 서버 대시보드의 구역·임계값이
|
|
> 다음 config 폴링에 반영된다.
|
|
|
|
---
|
|
|
|
## 1. 빌드/개발 머신 준비 (모델 · onnxruntime) — 폐쇄망 Pi에서 실행 금지
|
|
|
|
> **폐쇄망 원칙:** onnxruntime 휠과 `.onnx` 모델은 **빌드 머신에서 받아 번들**한다. 타깃(폐쇄망 Pi)에서는
|
|
> 인터넷을 받지 않으며, 아래 두 명령을 Pi에서 실행하면 안 된다(§0 참조 — Pi는 이미 번들로 설치됨).
|
|
> 노트북 파일럿은 빌드=타깃이 같은 머신이라 아래를 그대로 실행하면 된다.
|
|
|
|
```bash
|
|
pip install onnxruntime # CPU 추론 런타임 (에어갭 타깃엔 오프라인 휠로 번들)
|
|
python -m edge.tools.fetch_movenet # MoveNet SinglePose Lightning ONNX -> models/movenet_lightning.onnx
|
|
```
|
|
|
|
`fetch_movenet.py`의 URL은 빌드 편의용이다. 실제 내려받은 모델의 입력 크기/자료형과 출력 레이아웃
|
|
`[1,1,17,3]`(y,x,score)를 어댑터 기대값과 대조하라(다르면 `MoveNetPoseAdapter(input_size=...)` 조정;
|
|
COCO-17 remap 로직은 그대로). 어댑터 remap은 `tests/test_movenet_pose.py`(fake session)로 고정돼 있고,
|
|
실모델 경로는 `tests/hardware/test_movenet_smoke.py`가 모델이 번들됐을 때만 게이트로 검증한다.
|
|
|
|
---
|
|
|
|
## 2. 서버 로컬 기동
|
|
|
|
로컬 MySQL 8(스키마 CHECK 제약은 8.0.16+)과 PHP 8.2+ 필요. 상세 배포·전제조건(gd/argon2/HTTPS 등)은
|
|
[server/docs/server-deploy.md](../server/docs/server-deploy.md) 참조 — 여기선 로컬 최소 절차만.
|
|
|
|
1. DB 생성 후 스키마 임포트: `mysql <db> < server/schema.sql` (10개 테이블).
|
|
2. `server/config.php`를 `server/config.example.php`에서 복사, 로컬 DB DSN/계정 기입
|
|
(`snapshot_dir`는 쓰기 가능 경로로).
|
|
3. **관리자 계정 · 디바이스 시크릿 등록** — 앱에 셀프 가입 흐름이 없으므로 직접 넣는다.
|
|
CLI 스니펫과 SQL은 server-deploy.md **§6(디바이스 시크릿)** · **§7(첫 관리자)** 그대로 사용:
|
|
- 시크릿 생성: `php -r "echo bin2hex(random_bytes(32)), \"\n\";"`
|
|
- `INSERT INTO devices (device_id, name, secret, config_version) VALUES ('pi-01','노트북','<시크릿>',1);`
|
|
- 관리자 해시: `php -r "echo password_hash('바꿀-암구호', PASSWORD_ARGON2ID), \"\n\";"`
|
|
- `INSERT INTO users (username, password_hash, role, ...) VALUES ('admin','<해시>','admin',NULL,0,0);`
|
|
4. 기동: `php -S localhost:8000 -t server/public`
|
|
- 세션 쿠키가 `cookie_secure`라 순수 HTTP에선 로그인 쿠키가 안 붙을 수 있다. 로컬은 대개
|
|
`localhost`가 secure-context로 취급돼 동작하지만, 안 되면 server-deploy.md §8(HTTPS) 참조.
|
|
|
|
---
|
|
|
|
## 3. 엣지 환경변수
|
|
|
|
`edge/config.py`(`load_config`) + `edge/daemon.py`(`main`)가 읽는 실제 키:
|
|
|
|
| env | 의미 | 예시 |
|
|
|-----|------|------|
|
|
| `DEVICE_ID` | `devices.device_id`와 일치 | `pi-01` |
|
|
| `DEVICE_SECRET` | `devices.secret`와 일치(HMAC) | `<§2에서 생성한 값>` |
|
|
| `CAFE24_URL` | 서버 베이스 URL | `http://localhost:8000` |
|
|
| `CAMERA_ID` | 이벤트/구역의 camera_id | `cam-01` |
|
|
| `MOVENET_ONNX` | 모델 경로 **(필수, 없으면 즉시 KeyError)** | `models/movenet_lightning.onnx` |
|
|
| `UVC_DEVICE` | OpenCV 캡처 인덱스 | `0` |
|
|
| `TARGET_FPS` | 프레임 간격 = 1/fps | `12` |
|
|
| `QUEUE_DB` | 오프라인 큐 sqlite 경로 | `queue.db` |
|
|
|
|
감지 임계값(선택, 데모 단축용): `FALL_PRONE_S`(기본 3.0), `FALL_NO_MOTION_S`(10.0),
|
|
`IMMOBILE_S`(120.0), `GHOST_REAPPEAR_S`(10.0), `ZONE_DEBOUNCE_S`(0.75), `COOLDOWN_S`(30.0).
|
|
|
|
```bash
|
|
export DEVICE_ID=pi-01 DEVICE_SECRET='<시크릿>' CAFE24_URL=http://localhost:8000 \
|
|
CAMERA_ID=cam-01 MOVENET_ONNX=models/movenet_lightning.onnx UVC_DEVICE=0 TARGET_FPS=12
|
|
python -m edge # 또는 move-detect-edge
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 데모 시나리오
|
|
|
|
1. **낙상:** 웹캠 앞에서 눕는다 → `FALL_PRONE_S`(기본 3초) 후 `fall`(high) 이벤트.
|
|
2. **위급 상승:** 계속 안 움직이면 `FALL_NO_MOTION_S`(기본 10초) 후 `no_motion`(critical)
|
|
→ 대시보드에서 **데스크 비프 반복**(첫 클릭으로 오디오 언락 필요).
|
|
3. **가시화:** 목록의 이벤트 유형(링크) 클릭 → `/events/{id}/view` 상세 → **스냅샷 이미지** 확인.
|
|
4. **응답:** 상세/목록에서 **확인** → 미확인 critical이 사라지면 비프 정지.
|
|
5. **역루프(구역):** 대시보드 `구역 설정`에서 폴리곤 저장 → 발끝 진입 → `zone_*` 이벤트.
|
|
구역 변경은 엣지의 **다음 config 폴링** 때 적용된다 — 기본 cadence `sync_every=150` 프레임
|
|
≈ 12fps에서 약 12초. 저장 후 ~15초 기다렸다가 진입할 것.
|
|
6. **오프라인 큐:** 네트워크(또는 `php -S`) 중단 상태에서 이벤트 발생 → 큐에 적재 →
|
|
복구 후 `flush_every=300` 프레임(≈25초)마다 flush로 밀린 이벤트 전송.
|
|
7. **하트비트:** **30초 시간기반**(`heartbeat_interval_s=30`)으로 `POST /api/heartbeat`(서명).
|
|
부하와 무관하게 주기가 일정해 서버의 오프라인 판정(120초, 4배 마진)이 정상 장치를 오프라인으로
|
|
깜빡이지 않는다. temp/throttle/queue_pending/config_version 텔레메트리를 함께 싣는다.
|
|
|
|
> cadence(`sync_every`/`flush_every`는 프레임 수, `heartbeat_interval_s`는 초)는 `daemon.main()`
|
|
> 기본값이며 env로 노출돼 있지 않다. 데모에서 더 빠른 주기가 필요하면 `run_loop(...)` 호출 인자를
|
|
> 임시 조정한다. 감지 타이머는 monotonic 시계로 돌아 NTP 시각 점프에 영향받지 않는다(occurred_at만 벽시계).
|
|
|
|
---
|
|
|
|
## 5. 검증(테스트)
|
|
|
|
```bash
|
|
pytest -q # 엣지 유닛(하드웨어 스모크는 모델 없으면 skip)
|
|
cd server && ./vendor/bin/phpunit --display-warnings --display-deprecations # 서버(0 warnings)
|
|
```
|
|
|
|
모델을 번들한 뒤 실추론 경로까지 확인하려면 `models/movenet_lightning.onnx` 존재 상태로 `pytest -q` —
|
|
`tests/hardware/test_movenet_smoke.py`가 skip 해제돼 실행된다.
|
|
|
|
---
|
|
|
|
## 6. 한계 (명시)
|
|
|
|
> **이 절은 §1~§5의 노트북 웹캠 파일럿에 대한 것이다.** 폐쇄망 Pi 현장 설치(§0)는 아래 항목 다수를
|
|
> 이미 해소했다 — 실장비 Hailo 가속, 멀티카메라, 오프라인 전체 번들이 제공된다.
|
|
|
|
- **노트북 파일럿은 단일 카메라 · CPU pose(저 fps)** — 폐쇄망 Pi(§0)는 Hailo 가속 + 멀티카메라
|
|
(`CAMERAS=` 다중 배선)를 사용한다.
|
|
- **단일 프레임 스냅샷** — 前中後 링버퍼 다중 프레임은 P2(양끝 인프라는 존재, 배선 대기).
|
|
- **알림 1채널(데스크 Web Audio)** — critical SMS/webhook·미확인 에스컬레이션 메일은 후속(P3).
|
|
- **폐쇄망 전체 번들은 이미 존재한다** — `deploy/bundle/`에 오프라인 휠·HEF·소스·SHA256SUMS 검증
|
|
설치를 제공한다(§0). 노트북 파일럿만 `pip install`/`fetch`로 편의 실행할 뿐이다.
|
|
- **드릴 게이트 · F14 컴플라이언스 · 감사 해시체인** — 범위 밖(P1-C/P3).
|