dnackのブログ

コード書く仕事の間にコード書いてる知命目前のおっさんです。

Prism/XamarinでAndroidアプリの作成(5):BLE Pluginを使ってみる。

前回まではPrismをつかったMVVM的なコードの書き方を試してきたんだけど飽きてきたので、今回は趣向を変えて、BLEを動かしてみたい。 Prismのアーキテクチャ回りも、処理の部分をModelで書いたりとかまだやることはたくさんあるんだけどそっちはとりあえずお休み。 今回のBLEの処理も、本当はModelに独立したクラスを作るべきなのだろうけど、まずはごちゃごちゃ考えずに動かしてみるということでその辺りは気にせずViewModelのところにべたべたと書いてみる。(のでその辺りのお作法上いまいちなコードが出てくるけれど気にしない。)

Plugin.BLEのインストールと準備

NugetからPlugin.BLEを探して入れるだけ。 GithubにあるREADME.mdがわりと親切なのが助かる。

github.com

MvvmCross用にはPluginが用意されているが、残念ながらPrism用にはない。 日本語のTipsはPrismが圧倒的に多いんだけど、海外ではMvvmCross優勢とも聞くからなぁ。 そういえばVisualStudio 2019で新しく入ったmvvm toolkitが入ったという話は聞いたがそっちの話はあまり聞かないな。

さて、とりあえずUsageに従ってコード書いていく。 まずは、以下の4つのパーミションを加えろとのこと。 ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION BLUETOOTH BLUETOOTH_ADMIN プロパティのAndroid マニフェストのタブからこの4つのパーミションを追加。 f:id:dnack:20210906224431p:plain 多分直接AndroidManifest.xmlに直接足しても大丈夫なはず。

今回は手っ取り早く動かしてみるということで、 処理は前回のアプリのViewモデルのボタンのハンドラにコードべた書きし、 結果の表示も画面に出さずに、デバッグ出力というやっつけ仕事。

using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using Reactive.Bindings;
using SamplePrismApp.Views;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace SamplePrismApp.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        public ReactiveProperty<string> TextMessage { get; set; }

        public DelegateCommand ButtonACommand { get; set; }
        public DelegateCommand ButtonNextPageCommand { get; set; }

        private INavigationService _navigateionService;

        public MainPageViewModel(INavigationService navigationService)
            : base(navigationService)
        {
            Title = "Main Page";
            TextMessage = new ReactiveProperty<string>( "0000" );
            ButtonACommand = new DelegateCommand( ButtonAClickedAsync );
            ButtonNextPageCommand = new DelegateCommand( NavigateSecondPageAsync );
            _navigateionService = navigationService;
        }

        private async void ButtonAClickedAsync()
        {
            TextMessage.Value = "XXXX";
            var ble = CrossBluetoothLE.Current;
            var adapter = CrossBluetoothLE.Current.Adapter;
            var deviceList = new List<IDevice>();
            
            //State
            Debug.WriteLine("★★★★BLE Stete:"+ble.State);

            //scan device 
            adapter.DeviceDiscovered += (s, a) =>
            {
                deviceList.Add(a.Device);
                Debug.WriteLine("★★★★Found:"+a.Device.Id.ToString());
            };

            adapter.ScanTimeout = 10000;
            
            await adapter.StartScanningForDevicesAsync();

            foreach(var device in deviceList)
            {
                Debug.WriteLine("★★★★:ID" + device.Id.ToString() +":Name:" + device.Name +":RSSI:" + device.Rssi.ToString() +":State:" + device.State.ToString());
            }
        }

        private async void NavigateSecondPageAsync()
        {
            _ = await _navigateionService.NavigateAsync(nameof(SecondPage));
        }
    }
}

前回のコードと比べてもらえば一目瞭然だと思うが、 ButtonAClickedにデバイススキャンして結果をデバッグ出力するコードを足している。 メソッドが非同期になっているのは、前回の画面遷移の時とおなじで、 前回は画面遷移のメソッドが非同期だったが、 今回はデバイススキャンのメソッドが非同期なのでこれを待つ必要がある。 そして、待つ必要があるからasyncにする必要がある、という理由でこうなっている。 using句は、 CrossBluetoothLEのためにPlugin.BLE、IDeviceのためにPlugin.BLE.Abstractions.Contracts、 Debug.WriteLineのために System.Diagnosticsのをそれぞれ追加。

実行すると、 f:id:dnack:20210907132612p:plain

おや? 特にエラーは出てないけど、DeviceDiscovered が一度も呼ばれていないようなので、 BLEのデバイスはここらへんにはないらしいな。 いやいやいや、、そんなことはありえないので何かがおかしい。 (この間約30分) あ。もしかしてあれなのでは?

f:id:dnack:20210907133127p:plain

ほらねーー。 プロジェクトで位置情報の権限は付けたけど(ACCESS_COARSE_LOCATION,ACCESS_FINE_LOCATION)、 端末のほうでアプリに位置情報の権限を渡す設定も入れないといけないんだよな。 それをしてないので、BLEにアクセスできなかったわけ。 このケース何のエラーコードも出ないので本当にタチが悪いんだよな。 アプリのBLUETOOTHの権限がなかったりすると普通にエラーになるんだけどね。

f:id:dnack:20210907133317p:plain

ということで、こうやってアプリに位置情報の権限を与えればOK。なはず。 気を取り直して実行すると。

f:id:dnack:20210907133404p:plain

おー、なんかいっぱい見つかった。

しかしこれ、実行時に権限なかったら取りに行くコード入れたほうがいいね。 それは宿題ということで今回は気持ちよく動いたところでここまで。