diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..289bb12 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,241 @@ +# ZenAI KPC Counter — Edge Deployment Guide + +Production deployment for **RK3588** (or compatible RKNN NPU) edge devices running: + +| Component | Script | systemd unit | +|-----------|--------|--------------| +| RTSP counter (RKNN + ByteTrack) | `counter_live_rknn.py` | `zenai-kpc-counter.service` | +| Web dashboard (Flask) | `counter_dashboard.py` | `zenai-kpc-dashboard.service` | + +Both processes share a single `.env` file and read/write the same SQLite database and state JSON. + +--- + +## 1. Prerequisites + +### Hardware & OS + +- RK3588 board (or Jetson/RK device with RKNN Lite runtime) +- Linux with systemd +- Network access to the RTSP camera stream + +### System packages + +```bash +sudo apt update +sudo apt install -y python3 python3-venv python3-pip ffmpeg libgl1 +``` + +`ffmpeg` is required for low-latency RTSP capture via OpenCV. `libgl1` is often needed for `opencv-python` on headless systems. + +### RKNN model + +Export or copy your `.rknn` model to the device, e.g.: + +```text +/opt/models/your_model.rknn +``` + +Set `MODEL_PATH` in `.env` to match. The model class count must match `NUM_CLASSES`, and `OBJECT_CLASS_ID` must point at the class you count. + +--- + +## 2. Directory layout + +Default paths used by the service files and `env.example`: + +```text +/opt/zenai-kpc-python/ # application code (this repo) +├── counter_live_rknn.py +├── counter_dashboard.py +├── counter_store.py +├── templates/ +├── venv/ # Python virtual environment (created during install) +├── .env # runtime config (not in git) +├── env.example # template — copy to .env +└── DEPLOY.md + +/opt/zenai-kpc-counter/ # persistent runtime data (created automatically) +├── counter.db # SQLite daily records +├── current_counter.json # live counting-day state +├── snapshots/ # crossing/detect JPEGs (if enabled) +└── crossings.csv # optional per-event CSV + +/opt/models/ # RKNN models (deploy separately) +/dev/shm/zenai-kpc-counter/ # live JPEG frame for dashboard video (tmpfs) +``` + +--- + +## 3. Install application + +### 3.1 Copy code to the device + +```bash +sudo mkdir -p /opt/zenai-kpc-python +sudo rsync -av --exclude venv --exclude .env --exclude __pycache__ \ + ./ /opt/zenai-kpc-python/ +# Or: sudo git clone /opt/zenai-kpc-python +``` + +### 3.2 Create virtual environment and install dependencies + +```bash +cd /opt/zenai-kpc-python +sudo python3 -m venv venv +sudo ./venv/bin/pip install --upgrade pip +sudo ./venv/bin/pip install -r requirements.txt +``` + +> `rknn-toolkit-lite2` is platform-specific. Install on the target ARM device, not on a Windows dev machine. + +### 3.3 Create runtime config + +```bash +cd /opt/zenai-kpc-python +sudo cp env.example .env +sudo nano .env +``` + +**Minimum values to edit before starting:** + +| Variable | Description | +|----------|-------------| +| `SOURCE` | RTSP URL or local video file path | +| `MODEL_PATH` | Path to your `.rknn` model on device | +| `NUM_CLASSES` | Must match the exported model | +| `OBJECT_CLASS_ID` | Class index of the object being counted | +| `CLASS_OBJECT` / `OBJECT_LABEL` | Labels stored in DB (e.g. `karung`) | +| `LINE_Y1_FRAC` / `LINE_Y2_FRAC` | Counting line positions (tune per camera) | +| `SECRET_KEY` | Random string for Flask sessions | + +Ensure `STATE_FILE` and `DB_PATH` both live under `/opt/zenai-kpc-counter/` so data survives reboots (avoid `/tmp` in production). + +### 3.4 Create data directories (optional — app creates most paths automatically) + +```bash +sudo mkdir -p /opt/zenai-kpc-counter /opt/models /dev/shm/zenai-kpc-counter +``` + +--- + +## 4. Install systemd services + +```bash +cd /opt/zenai-kpc-python +sudo cp zenai-kpc-counter.service zenai-kpc-dashboard.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable zenai-kpc-counter zenai-kpc-dashboard +sudo systemctl start zenai-kpc-counter +sudo systemctl start zenai-kpc-dashboard +``` + +The dashboard unit starts **after** the counter unit (`After=zenai-kpc-counter.service`). + +### Verify + +```bash +systemctl status zenai-kpc-counter +systemctl status zenai-kpc-dashboard +journalctl -u zenai-kpc-counter -f +``` + +Open the dashboard in a browser: + +```text +http://:5000 +``` + +(Port is set by `DASHBOARD_PORT` in `.env`, default `5000`.) + +--- + +## 5. Operations + +### Restart after config change + +```bash +sudo systemctl restart zenai-kpc-counter +sudo systemctl restart zenai-kpc-dashboard +``` + +### View logs + +```bash +journalctl -u zenai-kpc-counter -n 100 --no-pager +journalctl -u zenai-kpc-dashboard -n 100 --no-pager +``` + +### Stop services + +```bash +sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter +``` + +The counter handles `SIGTERM` gracefully — it finishes the current frame, persists state to SQLite, then exits. + +### Update application code + +```bash +cd /opt/zenai-kpc-python +sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter +# rsync or git pull new code +sudo ./venv/bin/pip install -r requirements.txt # if dependencies changed +sudo systemctl start zenai-kpc-counter zenai-kpc-dashboard +``` + +--- + +## 6. Troubleshooting + +| Symptom | Things to check | +|---------|-----------------| +| Counter won't start | `journalctl -u zenai-kpc-counter`; verify `MODEL_PATH` exists; RKNN drivers installed | +| No RTSP frames | Ping camera; test with `ffplay `; check `OPENCV_FFMPEG_CAPTURE_OPTIONS` | +| Dashboard shows 0 count | `STATE_FILE` in `.env` must match between counter and dashboard; check file exists | +| Live video blank | `LIVE_STREAM_ENABLED=true`; path matches `LIVE_STREAM_FRAME_PATH` in both processes; if using nginx, set `proxy_read_timeout` (see §7) | +| Wrong counts | Tune `LINE_Y1_FRAC`/`LINE_Y2_FRAC`, `CONF`, ByteTrack thresholds; enable `DEBUG_TRACKING=true` temporarily | +| Service keeps restarting | `journalctl -u zenai-kpc-counter -e`; often missing model, bad RTSP URL, or venv not created | + +### Manual test (without systemd) + +```bash +cd /opt/zenai-kpc-python +source venv/bin/activate +python counter_live_rknn.py # terminal 1 +python counter_dashboard.py # terminal 2 +``` + +--- + +## 7. Optional: reverse proxy + +For HTTPS or port 80 access, put nginx in front of the dashboard: + +```nginx +server { + listen 80; + server_name counter.example.com; + + location / { + proxy_pass http://127.0.0.1:5000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_buffering off; # required for /api/live-video MJPEG stream + proxy_read_timeout 3600s; # keep long-lived MJPEG connections open + proxy_send_timeout 3600s; + } +} +``` + +The live JPEG at `LIVE_STREAM_FRAME_PATH` can also be served statically by nginx if you prefer not to use the Flask MJPEG endpoint. + +--- + +## 8. Security notes + +- Change `SECRET_KEY` from the default before exposing the dashboard on a network. +- Services currently run as `root` for simplicity on edge devices. For hardened deployments, create a dedicated user, chown `/opt/zenai-kpc-counter`, and update the `User=` / `Group=` lines in the service files. +- Do not commit `.env` — it may contain RTSP credentials. +- Set `FLASK_DEBUG=false` in production. diff --git a/config.env.example b/config.env.example index b386692..6b4bb92 100644 --- a/config.env.example +++ b/config.env.example @@ -223,4 +223,4 @@ DASHBOARD_PORT=5000 # Enable Flask debug mode (true/false) — auto-reloads on code changes; disable in production FLASK_DEBUG=false # Fallback name for the active counting-day JSON state file used by the dashboard -CURRENT_COUNTER_PATH=/tmp/bytetrack_current_counter.json +CURRENT_COUNTER_PATH=/tmp/bytetrack_current_counter.json \ No newline at end of file diff --git a/counter_dashboard.py b/counter_dashboard.py index 0bbb1e9..c645346 100644 --- a/counter_dashboard.py +++ b/counter_dashboard.py @@ -538,4 +538,4 @@ if __name__ == "__main__": print(f"Jetson counter dashboard at http://{DASHBOARD_HOST}:{DASHBOARD_PORT}") print(f"DB: {DB_PATH}") print(f"State: {CURRENT_COUNTER_PATH}") - app.run(host=DASHBOARD_HOST, port=DASHBOARD_PORT, debug=FLASK_DEBUG) + app.run(host=DASHBOARD_HOST, port=DASHBOARD_PORT, debug=FLASK_DEBUG) \ No newline at end of file diff --git a/counter_live_rknn.py b/counter_live_rknn.py index f4ba27e..4f8f0e0 100644 --- a/counter_live_rknn.py +++ b/counter_live_rknn.py @@ -1593,4 +1593,4 @@ object_tracked = {} if __name__ == "__main__": - run() + run() \ No newline at end of file diff --git a/env.example b/env.example index f2164da..bf99158 100644 --- a/env.example +++ b/env.example @@ -147,4 +147,4 @@ CROSS_SNAPSHOT_DIR=/opt/zenai-kpc-snaps/snapshots CROSS_SNAPSHOT_CLEANUP_SEC=3600 CROSS_SNAPSHOT_MAX_AGE_DAYS=3 -DEDUP_PX=-1 +DEDUP_PX=-1 \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index d1304e3..23640bd 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -1217,4 +1217,4 @@ function hideVideoError() { - + \ No newline at end of file diff --git a/zenai-kpc-counter.service b/zenai-kpc-counter.service new file mode 100644 index 0000000..c4e92a2 --- /dev/null +++ b/zenai-kpc-counter.service @@ -0,0 +1,32 @@ +[Unit] +Description=ZenAI KPC Edge Counter (RTSP + RKNN) +Documentation=file:///opt/zenai-kpc-python/DEPLOY.md +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=root +Group=root + +WorkingDirectory=/opt/zenai-kpc-python +EnvironmentFile=/opt/zenai-kpc-python/.env +Environment=PYTHONNOUSERSITE=1 +Environment=PATH=/opt/zenai-kpc-python/venv/bin:/usr/local/bin:/usr/bin:/bin + +ExecStart=/opt/zenai-kpc-python/venv/bin/python counter_live_rknn.py + +TimeoutStopSec=30 +KillSignal=SIGTERM + +Restart=always +RestartSec=10 +StartLimitInterval=120s +StartLimitBurst=5 + +NoNewPrivileges=true +ProtectHome=true +PrivateTmp=false + +[Install] +WantedBy=multi-user.target diff --git a/zenai-kpc-dashboard.service b/zenai-kpc-dashboard.service new file mode 100644 index 0000000..1b5cc13 --- /dev/null +++ b/zenai-kpc-dashboard.service @@ -0,0 +1,32 @@ +[Unit] +Description=ZenAI KPC Dashboard (Flask) +Documentation=file:///opt/zenai-kpc-python/DEPLOY.md +After=network-online.target zenai-kpc-counter.service +Wants=network-online.target + +[Service] +Type=simple +User=root +Group=root + +WorkingDirectory=/opt/zenai-kpc-python +EnvironmentFile=/opt/zenai-kpc-python/.env +Environment=PATH=/opt/zenai-kpc-python/venv/bin:/usr/local/bin:/usr/bin:/bin +Environment=FLASK_DEBUG=false + +ExecStart=/opt/zenai-kpc-python/venv/bin/python counter_dashboard.py + +TimeoutStopSec=15 +KillSignal=SIGTERM + +Restart=always +RestartSec=5 +StartLimitInterval=60s +StartLimitBurst=3 + +NoNewPrivileges=true +ProtectHome=true +PrivateTmp=false + +[Install] +WantedBy=multi-user.target