BLE通信でESP32のデータをiOSで受信する

BLE通信でESP32のデータをiOSで受信する

今回はBLE通信を使って、iOS端末でESP32から送られたデータを受信してみたいと思います。これが実現できれば、ESP32に繋げられたセンサの値などをiOSで受信し管理できるようになります。BLEはWiFiよりも消費電力が圧倒的に少なくて済む通信方法です。ESP32などの小型マイコンボードとの相性もとても良いのでぜひ挑戦してみてください。

なお、Bluetooth通信がはじめてという方は、前回こちらの記事で行ったBluetooth Lチカからはじめるのをオススメします。

前回のおさらい

はじめに前回のおさらいです。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

BLEの参考書籍

Bluetooth LE入門スマホにつながる低消費電力無線センサの開発をはじめよう
Bluetooth LE入門スマホにつながる低消費電力無線センサの開発をはじめよう
Amazon
iOS×BLE Core Bluetoothプログラミング
iOS×BLE Core Bluetoothプログラミング
Amazon

ESP32搭載ボード

VKLSVAN ESP32 デュアルコア開発ボードモジュール 2.4GHz ワイヤレスWiFi + Bluetooth
VKLSVAN ESP32 デュアルコア開発ボードモジュール 2.4GHz ワイヤレスWiFi + Bluetooth
Amazon
HiLetgo ESP32ワイヤレススティックLora + WiFi + BLE開発ボード LoRaWAN 868 / 915MHz、0.49インチOLEDディスプレイ
HiLetgo ESP32ワイヤレススティックLora + WiFi + BLE開発ボード LoRaWAN 868 / 915MHz、0.49インチOLEDディスプレイ
Amazon
ESPr Developer 32
ESPr Developer 32
Amazon
M5Stack M5StickC Plus ESP32-PICO
M5Stack M5StickC Plus ESP32-PICO
Amazon
M5Stack Core2 開発キットデュアルコア32ビット240Mhz LX6プロセッサー
M5Stack Core2 開発キットデュアルコア32ビット240Mhz LX6プロセッサー
Amazon

ESP32の書籍

ESP32&Arduino 電子工作 プログラミング入門
ESP32&Arduino 電子工作 プログラミング入門
KindleAmazon
IoT開発スタートブック ── ESP32でクラウドにつなげる電子工作をはじめよう!
IoT開発スタートブック ── ESP32でクラウドにつなげる電子工作をはじめよう!
KindleAmazon
超特急Web接続!ESPマイコン・プログラム全集[CD-ROM付き]
超特急Web接続!ESPマイコン・プログラム全集[CD-ROM付き]
Amazon
みんなのM5Stack入門
みんなのM5Stack入門
KindleAmazon

Arduinoで人気の周辺パーツ

サインスマート(SainSmart) 8チャンネル 5V リレーモジュール for Arduino DSP AVR PIC ARM
サインスマート(SainSmart) 8チャンネル 5V リレーモジュール for Arduino DSP AVR PIC ARM
Amazon
KKHMF 10個 TTP223 静電容量式 タッチ スイッチ ボタン セルフロック モジュール 「国内配送」
KKHMF 10個 TTP223 静電容量式 タッチ スイッチ ボタン セルフロック モジュール 「国内配送」
Amazon
DSD TECH 2 PCS OLED 0.91インチディスプレイ  I2C
DSD TECH 2 PCS OLED 0.91インチディスプレイ I2C
Amazon
OSOYOO(オソヨー) DIY センサーモジュール キット
OSOYOO(オソヨー) DIY センサーモジュール キット
Amazon
記事に関するご質問などがあれば、ぜひTwitterへお返事ください。