House Pub AV Control

A Q-SYS audio system, an in-room touch panel, and a DMX lighting rig that follow the same rules as the rest of the pub.

The I-House pub shares a building with residents who sleep upstairs. Its sound system had to be easy for a bartender, impossible to leave too loud at 3 AM, and visible to the software that runs the rest of the pub. This page covers the audio, the touch panel, and the lighting. The TVs and admin are in House Pub Platform, and song identification is in Now Playing.

In plain words. A bar under people’s bedrooms needs one simple panel for sound, lights, and projector, and a promise that the music cannot be left too loud overnight no matter who touches what. This is that panel, and the system behind it.

At a glance

  • Status: in production.
  • The hub: a Q-SYS Core 110f, the audio brain of the room.
  • The room: three audio areas (Banquet, Bar, and Stage), four lighting fixtures, and one touch panel.
  • Overnight rules: a loudness ceiling from 2 AM to 9 AM by default, and a reset at 4 AM.

See it

The touch panel's Home page: Sound buttons for Default and Party, Lights controls with Autopilot and Blackout, Projector on and off, and a note that the panel closes itself at 4 AM
The Home page of the touch panel. Sound presets on the left, lights in the middle, projector on the right, and a note that the room closes itself at 4 AM.
The touch panel's Lighting page: Autopilot, Dark if quiet, Song colors, and Match TVs toggles, a brightness control, and Moments buttons for Flash, Last Call, Birthday, and Closing
The Lighting page. Autopilot lets the lights follow the music on their own. Moments are one-shot effects such as Last Call.

How it works

  1. Every music source (the house account, a bartender’s laptop, a guest DJ) reaches the Q-SYS Core.
  2. The Core mixes it for each area and sends it to the amplifiers and speakers.
  3. The touch panel talks to the Core, and a Raspberry Pi turns the room’s audio and the staff’s choices into lighting.
flowchart LR
  S[Music sources] --> C[Q-SYS Core]
  C --> A[Amplifiers]
  A --> Sp[Speakers]
  Panel[Touch panel] <--> C
  C -. HTTP .-> Pi[Raspberry Pi]
  Pi --> L[Lights]

The Core also controls the video switcher and projector, and an IR and relay controller handles gear with no network interface. The amplifiers’ control interface exposes metering and settings, not audio. That mattered later: anything that needs to hear the room has to tap upstream, at the Core, which is how Now Playing works.

Problems I had to solve

Someone nudges a fader back up at 3 AM

Residents’ sleep is the constraint that matters most, so the quiet window is enforced rather than scheduled. A snapshot recall would set gains low once, and nothing would stop a later nudge. Instead a Core control script holds a loudness ceiling continuously during the quiet window (2 AM to 9 AM by default):

  • It clamps on every change and rechecks on a timer, computing the window from the Core’s own clock so a reboot at 3 AM comes back already enforcing.
  • Staff can lift the ceiling for the night with a PIN. The Core stores only a verifier, never the PIN, and the admin can rotate it remotely.
  • Two things I learned the hard way. The Core’s clock can be minutes slow even with the right time zone, so the curfew started 13 minutes late until I exposed the clock to the admin page. And the built-in digest function returned a result that never matched my server’s, so no correct PIN was accepted until I implemented SHA-256 directly in Lua 5.3 integer arithmetic and tested it against Python.

The panel has to work when the computer behind it does not

The touch panel is a native Q-SYS interface rendered by the Core, on purpose: audio, Quiet Mode, and the 4 AM close keep working when the Raspberry Pi is rebooting. The lighting and screens plugins poll the Pi for status, send it commands when a button is pressed, and show it as offline when it is unreachable.

  • Home page. Sound-stage buttons recall a saved snapshot (Default or Party; a Movie slot is planned), alongside lighting and projector controls. Snapshots are meant to be scoped to only the controls a mode may change, so EQ and routing stay put.
  • Audio page. A tab for each area plus a Bass tab. Each area has a mute button that needs a second tap to confirm, so a stray touch cannot silence a room.
  • Lighting page. Four tabs (Auto, Color, Effects, Moments) built on a custom plugin, with an online indicator for the Pi.
  • Screens. A plugin with three toggles for what the right bar TV and the projector show (song card and lyrics).
  • Notices and idle behavior. The admin can push pop-ups and a ticker onto the panel, such as “bar source is out of order”, and a pop-up always has an OK button so it never blocks the way back. An idle panel returns to Home, then to the splash screen.

The room is left in a mess at closing time

At 4 AM a control script on the Core puts the room to bed once: every audio fader goes to full travel and is muted (ready, but silent), the projector turns off, the screen goes up, and a signal tells the Pi to black out the lights. It is a one-time reset, not a hold. The only hold the Core’s scripts enforce is Quiet Mode, and the two scripts act on different controls, so the 9 AM release does not undo the 4 AM mutes.

Lighting that follows the room without anyone touching it

A Raspberry Pi acts as the DMX master for four RGB par cans, through a USB RS-485 adapter. A normal bar runs canned chases. This one reacts to the room: it builds a per-frame feature bus from the live audio (energy, spectral tilt, onset density, and build and drop dynamics, with tempo from a beat tracker used for style and strobe) and drives a five-state choreography: ambient, groove, build, drop, and breakdown, with hysteresis so it does not flicker between states. The wash color follows the music, drops punch through the blend, and the night follows an arc, set by the time of night, that winds down to amber at last call. Matching the TV theme is a separate option. It calibrates itself once per night from completed earlier nights, never the current one, to avoid a feedback loop.

Admin changes travel over a WebSocket held open by a Durable Object, so a change from a phone reaches the lights almost immediately instead of waiting for a poll that shared a loop with song identification.

Knowing who changed what on a shared panel

Every writable control on the lighting, screens, and quiet pages reports a closed, validated event with the exact control, command, before and after values, and an outcome. Events are queued in an on-disk outbox on the Pi until the server acknowledges them, so a network drop does not lose history. The allowed control names live in one contract file that the panel plugin, the Pi, and the Worker are all compared against in CI, with a test that runs a local no-op press from the plugin through the Pi and the real Worker handler to prove that no layer rejects a valid event and blocks the queue.

Native controls such as faders and mutes never pass through software I control, so the Pi also reads the Core’s writable controls about every 30 seconds and records differences. Those rows are labeled as observations, not attributions: the Core does not report which panel or script moved a fader, and a change made and undone between two reads is not seen. The log says so.

The admin separates an AV operator, who runs sound and lights on a shift and can adjust the Quiet Mode schedule, from an AV engineer, who owns the Q-SYS design: the lock policy that decides which faders anyone may touch, saved states, and restores. Restoring a saved Q-SYS state rewrites every control in the room in one poll, so it has its own permission.

One Raspberry Pi is a single point of failure

The Pi handles song identification, lighting, and the admin relay. The two K-array amplifiers in the room already run Linux and sit on the same network, so I have been investigating running the Pi’s services on them, to give the room a second home for those services and eventually retire the Pi. The work is deliberately cautious, because these are production amplifiers:

  • Lab first. Vendor images are examined offline and exercised in emulated virtual machines with disposable disks, with every input pinned by hash.
  • Evidence, not assertion. Each trial on a real amplifier produces a redacted receipt, and each write-up states what the result does not prove.
  • Where it stands. An owner-controlled maintenance channel exists on both amplifiers, and I verified it survives a reboot and a same-version firmware reinstall. An ordered Wi-Fi fallback policy is prepared and tested offline. Moving the services and failing over to the amplifiers are still design, not deployment, and the amplifiers’ normal operation has not been changed.

Tools

Q-SYS Designer (Lua control scripts and plugins), QRC over JSON-RPC, Python on a Raspberry Pi, DMX-512 through a USB RS-485 adapter (pyserial), Cloudflare Workers and Durable Objects.

Under the hood

The whole curfew is this function (about 10 lines)

Why this matters: during the quiet window, any gain above its ceiling is put straight back. The clamping flag stops the script from reacting to its own correction, which would loop forever.

-- qsys/quiet-mode.lua (excerpt)
-- Clamp one target back under its ceiling (no-op if already under / unresolved).
local function clampOne(g)
  if g.co and type(g.co.Value) == "number" and g.co.Value > g.ceiling then
    clamping = true
    g.co.Value = g.ceiling
    clamping = false
  end
end
Words used on this page
  • DSP and Q-SYS Core: a computer that mixes and routes audio for a whole building.
  • UCI: the touch-screen interface that Q-SYS draws and runs itself.
  • Snapshot: a saved set of audio settings that one button recalls.
  • DMX: the standard signal for controlling stage lights.
  • Par can: a simple stage light that washes a room in color.
  • Verifier: a stored fingerprint of a PIN that can check a PIN without keeping it.
  • Durable Object: a small piece of cloud state that stays consistent under many simultaneous requests.