screentinker/Examples/PIP-QR-Rotator/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

111 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PiP QR Rotator
Rotate **scannable QR codes** through a corner of your ScreenTinker screens via the PiP
API — Guest Wi-Fi, the lunch menu, a feedback survey, a "scan to download" link, the event
schedule, a checkout/tip link… anything a phone camera should grab.
The QR codes are generated **client-side, in the overlay itself** — no QR web service, no
image hosting, no external libraries, no network calls. That keeps it fast, private, and
compliant with the player's Content-Security-Policy (`script-src 'self'`).
```
qr.js --(POST /api/pip, type:web)--> player
uri = qr-overlay.html?data=<payload>&label=<caption>
|
qr-overlay.js encodes <payload> into a QR matrix and paints it on a <canvas>
every rotate_interval_sec, qr.js pushes the next entry (player = last-show-wins)
```
## Files
| File | Purpose |
|------|---------|
| `qr.js` | Rotates through `config.entries`, pushing each as a PiP overlay. `--clear` removes it. |
| `qr-overlay.html` / `qr-overlay.js` | The overlay page the player loads in an iframe. **Generates the QR client-side.** Must be served by your ScreenTinker host (same-origin with the player). |
| `config.example.json` | Copy to `config.json` and fill in. |
| `test.js` | Offline unit test (`npm test`) — pure helpers + the QR encoder's Reed-Solomon core. |
## Setup
1. **Mint a token.** In the dashboard create an API token with the **`full`** scope (PiP
is fleet-affecting and renders web content, so it requires `full`).
2. **Serve the overlay assets.** Copy `qr-overlay.html` and `qr-overlay.js` into the
directory your ScreenTinker server serves at the web root (its `frontend/` dir), so they
live at `https://<your-host>/qr-overlay.html`. They **must** be same-origin with the
player — the server applies a CSP that only allows same-origin scripts, which is exactly
why the QR is drawn by `qr-overlay.js` (no CDN).
3. **Configure.** `cp config.example.json config.json` and set `api_base`, `api_token`,
`overlay_base_url` (the URL from step 2), `device_id` (a device **or** a group id), and
your `entries`.
4. **Run.** `node qr.js` — it pushes the first code immediately, then rotates every
`rotate_interval_sec`. `Ctrl-C` clears the overlay.
## Configuration
| Key | Meaning |
|-----|---------|
| `entries` | Array of `{ label, data }`. `data` is the QR payload (required); `label` is the caption shown under it. |
| `rotate_interval_sec` | Seconds between entries (default `15`). A single entry just stays up. |
| `position` | `top-left`, `top-right`, `bottom-left`, `bottom-right` (default), or `center`. |
| `width` / `height` | Overlay box px (default `360` × `420` — tall so the caption fits under the code). |
| `border_radius`, `opacity` | Optional overlay styling. |
### QR payload cookbook
| Use | `data` value |
|-----|--------------|
| Open a link | `https://example.com/menu` |
| **Join Wi-Fi** (auto-connect) | `WIFI:T:WPA;S:<ssid>;P:<password>;;` — for an open network use `WIFI:T:nopass;S:<ssid>;;` |
| Pre-filled email | `mailto:hi@example.com?subject=Feedback` |
| Phone number | `tel:+15551234567` |
| Plain text | any text |
> Wi-Fi note: special characters in the SSID/password (`\ ; , : "`) must be backslash-escaped
> per the Wi-Fi QR spec, e.g. `P:p\;w\:d`.
## Local quick-start (this repo)
The local ScreenTinker instance serves on `https://localhost:3443/` (self-signed) and the
registered player is device `DEVICE_OR_GROUP_ID`.
```bash
# from the repo root: serve the overlay assets same-origin with the player
cp Examples/PIP-QR-Rotator/qr-overlay.html Examples/PIP-QR-Rotator/qr-overlay.js frontend/
# then in this dir:
cp config.example.json config.json
# edit config.json:
# "api_base": "https://localhost:3443/"
# "api_token": "st_REPLACE_WITH_A_FULL_SCOPE_TOKEN"
# "overlay_base_url": "https://localhost:3443/qr-overlay.html"
# "device_id": "DEVICE_OR_GROUP_ID"
# self-signed cert -> let Node accept it for this run
NODE_TLS_REJECT_UNAUTHORIZED=0 node qr.js
```
## Testing
```bash
npm test
```
Runs offline (no network, no player): validates the rotation/URL helpers and verifies the
embedded QR encoder's Reed-Solomon math against the published QR generator polynomials, plus
structural checks (finder/timing patterns, version sizing). For the real proof, point it at
a screen and **scan it with your phone**.
## Notes & limits
- The encoder is a compact **byte-mode** implementation of the QR spec (ISO/IEC 18004),
based on Nayuki's reference algorithm (MIT). Byte mode handles any UTF-8 payload; it
auto-selects the smallest version and the best mask, and boosts the error-correction level
for free when there's spare capacity (more robust scanning).
- Keep payloads reasonably short for at-a-distance scanning — long URLs make a denser code.
Use a link shortener for long destinations.
- Like all PiP overlays, this is **ephemeral**: a player reboot drops it (re-run to restore),
and the script clears it on `Ctrl-C`.