LoginSignup
10
13

More than 5 years have passed since last update.

[備忘録]micro:bitとiphoneをBLEで繋げてみる

Last updated at Posted at 2017-12-17

忘れるのでメモ

[前提]
・mpythonはスペックが上がらない限り、BLEのAPIを提供していないっぽい。
(16k中、12k使うかららしい)
・Javascript環境が提供するAPIはサービス?を自分で作れないっぽい。
(と、思う。。。)
・mbedはBLEのライブラリのコンパイルがうまく行かなかった

なので、micro:bit側は、aruduinoで実装することにした。
mbedは、そのうち、なんとかする。。。

参考ページ:

ほぼ、ここに、環境構築から接続まで書いてある。
すばらしい。。。

でも、忘備録なので、Aruduinoでの開発環境の構築を記載する。

[micro:bitのAruduino開発環境の構築]

1.AruduinoのIDEを起動し、環境設定を開き、「追加のボードマネージャーのURL」に以下追加。

1.png

2.環境設定で、「スケッチの保存場所」を覚えておく。

3.「ツール」→「ボード」→「ボードマネージャー」を開き
nRF5 で検索。Nordic Semiconductor nRF5 based boardsをインストール。

2.png

4.「ツール」にて、ボード選択
「ボード」   :「BBC Micro:bit」
3.png

5.すぐにLEDをためせるので、前述、参考ページの人のライブラリを導入。(とても助かる!)
①以下、URLから、ダウンロードし解凍。
https://github.com/ht-deko/microbit_Screen

②フォルダごと、「スケッチの保存場所」下のLibrariesフォルダにコピー
4.png

6.BLE接続環境の導入
(BLEを使わないなら要らない。今回はBLEを使う目的なので以下実行)
①2.のスケッチの保存場所下に、

「tools/nRF5FlashSoftDevice/tool」

フォルダを作成。

②以下のURLから、「nRF5FlashSoftDevice.jar」をダウンロードする。
https://github.com/sandeepmistry/arduino-nRF5/

ダウンロード後、①で作成したフォルダにコピー後、AruduinoIDEを再起動する。
5.png

③「ツール」にて、以下選択。
「SoftDevice」:S310
「書き込み装置」:CMSIS-DAP

6.png

④「ツール」にて、「nRF5 Flash SoftDevice」 を選択。
なんか聞かれる(ハズ。事後で、キャプチャがない。。。)ので「accept」し、
IDEを再起動する。

⑤以下URLからBLEのライブラリをダウンロードし、解凍。
https://github.com/sandeepmistry/arduino-BLEPeripheral

フォルダごと、「スケッチの保存場所」下のLibrariesフォルダにコピー

7.png

7.USBでmicro:bitを接続。IDEの「ツール」「シリアルポート」を、
micro:bitにする。

8.png

[Aruduinoでmicro:bitのLEDマトリクスを光らせてみる]

前述の親切な人のライブラリを使って、LEDを光らせてみる。
ソース

#include <microbit_Screen.h>

void setup() {
  // put your setup code here, to run once:
    Serial.begin(9600);
    SCREEN.begin();
    Serial.println("Hello micro:bit");
}

void loop() {
  // put your main code here, to run repeatedly:
    SCREEN.showIcon(IconNames::Happy);
}

実行結果は、こんな感じ。

9.png

10.png

シリアルで文字が送られ、LEDのマトリクスは、ちゃんと、
ニコちゃんマークになっている。

[micro:bitでペリフェラル作成]

[仕様]
セントラルから、テキトーな10文字の文字列を受け取り、LEDマトリクスで表示する。
接続時の認証とかはしない。(やり方をまだ知らない。。。)

1.macのコンソールでuuidgenでテキトーなUUIDを準備
(とりあえず、サービスもキャラクたりスティックも同じ奴を使った。。。)
ex.

uuidgen
ED3A76F6-70B5-4BBE-840D-EAA341F78417

2.ソースを準備。micro:bitに焼く。

ソース

// Import libraries (BLEPeripheral depends on SPI)
#include <SPI.h>
#include <BLEPeripheral.h>
#include <microbit_Screen.h>


BLEPeripheral blePeripheral = BLEPeripheral();

// create service
//uuidgen 
BLEService               ledService           = BLEService("ED3A76F6-70B5-4BBE-840D-EAA341F78417");

char defaultVal[] = {'N', 'O', 'N', 'E', ' ', ' ', ' ', ' ', ' ', 0x00};

BLECharacteristic hogeCharacteristic = BLECharacteristic("ED3A76F6-70B5-4BBE-840D-EAA341F78417", 
                                                          BLERead | BLEWrite, 
                                                          11);
char* receivedBuf = "          ";

void setup() {
  Serial.begin(9600);
  SCREEN.begin();

  blePeripheral.setDeviceName("BBC micro:bit TestA");
  blePeripheral.setLocalName("Micro:bit HogeDevice");

  blePeripheral.setAdvertisedServiceUuid(ledService.uuid());
  hogeCharacteristic.setValue(defaultVal);

  blePeripheral.addAttribute(ledService);
  blePeripheral.addAttribute(hogeCharacteristic);

  // BLE init
  blePeripheral.begin();
  Serial.println(F("BLE HOGE Peripheral"));
}

void loop() {

  // 
  SCREEN.showIcon(IconNames::Chessboard); 

  // Blocking wait...
  BLECentral central = blePeripheral.central();
  if (central) {
    // central connected to peripheral
    Serial.print(F("Connected to central: "));
    Serial.println(central.address());
    while (central.connected()) {

      SCREEN.showIcon(IconNames::Happy);

      if (hogeCharacteristic.written()) {
        // received from central...
        Serial.println("receive data!");
        if (hogeCharacteristic.value()) {
          String msg = String((const char*)hogeCharacteristic.value());
          Serial.println(msg);
          msg.toCharArray(receivedBuf, msg.length());          
          char fuga[10] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
          msg.toCharArray(fuga, msg.length());
          if (hogeCharacteristic.setValue(fuga)) {
            Serial.println("success response write ");
          } else {
            Serial.println("failed response write ");          
          }
          msg.trim();
          SCREEN.showString(msg);
          delay(2000);
        }        
      }      
    }
    Serial.print(F("Disconnected from central: "));
    Serial.println(central.address());
    SCREEN.showIcon(IconNames::Sad);
    delay(2000);    
  }
}


11.png

12.png

準備完了。。。
(交互に光ってるマトリクスはチェスボード。)

[iPhoneでmicro:bitに接続し、文字列を送信]

[仕様]
BLEでmicro:bitに接続し、テキトーな文字列を送信する。
認証は、前述のとーりしない。。。
単純に1画面で実装。
スキャン、接続、切断ボタン、送信ボタン、通信ログを表示テーブルを持つ。
楽なので、swiftで実装した。

//
//  ViewController.swift
//  MicrobitBleSample1
//
//  Created by pies on 2017/12/17.
//  Copyright © 2017年 pies. All rights reserved.
//
import UIKit
import CoreBluetooth

class ViewController: UIViewController,
    CBCentralManagerDelegate,CBPeripheralDelegate,
    UITableViewDataSource, UITableViewDelegate {
    let CMD_LEN = 10
    let hogeServiceUUId = CBUUID(string: "ED3A76F6-70B5-4BBE-840D-EAA341F78417")
    let testDeviceName = "Micro:bit TestDevice Hoge"


    @IBOutlet weak var tblLog: UITableView!

    @IBOutlet weak var btnScan: UIButton!

    @IBOutlet weak var btnConnectDevice: UIButton!

    @IBOutlet weak var btnTestSend: UIButton!

    @IBOutlet weak var btnDisConnectDevice: UIButton!


    var logMsg = [String]()

    var cm: CBCentralManager!
    var hogeDevice: CBPeripheral?
    var hogeService: CBService?
    var hogeChar: CBCharacteristic?

    var selectedCmdIdx: Int = -1

    override func viewDidLoad() {

        super.viewDidLoad()

        btnScan.isEnabled = false
        btnConnectDevice.isEnabled = false
        btnTestSend.isEnabled = false
        btnDisConnectDevice.isEnabled = false

        tblLog.dataSource = self
        tblLog.delegate = self
        // ble初期化
        cm = CBCentralManager(delegate: self, queue:nil)

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }

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

    @IBAction func tapBtnScan(_ sender: Any) {

        if !cm.isScanning {
            // BLEデバイスの検出を開始.
            cm.scanForPeripherals(withServices: [hogeServiceUUId], options: nil)
            btnScan.isEnabled = false
            btnConnectDevice.isEnabled = false
            hogeDevice = nil
            logMsg.append("スキャン開始")
            reloadAndLast()
        }
    }

    // micro:bitと接続
    @IBAction func tapBtnConnectDevice(_ sender: Any) {

        if let tgtDevice = hogeDevice {
            btnConnectDevice.isEnabled = false
            logMsg.append("接続開始")
            cm.connect(tgtDevice, options: nil)
            reloadAndLast()
        }
    }

    // テキトー送信
    @IBAction func tapBtnTestSend(_ sender: Any) {
        if let tgtDevice = hogeDevice {
            if hogeService != nil && hogeChar != nil {
                tgtDevice.writeValue(toCmd("hoge"), for: hogeChar!, type: CBCharacteristicWriteType.withResponse)
                logMsg.append("送信:hoge")
                reloadAndLast()
                hogeDevice?.readValue(for: hogeChar!)
            }
        }
    }


    // 固定長にしてbyte array化
    func toCmd(_ cmd: String) -> Data {
        var buf = "\(cmd)"
        while buf.utf8.count < CMD_LEN {
            buf = "\(buf) "
        }
        print("[\(buf)]")
        var arr = [UInt8]()
        for c in buf.utf8 {
            arr.append(c)
        }
        // 相手はC言語の世界なのでケツにnull。。。
        arr.append(0x0)
        return Data(bytes: arr)
    }

    @IBAction func tapBtnDisconnect(_ sender: Any) {
        btnTestSend.isEnabled = false
        if hogeDevice != nil {
            btnDisConnectDevice.isEnabled = false
            cm.cancelPeripheralConnection(hogeDevice!)
            logMsg.append("切断実行")
            reloadAndLast()
        }
    }


    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return logMsg.count
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80.0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CELL_LOG")!
        let lblLog = cell.contentView.viewWithTag(1001) as! UILabel
        lblLog.text = logMsg[indexPath.item]
        return cell
    }

    func reloadAndLast() {
        tblLog.reloadData()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [unowned self] in
            let idx = IndexPath(item: self.logMsg.count - 1, section: 0)
            self.tblLog.scrollToRow(at: idx, at:.bottom, animated: true)
        }
    }



    func centralManagerDidUpdateState(_ central: CBCentralManager) {        
        print("state \(central.state)")
        switch (central.state) {
        case .poweredOff:
            print("Bluetoothの電源がOff")
            logMsg.append("Bluetooth:電源Off")
            btnScan.isEnabled = false
            btnConnectDevice.isEnabled = false
        case .poweredOn:
            print("Bluetoothの電源はOn")
            logMsg.append("Bluetooth:電源On")

            if !btnScan.isEnabled && !cm.isScanning {
                btnScan.isEnabled = true
            }
        case .resetting:
            print("レスティング状態")
            logMsg.append("Bluetooth:レスティング状態")
        case .unauthorized:
            print("非認証状態")
            logMsg.append("Bluetooth:非認証状態")
        case .unknown:
            print("不明")
            logMsg.append("Bluetooth:不明")
        case .unsupported:
            print("非対応")
            logMsg.append("Bluetooth:非対応")

        }
        tblLog.reloadData()
    }



    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {

        if let dName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {

            print("device:\(dName)")

            // デバイス名確認
            if dName.hasPrefix("Micro:bit HogeDevice") {
                // サービス確認
                if let srvIds = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [Any] {
                    if srvIds.filter({$0 as? CBUUID == hogeServiceUUId}).count > 0 {
                        // hoge service確認
                        cm.stopScan()
                        logMsg.append("Micor:bit確認。スキャン停止")
                        print(CBAdvertisementDataServiceUUIDsKey)
                        logMsg.append("device:\(dName)\nuuid:\(peripheral.identifier)")
                        tblLog.reloadData()
                        self.hogeDevice = peripheral
                        btnScan.isEnabled = true
                        btnConnectDevice.isEnabled = true
                    } else {
                        logMsg.append("Micor:bit確認。hogeサービスなし")
                    }
                }
            }
        }
        reloadAndLast()


        // peripheral.

        //        cm.stopScan()
    }

    // peripheralと接続成功
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {

        print("デバイスと接続成功")
        logMsg.append("デバイスと接続成功")

        // デバイスのdelegate設定
        peripheral.delegate = self

        // サービスのUUIDを確認
        peripheral.discoverServices([hogeServiceUUId])

        reloadAndLast()
    }

    // peripheralと接続失敗
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {

        print("デバイスと接続失敗,\ncause:\(error)")
        logMsg.append("デバイスと接続失敗")
        //logMsg.append("error:\(error)")

        btnConnectDevice.isEnabled = true
        btnTestSend.isEnabled = false
        reloadAndLast()

    }
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {

        btnTestSend.isEnabled = false
        btnDisConnectDevice.isEnabled = false
        print("デバイス切断")
        logMsg.append("デバイス切断")
        if error == nil {
            btnConnectDevice.isEnabled = true
        } else {
            print("cause:\(error!)")
        }
        reloadAndLast()
    }

    // peripheralサービス検索結果
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {

        if error != nil {
            // サービス確認失敗
            logMsg.append("サービス確認失敗")
            print("サービス確認失敗\ncause:\(error!)")
        } else {
            // サービス確認成功
            logMsg.append("サービス確認成功")
            logMsg.append("Characteristics確認実行")
            btnTestSend.isEnabled = true
            btnDisConnectDevice.isEnabled = true

            hogeService = peripheral.services?.filter({$0.uuid == hogeServiceUUId})[0]
            peripheral.discoverCharacteristics([hogeServiceUUId], for: hogeService!)
        }

        reloadAndLast()
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if error != nil {
            logMsg.append("Characteristics確認:エラー")
            print("Characteristics確認:エラー")
            print("cause:\(error!)")
        } else {
            logMsg.append("Characteristics確認:成功")
            hogeChar = service.characteristics!.filter({$0.uuid == hogeServiceUUId})[0]
            logMsg.append("Characteristics読込リクエスト")
            peripheral.readValue(for: hogeChar!)
            btnTestSend.isEnabled = true
        }
        reloadAndLast()
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            logMsg.append("Characteristics読込:エラー")
            print("Characteristics読込:エラー")
            print("cause:\(error!)")


        } else {
            logMsg.append("Characteristics読込:成功")
            print("Characteristics:\(characteristic.value)")
            if let data = characteristic.value {
                let str = String(data: data, encoding: .utf8)
                print("receive:\(str)")
            }
        }
        reloadAndLast()
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {

            print(error!)

        } else {
            if let data = characteristic.value {
                let str = String(data: data, encoding: .utf8)
                print("receive:\(str)")
            }
        }
    }
}


一応、ソースはここに置きました。。。
https://bitbucket.org/cyclon2joker/microbitblesample1

ビルドし、実機で実行してみる。

13_0.png

スキャンをタップし、micro:bitを検出しているのを確認。

13.png

接続し、ログをみる。
14.png

キャラクタリスティックまで読んでいる。

で、micro:bitは、

15.png

ニコちゃんマーク(接続したらHappyに設定。)

送信ボタンをタップ。

16.png

。。。
わかりにくいが(。。。)、「hoge」という文字が、
LEDマトリクスにスクロールしている。

17.png

シリアルにも流れてます。。。

切断する。

18.png

19.png

無事、一通りの動作完了。

[まとめ、感想]
arudionoで焼けることによって、かなり、扱いやすいデバイスだなーと思う。。。
最初からLEDマトリクスもセンサーもついてるし、それでいて安いし、
かなり、楽しめるオモチャだと思う。

子供の時にこんな面白いものが、タダでもらえるなんて、イギリスの子供は
めぐまれてるなーと。。。

[TODO]
・iBeaconを作ってみる。
・kotolinで接続してみる。
・mbedでビルド出来るようにする。
・モータを制御する拡張ボードを買ってなんかする。

10
13
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
10
13