最終修正日:2020/8/3
【Contents】
- スケッチ
- 最初はシリアルで書き込む
- OTAの書込は2通り
- コラム
※OTA自体はESP32の独自仕様では無く、ArduinoIDEに可成り前から実装されていた仕組みです。
広義として、『無線ネットワークを利用(経由)した、主にデータの受信・同期の際の通信手段を指す語である』
狭義として、『無線LANや携帯電話網を使用して、端末にケーブルをつなぐことなく、ソフトウェアを更新できる技術。』
Arduino IDE 1.6.7で正式サポートしたようです。OTAの基本的な仕組みはhttps://garretlab.web.fc2.com/arduino/esp32/examples/ArduinoOTA/BasicOTA.htmlを参照してください。
ESP32はプログラムの書き込みをUSB経由以外にWifi経由で実現する事が可能です。これをOTA(Over The Air)といいます。
STM32duinoで、ArduinoIDEから普通にプログラムを書き込む際も、ブートローダ部は維持されユーザプログラム領域を書き換えているわけで、その意味でOTA部というのはブートローダとして書き込まれているので可能なのでしょう。
@ |
![]() |
USBシリアル変換/開発ボードであればUSB接続することでビルド後のバイナリファイルをESP32に書き込みます。 |
A |
![]() |
OTAに絡むサンプルコードを正しくBuildすると、接続先にネットワーク接続先が選択出来るようになります。 以後このポートを選択しておくとシリアル接続していなくても、ネットワーク経由でバイナリデータをダウンロード出来るようになります。 |
B |
![]() |
バイナリデータを作成する機能がIDEには用意されています。一旦バイナリファイルを作成し、ブラウザ経由でそのバイナリファイルをダウンロードすることが出来ます。 Aとの違いですが、AはStationModeでないと実現出来ませんが、BはAPModeでも実現出来ます。 ダウンロード速度も納得しうる実行速度です。 |
先人の記事によると環境に注意する必要があるとあります。Python27が必要でPython3.5では動かないとありました。ところが自分の環境はPython3.6.1(32bit) 最新版は3.8.3とあります。
Pythonを自分でインストールした覚えがありません。とりあえずUpdateすること無くOTAの検証をして見ることにします。OTAに対応したコードをまずUSB経由で書き込みました。すると、ポートに接続先のネットワークポートが見つかります。そして、そのポートを選択してプログラムを書き込むと、そのIP 宛てにUploadが開始されることが判りました。ただ、今回は成功していません。いろいろ制約があるようです。もう少し検討します。
バイナリファイルを構築後であればブラウザからもUpdate可能となるようです。OTA用のブートローダがダウンロードされたデバイスであればこれが実現するようです。
USB-TTL部を搭載していないモジュールの場合、この機能は有用ですね。以下はLastMinuteEngineersの提供するサンプルスケッチをレタッチしたモノです。
このプログラムをUSB経由でESP32に書き込みます。
/* * 20200709 T.Wanibe * LastMinuteEngineersの提供するサンプルコードを元にOTAの勉強をします。 * https://lastminuteengineers.com/esp32-ota-web-updater-arduino-ide/ * 最大1310720バイトのフラッシュメモリのうち、スケッチが758818バイト(57%)を使っています。 * 最大327680バイトのRAMのうち、グローバル変数が41568バイト(12%)を使っていて、ローカル変数で286112バイト使うことができます。 * */ #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h> #include <Update.h> #define HTTPport 80 const char* host = "esp32"; const char* ssid = ""; const char* password = ""; String bgColor = "#17A1A5"; WebServer WS(HTTPport); //Style1 String style1 = "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}" "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#17A1A5;font-family:sans-serif;font-size:14px;color:#777}" "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}" "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#17A1A5;width:0%;height:10px}" "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}" ".btn{background:#17A1A5;color:#fff;cursor:pointer}</style>"; //Style2 String style2 = "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}" "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}" "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}" "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}" "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}" ".btn{background:#3498db;color:#fff;cursor:pointer}</style>"; //Login page String loginIndex = "<form name=loginForm>" "<h1>ESP32 Login</h1>" "<input name=userid placeholder='User ID'> " "<input name=pwd placeholder=Password type=Password> " "<input type=submit onclick=check(this.form) class=btn value=Login></form>" "<script>" "function check(form) {" "if(form.userid.value=='admin' && form.pwd.value=='admin')" "{window.open('/serverIndex')}" "else" "{alert('Error Password or Username')}" "}" "</script>" + style1; //Server Index Page String serverIndex = "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>" "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>" "<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>" "<label id='file-input' for='file'> Choose file...</label>" "<input type='submit' class=btn value='Update'>" "<br><br>" "<div id='prg'></div>" "<br><div id='prgbar'><div id='bar'></div></div><br></form>" "<script>" "function sub(obj){" "var fileName = obj.value.split('\\\\');" "document.getElementById('file-input').innerHTML = ' '+ fileName[fileName.length-1];" "};" "$('form').submit(function(e){" "e.preventDefault();" "var form = $('#upload_form')[0];" "var data = new FormData(form);" "$.ajax({" "url: '/update'," "type: 'POST'," "data: data," "contentType: false," "processData:false," "xhr: function() {" "var xhr = new window.XMLHttpRequest();" "xhr.upload.addEventListener('progress', function(evt) {" "if (evt.lengthComputable) {" "var per = evt.loaded / evt.total;" "$('#prg').html('progress: ' + Math.round(per*100) + '%');" "$('#bar').css('width',Math.round(per*100) + '%');" "}" "}, false);" "return xhr;" "}," "success:function(d, s) {" "console.log('success!') " "}," "error: function (a, b, c) {" "}" "});" "});" "</script>" + style2; //------------setup function void setup(void) { Serial.begin(115200); // Connect to WiFi network WiFi.begin(ssid, password); Serial.println(F("")); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print(F(".")); } Serial.println(F(""));Serial.print(F("Connected to ")); Serial.println(ssid); Serial.print(F("IP address: "));Serial.println(WiFi.localIP()); /*use mdns for host name resolution*/ if (!MDNS.begin(host)) { //https://esp32.local Serial.println(F("Error setting up MDNS responder!")); while (1) { delay(1000); } } Serial.println(F("mDNS responder started")); /*return index page which is stored in serverIndex */ WS.on("/", HTTP_GET, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/html", loginIndex); }); WS.on("/serverIndex", HTTP_GET, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/html", serverIndex); }); /*handling uploading firmware file */ WS.on("/update", HTTP_POST, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = WS.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.printf("Update: %s\n", upload.filename.c_str()); if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { /* flashing firmware to ESP*/ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } } }); WS.begin(); } //------------ void loop(void) { WS.handleClient(); delay(1); } |
シリアルモニタを開いて実行すると、起動時に接続したネットワークからIPアドレスが割り振られ、表示されます。
そのIPアドレスにブラウザからアクセスするとログイン画面が現れます。これで前段が確認出来た事になります。
※DNSも起動しており、https://esp32.localでもログイン画面が確認出来るはずです。
ログインパスワードはHTMLに書き込まれていますが、ユーザーID:“admin” パスワード:“admin”となっています。serverIndexのページに飛びました。ここで.bin形式のファイルを選択して、Uploadすると、プログラムがweb経由で書き込まれ、リセットされて新たなプログラムが実行されることになります。
この仕組みを繰り返し使う為にUploadするbin形式のファイルにはOTAの機能が組み込まれている必要があります。テンプレートファイルを検討しました。以下の通りです。
実際にレタッチ後、ArduinoIDEでbinファイルを作成する必要があります。
[ スケッチ] > [ コンパイルしたバイナリを出力]に移動します。binファイルはinoファイルと同階層に作成されているはずですbinファイルを“Update”したところ、ESP32は勝手にリスタートしてアクセスポイントに接続しました。次のプログラムは更新に使用したプログラムですが、13pinに接続したLEDの点滅を追加したところ、ダウンロード後に勝手にリセットされ点滅開始します。
書き込み時間は先人が言うように短いかもしれません。ただ、事前にコンパイル済みですのでその時間を考慮する必要があります。
ブラウザからのダウンロードはシリアルコンソールを開いたままでも出来るためデバッグがしやすいです。
/* * 20200709 T.Wanibe * 最大1310720バイトのフラッシュメモリのうち、スケッチが758874バイト(57%)を使っています。 * 最大327680バイトのRAMのうち、グローバル変数が41576バイト(12%)を使っていて、ローカル変数で286104バイト使うことができます。 */ #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h> #include <Update.h> #define HTTPport 80 const char* host = "esp32"; const char* ssid = ""; const char* password = ""; //variabls for blinking an LED with Millis const int led = 13; //オンボードLEDが接続されているESP32ピン unsigned long previousMillis = 0; //LEDが最後に更新されたときに保存されます const long interval = 1000; //点滅する間隔(ミリ秒) int ledState = LOW; //LEDの設定に使用されるledState WebServer WS(HTTPport); /* Style */ String style = "<style>" "#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}" "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}" "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}" "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}" "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}" ".btn{background:#3498db;color:#fff;cursor:pointer}" "</style>"; /* Login page */ String loginIndex = "<form name=loginForm>" "<h1>ESP32 Login</h1>" "<input name=userid placeholder='User ID'> " "<input name=pwd placeholder=Password type=Password> " "<input type=submit onclick=check(this.form) class=btn value=Login></form>" "<script>" "function check(form) {" "if(form.userid.value=='admin' && form.pwd.value=='admin')" "{window.open('/serverIndex')}" "else" "{alert('Error Password or Username')}" "}" "</script>" + style; /* Server Index Page */ String serverIndex = "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>" "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>" "<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>" "<label id='file-input' for='file'> Choose file...</label>" "<input type='submit' class=btn value='Update'>" "<br><br>" "<div id='prg'></div>" "<br><div id='prgbar'><div id='bar'></div></div><br></form>" "<script>" "function sub(obj){" "var fileName = obj.value.split('\\\\');" "document.getElementById('file-input').innerHTML = ' '+ fileName[fileName.length-1];" "};" "$('form').submit(function(e){" "e.preventDefault();" "var form = $('#upload_form')[0];" "var data = new FormData(form);" "$.ajax({" "url: '/update'," "type: 'POST'," "data: data," "contentType: false," "processData:false," "xhr: function() {" "var xhr = new window.XMLHttpRequest();" "xhr.upload.addEventListener('progress', function(evt) {" "if (evt.lengthComputable) {" "var per = evt.loaded / evt.total;" "$('#prg').html('progress: ' + Math.round(per*100) + '%');" "$('#bar').css('width',Math.round(per*100) + '%');" "}" "}, false);" "return xhr;" "}," "success:function(d, s) {" "console.log('success!') " "}," "error: function (a, b, c) {" "}" "});" "});" "</script>" + style; //------------ setup function void setup(void) { pinMode(led, OUTPUT); Serial.begin(115200); // Connect to WiFi network WiFi.begin(ssid, password); // Wait for connection Serial.println(F("")); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print(F(".")); } Serial.println(F("")); Serial.print(F("Connected to "));Serial.println(ssid); Serial.print(F("IP address: "));Serial.println(WiFi.localIP()); //ホスト名解決にmdnsを使用する if (!MDNS.begin(host)) { //https://esp32.local Serial.println(F("Error setting up MDNS responder!")); while (1) { delay(1000); } } Serial.println(F("mDNS responder started")); //serverIndexに格納されているインデックスページを返す WS.on("/", HTTP_GET, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/html", loginIndex); }); WS.on("/serverIndex", HTTP_GET, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/html", serverIndex); }); //ファームウェアファイルのアップロードの処理 WS.on("/update", HTTP_POST, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = WS.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.printf("Update: %s\n", upload.filename.c_str()); if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //利用可能な最大サイズから始める Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { //ESPへのファームウェアのフラッシュ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //サイズを現在の進行状況に設定する場合はtrue Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } } }); WS.begin(); } //------------ void loop(void) { WS.handleClient(); delay(1); //ループして遅延なく点滅する unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { //最後にLEDを点滅させた時間を保存します previousMillis = currentMillis; //LEDがオフの場合はオンにし、逆の場合も同様です。 ledState = not(ledState); //変数のledStateでLEDを設定します。 digitalWrite(led, ledState); } }もう一つOLCDを追加したコードを書いてみました。アクセスすべきIPアドレスやSSID/PASSを表示するために使用しています。
また、modeを[WIFI_AP_STA]にしています。アクセスするIPが2通りになります。
I2CのBMP280をアクセスしてデータ表示するようにしています。BME280I2C.hを採用しています。なぜかAdafruitのライブラリがうまくコンパイル出来なかったからです。コンパイルするために以下のライブラリを追加してください。尚、arduino_secrets.hというのはメインソースに記述したくないLocalな値を別ファイル化する仕組みです。
- #include <Adafruit_GFX.h> Adafruitのグラフィックライブラリ
https://github.com/adafruit/Adafruit-GFX-Library- #include <Adafruit_SSD1306.h> AdafruitのOLCD1306ドライバ
https://github.com/adafruit/Adafruit_SSD1306- #include <BME280I2C.h> finitespace氏提供のドライバです。
https://github.com/finitespace/BME280
/* * 20200731 T.Wanibe * LEDの配置等を変更 * OTAを維持したまま、モードをST+APにして普段はPCとの間でトランシーバモード、一方、ファーム更新はSTモードで実現出来ないか確認する * Adafruit_BME280.hがESP32でうまく動作しないため、ライブラリを変更し、BME280I2C.hを採用した * ※HTMLの記述にてString型を駆使して記述するが、その手法も多岐にわたる。好みの方法を確立しておく必要がある * 最大1310720バイトのフラッシュメモリのうち、スケッチが840414バイト(64%)を使っています。 * 最大327680バイトのRAMのうち、グローバル変数が43072バイト(13%)を使っていて、ローカル変数で284608バイト使うことができます。 */ #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h> #include <Update.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> //#include <Adafruit_Sensor.h> //#include <Adafruit_BME280.h> #include <BME280I2C.h> //#include "index.h" //Web page header file #include "arduino_secrets.h" //機密データを「Secret」タブ/arduino_secrets.hに入力してください #define HTTPport 80 #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define NUMFLAKES 10 // アニメーション例の雪片の数 #define LOGO_HEIGHT 16 #define LOGO_WIDTH 16 #define OLCDAres 0x3C //0x3Cか0x3D #define LED 13 //ボード上のLEDは使えないのでGPIO13にLEDを接続 #define AIport A0 #define PO1 17 #define PO2 16 #define PO3 4 #define PO4 2 #define PI1 39 #define PI2 34 #define PI3 35 #define PI4 33 //#define BMEID 0x58 const char* host = "esp32"; char ssid[] = SECRET_SSID; //ネットワークSSID(名前) char pass[] = SECRET_PASS; //ネットワークパスワード(WPAの使用、またはWEPのキーとして使用) String myID = "admin"; String myPASS = "password"; //variabls for blinking an LED with Millis unsigned long previousMillis = 0; //LEDが最後に更新されたときに保存されます const long interval = 1000; //点滅する間隔(ミリ秒) int ledState = LOW; //LEDの設定に使用されるledState unsigned status; //Station用 IPAddress ip(192, 168, 0, 33); IPAddress gateway(192, 168, 0, 1); IPAddress netmask(255, 255, 255, 0); //SoftAP用 const char* ap_ssid = "ESP_AP"; const char* ap_password = "password"; char buf1[128]; char buf2[128]; String buf11 = "ABC"; String buf22 = "DEF"; float temp(NAN),hum(NAN),pres(NAN); // hum(NAN)は無効 IPAddress ap_ip(192, 168, 250, 33); IPAddress ap_gateway(192, 168, 250, 1); WebServer WS(HTTPport); //I2C(SDA、SCLピン)に接続されたSSD1306ディスプレイの宣言 Adafruit_SSD1306 OLCD(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); BME280I2C bmp; // Default : forced mode, standby time = 1000 ms // Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off, //-------------- Style String style = "<style>\n" "\t#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}\n" "\tinput{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}\n" "\t#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}\n" "\t#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}\n" "\tform{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}\n" "\t.btn{background:#3498db;color:#fff;cursor:pointer}\n" "</style>\n"; //-------------- Login page String loginIndex = "<form name=loginForm>\n" "\t<h1>ESP32 Login</h1>\n" "\t<input name=userid placeholder='User ID'>\n" "\t<input name=pwd placeholder=Password type=Password>\n" "<input type=submit onclick=check(this.form) class=btn value=Login>\n</form>\n" "<script>\n" "\tfunction check(form) {\n" "\t\tif(form.userid.value== '" + myID + "' && form.pwd.value== '" + myPASS +"' )\n" "\t\t\t{window.open('/serverIndex')}\n" "\t\telse\n" "\t\t\t{alert('Error Password or Username')}\n" "\t}\n" "</script>\n" + style; //-------------- Server Index Page String serverIndex = "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>\n" "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>\n" "\t<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>\n" "\t<label id='file-input' for='file'> Choose file...</label>\n" "\t<input type='submit' class=btn value='Update'>\n" "\t<br><br>\n" "\t<div id='prg'></div>\n" "\t<br><div id='prgbar'><div id='bar'></div></div><br>\n" "</form>\n" "<script>\n" "\tfunction sub(obj){\n" "\t\tvar fileName = obj.value.split('\\\\');\n" "\t\tdocument.getElementById('file-input').innerHTML = ' '+ fileName[fileName.length-1];\n" "\t};\n" "\t$('form').submit(function(e){\n" "\t\te.preventDefault();\n" "\t\tvar form = $('#upload_form')[0];\n" "\t\tvar data = new FormData(form);\n" "\t\t$.ajax({\n" "\t\t\turl: '/update',\n" "\t\t\ttype: 'POST',\n" "\t\t\tdata: data,\n" "\t\t\tcontentType: false,\n" "\t\t\tprocessData:false,\n" "\t\t\txhr: function() {\n" "\t\t\t\tvar xhr = new window.XMLHttpRequest();\n" "\t\t\t\txhr.upload.addEventListener('progress', function(evt) {\n" "\t\t\t\t\tif (evt.lengthComputable) {\n" "\t\t\t\t\t\tvar per = evt.loaded / evt.total;\n" "\t\t\t\t\t\t$('#prg').html('progress: ' + Math.round(per*100) + '%');\n" "\t\t\t\t\t\t$('#bar').css('width',Math.round(per*100) + '%');\n" "\t\t\t\t\t}\n" "\t\t\t\t}, false);\n" "\t\t\t\treturn xhr;\n" "\t\t\t},\n" "\t\t\tsuccess:function(d, s) {\n" "\t\t\t\tconsole.log('success!')\n" "\t\t\t},\n" "\t\t\terror: function (a, b, c) {\n" "\t\t\t}\n" "\t\t});\n" "\t});\n" "</script>\n" + style; //------------ viewValue String viewValue = "<html>\n" "\t<head>\n" "\t<meta http-equiv='refresh' content='5'/>\n" "\t<title>ESP32 Monitor</title>\n" "\t<style>\n" "\t\tbody { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\n" "\t</style>\n" "\t</head>\n" "\t<body>\n" "\t\t<center><h1>ESP32 Monitor!</h1>\n" "\t\t<p>" + buf11 + "</p>\n" "\t\t<p>" + buf22 + "</p></center>\n" "\t</body>\n" "</html>\n"; //------------ fileNotFound String fileNotFound = "<html><head>\n" "\t<title>ESP32 : 404 Not Found</title>\n" "</head><body>\n" "\t<center><h1>ESP32 404 : Not Found</h1></center>\n" "\t<p>The requested URL was not found on this server.</p>\n" "</body></html>\n"; //------------This routine is executed when you open its IP in browser //void handleRoot() { // String s = MAIN_page; //Read HTML contents // WS.send(200, "text/html", s); //Send web page //} //------------ void handleGetData() { //float a = analogRead(A0); //String adcValue = String(a); WS.send(200, "text/html", SendHTML(analogRead(A0),temp,pres)); //Send ADC value only to client ajax request } //------------ void handleNotFound(){ //WS.send(404, "text/plain", "Not found"); WS.send(404, "text/html",fileNotFound); } //------------ String SendHTML(float tempSensor1,float tempSensor2,float tempSensor3){ String ptr = "<html>\n" "<head><meta name='viewport' content='width=device-width, initial-scale=1.0, user-scalable=no' charset='UTF-8'>\n" "\t<title>ESP32 Monitor</title>\n" "\t<style>\n\t\thtml { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n" "\t\tbody{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n" "\t\tp {font-size: 24px;color: #444444;margin-bottom: 10px;}\n" "\t</style>\n" "</head>\n" "<body>\n" "\t<div id=\"webpage\">\n" "\t<h1>ESP32 Monitor</h1>\n" "\t<p>AD0 AD値: " + String(tempSensor1) + "</p>\n" "\t<p>Temp: " + String(tempSensor2) + "℃</p>\n" "\t<p>Pres: " + String(tempSensor3) + "Pa</p>\n" "\t</div>\n" "</body>\n" "</html>\n"; return ptr; } //------------ BMPprint void printValues() { BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); BME280::PresUnit presUnit(BME280::PresUnit_Pa); bmp.read(pres, temp, hum, tempUnit, presUnit); sprintf(buf1,"Temperature:%.2f%1s",temp,String(tempUnit == BME280::TempUnit_Celsius ? 'C' :'F')); sprintf(buf2,"Pressure: %.0f Pa",pres); buf11 = String(buf1); buf22 = String(buf2); Serial.println(buf1); Serial.println(buf2); OLCD.setCursor(0, 40); OLCD.fillRect(0,40,128,24,SSD1306_BLACK); OLCD.setTextSize(1);OLCD.setTextColor(SSD1306_WHITE);OLCD.setCursor(0, 40); OLCD.println(buf1); OLCD.println(buf2); OLCD.display(); } //------------ setup function void setup(void) { pinMode(LED,OUTPUT); digitalWrite(LED,LOW); //Onboard LED port Direction output pinMode(PO1,OUTPUT); digitalWrite(PO1,LOW); pinMode(PO2,OUTPUT); digitalWrite(PO2,LOW); pinMode(PO3,OUTPUT); digitalWrite(PO3,LOW); pinMode(PO4,OUTPUT); digitalWrite(PO4,LOW); pinMode(PI1,INPUT); pinMode(PI2,INPUT); pinMode(PI3,INPUT); pinMode(PI4,INPUT); // Serial.begin(115200); //SSD1306_SWITCHCAPVCC =内部で3.3Vから表示電圧を生成 Wire.setClock(400000); // クロック速度を最も速くなるように設定して、通信を改善します(高速モード) if(!OLCD.begin(SSD1306_SWITCHCAPVCC, OLCDAres)) { // 128x64のスレーブアドレスオリジナル値は0x3Dでしたが手元の元は0x3Cでした。 Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } status = bmp.begin(); if (!status) { Serial.println(F("Could not find BME280 sensor!")); } // bme.chipID(); // Deprecated. See chipModel(). switch(bmp.chipModel()){ case BME280::ChipModel_BME280: Serial.println(F("Found BME280 sensor! Success.")); break; case BME280::ChipModel_BMP280: Serial.println(F("Found BMP280 sensor! No Humidity available.")); break; default: Serial.println(F("Found UNKNOWN sensor! Error!")); } // バッファをクリアします OLCD.clearDisplay(); // アクセスポイント(無線LAN親機) + ステーション(無線LAN子機) WiFi.mode(WIFI_AP_STA); // まず既存のアクセスポイント(ネットワーク)に接続する //WiFi.config(ip,gateway,netmask); //固定IPにする場合に使用 WiFi.begin(ssid, pass); // Wait for connection Serial.println(F("")); while (WiFi.status() != WL_CONNECTED) { //接続するまでループするが、すごく電流を喰うようでchipが熱くなる。要対策。 delay(1000); Serial.print(F(".")); } Serial.println(F("")); Serial.print(F("Connected to "));Serial.println(ssid); IPAddress myIP =WiFi.localIP(); Serial.print(F("IP address: "));Serial.println(myIP); OLCD.setTextSize(1);OLCD.setTextColor(SSD1306_WHITE);OLCD.setCursor(0, 0); OLCD.print(F("LoginID:"));OLCD.println(myID); OLCD.print(F("LoginPASS:"));OLCD.println(myPASS); OLCD.print(F("IP:"));OLCD.println(myIP); //OLCD.display(); // SoftAPを開始する WiFi.softAPConfig(ap_ip,ap_gateway,netmask); WiFi.softAP(ap_ssid, ap_password); //IPAddress ap_ip = WiFi.softAPIP(); OLCD.setTextSize(1);OLCD.setTextColor(SSD1306_WHITE);OLCD.setCursor(0, 40); OLCD.print(F("SSID:"));OLCD.println(ap_ssid); OLCD.print(F("PASS:"));OLCD.println(ap_password); OLCD.print(F("IP:"));OLCD.println(ap_ip); OLCD.display(); Serial.print(F("SoftAPのIPアドレス(LAN側): "));Serial.println(myIP); //ホスト名解決にmdnsを使用する if (!MDNS.begin(host)) { //https://esp32.local Serial.println(F("Error setting up MDNS responder!")); OLCD.setCursor(0, 32);OLCD.print(F("Error setting up MDNS responder!")); while (1) { delay(1000); } } Serial.println(F("mDNS responder started")); //serverIndexに格納されているインデックスページを返す WS.on("/login", HTTP_GET, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/html", loginIndex); OLCD.fillRect(0,32,128,8,SSD1306_BLACK); OLCD.setCursor(0, 32); OLCD.setTextColor(SSD1306_WHITE); OLCD.print(F("Connection")); OLCD.display(); }); WS.on("/serverIndex", HTTP_GET, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/html", serverIndex); OLCD.fillRect(0,32,128,8,SSD1306_BLACK); OLCD.setCursor(0, 32); OLCD.setTextColor(SSD1306_WHITE); OLCD.print(F("UpdateReady")); OLCD.display(); }); WS.on("/", HTTP_GET, []() { WS.sendHeader("Connection", "close"); WS.send(200, "text/html", SendHTML(analogRead(A0),temp,pres)); //OLCD.setCursor(0, 32); OLCD.fillRect(0,32,128,8,SSD1306_BLACK); OLCD.display(); }); //ファームウェアファイルのアップロードの処理 WS.on("/update", HTTP_POST, []() { WS.sendHeader("Connection", "close"); OLCD.setCursor(0, 32); OLCD.drawRect(0,32,128,8,SSD1306_BLACK); OLCD.display(); WS.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = WS.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.printf("Update: %s\n", upload.filename.c_str()); OLCD.setCursor(0, 32); OLCD.drawRect(0,32,128,8,SSD1306_BLACK); OLCD.setTextColor(SSD1306_WHITE); OLCD.print(F("Updating...")); OLCD.display(); if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //利用可能な最大サイズから始める Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { //ESPへのファームウェアのフラッシュ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //サイズを現在の進行状況に設定する場合はtrue Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } } }); //WS.on("/", handleRoot); //This is display page //WS.on("/", handleGetData); //To get update of ADC Value only WS.on("/GetData", handleGetData); //To get update of ADC Value only //WS.on("/readPres", handlePres); //To get update of ADC Value only //WS.on("/readTemp", handleTemp); //To get update of ADC Value only WS.onNotFound(handleNotFound); WS.begin(); } //------------ int count = 0; void loop(void) { WS.handleClient(); delay(100); printValues(); //ループして遅延なく点滅する unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { //最後にLEDを点滅させた時間を保存します previousMillis = currentMillis; //LEDがオフの場合はオンにし、逆の場合も同様です。 ledState = not(ledState); //変数のledStateでLEDを設定します。 digitalWrite(LED, ledState); switch(count){ case 0: digitalWrite(PO1, true); digitalWrite(PO2, false); digitalWrite(PO3, false); digitalWrite(PO4, false); break; case 1: digitalWrite(PO1, false); digitalWrite(PO2, true); digitalWrite(PO3, false); digitalWrite(PO4, false); break; case 2: digitalWrite(PO1, false); digitalWrite(PO2, false); digitalWrite(PO3, true); digitalWrite(PO4, false); break; case 3: digitalWrite(PO1, false); digitalWrite(PO2, false); digitalWrite(PO3, false); digitalWrite(PO4, true); break; default: digitalWrite(PO1, false); digitalWrite(PO2, false); digitalWrite(PO3, false); digitalWrite(PO4, false); break; } count = (count + 1) % 5; Serial.println(count); } }
ESP32のスケッチ記述でStationモードで使用する場合、ソースコード内に接続するアクセスポイントのSSIDとパスワードを記述することになります。このままでは公開できません。
そんなことを思っていたら記述方法が紹介されていました。備忘録とします。
- ソースコードに隠したい情報は別ファイルに記述し、そのファイルをインクルードすることで解決します。
- SSIDとパスワードを隠したい場合、この2つの情報を記述したファイルを作成して、ファイル名を“arduino_secrets.h”として.inoと同階層に保存します。
- “arduino_secrets.h”の内容は、以下のように記述します。
#define SECRET_SSID "SSID" #define SECRET_PASS "PASS"- .inoには次のような記述を追加します。
#include "arduino_secrets.h" ・・・・ char ssid[] = SECRET_SSID; // char pass[] = SECRET_PASS; // ・・・・ WiFi.mode(WIFI_STA); //WIFI_AP_STAの場合も同様 WiFi.begin(ssid, pass);OTAを実行する上で3つあるMODE(WIFI_STA/WIFI_AP/WIFI_AP_STA) いずれでも実現可能であることを確認しました。WIFI_AP_STAの場合、2つのポイントからアクセスする事になりますが、OTAのサーバループは共通で使える様です。
WIFI_APモードでも使えることから、無線LANを装備したNotePCとESPモジュール間でトランシーバライクに使えることになります。ESP32側は電源だけ確保出来ればESP-WROOM32単体で機能することになり、ロボット組込も優位になりますね。
ちなみにAPモードでOTAを実施すると消費電流値が40mA程度上昇しました。通常実行時に0.15〜0.17Aだったのが、0.18〜0.21Aになりました。
ロボット等のコントローラにESP32を採用する際、OTAの仕組みは有り難いです。最初だけはシリアル接続してBOOTボタンを細工してプログラムを書き込むわけですが、それ以降はブラウザからダウンロード出来ます。
モードによってバイナリファイルのダウンロード時間も変わるようです。APモードは一番効率が良さそうです。
|
免責事項
本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。本ソフトウエアの著作権はToolsBoxに帰属します。
本ソフトウエアをご利用の結果生じた損害について、ToolsBoxは一切責任を負いません。
ToolsBoxはコンテンツとして提供する全ての文章、画像等について、内容の合法性・正確性・安全性等、において最善の注意をし、作成していますが、保証するものではありません。
ToolsBoxはリンクをしている外部サイトについては、何ら保証しません。
ToolsBoxは事前の予告無く、本ソフトウエアの開発・提供を中止する可能性があります。
商標・登録商標
Microsoft、Windows、WindowsNTは米国Microsoft Corporationの米国およびその他の国における登録商標です。
Windows Vista、Windows XPは、米国Microsoft Corporation.の商品名称です。
LabVIEW、National Instruments、NI、ni.comはNational Instrumentsの登録商標です。
I2Cは、NXP Semiconductors社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。