背景
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の設定
- Arduino IDEの環境設定を開いて,
追加のボードマネージャのURL
にhttps://adafruit.github.io/arduino-board-index/package_adafruit_index.json
を追加.
2. [ツール]→[ボード]→[ボードマネージャ]でAdafruit AVR Boards
をインストール
3. [ツール]→[ボード]でAdafruit Feather 32u4
を追加
4. MacにAdafruit Feather 32u4
を繋いでシリアルポートを選択
5. Adafruit_BluefruitLE_nRF51から最新版のライブラリを丸ごとダウンロードしてきて,Macの/Users/user_name/Documents/Arduino/libraries/
内に配置
ソースコード例
定数宣言のヘッダファイル
# 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
メインのソースコード
# 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サイドの実装
プロジェクトの設定
-
target -> Capabilities -> App Sandbox -> Hardware -> Bluetoothにチェックをいれる
-
info.plist
にPrivacy - Bluetooth Peripheral Usage Description
を追加して何かしらの文章を登録する
ソースコード例
BLE周りの実装
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の実装
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周りの実装
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して接続してみました.
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秒ごとに取得できてるっぽいです.