背景
これまで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の設定
-
環境設定の「追加のボードマネージャのURL」にライブラリのURLを追加
URL: https://redbear.github.io/arduino/package_redbear_nRF5x_index.json
-
ボードの選択を
BLE_Nano2
に変更
Xcodeプロジェクトの設定
- target -> Capabilities -> App Sandbox -> Hardware -> Bluetoothにチェックをいれる
-
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:ペリフェラル側
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:セントラル側
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)
}
}