Files
karung-counting-feedmill-se…/docs/deployment.md
T
2026-09-29 09:28:51 +07:00

161 lines
8.0 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.
# Deployment (Jetson)
Repo: `https://git.proit.id/andrew/karung-counting-feedmill-semarang`
(`git remote set-url origin <url>` after the ervan → andrew transfer).
## Systemd services
| Unit | Runs | After |
|---|---|---|
| `karung-counter.service` | `/usr/bin/python3 predict.py` (cwd `/home/jetson/karung`, `QT_QPA_PLATFORM=offscreen`) | `network.target` |
| `karung-counter-dashboard.service` | `/usr/bin/python3 counter_dashboard.py` | `network.target` + counter |
| `mediamtx.service` | MediaMTX restream (see `zones.json:external_stream_url`) | — |
Both app units: `Restart=always`, `RestartSec=5`, load `EnvironmentFile=/home/jetson/karung/.env`.
```bash
sudo systemctl enable --now karung-counter karung-counter-dashboard
sudo systemctl restart karung-counter karung-counter-dashboard
systemctl status karung-counter karung-counter-dashboard --no-pager
```
## Deploy flow (`deploy_to_jetson.py`)
Paramiko sync of `templates/{operator,monitoring,base}.html`, `counter_dashboard.py`,
`predict.py`, `config.yaml`, `.env` **plus `models/*.engine`** (v4-best,
yolo11n-sack+box, best, truck-detector, model_karung_truk) →
`192.168.192.96:/home/jetson/karung/` (creates remote `models/` if missing,
skips missing local files), then restarts both services and checks status +
ports (5000/5721). Run from the dev machine. `.pt`/`.onnx` stay local-only
(dev/export).
## TensorRT export
On the Jetson (needs CUDA): `python3 export_model.py models/<name>.pt` exports to
FP16 `.engine` next to the `.pt` (default: karung-dimuat seg model). Production
loads `.engine` only — see `models/modelREADME.md` for which weights each mode uses.
## Runtime data files
- SQLite `jetson_counter.db`: `batches(counting_date, batch_number, camera_name,
object_label, count, start/end_time, box_*, plate, do_numbers, expected_*,
net_sack, net_box)`, `daily_summaries(...)`, `delivery_orders(...)` (DO photos).
- `current_batch.json` (crash recovery), `batch_mode.json` (batch flow mode only:
`auto`|`do_manual`|`manual` — model mode lives in `config.yaml`),
`do_settings.json` (require_plate/do + ocr_engine),
`$OUTPUT_DIR/do_photos/YYYY-MM-DD/` (7-day retention),
`batch_history_folder/batch_<ts>.json` + `hasil_perhitungan.json` (per-batch reports).
- Live frame: `/dev/shm/jetson-counter/live_frame.jpg` (written every 2nd frame,
consumed by `/api/live-video` MJPEG).
- Helpers: `check_jetson_db.py` (root); retired ops scripts in `archive/`
(`backup.py`, `dump_db.py`, `migrate_jetson_db.py`, `merge_batches_*.py`,
`update_batches.py`, `diagnose_truck_jetson.py`).
## DO OCR packages
Default engine is **RapidOCR** (PP-OCR via onnxruntime, bundled models —
offline-friendly; installed with `requirements.txt`):
```bash
pip install rapidocr_onnxruntime
```
Backups (`ocr_engine` — office monitoring UI toggle, no restart):
`Tesseract (Backup, Light)`:
```bash
# lighter; system package + Indonesian traineddata
sudo apt install tesseract-ocr tesseract-ocr-ind
pip install pytesseract
```
`PaddleOCR (Accuracy, Heavy to run)`:
```bash
# optional; heavier — see paddleocr docs for Jetson wheels
pip install paddleocr
```
Missing deps → upload returns explicit error (never silent fallback); flip
engine back to `RapidOCR`/`Tesseract` from the office dashboard
(`:5721`). Operator page: `http://<jetson>:5000/operator`
(smartphone camera capture for DO photos). Photo dir under `output.dir` with
7-day retention (hourly purge in dashboard process).
## Dashboard (`counter_dashboard.py`)
Pages: `/` + `/monitoring`, `/operator` (manual start/stop; DO panel in
`do_manual`), `/history`, `/analytics`. Key APIs: `/api/live-video`,
`/api/current-batch`, `/api/previous-batch`,
`/api/batch/{start,stop,stop-preview,mode}`, `/api/model-modes`
(mode list is derived from `config.yaml`, so future modes appear automatically),
`/api/do/{upload,photo/<id>,staged,settings,retention}` +
`PUT/DELETE /api/do/<id>`, `/api/summary`, `/api/daily-data`,
`/api/day-detail/<date>`, `/api/recent-batches`, `/api/available-dates`,
`/api/export-daily-csv`, `/api/export-day-csv/<date>` (Excel via openpyxl),
`/api/batch-clip/<date>/<batch_number>` + `/status`, `/file`,
`/api/batch-clip/pending` (office-only, see below).
Port split: mode / model_mode / require_plate / require_do / ocr_engine POSTs →
**office 5721 only** (403 on 5000). Smartphones open `http://<host>:5000/operator` for camera capture.
## Batch clip download (motionEye)
History page `Klip` button runs an **async job** (all endpoints **office port
5721 only**, 403 on 5000):
- `POST /api/batch-clip/<date>/<batch_number>` — idempotent: starts the job
or returns the existing one → `200 {"success": true, "status":
"running"|"ready", "job": "<date>/<n>"}`.
- `GET …/<date>/<batch_number>/status` → `{"success": true, "status":
"running"|"ready"|"error", "error": "<msg|empty>", "elapsed": <s>}`
(404 if no job).
- `GET …/<date>/<batch_number>/file` → mp4 attachment (409 while running,
404 if error/none).
- `GET /api/batch-clip/pending` → all jobs, newest first — restores button
state after reload/navigation and feeds the cross-page toast.
Button states: `Klip` → `Memproses… <s> dtk` (disabled; status polled every
1.5 s, `pending` refreshed every 3 s while a job on the page runs) →
`Siap — unduh` → `Unduh lagi` after the first download (instant, file already
exists); failure → `Gagal` (click shows the error). Jobs for other dates are
left to the toast.
Toast: every office page (`templates/base.html`) polls `pending` every 3 s —
bootstrap once, a 403 on the operator port disables polling for that tab —
and shows a non-blocking bottom-right **`Klip siap`** / **`Klip gagal`** card
(auto-dismiss 8 s, click → `/history`). Shown once per job per tab
(`sessionStorage clipNotified`, capped at 50 keys).
Rendering itself is unchanged (`clip_batch` in `src/clip.py`, ffmpeg under
the hood). The finished temp file is kept **≤1 h** so `Unduh lagi`
re-downloads without re-rendering; older `clip_*.mp4` files are swept then.
Jobs live in dashboard **memory** — a `karung-counter-dashboard` restart
drops them (status 404 → button falls back to `Klip`, press again).
- URL keys (`.env`): `MOTIONEYE_URL` (base URL; empty → 404
`motionEye belum dikonfigurasi (MOTIONEYE_URL)`), `MOTIONEYE_CAMERA_ID`
(default `2`).
- `MOTIONEYE_CLIP_PAD` — float seconds padded before/after the batch window
at both cut points (default `3` → ±3 s; empty/invalid falls back to `3.0`).
- `MOTIONEYE_OSD_ALIGN` — OSD-clock alignment, default **on** (unset = on;
`0` / `false` / `no` / empty = off). Why: the camera's burned-in OSD clock
and the recording clock drift — up to ~+22 s inside a long clip — because
of dropped frames, so filename-based offsets cut at the wrong media time.
When on, `clip_batch` OCRs **1 frame** at each cut point (~2–6 s extra per
clip, result cached per clip+second) and corrects the cut times. `0` →
filename-based offsets only. If OCR fails, it silently falls back to
filename-based offsets; both cases log one
`[CLIP] batch=... pad=... align=...` line at request start (plus any
`[CLIP]` alignment detail lines) — check with `journalctl -u
karung-counter-dashboard`.
- motionEye HTTP API used: `GET /movie/<id>/list/` (recording segments in the
window) and `GET /movie/<id>/download<path>` (segment bytes).
- Requires `ffmpeg` / `ffprobe` on PATH — both already installed on the Jetson.
- **Recording retention: motionEye `preserve_movies: 3` → only the last 3 days
of recordings exist.** Older batches return 404 (`ClipError`, no recording in
window).
- Errors are surfaced on the job: `…/status` reports `status: "error"` with
the `error` message (`…/file` → 404), and a failing `POST` answers non-2xx
`{"success": false, "error": "<msg>"}`. Finished file lives at
`$(dirname DB)/tmp/clip_<uuid>.mp4` (kept ≤1 h, then swept).
- Output filename built from the batch **start_time**:
`b{batch}-{YYYY-MM-DD}-{HH-MM-SS}.mp4`
(e.g. batch 21 starting `2026-09-26T14:25:09.123456` →
`b21-2026-09-26-14-25-09.mp4`).