LoginSignup
7
4

More than 3 years have passed since last update.

macOS × Adafruit Feather 32u4でBLE GATTやる【決定版】

Last updated at Posted at 2019-01-05

背景

Arduino互換機でBLE通信楽してやりたい!皆もそうだよね?
Arduino IDEで書ける互換機マイコンAdafruit Feather 32u4 Bluefruit LEをご存知でしょうか? こいつはBLEモジュールが載ったワンボードマイコンです(他にもBLE NanoとかESP32とかあるよ).
今回はMacとこのAdafruit Feather 32u4でGATTやる方法についての備忘録.
Custom Serviceを登録してRead専門のCustom Characteristicを追加する例を載せます.

環境

PC: MacBook Pro, macOS Mojave,セントラル
IDE: Xcode10.1, Swift4

マイコン: Adafruit Feather 32u4 Bluefruit LEペリフェラル
IDE: Arduino IDE

現状,macOS Catalinaでは動かないらしいです!

仮の設定

Item UUID
Service FF02AC5B-32A0-0CDD-DB39-5D3AB4336C6D
Characteristic FF0234A7-32A0-0CDD-DB39-5D3AB4336C6D

Adafruit Feather 32u4サイドの実装

Arduino IDEの設定

  1. Arduino IDEの環境設定を開いて,追加のボードマネージャのURLhttps://adafruit.github.io/arduino-board-index/package_adafruit_index.jsonを追加.

ボードマネージャ.png

2. [ツール]→[ボード]→[ボードマネージャ]でAdafruit AVR Boardsをインストール
3. [ツール]→[ボード]でAdafruit Feather 32u4を追加
4. MacにAdafruit Feather 32u4を繋いでシリアルポートを選択
5. Adafruit_BluefruitLE_nRF51から最新版のライブラリを丸ごとダウンロードしてきて,Macの/Users/user_name/Documents/Arduino/libraries/内に配置

ソースコード例

定数宣言のヘッダファイル

BlueFruitConfig.h
#define BUFSIZE                        128   // Size of the read buffer for incoming data
#define VERBOSE_MODE                   true  // If set to 'true' enables debug output

#define BLUEFRUIT_SWUART_RXD_PIN       9    // Required for software serial!
#define BLUEFRUIT_SWUART_TXD_PIN       10   // Required for software serial!
#define BLUEFRUIT_UART_CTS_PIN         11   // Required for software serial!
#define BLUEFRUIT_UART_RTS_PIN         8    // Optional, set to -1 if unused

#ifdef Serial1    // this makes it not complain on compilation if there's no Serial1
  #define BLUEFRUIT_HWSERIAL_NAME      Serial1
#endif

#define BLUEFRUIT_UART_MODE_PIN        12    // Set to -1 if unused

#define BLUEFRUIT_SPI_CS               8
#define BLUEFRUIT_SPI_IRQ              7
#define BLUEFRUIT_SPI_RST              4    // Optional but recommended, set to -1 if unused

#define BLUEFRUIT_SPI_SCK              13
#define BLUEFRUIT_SPI_MISO             12
#define BLUEFRUIT_SPI_MOSI             11

メインのソースコード

custom_service.ino
#include <Arduino.h>
#include <SPI.h>
#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_SPI.h"
#include "Adafruit_BluefruitLE_UART.h"
#include "Adafruit_BLEGatt.h"

#include "BluefruitConfig.h"

#if SOFTWARE_SERIAL_AVAILABLE
  #include <SoftwareSerial.h>
#endif

Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);

Adafruit_BLEGatt gatt(ble);

void error(const __FlashStringHelper*err) {
  Serial.println(err);
  while (1);
}

int32_t serviceId;
int32_t characterId;

void setup(void) {
  randomSeed(micros());

  while (!Serial); // required for Flora & Micro
  delay(500);

  Serial.begin(115200);
  Serial.println(F("Adafruit Bluefruit Device Information Service (DIS) Example"));
  Serial.println(F("---------------------------------------------------"));

  Serial.print(F("Initialising the Bluefruit LE module: "));

  if (!ble.begin(VERBOSE_MODE)) {
    error(F("Couldn't find Bluefruit, make sure it's in Command mode & check wiring?"));
  }
  Serial.println(F("OK!"));

  Serial.println(F("Performing a factory reset: "));
  if (!ble.factoryReset()){
       error(F("Couldn't factory reset"));
  }

  ble.echo(false);

  Serial.println(F("Setting device name to 'Bluefruit': "));
  if (!ble.sendCommandCheckOK(F("AT+GAPDEVNAME=Bluefruit"))) {
    error(F("Could not set device name?"));
  }

  // Serial.println(F("Adding the Device Information Service definition (UUID = FF02AC5B-32A0-0CDD-DB39-5D3AB4336C6D): "));
  // uint8_t service_uuid128[] = {0xFF,0x02,0xAC,0x5B,0x32,0xA0,0x0C,0xDD,0xDB,0x39,0x5D,0x3A,0xB4,0x33,0x6C,0x6D};
  // serviceId = gatt.addService(service_uuid128);
  // if (serviceId == 0) {
  //   error(F("Could not add Device Information Service"));
  // }
  //
  // Serial.println(F("Adding the Custom Characteristic (UUID = 0x0002): "));
  // // uint8_t character_uuid128[] = {0xFF,0x02,0x34,0xA7,0x32,0xA0,0x0C,0xDD,0xDB,0x39,0x5D,0x3A,0xB4,0x33,0x6C,0x6D};
  // characterId = gatt.addCharacteristic(0x0002, GATT_CHARS_PROPERTIES_READ, 1, 20, BLE_DATATYPE_BYTEARRAY);
  // if (characterId == 0) {
  //   error(F("Could not add Custom Characteristic"));
  // }
  //
  //
  boolean success;
  Serial.println(F("Adding the Custom Service: "));
  success = ble.sendCommandWithIntReply(F("AT+GATTADDSERVICE=UUID128=FF-02-AC-5B-32-A0-0C-DD-DB-39-5D-3A-B4-33-6C-6D"), &serviceId);
  if (!success) {
    error(F("Could not add Custom Service"));
  }

  // 0x02 : Read
  // 0x04 : Write without a response
  // 0x08 : Write with a response
  // 0x10 : Notifications
  // 0x20 : ??
  Serial.println(F("Adding the Custom Characteristic: "));
  success = ble.sendCommandWithIntReply(F("AT+GATTADDCHAR=UUID128=FF-02-34-A7-32-A0-0C-DD-DB-39-5D-3A-B4-33-6C-6D,PROPERTIES=0x02,MIN_LEN=5, MAX_LEN=5, VALUE=00-00-00-00-00"), &characterId);
  if (!success) {
    error(F("Could not add Custom Characteristic"));
  }

  Serial.print(F("Adding Device Information Service UUID to the advertising payload: "));
  uint8_t advdata[] = {0x02, 0x01, 0x06, 0x05, 0x02, 0x09, 0x18, 0x0a, 0x18};
  ble.setAdvData(advdata, sizeof(advdata));
  ble.sendCommandCheckOK(F("AT+GAPSETADVDATA=02-01-06-05-02-0d-18-0a-18"));

  Serial.print(F("Performing a SW reset (service changes require a reset): "));
  ble.reset();

  Serial.println();
}

void loop(void) {
  uint8_t values[5] = {0,0,0,0,0};
  for (int j=0; j<5; j++)  {
    values[j] = (uint8_t)random(1, 255);
  }
  gatt.setChar(characterId, values, 5);
  delay(1000);
}

↑Serial通信周りはデバッグ時以外は必要ありません.

Macサイドの実装

プロジェクトの設定

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

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

ソースコード例

BLE周りの実装

BLEManager.swift
import Foundation
import CoreBluetooth

let service_uuid: String = "FF02AC5B-32A0-0CDD-DB39-5D3AB4336C6D"
let character_uuid: String = "FF0234A7-32A0-0CDD-DB39-5D3AB4336C6D"

protocol BLEManagerDelegate {
    func updateData(_ values: [Int])
}

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)
    }

    //BLEのスキャン開始
    func start() {
        if centralFlag {
            Swift.print("start")
            centralManager.scanForPeripherals(withServices: nil, options: nil)
        }
    }
    //BLE通信終了
    func stop() {
        if peripheral != nil {
            Swift.print("stop")
            timer?.invalidate()
            centralManager.cancelPeripheralConnection(peripheral)
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        //ペリフェラルの名前でデバイスを特定
        if peripheral.name == "Bluefruit" {
            Swift.print("catch")
            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: service_uuid)])
    }

    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 {
                peripheral.discoverCharacteristics([CBUUID(string: character_uuid)], 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
                    //データのRead周期は1秒とした
                    timer = Timer.scheduledTimer(withTimeInterval: 1.0, 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
        }
        //データはIntの配列でゲット
        let data: NSData = characteristic.value! as NSData
        var buffer = [UInt8](repeating: 0, count: data.length)
        data.getBytes(&buffer, length: data.length)
        let values: [Int] = buffer.map { (value) -> Int in
            return Int(value)
        }
        delegate?.updateData(values)
    }

}

AppDelegateの実装

AppDelegate.swift
import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, BLEManagerDelegate {

    private let ble = BLEManager()
    private var mainVC: ViewController? = nil

    static var sharedInstance: AppDelegate {
        return NSApplication.shared.delegate as! AppDelegate
    }

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        ble.delegate = self
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        ble.stop()
    }

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }

    public func startBLE() {
        ble.start()
    }

    public func stopBLE() {
        ble.stop()
    }

    func updateData(_ values: [Int]) {
        Swift.print(values)
    }

}

UI周りの実装

ViewController.swift
import Cocoa

class ViewController: NSViewController {

    let appDelegate = AppDelegate.sharedInstance

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

    override var representedObject: Any? {
        didSet {
        }
    }

    //ボタンといい感じに紐付け
    @IBAction func push(_ sender: NSButton) {
        if sender.tag == 0 {
            appDelegate.startBLE()
        } else {
            appDelegate.stopBLE()
        }
    }

}

動作チェック

とりあえず有線でいいので繋いだ状態で電源供給をし,両方Runして接続してみました.

Xcodeのコンソール
2019-01-06 03:00:52.177745+0900 Adafruit_Feather_32u4[88802:12599203] [default] Unable to load Info.plist exceptions (eGPUOverrides)
start
catch
[131, 99, 224, 182, 43]
[61, 248, 213, 67, 73]
[221, 128, 12, 180, 139]
[133, 143, 6, 216, 93]
[114, 187, 216, 193, 33]
[8, 35, 231, 83, 7]
[8, 110, 243, 19, 58]
[121, 206, 83, 1, 60]
[74, 23, 28, 217, 46]
[199, 138, 15, 2, 130]
[49, 234, 160, 78, 116]

しっかり5つの乱数が1秒ごとに取得できてるっぽいです.

7
4
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
7
4