なりたさまかく語りき

結局好きなこと書くのがいちばんよい。

【Xamarin.Forms】MVVMっぽくBluetoothを実装する(その2:ペアリング済みデバイスと通信する)

前回

naritasamadayo.hatenablog.com の続きです。といっても前回は端末に保存された情報を取得してるだけで、実際に通信はしてなかったのでこっからが本番。

TL;DR

  • Bluetoothバイスと通信した(Androidのみ)

  • スレッドの理解必須

  • 諸事情によりわかりやすい結果は示せない。

ソースコードはギッハブに置いてますが、前回からの拡張なので読みにくいと思います。 参考にしたい人がいたらごめんなさい。 github.com

View

いちいちLabelを作ってViewModelにPropertyChanged書いて(Fodyで省略出来るけど)…ってやるのも怠いのでコンソール出力で見ます。 なので今回はViewは無し。

ViewModel

IBluetoothManager btMan = DependencyService.Get<IBluetoothManager>();
btMan.DataReceived += (sender, e) =>
            {
                Console.WriteLine("#debug {0}", e.Data);
            };

            btMan.Start();

ViewModelではDependencyServiceしてAndroid固有機能の実装にアクセスし、データを受け取るイベントを監視します。 Xamarin.Forms初心者(私も含めて)が見たら何言ってんだこいつ状態だと思いますが、加速度センサー(内蔵センサー)とか、内部ストレージにデータ保存とかその辺の実装をするとDependencyService完全に理解した状態にわかると思います(どっちもPCLでできるNugetあるけどね…)

Model

public interface IBluetoothManager
    {
        event EventHandler<DataEventArgs> DataReceived;
        List<BTDevice> GetBondedDevices();
        void Start();

    }

PCLの方はAndroidの機能にアクセスするインターフェースだけです。 前回から追加でデータを受け取るイベントハンドラーとStart()なるメソッドが増えました。

Android

public event EventHandler<DataEventArgs> DataReceived;
private const string Uuid = "00001101-0000-1000-8000-00805f9b34fb"; //ここの値は機器に合わせて変えてください

private UUID getUUIDFromString()
{
    return UUID.FromString(Uuid);
}

public void Start()
{
    var btAdapter = BluetoothAdapter.DefaultAdapter;
    var devices = btAdapter.BondedDevices;
    if (devices != null && devices.Count > 0)
    {
         foreach (var device in devices)
         {
             try
             {
                 var socket = device.CreateRfcommSocketToServiceRecord(getUUIDFromString());
                 Task.Run(() =>
                 {
                     try
                     {
                          socket.Connect();
                          System.Console.WriteLine("#debug connect to {0}", device.Name);
                          var br = new BufferedReader(new InputStreamReader(socket.InputStream));
                          string data;
                          while ((data = br.ReadLine()) != null)
                          {
                               DataReceived?.Invoke(this, new DataEventArgs(data));
                          }
                     }
                     catch (IOException e)
                     {}
                     finally
                     {
                         socket.Close();
                         System.Console.WriteLine("#debug close socket  {0}", device.Name);
                      }
                  });
             }
             catch (IOException e)
             {}
        }
   }
}

イベントハンドラーを定義する部分は特に何も説明することないです。

private UUID getUUIDFromString()
{
    return UUID.FromString(Uuid);
}

getUUIDFromString()は名前の通りStringからUUID型に変換してます。 後で使うCreateRfcommSocketToServiceRecord()はUUID型しか取れないので別途メソッド作ってます。

var btAdapter = BluetoothAdapter.DefaultAdapter;
var devices = btAdapter.BondedDevices;

アダプターを取得して、BondedDevicesでペアリング済みデバイスの一覧を取得。

 var socket = device.CreateRfcommSocketToServiceRecord(getUUIDFromString());
 Task.Run(() =>{タスクの内容}

CreateRfcommSocketToServiceRecord()でソケットを取得。各所でTCP/IPみたいなもん、という記述を見るけどTCP/IPよくわかってないです。情報系出身だけど。 ソケット通信は、別スレッドで動かす必要があるのでTask.Run()で別スレッド開始します。

socket.Connect();
System.Console.WriteLine("#debug connect to {0}", device.Name);
var br = new BufferedReader(new InputStreamReader(socket.InputStream));
string data;
 while ((data = br.ReadLine()) != null)
 {
          DataReceived?.Invoke(this, new DataEventArgs(data));
}

Connect()でソケット接続を開始。 InputStreamにいろいろ送られてきます。それをReaderで読む。んでそれをStringで読める形にするためにBufferedReaderにします。というのがvar br = new BufferedReader(new InputStreamReader(socket.InputStream));です。 あとは送られてきたデータをイベントハンドラーに渡す。以上。

コンソール出力は気にするな。

以上だ!

はい。出力結果をみせたいところですが、最近のBluetoothバイスはつながるけど何のボタンを押しても値を返してこないのが多い… 会社にある変なセンサーは固定フォーマットの文字列返してくるんだが…

とりあえず、今回のキモ

次はなんとかして値を読み取れるセンサーを入手して、内部ストレージに保存するところを実装したいと思います。 では。