Genuino101とRN4020でお手軽なBLE電子工作（卓球マシンの例）

###導入
IoTとガジェット系スタートアップには欠かせなくなってきたBLE。
趣味の電子工作でも、BLEを使って作品を作る人が増えてきました。
ただ、いざBLEを使った工作を始めようとしても、ノルディックセミコンダクタとかのチップを使ったモジュールのプログラミングはC言語の知識とか、開発環境の構築、英語のドキュメントとの格闘が必要になるので、Arduinoみたいな手軽さはありません。
しかも、チップメーカーからサンプルは沢山提供されているのはいいけど、HellowWorld的な単純なコードから機能を追加していくような体系的なチュートリアルがないので、習得するにはすこし敷居が高いと個人的には思います。
またスクリプト言語を使って開発するモジュールもありますが、メーカー側が言うように簡単にできるわけではなく、分かりにくいスクリプトの仕様を理解しながら（これも日本語のドキュメントはほとんどないので英語）開発するのは大変な感じですね。

 お手軽に手っ取り早くBLEを使った工作に挑戦したい場合は、konashi、bCoreとかがよく使われています。（同じようなコンセプトのモジュールが他にもいくつかあります）このモジュールのいいところはBLE自体をあまり意識する必要がなく、短距離の無線モジュールとして使うことができる点です。ただ、BLE自体をうまくカプセル化しているために、BLEを使って作ったという感じがないのでBLEを勉強したい人にとっては少し物足りないかもしれません。

　Konashiでは物足りないけど、ガチのBLEモジュール開発はチョット...という人には、RN4020、Genuino101があります。
RN4020はシリアルモードでつなぐ（クラシックBluetoothのSPPみたいな）モードもありますが、これはBLEとしては少し反則っぽい...ので今回は触れませんw。
BLEを勉強したいので、プロファイル（キャラクタリスティック）ぐらいは自分で定義したい。そういう人はこのモジュールを使うと、BLEの細かな処理はライブラリ化されて見えませんが、BLEとしての通信の手続きは関数を呼び出す形なので、大まかなプロトコルを理解するにはいいかなと思っています。

　そのほか、mBed系（Arduino系より少し難しい）、Raspberry-pi3（node.js+nobleでの開発が便利）もあります。他にもまだ色々あるけど、技適がちゃんと通っている（ここ重要）ボードで、BLEをお手軽に使うにはRN4020かGenuino101がおすすめですね。
![fig5.png](https://qiita-image-store.s3.amazonaws.com/0/33661/d29e091a-dbbb-d808-ccf9-1103910a1fe6.png)

　それでは、Genuino101とRN4020を実際に使用した例として開発中の卓球マシンの例で説明します。

###卓球マシン
![fig4.png](https://qiita-image-store.s3.amazonaws.com/0/33661/c4f90b6a-c9a1-d312-ea00-ba3b332fbabd.png)
卓球の練習用に使用する、自動球出し機です。スマートフォンと連動するために、BLEで通信して、球の打ち出し間隔、スピード（カット方向）、打ち出し方向を決めるようになっています。（外側のケースを外した状態）
####構成
![fig1.png](https://qiita-image-store.s3.amazonaws.com/0/33661/7f9a6dc8-8f19-4529-b654-2a752d69d64e.png)


BLE的には、卓球マシンがペリフェラル、iPhoneがセントラルになります。
#####制御するもの
　球送りモータ、球出し上モータ、球出し下モータ、送出角度設定サーボモーター
#####入力情報
　球送り検出のフォトカプラー(1個の送り出しでON発生)
#####動作
　・iPhoneで卓球マシンの球出し間隔、スピード、球種、スイングを変更する。
　・球の送出のタイミングでNotify通知を受け、iPhoneで効果音、音声の再生を行う。

定義したキャラクタリスティックは以下の5つです。
・球出し間隔　　　19B10001-E8F2-537E-4F6C-D104768A1219　Read,Write
・スピード　　　　19B10002-E8F2-537E-4F6C-D104768A1219　Read,Write
・球種　　　　　　19B10003-E8F2-537E-4F6C-D104768A1219　Read,Write
・スイング角度　　　　　　19B10004-E8F2-537E-4F6C-D104768A1219　Read,Write
・球出し通知　　　　　　19B10005-E8F2-537E-4F6C-D104768A1219　Write,Notify

プライベートサービスのUUIDも決めておきます。
　19B10000-E8F2-537E-4F6C-D104768A1219

球出し通知は、ピンポン玉を１個送るごとに発生するNotifyで、iPhone側はこの通知を受けて、音声合成音で「さー」とかの掛け声とか効果音を再生する事が出来ます。
###Genuino101の場合
公式のサンプルコード(Button LED)をベースに追加分を加えていきます。
[https://www.arduino.cc/en/Tutorial/Genuino101CurieBLEButtonLED](https://www.arduino.cc/en/Tutorial/Genuino101CurieBLEButtonLED)
基本はArduinoなので初期設定をsetup()に、定期処理をloop()に書いてきます。

```c:pingpong.c
//キャラクラリスティックの定義
BLEPeripheral blePeripheral;  // BLE Peripheral Device
BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1219"); //

BLEUnsignedCharCharacteristic switchCharacteristic1("19B10001-E8F2-537E-4F6C-D104768A1219", BLERead | BLEWrite);
BLEUnsignedCharCharacteristic switchCharacteristic2("19B10002-E8F2-537E-4F6C-D104768A1219", BLERead | BLEWrite);
BLEUnsignedCharCharacteristic switchCharacteristic3("19B10003-E8F2-537E-4F6C-D104768A1219", BLERead | BLEWrite);
BLEUnsignedCharCharacteristic switchCharacteristic4("19B10004-E8F2-537E-4F6C-D104768A1219", BLERead | BLEWrite);
BLEUnsignedCharCharacteristic switchCharacteristic5("19B10005-E8F2-537E-4F6C-D104768A1219", BLEWrite | BLENotify);
```

setup()での処理
プライベートサービスとキャラクタリスティックを登録します。

```c:pingpong.c
  blePeripheral.setLocalName("PingPong");							//名称定義
  blePeripheral.setAdvertisedServiceUuid(ledService.uuid());

  // serviceとcharacteristicの追加
  blePeripheral.addAttribute(ledService);
  blePeripheral.addAttribute(switchCharacteristic1);
  blePeripheral.addAttribute(switchCharacteristic2);
  blePeripheral.addAttribute(switchCharacteristic3);
  blePeripheral.addAttribute(switchCharacteristic4);
  blePeripheral.addAttribute(switchCharacteristic5);

　//BLE開始
　blePeripheral.begin();
```

loop()での処理

```c:pingpong.c
  BLECentral central = blePeripheral.central();
    while (central.connected()) {
      // キャラクタリスティック１書き込みの検出
      if (switchCharacteristic1.written()) {
      	//書き込みデータの読出し
        Serial.println(switchCharacteristic1.value());
        //処理したい内容 モータの制御
      }
    }
```

こんな感じで、キャラクタリスティックの変化を検出して、処理したい内容を記述すします。

###RN4040の場合
RN4020とはシリアルポート経由でやり取りします。制御プログラムは、RN4020のスクリプトだけでは難しいので、別のマイコン（Arduino）を使います。RN4020とArduinoはシリアル通信でコマンドとレスポンスのやり取りをします。

![fig2.png](https://qiita-image-store.s3.amazonaws.com/0/33661/7e2e682e-7226-75b6-68fd-bbf5930a3733.png)

ありがたいことに、Micorchipの日本語のマニュアルがあってコマンドとかは解りやすく説明されています。
[http://ww1.microchip.com/downloads/jp/DeviceDoc/70005191A_JP.pdf](http://ww1.microchip.com/downloads/jp/DeviceDoc/70005191A_JP.pdf)

RN4020へは、プライベートサービス、キャラクタリスティックのデフォルト値はあらかじめ書き込んでおきます。（設定ツールを作ると便利）
![fig3.png](https://qiita-image-store.s3.amazonaws.com/0/33661/3874f949-81ca-bc73-c14c-0fbf54b56eac.png)

設定パラメータ

```c:
S-,PINGPONG\r                                 //名前の決定
SR,20006000\r
SS,80000001\r
ST,0010,0002,0064\r                           //コネクションパラメータ iOS用の設定
PZ\r	                                      //Private Serviceの設定をクリア
PS,19B10000E8F2537E4F6CD104768A1219           //Private ServiceのUUIDを設定
            
PC,19B10001E8F2537E4F6CD104768A1219,08,01\r   //Private CharacteristicのUUIDを設定 Write
PC,19B10002E8F2537E4F6CD104768A1219,08,01\r   //Private CharacteristicのUUIDを設定 Write
PC,19B10003E8F2537E4F6CD104768A1219,08,01\r   //Private CharacteristicのUUIDを設定 Write
PC,19B10004E8F2537E4F6CD104768A1219,08,01\r   //Private CharacteristicのUUIDを設定 Write
PC,19B10005E8F2537E4F6CD104768A1219,08,01\r   //Private CharacteristicのUUIDを設定 Write
PC,19B10006E8F2537E4F6CD104768A1219,18,01\r   //Private CharacteristicのUUIDを設定 Notify
R,1\r                                         //Reboot

```

　　＊データはすべて1バイトで定義
Arduinoのプログラムはシリアル通信でのコマンドをやり取りする、定番パターンになります。
####キャラクタリスティックの値が変更されたとき（変更はスマホ側で）
WV,0018,XX　（XXはデータ,0018キャラクタリスティックのID →　事前にターミナルで"LT"コマンドで確認　）
がRN4020から送られてきます。
コマンドを受信したら、コマンドに合わせて動作を行います。今回は卓球マシンのモーターの制御を行うわけですね。

#####notifyを送るとき
球送りのフォトセンサー検出で、球送りのタイミングをnotifyします。
実際には以下のコマンドを送信します。

　　SHW,0022,01

###iPhone側
スマートフォンのプログラムを組むのが面倒な場合は、LightBlueというBLEの定番アプリでチェックできるよ。

####アドバタイジングの受信リストから選択
![fig6.png](https://qiita-image-store.s3.amazonaws.com/0/33661/9d1b88fb-3ac6-b544-2aec-e16fbec358a0.png)
装置のリストから"PINGPONGXXX"を選んで、タッチすると
![fig7.png](https://qiita-image-store.s3.amazonaws.com/0/33661/63bad7a7-f4e5-6a6a-ddee-f479181c2f8a.png)
登録されている、サービスとキャラクタリスティックが表示されます。
####送信(書き込み)する場合
![fig8.png](https://qiita-image-store.s3.amazonaws.com/0/33661/72c52528-d462-1412-903a-359f836214df.png)
書き込みのキャラクタリスティックを選んでデータを書き込みます。
####Notifyを受信する場合
NOTIFIED VALUESの"Listen for notifucations"をタッチしてNotify受信待ち状態にします。
##![fig9.png](https://qiita-image-store.s3.amazonaws.com/0/33661/0e1622c9-6671-ee55-6340-c7982e5a3466.png)
受信したら受信データがリストで表示されます。

##swift
スマホ（iPhone)側のプログラムもswiftで作ってみました。
![fig10.png](https://qiita-image-store.s3.amazonaws.com/0/33661/fc8b3f24-9346-d27f-2a0a-c81795ce7281.png)

プログラムはLiteBlueで手動でやった手順を、プログラムすればOKです。
定義したアドバタイジングで送られるプライベートサービスのUUIDを検出したら接続します。そして見つかったキャラクタリスティックをUUIDを元に登録していきます。

```swift:
    // キャラクタリスティック発見時に呼ばれる
    func peripheral(_ peripheral: CBPeripheral,
                    didDiscoverCharacteristicsFor service: CBService,
                                                         error: Error?)
    {
        if (error != nil) {
            print("エラー: \(error)")
            return
        }
        
        let characteristics = service.characteristics!
        print("\(characteristics.count)個のキャラクタリスティックを発見しました。\n\(characteristics)\n")
        
        for characteristic in characteristics {
            //キャラクタリスティックの判定
            //
            if characteristic.uuid.isEqual(CBUUID(string: "19B10001-E8F2-537E-4F6C-D104768A1215")) {
                self.settingCharacteristic1 = characteristic
                print("卓球マシン　スピードUUID を発見")
            } else if characteristic.uuid.isEqual(CBUUID(string: "19B10002-E8F2-537E-4F6C-D104768A1215")) {
                self.settingCharacteristic2 = characteristic
                print("卓球マシン　上送出速度UUID を発見")
            } else if characteristic.uuid.isEqual(CBUUID(string: "19B10003-E8F2-537E-4F6C-D104768A1215")) {
                self.settingCharacteristic3 = characteristic
                print("卓球マシン　下送出速度UUID を発見")
            } else if characteristic.uuid.isEqual(CBUUID(string: "19B10004-E8F2-537E-4F6C-D104768A1215")) {
                self.settingCharacteristic4 = characteristic
                print("卓球マシン　スイングUUID を発見")
            } else if characteristic.uuid.isEqual(CBUUID(string: "19B10005-E8F2-537E-4F6C-D104768A1215")) {
                self.settingCharacteristic5 = characteristic
                print("卓球マシン　move UUID を発見")
            } else if characteristic.uuid.isEqual(CBUUID(string: "19B10006-E8F2-537E-4F6C-D104768A1215")) {
                self.settingCharacteristic6 = characteristic
                print("卓球マシン　球出し検出UUID を発見")
                self.peripheral.setNotifyValue(true, for: self.settingCharacteristic6)
            }
        }
    }
```

送信は、画面のボタンなどのオブジェクトからのイベント通知からパラメータを送信します。

```swift:
    func writeBLE1(_ value: CUnsignedChar) {
        
        if self.settingCharacteristic1 == nil{
            
            print("DEVICE is not ready!")
            return;
        }
        
        // 書き込みデータ生成
        var val: CUnsignedChar = value
        let data: Data = Data(bytes: &val, count: 1)
        print("BLE write \(data)")
       
        self.peripheral.writeValue(
            data,
            for: self.settingCharacteristic1,
            type: CBCharacteristicWriteType.withResponse)
    }
```

Noyifyの受信はdidUpdateValueForを定義して

```swift:
    // データ更新時に呼ばれる
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
    {
        
        if error != nil {
            print("データ更新通知エラー:%@", error)
            
        } else {
            var keyPress : UInt8 = 0
            characteristic.value? .copyBytes(to: &keyPress, count: 1)
            
            print("データ更新 \(keyPress)")
            if keyPress == 1 {
                //球出し検出　音声など "はい"　”さー”
                speechText(text: "さー")
            }
        }
    }
```

こんな感じです。
###まとめ
Genious101やRN4020を使うと、Arduinoの延長上で簡単にBLEのプログラムが作れます。電子工作でBLEに挑戦する入門用としてはぴったりですね。