Windows 10, UWP, Bluetooth LE and Background Advertising, Watching

2015-10-28 14:01:30来源:作者:Mike Taulty's Blog人点击

In thispost, I had a little example of 2 Universal Windows Apps which were acting as an advertiser/consumer of some data advertised over Bluetooth LE.

One was pretending to be a bluetooth ‘beacon’ attached to a car and advertising a license plate. One was pretending to be some kind of ‘car finder’ app running on any kind of device (perhaps phone) which picked up the advertisement data, displayed it and then tried to guess how far the device was away from the car in question.

In that post, all of the work that I did was in the foreground but the Bluetooth stack on Windows 10 and in the UWP also supports doing this work in the background (although it’s interesting to note that at the time of writing I didn’t have so much success getting this to work on a Windows Mobile device but that might be because my Windows Mobile device is a little out of date with respect to the latest OS builds).

Publishing

I can write some simple, vanilla code which acts as a publisher of Bluetooth data. Firstly, that involves registering a background task which makes use of the BluetoothLEAdvertisementPublisherTrigger .

Here’s some simple code along those lines that I put behind a button just to run and register my background task by calling this OnRegister function;

async void OnRegister(object sender, RoutedEventArgs e){ if (BackgroundTaskRegistration.AllTasks.Count == 0) { var trigger = this.MakeTrigger(); // this is needed for Phone, not so for Windows in this case. var allowed = await BackgroundExecutionManager.RequestAccessAsync(); if ((allowed != BackgroundAccessStatus.Denied) && (allowed != BackgroundAccessStatus.Unspecified)) { BackgroundTaskBuilder builder = new BackgroundTaskBuilder(); builder.Name = "My Task Name"; builder.TaskEntryPoint = typeof(TheTask).FullName; builder.SetTrigger(trigger); builder.Register(); } }}BluetoothLEAdvertisementPublisherTrigger MakeTrigger(){ var trigger = new BluetoothLEAdvertisementPublisherTrigger(); var dataToPublish = "Hello"; var writer = new DataWriter(); writer.WriteInt32(dataToPublish.Length); writer.WriteString(dataToPublish); trigger.Advertisement.ManufacturerData.Add( new BluetoothLEManufacturerData() { CompanyId = 0x0006, // Microsoft, I think, didn't check again. Data = writer.DetachBuffer() } ); return (trigger);}

The intention here is to advertise the string ‘Hello’ from the device over Bluetooth LE using the Microsoft company code (which I believe is 6 according to the table ). Naturally, just advertising a string using this Microsoft company code is kind of meaningless in the real world but this is just an example.

I can deploy this code down to my Raspberry PI 2 and once the OnRegister code has been run, the device is sitting there advertising the string ‘Hello’ over Bluetooth LE.

There’s also a background task here – you can see it’s called TheTask and that’s implemented in a separate WinRT component and just looks like this;

using System;using Windows.Devices.Bluetooth;using Windows.Devices.Background;using Windows.ApplicationModel.Background;using Windows.Devices.Bluetooth.Background;using NotificationsExtensions.Toasts;using Windows.UI.Notifications;public sealed class TheTask : IBackgroundTask{ public async void Run(IBackgroundTaskInstance taskInstance) { var deferral = taskInstance.GetDeferral(); taskInstance.Canceled += OnCancelled; try { // do background work. var triggerDetails = taskInstance.TriggerDetails as BluetoothLEAdvertisementPublisherTriggerDetails; if (triggerDetails != null) { var newStatus = triggerDetails.Status; this.ShowToast( "Your bluetooth advertisement status has changed", $"The new status is {newStatus}"); } } finally { deferral.Complete(); } } void OnCancelled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { } void ShowToast(string title, string content) { var toast = new ToastVisual() { TitleText = new ToastText() { Text = title }, BodyTextLine1 = new ToastText() { Text = content } }; var toastContent = new ToastContent() { Visual = toast, ActivationType = ToastActivationType.Foreground, Duration = ToastDuration.Short }; var toastNotifier = ToastNotificationManager.CreateToastNotifier(); toastNotifier.Show(new ToastNotification(toastContent.GetXml())); }}

This is simply trying to get the trigger details as a BluetoothLEAdvertisementPublisherTriggerDetails and then it attempts to look at the new status of the advertisement (which might be Started, Stopped, etc.) and it tries to display that in a toast message.

As an aside, I’m really not sure what happens to toast messages on a Raspberry PI when it is running in a headless mode but, as far as I can tell, the action of requesting a toast message does not cause anything to break and so I’m getting away with it.

As a second aside, I’m using the NuGet package NotificationsExtensions.Win10 to help with the hassle of creating Windows 10 toast message content.

The last step here is to make sure that my use of bluetooth is reflected in my app manifest;

and to make sure that my background task is also set up in that manifest;

Consuming

The consumption side is, not surprisingly, something of a mirror of the publishing side. Firstly, I can write some code that registers a background task but this time around it’s registered on a BluetoothLEAdvertisementWatcherTrigger rather than a publishing trigger. I set it up as below;

async void OnRegister(object sender, RoutedEventArgs e){ if (BackgroundTaskRegistration.AllTasks.Count == 0) { var trigger = this.MakeTrigger(); // this is needed for Phone, not so for Windows in this case. var allowed = await BackgroundExecutionManager.RequestAccessAsync(); if ((allowed != BackgroundAccessStatus.Denied) && (allowed != BackgroundAccessStatus.Unspecified)) { BackgroundTaskBuilder builder = new BackgroundTaskBuilder(); builder.Name = "My Watcher"; builder.TaskEntryPoint = typeof(TheTask).FullName; builder.SetTrigger(trigger); builder.Register(); } }}BluetoothLEAdvertisementWatcherTrigger MakeTrigger(){ var trigger = new BluetoothLEAdvertisementWatcherTrigger(); trigger.AdvertisementFilter.Advertisement.ManufacturerData.Add( new BluetoothLEManufacturerData() { CompanyId = 0x0006 } ); return (trigger);}

This also makes use of a background task which is packaged into a separate WinRT component and its code ends up looking like;

using System;using Windows.ApplicationModel.Background;using Windows.Devices.Bluetooth;using Windows.Devices.Background;using Windows.Devices.Bluetooth.Background;using Windows.Storage.Streams;using NotificationsExtensions.Toasts;using Windows.UI.Notifications;public sealed class TheTask : IBackgroundTask{ public async void Run(IBackgroundTaskInstance taskInstance) { var deferral = taskInstance.GetDeferral(); taskInstance.Canceled += OnCancelled; try { // do background work. var triggerDetails = taskInstance.TriggerDetails as BluetoothLEAdvertisementWatcherTriggerDetails; if (triggerDetails != null) { foreach (var advert in triggerDetails.Advertisements) { var advertType = advert.AdvertisementType; var address = advert.BluetoothAddress; var manufacturers = advert.Advertisement.GetManufacturerDataByCompanyId(0x0006); foreach (var manufacturer in manufacturers) { using (var dataReader = DataReader.FromBuffer(manufacturer.Data)) { var length = dataReader.ReadInt32(); var advertisedString = dataReader.ReadString((uint)length); // let's toast them all! this.ShowToast( "Spotted an advertisement", $"The content was {advertisedString}"); } } } } } finally { deferral.Complete(); } } void OnCancelled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { } void ShowToast(string title, string content) { var toast = new ToastVisual() { TitleText = new ToastText() { Text = title }, BodyTextLine1 = new ToastText() { Text = content } }; var toastContent = new ToastContent() { Visual = toast, ActivationType = ToastActivationType.Foreground, Duration = ToastDuration.Short }; var toastNotifier = ToastNotificationManager.CreateToastNotifier(); toastNotifier.Show(new ToastNotification(toastContent.GetXml())); }}

There’s a little more code here. We’re trying to interpret the trigger details that are passed into our background component as BluetoothLEAdvertisementWatcherTriggerDetails and if that works then we dig in to the advertising data to grab out the info that was actually advertised and, if we find it for the 0x0006 company ID then we ultimately send out toast messages to indicate what data we have seen.

This also requires me to specify in my application manifest that I am using Bluetooth and it also requires me to specify the details of the background task in that manifest.

Running It

I deployed my publisher to my Raspberry PI 2 which I have a Bluetooth dongle attached to.

I deployed my consumer to my local PC and ran it whereopen I saw my first toast notification that it had found the published event from the Raspberry PI 2. It’s not very visually exciting but here it is;

(I only saw one).

The second instance comes from my curiosity around what happens when I unplug the PI and then plug it back in having registered a background task like this one that is publishing advertising data periodically.

What happens is exactly what I would hope would happen – the PI comes back into life. It’s important to note that I have not set my application to be the default application on the PI and yet (as I would hope) my background task springs back into life and starts advertising again and the app on my PC displays a toast again (as you can see from the Action Center screenshot above).

Nice

What I Really Wanted to Do…

That’s all well and good and it works quite nicely but what I was really hoping to test out here was the idea of something like a ‘smarter beacon’. Something like;

A beacon advertises some snippet of data in the background. A consuming device passes by, sees the data in the background and offers it to the user in some fashion (e.g. by a toast notification or similar). The user decides that they want more data and so taps on a toast to launch the app which connects back to the bluetooth advertiser and grabs some more data that’s relevant to whatever scenario we’re working with.

The basic idea being that an initial advertisement can lead to a connection.

I was also tantalised a little bit by the presence of this AdvertisementType property that’s passed to my code when an advertisement gets picked up. That enum talks of;

ConnectableUndirected| connectableUndirected

The advertisement is undirected and indicates that the device is connectable and scannable. This advertisement type can carry data.

This corresponds with the ADV_IND type defined in the Bluetooth LE specifications.

and I was really hoping that I might be able to get this kind of advertisement into my code and follow it up with some kind of connection that was initiated by advertisement.

To date, I haven’t been able to do this. All advertisements that I’ve seen to date are of the form ScannableUndirected and I’ve asked around and I think that this is currently by design so making a connection based on an advertising packet might not be a scenario that can be implemented this way.

That said, I’m thinking there might be more than one way to achieve this and I’ll plan to follow up this post with another one if I can get something together around it…

Posted

by

相关文章

    无相关信息

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台