# 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/zenai-ktc-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.