11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

BLEでESP32から接続するWiFiのSSID・パスワードを設定する

Last updated at Posted at 2020-02-20

ESP32にWifiアクセスポイントに接続する能力があるのに、画面や入力手段がないと、SSIDやパスワードをファームウェアに埋め込むことになり、固定値となってしまいます。

世の中の記事を見ると、いったん自身がWifiアクセスポイントになった後に、Wifiアクセスポイントにつながる方法があるようです。
今回紹介するのは、いったん自身がBLEペリフェラルになった後に、BLEでSSIDやパスワードを受け取ってWifiアクセスポイントにつながる方法です。
この方法であれば、わざわざWifiのSSIDやパスワードを入力するために、入力端末側のWifi接続先を変える必要がなくなります。
また、最近は、ブラウザにWeb Bluetooth APIというのがあり、ブラウザからSSID・パスワードを入力することができるようになります。

Arduino側

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <WiFi.h>

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CHARACTERISTIC_UUID_2 "beb5483f-36e1-4688-b7f5-ea07361b26a8"
#define BLE_DEVICE_NAME  "MyESP32"

#define WIFI_MAX_COUNTER  30

std::string wifi_ssid;
std::string wifi_password;
std::string wifi_mask("this is the wifi mask.");

bool wifi_entered;

class MyCallbacks1: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      wifi_ssid = pCharacteristic->getValue();
      Serial.print("SSID:");
      Serial.println(wifi_ssid.c_str());
    }
};

class MyCallbacks2: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      wifi_password = pCharacteristic->getValue();
//      Serial.print("Password:");
//      Serial.println(wifi_password.c_str());

      wifi_entered = true;
    }
};

IPAddress wifi_connect_with_ble(std::string ssid, std::string password){
  wifi_ssid = ssid;
  wifi_password = password;
  
  BLEDevice::init(BLE_DEVICE_NAME);
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic1 = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID_1,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
  BLECharacteristic *pCharacteristic2 = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID_2,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
  pCharacteristic1->setCallbacks(new MyCallbacks1());
  pCharacteristic1->setValue("Input SSID");
  pCharacteristic2->setCallbacks(new MyCallbacks2());
  pCharacteristic2->setValue("Input Password");

  BLEAdvertising *pAdvertising = pServer->getAdvertising();

  int wifi_counter;
  do{
    wifi_counter = WIFI_MAX_COUNTER;
    wifi_entered = false;

    if(wifi_ssid == "" || wifi_password == "" ){
      Serial.println("");
      Serial.println("Waiting Wifi password with BLE...");
      pService->start();
      pAdvertising->start();
      while(!wifi_entered){
        delay(1000);
        Serial.print(".");
      }
      pAdvertising->stop();
      pService->stop();

      Serial.println("");
    }

    for( int i = 0 ; i < wifi_password.length() && i < wifi_mask.length() ; i++ )
      wifi_password[i] ^= wifi_mask[i];

    Serial.println("Connecting to Wifi AP...");
    WiFi.begin(wifi_ssid.c_str(), wifi_password.c_str());
    while (WiFi.status() != WL_CONNECTED) {
      delay(1000);
      Serial.print(".");

      wifi_counter--;
      if( wifi_counter <= 0 )
        break;
    }
    wifi_password = "";
  }while( wifi_counter <= 0 );

  Serial.println("");

  return WiFi.localIP();
}

wifi_maskには秘匿の適当なマスク値を指定してください。
BLE通信路上を流れるWiFiパスワードを盗聴されにくくするためです。
★ですが、これでもやっぱり破られるので、ご利用は自己責任でお願いします。★

また、各UUIDも、各自適当な値を生成し設定してください。

入力する端末側は、BLEセントラルとして CHARACTERISTIC_UUID_1 のキャラクタリスティックックにSSIDをWriteし、CHARACTERISTIC_UUID_2 のキャラクタリスティックにパスワードをWriteすることで、ESP32は入力された値を使ってWiFi接続します。
ただし、 WIFI_MAX_COUNTER秒経過しても、WiFiアクセスポイントに接続できなければ、また、BLEペリフェラルとして、WiFiパスワードの入力待ちとなります。

あとは、setup()で、上記の関数wifi_connect_with_ble()を呼び出せばよいです。

void setup() {
  Serial.begin(9600);
  
  IPAddress ipaddress = wifi_connect_with_ble("", "");
  Serial.print("Connected : ");
  Serial.println(ipaddress);
}


void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("loop() called");
  delay(2000);
}

設定端末側

こんな感じのWebページを作ってみました。
Web Bluetooth APIが動作するOSであればよいので、Windows、Mac、AndroidのChromeで動きます。

image.png

esp32_nameとwifi_maskは、Arduino側に埋め込んだ値と同じ値を入力してください。
そして、wifi_ssid とwifi_password には、これから接続したいWiFiアクセスポイントのSSIDとパスワードを入力します。

BLEペリフェラルとして待ち受けているはずですので、「esp32_scan」ボタンを押下するとESP32が発見されます。
「esp32_connect」ボタンを押下すると、BLEペリフェラルに接続します。
接続完了すると、「esp32_write」ボタンが有効化されますので、押下します。これで、WiFiアクセスポイントの接続が始まります。

設定端末側のHTMLとJavascriptコードです。

start.js
'use strict';

//var vConsole = new VConsole();

const UUID_ESP32_SERVICE = '4fafc201-1fb5-459e-8fcc-c5c9c331914b';
const UUID_ESP32_CHARACTERISTIC_1 = 'beb5483e-36e1-4688-b7f5-ea07361b26a8';
const UUID_ESP32_CHARACTERISTIC_2 = 'beb5483f-36e1-4688-b7f5-ea07361b26a8';

var esp32_device = null;
var esp32_chars = new Map();

const encoder = new TextEncoder();

var vue_options = {
    el: "#top",
    data: {
        progress_title: '',

        esp32_name: "MyESP32",
        wifi_ssid: '',
        wifi_password: '',
        wifi_mask: 'this is the wifi mask.',
        esp32_scaned: false,
        esp32_connected: false,
    },
    computed: {
    },
    methods: {
        onDisconnect: function(event){
            console.log('onDisconnect');
            this.esp32_connected = false;
        },
        
        esp32_scan: function(){
            return navigator.bluetooth.requestDevice({
                filters: [{ name: this.esp32_name}],
                optionalServices : [UUID_ESP32_SERVICE]
            })
            .then(device => {
                console.log("requestDevice OK");
                console.log(device);

                esp32_device = device;
                esp32_device.addEventListener('gattserverdisconnected', this.onDisconnect);
                this.esp32_scaned = true;
            })
            .catch(error =>{
                console.log(error);
            });
        },
        esp32_connect: function(){
            if( esp32_device.connected )
                return;

            return esp32_device.gatt.connect()
            .then(server => {
                console.log('Execute : getPrimaryService');
                return server.getPrimaryService(UUID_ESP32_SERVICE);
            })
            .then(service => {
                console.log('Execute : getCharacteristic');
                return Promise.all([
                    set_characteristic(service, UUID_ESP32_CHARACTERISTIC_1),
                    set_characteristic(service, UUID_ESP32_CHARACTERISTIC_2),
                ]);
            })
            .then(()=>{
                this.esp32_connected = true;
                console.log('Connected!!');
            })
            .catch(error =>{
                console.log(error);
            });
        },
        esp32_write: async function(){
            await esp32_chars.get(UUID_ESP32_CHARACTERISTIC_1).writeValue(encoder.encode(this.wifi_ssid));

            var buffer = encoder.encode(this.wifi_password);
            var mask = encoder.encode(this.wifi_mask);
            for( var i = 0 ; i < buffer.length && i < mask.length ; i++ )
                buffer[i] ^= mask[i];
            await esp32_chars.get(UUID_ESP32_CHARACTERISTIC_2).writeValue(buffer);

            alert('wrote!');
    	},
    },
    created: function(){
    },
    mounted: function(){
        proc_load();
    }
};
vue_add_methods(vue_options, methods_utils);
var vue = new Vue( vue_options );

function set_characteristic(service, characteristicUuid) {
    return service.getCharacteristic(characteristicUuid)
    .then(characteristic => {
        console.log('setCharacteristic : ' + characteristicUuid);
        esp32_chars.set(characteristicUuid, characteristic);
        return service;
    });
}
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  <title>WiFi SSID/パスワード設定</title>

  <script src="js/methods_utils.js"></script>
  <script src="js/vue_utils.js"></script>

  <script src="dist/js/vconsole.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="top" class="container">
        <h1>WiFi SSID/パスワード設定</h1>

        <label>esp32_name</label> <input type="text" class="form-control" v-model="esp32_name">
        <button class="btn btn-default" v-on:click="esp32_scan">esp32_scan</button>
        <label>esp32_scaned</label> {{esp32_scaned}}
        <br>
        <br>
        <button class="btn btn-default" v-on:click="esp32_connect">esp32_connect</button>
        <label>esp32_connected</label> {{esp32_connected}}
        <br>
        <br>
        <label>wifi_ssid</label> <input type="text" class="form-control" v-model="wifi_ssid">
        <label>wifi_password</label> <input type="text" class="form-control" v-model="wifi_password">
        <label>wifi_mask</label> <input type="text" class="form-control" v-model="wifi_mask"><br>
        <button v-bind:disabled="!esp32_connected" class="btn btn-default" v-on:click="esp32_write">esp32_write</button>

        <div class="modal fade" id="progress">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title">{{progress_title}}</h4>
                    </div>
                    <div class="modal-body">
                        <center><progress max="100" /></center>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="js/start.js"></script>
</body>

そうそう、HTML5のWeb Bluetooth APIを使うので、上記Webページは、HTTPSにホスティングしてください。

#補足

ほんとは、chirpを使いたかったんだけど、SDKをダウンロードできない。。。

以上

11
9
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?