The hackbank consist of a php front-end that communicates with a python back-end over mqtt.
After checking out a deposit to an account, the screen displays a large EPC QR
code with the deposited amount and the account's username as the transfer
description. The recipient defaults to Hack42, IBAN NL43ABNA0136777643;
override deposit.epc.name and deposit.epc.iban in config.yaml if needed.
The payment details are also displayed as text. QR images are generated locally.
The dialog is a payment request, not a bank confirmation. Deposits are credited using the existing checkout flow; bank payments are not automatically verified. Closing the dialog or entering the next command clears the request. Startup also clears retained requests. Cash checkouts do not produce a transfer code. Multiple deposit lines for the same user are combined; purchases do not reduce the requested deposit amount. Amounts must be whole cents.
The payload follows EPC069-12. Support for scanning EPC codes depends on the banking app.
You need python, paho-mqtt-client and apache+php.
To install the python dependencies, run 'pip install -r requirements.txt'.
The POS serial port defaults to /dev/ttyKASSA. Install the udev rule on the
machine connected to the Prolific PL2303 USB serial adapter:
sudo install -m 0644 udev/99-kassa.rules /etc/udev/rules.d/99-kassa.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --action=add --subsystem-match=tty
sudo udevadm settle
ls -l /dev/ttyKASSAGive the backend user membership of dialout and restart the backend. Set
pos.serial.port: /dev/ttyKASSA in an existing config.yaml; local overrides
remain supported. The adapter has no unique serial number, so this rule assumes
only one 067b:2303 adapter is connected. With several identical adapters, narrow
the rule to the appropriate USB port before installing it.
Receipts use NV logo 1 already stored in the printer (FS p 1 3, the existing
LOGO constant). Upload a replacement once with:
# Stop the kassa backend before uploading.
.venv/bin/python load_printer_logo.py images/hack42.png --write --test-printThe script reads pos.serial from config.yaml (default /dev/ttyKASSA,
19200 baud). Use --config, --port, or --baudrate to override it. Without
--write it only prepares the image; --output logo.bin exports the FS q
command for offline inspection. --test-print prints the stored logo after
uploading. Restart the backend afterwards.
The default stored width is 192 dots; LOGO prints at twice the stored width
and height, so the printed width is at most 384 dots. --width changes the
stored width. Images retain their aspect ratio; transparent pixels and padding
become white. Only near-white pixels (all RGB channels at least 245) remain
white; every other color becomes solid black, without dithering.
Stored logos persist after power-off; receipt generation does not
upload the image again.
Epson's FS q command replaces all existing NV logos/graphics and resets the printer. Run the script only when changing the logo, with the backend stopped, and keep the printer powered on until it finishes. A successful serial write cannot confirm that the printer accepted the logo; verify the test print. The printer must support the same legacy NV commands already used for the receipt header.
Data is stored in 'data'. In production it is also committed to git, but for
local development it's probably better to use fake data and keep it out of git.
To get started, copy the files from fakedata.
For the self-contained UI development setup at http://localhost:4123:
docker compose -f compose.dev.yaml up -d --buildThis runs PHP, MQTT, the unchanged Python backend, and a demo audio controller
in project-specific containers. The UI assets are mounted from www, so edits appear on refresh.
Sample members, balances, products, and stock are seeded once in the ignored
dev/data directory. This setup does not use production data or hardware;
the door command targets the development broker and no printer is connected.
All CSS, icons, fonts, and JavaScript are served locally, with no CDN or external
font service.
Member and user accounts use 84px-tall buttons, 50% taller than the original compact rows, with page tabs when needed. At 1920×1080 a page holds 42 accounts (six columns and seven rows); smaller windows calculate their capacity from the available space. Cash and amount controls remain above the grid, and the receipt and command bar stay visible. Filtering keeps the same button sizes, expands results on backspace, and restores the previous selection and page when cleared. Typing and pasting both update the filter; Tab still completes a unique match. Products and commands retain pages of up to 56 buttons (8×7) at 1920×1080, or 25 buttons on smaller screens. The command bar's Enter button submits the same command as pressing Enter on the keyboard. After one minute without keyboard, pointer, touch, wheel or form activity, the command line regains focus if it has lost it. This preserves unsubmitted text, the current screen and receipt, and sends no command. Music, Home Assistant and open dialogs keep their focus; hidden or unfocused browser windows are left alone. Activity inside the cross-origin embedded pages cannot be observed by the bar, so their selected views are excluded until the user leaves them.
The header identifies the venue as Lovelace Bar, with Drinks @ Hack42 below it. The log strip is labelled HISTORY and the receipt footer reads Eat. Drink. Hack. Repeat. The larger Members/Guests buttons and page tabs support touch selection. Commands are grouped into Products, Users, Sales, Wholesale, Market and Misc, with an Abort action and a Settings section. Category tabs and headings have emoji icons. All commands stacks the groups vertically, with four buttons per row on the bar display; category tabs show each group separately. Manage members contains member administration; all underlying commands remain available. Remove item edits the current unbilled sale; transaction undo is a separate action. Door open retains the existing configured door command.
Settings offers 🎉 Party mode, Dark, Banana, Dank Hit and Confetti modes. Party mode reflects backend state and can be disabled even while another question is pending. A missing printer no longer prevents exit: settlement totals and their history are saved in the party state before attempting to print. No sale or account balance is cleared by changing this setting.
Appearance settings apply to this browser and survive reloads. The command bar
also accepts darkmodeon / darkmodeoff, bananamodeon / bananamodeoff,
dankmodeon / dankmodeoff, and confettimodeon / confettimodeoff.
While Banana mode is enabled, each click or tap adds another 40px-wide dancing
banana at a random screen location. Bananas let clicks
through, fade during their final two seconds, and disappear individually after
one minute; refreshing preserves their deadlines. Switching the mode off
clears all bananas. Dank Hit mode plays the supplied hitmarker sample through
the POS browser audio and displays a click-through marker for 100ms at each click or tap.
Confetti mode drops a small shower from the top for every submitted command,
and a larger shower after a completed sale. Particles fall off the bottom and
let clicks through. The local canvas renderer stops when no particles remain;
switching the setting off clears them immediately. Refunds, deposits, transfers
and undo do not trigger the larger sale shower, and reconnecting does not replay it.
Members and Guests lists keep their Archive tab at the far right, after the page buttons, including when there are multiple pages. Archives list accounts whose last purchase is older than a year, in alphabetical order. New accounts with no purchase record use their last account update as a conservative fallback. A read-only index of the existing checkout log is shared between sessions, reuses unchanged results, and reads only appended data after the first scan. Initial reads are capped at 8MiB. If that window cannot establish a full year's coverage, archives stay unavailable and accounts remain in the normal lists. Deposits, transfers, refunds and undo lines are excluded from purchase history.
Clicking a user with an empty sale shows the balance and up to ten recent
purchase line items, alongside OK, undo, restore, receipt and cash drawer controls.
Every visit receives a fresh profile response, even when its data is unchanged.
The user page also offers Promote to guest or Promote to Member, using
the existing delmember <user> / addmember <user> commands. The action uses
actual membership, including while party mode changes the visible member list.
After checkout, the same view combines the transaction summary with the updated
profile and purchase history. Add again uses the current product price and only
adds to the sale, leaving the normal account selection and checkout flow intact.
Reordering is offered only when a historical description identifies one
current product. Older or ambiguous products remain visible as history.
Undo latest transaction appears only for an eligible latest transaction;
the backend rechecks its ID and owner before using the existing undo flow.
The local seed adds stitch, BugBlue, Jos, nino and Anus, two archive examples,
and sample purchase history without resetting existing development balances.
Clicking anywhere in the Hack42 logo, title, and subtitle area sends Abort and
clears the command input.
The Sounds page starts with the soundboard buttons, followed by the two volume
sliders, then the POS sound settings. The Sounds menu and command search omit
Deur off/on, Groove Salad, Jazz Radio
Blues, Kill Music, Louder, Quieter and Kassa off/on. The Sounds page also omits
Abort and Christmas; the header reset remains available. Ding Dong, Vergaderen,
Vergaderen Extreme, Throw and Yeet remain on that page after playback, without
the usual command confirmation beep. These sounds also work as typed commands.
Sounds provides two large
volume sliders for HackBank POS effects and Space audio. Each reads the current
output volume, applies changes, and refreshes every two seconds while open.
Opening Sounds is silent. Releasing a slider plays a short preview beep on
that output after saving its volume; dragging and automatic refreshes are
silent. At zero volume the output remains muted and no preview plays.
The development audio controller uses in-memory demo levels and never connects
to a sound device; it also simulates preview requests.
Soundboard samples live in www/sounds/soundboard/. POS effects live in
www/sounds/POS/<feature>/, with a separate choice for each feature:
| Feature | Default sound |
|---|---|
| Command | KDE_Beep_ClassicBeep.wav |
| Deposit | itsgone.wav |
| Insufficient funds | sinterklaas.wav |
| Sale | katsjing.wav |
| Error | KDE_Beep_Beep.wav |
Each dropdown lists audio files from its own feature folder. Changing a choice
immediately saves it in data/revbank.sounds.json, shares it with the other
browser sessions, then previews the selected sample. The Preview button plays
the current choice without changing it. Reset restores one feature's default;
Reset all to defaults restores all five and previews the command default.
Opening the page or receiving settings from another browser stays silent.
Settings requests use a separate route so they cannot answer a pending POS
question. The error alert retains its two repetitions for unrecognised input.
The 😎 / 🙂 button sits immediately to the right of Members Archive or Guests Archive. It toggles personal button styling for this browser: off gives user buttons standard colours and lettering, hides custom icons and backdrops, and pauses their shader rendering. Names, balances and button actions still work normally. The preference survives reloads and follows other tabs in the same browser; saved user designs stay intact. Studio previews continue to show the designs while editing. Sunglasses mean styles are on; a normal smile means styles are off.
The Button studio action on each user profile opens “Hack42 Magic Button
Studio (tm)”. Current and draft buttons remain visible beside the editing
tools. Backgrounds support solid paint, 2–6 gradient stops with percentages
and a full 360° angle, glitter, and plasma. Effect speed and intensity are
adjustable. Lettering has colour or a separate gradient, six system font
stacks and the bundled OCR-A font Cyber, size, weight, a fixed angle and
optional continuous rotation. Icons can be built-in symbols or a personal image,
with size and rotation controls.
Lettering also has an optional label backdrop with its own colour, opacity,
padding and corner rounding. It follows text rotation and supports gradient
letters. Name case offers Original, UPPERCASE, lowercase, Capitalized Words,
camelCase, PascalCase, snake_case and kebab-case. Word-based styles recognise
spaces, underscores, hyphens, camel-case and acronym boundaries: BugBlue
becomes bugBlue, and HTTPServer becomes httpServer in camelCase.
These change only the button's visible name; account IDs, search and commands
keep the original username. Turning personal styles off restores original
spelling; Studio previews still show the selected design. Older saved designs
continue to load with the backdrop off and Original casing.
Cyber's font source and notices are in www/fonts/README.md.
The native colour picker and six-digit hex fields work together.
Large scroll buttons also work on the bar's touch screen without swipe gestures.
Pick your kind of magic includes ten animated shader presets alongside
the other backgrounds: Five lights, Snow, Cubes, Synthwave, Wonderful,
Heartfelt, Star Nest, 2D Clouds, Universe Within and Matrix.
Matrix has falling green code on black, with bright heads and fading trails;
its procedural glyphs need no textures. Its colours, speed and intensity are
editable, and it can be combined with any font or name case.
Cubes rotate in a wave from bottom left to top right; Synthwave drives through
a landscape of wireframe hills. Tap a sample or choose it under Background style, then
adjust its scene colours, effect cycle (longer is slower) and intensity.
The first four are original, texture-free shaders inspired by the supplied
lights,
snow,
cubes and
synthwave references.
Five presets adapt the GLSL supplied by the user; their source credits,
individual licence notices and adaptation details are in
www/shaders/README.md.
Heartfelt uses a procedural palette backdrop instead of a missing image;
Heartfelt and Universe Within run silently without audio inputs or long
intro/outro blackouts. No remote assets or third-party runtime libraries load.
The local WebGL 1 renderer shares one context across every button and preview, batches identical scenes for the same user, caps its internal image at 320 × 128 pixels and runs at up to 24 frames per second. Hidden/off-screen buttons pause; opening the studio pauses buttons behind the dialog. Reduced-motion mode uses a still scene. If WebGL is unavailable or its context is lost, the saved colour gradient stays visible and names, balances and button actions keep working. Shader choices use the existing private button design storage, with the same version-1 schema and a fixed allowlist of scene names. Animation phases include a stable hash of the username, so people sharing a preset move differently. Studio samples and the draft use the current user's seed. Selecting another preset keeps the gallery canvases and draft canvas alive; the small shared render buffer is preserved between copies to avoid empty frames on Firefox graphics backends. Repeated scan/selection updates retain an unchanged account grid and its page. Balances and debt colours update directly, preserving shader canvases, glitter, plasma, and text/icon rotations. Changed account sets or grid capacities still rebuild the list; contextual command controls update on every response.
Upload a PNG, JPEG, WebP or GIF (up to 8 MB), drag it into the icon area, or
paste an image with Ctrl+V / ⌘V while the dialog is focused. The browser crops
it to a square and resizes it to a 144 × 144 PNG; GIFs become still icons.
The original upload is not stored. Images use object-fit: cover.
Use my new button saves to data/revbank.buttons.json, using an atomic
write. Designs and their embedded icons survive backend/browser restarts and
are shared across the bar's terminals through retained MQTT messages. This
file belongs to the installation's private runtime data and is ignored by
the public application repository. Include it in private data backups.
Cancel or Escape discards the draft; Revert changes restores the saved design,
and Start fresh restores the account's initial design as an unsaved draft.
Initial designs keep stitch’s blue and bold white text (without a bundled
character image), BugBlue’s glitter, Jos’s key, nino’s blue/yellow flag and
Anus’s plasma. Personal icons must be supplied locally through the studio.
All effects are clipped inside their own button; balance badges keep their
debt colours. Animation respects reduced-motion settings. Saving a design
uses its own button_studio route, so it cannot answer a pending sale prompt
or change receipts, account names or balances. Only validated presentation
settings and small PNG data URLs are accepted; no arbitrary CSS or remote
image/font URLs are stored. No new external runtime dependencies are needed.
Home Assistant and Music open directly in the left-hand iframe with rounded
top corners, square bottom corners and no extra toolbar. Music uses the LAN
sound-system interface at
http://192.168.142.157:3000/.
The selected Music tab matches Trollibox orange, while Home Assistant uses
its primary blue.
Both use dark text and icons for contrast.
The supplied Hack42 logo
is already included as www/images/Hack42.png and is now displayed in the header.
To stop this setup, run docker compose -f compose.dev.yaml down. To reset
the sample data, stop the containers, remove dev/data, and start them again.
The original development setup remains available:
If you don't want to install apache and an mqtt server yourself you can use
docker-compose for local development. Simply running docker-compose up will
start an mqtt server on port 1883, an apache+php server serving the files in
'www' on port 4242, and hook
them up to each other. All that's left is to run kassa.py and point your
browser at http://localhost:4242
audio_control.py uses the system's existing pactl command to read and adjust
only hackbank_pos (POS effects) and snapcast (Space audio). Previews use
the existing paplay command and the local
www/sounds/POS/command/KDE_Beep_ClassicBeep.wav file,
explicitly selecting the adjusted output at full stream gain so its sink
volume determines the preview level. It does not change
the existing audio routing. www/audio.php connects to the controller through
/run/hackbank-audio/control.sock; the controller does not open a network port.
Volumes range from 0 to 100 percent; zero also mutes, and a nonzero setting
unmutes that output. Missing devices or an unavailable controller disable the
affected sliders. Financial commands continue through the existing backend.
The supplied systemd unit targets the bar installation at /home/barapp/kassa,
runs as kiosk user bar, and gives Apache's www group access to the socket.
After deploying the UI branch, register and start it as root:
systemctl enable --now /home/barapp/kassa/deploy/hackbank-audio-control.service
systemctl status hackbank-audio-control.serviceThe unit depends on the existing kiosk PulseAudio/PipeWire socket and named
outputs. The page title remains Hack42 - HackBank, the identifier used by the
existing POS audio router. Restart hackbank-audio-control.service after
updating its Python controller. Changes to plugins/sounds.py, including the
silent Sounds navigation, also require restarting the existing POS backend.
The profile response fix in kassa.py and sale display events in
plugins/historie.py require the same backend restart.
Button studio deployments must also include button_themes.py,
plugins/buttonstudio.py, the JS/CSS files (including
www/kassa-button-shaders.js), the www/fonts directory and the updated PHP routes,
and restart the POS backend. Existing private button data must be preserved.
The sound-folder update also requires a backend restart and an audio-controller
restart; include pos_sound_settings.py and the entire www/sounds tree when
deploying. Preserve the saved choices in the existing data directory.
For a different installation, adapt the unit's application path, kiosk user,
web-server group, and runtime audio socket before enabling it.