九州・福岡・東京ときどきIoT

21年間のはてなダイアリー&アメブロからの避難所

スマフォのアプリで、後からWi-Fiの設定をESP32に送るOLED付きプログラム

技術資料: ESP32 Wi-Fi 簡単セットアップシステム

1. 概要:これは何をするもの?

通常、マイコン(ESP32)をWi-Fiに繋ぐには、プログラムの中に直接「IDとパスワード」書き込む必要があります。しかし、それでは場所を移動するたびにプログラムを書き換えなければなりません。

このプログラムは、**スマートフォンのアプリを使って、後からWi-Fiの設定情報をESP32に送り込む」**機能を実装したものです。市販のスマート家電(スマートスピーカーやロボット掃除機など)と同じような初期設定体験を提供します。

さらに、**小型画面(OLED)**を搭載することで、現在何が起きているかを目で見て確認できるようにした、非常に親切な設計になっています。

2. 主な機能と特徴

このプログラムが「有用」である理由は、以下の3つの機能が統合されているからです。

  1. スマホWi-Fi設定(プロビジョニング機能)

    • Bluetoothを使って、スマホからESP32へ安全にWi-Fiパスワードを渡せます。

    • 一度設定すれば、電源を切っても設定を覚えています。

  2. 状況が見える化(ステータス表示機能)

    • Bluetooth待機中」「Wi-Fi接続中」「接続完了(IPアドレス)」などの状態を画面に表示します。

    • 「なぜ繋がらないのか?」という不安を解消します。

  3. カンタン初期化(リセット機能)

    • 物理ボタンを3秒長押しするだけで、Wi-Fi設定を削除して初期状態に戻せます。

    • Wi-Fiルーターを買い替えた時などに便利です。

3. システム構成図(イメージ)

仕組みを簡単な図で表すと以下のようになります。

動作の流れ

 

 

4. 技術的な仕組み

このプログラムがどのように動いているか、専門用語を噛み砕いて解説します。

① プロビジョニング(Provisioning)

  • 意味: 「準備・供給」という意味です。ここでは「Wi-Fi情報をスマホからESP32に供給してあげること」を指します。

  • 技術: WiFiProv という公式の強力なライブラリ(道具箱)を使っています。セキュリティ(暗号化)もしっかりしています。

イベントハンドラ(Event Handler)

  • 役割: 「交通整理係」です。

  • 動作: プログラムの中で「Bluetoothが繋がった!」「パスワードが間違っていた!」「IPアドレスが取れた!」といった**出来事(イベント)**が発生すると、この係が飛んできて、「じゃあ画面にこう表示しよう」「次はこうしよう」と指示を出します。

  • コード内の場所: void SysProvEvent(...) という部分がこれに当たります。

③ 不揮発性メモリ(NVS

  • 役割: 「電源を切っても消えないメモ帳」です。

  • 動作: スマホから受け取ったWi-Fiのパスワードはここに保存されます。次回からは、ここから自動的に読み込んでWi-Fiに接続します。

5. ユーザー操作マニュアル

このデバイスを使用する際の手順です。

ステップ1: 初回起動

  1. 電源を入れると、画面に「Prov(設定待機中)」や「PoP: abcd1234」などが表示されます。

  2. これは「スマホからの連絡を待っている状態」です。

ステップ2: アプリで設定

  1. スマホアプリ「ESP BLE Provisioning」を起動します。

  2. バイスを探し、「PROV_ESP32」を選択します。

  3. 合言葉(PoP)として abcd1234 を入力します。

  4. 自宅のWi-Fiを選び、パスワードを入力して送信します。

ステップ3: 接続完了

  1. 成功すると、ESP32の画面に「Connected!」と表示されます。

  2. IPアドレス(例: 192.168.1.15)も表示され、通信準備完了です。

ステップ4: 設定を消したい時(リセット)

  1. 本体のBOOTボタンを3秒間押し続けます。

  2. 画面に「Resetting...」と出て、工場出荷状態(ステップ1)に戻ります。

6. 開発者向けメモ(設定のポイント)

このプログラムをビルド(書き込み)する際の重要な技術的注意点です。

項目 設定値 理由
Partition Scheme Huge APP (3MB No OTA) Wi-FiBluetoothの両方を使うため、メモリ領域を広く確保する必要があるため。
BLE Handler HANDLER_NONE スマホとの通信が完了する前にBluetoothを切断してしまうバグを防ぐため(コード修正済み)。
セキュリティ Security 1 (PoP使用) 三者に勝手に接続されないよう、パスワード(Proof of Possession)を必須にするため。

 

Amazon.co.jp: YUWENW OLEDディスプレイ 0.96インチ インチ 2個入り モジュール 128X64 液晶パネル OLEDモジュール SSD1306 楕円穴 IIC Arduinoに対応 ホワイト : 産業・研究開発用品

ESP32 Dev Kit C https://amzn.to/4pgjiR7

 

#include <WiFi.h>
#include <WiFiProv.h>
#include <U8g2lib.h>
#include <Wire.h>

// --- OLED設定 ---
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// --- I2Cピン設定 ---
#define I2C_SDA 21
#define I2C_SCL 22

// --- リセットボタンピン ---
#define RESET_BUTTON_PIN 0  // BOOTボタン(GPIO 0)

// --- プロビジョニング設定 ---
const char* service_name = "PROV_ESP32";  // BLEデバイス
const char* service_key = NULL;           // BLEでは不要
const char* pop = "abcd1234";             // Proof of Possession
uint8_t uuid[16] = {0};                   // カスタムUUID(0=デフォルト使用)

// --- 状態変数 ---
bool isProvisioningDone = false;
bool isWiFiConnected = false;
bool isBLEActive = false;

// -----------------------------------------------------
// WiFi設定を完全リセット
// -----------------------------------------------------
void resetWiFiSettings() {
  Serial.println("\n=== Resetting WiFi Settings ===");
  showStatus("Resetting...");
 
  // WiFi完全停止
  WiFi.mode(WIFI_MODE_NULL);
  delay(100);
 
  // NVS消去
  WiFi.mode(WIFI_MODE_STA);
  WiFi.disconnect(true, true);
  delay(1000);
 
  Serial.println("WiFi settings cleared!");
  Serial.println("Restarting ESP32...");
  showStatus("Restarting...");
  delay(2000);
 
  ESP.restart();
}

// -----------------------------------------------------
// 画面表示ヘルパー関数
// -----------------------------------------------------
void showStatus(const char* message) {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_7x13_tf);
 
  const int lineHeight = 14;
  int y = lineHeight;
 
  u8g2.drawStr(0, y, "--- Status ---");
  y += lineHeight;
 
  u8g2.drawStr(0, y, message);
  y += lineHeight;

  y += 2;
  if(isBLEActive) {
    u8g2.drawStr(0, y, "BLE: Active");
    y += lineHeight;
    u8g2.drawStr(0, y, service_name);
  } else if(isWiFiConnected) {
    u8g2.drawStr(0, y, "WiFi: OK");
    y += lineHeight;
    String ip = "IP:" + WiFi.localIP().toString();
    u8g2.drawStr(0, y, ip.c_str());
  } else {
    u8g2.drawStr(0, y, "Waiting...");
  }

  u8g2.sendBuffer();
}

// -----------------------------------------------------
// WiFiプロビジョニングイベントハンドラ
// -----------------------------------------------------
void SysProvEvent(arduino_event_t *sys_event) {
  switch (sys_event->event_id) {
   
    case ARDUINO_EVENT_PROV_INIT:
      Serial.println(">>> Provisioning initialized");
      break;
   
    case ARDUINO_EVENT_PROV_START:
      Serial.println("\n========================================");
      Serial.println(">>> BLE PROVISIONING STARTED!");
      Serial.println("========================================");
      Serial.print("Device Name: ");
      Serial.println(service_name);
      Serial.print("PoP (Proof of Possession): ");
      Serial.println(pop);
      Serial.println();
      Serial.println("Instructions:");
      Serial.println("1. Open 'ESP BLE Provisioning' app");
      Serial.println("2. Tap 'Provision New Device'");
      Serial.println("3. Select 'I don't have a QR code'");
      Serial.println("4. Choose 'Bluetooth'");
      Serial.println("5. Select 'PROV_ESP32'");
      Serial.println("6. Enter PoP: abcd1234");
      Serial.println("7. Select your WiFi network");
      Serial.println("========================================\n");
     
      isBLEActive = true;
      showStatus("BLE Active!");
      break;
   
    case ARDUINO_EVENT_PROV_CRED_RECV:
      Serial.println(">>> Credentials received!");
      Serial.print("    SSID: ");
      Serial.println(WiFi.localIP());
      Serial.print("    RSSI: ");
      Serial.print(WiFi.RSSI());
      Serial.println(" dBm");
     
      isWiFiConnected = true;
      showStatus("Connected!");
      break;
     
    default:
      break;
  }
}

// -----------------------------------------------------
// Setup
// -----------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(1000);
 
  Serial.println("\n\n");
  Serial.println("========================================");
  Serial.println("  ESP32 BLE WiFi Provisioning");
  Serial.println("========================================");
  Serial.println();

  // リセットボタンの設定
  pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);

  // I2C初期化
  Wire.begin(I2C_SDA, I2C_SCL);
 
  // OLED初期化
  if (!u8g2.begin()) {
    Serial.println("ERROR: OLED initialization failed!");
    while(1) delay(1000);
  }
  u8g2.setFlipMode(0);
  showStatus("Booting...");
  delay(1000);
 
  // BOOTボタン長押しでリセット(起動後5秒以内)
  Serial.println("Press BOOT button (3 sec) to reset WiFi");
  showStatus("BOOT=Reset");
 
  unsigned long checkStart = millis();
  while(millis() - checkStart < 5000) {
    if(digitalRead(RESET_BUTTON_PIN) == LOW) {
      unsigned long pressStart = millis();
      while(digitalRead(RESET_BUTTON_PIN) == LOW) {
        if(millis() - pressStart > 3000) {
          Serial.println("\n>>> Reset button pressed!");
          resetWiFiSettings();
        }
        delay(50);
      }
    }
    delay(50);
  }
 
  WiFi.onEvent(SysProvEvent);

  // WiFi診断情報
  Serial.println("--- WiFi Diagnostic Info ---");
  Serial.print("WiFi Mode: ");
  Serial.println(WiFi.getMode());
  Serial.print("Saved SSID: '");
  Serial.print(WiFi.SSID());
  Serial.println("'");
  Serial.print("SSID Length: ");
  Serial.println(WiFi.SSID().length());
  Serial.println("---------------------------\n");

  // 既存の設定があるかチェック
  if (WiFi.getMode() & WIFI_MODE_STA && WiFi.SSID().length() != 0) {
    Serial.println("!!! Found saved WiFi credentials !!!");
    Serial.print("SSID: ");
    Serial.println(WiFi.SSID());
    Serial.println("Press BOOT button to reset (3 sec)");
    showStatus("Old WiFi Found");
   
    // リセットの機会を与える(3秒)
    checkStart = millis();
    while(millis() - checkStart < 3000) {
      if(digitalRead(RESET_BUTTON_PIN) == LOW) {
        unsigned long pressStart = millis();
        while(digitalRead(RESET_BUTTON_PIN) == LOW) {
          if(millis() - pressStart > 3000) {
            resetWiFiSettings();
          }
          delay(50);
        }
      }
      delay(50);
    }
   
    Serial.println("Connecting with saved credentials...");
    showStatus("Connecting WiFi");
    WiFi.begin();
    isProvisioningDone = true;
   
  } else {
    // BLEプロビジョニング開始
    Serial.println("No saved credentials found");
    Serial.println("\n========================================");
    Serial.println("  STARTING BLE PROVISIONING");
    Serial.println("========================================\n");
   
    showStatus("Init BLE...");
    delay(500);
   
    // ★重要: WiFiを完全に無効化
    WiFi.mode(WIFI_MODE_NULL);
    delay(500);
   
    Serial.println("Initializing BLE provisioning...");
   
    // ★重要: NETWORK_PROV_SCHEME_HANDLER_FREE_BTDMを使用
    WiFiProv.beginProvision(
        NETWORK_PROV_SCHEME_BLE,
        NETWORK_PROV_SCHEME_HANDLER_NONE, // ここを変更
        NETWORK_PROV_SECURITY_1,
        pop,
        service_name,
        service_key,
        NULL  // uuidはデフォルトを使用するためNULL推奨
//        uuid
    );
   
    Serial.println(">>> BLE provisioning initiated!");
    Serial.println(">>> Waiting for PROV_START event...\n");
  }
}

// -----------------------------------------------------
// Loop
// -----------------------------------------------------
void loop() {
  // BOOTボタン監視(常時)
  static unsigned long buttonPressStart = 0;
 
  if(digitalRead(RESET_BUTTON_PIN) == LOW) {
    if(buttonPressStart == 0) {
      buttonPressStart = millis();
    } else if(millis() - buttonPressStart > 3000) {
      Serial.println("\n>>> Resetting WiFi (BOOT pressed)...");
      resetWiFiSettings();
    }
  } else {
    buttonPressStart = 0;
  }
 
  // BLE待機中のステータス表示
  if(isBLEActive) {
    static unsigned long lastMsg = 0;
    if(millis() - lastMsg > 10000) {
      Serial.println("\n[BLE] Waiting for smartphone...");
      Serial.print("      Device: ");
      Serial.println(service_name);
      Serial.print("      PoP: ");
      Serial.println(pop);
      Serial.println();
      lastMsg = millis();
    }
  }
 
  // WiFi接続後の更新
  if(isProvisioningDone && isWiFiConnected) {
    static unsigned long lastUpdate = 0;
    if(millis() - lastUpdate > 5000) {
      showStatus("Connected!");
      lastUpdate = millis();
    }
  }
 
  delay(100);
}

 

2025年12月9日修正しました。

ちなみに、スマホソフトは、ESP-BLE-Prov でググったうえで

インストール下さい。 これが必須です。