ESP32でBLE通信、ESP32からiOSでデータ受信
今回はBLE通信を使って、iOS端末でESP32から送られたデータを受信します。これが実現できれば、ESP32に繋げられたセンサの値などをiOSで受信し管理できるようになります。BLEはWiFiよりも消費電力が圧倒的に少なくて済む通信方法です。ESP32などの小型マイコンボードとの相性もとても良いのでぜひ挑戦してみてください。
▼ Bluetooth通信がはじめてな方は、 はじめてのBLE通信、iOSからESP32のLチカ をお読みになるのをおすすめします。
▼ ESP32同士での通信でしたら「ESP-NOW」という通信方法もあります。ペアリングの必要がないので、手軽に相互通信ができます。
はじめに
本記事を書くにあたって参考になったBluetooth関連の本を紹介します。
前回のおさらい
はじめに前回のおさらいです。BLE通信をはじめるには、アドバタイジングで目的のサービスを持つペリフェラルを見つけ、キャラクタリスティックを取得する必要がありました。よってペリフェラルのサービスUUIDとキャラクタリスティックUUIDの2つ以上が必要になります。
前回は、iOSからESP32へデータを送信してLチカを実現させました。よってキャラクタリスティックの属性にはWriteが必要でした。 今回は、ESP32のデータをiOSで受信したいのでNotify属性を使ってデータを受信します。
ところで、この記事で扱うiOSとESP32は次のような関係になります。混乱しないよう気をつけてください。
iOS | ESP32 |
---|---|
セントラル | ペリフェラル |
マスタ | スレイブ |
クライアント | サーバ |
ESP32のUUIDをそれぞれ次のように決めました。
項目 | 値 |
---|---|
SERVICE UUID | 4fafc201-1fb5-459e-8fcc-c5c9c331914b |
CHARACTERISTIC UUID | beb5483e-36e1-4688-b7f5-ea07361b26a8 |
これらの値はESP32のサンプルの値を使用してます。必要あればこちらのジェネレータでUUIDを生成して書き換えてください。 Online UUID Generator Tool
BLE通信でESP32のデータをiOSで受信するプログラム
概要
ESP32のGPIO32へ可変抵抗を取り付け、読み取ったアナログ値をiOSでBLE経由で受信するプログラムを作っていきます。
可変抵抗は両側をGNDと3.3Vに接続し、真ん中の端子をGPIOへ繋いでください。BLE通信を開始したらNotify通知を設定し、抵抗値をiOSで受け取りUILabeで表示させます。
ESP32(ペリフェラル)
ESP32(ペリフェラル)のArduinoで書いたプログラムです。前回のBluetooth Lチカとそれほど変わりません。ESP32でデータを通知するにはキャラクタリスティックにデータをセットして通知します。
pCharacteristic->setValue(str);
pCharacteristic->notify();
なお、ここでは文字列をセットしましたが、int型などもセットできるようです。
/*
sendData2Iphone.ino
https://101010.fun
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#define TOUCH_PIN 32
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic* pCharacteristic;
void sendMessage(int v) {
char str[30];
sprintf(str, "%d", v);
Serial.println(str);
pCharacteristic->setValue(str);
pCharacteristic->notify();
}
void setup() {
Serial.begin(115200);
pinMode(TOUCH_PIN, INPUT_PULLUP); // 内蔵抵抗でプルアップまたは10kΩの抵抗でプルアップ
BLEDevice::init("ESP32 Bluetooth Server");
BLEServer* pServer = BLEDevice::createServer();
BLEService* pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
pService->start();
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // iPhone接続の問題に役立つ
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
void loop() {
int val = analogRead(TOUCH_PIN);
Serial.println(val);
sendMessage(val);
delay(10);
}
iOS(セントラル)
基本的には前回のLチカプログラムと変わりません。今回はNotifyですのでキャラクタリスティックが一致したら、しっかり通知登録をしてあげます。
peripheral.setNotifyValue(true, for: kRXCBCharacteristic)
すると CBPeripheralDelegate のデリゲートメソッド func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) でデータを受信できるはずです。
//
// ReciveViewController.swift
//
// Created by 101010.fun Arai on 2021/07/14.
//
import UIKit
import CoreBluetooth
class ReciveViewController: UIViewController {
@IBOutlet weak var counterLabel:UILabel!
let kUARTServiceUUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b" // サービス
let kRXCharacteristicUUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8" // ペリフェラルからの受信用
var centralManager: CBCentralManager!
var peripheral: CBPeripheral!
var serviceUUID : CBUUID!
var kRXCBCharacteristic: CBCharacteristic?
var charcteristicUUIDs: [CBUUID]!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
print("setup...")
centralManager = CBCentralManager()
centralManager.delegate = self as CBCentralManagerDelegate
serviceUUID = CBUUID(string: kUARTServiceUUID)
charcteristicUUIDs = [CBUUID(string: kRXCharacteristicUUID)]
}
}
//MARK : - CBCentralManagerDelegate
extension ReciveViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("CentralManager didUpdateState")
switch central.state {
//電源ONを待って、スキャンする
case CBManagerState.poweredOn:
let services: [CBUUID] = [serviceUUID]
centralManager?.scanForPeripherals(withServices: services,
options: nil)
default:
break
}
}
/// ペリフェラルを発見すると呼ばれる
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber) {
self.peripheral = peripheral
centralManager?.stopScan()
//接続開始
central.connect(peripheral, options: nil)
print(" - centralManager didDiscover")
}
/// 接続されると呼ばれる
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
print(" - centralManager didConnect")
}
/// 切断されると呼ばれる?
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print(#function)
if error != nil {
print(error.debugDescription)
setup() // ペアリングのリトライ
return
}
}
}
//MARK : - CBPeripheralDelegate
extension ReciveViewController: CBPeripheralDelegate {
/// サービス発見時に呼ばれる
func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {
if error != nil {
print(error.debugDescription)
return
}
//キャリアクタリスティク探索開始
if let service = peripheral.services?.first {
print("Searching characteristic...")
peripheral.discoverCharacteristics(charcteristicUUIDs,
for: service)
}
}
/// キャリアクタリスティク発見時に呼ばれる
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if error != nil {
print(error.debugDescription)
return
}
print("service.characteristics.count: \(service.characteristics!.count)")
for characteristics in service.characteristics! {
if(characteristics.uuid == CBUUID(string: kRXCharacteristicUUID)) {
self.kRXCBCharacteristic = characteristics
print("kTXCBCharacteristic did discovered!")
}
}
if(self.kRXCBCharacteristic != nil) {
startReciving()
}
print(" - Characteristic didDiscovered")
}
private func startReciving() {
guard let kRXCBCharacteristic = kRXCBCharacteristic else {
return
}
peripheral.setNotifyValue(true, for: kRXCBCharacteristic)
print("Start monitoring the message from Arduino.\n\n")
}
/// データ送信時に呼ばれる
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print(#function)
if error != nil {
print(error.debugDescription)
return
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
print(#function)
}
/// データ更新時に呼ばれる
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print(#function)
if error != nil {
print(error.debugDescription)
return
}
updateWithData(data: characteristic.value!)
}
private func updateWithData(data : Data) {
if let dataString = String(data: data, encoding: String.Encoding.utf8) {
print(dataString)
counterLabel.text = dataString
}
}
}
今回のプログラムでツマヅイたら、前回のBluetooth Lチカをもう一度見直してみてください。プログラム中で書き換えた箇所はそれほど多くないはずです。
Bluetooth開発に役に立つツール
最後に、Bluetooth開発をする際におすすめのアプリを紹介します。こちらのアプリは、ペリフェラルのサービスやキャラクタリスティック情報をスキャンできるアプリです。UUIDなどで何かつまずいた場合に役立ちますので入れておくと便利です。