screentinker/Examples/PIP-Room-Status-Calendar/README.md
screentinker 0b138f10c6
Add PiP overlay example recipes (#132)
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>
2026-06-18 20:20:37 -05:00

108 lines
3.9 KiB
Markdown

# Room Status sign (calendar-driven Available / Busy)
Turns a ScreenTinker display into a meeting-room sign. It polls an **ICS calendar
feed** and pushes a [PiP](../../docs) web overlay that shows **AVAILABLE** (green) or
**BUSY** (red) plus the current/next meeting time. Re-pushed every poll so the state
stays fresh; cleared when you stop the script.
No dependencies — Node 18+ only.
## How it works
```
ICS feed ──poll──> room.js ──POST /api/pip (type=web)──> player renders room-overlay.html
```
- `room.js` fetches the calendar, parses VEVENTs, and decides busy/free at *now*.
- The overlay is `room-overlay.html` + `room-overlay.js`, served by the signage server
and rendered by the player in an iframe. The script reads the status from the URL
query string (the server CSP forbids inline scripts, so the logic lives in the
external `.js`).
## Get an ICS URL
- **Google Calendar:** Calendar settings → *Integrate calendar* → **Secret address in
iCal format**. (Treat it like a password.) For a room, use the room/resource calendar.
- **Outlook / Microsoft 365:** Calendar → Share → **Publish**, then copy the **ICS** link.
- Any CalDAV/ICS publisher works. The feed must be reachable by the machine running `room.js`.
## Serve the overlay assets
Copy `room-overlay.html` and `room-overlay.js` into the signage server's web root (the
same directory that serves the SPA), so they're reachable at
`https://<your-server>/room-overlay.html`. They must be **same-origin** with the player
(the overlay runs in an iframe under the server's CSP).
## Configure
```bash
cp config.example.json config.json
# edit config.json: api_base, api_token (st_ token with the 'full' scope),
# overlay_base_url, device_id (a device OR a group id), room_name, ics_url
```
## Run
```bash
npm start # or: node room.js config.json
```
Stop with Ctrl-C — it clears the overlay on the way out.
### Local quick-start (self-signed dev server)
For a local ScreenTinker instance on `https://localhost:3443` with a self-signed cert:
```json
{
"room_name": "Aspen Room",
"api_base": "https://localhost:3443/",
"api_token": "st_REPLACE_WITH_A_FULL_SCOPE_TOKEN",
"overlay_base_url": "https://localhost:3443/room-overlay.html",
"device_id": "DEVICE_OR_GROUP_ID",
"ics_url": "https://calendar.google.com/calendar/ical/.../basic.ics",
"poll_interval_sec": 60
}
```
```bash
NODE_TLS_REJECT_UNAUTHORIZED=0 node room.js config.json
```
(`NODE_TLS_REJECT_UNAUTHORIZED=0` only to accept the dev cert — never in production.)
Remember to copy `room-overlay.html` + `room-overlay.js` into the server's web root first.
## Offline demo / test
`test.js` runs the ICS parser and status logic against `fixture-room.ics` at a fixed
clock — no server, no network:
```bash
npm test
```
You can also drive the overlay against the fixture by setting `ics_file` (instead of
`ics_url`) in `config.json`.
## Config reference
| key | meaning |
| --- | --- |
| `room_name` | label shown on the overlay |
| `api_base` | ScreenTinker server base URL |
| `api_token` | `st_` API token with the **full** scope |
| `overlay_base_url` | URL where `room-overlay.html` is served (same-origin with the player) |
| `device_id` | target device **or** group id |
| `ics_url` | calendar feed URL (or use `ics_file` for a local file) |
| `poll_interval_sec` | refresh cadence (default 120) |
| `colors.available` / `colors.busy` | band colors, 6-hex no `#` |
| `overlay.position` | `center` (default), `top-right`, `top-left`, `bottom-right`, `bottom-left` |
| `overlay.width` / `overlay.height` / `overlay.border_radius` | overlay box geometry |
## Time-zone note
DTSTART/DTEND in UTC (`…Z`) are handled exactly. A *floating* time (no `Z`) is read as
the **local time of the machine running `room.js`**, and `TZID` parameters are not
resolved to their zone. For a single room whose host shares the room's timezone this is
correct; for cross-timezone calendars, publish the feed in UTC.