kenichiro246’s blog

主にC#での仕事をしてます。今はAWSに関わり始めました。

[Xamarin.Forms]Plugin.BLEを使用してペリフェラルの情報を取得する方法

この記事は[初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 - Qiitaの22日目の記事です。
昨日は@gnk263さんのXamarinで作成したiOSアプリをAppStoreで配布する手順 - Qiita でした。

qiita.com

アドベントカレンダー初参戦であり、このネタで不安ですが暖かく見守っていただければ幸いです。 ホント皆さんの記事が凄いのなんのって。

さて、今回のエントリーでは、Xamarin.FormsとPlugin.BLEを使用し、簡単なアプリを作ってみます。
簡単なアプリと言っても、タイトルのとおりアドバタイズ中のペリフェラルへ接続し、情報を取得するだけなので、 アプリというほどでも無いですが、こんな感じで取得できるのかと思ってもらえれば宜しいかと。

なぜPlugin.BLEかというと、そもそもはBluetooth Low Energy(以下、BLE)に興味があって勉強していたところ、「Essential Xamarin」でPlugin.BLEの情報が載っており、利用してみたら便利であったことから、今回のエントリーのネタとしました。

書籍はこちら。様々な情報が盛り沢山なので大変重宝しています。 prtimes.jp

BLEを学び始めた頃は、事前知識が無くてさっぱりわからなかったのですが、やっと理解出来てきたため、これからBLEを学びたい人の助けになればと思います。
ソースコードはMvvmは使わず、比較的わかりやすいように書いたつもりですが、出来が悪かったらごめんなさい。

ゴール

以下のとおり、Searchボタンをタップすると、アドバタイズ中の情報を一覧表示し、キャラクタリスティックの情報を取得するものを作ります。
f:id:kenichiro246:20171221203644g:plain

BLEの概要

もしBLEについてまったく知らない場合は以下の記事を見ていただくと参考になります。
私としては、この記事無かったら結構わからないことが多くてダメだったかも。
blog.fenrir-inc.com

また、以下の書籍も大変勉強になりました。
BLEについて、より詳細な仕組みを知りたい場合はかなり重宝する書籍かと思っています。  iOS×BLE Core Bluetoothプログラミング 単行本 – 著者:堤 修一, 松村 礼央

上記の記事のとおりBLEは様々なことを学ぶ必要がありますが、今回のこのエントリーでは以下の用語をざっくりでもいいので理解いただければ、その後の話がスムーズに理解できるように書いています。たぶん・・・。

用語

  1. ペリフェラル(Peripheral)
    タイトルにも書かれていますがペリフェラルとは、コンピュータと組み合わせて利用される各種機器のことを指しています。ここではBluetoothのマウスやキーボード、あとはApple Watchなどをイメージしてもらえれば良いかと思います。

  2. アドバタイズ(Advertise)
    アドバタイズとは接続可能なペリフェラルが機器情報を周辺に発信している状態のことを指しています。Bluetoothのマウスなどを接続する際に、Connectボタンを押した後、LEDランプが点滅している状態だと思ってください。

  3. セントラル(Central)
    周辺でアドバタイズしているペリフェラルをスキャンし、接続後にペリフェラルが提供している情報を取得したり、ペリフェラルを操作したりする機器のことを指しています。ここではiPhoneAndroidなどのスマートデバイスをイメージしてもらえれば良いかと思います。

  4. サービス(Service)
    特性およびデバイスの一部の動作をカプセル化した分類のようなもの。サービス毎に1つ以上のキャラクタリスティックを持っています。前述の記事にも記載されていますが、オブジェクト指向プログラミングで言えば、サービスがクラス、また次の用語のキャラクタリスティックがプロパティと思ってもらえるとわかりやすいかと。

  5. キャラクタリスティック(Characteristic)
    ペリフェラルが持っている情報のことを指しています。ペリフェラルの名称やバッテリーの残量、シリアルNoなど様々な情報をキャラクタリスティックを介して取得できるものとなります。

当アプリについて

当アプリは以下のように作っています。

  • PeripheralsPage
    アドバタイズ中のペリフェラルの情報を一覧表示します。表示されたペリフェラルをタップすると、実際に接続できるか確認後、問題なく接続できたらServicesPageへ遷移します。
  • ServicesPage
    接続したペリフェラルのサービス情報を一覧表示します。表示されたサービスをタップすると、CharacteristicsPageへ遷移します。
  • CharacteristicsPage
    選択したサービスのキャラクタリスティックの情報を一覧表示します。基本的にキャラクタリスティックのプロパティをそのまま表示させるだけとなっています。(なので、ものによっては良くわからない情報が表示されています。) 

環境

当エントリーは以下の環境で作っています。
* Visual Studio for Mac Community :7.2.2
* Xamarin.Forms :2.5.0.121934
* Plugin.BLE :1.3


それでは実際に作ってみましょう!




GitHub

本文にソースコードは載せていますが、詳細はGitHubをご確認ください。
github.com

ソリューション構成

今回はPCLに以下のようにPageを配置した構成としています。
f:id:kenichiro246:20171220015939p:plain

Nuget

Nugetから以下のとおり「Plugin.BLE」で検索し、各プロジェクトへ追加します。
f:id:kenichiro246:20171221060609p:plain

AndroidManifest.xmlの変更(Androidのみ)

Androidで実行する際には、マニフェストファイルの変更が必要です。
.DroidプロジェクトのProperties配下のAndroidManifest.xmlを開き、以下の4つにチェックをつけます。

  • AccessCoarseLocation
  • AccessFineLocation
  • Bluetooth
  • BluetoothAdmin

ソースコード

各PageのC#ソースコードは以下のとおりです。
XamlはListViewでプロパティの値を表示されているだけのため割愛します。

PeripheralsPage

using System.Collections.ObjectModel;
using System.Diagnostics;
using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;
using Xamarin.Forms;

namespace PluginBleTest
{
    public partial class PeripheralsPage : ContentPage
    {
        IBluetoothLE _BluetoothLe;
        IAdapter _Adapter;
        private ObservableCollection<IDevice> _deviceList = new ObservableCollection<IDevice>();

        public PeripheralsPage()
        {
            InitializeComponent();

            _BluetoothLe = Plugin.BLE.CrossBluetoothLE.Current;
            _Adapter = _BluetoothLe.Adapter;
            _Adapter.ScanTimeout = 5000;
            _Adapter.DeviceDiscovered += (s, e) =>
            {
                _deviceList.Add(e.Device);
            };
            ListView1.ItemsSource = _deviceList;
            AddSearchButton();
        }

        private void AddSearchButton()
        {
            var Toolbarbutton = new ToolbarItem { Text = "Search" };
            Toolbarbutton.Clicked += async (s, e) =>
            {
                if (_BluetoothLe.State == BluetoothState.Off)
                {
                    _deviceList.Clear();
                    await DisplayAlert("Bluetooth LE", "BLE states is off.", "OK");
                    return;
                }
                if (_Adapter.IsScanning)
                {
                    return;
                }
                _deviceList.Clear();
                await _Adapter.StartScanningForDevicesAsync();
            };
            ToolbarItems.Add(Toolbarbutton);
        }

        async void ListView1_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            var device = e.SelectedItem as IDevice;
            if (device == null)
            {
                return;
            }

            try
            {
                await _Adapter.ConnectToDeviceAsync(device);
                if (device.State == DeviceState.Disconnected)
                {
                    await DisplayAlert("PluginBleTest", "Do Not Connect", "OK");
                    return;
                }
                if (device.State == DeviceState.Connected || device.State == DeviceState.Limited)
                {
                    await Navigation.PushAsync(new ServicesPage(device));
                }
            }
            catch (DeviceConnectionException ex)
            {
                Debug.WriteLine(ex.StackTrace);
                await DisplayAlert("PluginBleTest", "Do Not Connect", "OK");
            }
            finally
            {
                ListView1.SelectedItem = null;
            }
        }
    }
}

解説

Searchボタンをタップすることで、セントラルの周辺でアドバタイズ中のペリフェラルの情報を取得し、ListViewへ設定しています。
XamlではペリフェラルのName、Id、Rssi、Stateのプロパティの値を表示させています。
また、ペリフェラルBluetoothがONになっていない場合は、検索しないくらいの仕組みは組み込んでいます。

  • StartScanningForDevicesAsync
    アドバタイズ中のペリフェラルの情報を取得するメソッドです。
    検知したペリフェラルは非同期でDeviceDiscoveredイベントが呼び出され、ペリフェラルの情報を参照することが出来ます。

  • DeviceDiscovered
    ペリフェラルを検知した際に呼び出されるイベントです。
    パラメータのDeviceがペリフェラルそのものになり、このDeviceからIDや名称などを取得することが出来ます。

  • ConnectToDeviceAsync
    選択したDeviceを実際に接続するメソッドです。
    正常に接続できれば、そのDeviceのサービスの情報を見ることが出来ます。
    もし接続できない場合は、DeviceConnectionExceptionが発生し、エラーとなります。

ServicesPage

using System.Collections.ObjectModel;
using Plugin.BLE.Abstractions.Contracts;
using Xamarin.Forms;

namespace PluginBleTest
{
    public partial class ServicesPage : ContentPage
    {
        private ObservableCollection<IService> _ServiceList = new ObservableCollection<IService>();
        private IDevice _device = null;

        public ServicesPage(IDevice device)
        {
            InitializeComponent();

            _device = device;
            ListView1.ItemsSource = _ServiceList;
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();

            _ServiceList.Clear();
            var services = await _device.GetServicesAsync();
            foreach (var service in services)
            {
                _ServiceList.Add(service);
            }
        }

        async void ListView1_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            var service = e.SelectedItem as IService;
            if (service == null)
            {
                return;
            }
            await Navigation.PushAsync(new CharacteristicsPage(service));
            ListView1.SelectedItem = null;
        }
    }
}

解説

全体的に特に難しいことはしていません。Deviceからペリフェラルの情報を取得し、ListViewに設定しているだけの内容としています。
XamlではサービスのName、Id、Primaryのプロパティの値を表示させています。

  • GetServicesAsync
    ペリフェラルのサービスの一覧を取得するメソッドです。
    この各サービスにはIDや名称などのプロパティが保持されています。

CharacteristicsPage

using System.Collections.ObjectModel;
using Plugin.BLE.Abstractions.Contracts;
using Xamarin.Forms;

namespace PluginBleTest
{
    public partial class CharacteristicsPage : ContentPage
    {
        private ObservableCollection<ICharacteristic> _CharacteristicList = new ObservableCollection<ICharacteristic>();
        private IService _service = null;

        public CharacteristicsPage(IService service)
        {
            InitializeComponent();

            _service = service;
            ListView1.ItemsSource = _CharacteristicList;
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();

            var characteristics = await _service.GetCharacteristicsAsync();
            foreach (var characteristic in characteristics)
            {
                if(characteristic.CanRead)
                {
                    await characteristic.ReadAsync();
                }
                _CharacteristicList.Add(characteristic);
            }
        }
    }
}

解説

ServicesPageと同じく、サービスの各キャラクタリスティックの情報をListViewへ設定しているだけとなります。
XamlではキャラクタリスティックのName、Id、Uuid、Properties、Valueなどいくつかのプロパティの値を表示させています。
Value値を取得するために一度ReadAsyncする必要があります。

  • GetCharacteristicsAsync
    サービスのキャラクタリスティックの一覧を取得するメソッドです。
    これで取得したキャラクタリスティックからペリフェラルの様々な情報が取得できます。

実行

ここまでの内容で実行してみましょう。
冒頭のとおり動くかと思います。
因みにBLEは実機でないと動かないので実機へデプロイしてお使いください。

また、ここまで書いていて何ですが、接続したペリフェラルを切断(DisconnectDeviceAsync)するの忘れてた。
このままだと接続したまま残ってしまうので良くないですね。
もし実機で試す場合は、自分でDisconnectDeviceを入れるようにしてみてください。ごめんなさい。
※後日組み込むようにしておきます。

まとめ

今回は「Essential Xamarin」の内容を元に作ってみましたが、Plugin.BLE自体、そこまで使い方が難しいものではない感じでしたので、興味があれば使ってみてください。
また、今回のエントリーではサービスなどを全て表示させるようなものにしましたが、次回あたりはUUIDを指定し、予めわかっているキャラクタリスティックの情報を取得するようなものも書いてみようかと思います。

明日は@shiita0903さんです。
宜しくお願いします。