LoginSignup
9
6

More than 5 years have passed since last update.

chibi:bitにCoreBluetoothで接続する

Last updated at Posted at 2016-12-25

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/

9
6
1

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