มั่ว

ESP8266 OTA

อ้างอิงจาก https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/
และ http://esp8266.github.io/Arduino/versions/2.1.0-rc2/doc/ota_updates/ota_updates.html

การเขียนโปรแกรมสั่งงานอุปกรณ์ IoT (Internet of Things) นั้นส่วนใหญ่จะทำการเขียนบนเครื่องมือพัฒนาที่เรียกว่า IDE (Integrated development environment) เช่น Arduino IDE จากนั้นก็จะทำการ upload ตัวโปรแกรม (binary compiled) ไปยังบอร์ดผ่านทาง serial port (USB) หรือสายอนุกรมที่ต่อระหว่างคอมพิวเตอร์กับบอร์ด เมื่อมีการแก้ไขโปรแกรมและทำการ compile ใหม่ก็จะทำแบบเดียวกันนี้อีกครั้ง เมื่อนำไปใช้งานแล้วถ้าหากจะต้องมีการแก้ไขก็จะต้องนำอุปกรณ์ IoT นั้นมาเชื่อมต่อกับเครื่องคอมพิวเตอร์ที่เขียนโปรแกรม เพื่อทำการ upload โปรแกรมที่แก้ไขแล้วลงไปที่อุปกรณ์ใหม่อีกครั้งผ่านสายอนุกรม จะเห็นได้ว่าในบางกรณีนั้นการทำแบบนี้เป็นไปได้ยากมาก เช่น ถ้าอุปกรณ์นั้นไม่ได้อยู่ในบ้านหรือในที่ทำงาน แต่อยู่ในสถานที่ห่างไกลออกไป เช่น อยู่กับลูกค้า อยู่ต่างจังหวัด อยู่ต่างประเทศ การทำ upload ผ่านทางสายอนุกรมนั้นแทบจะเป็นไปไม่ได้เลย การส่งอุปกรณ์กลับก็สร้างความยุ่งยากเช่นเดียวกัน จึงได้มีแนวคิดของการ upload โปรแกรมผ่านทางเครือข่ายเช่น WiFi หรือผ่านทางอินเทอร์เน็ต เพื่อที่อุปกรณ์ที่ใช้งานอยู่นั้นสามารถ download โปรแกรมอันใหม่ผ่านทางเครือข่ายไปปรับปรุงตัวเองได้ หรือสามารถให้ผู้ใช้ปลายทางสามารถที่จะ download โปรแกรมตัวใหม่ทำการ upload ด้วยตัวเองผ่าน wifi เพื่อ upload เข้าไปที่บอร์ด ซึ่งเมื่อบอร์ด upload โปรแกรมใหม่เสร็จก็จะทำการ reboot ตัวเองใหม่เพื่อทำงานตามโปรแกรมที่ได้รับการปรับปรุง

การ upload firmware ผ่านทาง WiFi

บอร์ด (esp8266) ทำการจำลองตัวเองเป็น web server พร้อมกับต่อ WiFi ที่กำหนดเพื่อให้ผู้ใช้ upload file (binary compiled) จากนั้นเราใช้คอมพิวเตอร์เชื่อมต่อ WiFi เดียวกันกับที่บอร์ดเชื่อมต่อแล้วใช้ browser เปิดเข้าไปที่ url ตามที่เรากำหนดใน code เช่น http://ogoswitch-7228204.local/firmware เพื่อทำการ upload binary file ที่เรา compiled แล้วไปยังบอร์ด

ตัวอย่าง code ได้แก่


#include "DNSServer.h"
#include "WiFiManager.h"  

#include "ESP8266HTTPUpdateServer"
#include "ESP8266WebServer.h"
#include "ESP8266mDNS"

const char* host = "ogosense-webupdate";
const char* update_path = "/firmware";
const char* update_username = "admin";
const char* update_password = "ogosense";
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

setup()
{
   WiFiManager wifiManager;
   Serial.begin(115200);
   Serial.println();
   wifiManager.setTimeout(300);
   String APName = "OgoSense-"+String(ESP.getChipId());
   if(!wifiManager.autoConnect(APName.c_str()) ) {
        Serial.println("failed to connect and hit timeout");
        delay(3000);
        // reset and try again, or maybe put it to deep sleep
        ESP.reset();
        delay(5000);
     }

   // if you get here you have connected to the WiFi
   Serial.println("connected...yeey :)");

   // web update OTA
   String host_update_name;
   host_update_name = "ogoswitch-"+String(ESP.getChipId());
   MDNS.begin(host_update_name.c_str());
   httpUpdater.setup(&httpServer, update_path, update_username, update_password);
   httpServer.begin();
   MDNS.addService("http", "tcp", 80);
   Serial.printf("HTTPUpdateServer ready! Open http://%s.local%s in your browser and login with username '%s' and password '%s'\n", host_update_name.c_str(), update_path, update_username, update_password);
}

loop()
{
   httpServer.handleClient();
}

การปรับปรุงโปรแกรมผ่านอินเทอร์เน็ตโดยอัตโนมัติเมื่อมีการปรับปรุง

การให้บอร์ด (IoT) ทำการตรวจสอบและทำการ update ผ่าน web server ที่เรากำหนดไว้ โดยเราจะนำไฟล์ที่ compiled แล้วไปเก็บไว้บน web server และสร้างไฟล์เพื่อระบุหมายเลขรุ่นใส่ไว้ โดยโปรแกรมบนบอร์ดจะทำการเปรียบเทียบเลขดังกล่าว ถ้าเลขในไฟล์บน web server มีค่ามากกว่าบนบอร์ด โปรแกรมบนบอร์ดก็จะทำการ download binary file อันใหม่ตามที่เราระบุชื่อ มาทำการ update บนบอร์ด เมื่อการปรับปรุงเสร็จตัวบอร์ดจะทำการ reboot เพื่อที่จะทำงานบนโปรแกรมอันใหม่ ดังนั้นโปรแกรมอันใหม่ที่เขียนปรับปรุงจะต้องมีการระบุหมายเลขในตัวแปร FW_VERSION ให้ตรงกับตัวเลขในไฟล์ที่ระบุหมายเลขรุ่นล่าสุด เมื่อเราต้องการปรับปรุงโปรแกรมก็เพิ่มค่าตัวเลขดังกล่าวขึ้นไปเช่น ถ้าของเดิมเป็น 1 ก็เปลี่ยนเป็น 2 โดยจะต้องเปลี่ยนทั้งในตัวโปรแกรม (source code) และค่าในไฟล์หมายเลขรุ่น

ตัวอย่าง


#include "DNSServer.h"
#include "ESP8266WebServer.h"
#include "WiFiManager.h"    

#include "Timer.h"

#include "WiFiClient.h"
#include "ESP8266mDNS.h"
#include "ESP8266HTTPUpdateServer.h"
#include "ESP8266HTTPClient.h"
#include "ESP8266httpUpdate.h"

const char* host = "ogosense-webupdate";
const char* update_path = "/firmware";
const char* update_username = "admin";
const char* update_password = "ogosense";
const int FW_VERSION = 1;
const char* firmwareUrlBase = "http://www.ogonan.com/ogoupdate/";
String firmware_name = "ogoswitch_temperature_humidity_nodisplay.ino.ino.d1_mini";
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;
Timer checkFirmware;

void setup()
{
   WiFiManager wifiManager;

   Serial.begin(115200);
   Serial.println();
   wifiManager.setTimeout(300);
   String APName = "OgoSense-"+String(ESP.getChipId());
   if(!wifiManager.autoConnect(APName.c_str()) ) {
     Serial.println("failed to connect and hit timeout");
     delay(3000);
     //reset and try again, or maybe put it to deep sleep
     ESP.reset();
     delay(5000);
   }

  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");

  // web update OTA
  String host_update_name;
  host_update_name = "ogoswitch-"+String(ESP.getChipId());
  MDNS.begin(host_update_name.c_str());
  httpUpdater.setup(&httpServer, update_path, update_username, update_password);
  httpServer.begin();
  MDNS.addService("http", "tcp", 80);
  Serial.printf("HTTPUpdateServer ready! Open http://%s.local%s in your browser and login with username '%s' and password '%s'\n", host_update_name.c_str(), update_path, update_username, update_password);

  checkFirmware.every(86400000L, upintheair);
  upintheair();
}

void loop()
{
  checkFirmware.update();
}

void upintheair()
{
  String fwURL = String( firmwareUrlBase );
  fwURL.concat( firmware_name );
  String fwVersionURL = fwURL;
  fwVersionURL.concat( ".version" );

  Serial.println( "Checking for firmware updates." );
  // Serial.print( "MAC address: " );
  // Serial.println( mac );
  Serial.print( "Firmware version URL: " );
  Serial.println( fwVersionURL );

  HTTPClient httpClient;
  httpClient.begin( fwVersionURL );
  int httpCode = httpClient.GET();
  if( httpCode == 200 ) {
    String newFWVersion = httpClient.getString();

    Serial.print( "Current firmware version: " );
    Serial.println( FW_VERSION );
    Serial.print( "Available firmware version: " );
    Serial.println( newFWVersion );

    int newVersion = newFWVersion.toInt();

    if( newVersion > FW_VERSION ) {
      Serial.println( "Preparing to update" );

      String fwImageURL = fwURL;
      fwImageURL.concat( ".bin" );
      t_httpUpdate_return ret = ESPhttpUpdate.update( fwImageURL );

      switch(ret) {
        case HTTP_UPDATE_FAILED:
          Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
          break;

        case HTTP_UPDATE_NO_UPDATES:
          Serial.println("HTTP_UPDATE_NO_UPDATES");
          break;
      }
    }
    else {
      Serial.println( "Already on latest version" );
    }
  }
  else {
    Serial.print( "Firmware version check failed, got HTTP response code " );
    Serial.println( httpCode );
  }
  httpClient.end();
}

ในตัวอย่างจะมีการตรวจสอบ firmware ใหม่เมื่อเริ่มทำงาน และจะมีการตรวจสอบทุก 24 ชั่วโมง โดยใช้ Timer เรียกใช้ function upintheair()

   checkFirmware.every(86400000L, upintheair);

ogoswitch_temperature_humidity_nodisplay.ino.d1_mini.version

1

การนำโปรแกรมที่ compile แล้วไปไว้บน web server เราสามารถทำได้โดยการ compile โปรแกรมในเมนู Sketch เลือก Export Compiled Binary หรือกด Compile the sketch ด้วยปุ่ม Ctrl+R (windows) หรือ command+r (mac) และ export binary ด้วยปุ่ม Ctrl+Alt+S (windows) หรือ command+option+s (mac) จากนั้นนำ binary file upload ขึ้นไปยัง web server ในตำแหน่งที่กำหนด ตามตัวแปร firmwareUrlBase โดยอย่าลืมสร้างไฟล์เก็บเลขรุ่นแล้ว upload ขึ้นไปด้วยเช่น ogoswitch_temperature_humidity_nodisplay.ino.d1_mini.version โดยในไฟล์ให้ระบุหมายเลขรุ่นตามที่กำหนด เมื่อมีการปรับปรุงก็ให้เพิ่มเลขนี้ขึ้นไปให้มากกว่าเดิม

มีวิธีง่ายง่ายอีกหนึ่งวิธีคือ

   ESPhttpUpdate.update("192.168.0.2", 80, "/arduino.bin");

คำสั่งเดียว โดย 192.168.0.2 ก็เป็น ip address ของ web server, 80 คือหมายเลข port, /arduino.bin ก็เป็น directory และชื่อไฟล์ของโปรแกรมที่ต้องการ update เวลาที่ต้องการ update ก็ทำเป็น function แล้วเรียกใช้งาน อาจจะทำผ่านการกดปุ่มแล้วเรียก function ดังกล่าวก็ได้ สำหรับการใช้งานที่ซับซ้อนและละเอียดมากกว่านี้ก็สามารถดูได้จาก link ที่ให้ไว้ข้างบนสุด

ด้วยวิธีการแบบนี้ต่อให้อุปกรณ์ IoT เราไปอยู่ถึงญี่ปุ่น เราก็ยังสามารถปรับปรุงโปรแกรมเพิ่มความสามารถใหม่ หรือ upload โปรแกรมใหม่ ที่แก้ไข bug แล้วซึ่งอาจเกิดในโปรแกรมรุ่นแรกที่มี bug เพียบบบบบบ 😉 ไปยังอุปกรณ์ดังกล่าวได้โดยไม่ต้องเหาะเอาคอมพิวเตอร์ไปจิ้มที่เครื่อง หรือในกรณีที่คนใช้งานทั่วไปที่ไม่สามารถทำเองได้ วิธีการเหล่านี้ก็สามารถช่วยแก้ไขปัญหาดังกล่าวได้อย่างดี

หมายเหตุ กรุณาเพิ่มเติม code เกี่ยวกับเรื่องความปลอดภัยด้วย เพราะตัวอย่างนี้ไม่รวมเรื่องการตรวจสอบยืนยันเรื่องความปลอดภัย ถ้าไม่ทำท่านอาจถูก hacker ทำการ hack DNS หรือ redirect http request ไปยัง host ของ hacker แล้ว upload โปรแกรมของ hacker แทน เจอแบบนั้นตัวใครตัวมันนะครับท่าน 😛

Advertisements

ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out /  เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out /  เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out /  เปลี่ยนแปลง )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.