During the week, there's always something happening on campus: staff meetings, classes, outside events. But the TV in our campus operations office stays on the same layout, and sometimes it's looking at empty rooms, playgrounds or parking lots. I figured it would be more useful if the layout changed based on what's actually happening around campus.

We already book every room in eSPACE, and those bookings already show up on the monitor in the IT office (I wrote about that in eSPACE Calendar as Digital Signage). So I wrote espace-wave-sync. Every 15 minutes it reads today's events, swaps the right cameras into a shared "Daily View" layout, and drops a bookmark on each of those cameras for the length of the event. When nothing is happening, the layout falls back to a set of default cameras.

How it works

Flow diagram: eSPACE room bookings feed the Events Kiosk JSON feed, which feeds espace-wave-sync. The sync updates two things on the WAVE server: the Daily View layout and camera bookmarks.
espace-wave-sync reads the Kiosk’s feed every 15 minutes and writes to WAVE. It never talks to eSPACE directly.

The sync never talks to eSPACE directly. It reads a JSON feed from eSPACE Events Kiosk, the signage app I'd already built, so the eSPACE API token lives in one place.

Each run does four things:

  1. Pulls today's events and the rooms they're in.
  2. Maps each room to its cameras from a config file. Rooms without cameras map to an empty list.
  3. Fills the layout: cameras for events happening now come first, taking turns when several events compete for tiles, then all-day events, then the default cameras, up to a tile cap. I use 16 for a 4x4 grid.
  4. Bookmarks each current event on each of its cameras, once.

It records what it has already added and bookmarked in a small state.json file on disk. A restart or crash doesn't create duplicate tiles or bookmarks, which I confirmed by restarting the container in the middle of an event.

Three things that tripped me up

None of these are in the WAVE docs in a way I could find, so here they are.

1. The certificate is issued to the server ID, not the IP

WAVE's self-signed certificate names the server's ID (a long GUID), not its IP address or hostname. Connect by IP and TLS verification fails, which is where most guides tell you to turn verification off.

Don't. Export the server's certificate, trust it as a CA, and tell your HTTP client to expect the server ID as the TLS server name while still connecting to the IP. In espace-wave-sync that's two settings: WAVE_CA_CERT and WAVE_TLS_SERVERNAME. Verification stays fully on. If you run merged servers, each one has its own ID and certificate, so add each one to the CA file.

2. Only admins can create layouts

Creating a layout through the REST API is admin-only. Rather than give the service admin rights, I had an admin create one empty shared layout by hand. The sync finds it by name and only ever adds or removes items inside it. It never creates, renames or deletes layouts.

3. A 404 error that's really a permissions problem

When the service account couldn't see a camera, bookmark requests came back 404 "Device not found", not 403. WAVE hides devices you can't access instead of refusing you. If you're chasing a 404 on a camera you know exists, check the account's camera rights before you check the camera ID.

What permissions it actually needs (WAVE 6.0.5)

On WAVE 6.0.5, editing a shared layout's items through the REST API takes a Power User. I tried two lower roles first, each with rights granted on that one layout and a fresh login session.

Role triedLogin and read layoutAdd or remove tilesBookmarks
Viewer + all rights on the layoutWorks403 "not permitted to modify"404, cameras not visible
Advanced Viewer + rights on the layout and camerasWorks403 "not permitted to modify"Not tested yet
Power UserWorksWorksWorks

The fresh session matters. The service keeps its login session between runs, so after changing a role, restart it before you test, or you're testing the old permissions.

If you run it as a Power User, harden the account

Power User is broad, so treat these as required, not optional:

  • A dedicated local account, not a cloud account and not anyone's personal login.
  • A long, unique password, stored only in your password manager and the service's environment file. Rotate it if it's ever exposed, even in a screenshot.
  • Lock down the environment file (chmod 600) and keep it out of the repo. The repo has secret scanning and a pre-commit check for exactly this reason.
  • Run the service on an internal host only. It never needs to be reachable from the internet.

What's next

Two questions are still open, and I'll update this post when I have answers:

  • Is there a narrower role that works? I couldn't find a right that lets a non-Power User modify one shared layout's items through REST v3. A custom role with edit rights on just that layout and its cameras is the obvious thing to try.
  • Does WAVE 6.1.2 change any of this? We're upgrading soon, and I'll re-test the endpoints and the permissions afterward.

After that, I'd like to try motion and person detection through WAVE's event rules, so a busy room can surface even when nothing is on the calendar.

The code is on GitHub at spiff888/espace-wave-sync. If you try it, or know the answer to the permissions question, open an issue there.