mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-29 09:23:16 -06:00
Self-contained examples for the PiP overlay API (POST /api/pip), each with a CSP-safe query-param overlay (external JS), config.example.json, zero runtime deps, an offline test, and a README: - PIP-Announce-Broadcast manual one-shot message to a screen/group - PIP-Weather-Widget Open-Meteo current conditions (keyless) - PIP-Air-Quality Open-Meteo US AQI widget (keyless) - PIP-Crypto-Ticker CoinGecko price strip (keyless) - PIP-News-Ticker scrolling RSS/Atom headlines - PIP-Room-Status-Calendar ICS-driven Available/Busy room sign - PIP-Event-Countdown client-side countdown, auto-clears at zero - PIP-Welcome-Board rotating welcome/birthday cards from CSV - PIP-Fundraiser-Thermometer goal-progress bar from local/URL JSON - PIP-QR-Rotator rotating QR codes, encoded client-side - PIP-Incident-Webhook event-driven: red on firing, clear on resolved Also includes the CAP-AU (NSW RFS) and US NWS/NOAA emergency-alert monitors that push expiry-aware PiP overlays. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 KiB
4 KiB
CAP-AU → ScreenTinker PiP alert monitor (example)
Watches a CAP-AU emergency feed (default: the NSW RFS majorIncidentsCAP feed) and,
when a qualifying alert covers a screen's location, pushes a PiP web overlay to that
screen — then clears it when the alert expires, is cancelled, or leaves the feed.
It uses the existing ScreenTinker PiP API (POST /api/pip, POST /api/pip/clear).
No server changes required.
How it works
CAP-AU feed ──poll──▶ parse (EDXL unwrap) ──▶ gate (AlertLevel + geofence) ──▶ POST /api/pip
◀─ clear on expiry/cancel/gone
Three non-obvious things this example gets right, learned from the real feed:
- It's EDXL-DE wrapped. The feed is not a flat list of CAP alerts — each
<alert>is embedded underEDXLDistribution > contentObject > xmlContent > embeddedXMLContent.cap-parse.jsunwraps that. - Gate on
AlertLevel, not CAP<severity>. RFS leaves<severity>/<urgency>asUnknownfor routine incidents. The real urgency lives in a<parameter>namedAlertLevel(Planned Burn/Advice/Watch and Act/Emergency Warning). Default threshold shows onlyWatch and ActandEmergency Warning, so routine hazard-reduction burns never hit a screen. - CAP coordinates are
lat,lon— the reverse of GeoJSON'slon,lat. The geofence keeps that flip in one place; feeding raw CAP coords into alon,latlibrary is the classic "matches on the wrong side of the planet" bug.
Setup
npm install
cp config.example.json config.json # then edit it
In config.json:
api_base— your ScreenTinker server URL.api_token— anst_API token with thefullscope (PiP is fleet-affecting and full-trust, so the route requires it). Create one in the dashboard's API-token section.overlay_base_url— wherealert-overlay.htmlis hosted, reachable by the player (the player fetches the overlay URL directly). Drop the file on the ScreenTinker host or any static host.screens— each screen'slat/lon(its physical location, used for the geofence) and thedevice_id(a device or group id) to push the overlay to.alert_levels— the AlertLevel threshold (default["Watch and Act","Emergency Warning"]).
Run
npm start # uses ./config.json
# or
node monitor.js /path/to/config.json
On Ctrl-C it clears any overlays it put up, so a screen never keeps a stale alert.
Test the parser (no server needed)
npm test
Runs the EDXL/gate/geofence logic against fixture-feed.xml (two real RFS planned burns
plus a synthetic Emergency Warning and a distant Watch-and-Act) and asserts that only the
in-area Emergency Warning would fire.
Files
| File | Purpose |
|---|---|
monitor.js |
Poll loop + PiP show/clear lifecycle (dedup by CAP identifier). |
cap-parse.js |
EDXL unwrap, AlertLevel/field extraction, polygon+circle geofence, gate. |
alert-overlay.html |
The web overlay the PiP points at; renders from ?level=&headline=&area=…. |
config.example.json |
Copy to config.json and fill in. |
fixture-feed.xml / test-parse.js |
Offline test of the parser/gate. |
Notes / next steps
- Targeting model: one screen → one
device_idhere. For many screens you'd likely drivescreensfrom your device inventory (each device's stored location) rather than hand-listing them. msgTypeUpdate: currently an Update re-shows only if the identifier changed; if RFS reuses an identifier on update you may want to force a re-push (clear + show) to refresh the overlay content.- Other states/agencies: point
feed_urlat other CAP-AU sources (state SES/fire services). Field names in<parameter>are RFS-specific; other agencies differ, so theAlertLevelmapping may need adjusting per source. - This is an example/reference, not a life-safety system. Don't make it the only way people are warned.