macOS × BLE Nano V2でBLE通信してみた 

#背景
これまでBLEを使った子機を自作する場合は`mbed TY51822r3`を愛用してきたのですが，どうしてもArduino環境で実装したくなり，代替案としてArduino互換機の`BLE Nano　V2`を使ってみることにしました．これまでの経験もあり，特につまづくところなく通信できたので備忘録としてまとめます．

#環境
> セントラル: MacBook Pro, macOS High Sierra, IDE: Xcode9.4.1, 言語：Swift4
> ペリフェラル: BLE Nano V2, IDE: Arduino

#下準備

##Arduino　IDEの設定
1. 環境設定の「追加のボードマネージャのURL」にライブラリのURLを追加
URL: https://redbear.github.io/arduino/package_redbear_nRF5x_index.json
![one.png](https://qiita-image-store.s3.amazonaws.com/0/67153/acf8eaf9-b2f4-fa66-36bb-eddf9fe9fb0a.png)

2. ツール->ボード->ボードマネージャを開いて`RedBearLab nRF51822 Boards`をインストール
![two.png](https://qiita-image-store.s3.amazonaws.com/0/67153/5c68ce7a-cfa7-7785-66be-4bd134d22e98.png)

3. ボードの選択を`BLE_Nano2`に変更

4. 書込装置を`RBL_DAPLink`に変更（他のArduino機器に書き込むときに戻すのを忘れないこと）
![four.png](https://qiita-image-store.s3.amazonaws.com/0/67153/3f6e030e-463b-8db2-8828-3236392c42fa.png)

##Xcodeプロジェクトの設定
1. target -> Capabilities -> App Sandbox -> Hardware -> Bluetoothにチェックをいれる
2. `info.plist`に`Privacy - Bluetooth Peripheral Usage Description`を追加して何かしらの文章を登録する

#ソース

今回はUUIDを以下のように決めました

| Item            | UUID                                 |
|:----------------|:-------------------------------------|
| Service         | 00002525-0000-1000-8000-00805F9B34FB |
| Characteristics | 00002828-0000-1000-8000-00805F9B34FB |

BLEの仕様で最初の8桁の後半4桁（2525の部分）を自由に変えられるみたいです．

##BLE Nano:ペリフェラル側
```c++:BLE_Nano_Test.ino
#include <nRF5x_BLE_API.h>

#define DEVICE_NAME "BLE_Nano"
#define BUF_LEN 20

BLE ble;
Ticker ticker_task;
static uint8_t value = 0;

// UUIDの生成
static const uint8_t service_uuid[]         = {0x00, 0x00, 0x25, 0x25, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
static const uint8_t chars_uuid[]           = {0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
static const uint8_t service_uuid_reverse[] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x25, 0x25, 0x00, 0x00};
uint8_t chars_value[BUF_LEN] = {0};

// キャラクタリスティックの生成　今回はRead特化
GattCharacteristic characteristic(chars_uuid, chars_value, 1, BUF_LEN, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
GattCharacteristic *chars[] = {&characteristic};
// サービスの生成
GattService service(service_uuid, chars, sizeof(chars) / sizeof(GattCharacteristic *));

// 接続された時の処理
void connectionCallBack( const Gap::ConnectionCallbackParams_t *params ) {
  digitalWrite(D13, HIGH);
  Serial.println("Connected");
}

// 接続が途絶えた時の処理
void disconnectionCallBack(const Gap::DisconnectionCallbackParams_t *params) {
  digitalWrite(D13, LOW);
  Serial.println("Disconnected, Restart advertising");
  ble.startAdvertising();
}

// 一定間隔の処理
void periodicCallback() {
  value++;
  // キャラクタリスティックの値の更新(uint8_tの配列にする)
  ble.updateCharacteristicValue(characteristic.getValueAttribute().getHandle(), (uint8_t *)&value, 1);
}

void setup() {
  pinMode(D13, OUTPUT);
  Serial.begin(9600);
 
  ticker_task.attach(periodicCallback, 0.01); //micro second

  ble.init();
  ble.onConnection(connectionCallBack);
  ble.onDisconnection(disconnectionCallBack);
  
  ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
  // ショートネームの登録
  ble.accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, (const uint8_t *)"Nano", sizeof("Nano") - 1);
  // 128bit対応（サービスUUIDを反転したものを送る）
  ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)service_uuid_reverse, sizeof(service_uuid_reverse));
  // デバイスネームの登録
  ble.accumulateScanResponse(GapAdvertisingData::COMPLETE_LOCAL_NAME, (const uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME) - 1);

  ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
  ble.addService(service);
  ble.setDeviceName((const uint8_t *)DEVICE_NAME);
  ble.setTxPower(4);
  ble.setAdvertisingInterval(160);
  ble.setAdvertisingTimeout(0);
  ble.startAdvertising();
  Serial.println("Advertising Start!");
}

void loop() {
  ble.waitForEvent();
}
```

##macOS:セントラル側

```swift:BLEManager.swift
import Foundation
import CoreBluetooth

protocol BLEManagerDelegate {
    func updateData(_ values: [Int])
}

class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    private var centralManager: CBCentralManager!
    private var centralFlag: Bool = false
    private var peripheral: CBPeripheral! = nil
    private var characteristic: CBCharacteristic!
    private var ticker: Timer?
    public var delegate: BLEManagerDelegate?
    
    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    func start() {
        // 00002525-0000-1000-8000-00805F9B34FB
        centralManager.scanForPeripherals(withServices: [CBUUID(string: "2525")], options: nil)
    }
    
    func stop() {
        if peripheral != nil {
            ticker?.invalidate()
            centralManager.cancelPeripheralConnection(peripheral)
        }
    }
    
    //Central
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        centralFlag = (central.state == CBManagerState.poweredOn)
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        Swift.print(peripheral)
        if peripheral.name == "BLE_Nano" || peripheral.name == "Nano" { //Nanoが呼ばれることもある？(謎)
            self.peripheral = peripheral
            centralManager.stopScan()
            centralManager.connect(self.peripheral, options: nil)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheral.delegate = self
        peripheral.discoverServices([CBUUID(string: "2525")])
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        Swift.print("Connect Failed")
    }
    
    //Periferal
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let services = peripheral.services {
            for service in services {
                peripheral.discoverCharacteristics([CBUUID(string: "2828")], for: service)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let characteristics = service.characteristics {
            for characteristic in characteristics {
                // Read可能かどうかチェック
                if (UInt8(characteristic.properties.rawValue) & UInt8(CBCharacteristicProperties.read.rawValue)) != 0 {
                    self.characteristic = characteristic
                    Swift.print("Start Communication")
                    // 一定間隔でBLE　Nanoにお問い合わせ
                    ticker = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { (t) in
                        peripheral.readValue(for: self.characteristic)
                    })
                }
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            Swift.print("No Data")
            return
        }
        let data: NSData = characteristic.value! as NSData
        var buffer = [UInt8](repeating: 0, count: data.length)
        data.getBytes(&buffer, length: data.length)
        let values: [Int] = buffer.map { (value) -> Int in
            return Int(value)
        }
        delegate?.updateData(values)
    }
}

```

#参考
- [RedBearLab BLE Nanoキット V2](https://www.switch-science.com/catalog/3444/)
- [Blenano2をPeripheralとしてCentralからデータを貰うメモ](http://www.autumn-color.com/archives/1313)
- [mbedのBLE_APIライブラリで128bitUUIDを利用する](https://miso-engine.hatenablog.com/entry/mbedBLE128bit)
- [BLE NanoでiOSアプリとBluetooth通信(BLE Nano編)](https://www.pentacreation.com/blog/2015/12/151207.html)