forked from dsutanto/zenai-kpc-python
update webhook service dan API untuk webhook control web dapat status json sekarang
This commit is contained in:
1 parent
c80984ba80
commit
b52e876eed
3 files changed
+90
-18
No files matched your search
@@ -4,10 +4,11 @@ Production deployment for **RK3588** (or compatible RKNN NPU) edge devices runni
|
||||
|
||||
| Component | Script | systemd unit |
|
||||
|-----------|--------|--------------|
|
||||
| Status webhook (IN/OUT/OFF gate) | `status_webhook.py` | `zenai-kpc-status-webhook.service` |
|
||||
| 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.
|
||||
All three processes share a single `.env` file. The counter and dashboard read/write the same SQLite database and state JSON. The webhook writes `status_webhook_state.json`, which the counter polls when `STATUS_WEBHOOK_ENABLED=true`.
|
||||
|
||||
---
|
||||
|
||||
@@ -23,7 +24,7 @@ Both processes share a single `.env` file and read/write the same SQLite databas
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y python3 python3-venv python3-pip ffmpeg libgl1
|
||||
sudo apt install -y python3 python3-venv python3-pip ffmpeg libgl1 openssl
|
||||
```
|
||||
|
||||
`ffmpeg` is required for low-latency RTSP capture via OpenCV and for session MP4 recording (`RECORD_VIDEO=true`). `libgl1` is often needed for `opencv-python` on headless systems.
|
||||
@@ -49,6 +50,7 @@ Default paths used by the service files and `env.example`:
|
||||
├── counter_live_rknn.py
|
||||
├── counter_dashboard.py
|
||||
├── counter_store.py
|
||||
├── status_webhook.py
|
||||
├── templates/
|
||||
├── venv/ # Python virtual environment (created during install)
|
||||
├── .env # runtime config (not in git)
|
||||
@@ -123,21 +125,23 @@ sudo mkdir -p /opt/zenai-kpc-counter /opt/models /dev/shm/zenai-kpc-counter
|
||||
|
||||
```bash
|
||||
cd /opt/zenai-kpc-python
|
||||
sudo cp zenai-kpc-counter.service zenai-kpc-dashboard.service /etc/systemd/system/
|
||||
sudo cp zenai-kpc-status-webhook.service 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 enable zenai-kpc-status-webhook zenai-kpc-counter zenai-kpc-dashboard
|
||||
sudo systemctl start zenai-kpc-status-webhook
|
||||
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`).
|
||||
The dashboard unit starts **after** the counter unit (`After=zenai-kpc-counter.service`). Enable the webhook so it comes up on boot with the other two.
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
systemctl status zenai-kpc-status-webhook
|
||||
systemctl status zenai-kpc-counter
|
||||
systemctl status zenai-kpc-dashboard
|
||||
journalctl -u zenai-kpc-counter -f
|
||||
journalctl -u zenai-kpc-status-webhook -f
|
||||
```
|
||||
|
||||
Open the dashboard in a browser:
|
||||
@@ -148,6 +152,14 @@ http://<device-ip>:5000
|
||||
|
||||
(Port is set by `DASHBOARD_PORT` in `.env`, default `5000`.)
|
||||
|
||||
Status webhook endpoints (fixed ports):
|
||||
|
||||
```text
|
||||
http://<device-ip>:8002/?lokasi=IN|OUT&status=ON|OFF
|
||||
https://<device-ip>:8443/?lokasi=IN|OUT&status=ON|OFF
|
||||
http://<device-ip>:8002/status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Operations
|
||||
@@ -155,6 +167,7 @@ http://<device-ip>:5000
|
||||
### Restart after config change
|
||||
|
||||
```bash
|
||||
sudo systemctl restart zenai-kpc-status-webhook
|
||||
sudo systemctl restart zenai-kpc-counter
|
||||
sudo systemctl restart zenai-kpc-dashboard
|
||||
```
|
||||
@@ -162,6 +175,7 @@ sudo systemctl restart zenai-kpc-dashboard
|
||||
### View logs
|
||||
|
||||
```bash
|
||||
journalctl -u zenai-kpc-status-webhook -n 100 --no-pager
|
||||
journalctl -u zenai-kpc-counter -n 100 --no-pager
|
||||
journalctl -u zenai-kpc-dashboard -n 100 --no-pager
|
||||
```
|
||||
@@ -169,7 +183,7 @@ journalctl -u zenai-kpc-dashboard -n 100 --no-pager
|
||||
### Stop services
|
||||
|
||||
```bash
|
||||
sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter
|
||||
sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter zenai-kpc-status-webhook
|
||||
```
|
||||
|
||||
The counter handles `SIGTERM` gracefully — it finishes the current frame, persists state to SQLite, then exits.
|
||||
@@ -178,10 +192,10 @@ The counter handles `SIGTERM` gracefully — it finishes the current frame, pers
|
||||
|
||||
```bash
|
||||
cd /opt/zenai-kpc-python
|
||||
sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter
|
||||
sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter zenai-kpc-status-webhook
|
||||
# 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
|
||||
sudo systemctl start zenai-kpc-status-webhook zenai-kpc-counter zenai-kpc-dashboard
|
||||
```
|
||||
|
||||
---
|
||||
@@ -191,8 +205,10 @@ sudo systemctl start zenai-kpc-counter zenai-kpc-dashboard
|
||||
| Symptom | Things to check |
|
||||
|---------|-----------------|
|
||||
| Counter won't start | `journalctl -u zenai-kpc-counter`; verify `MODEL_PATH` exists; RKNN drivers installed |
|
||||
| Status webhook won't start | `journalctl -u zenai-kpc-status-webhook`; ports 8002/8443 free; `openssl` installed if certs are missing |
|
||||
| No RTSP frames | Ping camera; test with `ffplay <SOURCE>`; check `OPENCV_FFMPEG_CAPTURE_OPTIONS` |
|
||||
| Dashboard shows 0 count | `STATE_FILE` in `.env` must match between counter and dashboard; check file exists |
|
||||
| Counting stays OFF | Check `STATUS_WEBHOOK_ENABLED` and `GET http://<device-ip>:8002/status`; `STATUS_WEBHOOK_FILE` must match the webhook state file |
|
||||
| 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 |
|
||||
@@ -202,8 +218,9 @@ sudo systemctl start zenai-kpc-counter zenai-kpc-dashboard
|
||||
```bash
|
||||
cd /opt/zenai-kpc-python
|
||||
source venv/bin/activate
|
||||
python counter_live_rknn.py # terminal 1
|
||||
python counter_dashboard.py # terminal 2
|
||||
python status_webhook.py # terminal 1
|
||||
python counter_live_rknn.py # terminal 2
|
||||
python counter_dashboard.py # terminal 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+30
-7
@@ -2,7 +2,9 @@
|
||||
"""
|
||||
Standalone status webhook (stdlib only, HTTP + HTTPS).
|
||||
|
||||
Receives GET /?lokasi=IN|OUT&status=ON|OFF.
|
||||
Receives:
|
||||
GET /status -> {"status": "IN"|"OUT"|"OFF"}
|
||||
GET /?lokasi=IN|OUT&status=ON|OFF -> success | fail
|
||||
|
||||
State stored in JSON as IN | OUT | OFF.
|
||||
|
||||
@@ -19,6 +21,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import ssl
|
||||
import subprocess
|
||||
import threading
|
||||
@@ -144,7 +147,12 @@ class StatusHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self) -> None:
|
||||
parsed = urlparse(self.path)
|
||||
if parsed.path not in ("/", ""):
|
||||
path = parsed.path.rstrip("/") or "/"
|
||||
if path == "/status":
|
||||
current = load_status() or "OFF"
|
||||
self._send_json(200, {"status": current})
|
||||
return
|
||||
if path not in ("/", ""):
|
||||
self._send_text(404, "Not Found")
|
||||
return
|
||||
|
||||
@@ -184,6 +192,14 @@ class StatusHandler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
self.wfile.write(raw)
|
||||
|
||||
def _send_json(self, code: int, payload: dict) -> None:
|
||||
raw = json.dumps(payload).encode("utf-8")
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(raw)))
|
||||
self.end_headers()
|
||||
self.wfile.write(raw)
|
||||
|
||||
|
||||
def make_http_server(port: int = HTTP_PORT) -> ThreadingHTTPServer:
|
||||
return ThreadingHTTPServer(("0.0.0.0", port), StatusHandler)
|
||||
@@ -256,13 +272,20 @@ def main() -> None:
|
||||
if http_server is None and https_server is None:
|
||||
raise SystemExit("status_webhook: neither HTTP nor HTTPS could bind")
|
||||
|
||||
stop = threading.Event()
|
||||
|
||||
def _handle_stop(_signum, _frame) -> None:
|
||||
stop.set()
|
||||
|
||||
signal.signal(signal.SIGTERM, _handle_stop)
|
||||
signal.signal(signal.SIGINT, _handle_stop)
|
||||
|
||||
try:
|
||||
# serve_forever already running on threads; park main until interrupt.
|
||||
while True:
|
||||
threading.Event().wait(3600)
|
||||
except KeyboardInterrupt:
|
||||
print("\nstatus_webhook stopped")
|
||||
# serve_forever already running on threads; park main until stop.
|
||||
while not stop.is_set():
|
||||
stop.wait(3600)
|
||||
finally:
|
||||
print("status_webhook stopped")
|
||||
stop_servers(http_server, https_server)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
[Unit]
|
||||
Description=ZenAI KPC Status Webhook (HTTP + HTTPS)
|
||||
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 status_webhook.py
|
||||
|
||||
TimeoutStopSec=15
|
||||
KillSignal=SIGTERM
|
||||
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StartLimitInterval=60s
|
||||
StartLimitBurst=3
|
||||
|
||||
NoNewPrivileges=true
|
||||
ProtectHome=true
|
||||
PrivateTmp=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in new issue
Block a user