Browse docs
Frontelio Access · Hardware

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.

BuildPartsCostBest for
Raspberry Pi 4Pi 4 (2GB) + power supply + PN532 + 5V relay + LEDs + buzzer + 16GB SD~$75Headend door. Full Linux, easy SSH, ample CPU.
Raspberry Pi Zero 2WPi Zero 2W + power supply + PN532 + 5V relay + LEDs + 16GB SD~$45Tight ceiling spaces. Same software stack as Pi 4.
ESP32 + PN532ESP32 dev board + PN532 + 5V relay + LEDs~$25Lowest 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-door1 and 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

bash
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:

bash
ls -l /dev/serial0

It 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.

bash
# 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.sh

The installer:

  • Uses apt to install Python 3 and the packages the smart-card (ACR122U) support needs, including pcscd, swig and build-essential.
  • Creates /opt/missan-bridge with a venv and pip-installs requirements.txt.
  • Installs the systemd unit missan-bridge.service and enables it, without starting it.
  • Copies config.example.toml to /etc/missan-bridge/config.toml if 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

bash
sudo nano /etc/missan-bridge/config.toml

Edit at minimum the [missan] section:

toml
[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.group host: it redirects, and a redirected request loses its Authorization header, 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_gpio commented 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

bash
sudo systemctl start missan-bridge
sudo journalctl -u missan-bridge -f

You should see these lines (journalctl puts its own date, host and process id in front of each):

journalctl
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=0

If 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:

bash
curl http://localhost:8080/health

It answers with JSON: status, started_at, last_tap_at, total_taps_today and firmware_version.

Step 6 — Bind in the admin UI

  1. Sign in to your tenant as an Owner or Company Admin.
  2. Go to /admin/access and 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.
  3. Open the Zones tab and choose New zone. Put the reader_id you 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").
  4. Open the Grants tab and grant at least one user access to that zone. Only active zones are offered.
  5. 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:
journalctl
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=None

Until 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.example to config.h, set the WiFi name and password, API_KEY and READER_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.example and a README.
  • docs/ — HARDWARE.md (bill of materials, wiring diagrams as text, enclosure notes) and DEPLOY.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 (labelled HSU on 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 that ls -l /dev/serial0 shows the link from step 2. An error that mentions No such file or directory means the nfc_path device does not exist.
  • ESP32: the PN532 is not found — the Serial Monitor prints [pn532] not found and 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 shows heartbeat failed with a 401), a config that has no [missan] section, or a Pi that can't reach the API. Test the connection from the Pi with curl -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_id in config.toml doesn'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/verify can return.
  • Three beeps and a red LED on every tap — the bridge got no answer from the API. The journal says either verify timed out or verify request failed with the HTTP error, which is usually a 401 from a bad API key or a wrong api_base.