8.0 KiB
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.
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 inconfig.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-videoMJPEG). - Helpers:
check_jetson_db.py(root); retired ops scripts inarchive/(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):
pip install rapidocr_onnxruntime
Backups (ocr_engine — office monitoring UI toggle, no restart):
Tesseract (Backup, Light):
# lighter; system package + Indonesian traineddata
sudo apt install tesseract-ocr tesseract-ocr-ind
pip install pytesseract
PaddleOCR (Accuracy, Heavy to run):
# 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 → 404motionEye belum dikonfigurasi (MOTIONEYE_URL)),MOTIONEYE_CAMERA_ID(default2). MOTIONEYE_CLIP_PAD— float seconds padded before/after the batch window at both cut points (default3→ ±3 s; empty/invalid falls back to3.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_batchOCRs 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 withjournalctl -u karung-counter-dashboard.- motionEye HTTP API used:
GET /movie/<id>/list/(recording segments in the window) andGET /movie/<id>/download<path>(segment bytes). - Requires
ffmpeg/ffprobeon 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:
…/statusreportsstatus: "error"with theerrormessage (…/file→ 404), and a failingPOSTanswers 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 starting2026-09-26T14:25:09.123456→b21-2026-09-26-14-25-09.mp4).