かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPFからUWPのAPIを使ってBLEの操作をしよう

さて、UWPのAPIを使えるということで先日アドバタイズパケットの受信をやりました。

blog.okazuki.jp

今度はBLEの通信をしてみたいと思います。

参考ページは以下。

blog.fenrir-inc.com

そして、今回使うセンサーはSensorTagのv1です。

www.tij.co.jp

Windows Runtime用ソースコードがあるので、参考にさせてもらいます。

sensortag.codeplex.com

作ってみよう

WPFのプロジェクトを作ってUwpDesktopパッケージをNuGetからインストールします。SensorTagで使うUUIDを定義します。

namespace BleSample
{
    public class SensorTagUuid
    {
        // https://sensortag.codeplex.com/SourceControl/latest#SensorTagLibrary/SensorTagLibrary/Source/SensorTagUuid.cs
        public const string UuidIrtService = "f000aa00-0451-4000-b000-000000000000";
        public const string UuidIrtData = "f000aa01-0451-4000-b000-000000000000";
        public const string UuidIrtConf = "f000aa02-0451-4000-b000-000000000000";
    }
}

画面に初期化用ボタンと値を読み込み始めるボタンとデータ表示用TextBlockを置きます。

<Window x:Class="BleSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BleSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button Content="Connect"
                Click="ButtonConnect_Click" />
        <Button Content="Read value"
                Click="ButtonReadValue_Click" />
        <TextBlock x:Name="TextBlockTemp" />
    </StackPanel>
</Window>

そして、コードビハインドにUWPのAPIを使ってさくっとコードを書きましょう。

using System;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Windows;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Storage.Streams;

namespace BleSample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private GattDeviceService GattDeviceService { get; set; }

        private GattCharacteristic GattCharacteristic { get; set; }

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void ButtonConnect_Click(object sender, RoutedEventArgs e)
        {
            // SensorTagを取得
            var selector = GattDeviceService.GetDeviceSelectorFromUuid(new Guid(SensorTagUuid.UuidIrtService));
            var devices = await DeviceInformation.FindAllAsync(selector);
            var deviceInformation = devices.FirstOrDefault();
            if (deviceInformation == null)
            {
                MessageBox.Show("not found");
                return;
            }

            this.GattDeviceService = await GattDeviceService.FromIdAsync(deviceInformation.Id);
            MessageBox.Show($"found {deviceInformation.Id}");

            // センサーの有効化?
            var configCharacteristic = this.GattDeviceService.GetCharacteristics(new Guid(SensorTagUuid.UuidIrtConf)).First();
            var status = await configCharacteristic.WriteValueAsync(new byte[] { 1 }.AsBuffer());
            if (status == GattCommunicationStatus.Unreachable)
            {
                MessageBox.Show("Initialize failed");
                return;
            }
        }

        private async void ButtonReadValue_Click(object sender, RoutedEventArgs e)
        {
            if (this.GattDeviceService == null)
            {
                MessageBox.Show("Please click connect button");
                return;
            }

            // 値を読み始める
            if (this.GattCharacteristic == null)
            {
                this.GattCharacteristic = this.GattDeviceService.GetCharacteristics(new Guid(SensorTagUuid.UuidIrtData)).First();
                this.GattCharacteristic.ValueChanged += this.GattCharacteristic_ValueChanged;

                var status = await this.GattCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
                if (status == GattCommunicationStatus.Unreachable)
                {
                    MessageBox.Show("Failed");
                }
            }
        }

        private async void GattCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
        {
            // 値を読んで表示する
            await this.Dispatcher.InvokeAsync(() =>
            {
                var data = new byte[args.CharacteristicValue.Length];
                DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);
                var temp = BitConverter.ToUInt16(data, 2) / 128.0;
                this.TextBlockTemp.Text = $"{temp}℃";
            });
        }
    }
}

基本的にGattなにがし系のクラスを使う感じですね。あとはIBufferとbyte[]の相互変換あたりがポイントでしょうか。あとはSensorTagのコードを参考に真似させてもらいました。

実行してボタンをぽちぽちっと押すと温度が表示されます。

f:id:okazuki:20160722225923p:plain

ソースコード

ソースコード全体はGitHubに上げておきます。

github.com