본문 바로가기
MCU/ESP32

ESP32 firmware 원격 업데이트 구현

by 누워있는말티즈 2022. 8. 5.

ESP32 firmware 원격 업데이트 구현

이 페이지에서는 web server를 통해 ESP32 보드의 펌웨어를 원격 업데이트 하는 방법에 대해 다룬다.


개발 보드 : WT32-ETH01(Wireless-Tag)

에디터 : Arduino IDE

https://www.youtube.com/watch?v=2ANVWzfXekY

네트워크 : Ethernet (대부분 ESP32는 WIFI를 사용하지만 본인은 안정성이 보장된 이더넷을 사용하고자 한다.)


  • Arduino IDE에서 보드를 ESP32 Dev Module 선택

  • Example에서 ArduinoOTA 아래의 “OTAWebUpdator”를 선택


    예제를 열어보면 wifi로 네트워크에 연결해 무선 업데이트를 위해 필요한 웹서버에 접속할 수 있다. 접속 IP는 시리얼 모니터에 찍히는 IP를 이용한다.

본인은 wifi 대신 이더넷을 사용하므로 코드를 살짝 변경해주었다.

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <HTTPClient.h>
#include <Arduino.h> //for ethernet
#include <ETH.h>     //for ethernet

#define ETH_POWER_PIN   16
#define ETH_MDC_PIN     23
#define ETH_MDIO_PIN    18
#define ETH_ADDR        1
#define ETH_CLK_MODE    ETH_CLOCK_GPIO0_IN
#define ETH_TYPE        ETH_PHY_LAN8720
static bool eth_connected = false;
unsigned long startMillis;

const char* host = "esp32";
// const char* ssid = "xxx";
// const char* password = "xxxx";

WebServer server(80);

/*
 * Login page
 */

const char* loginIndex =
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
             "<td>Username:</td>"
             "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";

/*
 * Server Index Page
 */

const char* 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'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('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) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

void WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case ARDUINO_EVENT_ETH_START:
      Serial.println("ETH Started");
      //set eth hostname here
      ETH.setHostname("esp32-ethernet");
      break;
    case ARDUINO_EVENT_ETH_CONNECTED:
      Serial.println("ETH Connected");
      break;
    case ARDUINO_EVENT_ETH_GOT_IP:
      Serial.print("ETH MAC: ");
      Serial.print(ETH.macAddress());
      Serial.print(", IPv4: ");
      Serial.print(ETH.localIP());
      if (ETH.fullDuplex()) {
        Serial.print(", FULL_DUPLEX");
      }
      Serial.print(", ");
      Serial.print(ETH.linkSpeed());
      Serial.println("Mbps");
      eth_connected = true;
      break;
    case ARDUINO_EVENT_ETH_DISCONNECTED:
      Serial.println("ETH Disconnected");
      eth_connected = false;
      break;
    case ARDUINO_EVENT_ETH_STOP:
      Serial.println("ETH Stopped");
      eth_connected = false;
      break;
    default:
      break;
  }
}

/*
 * setup function
 */
void setup(void) {
  Serial.begin(115200);
  // Connect to Ethernet network

  ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);

  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.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);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);
  Serial.println("uno"); // 이걸 바꿔가며 업데이트가 되었는지 확인할 예정이다
  delay(100);
}

처음 한 번은 직접 업로드를 해줘야 한다. 업로드 후 시행하면 다음과 같은 시리얼을 확인할 수 있다.

저기 아래에서 6번째 줄에 보이는 172.30.10.22가 이제 우리가 사용할 업데이트 페이지의 주소이다.

해당 IP로 접속하면 다음과 같은 화면을 마주하게 된다. ID, PW 모두 admin이다.


로그인을 하면 다음 창에서 새로운 바이너리 파일로 업데이트를 할 수 있게 된다.

그럼 이제 업데이트 파일을 만들어보자.

Arduino IDE로 돌아와서 “Sketch →Export Compiled Binary”를 시행한다.

테스트를 위해 loop 함수 내부를 아래와 같이 변경했다.

void loop(void) {
  server.handleClient();
  delay(1);
  Serial.println("uno"); // 이걸 바꿔가며 업데이트가 되었는지 확인할 예정이다
  delay(100);
}

위에서 생성한 바이너리 파일(.bin / .ino가 아니다!)을 위의 웹에서 선택해준다. 선택 후 update 버튼을 클릭하면 업데이트가 진행되고 재부팅 후 변경된 펌웨어가 적용된다.


업데이트 이후 시리얼

구디굳🙂

반응형

댓글