ESP32でBLE通信、ESP32からiOSでデータ受信

BLE通信でESP32のデータをiOSで受信する
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は次のような関係になります。混乱しないよう気をつけてください。

iOSESP32
セントラルペリフェラル
マスタスレイブ
クライアントサーバ

ESP32のUUIDをそれぞれ次のように決めました。

項目
SERVICE UUID4fafc201-1fb5-459e-8fcc-c5c9c331914b
CHARACTERISTIC UUIDbeb5483e-36e1-4688-b7f5-ea07361b26a8

これらの値はESP32のサンプルの値を使用しています。必要あればこちらのジェネレータでUUIDを生成して書き換えてください。

BLE通信でESP32のデータをiOSで受信するプログラム

概要

BLE通信でESP32のデータをiOSで受信
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などで何かつまずいた場合に役立ちますので入れておくと便利と思います。

BLE Scanner

Bluepixel Technologies LLP

Download on theApp Store Get it on Google Play
記事に関するご質問などがあればTwitterへお返事ください。
この記事で紹介した商品
ESP32搭載ボード
ESP32の書籍
Arduinoで人気の周辺パーツ
関連記事