From b52e876eedaff2bcfb329e442d2791f909a1122b Mon Sep 17 00:00:00 2001 From: Alberto-Audrix Date: Fri, 14 Aug 2026 16:29:58 +0700 Subject: [PATCH] update webhook service dan API untuk webhook control web dapat status json sekarang --- DEPLOY.md | 39 +++++++++++++++++++++++--------- status_webhook.py | 37 ++++++++++++++++++++++++------ zenai-kpc-status-webhook.service | 32 ++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 zenai-kpc-status-webhook.service diff --git a/DEPLOY.md b/DEPLOY.md index b07db81..fe34fe4 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -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://:5000 (Port is set by `DASHBOARD_PORT` in `.env`, default `5000`.) +Status webhook endpoints (fixed ports): + +```text +http://:8002/?lokasi=IN|OUT&status=ON|OFF +https://:8443/?lokasi=IN|OUT&status=ON|OFF +http://:8002/status +``` + --- ## 5. Operations @@ -155,6 +167,7 @@ http://: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 `; 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://: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 ``` --- diff --git a/status_webhook.py b/status_webhook.py index 6772bed..454f3dc 100644 --- a/status_webhook.py +++ b/status_webhook.py @@ -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) diff --git a/zenai-kpc-status-webhook.service b/zenai-kpc-status-webhook.service new file mode 100644 index 0000000..7b6bc7e --- /dev/null +++ b/zenai-kpc-status-webhook.service @@ -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