Configuring offline screens over Bluetooth
A BLE GATT server written in Go, used to configure Raspberry Pi signage players from a phone before they have a network connection.
My role: Led the workstream. I wrote 55 of 62 commits, maintained the project, and reviewed all five team pull requests.
June 2023 to October 2023
- Go
- Raspberry Pi
- BLE GATT
- ARM cross-compilation
Starting with no network
A technician installing a screen first needs to connect it to the local network. However, the Raspberry Pi player has no keyboard or settings page, and it cannot receive its configuration over the network because that is the part currently being configured.
At that point, the only useful connection available is Bluetooth Low Energy, so the initial configuration comes from the technician’s phone.
What I built
I built a BLE GATT server in Go that runs directly on the Raspberry Pi players and advertises itself as “DoohYouLike Screen”. It has five services and eight characteristics, all protected by a password. From the phone, a technician can scan for nearby Wi-Fi networks, send credentials, read the current configuration, run network diagnostics, reboot the player or trigger a software update.
The Wi-Fi scan did not go as planned. The available Go libraries did not work on
the target hardware, so I ended up calling iwlist and parsing its output. It
was not the implementation I originally wanted, but it worked reliably on the
actual device.
Working inside the BLE limits
Packets of around twenty bytes. An SSID and its password do not fit inside
the usable BLE MTU, and a complete network scan is much larger. I created a
small protocol on top of GATT using single-byte opcodes, length-prefixed strings,
chunked transfers and 0xFF-delimited reassembly. The receiver checks that every
chunk is present and in the correct order before using the result.
Several phones at the same time. Two technicians, or one phone with an old background connection, can be subscribed at once. Notifications are sent through per-device channels backed by mutex-protected maps, so two goroutines do not write to the same connection state.
Concurrent software updates. The update command is the one operation that cannot run twice. I added a re-entrancy lock so a second request does nothing while an update is already running. A partially applied update on a Pi mounted inside a venue usually means another visit for a technician.
Getting it onto real screens
I also handled deployment: cross-compiling for ARM Linux from our development
machines, pushing the binary through a scripted scp command, and making sure
Wi-Fi credentials were never written to the logs.
The rest of the team opened five pull requests during the project. I reviewed and merged each of them.
In numbers
From the first commit to release, the project took four months. I wrote 55 of 62 commits, and the final server contains roughly 850 lines of production Go across five GATT services and eight characteristics.
Get in touch
I am available for remote freelance work. The easiest way to reach me is by email.