1. poruruba

    Posted

    poruruba
Changes in title
+ESP32のWiFi SSID・パスワードをBLEで設定する
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,330 @@
+ESP32にWifiアクセスポイントに接続する能力があるのに、画面や入力手段がないと、SSIDやパスワードをファームウェアに埋め込むことになり、固定値となってしまいます。
+
+世の中の記事を見ると、いったん自身がWifiアクセスポイントになった後に、Wifiアクセスポイントにつながる方法があるようです。
+今回紹介するのは、いったんBLEペリフェラルになった後に、Wifiアクセスポイントにつながる方法です。
+この方法であれば、わざわざWifiのSSIDやパスワードを入力するために、入力端末側のWifi接続先を変える必要がなくなります。
+また、最近は、Web Bluetoothというのがあり、ブラウザからSSID・パスワードを入力することができるようになります。
+
+# Arduino側
+
+```c
+#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()を呼び出せばよいです。
+
+```c
+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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/d1e6df7c-74d9-c794-43a2-d15669732566.png)
+
+esp32_nameとwifi_maskは、Arduino側に埋め込んだ値と同じ値を入力してください。
+そして、wifi_ssid とwifi_password には、これから接続したいWiFiアクセスポイントのSSIDとパスワードを入力します。
+
+BLEペリフェラルとして待ち受けているはずですので、「esp32_scan」ボタンを押下するとESP32が発見されます。
+「esp32_connect」ボタンを押下すると、BLEペリフェラルに接続します。
+接続完了すると、「esp32_write」ボタンが有効化されますので、押下します。これで、WiFiアクセスポイントの接続が始まります。
+
+設定端末側のHTMLとJavascriptコードです。
+
+```js: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;
+ });
+}
+```
+
+```html: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にホスティングしてください。
+
+以上