よいちろ日記

忘れないようにメモ。

UnityのiOSプラグイン作ってBluetooth(LightBlue Bean)連携。

やったこと

  • iOS用にUnityアプリをビルド。
  • iOSビルドしたUnityアプリのネイティブコード部分(Objective-C)で外部のBluetooth機器からの通信を受ける。
  • iOSビルドしたUnityアプリに対してBluetoothでインタラクション出来るようにする。後にこれを「UnityのiOSプラグインを作る」と言うことを知る。

BluetoothiPhoneObjective-Cで受ける)→ iOSビルドUnityアプリ
といった経路でBluetoothの情報でUnityアプリを操作したい。

今回は、Bluetooth機器の加速度を読み取って、その状態によってUnityアプリを操作する。
とりあえず加速度センサーの値を渡すところまで。

f:id:yoichiro0903:20160628005855p:plain

大まかな流れ

  • Unity上でiOSプラグインの枠を作る。iOSとの連携部分はC#で。
  • Xcode上でBluetooth連携部分を実装。疎通確認する。
  • Bluetooth機器(LightBlue Bean)の加速度受け渡し部分実装。
  • 動くか確認。

※ 有料プラグインは奥の手。Bluetoothとの通信部分がいじれなさそうなので及び腰。
Bluetooth LE for iOS Shatalmic, llc

※ 有料プラグイン海賊版。まあそうなるよね。
Unity3D bluetooth

詳細メモ

Unity上でiOSプラグインの枠を作る。iOSとの連携部分はC#で。

まず、全体の流れはここがわかりやすい。参考にさせていただいた。

Assetsに以下の用にフォルダを作る。
f:id:yoichiro0903:20160628010057p:plain
Plugins内には、Binding.csをつくり、こいつが実質Objective-Cのコードを呼び出してくれる。
Plugins>iOS内には、Objective-Cコード(をUnityプラグイン用に改変したもの)を置く。(後述)

Unityで新規プロジェクトを作成し、適当に空のGame Object(何でもいい)を作る。
こいつのComponentにC#スクリプトを追加して、どのタイミングでiOSコードを呼び出すか(連携タイミング)をコントロールする。
f:id:yoichiro0903:20160628010412p:plain

今回は、iOSConnectionTextという名前でUI>Textを作った。
ここにObjective-Cから受け取った情報を表示する。

f:id:yoichiro0903:20160628010430p:plain
(x,y,z)は(0,0,0)にして、色も見やすい感じに。実行した時の画面。

で、このiOSConnectionTextにC#スクリプトを追加。iOSConnectionText.csはAssets下においた。
ここでハマったのが、Script作った時のファイル名がスクリプトのClassにも適用されているため、あとからファイルをリネームした時にエラーが起こるということ。ファイル名をUnityのProjectウィンドウから変更した時は、スクリプト内のClassも書き換えること。
オブジェクトにスクリプトをアタッチすると "Can't add script"エラーが発生しスクリプトをアタッチできない

using UnityEngine.UI; // Added

これを追加しないと、Component情報が取得できない。

this.GetComponent<Text> ().text

部分が通らないため。

void Start は開始時に一度だけ、void Updateはフレームごとに、void OnGUIは描画ごとに呼び出される。(詳しくはUnityレファレンス参照。)

Binding.csでBindingクラスを定義しているので、

Binding.BluetoothConnectionUnity ()

という形で呼び出せる。
recieveAccelは、Objective-Cからメッセージを受け取るための関数。(後述)


Binding.csはこんな感じ。
UnityEditorから実行した場合、呼び出すべきObjective-Cがないため、エラー処理をしている。返り値を受けるため、Stringを指定。
この例だと、BluetoothConnectionUnityというメソッドをObjective-C上で定義することとなる。

この状態で、実行すると以下のようになる。
f:id:yoichiro0903:20160628011718p:plain
これで一旦Unity(C#)をいじる部分はOK。

Xcode上でBluetooth連携部分を実装。疎通確認する。

ベースは「CoreBluetoothをバックグラウンドで動かすiOSアプリ作った。」を参照。

ここでは、Unityからの呼び出しメソッド名であるBluetoothConnectionUnityの実装を記載。

必要なのはBluetoothConnection.h,mのみ。
呼び出し先のBluetoothConnectionUnityをBluetoothConnection.mに記載。

ここでのハマりポイントは、メソッドの定義の仕方。

-(char*) BluetoothConnectionUnity_ {

はだめで、

char *BluetoothConnectionUnity_ () {

はOK。

こうなると、クラスメソッドとして実装できないのか、self.propertyみたいな記法が全く使えない。これがなにが困るかというと、このBluetoothConnectionクラスオブジェクトをインスタンス化するときに、dispatch_once使っている関係で、2回以上、BluetoothConnectionUnityを呼び出すと、エラーになってしまうという点。今回は、加速度を随時取得したいため2回目以降走らなくなるのはかなり致命的。

最初は void OnGUI内で呼び出してたから気づかなかったけど、void Updateで変えられるように(実際にやりたいことしようとしたら)気付いた。void Updateの中で実行しているのに、一回しか呼び出されないということに。(試しに単純に文字を返すメソッド書いて、dispatch_onceが原因だと気付いた。)

これについて対応策を調べると、UnitySendMessageなるものを発見。これは結構感動した。(普通にマニュアルに書いてあったけど。)

didUpdateValueForCharacteristic内に以下のように実装。
XcodeもこのUnityのメソッドを認識してくれる。(iOS用ビルドのプログラムからいじってるからかもだけど。)

今回、Bluetooth機器のCharacteristicsが変更あり次第、都度通知を受け取るというふうにしたいので、シリアル通信かな?って思ったけど、 Core BluetoothでBLE通信(その2)ペリフェラル | 株式会社インデペンデンスシステムズ横浜 ここを参考にさせてもらって、あれ?これってシリアル通信なの?Characteristicsの値でやってない?いけるのかな?と調べてみると、Appleマニュアルに「特性の値が変化したときに通知するよう申し込む」というドンピシャなページが。参照したらできた。
setNotifyValueって部分と、didUpdateNotificationStateForCharacteristicメソッド定義するだけでできた。

で、これらのコード(BluetoothConnection.h,m)をFinderからドラッグアンドドロップでUnityのPlugins>iOSへコピー。
f:id:yoichiro0903:20160628012234p:plain

UnityのBuild&SettingsからiOS用のビルドを作る。
f:id:yoichiro0903:20160628012245p:plain
結構時間かかる。

Xcodeは自動で開くので、実機でビルド。(実機じゃないと動いてくれない。)
これが5分位かかる。気長に待つ。

で、実行。「Build Failed」うう。
f:id:yoichiro0903:20160628012308p:plain
なけりゅ!

とりあえず盲目的に、エラーコードでぐぐった。

これ?
違う。一応やったけど。

これか?
違う。

これ?
違う。すでになってた。

と、ここまででリンクがなんちゃらってのと、死んでるのがBluetoothConnection部分だったので、もしやと思ったら、きた。
CoreBluetoothフレームワーク追加していないからだった。これは盲点。

まだ問題はあって、読み込みタイミングがおかしい(のかリトライしてくれない)せいで、Bluetooth接続してくれなかった。
なんとかBlutoothクラス生成する部分にStartScanを直接呼び出すメソッド書いて乗り越えたけど、
読み込みタイミングの制御とかクソムズい。諦めよ。とかなってた。

2016-06-25 01:44:11.457 ProductName[3032:1476446] init instance
2016-06-25 01:44:11.457 ProductName[3032:1476446] sharedManager
2016-06-25 01:44:11.457 ProductName[3032:1476446] Central BT is powerd off
Setting up 1 worker threads for Enlighten.
  Thread -> id: 16e9b3000 -> priority: 1
2016-06-25 01:44:11.517 ProductName[3032:1476446] CBCentralManagerStatePoweredOn

でも諦めず、

            self.isCentralBluetoothPoweredOn = YES;
            NSLog(@"CBCentralManagerStatePoweredOn");
            [self startScanning]; //resume
            break;

ってしてみた。

要は、BluetoothのON/OFF判定がされる前に(OFFだと判定されてしまうタイミングで)、ペリフェラルのスキャンが走ってしまう模様。この問題を解決するために以下のように、centralManagerDidUpdateState中でBluetoothがONの判定になったら、再度スキャンかけるようにした。(これにメッチャクチャハマって、できた時は思わず絶叫した。)勝利。

UnitySendMessageでハマったのは、送り先のGame Objectがユニークな名前じゃないとダメだということ。

http://forum.unity3d.com/threads/sendmessage-object-everyplay-does-not-have-receiver-for-function.324289/

記事序盤に設定した、iOSConnectionTextをTextっていう汎用的な名前にしてて、でこけた。

ここはどういう概念かを理解するのにとてもわかり易かった。
【Unity初心者向け】別オブジェクトのメソッドを実行する方法まとめ

Bluetooth機器(LightBlue Bean)の加速度受け渡し部分実装。

LightBlue Beanの加速度に関する仕様はこちら

はじめ、Batteryサービスのように、加速度情報が変化する度にどこかのCharacteristicsに書き込まれているのかな?って思ったのだけど違うみたい。
LightBlue Beanアプリでみてみると、Bean transport ってサービスがあったけど、それはシリアル通信用だった。

LightBlue Explorer - Bluetooth Low Energy

LightBlue Explorer - Bluetooth Low Energy

  • Punch Through
  • ユーティリティ
  • 無料

セントラル側では、didUpdateNotificationStateForCharacteristicで受け取るので、Characteristicsに情報を書き込むようにする必要がある。
加速度をgetAccelerationで取得して、どこかScratchに書き込みして都度読み取りする方針に。

加速度の取り方はこれ参考に。(これはこれですごい良いアイデア。)

加速度の値は-512〜511までの値が入るので、512から引いて、正の整数にする。(512からの距離に変換。)
で、それを2で割って、

setScratchNumber(1,diff_char);

とScratch1 にセット。

ここで2で割っている理由は以下。
Scrath1に入っている(今回は加速度)データを実際にXcodeでコンソールに出してみると、

faffffff

で、これは、

[characteristic.value getBytes:&hoge length:1];

を通すと、250になる。
これは、

faffffff

を2進数に変換して、

11111010111111111111111111111111

で、
あたまから8bit(=length:1byteなので)分取得すると、11111010。これは10進数で250という感じ。

今回は回転がどのくらいのスピードで行われているか知りたいので、回転運動の右端と左端つまり、加速度がプラスの最大値からマイナスの最小値になるまでの時間を測定して速度を概算したい。(本来であればそうしたいが、今回は検証のため作りこまなかったので、プラスからマイナスまでの判定のみとした。)

で最終こうなった。

シリアル通信のアウトプットはこんな感じ。ちゃんと動いてる。
f:id:yoichiro0903:20160628013114p:plain

動くか確認。

で、ようやくここまで辿り着いた。さあこいよ!

キタ━━━━(゚∀゚)━━━━!!
f:id:yoichiro0903:20160628003700p:plain
が一瞬表示されて、(これは後続処理をわざとエラーにして出した画面)
f:id:yoichiro0903:20160628013133p:plain
と加速度を元に計算された数字が1秒毎に表示される。

いやあ、楽しいなあ。長かったなあ。