Sergiy Baydachnyy

Blog about technologies

UWP: Working with Bluetooth devices (part 2: pairing)

leave a comment »

Implementing the previous example, I didn’t pair my device with my Windows 10 PC, but in the most cases you cannot omit pairing. Exactly thanks to pairing you can establish trust communications with a Bluetooth device that will allow your application get access to characteristics, lock the device to your computer and many other things. Windows 10 allows you to pair any device (not just Bluetooth) using Settings window:

clip_image002

But you can pair your devices directly from your application. In order to do it you can use the same DeviceInformation class that we used to track available devices.

Let’s modify our code. We will check the status and if the device is not paired we will pair it. You can use the following code in order to pair the device and navigate the application to the next page:

private async void deviceListView_ItemClick(object sender, ItemClickEventArgs e)
{
    var item = e.ClickedItem as DeviceInformation;
    if (item.Pairing.CanPair)
    {
        var result = await item.Pairing.PairAsync();
        if ((result.Status == DevicePairingResultStatus.Paired) ||
            (result.Status == DevicePairingResultStatus.AlreadyPaired))
        {
            this.Frame.Navigate(typeof(DevicePage), item);
        }
    }
    else if (item.Pairing.IsPaired == true)
    {
        this.Frame.Navigate(typeof(DevicePage), item);
    }
}

You can see that we used Pairing property to get a reference to an object of DeviceInformationPairing type. Using it we can check if the device is already paired, and pair the device if needed.

Pay attention that Windows is caching all paired devices. So, once your device is paired, you will be able to see it in the list, even if the device is not present physically. It’s a problem and it’s better to check if the device is still available.

Of course, pairing process may require some actions from user side. For example, STEVAL-IDB007V1 evaluation board requires to inter a pin (123456 by default) to complete the pairing process. So, the PairAsync method will show all needed dialogs:

clip_image004

Once the device is paired, the application will display the next page with data. If you navigate your application back, you will be able to see that the device is already paired:

clip_image006

UWP API supports one more way for pairing: custom pairing mechanism. Using this way, you can create your own dialogs that will requires all needed pins or any additional information. In our case we can suppress the default pin dialog and hardcode the default pin in code. The following code demonstrates this feature:

private async void deviceListView_ItemClick(object sender, ItemClickEventArgs e)
{
    var item = e.ClickedItem as DeviceInformation;
    if (item.Pairing.CanPair)
    {
        //var result = await item.Pairing.PairAsync();
        var customPairing = item.Pairing.Custom;
        customPairing.PairingRequested += CustomPairing_PairingRequested;
        var result =
            await customPairing.PairAsync(DevicePairingKinds.ProvidePin);
        customPairing.PairingRequested -= CustomPairing_PairingRequested;
        if ((result.Status == DevicePairingResultStatus.Paired) ||
            (result.Status == DevicePairingResultStatus.AlreadyPaired))
        {
            this.Frame.Navigate(typeof(DevicePage), item);
        }
    }
    else if (item.Pairing.IsPaired == true)
    {
        this.Frame.Navigate(typeof(DevicePage), item);
    }
}
private void CustomPairing_PairingRequested(
    DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
{
    args.Accept("123456");
}

You can see that we used Custom property in order to invoke custom pairing process. Using this property and PairAsync method there we can activate pairing process. It will generate a special event PairingRequested where we can provide all needed information. In our case we simply use Accept method with the pin as a parameter.

Of course, Microsoft try to avoid any security issues with pairing. So, even in the case of custom pairing users will be able to see a notification window:

clip_image008

One more benefit due to pairing is ability to use a watcher to list services. In the previous example we used a snapshot of all services, but in the real life this process may take some time or some services may be available later. Using a watcher, you can control the process showing progress and all recognized services dynamically. Below you can find modified code that uses DeviceWatcher class to list all services for a selected device:

public async void StartReceivingData()
{
    leDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
    string selector = "(System.DeviceInterface.Bluetooth.DeviceAddress:=\"" +
        leDevice.BluetoothAddress.ToString("X") + "\")";
    watcher = DeviceInformation.CreateWatcher(selector);
    watcher.Added += Watcher_Added;
    watcher.Removed += Watcher_Removed;
    watcher.Start();
    timer.Interval = new TimeSpan(0, 0, 1);
    timer.Tick += Timer_Tick;
    timer.Start();
}
private async void Watcher_Removed(DeviceWatcher sender,
    DeviceInformationUpdate args)
{ }
private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    var service = await GattDeviceService.FromIdAsync(args.Id);
    if (service!=null)
    {
        switch (service.Uuid.ToString())
        {
        case SensorUUIDs.UUID_ENV_SERV:
            InitializeTemperatureSensor(service);
            InitializePressureSensor(service);
        break;
        case SensorUUIDs.UUID_ACC_SERV:
            InitializeAccelerationSensor(service);
        break;
        }
    }
}

You can see that code is not very complex, but you can extend your UI now showing progress.

Advertisements

Written by Sergiy Baydachnyy

04/28/2017 at 11:08 PM

UWP: Working with Bluetooth devices (part 1)

with 4 comments

Code: Chapter26_BluetoothData on https://github.com/sbaidachni/Win10BookSamples

Universal Windows Platform supports really great Bluetooth API that allows us to build applications that are connected to various kinds of wiring devices. Your application can support Bluetooth LE (Low Energy, 4.x version) or even previous versions of the protocol, and I am going to cover how to use all of them, but let’s start with Bluetooth LE.

Bluetooth Low Energy

In order to build couple samples, I would recommend to use any BLE development board. The board should contain several sensors and a preinstalled example. In my case I have an evaluation board from STMicroelectronics (STEVAL-IDB007V1) based on BlueNRG-1 Bluetooth chip. This board contains a 3D digital accelerometer, a gyroscope (preinstalled example doesn’t return data from gyroscope by design) and a pressure sensor with an embedded temperature sensor. All these things we will be able to use in our application.

clip_image002

In fact, you can use any BLE device like a smart bulb that you can buy in Home Depot, but prior to buy anything you need to make sure that there is a document that describes all services and characteristics. Of course, using UWP API you will be able to read all available characteristics, but in the case of “no name” devices it’s really hard to understand how to interpret incoming data.

Universal Windows Platform contains five namespaces with Bluetooth keyword including Windows.Devices.Bluetooth. Exactly this namespace contains the BluetoothLEDevice class that will help us to get information about all available services and characteristics. But prior to start creating any instance of this class we need to implement interface that will allow a user to select our device from the list. Of course, we can list all device in code and connect to any of them without any interaction with the user, but it’s always better to get confirmation from the user.

Therefore, I am going to implement UI that will use some criteria to list all available devices and allows users to select one from the list. We can do it using the Windows.Devices.Enumeration namespace. In general, the namespace doesn’t contain any Bluetooth related classes. Instead, there are several universal classes that allow us to list anything that is connected to our computer/phone. This namespace even supports DevicePicker and DevicePickerFilter classes that can simplify our work thanks to embedded dialogs. But using these classes is a trivial task:

DevicePicker picker = new DevicePicker(); picker.Filter.SupportedDeviceSelectors.Add
    BluetoothLEDevice.GetDeviceSelectorFromPairingState(false));
picker.Filter.SupportedDeviceSelectors.Add(
    BluetoothLEDevice.GetDeviceSelectorFromPairingState(true));
picker.Show(new Rect(x, y, width, height));

Code below will show the following dialog that display all paired and non-paired Bluetooth Low Energy devices:

clip_image004

The dialog support DeviceSelected event that helps us to understand which device is selected. Alternatively, you can use PickSingleDeviceAsync method to avoid any event handlers. You can see that DevicePicker can display any devices. So, in order to display just BLE devices we need to setup a filter using Advanced Query Syntax string. ADS can be a little bit complex to setup. That’s why we used the BluetoothLEDevice class to get access to some predefined strings. Using the GetDeviceSelectorFromPairingState method we can return all BLE devices that are paired or non-paired. Adding both strings to the filter, we can select all available BLE devices.

In my examples, I am not going to use embedded dialogs. Instead of using them, I decided to build my own interface. In order to implement it, I need to create a page that will display a list of all devices in my own way. Using the DeviceInformation class I can find all BLE devices using the FindAllAsync method, but this approach is not the best one, because, I have to assume that users can activate new Bluetooth devices “on fly”. So, we have to look at all changes all time rather than use a snapshot and we cannot use just the FindAllAsync method in the DeviceInformation class. Instead, we will create a watcher that will notify us about any changes in the list of available devices. In order to do that, we can use the DeviceWatcher class that is available in the Windows.Devices.Enumeration namespace. Let’s look at the following code:

DeviceWatcher deviceWatcher;
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    deviceWatcher = DeviceInformation.CreateWatcher(
        "System.ItemNameDisplay:~~\"BlueNRG\"",
        new string[] {
            "System.Devices.Aep.DeviceAddress",
            "System.Devices.Aep.IsConnected" },
        DeviceInformationKind.AssociationEndpoint);
    deviceWatcher.Added += DeviceWatcher_Added;
    deviceWatcher.Removed += DeviceWatcher_Removed;
    deviceWatcher.Start();
    base.OnNavigatedTo(e);
}

You can see that in order to create the watcher, we used the CreateWatcher static method that you can find in the DeviceInformation class. This method accepts several parameters. Using the first one we can provide a filter that can help us list just needed devices. It’s the same Advanced Query Syntax string that we used before, but in this case, we created it from scratch rather than using a predefined one. It’s a good idea to show different approaches and I used a filter that helps me find exactly devices that contain BlueNRG string in their names. To implement this filter I used the System.ItemNameDisplay property. Because all my dev kit is available as BlueNRG by default, my application will show just my device. Of course, we should not hardcode any names and it’s better to use less restrictive filter like we did in the first block of codeIf you want to find more information about possible ADS properties you can visit this page.

The second parameter is a set of properties that should be available for a device. In this case we requested DeviceAddress and IsConnected properties, but I used it just for the demo. I assume that you will not able to find many different devices with BlueNRG name around, so, you can simply remove this parameter.

Finally, we have to pass the DeviceInformationKind flag. In our case it should be AssociationEndpoint that is true for all Bluetooth LE devices that advertise their interface.

Once we create the watcher, we need to assign event handlers to Added and Removed events and start the watcher. Pay attention that if you forget to assign Removed event handler, the watcher will not work. I am not sure “why”, but once I assigned an empty event handler, the watcher started without any problem. Here is my code:

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    deviceWatcher.Stop();
    base.OnNavigatedFrom(e);
}
private void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
    //throw new NotImplementedException();
}
private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    var device = await BluetoothLEDevice.FromIdAsync(args.Id);
    var services=await device.GetGattServicesAsync();
    foreach(var service in services.Services)
    {
        Debug.WriteLine($"Service: {service.Uuid}");
        var characteristics=await service.GetCharacteristicsAsync();
        foreach (var character in characteristics.Characteristics)
        {
            Debug.WriteLine($"Characteristic: {character.Uuid}");
        }
    }
}

Now, you can run the code above and it will start looking for a BlueNRG device and once it’s available, it will print all supported services and characteristics to the Output window. I got the following data for my device:

Service: 00001801-0000-1000-8000-00805f9b34fb
Characteristic: 00002a05-0000-1000-8000-00805f9b34fb
Service: 00001800-0000-1000-8000-00805f9b34fb
Characteristic: 00002a00-0000-1000-8000-00805f9b34fb
Characteristic: 00002a01-0000-1000-8000-00805f9b34fb
Characteristic: 00002a04-0000-1000-8000-00805f9b34fb
Service: 02366e80-cf3a-11e1-9ab4-0002a5d5c51b
Characteristic: e23e78a0-cf4a-11e1-8ffc-0002a5d5c51b
Characteristic: 340a1b80-cf4b-11e1-ac36-0002a5d5c51b
Service: 42821a40-e477-11e2-82d0-0002a5d5c51b
Characteristic: a32e5520-e477-11e2-a9e3-0002a5d5c51b
Characteristic: cd20c480-e48b-11e2-840b-0002a5d5c51b

You can see that the device supports four services and each of them contains from one to three characteristics. In order to understand what is it, we need to visit STMicroelectronics web-site (st.com) and find a document that describes all these things. The document called UM2071: BlueNRG-1 development kit. If you type this name in the Search box and open the Resource tab, you will be able to find it. This document contains much information, but we need just a table with characteristics (Figure 20):

clip_image006

Looking at this table we can note service UUIDs for Acceleration, Temperature and Pressure. It’s exactly that I am going to display in my application. Potentially we can check Free Fall, but I have just one board, and I will try to avoid any scary experiments.

Pay attention that Bluetooth LE standard contains some standard profiles that can help you recognize some standard services. Profiles, it’s something that operating system uses to connect any Bluetooth mouse or stream video/audio between different devices (not BLE, but the idea is the same). In some case you will need to rely on standard profiles, but in some cases, you can create your own. Looking at the table below you can see that our development board implements standard attribute and generic access profiles. Thanks to them Windows can read some information about the device like device name. But all other services implement custom profiles. It’s ok if your device is not going to support any standard feature and it can be a problem for you if you want to connect some low-cost Bluetooth devices to your application. I found that lots of devices from “no name” companies implement own custom profile and if you want to connect your phone or tablet to these devices you have to download an application. But once you want to create your own application, it’s really hard to find how the device works.

Ok. Now we know where to find information about supported services and characteristics, but we still didn’t finish our main page. So, let’s do it.

In order to store information about all available devices I will use a collection that will store references to DeviceInformation objects:

ObservableCollection<DeviceInformation> deviceList =
    new ObservableCollection<DeviceInformation>();

The collection should be observable because we are going to bind it to our interface and it should track any changes dynamically.

Now, we can modify our event handlers:

private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
    var toRemove = (from a in deviceList where a.Id == args.Id select a).FirstOrDefault();
    if (toRemove != null)
        await this.Dispatcher.RunAsync(
            Windows.UI.Core.CoreDispatcherPriority.Normal,
            () => { deviceList.Remove(toRemove); });
}
private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await this.Dispatcher.RunAsync(
        Windows.UI.Core.CoreDispatcherPriority.Normal,
        () => { deviceList.Add(args); });
}

You can see that we use the handlers to track changes in the list. Pay attention that both handlers are running in non-UI thread. So, we have to invoke dispatcher to modify our collection.

Finally, we can build UI. It will be primary a ListView that will display Id, Name and Pairing properties:

<ListView Grid.Row="2" Name="deviceListView" ItemsSource="{Binding}" IsItemClickEnabled="True" ItemClick="deviceListView_ItemClick" HorizontalAlignment="Center">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid Margin="10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"></ColumnDefinition> 
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Image Source="Assets/stlogo.png" Margin="10" Grid.RowSpan="3"></Image>
                <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Id}"></TextBlock>
                <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Name}"></TextBlock>
                <StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal">
                    <TextBlock Text="Can be paired: "></TextBlock>
                    <TextBlock Text="{Binding Pairing, Converter={StaticResource pairingConv}, Mode=OneWay}"></TextBlock>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

To run this code, you need to add an empty event handler for ItemClick. We will implement it later. Additionally, you can use OnNavigatedTo to set DataContext property and activate binding:

this.DataContext = deviceList;

Running this code, we will see the following window:

clip_image008

Pay attention that our device is still not paired, but in order to start reading data from it we should not pair it. This feature is available since Windows 10 Creators Update. All previous UWP versions requires pairing prior start reading any data.

Ok. Now we can implement ItemClick event handler and once a user selects the device, we may navigate our application to the second page:

private void deviceListView_ItemClick(object sender, ItemClickEventArgs e)
{
    this.Frame.Navigate(typeof(DevicePage), e.ClickedItem);
}

In order to simplify work with all sensors I created a base class that implements all needed features to read values from characteristics:

public class SensorBase:IDisposable
{
    protected GattDeviceService deviceService;
    protected string sensorDataUuid;
    protected byte[] data;
    protected bool isNotificationSupported = false;
    private GattCharacteristic dataCharacteristic;
    public SensorBase(GattDeviceService dataService, string sensorDataUuid)
    {
        this.deviceService = dataService;
        this.sensorDataUuid = sensorDataUuid;
    }
    public virtual async Task EnableNotifications()
    {
        isNotificationSupported = true;
        dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
            new Guid(sensorDataUuid))).Characteristics[0];
        dataCharacteristic.ValueChanged += dataCharacteristic_ValueChanged;
        GattCommunicationStatus status =
            await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                GattClientCharacteristicConfigurationDescriptorValue.Notify);
    }
    public virtual async Task DisableNotifications()
    {
        isNotificationSupported = false;
        dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
            new Guid(sensorDataUuid))).Characteristics[0];
        dataCharacteristic.ValueChanged -= dataCharacteristic_ValueChanged;
        GattCommunicationStatus status =
            await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
            GattClientCharacteristicConfigurationDescriptorValue.None);
    }
    protected async Task<byte[]> ReadValue()
    {
        if (!isNotificationSupported)
        {
            if (dataCharacteristic == null)
                dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
                    new Guid(sensorDataUuid))).Characteristics[0];
            GattReadResult readResult =
                await dataCharacteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
            data = new byte[readResult.Value.Length]; 
            DataReader.FromBuffer(readResult.Value).ReadBytes(data); 
        }
        return data;
    }
    private void dataCharacteristic_ValueChanged(GattCharacteristic sender,
        GattValueChangedEventArgs args)
    {
        data = new byte[args.CharacteristicValue.Length];
        DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);
    }
    public async void Dispose()
    {
        await DisableNotifications();
    }
}

In general, the most important method in this class is ReadValue, but BLE supports notification mechanism. From the GATT table, we can see that our board supports at least one sensor that allows us use notifications to receive updated data. This is accelerometer sensor. Of course, I could read data from accelerometer using just ReadValueAsync method, but I wanted to show how to use notifications. It’s not always true that your device will send updated data several time per second and in this case notifications allows to save some time, avoiding not needed queries. You can see that in order to enable notification for a characteristic we simply have to add a special descriptor using WriteClientCharacteristicConfigurationDescriptorAsync method. Once the descriptor is assigned, we will be able to receive messages with new values for the characteristic.

Using our base class, it’s not a problem to create three more classes that describe our sensors. Here is an example for the temperature sensor:

public class TemperatureSensor: SensorBase
{
    public TemperatureSensor(GattDeviceService dataService) :
        base(dataService, SensorUUIDs.UUID_ENV_TEMP) { }
    public async Task<double> GetTemperature()
    {
        byte[] data = await ReadValue();
        return ((double)BitConverter.ToInt16(data,0))/10;
    }
}

Pay attention that by default our sensors don’t use notification mechanism. So, if you want to enable notification for accelerometer, it’s needed to call EnableNotification method.

protected async void InitializeAccelerationSensor(GattDeviceService service)
{
    accSensor = new AccelerationSensor(service);
    await accSensor.EnableNotifications();
}

Finally, we can create a view model and bind it to our interface. I am not going to discuss all aspects of the view model, but want to concentrate your attention around couple methods. The first one is a method that initializes everything:

public async void StartReceivingData()
{
    leDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
    string selector = "(System.DeviceInterface.Bluetooth.DeviceAddress:=\"" +
         leDevice.BluetoothAddress.ToString("X") + "\")";
    var services = await leDevice.GetGattServicesAsync();
    foreach (var service in services.Services)
    { 
        switch (service.Uuid.ToString())
        {
        case SensorUUIDs.UUID_ENV_SERV:
            InitializeTemperatureSensor(service); 
            InitializePressureSensor(service); 
            break;
        case SensorUUIDs.UUID_ACC_SERV:
            InitializeAccelerationSensor(service);
            break;
        }
    }
    timer.Interval = new TimeSpan(0, 0, 1);
    timer.Tick += Timer_Tick;
    timer.Start();
}

You can see that we simple list all available services and once we found needed service we initialize appropriate sensor. This code should work even if you select a wrong device from the list of all BLE devices. The user interface will simply display nothing. Because two of our characteristics don’t support notifications we will simply use a timer to update the UI once per second.

The second method is exactly Tick implementation. It simply read data from all available sensors (characteristics) and uses INotifyPropertyChanged to update bindings:

public async void UpdateAllData()
{
    if (tempSensor != null)
    {
        temperature = String.Format(
            $"{(await tempSensor.GetTemperature())} Celsius");
        if (PropertyChanged!=null)
            PropertyChanged(this,
                new PropertyChangedEventArgs("Temperature"));
    } 
    if (presSensor != null)
    {
        pressure = String.Format(
            $"{(await presSensor.GetPressure()).ToString()} milliBar");
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("Pressure"));
    }
    if (accSensor != null)
    {
        var data = await accSensor.GetAcceleration();
        angleX = data[0];
        angleY = data[1];
        angleZ = data[2];
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("AngleX"));
            PropertyChanged(this, new PropertyChangedEventArgs("AngleY"));
            PropertyChanged(this, new PropertyChangedEventArgs("AngleZ"));
        }
    }
}

Running our code, we will be able to see two tabs. On the first tab (Acceleration), you will be able to see data from the accelerometer:

clip_image010

The second tab provides information about temperature and pressure:

clip_image012

Now, you know how to connect your application to any BLE device and get data from there. At the same time, we didn’t pair out device. Let’s see, how to use Bluetooth API to implement pairing and get some benefits from there.

Written by Sergiy Baydachnyy

04/28/2017 at 4:10 AM

Developing for Surface Dial

leave a comment »

Code: Chapter47_RotateImage on https://github.com/sbaidachni/Win10BookSamples

Today, I want to discuss one more sensor – Surface Dial. It’s a new Bluetooth device from Microsoft that has own API that you can use in your UWP applications. Due to this API, I decided to show how to work with this device in a separate post rather than extend my post about Bluetooth (see next one in couple days).

clip_image002

Surface Dial is a device that you can buy separately, and it can work with any computer on Windows 10 Creators Update and later. Once you connect the device to your computer, you will be able to use Wheel settings window to setup default tools and even associate your Surface Dial with an application, assigning application shortcuts to Surface Dial tools.

clip_image004

In general, Surface Dial supports three ways to interact: rotation, click, tap and hold. Of course, it’s possible to understand rotation direction and other parameters like angle. In the case of tap and hold this action is reserved for the Dial and allows users to display Surface Dial menu. Counting all available actions (click, rotation left and rotation right), you can assign up to three application shortcuts to each custom tool. Therefore, we can say that it’s possible to integrate the device with any application that supports shortcuts.

There are several standard tools like Volume, Scroll, Zoom and so on. Some of these tools like Volume can have sense anywhere, but some of them like Scroll have sense just for subset of applications. In any case, you can configure up to six custom and standard tools in total and start using them without any development effort. But we are developers and below I will describe how to create own custom tools dynamically that users will be able to invoke in your application’s context.

Let’s look at the API. Almost all needed classes are located in Windows.UI.Input namespace and you should not install any additional packages, because Surface Dial API is a part of UWP platform in Windows 10. In our example, we will primary use three classes from the namespace: RadialController, RadialControllerConfiguration and RadialControllerMenuItem. The first class describes the controller itself and allows us to get access to all menu items and events. The second class allows us to activate/deactivate some features that are related to the controller. Finally, the RadialControllerMenuItem class helps us to create additional menu items/tools.

It’s not wisely to setup the controller in the context of the whole application. Each page in your application may have own features and custom tools and it’s better to setup the controller in the context of each page. Therefore, in the case of multi-page applications you need to create a separate class that will help you to setup the controller in OnNavigatedTo method. In our case we will have just one page and it’s possible to setup the controller in the constructor right after InitializeComponent call.

Let’s look at the code below:

controller = RadialController.CreateForCurrentView();
customItem = RadialControllerMenuItem.CreateFromKnownIcon(
    "Rotate", RadialControllerMenuKnownIcon.Ruler);
controller.Menu.Items.Add(customItem);
controller.RotationChanged += Controller_RotationChanged;

This code allows us to get a reference to the controller and create a new menu item with Rotate name and using a standard icon Ruler. Of course, I could use my own icon using CreateFromIcon method or even glyph using CreateFromFontGlyph, but you can experiment with these methods yourselves. Additionally, we assign our own event handler to RotationChanged event. Thanks to this event handler we are going to rotate an image in our application:

private void Controller_RotationChanged(RadialController sender,
    RadialControllerRotationChangedEventArgs args)
{
    rotateTransform.Angle += args.RotationDeltaInDegrees;
}

XAML for our page is below:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Image Source="Assets/mushroom.jpg" Margin="100" RenderTransformOrigin="0.5,0.5">
        <Image.RenderTransform>
            <RotateTransform
                x:Name="rotateTransform"></RotateTransform>
        </Image.RenderTransform>
     </Image>
</Grid>

Of course, there are some tricks that you have to know even implementing this simple code:

  • RotationChanged or button events will work only for your custom tools. It means that your image will not be rotated until a user select our Rotate tool. Therefore, it’s safe to assign handlers and it will not override default behavior for standard tools;
  • In our case we have just one custom menu item, but in the case of two menu items RotationChanged will fire for both. That’s why it’s important to use Invoke event that you can find in each menu item. Using this event, you can setup some flags to store information about selected menu item and implement your code based on the context;

One more important thing that you have to count is maximum number of menu items (six). Potentially you can get some problems if a user filled all slots. At the same time your application may not support all standard tools. So, it’s better to leave just needed tools in your application. It’s possible thanks to the RadialControllerConfiguration class.

config = RadialControllerConfiguration.GetForCurrentView();
config.SetDefaultMenuItems(new[] {
    RadialControllerSystemMenuItemKind.Volume,
    RadialControllerSystemMenuItemKind.Scroll });

You can see that we left just two menu items, and we have enough room for our custom tools. If you want to remove all of them, you can pass empty array as the parameter.

It’s time to start our application. If you start the application, you will be able to see the following menu once you tap and hold the controller:

clip_image006

There are two standard menu items and our custom one. If you select the custom one and rotate the controller, it will rotate our image. But the application has some problems. The first one is in that the controller generates RotationChanged event if you rotate it for 10 degrees or more. So, our rotation effect is not smooth. We can change this behavior using RotationResolutionInDegrees property:

controller.RotationResolutionInDegrees = 1;

But once we change the property, we will have another problem: the controller will produce permanent buzz. It will happen due to haptic feedback. If the controller generates the feedback not too often, it works fine, but once the controller starts to generate feedback continuously, you will want to disable it. It’s possible to do using the following line:

controller.UseAutomaticHapticFeedback = false;

So, we have added two more line to our code and now the application works better. But for the final user it’s still not clear how our tool work, and the rotation itself didn’t display any additional parameters like the current angle. So, it’s wisely to display own user control once a user selects your custom tool. In our case we can build a user control that will display the current angle using digits and graphical representation.

I decided to use Win2D in order to build own control. It’s easy to do and our XAML contains just couple elements:

<Grid>
   <canvas:CanvasControl Name="canvasControl" Draw="canvasControl_Draw" />
</Grid>

C# code is not very complex as well. It just declares a dependency property and a method that draws our UI:

public static readonly DependencyProperty AngleProperty = DependencyProperty.Register(
    "Angle",
    typeof(float),
    typeof(RotationControl),
    new PropertyMetadata(null)
);
public float Angle
{
    get
    {
        return (float)GetValue(AngleProperty);
    }
    set
    {
        SetValue(AngleProperty, value);
        canvasControl.Invalidate();
    }
}
private void canvasControl_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
    int stroke = 3;
    float width = (float)this.ActualWidth;
    float height = (float)this.ActualHeight;
    float radius=Math.Min(width, height)/2-2*stroke;
    float centerX = width / 2;
    float centerY = height / 2;
    float lineEndX = radius * (float)Math.Cos(Math.PI * Angle / 180) + centerX;
    float lineEndY = radius * (float)Math.Sin(Math.PI * Angle / 180) + centerY;
    args.DrawingSession.DrawCircle(centerX, centerY, radius, Colors.Red,stroke);
    args.DrawingSession.DrawLine(centerX, centerY,lineEndX , lineEndY,
        Colors.Green, stroke);
    args.DrawingSession.DrawText(Angle.ToString(), centerX, centerY, Colors.Black);
}

We use Invalidate method of Win2D surface to redraw our surface once we change the angle and the surface itself contain a circle, a line and text. You can design better UI, but I wanted to simplify my code.

Now, I can modify my XAML in Main.xaml:

<Image Source="Assets/mushroom.jpg" Margin="100" RenderTransformOrigin="0.5,0.5">
    <Image.RenderTransform>
        <RotateTransform Angle="{Binding Angle, ElementName=rotationControl}"
            x:Name="rotateTransform"></RotateTransform>
    </Image.RenderTransform>
</Image>
<controls:RotationControl Visibility="Collapsed" x:Name="rotationControl" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" Height="100" Margin="20"></controls:RotationControl>

You can see that there is an element binding to avoid unsynchronized behavior between the image and our control. Additionally, our control is collapsed by default. We have to display it just in the case if our custom tool is selected. To understand it, we can use ControlLost and ControlAquired events:

controller.ControlLost += Controller_ControlLost;
controller.ControlAcquired += Controller_ControlAcquired;
private void Controller_ControlAcquired(RadialController sender, RadialControllerControlAcquiredEventArgs args)
{
    rotationControl.Visibility = Visibility.Visible;
}
private void Controller_ControlLost(RadialController sender, object args)
{
    rotationControl.Visibility = Visibility.Collapsed;
}

Finally, we need to modify RotationChanged method:

private void Controller_RotationChanged(RadialController sender, RadialControllerRotationChangedEventArgs args)
{
    rotationControl.Angle += (float)args.RotationDeltaInDegrees;
}

If you run the application and select our custom tool, you will be able to see our user control:

clip_image008

That’s not all. Now, I want to modify the code counting that we don’t need any standard tools. But if we have just our tool, probably, it’s wisely to suppress standard menu at all. API allows me to fully suppress the standard menu and work with ButtonHolding event if the menu is suppressed. In order to do that we can add just four lines of code:

controller.Menu.SelectMenuItem(customItem);
config.ActiveControllerWhenMenuIsSuppressed = controller;
config.IsMenuSuppressed = true;
controller.ButtonHolding += Controller_ButtonHolding;

The first line activates exactly our custom menu item. Next two lines allow us to suppress the menu. It’s important to use the ActiveControllerWhenMenuIsSuppressed property and if you don’t assign it nothing will work. Finally, we need to handle ButtonHolding event to activate and deactivate our control. You can find ButtonHolding event handler below:

private void Controller_ButtonHolding(RadialController sender,
RadialControllerButtonHoldingEventArgs args)
{
    if (rotationControl.Visibility == Visibility.Visible)
    {
        controller.RotationChanged -= Controller_RotationChanged;
        rotationControl.Visibility = Visibility.Collapsed;
    }
    else
    {
        controller.RotationChanged += Controller_RotationChanged;
        rotationControl.Visibility = Visibility.Visible;
    }
}

Running this code, you will be able to see that we fully suppressed the default menu and used our own control as an alternative menu instead. It’s really great! In fact, you can do anything with the controller in the context of the application, but that’s not all.

Some devices like Surface Studio allows users to work with the controller directly on screen. In this case, you can handle three events: ScreenContactStarted, ScreenContactEnded and ScreenContactContinued. Thanks to these events you will be able to know a position of the controller and a virtual rectangle that contains the controller. Using this information, it’s possible to execute tools applying actions to the position of the controller. For example, we can rotate our image not around its center but around the center of the controller. But the most beautiful effects can be created exactly around your own custom menu. In our case, the menu can be expanded to include the controller inside the area, and as for me, it’s the most important guideline that you have to follow: if you have a custom menu always handle screen contact events and adjust your menu based on location of the controller on the screen. In our case it will require some additional work, but it’s not a problem to write ten more lines of code (add width and height properties to the control, change text location and handle the events placing the control using Margin property).

Written by Sergiy Baydachnyy

04/25/2017 at 10:39 PM

How to manage AllJoyn devices in C#: LIFX Color 1000 Example

with one comment

In the previous video we discussed, how to onboard an AllJoyn device using Visual Studio, C# and Windows 10 AllJoyn features (How to onboard AllJoyn devices in C#: LIFX Color 1000 Example).

Today, I am going to show, how we can start working with an “onboarded” AllJoyn device.

https://channel9.msdn.com/Series/Internet-of-Things-micro-boards-for-beginners/How-to-work-with-AllJoyn-devices-in-C-LIFX-Color-1000-example/player

Written by Sergiy Baydachnyy

06/06/2016 at 7:06 PM

Posted in IoT, Visual Studio, Windows 10

Tagged with

About Extensions in Visual Studio Code

leave a comment »

Written by Sergiy Baydachnyy

06/03/2016 at 8:08 PM

Posted in Visual Studio

Tagged with

ASP.NET Core and VS Code

leave a comment »

.NET Core RC 2, ASP.NET Core RC 2 and even .NET Core SDK are available for download. I have tested if it work with Visual Studio Code and… It works great!!!

You should not use any dnx/think about available runtimes and select between mono and coreclr/understand what is dotnet package manager and so on. You can simply install .NET Core and start creating your ASP.NET applications.

My updated video about VS Code and ASP.NET is here:

 

https://channel9.msdn.com/Series/Visual-Studio-Code-for-Mac-Developers/16-Start-working-with-ASPNET/player

And couple new videos in the series (about Express and Cordova tools):

https://channel9.msdn.com/Series/Visual-Studio-Code-for-Mac-Developers/17-Express/player

https://channel9.msdn.com/Series/Visual-Studio-Code-for-Mac-Developers/18-Tools-for-Apache-Cordova/player

Written by Sergiy Baydachnyy

06/01/2016 at 10:43 PM

Posted in Visual Studio

Tagged with

How to onboard AllJoyn devices in C#: LIFX Color 1000 Example

leave a comment »

I already published some articles about AllJoyn, ZigBee/Z-Wave bridges and bulbs. You can find all my previous posts using these links:

Smart bulbs or how to be lazier (part 1)

Smart bulbs or how to be lazier (part 2)

Smart bulbs or how to be lazier (part 3): AllJoyn client

Smart bulbs or how to be lazier (part 4): IoT Hub

 

But today I got a chance to play with a truly AllJoyn device – LIFX Color 1000 bulb. And the first task that I had to solve is how to onboard the bulb to my local network. More information about the task you can find watching this video:

https://channel9.msdn.com/Series/Internet-of-Things-micro-boards-for-beginners/How-to-onboard-AllJoyn-devices-in-C-LIFX-Color-1000-example/player

Written by Sergiy Baydachnyy

05/20/2016 at 9:29 PM

Posted in IoT, Universal Applications, Windows 10

Tagged with