Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions signage/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Deployed Google Apps Script /exec URL. Leave blank to use the bundled sample feed.
SIGNAGE_GOOGLE_FEED_URL=

# Must match the Apps Script property SIGNAGE_FEED_TOKEN.
SIGNAGE_FEED_TOKEN=

# Selects the room, scheduling rules, and visual theme for this device.
# Valid values: classroom or mezzanine
SIGNAGE_DISPLAY=classroom

# Optional local settings.
PORT=4173
SIGNAGE_REFRESH_SECONDS=60
SIGNAGE_VISIBILITY_REFRESH_MINUTES=30
5 changes: 5 additions & 0 deletions signage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
cache/
node_modules/
outputs/
work/
120 changes: 120 additions & 0 deletions signage/CONTINUE_HERE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Continue work on the Scripps Sandbox signage

This file is the durable handoff for continuing the project without the original Codex conversation.

## Project identity

- Canonical repository: `https://github.com/ScriptsSandbox/sandbox-appsscript/tree/main/signage`
- Repository folder: `signage/`
- Local source folder: `/Users/ritterratter/Documents/Codex/2026-08-06/referenced-chatgpt-conversation-this-is-an`
- Displays: Sandbox Classroom and Mezzanine Conference Table signage, 3840 × 2160, 16:9
- Classroom Raspberry Pi: `sandbox@sandbox-signage-classroom.local`, profile `classroom`
- Mezzanine Raspberry Pi: hostname to be assigned, profile `mezzanine`
- Installed Pi application: `/home/sandbox/sandbox-signage`
- Pi service: `sandbox-signage.service`
- Local preview: `http://127.0.0.1:4173/`
- Review controls: `http://127.0.0.1:4173/?controls=1`

## Start a future Codex task

Open this folder as the Codex workspace and begin with:

> This is an existing Raspberry Pi digital-signage project. Read `CONTINUE_HERE.md`, `README.md`, and the phase documents before changing anything. Preserve the approved visual system and current data rules. Inspect the implementation, make the requested change, run all tests, and show me the local result before deploying to the Pi.

Then describe the desired change. The previous chat is not required.

## Approved information design

- Fixed 3840 × 2160 canvas scaled to the available screen
- Source Sans 3 for most text
- Jost for numbers and the primary availability word
- Orange environmental rail on the left
- Aqua primary field
- Current time and date
- Current classroom status and today's graphical schedule
- Five-workday summary on the right
- Water temperature, visibility, and tide conditions
- Quiet source and stale-data indicators
- One event per line when a workday contains multiple bookings
- Actual time ranges on separate lines when a day contains multiple open windows
- Mezzanine theme: navy `#182B49` rail, warm cream `#F5F0E6` field, black main text, white rail text

## Data sources and rules

- `H-Lab - 1 - Sandbox Classroom (24)` is authoritative for classroom bookings.
- `H-Lab - 2 - Mezzanine Conference Table (12)` is authoritative for mezzanine reservations.
- `Sandbox Access` events titled/classified as `door_open` define public access windows.
- An empty access-calendar day is closed.
- Time outside a `door_open` event is closed.
- The mezzanine is available only during a Sandbox Access `door_open` window; an empty access-calendar day is closed on both displays.
- Both displays show a 20-minute opening countdown and a 15-minute closing countdown based on the actual access window. Closed and pre-opening states are monochrome.
- Access events classified as closed or maintenance override open windows.
- Google Apps Script provides a display-safe public feed; deployment details are in `PHASE2_SETUP.md`.
- NOAA station 9410230 provides La Jolla tide predictions and water temperature.
- Just Get Wet provides supplemental reported underwater visibility. It is cached and attributed.
- The server and browser retain last reliable data when a source or network is unavailable.

## Local change workflow

1. Ask Codex to inspect this folder and make the change.
2. Keep `http://127.0.0.1:4173/?controls=1` open for state and time testing.
3. Run the complete test suite before deployment.
4. Review the live-data screen without `?controls=1`.
5. Deploy only after the local result is approved.

Tests:

```sh
npm test
```

Local server:

```sh
npm run serve
```

If `npm` is unavailable on the Mac, ask Codex to run the local server and tests using its bundled Node runtime.

## Update the Raspberry Pi

Run from the Mac Terminal:

```sh
cd /Users/ritterratter/Documents/Codex/2026-08-06/referenced-chatgpt-conversation-this-is-an && sh scripts/deploy-to-pi.sh sandbox@sandbox-signage-classroom.local
```

For the mezzanine, substitute its hostname and append `mezzanine` as the second argument. The explicit classroom form is:

```sh
sh scripts/deploy-to-pi.sh sandbox@sandbox-signage-classroom.local classroom
```

The installer preserves the previous Pi installation in a timestamped backup, replaces the application, restarts the service, and offers to reboot. Reboot so the kiosk browser loads new JavaScript and styling.

## Useful Pi diagnostics

```sh
ssh sandbox@sandbox-signage-classroom.local
systemctl status sandbox-signage.service
journalctl -u sandbox-signage.service -n 50 --no-pager
rpi-connect status
rpi-connect doctor
```

On networks that block `.local` discovery or local SSH, use the Raspberry Pi Connect remote shell.

## Files to read before changing behavior

- `README.md`: application overview and live-data policy
- `PHASE2_SETUP.md`: Google feed and source classification
- `PHASE3_DEPLOYMENT.md`: Pi kiosk installation and update process
- `server/data-service.mjs`: data normalization, access rules, and caching
- `src/model.js`: current room state and schedule segmentation
- `src/app.js`: display rendering
- `src/styles.css`: fixed-canvas layout and visual system
- `tests/`: executable behavior specifications

## Source and configuration backup

The application source is maintained in the `signage/` directory of `ScriptsSandbox/sandbox-appsscript`. The `.env` file is deliberately excluded from Git because it contains the deployed feed configuration required by a new installation. Back up `.env` separately in a secure location; do not commit it.
51 changes: 51 additions & 0 deletions signage/PHASE2_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Phase 2 setup

The application-side integration is complete. The Google-hosted feed has been deployed and this workspace's `.env` already points to it. The steps below are retained for rebuilding or moving the feed later.

## 1. Create the read-only Google feed

1. Open [Google Apps Script](https://script.google.com/) with the account that can read the Sandbox calendars and settings workbook.
2. Create a new project named **Sandbox Signage Feed**.
3. Replace its `Code.gs` with the contents of `apps-script/Code.gs`.
4. Confirm **Project Settings → Time zone** is **Pacific Time – Los Angeles**.
5. Choose **Deploy → New deployment → Web app**.
6. Set **Execute as** to yourself and **Who has access** to **Anyone**. The schedule is already public, and this allows the Raspberry Pi to refresh without an interactive Google login.
7. Authorize the script's read access to the spreadsheet and calendars, deploy it, and copy the `/exec` URL.

The feed returns only display-ready room titles, times, access classifications, and settings. It does not return attendees, descriptions, email addresses, or Google OAuth tokens.

## 2. Configure the local server

Copy `.env.example` to `.env`, then add the web-app URL:

```text
SIGNAGE_GOOGLE_FEED_URL=https://script.google.com/macros/s/DEPLOYMENT_ID/exec
SIGNAGE_FEED_TOKEN=
SIGNAGE_DISPLAY=classroom
PORT=4173
SIGNAGE_REFRESH_SECONDS=60
```

`.env` and the disk cache are excluded from source control.

## 3. Run and verify

```sh
npm run serve
```

Open [http://127.0.0.1:4173](http://127.0.0.1:4173). The footer should change from **SETUP FEED · USING SAMPLE CALENDAR DATA** to **DATA CURRENT** once both the Google feed and NOAA respond.

Set `SIGNAGE_DISPLAY=mezzanine` on the second Pi. The server adds `display=classroom` or `display=mezzanine` to the feed request automatically.

The health endpoint at [http://127.0.0.1:4173/api/health](http://127.0.0.1:4173/api/health) reports whether the server is currently using a successful refresh or a cached fallback.

## Source policy

- Classroom bookings come only from **H-Lab - 1 - Sandbox Classroom (24)**.
- Sandbox Access `door_open` events define the hours when the space is open. Empty days and time outside those events are closed.
- Sandbox Access `closed` and `maintenance` events override open windows and block the classroom timeline.
- Sandbox Access `pickup_only` and `poster_pickup` events do not create general classroom availability.
- An active `closed` or `maintenance` LiveStatus override temporarily blocks both displays until its expiration time.
- Mezzanine reservations come only from **H-Lab - 2 - Mezzanine Conference Table (12)**. The table is available only within a Sandbox Access `door_open` window; outside those windows it is closed with the rest of the Makerspace.
- NOAA station **9410230 La Jolla** supplies tide predictions and water temperature.
48 changes: 48 additions & 0 deletions signage/PHASE3_DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Phase 3: Raspberry Pi deployment

The deployment script packages the current local application, transfers it to the Raspberry Pi, installs the required runtime and Chromium, and configures the display to recover automatically after a reboot or application failure.

## Deploy from the Mac

Run the matching command from the project folder on the Mac.

Classroom Pi:

```sh
sh scripts/deploy-to-pi.sh sandbox@sandbox-signage-classroom.local classroom
```

Mezzanine Pi (replace the hostname with the name assigned when that Pi is flashed):

```sh
sh scripts/deploy-to-pi.sh sandbox@sandbox-signage-mezzanine.local mezzanine
```

The second argument writes the correct profile into the packaged `.env`; it does not alter the Mac's local profile.

The first connection may ask whether to trust the Raspberry Pi's SSH host key. It may also request the `sandbox` account password for SSH and `sudo`. At the end, choose whether to reboot immediately.

## What the installer configures

- application files at `/home/sandbox/sandbox-signage`
- the live Google, NOAA, and visibility configuration from `.env`
- a `sandbox-signage.service` system service that restarts after failures
- Chromium kiosk mode through `~/.config/labwc/autostart`
- Chromium's local basic password store, so the unattended kiosk does not wait for a keyring unlock
- a standard desktop-autostart fallback for older Raspberry Pi OS desktops
- desktop autologin and disabled screen blanking through `raspi-config`
- user lingering so Raspberry Pi Connect's remote shell can remain available across reboots
- a timestamped backup of any previous signage installation

The application remains local to the Raspberry Pi at `http://127.0.0.1:4173/`. It is not exposed to other computers on the network.

## Update later

Run the same profile-specific deployment command again. The installer saves the previous version before activating the replacement.

## Useful checks on the Pi

```sh
systemctl status sandbox-signage.service
journalctl -u sandbox-signage.service -n 50 --no-pager
```
71 changes: 71 additions & 0 deletions signage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Scripps Sandbox signage

Returning to this project after a break? Start with [CONTINUE_HERE.md](./CONTINUE_HERE.md).

This is the shared, data-driven 3840 × 2160 application for the Sandbox classroom and mezzanine conference-table displays. One codebase supports both Raspberry Pis; the `SIGNAGE_DISPLAY` setting selects the room, scheduling rules, copy, and color theme.

| Profile | Calendar | Empty calendar means | Palette |
| --- | --- | --- | --- |
| `classroom` | H-Lab - 1 - Sandbox Classroom (24) | Closed unless Sandbox Access has a Door Open event | Orange, aqua, cream, black |
| `mezzanine` | H-Lab - 2 - Mezzanine Conference Table (12) | Closed unless Sandbox Access has a Door Open event | Navy `#182B49`, warm cream `#F5F0E6`, black, white |

## Run it locally

From this folder:

```sh
npm run serve
```

Then open [http://localhost:4173](http://localhost:4173).

Open [http://localhost:4173/?controls=1](http://localhost:4173/?controls=1) to show the scenario picker and time-of-day slider. The picker includes:

- standard day
- available all day
- multiple bookings
- closing soon
- class in progress
- closed all day
- offline fallback
- mezzanine conference table

## Test the display logic

This project has no production dependencies. With Node.js 20 or newer installed:

```sh
npm test
```

## Live-data behavior

The local server exposes `/api/display-data`. Depending on the selected profile, it combines:

- **H-Lab - 1 - Sandbox Classroom (24):** authoritative classroom bookings
- **H-Lab - 2 - Mezzanine Conference Table (12):** authoritative conference-table reservations
- **Sandbox Access:** authoritative public-access windows; a day is closed unless it contains a `Door Open` event, and closures or maintenance override those windows
- **Sandbox Summer Access Widget Settings:** timezone, refresh rate, temporary closed/maintenance overrides, and keyword classification
- **NOAA station 9410230:** La Jolla tide predictions and observed water temperature
- **Just Get Wet dive reports:** the latest locally reported underwater visibility; refreshed no more than every 30 minutes and cached if the page is unavailable

The renderer receives one normalized display object containing:

- the selected room and timezone
- today's open and close times
- today's classroom bookings
- the next five workday summaries
- water temperature, today's La Jolla tide points, and locally reported underwater visibility
- connectivity, staleness, and last-updated state

Both displays become monochrome while the Makerspace is closed. During the last 20 minutes before an access window they show an opening countdown; during the last 15 minutes of an access window they show a closing countdown. These transitions use the actual Sandbox Access event times, so temporary summer, finals, and holiday hours require no application change.

The server caches its last successful normalized response in `cache/display-data.json`. The browser also caches its last successful response in local storage. If either Google or NOAA is unavailable, the screen keeps showing the most recent reliable information and quietly marks it as stale.

Without a configured Google feed, the server deliberately labels the screen **SETUP FEED · USING SAMPLE CALENDAR DATA**. Scenario previews remain available with `?scenario=standard` and the review controls with `?controls=1&scenario=standard`.

See [PHASE2_SETUP.md](./PHASE2_SETUP.md) for the one-time Google Apps Script deployment and local configuration. Visibility is an informal third-party observation, so the display attributes it and treats it as supplemental rather than authoritative operating data.

## Raspberry Pi deployment

Phase 3 packages either profile as a self-starting Raspberry Pi kiosk. See [PHASE3_DEPLOYMENT.md](./PHASE3_DEPLOYMENT.md). Re-running the same deployment command installs future updates while retaining a timestamped copy of the previous version.
Loading