[備忘録]micro:bitとiphoneをBLEで繋げてみる


忘れるのでメモ

[前提]
・mpythonはスペックが上がらない限り、BLEのAPIを提供していないっぽい。
（16k中、12k使うかららしい）
・Javascript環境が提供するAPIはサービス？を自分で作れないっぽい。
（と、思う。。。）
・mbedはBLEのライブラリのコンパイルがうまく行かなかった

なので、micro:bit側は、aruduinoで実装することにした。
mbedは、そのうち、なんとかする。。。


参考ページ：

https://ht-deko.com/arduino/microbit.html

ほぼ、ここに、環境構築から接続まで書いてある。
すばらしい。。。

でも、忘備録なので、Aruduinoでの開発環境の構築を記載する。



[micro:bitのAruduino開発環境の構築]

１．AruduinoのIDEを起動し、環境設定を開き、「追加のボードマネージャーのURL」に以下追加。
	
https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json

![1.png](https://qiita-image-store.s3.amazonaws.com/0/38889/9fd57b1c-bdfc-ffba-4592-47c47df293cd.png "1.png")


２．環境設定で、「スケッチの保存場所」を覚えておく。

３．「ツール」→「ボード」→「ボードマネージャー」を開き
nRF5 で検索。Nordic Semiconductor nRF5 based boardsをインストール。

![2.png](https://qiita-image-store.s3.amazonaws.com/0/38889/ea02f8ae-ab7d-a4de-a94a-1f1e0c2bf60b.png "2.png")



４．「ツール」にて、ボード選択
「ボード」　　　：「BBC Micro:bit」
![3.png](https://qiita-image-store.s3.amazonaws.com/0/38889/65c2a3b6-4c50-41b8-28a6-ea3f2c59b6e3.png "3.png")


５．すぐにLEDをためせるので、前述、参考ページの人のライブラリを導入。（とても助かる！）
①以下、URLから、ダウンロードし解凍。
https://github.com/ht-deko/microbit_Screen

②フォルダごと、「スケッチの保存場所」下のLibrariesフォルダにコピー
![4.png](https://qiita-image-store.s3.amazonaws.com/0/38889/45f896c7-c152-f7af-0e26-0f6405e50810.png "4.png")





６．BLE接続環境の導入
(BLEを使わないなら要らない。今回はBLEを使う目的なので以下実行)
①２．のスケッチの保存場所下に、

「tools/nRF5FlashSoftDevice/tool」

フォルダを作成。

②以下のURLから、「nRF5FlashSoftDevice.jar」をダウンロードする。
https://github.com/sandeepmistry/arduino-nRF5/

ダウンロード後、①で作成したフォルダにコピー後、AruduinoIDEを再起動する。
![5.png](https://qiita-image-store.s3.amazonaws.com/0/38889/8dfc8952-f148-116c-6ff1-d2ca0d603842.png "5.png")




③「ツール」にて、以下選択。
「SoftDevice」：S310	
「書き込み装置」：CMSIS-DAP

![6.png](https://qiita-image-store.s3.amazonaws.com/0/38889/b99b7169-d947-aeeb-dbe1-9cd3568555e9.png "6.png")


④「ツール」にて、「nRF5 Flash SoftDevice」 を選択。
なんか聞かれる（ハズ。事後で、キャプチャがない。。。）ので「accept」し、
IDEを再起動する。

⑤以下URLからBLEのライブラリをダウンロードし、解凍。
https://github.com/sandeepmistry/arduino-BLEPeripheral

フォルダごと、「スケッチの保存場所」下のLibrariesフォルダにコピー

![7.png](https://qiita-image-store.s3.amazonaws.com/0/38889/5c7f6c11-69b2-5e6e-73c3-4638983ced97.png "7.png")




７．USBでmicro:bitを接続。IDEの「ツール」「シリアルポート」を、
micro:bitにする。

![8.png](https://qiita-image-store.s3.amazonaws.com/0/38889/cef99136-2969-d8ec-6d74-df8aca92f5e8.png "8.png")




[Aruduinoでmicro:bitのLEDマトリクスを光らせてみる]

前述の親切な人のライブラリを使って、LEDを光らせてみる。
ソース

```
#include <microbit_Screen.h>

void setup() {
  // put your setup code here, to run once:
    Serial.begin(9600);
    SCREEN.begin();
    Serial.println("Hello micro:bit");
}

void loop() {
  // put your main code here, to run repeatedly:
    SCREEN.showIcon(IconNames::Happy);
}

```

実行結果は、こんな感じ。

![9.png](https://qiita-image-store.s3.amazonaws.com/0/38889/7642ef73-f47a-993a-52f6-86f0ad77b056.png "9.png")

![10.png](https://qiita-image-store.s3.amazonaws.com/0/38889/16252ae3-3cb1-2064-08d5-f22c6011273a.png "10.png")


シリアルで文字が送られ、LEDのマトリクスは、ちゃんと、
ニコちゃんマークになっている。






[micro:bitでペリフェラル作成]

[仕様]
セントラルから、テキトーな１０文字の文字列を受け取り、LEDマトリクスで表示する。
接続時の認証とかはしない。(やり方をまだ知らない。。。)

１．macのコンソールでuuidgenでテキトーなUUIDを準備
（とりあえず、サービスもキャラクたりスティックも同じ奴を使った。。。）
ex.

```
uuidgen
ED3A76F6-70B5-4BBE-840D-EAA341F78417
```

２．ソースを準備。micro:bitに焼く。

ソース

```
// Import libraries (BLEPeripheral depends on SPI)
#include <SPI.h>
#include <BLEPeripheral.h>
#include <microbit_Screen.h>


BLEPeripheral blePeripheral = BLEPeripheral();

// create service
//uuidgen 
BLEService               ledService           = BLEService("ED3A76F6-70B5-4BBE-840D-EAA341F78417");

char defaultVal[] = {'N', 'O', 'N', 'E', ' ', ' ', ' ', ' ', ' ', 0x00};

BLECharacteristic hogeCharacteristic = BLECharacteristic("ED3A76F6-70B5-4BBE-840D-EAA341F78417", 
                                                          BLERead | BLEWrite, 
                                                          11);
char* receivedBuf = "          ";

void setup() {
  Serial.begin(9600);
  SCREEN.begin();

  blePeripheral.setDeviceName("BBC micro:bit TestA");
  blePeripheral.setLocalName("Micro:bit HogeDevice");

  blePeripheral.setAdvertisedServiceUuid(ledService.uuid());
  hogeCharacteristic.setValue(defaultVal);

  blePeripheral.addAttribute(ledService);
  blePeripheral.addAttribute(hogeCharacteristic);

  // BLE init
  blePeripheral.begin();
  Serial.println(F("BLE HOGE Peripheral"));
}

void loop() {

  // 
  SCREEN.showIcon(IconNames::Chessboard); 

  // Blocking wait...
  BLECentral central = blePeripheral.central();
  if (central) {
    // central connected to peripheral
    Serial.print(F("Connected to central: "));
    Serial.println(central.address());
    while (central.connected()) {

      SCREEN.showIcon(IconNames::Happy);
 
      if (hogeCharacteristic.written()) {
        // received from central...
        Serial.println("receive data!");
        if (hogeCharacteristic.value()) {
          String msg = String((const char*)hogeCharacteristic.value());
          Serial.println(msg);
          msg.toCharArray(receivedBuf, msg.length());          
          char fuga[10] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
          msg.toCharArray(fuga, msg.length());
          if (hogeCharacteristic.setValue(fuga)) {
            Serial.println("success response write ");
          } else {
            Serial.println("failed response write ");          
          }
          msg.trim();
          SCREEN.showString(msg);
          delay(2000);
        }        
      }      
    }
    Serial.print(F("Disconnected from central: "));
    Serial.println(central.address());
    SCREEN.showIcon(IconNames::Sad);
    delay(2000);    
  }
}
	
		
```

![11.png](https://qiita-image-store.s3.amazonaws.com/0/38889/bb57c1f0-d0f3-3d86-9a91-9687c43fde84.png "11.png")

![12.png](https://qiita-image-store.s3.amazonaws.com/0/38889/ae5939fe-5d54-f464-fe60-6243a4a53441.png "12.png")


準備完了。。。
（交互に光ってるマトリクスはチェスボード。）


[iPhoneでmicro:bitに接続し、文字列を送信]

[仕様]
BLEでmicro:bitに接続し、テキトーな文字列を送信する。
認証は、前述のとーりしない。。。
単純に１画面で実装。
スキャン、接続、切断ボタン、送信ボタン、通信ログを表示テーブルを持つ。
楽なので、swiftで実装した。


```
//
//  ViewController.swift
//  MicrobitBleSample1
//
//  Created by pies on 2017/12/17.
//  Copyright © 2017年 pies. All rights reserved.
//
import UIKit
import CoreBluetooth

class ViewController: UIViewController,
    CBCentralManagerDelegate,CBPeripheralDelegate,
    UITableViewDataSource, UITableViewDelegate {
    let CMD_LEN = 10
    let hogeServiceUUId = CBUUID(string: "ED3A76F6-70B5-4BBE-840D-EAA341F78417")
    let testDeviceName = "Micro:bit TestDevice Hoge"
    
    
    @IBOutlet weak var tblLog: UITableView!
    
    @IBOutlet weak var btnScan: UIButton!
    
    @IBOutlet weak var btnConnectDevice: UIButton!
    
    @IBOutlet weak var btnTestSend: UIButton!
    
    @IBOutlet weak var btnDisConnectDevice: UIButton!
    
    
    var logMsg = [String]()
    
    var cm: CBCentralManager!
    var hogeDevice: CBPeripheral?
    var hogeService: CBService?
    var hogeChar: CBCharacteristic?
    
    var selectedCmdIdx: Int = -1
    
    override func viewDidLoad() {
        
        super.viewDidLoad()

        btnScan.isEnabled = false
        btnConnectDevice.isEnabled = false
        btnTestSend.isEnabled = false
        btnDisConnectDevice.isEnabled = false
        
        tblLog.dataSource = self
        tblLog.delegate = self
        // ble初期化
        cm = CBCentralManager(delegate: self, queue:nil)
        
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    @IBAction func tapBtnScan(_ sender: Any) {
        
        if !cm.isScanning {
            // BLEデバイスの検出を開始.
            cm.scanForPeripherals(withServices: [hogeServiceUUId], options: nil)
            btnScan.isEnabled = false
            btnConnectDevice.isEnabled = false
            hogeDevice = nil
            logMsg.append("スキャン開始")
            reloadAndLast()
        }
    }
    
    // micro:bitと接続
    @IBAction func tapBtnConnectDevice(_ sender: Any) {
        
        if let tgtDevice = hogeDevice {
            btnConnectDevice.isEnabled = false
            logMsg.append("接続開始")
            cm.connect(tgtDevice, options: nil)
            reloadAndLast()
        }
    }
    
    // テキトー送信
    @IBAction func tapBtnTestSend(_ sender: Any) {
        if let tgtDevice = hogeDevice {
            if hogeService != nil && hogeChar != nil {
                tgtDevice.writeValue(toCmd("hoge"), for: hogeChar!, type: CBCharacteristicWriteType.withResponse)
                logMsg.append("送信:hoge")
                reloadAndLast()
                hogeDevice?.readValue(for: hogeChar!)
            }
        }
    }
    
    
    // 固定長にしてbyte array化
    func toCmd(_ cmd: String) -> Data {
        var buf = "\(cmd)"
        while buf.utf8.count < CMD_LEN {
            buf = "\(buf) "
        }
        print("[\(buf)]")
        var arr = [UInt8]()
        for c in buf.utf8 {
            arr.append(c)
        }
        // 相手はC言語の世界なのでケツにnull。。。
        arr.append(0x0)
        return Data(bytes: arr)
    }
    
    @IBAction func tapBtnDisconnect(_ sender: Any) {
        btnTestSend.isEnabled = false
        if hogeDevice != nil {
            btnDisConnectDevice.isEnabled = false
            cm.cancelPeripheralConnection(hogeDevice!)
            logMsg.append("切断実行")
            reloadAndLast()
        }
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return logMsg.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80.0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CELL_LOG")!
        let lblLog = cell.contentView.viewWithTag(1001) as! UILabel
        lblLog.text = logMsg[indexPath.item]
        return cell
    }
    
    func reloadAndLast() {
        tblLog.reloadData()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [unowned self] in
            let idx = IndexPath(item: self.logMsg.count - 1, section: 0)
            self.tblLog.scrollToRow(at: idx, at:.bottom, animated: true)
        }
    }
    
    
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {        
        print("state \(central.state)")
        switch (central.state) {
        case .poweredOff:
            print("Bluetoothの電源がOff")
            logMsg.append("Bluetooth:電源Off")
            btnScan.isEnabled = false
            btnConnectDevice.isEnabled = false
        case .poweredOn:
            print("Bluetoothの電源はOn")
            logMsg.append("Bluetooth:電源On")
            
            if !btnScan.isEnabled && !cm.isScanning {
                btnScan.isEnabled = true
            }
        case .resetting:
            print("レスティング状態")
            logMsg.append("Bluetooth:レスティング状態")
        case .unauthorized:
            print("非認証状態")
            logMsg.append("Bluetooth:非認証状態")
        case .unknown:
            print("不明")
            logMsg.append("Bluetooth:不明")
        case .unsupported:
            print("非対応")
            logMsg.append("Bluetooth:非対応")
            
        }
        tblLog.reloadData()
    }
    
    
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        if let dName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
            
            print("device:\(dName)")
            
            // デバイス名確認
            if dName.hasPrefix("Micro:bit HogeDevice") {
                // サービス確認
                if let srvIds = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [Any] {
                    if srvIds.filter({$0 as? CBUUID == hogeServiceUUId}).count > 0 {
                        // hoge service確認
                        cm.stopScan()
                        logMsg.append("Micor:bit確認。スキャン停止")
                        print(CBAdvertisementDataServiceUUIDsKey)
                        logMsg.append("device:\(dName)\nuuid:\(peripheral.identifier)")
                        tblLog.reloadData()
                        self.hogeDevice = peripheral
                        btnScan.isEnabled = true
                        btnConnectDevice.isEnabled = true
                    } else {
                        logMsg.append("Micor:bit確認。hogeサービスなし")
                    }
                }
            }
        }
        reloadAndLast()
        
        
        // peripheral.
        
        //        cm.stopScan()
    }
    
    // peripheralと接続成功
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        
        print("デバイスと接続成功")
        logMsg.append("デバイスと接続成功")
        
        // デバイスのdelegate設定
        peripheral.delegate = self
        
        // サービスのUUIDを確認
        peripheral.discoverServices([hogeServiceUUId])
        
        reloadAndLast()
    }
    
    // peripheralと接続失敗
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        
        print("デバイスと接続失敗,\ncause:\(error)")
        logMsg.append("デバイスと接続失敗")
        //logMsg.append("error:\(error)")
        
        btnConnectDevice.isEnabled = true
        btnTestSend.isEnabled = false
        reloadAndLast()
        
    }
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        
        btnTestSend.isEnabled = false
        btnDisConnectDevice.isEnabled = false
        print("デバイス切断")
        logMsg.append("デバイス切断")
        if error == nil {
            btnConnectDevice.isEnabled = true
        } else {
            print("cause:\(error!)")
        }
        reloadAndLast()
    }
    
    // peripheralサービス検索結果
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        
        if error != nil {
            // サービス確認失敗
            logMsg.append("サービス確認失敗")
            print("サービス確認失敗\ncause:\(error!)")
        } else {
            // サービス確認成功
            logMsg.append("サービス確認成功")
            logMsg.append("Characteristics確認実行")
            btnTestSend.isEnabled = true
            btnDisConnectDevice.isEnabled = true
            
            hogeService = peripheral.services?.filter({$0.uuid == hogeServiceUUId})[0]
            peripheral.discoverCharacteristics([hogeServiceUUId], for: hogeService!)
        }
        
        reloadAndLast()
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if error != nil {
            logMsg.append("Characteristics確認:エラー")
            print("Characteristics確認:エラー")
            print("cause:\(error!)")
        } else {
            logMsg.append("Characteristics確認:成功")
            hogeChar = service.characteristics!.filter({$0.uuid == hogeServiceUUId})[0]
            logMsg.append("Characteristics読込リクエスト")
            peripheral.readValue(for: hogeChar!)
            btnTestSend.isEnabled = true
        }
        reloadAndLast()
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            logMsg.append("Characteristics読込:エラー")
            print("Characteristics読込:エラー")
            print("cause:\(error!)")
            
            
        } else {
            logMsg.append("Characteristics読込:成功")
            print("Characteristics:\(characteristic.value)")
            if let data = characteristic.value {
                let str = String(data: data, encoding: .utf8)
                print("receive:\(str)")
            }
        }
        reloadAndLast()
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            
            print(error!)
            
        } else {
            if let data = characteristic.value {
                let str = String(data: data, encoding: .utf8)
                print("receive:\(str)")
            }
        }
    }
}


```

一応、ソースはここに置きました。。。
https://bitbucket.org/cyclon2joker/microbitblesample1


ビルドし、実機で実行してみる。

![13_0.png](https://qiita-image-store.s3.amazonaws.com/0/38889/8f31ecd7-9154-b38e-d256-dec23c6d0ff2.png "13_0.png")

スキャンをタップし、micro:bitを検出しているのを確認。

![13.png](https://qiita-image-store.s3.amazonaws.com/0/38889/84ff5abc-60ce-436a-3cc5-a25944015373.png "13.png")

接続し、ログをみる。
![14.png](https://qiita-image-store.s3.amazonaws.com/0/38889/6f69e96e-7a89-4d70-56b8-e8ac395e7e41.png "14.png")

キャラクタリスティックまで読んでいる。

で、micro:bitは、

![15.png](https://qiita-image-store.s3.amazonaws.com/0/38889/2637026b-b36a-24f4-f2e3-65edd191c270.png "15.png")

ニコちゃんマーク（接続したらHappyに設定。）

送信ボタンをタップ。

![16.png](https://qiita-image-store.s3.amazonaws.com/0/38889/1d3ef1ec-e383-150e-4292-3ce0333980d0.png "16.png")

。。。
わかりにくいが(。。。)、「hoge」という文字が、
LEDマトリクスにスクロールしている。

![17.png](https://qiita-image-store.s3.amazonaws.com/0/38889/5beca528-6bf0-b18d-c1b5-32040b688366.png "17.png")

シリアルにも流れてます。。。

切断する。

![18.png](https://qiita-image-store.s3.amazonaws.com/0/38889/63429a77-403c-383d-3dd2-0c4f01408df9.png "18.png")

![19.png](https://qiita-image-store.s3.amazonaws.com/0/38889/31481225-2122-3282-04e9-dacb63e9597d.png "19.png")

無事、一通りの動作完了。


[まとめ、感想]
arudionoで焼けることによって、かなり、扱いやすいデバイスだなーと思う。。。
最初からLEDマトリクスもセンサーもついてるし、それでいて安いし、
かなり、楽しめるオモチャだと思う。

子供の時にこんな面白いものが、タダでもらえるなんて、イギリスの子供は
めぐまれてるなーと。。。



[TODO]
・iBeaconを作ってみる。
・kotolinで接続してみる。
・mbedでビルド出来るようにする。
・モータを制御する拡張ボードを買ってなんかする。