The other week, I wanted to make a basic sample that showed how a Windows 10 device (universally – i.e. a phone, a PC or a Raspberry PI 2 etc.) with appropriate hardware (e.g. on the PI 2 you’d need a suitable bluetooth dongle) could both publish and consume advertisements over Bluetooth LE.
There’s an official sample for this over on github but I wanted to create something from scratch and I wanted to make sure that I understood what I was writing and so I wrote my own.
Since then, I was involved in a hackathon where some of the participants were using bluetooth beacons (or iBeacons) and wanted to detect them from Windows 10 apps and I shared my small pieces of code at that hackathon and it made me think that I should perhaps also write it up and share it here.
There seems to be a lot of mystery around Bluetooth LE and it seems to get quite muddied with references to iBeacon and lots of online resources mix Bluetooth LE and iBeacon as though iBeacon was the only type of beacon and as though beacons were the only type of Bluetooth LE advertising and as though advertising was the only kind of Bluetooth LE data transmission.
I quite like this overview of what Bluetooth LE is and its coverage of the range, data rates and so on;
and that data falls into the categories of advertising/scanning and connections.
I also like the same site’s introduction to using Bluetooth LE for advertising;
and it explains that advertising packets sent by a device have 31 bytes for the payload and that there is a byte specified here which denotes the type of data that is being advertised (with 0xFF meaning ‘manufacturer specific data’).
That guide also explains that a beacon is a device which only advertises (i.e. which doesn’t allow connections) and that an iBeacon is a beacon which sets its Advertising Data type to 0xFF which means ‘manufacturer specific’ and which sets a specific manufacturer ID (Apple uses 0x4C) with those IDs being listed here;
with Microsoft’s being 6 and (e.g.) Audi’s being 0x010E.
Apple also has some more detail about how an iBeacon should advertise itself which states that a developer should advertise their beacon with a GUID (UUID) which is specific to their app/use case and then also advertise 2 WORDs which are used for ‘Major’ and ‘Minor’ identifiers which might be used to signify (e.g.) country+city or office+floor or some such.
While a developer using Windows UWP APIs can;
- Advertise their service as though it was an iBeacon
- Locate iBeacons just like an iPhone app can
they aren’t limited to those scenarios – this is just a narrow subset of the functionality offered by the APIs that the Windows UWP makes available and these APIs are part of the UWP and so exist on phones, PCs, IoT devices and so on.
For my own sample, I thought that I’d write two apps;
- An app that pretended to be a beacon attached to an Audi car which was advertising its license plate such that someone looking for that particular car (e.g. someone picking up a hire car at an airport) might better locate it.
- An app that looked for bluetooth advertisements from Audi cars, hoping to determine the signal strength and use it to signify that the app was near to the car in question.
The Advertising App
My advertising app is very simple. It displays a photo of an Audi R8 (no, I don’t have one ) with a license plate;
and there’s some very minimal code sitting behind that UI;
void OnLoaded(object sender, RoutedEventArgs e) { // Here's an imaginary UK car registration number (from Wikipedia) string carReg = "BD51 SMR"; DataWriter writer = new DataWriter(); writer.WriteInt32(carReg.Length); writer.WriteString(carReg); // Let's pretend we're Audi. 0x010E is their company identifier. var manufacturerData = new BluetoothLEManufacturerData( 0x010E, writer.DetachBuffer()); this.publisher = new BluetoothLEAdvertisementPublisher(); this.publisher.Advertisement.ManufacturerData.Add(manufacturerData); this.txtCarReg.Text = carReg; this.publisher.Start(); } BluetoothLEAdvertisementPublisher publisher;
Yup, that’s it – the code is publishing an advertisement for a particular manufacturer (in this case 0x010E) and it puts the license plate number into the advertisement packet and starts the BluetoothLEAAdvertisementPublisher pushing that advertisement.
It’s worth flagging that this is foreground publishing and it requires the application to be running which may/may not be ok. For instance – I can deploy this app to my Raspberry PI 2 and leave it running. That might be fine. Conversely, if this app were running on a phone then keeping it running isn’t likely to be very practical.
However, there’s a background way of doing this via the BluetoothLEAAdvertisementPublisherTrigger class which can be used to do similar work from a background perspective.
The Scanning App
My scanning app is also very simple. It displays a simple piece of UI;
and it spins up a BluetoothLEAdvertisementWatcher which then attempts to listen for the license plate advertisements from the publishing app with a fairly flimsy attempt to keep track of whether it is seeing the same car over a period of time or whether a new car arrives or a previous car is lost;
using PublishingBeacon; using System; using Windows.Devices.Bluetooth.Advertisement; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.Loaded += OnLoaded; } void OnLoaded(object sender, RoutedEventArgs e) { this.StartTimer(); this.speechQueue = new SpeechQueue(this.mediaElement); this.StartWatchingForAdvertisements(); } void StartWatchingForAdvertisements() { this.watcher = new BluetoothLEAdvertisementWatcher(); BluetoothLEManufacturerData manufacturerData = new BluetoothLEManufacturerData() { CompanyId = 0x010E }; this.watcher.AdvertisementFilter.Advertisement.ManufacturerData.Add( manufacturerData); watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(500); this.watcher.Received += OnAdvertisementReceived; watcher.Start(); } void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) { foreach (var item in args.Advertisement.GetManufacturerDataByCompanyId( 0x010E)) { using (var dataReader = DataReader.FromBuffer(item.Data)) { var length = dataReader.ReadInt32(); this.currentRegistration = dataReader.ReadString((uint)length); this.currentSignalStrength = args.RawSignalStrengthInDBm; } } } void StartTimer() { this.timer = new DispatcherTimer(); this.timer.Interval = TimeSpan.FromSeconds(2); this.timer.Tick += OnTimerTick; this.timer.Start(); } void OnTimerTick(object sender, object e) { if (!string.IsNullOrEmpty(this.currentRegistration)) { if (string.IsNullOrEmpty(this.previousRegistration)) { this.speechQueue.SayAsync( $"found new car {this.currentRegistration}"); } else { string speech = this.currentSignalStrength > this.previousSignalStrength ? "warmer" : "colder"; // should really do some averaging and so on for these signal strengths as // they jump around a lot - not really a very reliable indicator of // distance the way I'm using it. if (Math.Abs(this.currentSignalStrength - this.previousSignalStrength) > SIGNAL_DELTA) { this.speechQueue.SayAsync(speech); this.previousSignalStrength = this.currentSignalStrength; } } this.previousRegistration = this.currentRegistration; this.txtCarReg.Text = this.currentRegistration; this.txtSignalStrength.Text = $"Signal Strength {this.currentSignalStrength}"; } else if (!string.IsNullOrEmpty(this.previousRegistration)) { this.speechQueue.SayAsync( $"lost car {this.previousRegistration}"); this.previousRegistration = null; this.previousSignalStrength = 0; this.txtCarReg.Text = "NO REG"; this.txtSignalStrength.Text = "Signal Strength 0"; } this.currentRegistration = null; this.currentSignalStrength = 0; } string currentRegistration; string previousRegistration; int currentSignalStrength; int previousSignalStrength; DispatcherTimer timer; BluetoothLEAdvertisementWatcher watcher; SpeechQueue speechQueue; static readonly int SIGNAL_DELTA = 20; }
this works really well in the sense that it detects the data being advertised by the ‘car’ but it doesn’t work so well in that it’s estimation of whether the distance between this app and the ‘car’ is growing or shrinking – that estimation seems to be very rough indeed.
There’s also some basic use of the speech to text API in the app such that it speaks to let the user know what’s going on.
This is a foreground app but, again, this work can also be done in the background via the BluetoothLEAdvertisementWatcherTrigger class so you don’t have to have an actively running foreground app.
Here’s a basic video of this app in action (tethered to the PC which makes it hard to move the app closer/further from the PC which is acting as the ‘car’);
and here’s the code for download should it be of use to you in something that you’re doing.