背景
以前にmbed × BLE × iOSでとりあえず通信したい人のための記事を投稿した者です。やはりMacともBLE通信したいなぁと思って、そのまま移植したら全然うまく動かなくて大変だったので、備忘録として手法をまとめます。
環境
PC: MacBook Pro, macOS High Sierra,セントラル
IDE: Xcode9.2, Swift4
mbed: TY51822r3, mbed-os 5,ペリフェラル
主な躓きどころ
iOSでは出なかったエラーXPC connection invalid
が出る
macOS(OSX)にはサンドボックスと呼ばれる仕組みがあり、iOSとは勝手が少々異なる。
-
target -> Capabilities -> App Sandbox -> Hardware -> Bluetoothにチェックをいれる
-
info.plist
にPrivacy - Bluetooth Peripheral Usage Description
を追加して何かしらの文章を登録する
レイテンシー(待ち時間、反応速度)がとにかく遅い.凄まじい遅延を感じる
これはペリフェラル側の設定が甘いことによる問題だと思われる。
今回の場合、mbedをペリフェラルとしており、そのGap::ConnectionParams_t
の持つパラメータ.slaveLatency
を0にすることで遅延をなくすことができた。
macOS側をペリフェラルにする場合は,CBPeripheralManager.setDesiredConnectionLatency(, for: )
を使うことでレイテンシーを変更できるらしい。
ソースコード例
ペリフェラルであるmbedにつけたセンサの値をReadしてセントラルのMacで100Hzにて取得するサンプル
macOS:セントラル側
import Foundation
import CoreBluetooth
protocol BLEManagerDelegate {
func updateData(data: NSData)
}
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 timer: Timer?
public var delegate: BLEManagerDelegate?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
centralFlag = (central.state == CBManagerState.poweredOn)
}
func start() {
if centralFlag {
//Device Information 0x180A
centralManager.scanForPeripherals(withServices: [CBUUID(string: "180A")], options: nil)
}
}
func stop() {
if peripheral != nil {
timer?.invalidate()
centralManager.cancelPeripheralConnection(peripheral)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if peripheral.name == "DEVICE_NAME" {
self.peripheral = peripheral
self.centralManager.stopScan()
self.centralManager.connect(self.peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([CBUUID(string: "180A")])
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
Swift.print("Connect Failed")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
for service in services {
//UUIDは環境による
peripheral.discoverCharacteristics([CBUUID(string: "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")], for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let characteristics = service.characteristics {
for characteristic in characteristics {
if (UInt8(characteristic.properties.rawValue) & UInt8(CBCharacteristicProperties.read.rawValue)) != 0 {
self.characteristic = characteristic
timer = 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
}
delegate?.updateData(data: characteristic.value! as NSData)
}
}
mbed:ペリフェラル側
#include "mbed.h"
#include "BLE.h"
#if 0
Serial pc(USBTX, USBRX);
#define DEBUG(...) { pc.printf(__VA_ARGS__); }
#else
#define DEBUG(...)
#endif
#define DEVICE_NAME "DEVICE_NAME"
DigitalIn dIn(p8);
DigitalOut led(LED4);
int btn = 0;
Ticker ticker;
void tickerCallback(void);
BLE ble;
// GattService::UUID_DEVICE_INFORMATION_SERVIC
uint16_t UUID_SERVICE[] = {GattService::UUID_DEVICE_INFORMATION_SERVICE};
uint8_t UUID_CHAR_DATA[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
uint8_t data[1] = {0};
GattCharacteristic customCharastic(UUID_CHAR_DATA, data, sizeof(data), sizeof(data), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
GattCharacteristic *customChars[] = {&customCharastic};
GattService customService(GattService::UUID_DEVICE_INFORMATION_SERVICE, customChars, sizeof(customChars) / sizeof(GattCharacteristic *));
static Gap::ConnectionParams_t fast;
void connectionCallback(const Gap::ConnectionCallbackParams_t *params) {
DEBUG("Connected!\r\n");
led = 1;
ble.gap().stopAdvertising();
ble.updateConnectionParams(params->handle, &fast);
ticker.attach(&tickerCallback, 0.01);
}
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params) {
DEBUG("Disconnected!\r\n");
led = 0;
ticker.detach();
ble.gap().startAdvertising();
}
void connectTimeoutCallback(Gap::TimeoutSource_t source) {
DEBUG("Timeout");
}
void tickerCallback() {
DEBUG("update\r\n");
uint8_t btn_ = (uint8_t)((dIn.read()+1)%2);
data[0] = (btn_ == 1 && btn == 0)?(uint8_t)(1):(uint8_t)(0);
btn = btn_;
ble.updateCharacteristicValue(customCharastic.getValueAttribute().getHandle(), (uint8_t *)&data, sizeof(data));
}
int main(void) {
dIn.mode(PullUp);
DEBUG("start\r\n");
DEBUG("Initialise\r\n");
ble.init();
DEBUG("Setup the event handlers\r\n");
ble.gap().onConnection(connectionCallback);
ble.gap().onDisconnection(disconnectionCallback);
ble.gap().onTimeout(connectTimeoutCallback);
DEBUG("Setup latency\r\n"); //ここが最重要
ble.getPreferredConnectionParams(&fast);
fast.minConnectionInterval = 7.5;
fast.maxConnectionInterval = 10;
fast.slaveLatency = 0;
ble.setPreferredConnectionParams(&fast);
DEBUG("Advertising payload\r\n");
ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)UUID_SERVICE, sizeof(UUID_SERVICE));
ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (const uint8_t *)DEVICE_NAME, strlen(DEVICE_NAME));
ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
ble.gap().setAdvertisingInterval(160); //100ms; in multiples of 0.625ms.
ble.gap().startAdvertising();
DEBUG("Add service\r\n");
ble.gattServer().addService(customService);
while (true) {
ble.waitForEvent();
}
}
余談
macOS(OSX) でCoreBluetooth使ってる例とか参考記事が本当に少ない...BLE開発本当に難しいです。ちょっとでも心が折れそうな人の支えになれば嬉しいです。