forked from dsutanto/zenai-kpc-python
180 lines
5.3 KiB
Markdown
180 lines
5.3 KiB
Markdown
# Runtime Control — Start/Stop Counting
|
|
|
|
The counter can pause and resume object detection & counting **on the fly**,
|
|
without restarting the process. There are three interchangeable ways to control
|
|
it, and they all converge on a single source of truth: the **control file**.
|
|
|
|
- **Control file** — a small JSON file the counter polls.
|
|
- **TCP control socket** — line commands over the network that update the file.
|
|
- **Dashboard button** — a COUNTING ON/OFF toggle that writes the file via its API.
|
|
|
|
When counting is **OFF**, the counter skips inference entirely (no detection, no
|
|
counting, lower CPU/NPU load), the video/live stream keeps running, and a
|
|
`COUNTING PAUSED` badge is drawn on the frame. When **ON**, normal counting
|
|
resumes.
|
|
|
|
---
|
|
|
|
## 1. Enable runtime control
|
|
|
|
Runtime control is opt-in. In your `.env`:
|
|
|
|
```ini
|
|
# Master switch — required for ALL control methods (file, socket, dashboard).
|
|
CONTROL_ENABLED=true
|
|
|
|
# Shared control file. MUST be identical for the counter and the dashboard.
|
|
CONTROL_FILE=/opt/bytetrack-counter/control.json
|
|
|
|
# Counting state on startup / when the control file does not exist yet.
|
|
CONTROL_DEFAULT_COUNTING=true
|
|
|
|
# How often (seconds) the counter re-reads the control file.
|
|
CONTROL_POLL_SEC=1.0
|
|
```
|
|
|
|
When `CONTROL_ENABLED=false`, the counter always counts, the control file is
|
|
ignored, and the dashboard hides the toggle button.
|
|
|
|
> Changes take effect within `CONTROL_POLL_SEC` seconds (default 1s), because the
|
|
> counter re-reads the file on a timer.
|
|
|
|
---
|
|
|
|
## 2. Control file
|
|
|
|
### Format
|
|
|
|
```json
|
|
{ "counting": true }
|
|
```
|
|
|
|
- `"counting": true` → counting **ON**
|
|
- `"counting": false` → counting **OFF** (paused)
|
|
|
|
The counter creates this file on startup (seeded from `CONTROL_DEFAULT_COUNTING`)
|
|
if it does not exist. All writers (counter, dashboard, socket) write it
|
|
**atomically** (temp file + rename), so readers never see a half-written file.
|
|
|
|
### Toggle by editing the file
|
|
|
|
Pause counting:
|
|
|
|
```bash
|
|
printf '{"counting": false}\n' > /opt/bytetrack-counter/control.json
|
|
```
|
|
|
|
Resume counting:
|
|
|
|
```bash
|
|
printf '{"counting": true}\n' > /opt/bytetrack-counter/control.json
|
|
```
|
|
|
|
Check current state:
|
|
|
|
```bash
|
|
cat /opt/bytetrack-counter/control.json
|
|
```
|
|
|
|
> Use the exact path from your `CONTROL_FILE` setting. If you write it by hand,
|
|
> keep it valid JSON — an unreadable file falls back to `CONTROL_DEFAULT_COUNTING`.
|
|
|
|
---
|
|
|
|
## 3. TCP control socket
|
|
|
|
The socket lets you toggle counting over the network. It updates the same control
|
|
file, so changes still apply within `CONTROL_POLL_SEC`.
|
|
|
|
### Enable
|
|
|
|
```ini
|
|
# Requires CONTROL_ENABLED=true as well.
|
|
CONTROL_SOCKET_ENABLED=true
|
|
|
|
# 127.0.0.1 = local only. Use 0.0.0.0 to allow remote clients.
|
|
CONTROL_SOCKET_HOST=127.0.0.1
|
|
|
|
# TCP port.
|
|
CONTROL_SOCKET_PORT=5090
|
|
```
|
|
|
|
### Commands
|
|
|
|
Newline-terminated, case-insensitive. One connection can send multiple commands.
|
|
|
|
| Command | Effect | Reply |
|
|
|----------------------------|-------------------------------|-----------------------|
|
|
| `START` / `RESUME` / `ON` | Counting ON | `OK counting=on` |
|
|
| `STOP` / `PAUSE` / `OFF` | Counting OFF | `OK counting=off` |
|
|
| `TOGGLE` | Flip current state | `OK counting=on/off` |
|
|
| `STATUS` / `GET` | Report state (no change) | `OK counting=on/off` |
|
|
| *(anything else)* | — | `ERR unknown command` |
|
|
|
|
### Examples
|
|
|
|
Using `nc` (netcat):
|
|
|
|
```bash
|
|
printf 'STOP\n' | nc 127.0.0.1 5090
|
|
printf 'START\n' | nc 127.0.0.1 5090
|
|
printf 'TOGGLE\n' | nc 127.0.0.1 5090
|
|
printf 'STATUS\n' | nc 127.0.0.1 5090
|
|
```
|
|
|
|
Using bash `/dev/tcp` (no netcat needed):
|
|
|
|
```bash
|
|
exec 3<>/dev/tcp/127.0.0.1/5090
|
|
printf 'STATUS\n' >&3
|
|
head -n1 <&3
|
|
exec 3>&-
|
|
```
|
|
|
|
Python client:
|
|
|
|
```python
|
|
import socket
|
|
|
|
def control(cmd, host="127.0.0.1", port=5090):
|
|
with socket.create_connection((host, port), timeout=2) as s:
|
|
s.sendall((cmd + "\n").encode())
|
|
return s.recv(256).decode().strip()
|
|
|
|
print(control("STATUS")) # OK counting=on
|
|
print(control("STOP")) # OK counting=off
|
|
```
|
|
|
|
> **Security:** the socket has **no authentication**. Keep `CONTROL_SOCKET_HOST`
|
|
> on `127.0.0.1`, or restrict access with a firewall / trusted network if you
|
|
> bind to `0.0.0.0`.
|
|
|
|
---
|
|
|
|
## 4. Dashboard button
|
|
|
|
When `CONTROL_ENABLED=true`, the dashboard header shows a **COUNTING ON/OFF**
|
|
button (green when on, red when off). Clicking it flips the state immediately.
|
|
|
|
The dashboard must point at the **same** `CONTROL_FILE` as the counter (set it in
|
|
the dashboard's environment too). The dashboard exposes:
|
|
|
|
- `GET /api/control` → `{ "enabled": true, "counting": true }`
|
|
- `POST /api/control` with body `{ "counting": false }` → writes the control file
|
|
(returns `403` if `CONTROL_ENABLED=false`)
|
|
|
|
---
|
|
|
|
## Notes & behavior
|
|
|
|
- **Single source of truth:** the socket and dashboard both write the control
|
|
file; the counter reacts only to the file. This avoids race conditions between
|
|
control methods.
|
|
- **Latency:** expect up to `CONTROL_POLL_SEC` (default 1s) between issuing a
|
|
command and the counter reacting.
|
|
- **Live stream keeps running** while paused, so you still see the camera feed
|
|
with the `COUNTING PAUSED` overlay.
|
|
- **Shared path requirement:** counter and dashboard must use the same
|
|
`CONTROL_FILE`. If they run on different machines, use the TCP socket (or a
|
|
shared network path) instead.
|