secutorpro commited on
Commit
6cb1a1b
Β·
verified Β·
1 Parent(s): cd25c51

README: overview, quickstart, protocol, limitations

Browse files
Files changed (1) hide show
  1. README.md +63 -71
README.md CHANGED
@@ -1,97 +1,89 @@
1
  ---
2
  title: Dispatch Console
3
  emoji: πŸ—ΊοΈ
 
 
4
  sdk: docker
5
  app_port: 8080
6
  pinned: false
7
  ---
8
 
9
- # Dispatch Console (PoC-style, self-hosted)
10
 
11
- A production-ready, self-hosted alternative to hosted PoC dispatch consoles:
12
- live member map, GPS tracking, SOS alert workflow, push-to-talk intercom with
13
- server-enforced floor control, and member/talkgroup management.
14
 
15
- ## Stack
16
 
17
- - **Backend** β€” Node.js, Express (REST API), `ws` (WebSocket: GPS, SOS, PTT signaling, audio relay), SQLite (`better-sqlite3`), JWT auth
18
- - **Frontend** β€” React 18 + Vite, Leaflet (OpenStreetMap), AudioWorklet capture / WebAudio playback
19
- - **Deploy** β€” Docker / docker-compose; also runnable directly and on HF Spaces (this repo)
 
20
 
21
- ## Quickstart (local, no Docker)
22
 
23
- ```bash
24
- # 1. backend
25
- cd server
26
- npm install
27
- npm start # listens on :8080
28
-
29
- # 2. frontend (dev server with proxy)
30
- cd ../web
31
- npm install
32
- npm run dev # http://localhost:5173
33
-
34
- # 3. simulated fleet (separate terminal)
35
- cd ../server
36
- npm run sim # 5 units: GPS every 5 s, SOS at 25 s, PTT turns
37
  ```
38
 
39
- Default dispatcher: **admin / admin** (override with `ADMIN_PASSWORD` on first run; change it after first login).
 
 
40
 
41
  ## Quickstart (Docker)
42
 
43
  ```bash
44
- docker compose up --build -d # http://localhost:8080
 
45
  ```
46
 
47
- Data (SQLite + JWT secret) persists in `./data`.
48
 
49
- ## Architecture
 
 
 
 
 
 
 
50
 
 
 
 
51
  ```
52
- web (React+Vite) ──HTTP /api/*──> Express ──> SQLite (better-sqlite3)
53
- | |
54
- +--WebSocket /ws (JSON + PCM) --> Hub (realtime.js)
55
- |- GPS ingestion -> members + gps_pings
56
- |- SOS alerts -> broadcast + REST ack/resolve
57
- +- PTT floor control + 16 kHz PCM audio relay
 
58
  ```
59
 
60
- Devices (radios/phones) connect as WebSocket **device** clients
61
- (`{type:"auth", token, memberId}`); dispatch consoles connect as plain
62
- authenticated clients and see everything. Binary WebSocket frames carry audio:
63
- `[1 byte type=0x01][4 bytes groupId BE][4 bytes memberId BE][16 kHz mono Int16 LE PCM]`.
64
- Only the current floor holder may transmit; the server enforces one speaker per
65
- talkgroup and relays frames to everyone subscribed to that group.
66
-
67
- ## REST API (JWT Bearer)
68
-
69
- | Method | Path | Purpose |
70
- |---|---|---|
71
- | POST | `/api/login` | get token |
72
- | GET | `/api/me` | current user |
73
- | GET/POST | `/api/groups` | talkgroups |
74
- | GET/POST | `/api/members` | list / create members |
75
- | PUT/DELETE | `/api/members/:id` | edit / delete |
76
- | GET | `/api/members/:id/trail` | GPS history |
77
- | GET | `/api/sos` | alert list (`?state=open`) |
78
- | POST | `/api/sos/:id/ack` / `/api/sos/:id/resolve` | SOS workflow |
79
- | GET | `/api/calls` | call log |
80
-
81
- ## Limitations (v1, honest)
82
-
83
- - Audio is relayed 16 kHz PCM over WebSocket β€” fine for small fleets; move to
84
- Opus + a media server (LiveKit/Janus) for scale and better quality.
85
- - Devices authenticate with any valid dispatcher token + memberId; per-device
86
- credentials are the first thing to add before real deployment.
87
- - Single-node, single SQLite file; no clustering.
88
- - The fleet simulator needs a real browser (or device client) to produce audio;
89
- it exercises GPS, SOS and floor events only.
90
-
91
- ## Files
92
-
93
- - `server/src/realtime.js` β€” WebSocket hub: auth, GPS, SOS, PTT floor, audio relay
94
- - `server/src/routes.js` β€” REST CRUD + SOS workflow
95
- - `server/simulator.js` β€” simulated fleet for testing
96
- - `web/src/App.jsx` β€” state + realtime wiring
97
- - `web/src/audio.js` β€” AudioWorklet capture / playback / SOS alarm
 
1
  ---
2
  title: Dispatch Console
3
  emoji: πŸ—ΊοΈ
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
  app_port: 8080
8
  pinned: false
9
  ---
10
 
11
+ # Dispatch Console
12
 
13
+ A self-hostable PoC-style fleet dispatch console β€” in the spirit of platforms like Global-PTT's dispatch: a live map of members, SOS alert handling, group push-to-talk, and member/talkgroup management. One process, one SQLite file, no external services.
 
 
14
 
15
+ ## Features
16
 
17
+ - **Live map + GPS** β€” Leaflet/OpenStreetMap map, member markers colored by talkgroup, online/offline styling, SOS pulse, popup with battery and coordinates; devices push positions over WebSocket, every ping is stored for trails (`GET /api/members/:id/trail`)
18
+ - **SOS alerts** β€” devices raise SOS, console shows a queue with Ack β†’ Resolve flow and an audio alarm; state is broadcast to all consoles instantly
19
+ - **Intercom / PTT** β€” talkgroups with floor control (one speaker per group); dispatcher holds a button to talk, mic is captured at 16 kHz via AudioWorklet and relayed as PCM over the same WebSocket; floor indicator shows who is talking; call log recorded
20
+ - **Member management** β€” add/edit/remove members, assign talkgroups, see status/battery/last-seen; talkgroup list with colors
21
 
22
+ ## Architecture
23
 
24
+ ```
25
+ browser / devices
26
+ β”‚ HTTPS (REST: members, groups, SOS, trails)
27
+ β”‚ WSS /ws (GPS, SOS, PTT control, PCM audio frames)
28
+ β–Ό
29
+ Node + Express ── better-sqlite3 (dispatch.db)
30
+ └─ ws Hub: auth, floor control per talkgroup, audio relay
31
+ React + Vite + Leaflet frontend (served statically by the same process)
 
 
 
 
 
 
32
  ```
33
 
34
+ Binary audio frame on the WebSocket:
35
+ `[1B type=0x01][4B groupId BE][4B memberId BE][Int16LE PCM @ 16 kHz]`
36
+ Only the current floor holder of a talkgroup may transmit; the server drops audio from anyone else.
37
 
38
  ## Quickstart (Docker)
39
 
40
  ```bash
41
+ docker compose up --build
42
+ # open http://localhost:8080 β€” log in with admin / admin (change it)
43
  ```
44
 
45
+ ## Quickstart (dev)
46
 
47
+ ```bash
48
+ cd server && npm install && npm run dev # API + WS on :8080
49
+ cd web && npm install && npm run dev # Vite on :5173, proxied to :8080
50
+ ```
51
+
52
+ ## Simulated fleet
53
+
54
+ With the server running, drive a fake fleet so you can see the console come alive:
55
 
56
+ ```bash
57
+ cd server && npm run sim
58
+ # 5 units driving around Paris, one SOS after ~25 s, rotating PTT floor requests
59
  ```
60
+
61
+ ## Device integration
62
+
63
+ A device (or backend integration) authenticates with a dispatcher JWT and identifies as a member:
64
+
65
+ ```json
66
+ {"type": "auth", "token": "<jwt>", "memberId": 12}
67
  ```
68
 
69
+ Then, per message: `{"type":"gps","lat":48.85,"lng":2.35,"speed":34,"battery":80}`,
70
+ `{"type":"sos"}`, `{"type":"ptt_start"}`, `{"type":"ptt_stop"}`,
71
+ and raw binary frames for audio (format above).
72
+
73
+ ## Configuration
74
+
75
+ | Env | Meaning |
76
+ | --- | --- |
77
+ | `PORT` | HTTP/WS port (default 8080) |
78
+ | `DATA_DIR` | where `dispatch.db` and the JWT secret live (default `server/data`) |
79
+ | `JWT_SECRET` | signing secret; generated and persisted on first run if unset |
80
+ | `ADMIN_PASSWORD` | password for the first-run `admin` dispatcher |
81
+
82
+ ## Limitations & next steps
83
+
84
+ - **Device auth is trusted-by-JWT**: any valid token can claim any `memberId`. Production needs per-device credentials and revocation.
85
+ - **Audio is uncompressed 16 kHz PCM relayed over WebSocket** β€” fine for small fleets on good links; swap in Opus + an SFU (LiveKit, mediasoup) for larger ones and lossy networks.
86
+ - Single-node SQLite; horizontal scale would need Postgres and a Redis pub/sub fan-out.
87
+ - Map tiles come from public OpenStreetMap servers β€” self-host tiles for production privacy.
88
+ - No TLS in the container β€” terminate TLS at your reverse proxy (devices require WSS/HTTPS for mic access anyway).
89
+ - On HF Spaces, storage is ephemeral: the SQLite file resets on rebuild (fine for demos; use a mounted volume or external DB for real data).