Scanning for iBeacon advertisements in Go
I spent some time this evening faffing around trying to make a Raspberry Pi turn into a device that could detect nearby iBeacons, filtered by signal strength to find the closest, to basically create a kind of iBeacon scanner that spat out the UUID, minor and major values.
Eventually, I gave up on the Raspberry Pi for various reasons, and moved to my Windows laptop, found a good library, a working example to build on and managed to create a scanner that outputs what you see below!
The details
Searching around, I came across https://github.com/tinygo-org/bluetooth, which included an example that got me scanning for Bluetooth devices right away.
Expanding on that example, and looking at the iBeacon spec on Wikipedia, I figured out that apparently device.ManufacturerData()[76]
gives me byte 7 onwards.
So using this library and making use of device.ManufacturerData()[76]
…
- Byte 1: SubType: 0x02 (Apple’s iBeacon type of Custom Manufacturer Data)
- Byte 2: SubType Length: 0x15 (Of the rest of the iBeacon data; UUID + Major + Minor + TXPower)
- Byte 3-18: Proximity UUID (Random or Public/Registered UUID of the specific beacon)
- Byte 19-20: Major (User-Defined value)
- Byte 21-22: Minor (User-Defined value)
- Byte 23: Measured Power (8 bit Signed value, ranges from -128 to 127, use Two’s Complement to “convert” if necessary, Units: Measured Transmission Power in dBm @ 1 meters from beacon) (Set by user, not dynamic, can be used in conjunction with the received RSSI at a receiver to calculate rough distance to beacon)
Here is the modified code includes iBeacon data output.
package main
import (
"encoding/binary"
"fmt"
"tinygo.org/x/bluetooth"
)
var adapter = bluetooth.DefaultAdapter
func main() {
// Enable BLE interface.
must("enable BLE stack", adapter.Enable())
// Start scanning.
println("scanning...")
err := adapter.Scan(func(adapter *bluetooth.Adapter, device bluetooth.ScanResult) {
println("-------------------------------------------------------------------")
println("found nearby device advertisement:", device.Address.String(), device.RSSI, device.LocalName())
if _, ok := device.ManufacturerData()[76]; !ok {
println("not an iBeacon advertisement")
}
if byteArray, ok := device.ManufacturerData()[76]; ok {
subType := byteArray[0]
subTypeLength := byteArray[1]
uuid := byteArray[2:18]
major := binary.BigEndian.Uint16(byteArray[18:20])
minor := binary.BigEndian.Uint16(byteArray[20:22])
measuredPower := int8(byteArray[22])
fmt.Printf("SubType: %02X\n", subType)
fmt.Printf("SubType Length: %02X\n", subTypeLength)
fmt.Printf("UUID: %X\n", uuid)
fmt.Printf("Major: %d\n", major)
fmt.Printf("Minor: %d\n", minor)
fmt.Printf("Measured Power: %d dBm\n", measuredPower)
}
})
must("start scan", err)
}
func must(action string, err error) {
if err != nil {
panic("failed to " + action + ": " + err.Error())
}
}
Code language: JavaScript (javascript)