- Koa server on port 10000 with WebSocket live feed from 112-nu.nl RSS - PM2 watch mode for auto-restart on file changes - Dark navy UI with per-type color accents (fire/ambulance/police/rescue) - Slide-in filter panel with service type + 12 Dutch province filters - Card click opens detail modal: parsed priority (A1/A2/MGS), vehicle number, rit/bon number, alarm type, meldkamer, and eenheden - Server-side Nominatim geocoder (cached, rate-limited) powering an interactive Leaflet/OpenStreetMap map in the modal (CartoDB Voyager tiles) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
2.5 KiB
Markdown
98 lines
2.5 KiB
Markdown
# P-2000 Monitor
|
|
|
|
Real-time Dutch emergency services (P-2000) channel monitor. Koa server + WebSocket + RSS polling, dark shadcn-style UI.
|
|
|
|
## Stack
|
|
|
|
| Layer | Tech |
|
|
|-------------|-------------------------------------------------------|
|
|
| Server | Node.js 20, Koa 2, koa-static, koa-router |
|
|
| WebSocket | `ws` library — attached to same HTTP server |
|
|
| P-2000 data | RSS polling via axios + xml2js (30s interval) |
|
|
| Process mgr | PM2 with watch mode (`pm2.config.js`) |
|
|
| Frontend | Vanilla JS, TailwindCSS Play CDN, shadcn-style design |
|
|
|
|
## Port
|
|
|
|
`10000` (override via `PORT` env var)
|
|
|
|
## Running
|
|
|
|
```bash
|
|
# Install
|
|
npm install
|
|
|
|
# Dev (PM2 watch mode — auto-restart on src/ or server.js changes)
|
|
npm run dev
|
|
|
|
# Logs
|
|
npm run logs
|
|
|
|
# Stop
|
|
npm run stop
|
|
|
|
# Plain node (no PM2)
|
|
npm start
|
|
```
|
|
|
|
## Project layout
|
|
|
|
```
|
|
server.js # Koa HTTP + WebSocket server, P-2000 event bridge
|
|
src/p2000.js # EventEmitter: RSS fetcher, XML parser, type detector
|
|
public/index.html # Single-page frontend (TailwindCSS + vanilla JS)
|
|
pm2.config.js # PM2 watch config
|
|
logs/ # PM2 out/error logs (git-ignored)
|
|
```
|
|
|
|
## P-2000 Feed source
|
|
|
|
**Primary:** `https://112-nu.nl/hulpdiensten/rss` (all services combined, polled every 30s)
|
|
|
|
Per-service feeds also available but not used (combined is sufficient):
|
|
- `https://112-nu.nl/brandweer/rss`
|
|
- `https://112-nu.nl/ambulance/rss`
|
|
- `https://112-nu.nl/politie/rss`
|
|
|
|
Change the feed URL via `COMBINED_FEED` constant in `src/p2000.js`.
|
|
|
|
## Message types
|
|
|
|
Detected from `<category>` RSS tag first, then keyword fallback:
|
|
|
|
| Category code | Type |
|
|
|---------------|-------------|
|
|
| BRA, BRW | `fire` |
|
|
| AMB, MKA | `ambulance` |
|
|
| POL | `police` |
|
|
| KNRM, HELI | `rescue` |
|
|
|
|
Extend `CATEGORY_MAP` or `KEYWORD_MAP` in `src/p2000.js` to tune detection.
|
|
|
|
## WebSocket protocol
|
|
|
|
Client connects to `ws://localhost:10000/ws`
|
|
|
|
Server → Client messages:
|
|
```json
|
|
{ "type": "history", "messages": [ ...MessageObject ] }
|
|
{ "type": "message", "message": MessageObject }
|
|
```
|
|
|
|
`MessageObject`:
|
|
```json
|
|
{
|
|
"id": "unique-guid",
|
|
"title": "P1 Melding Brand - Rotterdam",
|
|
"description": "Extended alert text",
|
|
"pubDate": "2024-01-15T12:34:56.000Z",
|
|
"link": "https://...",
|
|
"type": "fire",
|
|
"region": "Zuid-Holland"
|
|
}
|
|
```
|
|
|
|
## Health check
|
|
|
|
`GET /health` → `{ status, uptime, messages }`
|