chibi:bitにCoreBluetoothで接続する
chibi:bitがスイッチサイエンスから正式に発売されたので
iOSのCoreBluetoothを使って連携を試みてみました。
#chibi:bitとは?
以下はスイッチサイエンスのページからの抜粋です。
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テスト版です。
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を選択して追加しましょう。
この時、BluetoothとRadioのどちらかを削除することになるのでRadioを削除してください。
そして、メニューから以下のようなコードを描いてみました。
起動時のメッセージ、接続時の表示、接続時のButtonServiceの開始、切断時の表示について記載してあります。
##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)
}
}
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/