Deploy a reader bridge.
The reference reader bridge turns a Raspberry Pi or an ESP32 plus a PN532 NFC module into a Frontelio Access reader that reports to the API and can drive a door relay. This page takes you from a blank SD card to a reader that shows as online and is bound to a zone.
What the bridge is
A small Linux daemon (or, on ESP32, microcontroller firmware) that polls an NFC reader, reads the signed credential token (a JWT) an Android phone presents over Host Card Emulation, POSTs it to /access/verify, and pulses a relay if the API responds GRANT. The bridge holds one Frontelio secret, a per-tenant API key. All policy lives server-side, so revoking a grant takes effect on the next tap regardless of bridge state.
It is a reference implementation for cafés deploying phone-tap access for the first time. The Raspberry Pi bridge is one Python file plus a thin hardware layer, so an installer's engineer can read it, audit it, and modify it for their site. The source is shared with pilot customers and implementation partners; see Getting the source.
What the phone has to do:
- Be an Android phone with NFC. iPhones cannot present a credential over NFC to this bridge: iOS does not let third-party apps emulate a card, and the bridge has no camera or QR reader. iPhone users need a reader that scans the QR code on their Apple Wallet pass instead.
- Be unlocked. The Android app requires the device to be unlocked before it will answer a reader.
- Have the app open on More → My Access. That screen mints the day's credential (valid for 24 hours) and loads it onto the phone.
When you need a bridge
- Greenfield cafe / kitchen / store. No existing access readers, no incumbent system. The bridge is the lowest-cost reader hardware, about $75 of parts per door for the recommended Pi 4 build.
- One-off side door. You have Kisi on the main entrance but need to control a back door that Kisi can't reach. Bridge plugs the gap.
- You're a pilot deployment for Frontelio Access and want to see the full picture end-to-end before committing to enterprise readers.
If you already own Kisi / Salto / Brivo / HID Origo readers, skip this page and go to Integrations instead.
Hardware shopping list
Three reference builds, depending on budget and operating environment. All three use the same PN532 NFC module — that part is universal. Part sources, wiring diagrams for the PN532, relay, LEDs and buzzer, and enclosure notes are in bridge/docs/HARDWARE.md.
| Build | Parts | Cost | Best for |
|---|---|---|---|
| Raspberry Pi 4 | Pi 4 (2GB) + power supply + PN532 + 5V relay + LEDs + buzzer + 16GB SD | ~$75 | Headend door. Full Linux, easy SSH, ample CPU. |
| Raspberry Pi Zero 2W | Pi Zero 2W + power supply + PN532 + 5V relay + LEDs + 16GB SD | ~$45 | Tight ceiling spaces. Same software stack as Pi 4. |
| ESP32 + PN532 | ESP32 dev board + PN532 + 5V relay + LEDs | ~$25 | Lowest unit cost. Reference sketch for bench bring-up; read the ESP32 notes below for its limits. |
Deploy in 6 steps
End-to-end checklist for a brand-new Raspberry Pi reader (Pi 4 or Pi Zero 2 W; a Pi 5 numbers its serial ports differently and is not covered here). The ESP32 build differs; see the ESP32 notes.
Before you start: mint an API key
In the web console open /admin/access, go to the API keys tab and choose New key. Copy the key straight away: it starts with mk_ and is shown once. Use one key per outlet so a stolen reader can be revoked without touching the others.
Managing keys, zones, grants and readers in the console needs a plan that includes Frontelio Access (Growth or Enterprise). The bridge itself is authenticated by the key alone.
Step 1 — Flash Raspberry Pi OS Lite
Use the official Raspberry Pi Imager. Pick:
- OS: Raspberry Pi OS Lite (64-bit) — Bookworm or newer.
- Use SSH: yes, with a password. Set a hostname like
frontelio-bridge-cafe1-door1and a username. - WiFi (or Ethernet): configure either. WiFi is fine — worst-case bandwidth is measured in KB/min.
Boot the Pi and confirm you can SSH in: ssh <username>@frontelio-bridge-cafe1-door1.local.
Step 2 — Enable the serial port, disable the serial console
sudo raspi-config- Interface Options → Serial Port
- "Would you like a login shell to be accessible over serial?" → No
- "Would you like the serial port hardware to be enabled?" → Yes
Reboot, then check the port:
ls -l /dev/serial0It should be a link to ttyS0. The PN532 is wired to GPIO 14 (pin 8) and GPIO 15 (pin 10), which on a Pi 4 and a Pi Zero 2 W belong to the primary UART, the mini UART, and Linux exposes it as /dev/serial0. The device called /dev/ttyAMA0 is the other UART, and it is wired to the Bluetooth chip, so the PN532 never answers there. That is why nfc_path is /dev/serial0 in step 4.
Step 3 — Install the bridge
Copy the bridge/rpi directory to the Pi and run the installer (see Getting the source for where the directory comes from). It must contain all five files: install.sh, missan_bridge.py, missan-bridge.service, requirements.txt and config.example.toml.
# on the machine that holds the bridge source
scp -r bridge/rpi <username>@frontelio-bridge-cafe1-door1.local:~/bridge-rpi
# on the Pi
cd ~/bridge-rpi
sudo bash install.shThe installer:
- Uses
aptto install Python 3 and the packages the smart-card (ACR122U) support needs, includingpcscd,swigandbuild-essential. - Creates
/opt/missan-bridgewith a venv and pip-installsrequirements.txt. - Installs the systemd unit
missan-bridge.serviceand enables it, without starting it. - Copies
config.example.tomlto/etc/missan-bridge/config.tomlif no config exists yet. It never overwrites an existing one.
The bridge predates the Frontelio name, so its files, service and config section keep the missan name. Use them exactly as written on this page.
Step 4 — Configure
sudo nano /etc/missan-bridge/config.tomlEdit at minimum the [missan] section:
[missan]
api_base = "https://api.frontelio.com/api/v1"
api_key = "mk_REPLACE_WITH_THE_KEY_YOU_MINTED"
reader_id = "BRIDGE-cafe1-door1"
[hardware]
nfc_device = "pn532_uart"
nfc_path = "/dev/serial0"
relay_gpio = 17
relay_pulse_ms = 800
[behavior]
verify_timeout_seconds = 2.0
led_grant_gpio = 22
led_deny_gpio = 23
# buzzer_gpio = 27- api_base — keep it exactly as shown. Do not use the old
api.missan.grouphost: it redirects, and a redirected request loses itsAuthorizationheader, so the API answers 401 to every tap. - api_key — paste the
mk_*key you minted before you started. - reader_id — a unique name for this bridge. Convention:
BRIDGE-<outletCode>-<doorName>. You'll bind it to a Zone in step 6, and the match is exact and case-sensitive. - relay_gpio, led_grant_gpio, led_deny_gpio, buzzer_gpio — BCM pin numbers. Leave
buzzer_gpiocommented out if no buzzer is fitted. - relay_pulse_ms — how long the relay stays energised on a GRANT. Most electric strikes need 300–500 ms; 800 is a safe default.
Step 5 — Start the service
sudo systemctl start missan-bridge
sudo journalctl -u missan-bridge -fYou should see these lines (journalctl puts its own date, host and process id in front of each):
2026-09-20 10:04:12,007 [INFO] root: PN532 UART reader initialised on /dev/serial0, firmware=(50, 1, 6, 7)
2026-09-20 10:04:12,008 [INFO] root: missan-bridge started reader_id=BRIDGE-cafe1-door1 api_base=https://api.frontelio.com/api/v1 nfc=pn532_uart
2026-09-20 10:04:12,123 [INFO] root: heartbeat ok reader_id=BRIDGE-cafe1-door1 taps_today=0If the PN532 does not answer, the first line is replaced by an error that repeats every second or two, while the other two still appear; see Troubleshooting.
Local health-check endpoint as a smoke test:
curl http://localhost:8080/healthIt answers with JSON: status, started_at, last_tap_at, total_taps_today and firmware_version.
Step 6 — Bind in the admin UI
- Sign in to your tenant as an Owner or Company Admin.
- Go to
/admin/accessand open the Readers tab. The bridge sends its first heartbeat as soon as it starts, so it should appear within seconds, then every 5 minutes. It shows Online while its last heartbeat is less than 15 minutes old. - Open the Zones tab and choose New zone. Put the
reader_idyou used above in Reader IDs (one per line), set the weekly hours, and tick Active. New zones start inactive on purpose, and an inactive zone answers every tap with DENY ("Zone inactive"). - Open the Grants tab and grant at least one user access to that zone. Only active zones are offered.
- Tap the phone on the PN532: an unlocked Android phone with the app open on More → My Access. The journal logs the tap and the Audit tab shows the decision:
2026-09-20 10:09:31,442 [INFO] root: tap reader_id=BRIDGE-cafe1-door1 decision=GRANT replay=False latency_ms=87 zone=Front Door reason=NoneUntil the tap limitation at the top of this page is lifted, that last step does not complete on real hardware. To test everything else (reader binding, zone, grant, hours, audit trail) without a phone, call POST /access/verify with the same API key, the reader_id and a credential from POST /access/credential.
What the reader signals at the door
- GRANT: the relay pulses for
relay_pulse_ms(800 ms by default), the green LED lights for 1.5 seconds and the buzzer gives one beep. - DENY: the red LED lights for 1.5 seconds and the buzzer gives two beeps.
- No answer from the API: a timeout (
verify_timeout_seconds, 2 seconds by default) or any non-2xx response, such as the 401 from a bad API key. The red LED lights for 1.5 seconds and the buzzer gives three beeps, so the worker knows to retry rather than to argue with a manager.
The buzzer is optional; without one, only the LEDs signal. The API answers 200 for every decision, GRANT and DENY alike, so the bridge treats anything else as a failure to get an answer.
ESP32 notes
The ESP32 build is an Arduino sketch (bridge/esp32/missan_bridge.ino), and its README in the same directory has the wiring and the build steps. It differs from the Raspberry Pi bridge in these ways:
- The PN532 runs in SPI mode, not UART. Set the breakout's DIP switches to SEL0 OFF and SEL1 ON. (Both OFF is UART, which the Raspberry Pi build uses; SEL0 ON and SEL1 OFF is I2C.)
- Settings are compiled in. Copy
config.h.exampletoconfig.h, set the WiFi name and password,API_KEYandREADER_ID, and flash from the Arduino IDE. The API key lives in the firmware, so revoke it and reflash if a board is stolen. - It sends no heartbeat. The sketch only ever calls
/access/verify, so an ESP32 reader never appears in the Readers tab. Zone binding still works by reader ID; watch the Serial Monitor at 115200 baud (it prints a[ready]line once it is up) and the Audit tab. - It signals with LEDs only. GRANT lights the green LED and pulses the relay, DENY blinks the red LED twice, and no answer from the API blinks it three times quickly.
- It does not verify the server's TLS certificate (the sketch calls
setInsecure()). Pin the certificate authority before any production use. - It has the tap limitation described at the top, in a stricter form.
Getting the source
The bridge source is not published in a public repository. It is shared with pilot customers and implementation partners: email support@frontelio.com to ask for it. The bridge/ directory contains:
rpi/— the Python daemon, systemd unit, installer and example config.esp32/— the Arduino sketch,config.h.exampleand a README.docs/—HARDWARE.md(bill of materials, wiring diagrams as text, enclosure notes) andDEPLOY.md(this page as a Markdown checklist).
Troubleshooting
- Raspberry Pi: the PN532 is not detected — the journal shows
Failed to open PN532 UART on /dev/serial0: Failed to detect the PN532. Check the DIP switches on the breakout first: UART (labelledHSUon some boards) is SEL0 OFF and SEL1 OFF, while SEL0 ON and SEL1 OFF is I2C and SEL0 OFF and SEL1 ON is SPI. Then check that the wires are crossed (PN532 TX to GPIO 15, PN532 RX to GPIO 14) and thatls -l /dev/serial0shows the link from step 2. An error that mentionsNo such file or directorymeans thenfc_pathdevice does not exist. - ESP32: the PN532 is not found — the Serial Monitor prints
[pn532] not foundand the sketch stops. Set the DIP switches to SPI (SEL0 OFF, SEL1 ON), check SCK, MISO, MOSI and SS, then reset the board; the sketch does not retry. - No heartbeat, or the reader is missing in the admin UI — usually a wrong or revoked
api_key(the journal showsheartbeat failedwith a 401), a config that has no[missan]section, or a Pi that can't reach the API. Test the connection from the Pi withcurl -fsS https://api.frontelio.com/api/v1/health; it should print JSON that starts with{"status":"ok". (An ESP32 reader never shows here at all; see the ESP32 notes.) - GRANT in the journal but no door click — the relay wiring. Test the relay on its own: put an LED and a resistor in series with a small separate supply through the relay's NO and COM contacts, and it should light for about 800 ms on a tap. Never power a door strike from the Pi's 5V rail; use a separate door supply and let the relay be only the switch.
- DENY with reason "Unknown reader" — the
reader_idinconfig.tomldoesn't match any zone's Reader IDs. The match is exact and case-sensitive; copy and paste, don't retype. With reason "Zone inactive", the zone's Active box is not ticked. The API reference lists every reason/access/verifycan return. - Three beeps and a red LED on every tap — the bridge got no answer from the API. The journal says either
verify timed outorverify request failedwith the HTTP error, which is usually a 401 from a bad API key or a wrongapi_base.