WPF(C#)でSwitchBot(BLEデバイス)を操作する


概要

UWP(C#)でSwitchBot(BLEデバイス)を操作する - Qiita
のWPF編
微妙に書き方が違ったり、できることが違ったりする。

環境

  • Windows10
  • Visual Studio 2017(C#)
  • SwitchBot
  • UwpDesktop(nuget)

WPFといいつつ、BLE周りのAPIはUWPにしかないので、UwpDesktopをnugetで入れる。

ソースコード

経緯はUWP編の方で書いたので、最終的なコードだけ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using System.Threading;
using System.Runtime.InteropServices.WindowsRuntime;

namespace SwitchBotTry
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private BluetoothLEAdvertisementWatcher advWatcher;

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void BtnScan_Click(object sender, RoutedEventArgs e)
        {
            await StartScan();
        }

        private async Task StartScan()
        {
            this.advWatcher = new BluetoothLEAdvertisementWatcher();
            this.advWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(1000);
            this.advWatcher.ScanningMode = BluetoothLEScanningMode.Active;
            this.advWatcher.Received += this.Watcher_Received;

            // スキャン開始
            this.advWatcher.Start();
        }

        private Guid switchUUID = new Guid("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
        private Guid commandUUID = new Guid("cba20002-224d-11e6-9fb8-0002a5d5c51b");
        private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            var mac = args.BluetoothAddress.ToString("x");
            var bleServiceUUID = args.Advertisement.ServiceUuids.FirstOrDefault();
            var targetMac = "****"; // 事前に調べたMAC
            if (mac == targetMac)
            {
                //アドバタイスパケットの中身を羅列する
                foreach (var adv in args.Advertisement.DataSections)
                {
                    addLog(" " + adv.DataType.ToString("x") + ":" + BitConverter.ToString(adv.Data.ToArray()));
                }
            }
            //目当てのUUIDで絞り込み
            if (bleServiceUUID == switchUUID)
            {
                advWatcher.Stop(); //デバイスを見つけたので止める
                try
                {
                    //接続
                    BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
                    //サービス・キャラクタリスティックを列挙
                    addLog("get service");
                    foreach (var service in device.GattServices)
                    {
                        addLog(service.Uuid.ToString());
                        addLog("   get characteristics");
                        foreach (var ch in service.GetAllCharacteristics())
                        {
                            addLog("     "  + ch.Uuid.ToString() + "  " + ch.CharacteristicProperties.ToString());
                            if (ch.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read)){
                                var val = await ch.ReadValueAsync();
                                var data = val.Value.ToArray();
                                string text = System.Text.Encoding.UTF8.GetString(data);
                                addLog("     value " + BitConverter.ToString(data) + " >> " + text);

                            }
                        }
                    }
                    //サービスUUIDを使って目的のサービスを取得
                    //asyncバージョンは使えない
                    var gattService = device.GetGattService(switchUUID);
                    //キャラクタリスティックUUIDを使って目的のキャラクタリスティックを取得
                    //asyncバージョンは使えない
                    var characteristics = gattService.GetCharacteristics(commandUUID);
                    //戻ってくるのが配列なので空じゃないか確認
                    if (characteristics.Count > 0)
                    {
                        var command = characteristics.First();
                        if (command.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write))
                        {
                            byte[] comPress = { 0x57, 0x01, 0x00};
                            var res = await command.WriteValueAsync(comPress.AsBuffer(), GattWriteOption.WriteWithResponse);
                            addLog(res.ToString());
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception...{ex.Message})");
                }
            }
        }

        private void addLog(String log)
        {
            Dispatcher.Invoke(new Action<string>(loglog =>
            {
                this.txtLog.Text += loglog + "\r\n";
            }), log);

            Console.WriteLine(log);
        }

        private void BtnStop_Click(object sender, RoutedEventArgs e)
        {
            this.advWatcher.Stop();
        }
    }
}

UWPとの違い(制限事項)

  • サービス/キャラクタリスティックの取得メソッドが違う

中身的にはUWPのAPIを呼んでるだけのはずなのだが、asyncの方はWPFからは呼べない。
それに伴って、エラーチェック周りの処理も変わる。

  • 事前にペアリングしたSwitchBotしか操作できない

ペアリングしていないSwitchBotも見つかるが、GattServicesでサービスを取得できない。
ソースコード中でペアリングすることも考えたが、煩雑になるのでやめて、UWPに移行した。
UWPでは事前のペアリングに関わらずコマンドを送信することができる。
SwitchBotのiOSアプリではペアリング有無に関わらず、電波の届く範囲のSwitchBotが検出/操作できるのでUWPではそれに近い実装ができる。