3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-26

概要

SwitchBotで何かできない?と上司に言われ「C#から操作できればHoloLensで遊べるんじゃね?(ピコーン」ってなったはいいけど、世の中に情報がなくて辛かったのでまとめ。
最初はWPFでやってたのですが、限界を感じてUWPでやりました。
WPF編はこちら → WPF(C#)でSwitchBot(BLEデバイス)を操作する - Qiita

参考

環境

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

まずはラズパイで

公式のコードがラズパイ向けなので、まずは動かしてみる。

が、動かない。
iOSアプリでデバイスのMACは取れたのでBLEスキャンしてるあたりを適当にデバッグ。
結果的に

service_uuid = '1bc5d5a50200b89fe6114d22000da2cb'

これを

service_uuid = 'cba20d00-224d-11e6-9fb8-0002a5d5c51b'

に直したら動いた。

UWPでスキャン

BLEで調べるとアドバタイズだのサービスだのキャラクタリスティックだのUUIDだの出てくる。
よくわからない。
とりあえずスキャンしてみる。addLogはデバッグ用のTextBoxに追記する関数。

private async Task StartScan()
        {
            //ウォッチャー作成
            this.advWatcher = new BluetoothLEAdvertisementWatcher();
            this.advWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(1000);
            this.advWatcher.Received += this.Watcher_Received;
            // スキャン開始
            this.advWatcher.Start();
        }

        private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            var mac = args.BluetoothAddress.ToString("x");
            var bleServiceUUID = args.Advertisement.ServiceUuids.FirstOrDefault();
            addLog("MAC " + mac);
            addLog("uuid " + bleServiceUUID);
        }

嵐のように出てくる。
あとUUIDが取れない。なんでやねん。

なるほど。なるほど?
単なるスキャンでは基本的な情報しか取れず、詳細情報をデバイスにリクエストする必要があるらしい。(初期スキャンにUUIDが載ってくる場合もあるらしい)
ので追記。
MS公式曰く、消費電力が増えるらしいが、情報取れないんじゃ何もできないんだから仕方ない。

this.advWatcher.ScanningMode = BluetoothLEScanningMode.Active;

あとドバドバ出てくるのが心臓に悪いのでフィルタ。

private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            var targetMac = "*****"; // アプリで調べたMAC
            var mac = args.BluetoothAddress.ToString("x2");
            var bleServiceUUID = args.Advertisement.ServiceUuids.FirstOrDefault();
            var name = args.Advertisement.LocalName;
            //目当てのデバイスだけ
            if (mac == targetMac)
            {
                addLog("name " + name);
                addLog("uuid " + bleServiceUUID);
                //アドバタイズデータを羅列
                foreach (var adv in args.Advertisement.DataSections)
                {
                    addLog(" " + adv.DataType.ToString("x") + ":" + BitConverter.ToString(adv.Data.ToArray()));
                }
            }
        }
実行結果
name 
uuid 00000000-0000-0000-0000-000000000000
 01:06
 ff:59-00-FD-CF-4B-A5-38-C4
name 
uuid cba20d00-224d-11e6-9fb8-0002a5d5c51b
 07:1B-C5-D5-A5-02-00-B8-9F-E6-11-4D-22-00-0D-A2-CB
 16:00-0D-48-10-64

複数回受信して、内容が変わっているのが分かる。
Advertising(解説) - BLE Docs
の通り、UUIDがType=07に入っている。
でもってデコード前のバイト列をPythonのコードと比べてみると

service_uuid = '1bc5d5a50200b89fe6114d22000da2cb'

なので、Pythonのコードはデコード前のUUIDを期待してたっぽい。

サービスとキャラクタリスティック

サービスを取って、キャラクタリスティックを取って、というのがお作法っぽいのでとりあえず全部取って羅列してみる。

private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            var targetMac = "*****"; // アプリで調べたMAC
            var mac = args.BluetoothAddress.ToString("x");
            var bleServiceUUID = args.Advertisement.ServiceUuids.FirstOrDefault();
            var name = args.Advertisement.LocalName;
            //目当てのデバイスだけ
            if (mac == targetMac)
            {
                addLog("name " + name);
                addLog("uuid " + bleServiceUUID);
                //アドバタイズデータを羅列
                foreach (var adv in args.Advertisement.DataSections)
                {
                    addLog(" " + adv.DataType.ToString("x") + ":" + BitConverter.ToString(adv.Data.ToArray()));
                }
            }
            //目当てのデバイスのUUIDが取れたら
            if (mac == targetMac && bleServiceUUID != Guid.Empty)
            {
                advWatcher.Stop();
                try
                {
                    //接続
                    BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
                    addLog("get service");
                    var services = await device.GetGattServicesAsync();
                    foreach (var serv in services.Services)
                    {
                        addLog(serv.Uuid.ToString());
                        addLog("  get characteristics");
                        var charcteristic = await serv.GetCharacteristicsAsync();
                        foreach (var ch in charcteristic.Characteristics)
                        {
                            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);
                            }
                        }
                    }         
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception...{ex.Message})");
                }
            }
        }
実行結果
name 
uuid 00000000-0000-0000-0000-000000000000
 01:06
 ff:59-00-FD-CF-4B-A5-38-C4
name 
uuid 00000000-0000-0000-0000-000000000000
 01:06
 ff:59-00-FD-CF-4B-A5-38-C4
name 
uuid cba20d00-224d-11e6-9fb8-0002a5d5c51b
 07:1B-C5-D5-A5-02-00-B8-9F-E6-11-4D-22-00-0D-A2-CB
 16:00-0D-48-10-64
get service
00001800-0000-1000-8000-00805f9b34fb //汎用情報
  get characteristics
     00002a00-0000-1000-8000-00805f9b34fb  Read, Write
     value 57-6F-48-61-6E-64 >> WoHand //デバイス名
     00002a01-0000-1000-8000-00805f9b34fb  Read
     value 00-00 >>      
     00002a04-0000-1000-8000-00805f9b34fb  Read
     value 06-00-18-00-00-00-90-01 >> 
00001801-0000-1000-8000-00805f9b34fb
  get characteristics
0000fee7-0000-1000-8000-00805f9b34fb // Custom UUID of Tencent Holdings Limited
  get characteristics
     0000fec8-0000-1000-8000-00805f9b34fb  Indicate // Custom UUID of Apple, Inc.
     0000fec7-0000-1000-8000-00805f9b34fb  Write    // Custom UUID of Apple, Inc.
     0000fec9-0000-1000-8000-00805f9b34fb  Read     // Custom UUID of Apple, Inc.
     value FD-CF-4B-A5-38-C4 >>   K 8 
cba20d00-224d-11e6-9fb8-0002a5d5c51b // ***目的のUUID***
  get characteristics
     cba20003-224d-11e6-9fb8-0002a5d5c51b  Notify
     cba20002-224d-11e6-9fb8-0002a5d5c51b  WriteWithoutResponse, Write

サービスは一種の名前空間みたいなもので、その中に個別のコマンド=キャラクタリスティックが入っていると理解。

コマンドを送る

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 targetMac = "*******"; // アプリで調べたMAC
            var mac = args.BluetoothAddress.ToString("x");
            var bleServiceUUID = args.Advertisement.ServiceUuids.FirstOrDefault();
            var name = args.Advertisement.LocalName;
            //アドレスじゃなくてUUIDで選択
            if (bleServiceUUID == switchUUID)
            {
                advWatcher.Stop();
                try
                {
                    //接続
                    BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
                    //サービスUUIDを使って目的のサービスを取得
                    var gattService = await device.GetGattServicesForUuidAsync(switchUUID);
                    if (gattService.Status == GattCommunicationStatus.Success)
                    {
                        //コマンド送信用UUIDを使ってキャラクタリスティックを取得
                        var characteristics = await gattService.Services.FirstOrDefault().GetCharacteristicsForUuidAsync(commandUUID);
                        if (characteristics.Status == GattCommunicationStatus.Success)
                        {
                            var command = characteristics.Characteristics.FirstOrDefault();
                            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})");
                }
            }
        }

キュイッキュイッ
うごいたー!
複数のSwitchBotがあるとどれかがランダムで動きますw
実際には裏にマスタを持って指定するのがよいでしょう。

途中、コマンドを間違えたらSwitchBotがただの箱になって焦ったが、蓋を開けてリセットボタンを押すことで復旧。(電池のON/OFFでもいいらしい)

できてないこと

  • 長押し時間の切り替え
  • 「押す」「スイッチ」モードの切り替え
  • パスワード設定

アプリからは長押し時間を変更できるが、このコマンドが公開されていない?
「押す」と「スイッチ」の切り替え、パスワード設定も同様。
設定できそうなのは'cba20002-224d-11e6-9fb8-0002a5d5c51b'だけなので、ここに何らかのバイト列を送ればいいと思われる。

    if act == "Turn On":
        con.sendline('char-write-cmd ' + cmd_handle + ' 570101')
    elif act == "Turn Off":
        con.sendline('char-write-cmd ' + cmd_handle + ' 570102')
    elif act == "Press":
        con.sendline('char-write-cmd ' + cmd_handle + ' 570100')
    elif act == "Down":
        con.sendline('char-write-cmd ' + cmd_handle + ' 570103')
    elif act == "Up":
        con.sendline('char-write-cmd ' + cmd_handle + ' 570104')

公式のソースでは、5種類のコマンドがあるが、今の所呼べるのは「Press」のみ。。。

今後

C#から呼べるのは確定したので、HoloLensでバーチャルボタンを押したら現実のボタンが押される、みたいなことをしたい。
ホームオートメーション的なIoTデバイスが増えているものの、GoogleHome連携やAlexa連携しかインタフェースが無いものが大半なので、BLEで直接コントロールできるというのは嬉しいですね!
できれば他のコマンドも公開して欲しいところですが。。。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?