Case study
Perfume dispenser
A Raspberry Pi perfume dispenser. A Nayax reader takes a card over MDB, and the Pi pulses one of five relays only after the payment is approved.
Button → Nayax MDB → Raspberry Pi → relay
- Record
- Public repository. Shipped product firmware.
- Stack
- Raspberry Pi, Python, Nayax, MDB, GPIO
- Hardware
- Raspberry Pi 4 · Nayax cashless reader · 5 relays · 5 buttons · 5 LEDs
- Links
- GitHub
The problem
A counter dispenser has to take a card, wait for a real approval, and only then open a valve. If the reader times out or the customer never picks a scent, the pump must stay off. The app also has to come back after a power cut without someone logging in.
What I built
A Python app on a Raspberry Pi 4. It runs as a systemd service: start on boot, and restart if the process dies. Five buttons select the item. Five LEDs show which one is active. Five relays drive the pumps.
The payment reader is a Nayax unit on TTL serial, speaking MDB at 9600 baud (/dev/ttyUSB0, GPIO 14 and 15). The price sits in config. In the public build that price is 200 cents. After a card tap, the Pi sends the price, waits for Nayax, and only then holds the matching relay for 1.35 seconds. There is a 55 second window to choose an item, a 15 second Nayax timeout, and a 2 second pause after the dispense.
Holding button 1 and button 5 together for 20 seconds turns on a test mode. A second service plays videos from a video folder on a USB stick, full screen, and stops as soon as the stick is pulled.
Decisions
The relay never fires on a button alone. Approval from the reader is the gate. The price, the relay time, and the timeouts are constants in config.py, so a shop can change the vend without rewriting the state machine.
systemd is the process supervisor, with Restart=always and a 10 second gap, and the service waits until the network is up. The pins are BCM numbers in that same config file: relays on GPIO 4, 17, 27, 22, and 5.
Pins
Buttons are GPIO 13, 21, 16, 7, and 24, active low with pull-ups. LEDs are GPIO 26, 20, 8, 25, and 23.