BLE scanner apps: what nRF Connect and LightBlue never test

nRF Connect, LightBlue and Simplicity Connect prove a peripheral works when an engineer drives it by hand. A shipping app has to cover everything they leave out.

Share

Every free BLE scanner on the App Store and Google Play answers one question: does this peripheral behave when an engineer drives it by hand, screen on, standing a metre away? Answer it early. Just don't mistake it for the hard question, because it is the easiest test your product will ever get. A device that works in nRF Connect has passed a smoke test. The integration test is still ahead of you.

Most of the work in a companion app lives in the gap between those two, and nobody sees the gap until the first customer puts the phone in a pocket and walks off.

Install the scanners and use them daily. A green result means the firmware is reachable. That is all it means. Background sync, reconnecting and bonding are your app's job, and so is knowing what the bytes mean.

Key takeaways:
  • nRF Connect, LightBlue and Simplicity Connect show that a peripheral is reachable. They do not show that your product works.
  • Use a scanner before writing app code: check the advertised name and service UUID, then walk the protocol by hand.
  • Repeat the checks on iOS, which never shows a MAC address and hides iBeacon advertisements from CoreBluetooth.
  • Scanners never test a locked phone, reconnects, lost bonds, interrupted updates, refused permissions or what the bytes mean.
  • Open-source sample apps are a sensible start, worth roughly the first week of a project.

The three worth installing

Both stores have dozens of scanner apps. I keep three, all free, all from companies that make Bluetooth chips or do BLE engineering for a living, so they get updated when the chips do.

AppMade byBest at
nRF Connect for MobileNordic SemiconductorReading advertising data and driving a GATT table by hand. The Android build has long carried more tooling, including recorded macros and logging.
LightBluePunch ThroughA clean GATT explorer, and virtual devices: the phone pretends to be a peripheral so app work can start before hardware exists.
Simplicity ConnectSilicon Labs (formerly EFR Connect)Test tools beyond browsing: a throughput test, an interoperability test with an exportable log, an advertiser and a GATT configurator. Source code for both platforms is on GitHub under Apache 2.0.

If your module is a Nordic or Silicon Labs part, the vendor's app will also run that vendor's firmware update and demo profiles, so you don't have to build a test harness for them. For feature-by-feature comparisons, Hubble has a guide aimed at embedded developers and Punch Through publishes a screen-by-screen LightBlue walkthrough. I won't repeat them. What happens after the scanner says yes is more interesting.

How to use them on a hardware project

Before anyone writes app code, someone should open a scanner and check that the device advertises under the name and service UUID the protocol document promises. Five minutes. It is worth it, because firmware that advertises a different UUID from the one in the spec is a common way to lose the first week.

After that, walk the protocol by hand. Read every characteristic, send the commands the document describes, subscribe to the notifications and watch what comes back. It is the cheapest protocol review you will ever get. Vague lines like "send the start command", with no byte order and no response format, surface in an afternoon instead of three weeks into the app.

Do it all again on the other platform, because CoreBluetooth changes what a scanner can show. iOS never shows a MAC address. You get an identifier that is stable on one phone and means nothing on another, so a protocol that expects the app to know the device address needs rethinking. iOS also filters iBeacon advertisements out of CoreBluetooth scans. And when a device shows up on Android but not on iOS, it is usually a firmware detail the iPhone is stricter about, which the ESP32 post goes through.

Keep a second scanner installed. If a read fails in one app, try it in the other; about a minute later you know whether you are looking at a firmware bug or a tool bug.

What a scanner app never tests

A scanner sits in the foreground and connects when you tap, and the person tapping already knows what the bytes mean. Your users get none of those conditions. Each one hides its own kind of bug.

  • The phone is locked in a pocket. iOS suspends apps and Android defers work, so whether readings still arrive depends on background modes, foreground services and state restoration. A scanner exercises none of it.
  • The user walks out of range and back. The scanner shows a greyed-out row. Your app has to notice, retry without hammering the radio, and pick up where it left off without anyone touching the screen.
  • The bond goes stale. Pairing once in a scanner proves very little. Firmware updates, factory resets and new phones all break bonds, and "go to Settings and forget the device" is not a recovery path.
  • A firmware update fails halfway. On a desk the vendor apps update cleanly. Out in the world the connection drops at 60%, and the update has to resume.
  • Someone taps Don't Allow. A scanner asks for Bluetooth once and assumes yes. Your app has to ask at a sensible moment and still make sense when the answer is no.
  • Android runs on hundreds of phone models, each with its own Bluetooth stack. The engineer tested on two, and errors like GATT 133 tend to come from the rest.
  • The bytes need meaning. A scanner shows 0x01 0x2C. Only your app knows that is a 300 gram reading, that it arrived out of order, and that it must not be stored twice.
Your users will never stand next to the device with the scanner open and the screen on.

Starting your own app from open source

Vendor guides promise a working app fast, and for a demo they deliver. Silicon Labs' community has one called How to create your own BLE mobile app in 10 minutes. Simplicity Connect's own source is public too, and Nordic publishes open-source BLE libraries plus a CoreBluetooth mock. Starting from that code is sensible. Scanning, connecting and a GATT client arrive already debugged.

The list in the previous section does not arrive with them. Sample apps exist to demonstrate a chip: they connect in the foreground, assume a cooperative user and keep error handling thin. I'd budget the sample as roughly the first week of the project. Everything after that is the background, reconnect, bonding and update behaviour, plus the product screens on top. We wrote AntennaKit, our Swift package, after rebuilding that layer on one project too many.

When the scanner works and your app does not

This one comes up all the time, and the scanner is what makes it quick to answer. If another app on the same phone can do it, the radio and the firmware are fine and the bug is in your code. I'd check in this order.

  • Same phone, same distance, same firmware build. Rule out the boring causes before reading code.
  • Is the device advertising the service UUID your scan filters on? If not, the scan finds nothing and no error tells you why.
  • Has the user granted Bluetooth? On Android 12 and later that means both BLUETOOTH_SCAN and BLUETOOTH_CONNECT.
  • On iOS, is anything holding a strong reference to the CBPeripheral? If nothing retains it, the connection is dropped almost at once.
  • Does your write type match the characteristic's properties? A mismatch is one way writes fail silently on iOS.
  • Did you enable notifications on the characteristic and wait for the confirmation? Skip that and nothing arrives.
  • On Android, is it GATT error 133? Then look at timing, caching and the phone's stack before blaming the device.

Got a peripheral that behaves in a scanner and want an app that behaves the same way in a pocket? Send over the protocol document and the hardware details and we can scope the app properly.