LoginSignup
9
6

More than 3 years have passed since last update.

macOS × mbed OS でBLE通信する方法

Last updated at Posted at 2018-01-05

背景

以前に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とは勝手が少々異なる。

  1. target -> Capabilities -> App Sandbox -> Hardware -> Bluetoothにチェックをいれる
    sandbox.png

  2. info.plistPrivacy - Bluetooth Peripheral Usage Descriptionを追加して何かしらの文章を登録する
    info.png

レイテンシー(待ち時間、反応速度)がとにかく遅い.凄まじい遅延を感じる

これはペリフェラル側の設定が甘いことによる問題だと思われる。
今回の場合、mbedをペリフェラルとしており、そのGap::ConnectionParams_tの持つパラメータ.slaveLatencyを0にすることで遅延をなくすことができた。

macOS側をペリフェラルにする場合は,CBPeripheralManager.setDesiredConnectionLatency(, for: )を使うことでレイテンシーを変更できるらしい。

ソースコード例

ペリフェラルであるmbedにつけたセンサの値をReadしてセントラルのMacで100Hzにて取得するサンプル

macOS:セントラル側

BLEManager.swift
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:ペリフェラル側

main.cpp
#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開発本当に難しいです。ちょっとでも心が折れそうな人の支えになれば嬉しいです。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6