EmbeddedEra EmbeddedEra

← Notes from the bench.

BLE provisioning for a device with no screen

Sep 19, 2026firmwaresecurityDrafted with AI assistance, reviewed and edited by our engineer.

The product has one button, an LED, and no way to type. The customer has to get their home Wi-Fi name and password onto it before it does anything useful. On the bench, with a dev kit and a generic BLE app, this works on the first afternoon. Then it meets a phone that shows a pairing dialog every time, a router with two bands under one name, and a wrong password typed at 9 p.m. by someone who will never read a manual.

The same questions keep turning up in vendor forums and issue trackers, and many of the threads sit unanswered: why iOS pairs and complains at once, why a device shows up on one Android phone and not another, whether “Just Works” is good enough. This note covers the decisions that matter.

Start from an existing protocol

Do not design your own GATT service for this. Three maintained options already exist:

  • ESP-IDF unified provisioning for Espressif parts. It supports BLE and SoftAP transports and three security schemes, described below.
  • Improv Wi-Fi, an open standard from the ESPHome and Home Assistant community. It runs over BLE or serial, and both can be driven from a browser. It defines four states (authorization required, authorized, provisioning, provisioned) and a small set of commands, including scan and send credentials.
  • Nordic’s Wi-Fi provisioning over BLE, a sample and service in the nRF Connect SDK, for products built on Nordic’s Wi-Fi parts.

Whichever you pick, read what it protects and what it leaves to you. Improv’s specification treats authorization as optional but recommended, and gates it on a physical action at the device. That is a sound idea, but it is your job to turn it on.

BLE is not the only transport. SoftAP provisioning, where the device briefly becomes its own access point, is the most interoperable. But the phone has to leave the customer’s network to join it, and Espressif’s documentation notes that iOS has no native API for discovering and joining that access point, so the user ends up in system settings. BLE gives native discovery on both platforms. Espressif also quotes about 110 KB of runtime memory for its BLE transport, which matters on a small part. If the product has a USB port, a serial provisioning path is worth having as well.

What pairing does and does not protect

BLE offers four pairing methods: Just Works, Numeric Comparison, Passkey Entry, and out-of-band. Numeric Comparison needs a display and a yes/no input on both sides. Passkey Entry needs a display on one side and a keyboard on the other. A device with a button and an LED has neither, so it falls back to Just Works, unless it has an out-of-band channel such as NFC.

Just Works with LE Secure Connections encrypts the link, which stops a passive eavesdropper. It does not prove who is on the other end. Nothing in the exchange lets the user confirm they are talking to their device, and an active attacker within radio range can sit in the middle. The Wi-Fi password is the thing being sent over that link.

So authenticate at the application layer. The realistic options:

ApproachWhat it provesWatch for
Physical authorization window (button press, timeout)Someone is at the device right nowShort window, and a clear way to reopen it
Per-device secret on the label or a QR codeThe person holds this unitEvery unit needs its own secret
Just Works aloneNothing beyond link encryptionFine for a toy, hard to defend for anything on a home network

Espressif’s scheme 1 supports a proof of possession, a unique per-device secret entered on the client, and scheme 2 uses SRP6a with a per-device salt and verifier generated ahead of time. Scheme 0 is no encryption, and scheme 1 with a null proof is encryption without authentication. Read the configuration, because the default that is easiest to demo is not the one to ship.

The per-device secret has one hard rule: it must be different on every unit. A secret compiled into the firmware image is one secret for the whole product line, and anyone can extract it from a downloaded update. Generate it at production, program it with the firmware, and print the label from the same record. That makes the end-of-line test step the natural home for it, and it means the fixture and the label printer need to agree on where the secret comes from before the first production run, not after.

Combine the two. A button press to open the window plus a per-device secret to authenticate the session covers the cases each one misses alone.

The phone is half your product

iOS. Pairing is not something the app drives. Developers report that the system starts it when the app touches a characteristic that requires encryption, so where you put the security requirement in your GATT table decides when the dialog appears. The same reports describe the dialog returning on every connection, pairing failing on the peripheral while iOS records a successful bond, and lost bonds after the phone rotates its private address. Some of those threads have no resolution. Treat them as evidence that bonding is a convenience, not something provisioning can depend on.

The practical rule: make the provisioning session work without a bond. Establish your application-layer session on every connection. If the bond survives, good. If it does not, nothing breaks.

iOS 18 added AccessorySetupKit, which shows a system picker for your accessory and handles the discovery and pairing step without the app asking for Bluetooth permission. It needs iOS 18 or later, the discovery filter lives in the app’s Info.plist and cannot change at runtime, and the app still talks to the device through CoreBluetooth afterward. It is worth adopting for a new app. It does not replace the older path for customers on older iOS versions.

Android. From Android 12, scanning needs the BLUETOOTH_SCAN runtime permission and connecting needs BLUETOOTH_CONNECT. If the app declares that it never uses scanning to derive location, the system filters some results, which is fine for a provisioning app but matters if your device advertises in an unusual way. A device that appears on the developer’s phone and not on a customer’s is often a permission or advertising-format problem, not a radio problem.

Test against a real matrix: two or three iPhones on different iOS versions, and Android phones from more than one manufacturer, including one that is a few years old. Emulators cannot do this.

Design the failure paths first

Most of the support burden is in the paths nobody demoed.

Wrong password. The device tries, fails, and has to say so. Improv’s error set has a single “unable to connect” code, so if you want the app to distinguish a wrong password from a network that is out of range, you need to carry that detail yourself. Then return to a state where the user can try again, without a power cycle.

Band mismatch. The network list shows what the device’s radio can see, not what the phone can see. Many low-cost Wi-Fi parts are 2.4 GHz only. A customer whose phone is on a 5 GHz network name will look for something that is not on the list. Say so in the app.

Timeouts. An open provisioning window should close: Improv suggests about a minute for the authorization. Advertising also costs battery on a battery product, so bound it, and define what a button hold does to reopen it.

Re-provisioning. People change routers. Decide how stored credentials get replaced or erased, and what a factory reset removes. Credentials in on-chip flash are only as private as the chip’s readout protection, so turn that on for production parts.

Update path. A device that accepts Wi-Fi credentials will usually also accept firmware, and the same recovery and rollback questions apply there. Make sure provisioning authorization and update authorization are separate decisions, and that both are written down.

Each of these is firmware behavior you can specify on paper before writing code, and each is cheaper to specify than to patch after the first batch of returns.

How we handle it

We start from the chip vendor’s provisioning stack, add the per-device secret at production test, and try the flow on real phones and real routers before it ships. If you want this designed into a product, send us the MCU or radio module, the phone and OS versions you have to support, and what the customer will be holding when they set it up.

Building a BLE product that has to pair without a screen? We write the provisioning firmware and the app that talks to it.

ENGINEERING NOTES

One note a week on PCB design, firmware and getting hardware built. No sales email.

Double opt-in. Unsubscribe in one click.