More than 1 year has passed since last update.

chibi:bitにCoreBluetoothで接続する

chibi:bitがスイッチサイエンスから正式に発売されたので
iOSのCoreBluetoothを使って連携を試みてみました。

chibi:bitとは?

以下はスイッチサイエンスのページからの抜粋です。

//mag.switch-science.com/2016/12/19/chibibit_release/より抜粋
chibi:bitは、イギリスの教育向けマイコンボード「BBC micro:bit」の互換機です。
chibi:bitには工事設計認証(いわゆる技適)付きのBLEモジュールを搭載しており、日本国内で使用することができます。
(BBC micro:bitには技適が無いため、日本国内では使用できません)

micro:bitとは?

BBCが子供のプログラミング教育用のボードです。
ScratchのようなGUIのほか、pythonやjs、C++での開発が可能です。
C++の場合はchibi:bitやmicro:bitのIDEではなく、mbedのIDEを使います。
以下の写真の左から本家BBC micro:bit、chibi:bit、chibi:bitテスト版です。
IMG_1680.JPG

http://microbit.org
https://www.infoq.com/jp/news/2015/07/bbc-microbit

Bluetooth Button Serviceを検知する

chibi:bitには専用のIDE環境が用意してありますので、
それを使って以下のようなコードを描いてみました。
http://chibibit.io/ide/

新規プロジェクトではメニューにBluetoothが表示されていませんので、メニューの下の+ボタンからBluetoothを選択して追加しましょう。
out 2.png

この時、BluetoothとRadioのどちらかを削除することになるのでRadioを削除してください。
Screen Shot 2016-12-25 at 19.29.06.png

そして、メニューから以下のようなコードを描いてみました。
起動時のメッセージ、接続時の表示、接続時のButtonServiceの開始、切断時の表示について記載してあります。
Screen Shot 2016-12-25 at 19.16.26.png

chibi:bitへのインストール

chibi:bitはPCに接続すると外部ストレージとして認識されるかと思います。IDEの「Download」ボタンで落ちてくるファイルをドラッグ&ドロップでコピーしてもらえばインストールは完了です。

iOS側のサンプルコード

iOS側はセントラルとしてペリフェラルであるchibi:bitに接続します。

接続するために必要なUUIDの情報はMicroBitServiceにEnumでまとめて定義して起きました。

import Foundation
import CoreBluetooth

enum MicroBitService {
    case button
    case accelerometer
    case ioPin
    case led
    case magnetometer
    case temperature
    case uart
    case eventService


    func uuid() -> CBUUID {
        switch self {
        case .button:
            return CBUUID(string: "E95D9882-251D-470A-A062-FA1922DFA9A8")
        case .accelerometer:
            return CBUUID(string: "E95D0753-251D-470A-A062-FA1922DFA9A8")
        case .ioPin:
            return CBUUID(string: "E95D127B-251D-470A-A062-FA1922DFA9A8")
        case .led:
            return CBUUID(string: "E95Dd91D-251D-470A-A062-FA1922DFA9A8")
        case .magnetometer:
            return CBUUID(string: "E95DF2D8-251D-470A-A062-FA1922DFA9A8")
        case .temperature:
            return CBUUID(string: "E95D6100-251D-470A-A062-FA1922DFA9A8")
        case .uart:
            return CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
        case .eventService:
            return CBUUID(string: "")
        }
    }

    func characteristics() -> [CBUUID] {
        switch self {
        case .button:
            return [
                ButtonCharacteristic.button1State.uuid(),
                ButtonCharacteristic.button2State.uuid()
            ]
        case .accelerometer:
            return [
                AccelerometerCharacteristic.data.uuid(),
                AccelerometerCharacteristic.period.uuid(),
            ]
        case .ioPin:
            return [
                IoPinCharacteristic.pinData.uuid(),
                IoPinCharacteristic.pinAdConfiguration.uuid(),
                IoPinCharacteristic.pinIoConfiguration.uuid()
            ]
        case .led:
            return [
                LedCharacteristic.matrixState.uuid(),
                LedCharacteristic.text.uuid(),
                LedCharacteristic.scrollingDelay.uuid()
            ]
        case .magnetometer:
            return [
                MagnetometerCharacteristic.data.uuid(),
                MagnetometerCharacteristic.period.uuid(),
                MagnetometerCharacteristic.bearing.uuid()
            ]
        case .temperature:
            return [
                TemperatureCharacteristic.temperature.uuid()
            ]
        case .uart:
            return [
                UartCharacteristic.rx.uuid(),
                UartCharacteristic.tx.uuid()
            ]
        case .eventService:
            return [
            ]
        }
    }

    private enum AccelerometerCharacteristic: String {
        case data   = "E95DCA4B-251D-470A-A062-FA1922DFA9A8"
        case period = "E95DFB24-251D-470A-A062-FA1922DFA9A8"

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }

    private enum ButtonCharacteristic: String {
        case button1State = "E95DDA90-251D-470A-A062-FA1922DFA9A8"
        case button2State = "E95DDA91-251D-470A-A062-FA1922DFA9A8"

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }

    private enum IoPinCharacteristic: String {
        case pinData            = "E95D8D00-251D-470A-A062-FA1922DFA9A8"
        case pinAdConfiguration = "E95DDA90-251D-470A-A062-FA1922DFA9A8"
        case pinIoConfiguration = "E95DDA91-251D-470A-A062-FA1922DFA9A8"

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }

    private enum LedCharacteristic: String {
        case matrixState    = "E95D7b77-251D-470A-A062-FA1922DFA9A8"
        case text           = "E95D93EE-251D-470A-A062-FA1922DFA9A8"
        case scrollingDelay = "E95D0d2d-251D-470A-A062-FA1922DFA9A8"

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }

    private enum MagnetometerCharacteristic: String {
        case data    = "E95Dfb11-251D-470A-A062-FA1922DFA9A8"
        case period  = "E95D386C-251D-470A-A062-FA1922DFA9A8"
        case bearing = "E95D9715-251D-470A-A062-FA1922DFA9A8"

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }

    private enum TemperatureCharacteristic: String {
        case temperature = "E95D9250-251D-470A-A062-FA1922DFA9A8"

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }

    private enum UartCharacteristic: String {
        case rx = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
        case tx = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }

    private enum EventServiceCharacteristic: String {
        case microbitRequirements = "E95DB84C-251D-470A-A062-FA1922DFA9A8";
        case microbitEvent        = "E95D9775-251D-470A-A062-FA1922DFA9A8";
        case clientRequirements   = "E95D23C4-251D-470A-A062-FA1922DFA9A8";
        case clientEvent          = "E95D5404-251D-470A-A062-FA1922DFA9A8";

        func uuid() -> CBUUID {
            return CBUUID(string: self.rawValue)
        }
    }
}

今回はサンプルなので雑に各種デリゲートをViewControllerにつけました。

import UIKit
import CoreBluetooth

class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
    let manager: CBCentralManager = CBCentralManager()
    var peripherals: [CBPeripheral] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        manager.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func tappedScan(_ sender: Any) {
        if manager.isScanning {
            debugPrint("stop scan")
            manager.stopScan()
        } else {
            debugPrint("scan start")
            manager.scanForPeripherals(withServices: nil, options: nil)
        }
    }

    @IBAction func tappedDisconnect(_ sender: Any) {
        peripherals.forEach { (peripheral) in
            manager.cancelPeripheralConnection(peripheral)
        }
    }

    // MARK: - CBCentralManagerDelegate
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        debugPrint(#function)
        var stateString = ""
        switch central.state {
        case .poweredOff:
            stateString = "powerOff"
        case .poweredOn:
            stateString = "powerOn"
        case .resetting:
            stateString = "resetting"
        case .unauthorized:
            stateString = "unauthorized"
        case .unknown:
            stateString = "unknown"
        case .unsupported:
            stateString = "unsupported"
        }
        debugPrint("central state: \(stateString)")
    }
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        debugPrint("\(#function), peripheral: \(peripheral.name)")
        peripheral.discoverServices([MicroBitService.button.uuid()])
//        peripheral.discoverServices(nil)
    }
    func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
        debugPrint(#function)
    }
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        debugPrint("\(#function), pripheral: \(peripheral.name)")
    }
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        debugPrint("\(#function), peripheral: \(peripheral.name)")
    }
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//        debugPrint(#function)
        if peripheral.name?.range(of: "micro:bit") != nil{
            debugPrint("didDiscover, peripheral: \(peripheral.name)")
            if peripherals.filter({ (keepedPeripheral) -> Bool in
                return keepedPeripheral.identifier == peripheral.identifier ? true : false
            }).count == 0 {
                peripherals.append(peripheral)
                peripheral.delegate = self
            }
            manager.connect(peripheral, options: nil)
        }
    }

    // MARK: - CBPeripheralDelegate
    func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
        debugPrint(#function)
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        debugPrint(#function)
        peripheral.services?.forEach({ (service) in
            debugPrint("ServiceUUID: \(service.uuid)")
            peripheral.discoverCharacteristics(MicroBitService.button.characteristics(), for: service)
//            peripheral.discoverCharacteristics(nil, for: service)
        })
    }

    func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
        debugPrint(#function)
    }

    func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
        debugPrint(#function)
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
        debugPrint(#function)
    }
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
        debugPrint(#function)
    }
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        debugPrint(#function)
        service.characteristics?.forEach({ (characteristic) in
            debugPrint("CharacteristicUUID: \(characteristic.uuid)")
            peripheral.setNotifyValue(true, for: characteristic)
//            peripheral.readValue(for: characteristic)
        })
    }
    func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error?) {
        debugPrint(#function)
    }
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        debugPrint(#function)
    }
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        debugPrint("\(#function): \(characteristic.value)")
    }
    func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
        debugPrint(#function)
    }
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        debugPrint(#function)
    }
}

サンプルなので画面はかなり手抜きでこんな感じです。
Screen Shot 2016-12-25 at 19.42.54.png

Scanを開始するとButtonServiceからButtonCharacteristicを検索し、変更検知するためにsetNotifyValueを実行します。
無事接続が完了したらAボタンを押してXcodeのコンソールログに変更検知のログが出てることを確認してください。

※私の環境ではBボタンのCharacteristicが検出されず、Bボタンを押した時のイベントを検知できていません。原因を調査中です。

Tips?

稀にchibi:bitがいくらScanしても見つからないなどの現象が発生します。今のところ私の方でわかってる方法としてcibhi:bitをペアリングモードにし、micro:bitアプリでペアリングを試みることです。
iOSのBluetoothからchibi:bitはデバイス登録から解除しておいてください。

最後に

今回はButtonServiceの検知を試してみましたが、
同様にAccelerometerやLED,Temperatureなどの値も同様の方法で読めるかと思います(まだ試してない...)
iOSでCoreBluetoothを試す際、iOS同士のほか、Konashiのようなボードがよく使われているのを目にしますが、cibhi:bitはKonashiよりも若干安いため今後CoreBluetoothを勉強するにはオススメのツールかもしれません
https://www.switch-science.com/catalog/2900/

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.