Firmware update over the air (FOTA)

Firmware update over the air (FOTA)

Các phương pháp phát triển phần mềm và sản phẩm phổ biến hiện nay, thì xuất bản kết quả từng giai đoạn thường mang lại hiệu quả cao, sản phẩm có thể đến tay người dùng sớm, nhận được phản hồi sớm từ khách hàng, và được điều chỉnh để hợp lý hơn. Chính việc phát hành sản phẩm sớm thường sẽ ít tính năng và cần cập nhật thêm tính năng, nâng cao chất lượng sản phẩm trong tương lai.

Cập nhật ứng dụng từ xa trên các phần mềm điện thoại, máy tính đã rất phổ biến. Các phần mềm được cập nhật hàng tuần, tháng…​ để sửa lỗi, nâng cấp tính năng.

Đối với các sản phẩm phần cứng cũng tương tự, chúng ta nên bổ sung các tính năng cập nhật từ xa ngay từ giai đoạn phát triển sản phẩm. Ngoài việc giúp nâng cấp các tính năng trong tương lai một cách dễ dàng, thì vấn để sửa lỗi, nâng cấp hệ thống từ xa sẽ giúp tiết kiệm được rất nhiều chi phí và nguồn lực.

Trong phần này, chúng ta sẽ tìm hiểu các phương pháp cập nhật từ xa cho ESP8266, làm sao để nạp Firmware không dây cho module, làm sao để ESP8266 có thể tự tải Firmware về, làm sao để ESP8266 có thể tự khởi động 1 HTTP Server để có giao diện Web upload firmware lên chip.

Cập nhật firmware từ xa

Cập nhật firmware OTA (Over the Air) là tiến trình tải firmware mới vào ESP8266 module thay vì sử dụng cổng Serial. Tính năng này thực sự rất hữu dụng trong nhiều trường hợp giới hạn về kết nối vật lý đến ESP Module.

OTA có thể thực hiện với:

  • Arduino IDE

  • Web Browser

  • HTTP Server

Sử dụng OTA với tùy chọn dùng Arduino IDE trong quá trình phát triển, thử nghiệm, 2 tùy chọn còn lại phù hợp cho việc triển khai ứng dụng thực tế, cung cấp tính năng cập nhật OTA thông qua web hay sử dụng HTTP Server.

Trong tất cả các trường hợp, thì Firmware hỗ trợ OTA phải được nạp lần đầu tiên qua cổng Serial, nếu mọi thứ hoạt động trơn tru, logic ứng dụng OTA hoạt động đúng thì có thể thực hiện việc cập nhật firmware thông qua OTA.

Sẽ không có đảm bảo an ninh đối với quá trình cập nhật OTA bị hack. Nó phụ thuộc vào nhà phát triển đảm bảo việc cập nhật được phép từ nguồn hợp pháp, đáng tin cậy. Khi cập nhật hoàn tất, ESP8266 sẽ khởi động lại và thực thi code mới. Nhà phát triển phải đảm bảo ứng dụng thực trên module phải được tắt và khởi động lại 1 cách an toàn. Nội dung bên dưới cung cấp bổ sung các thông tin về an ninh, và an toàn cho tiến trình cập nhật OTA.

Bảo mật

Khi ESP8266 được phép thực thi OTA, có nghĩa nó được kết nối mạng không dây và có khả năng được cập nhập Sketch mới. Cho nên khả năng ESP8266 bị tấn công sẽ nhiều hơn và bị nạp bởi mã thực thi khác là rất cao. Để giảm khả năng bị tấn công cần xem xét bảo vệ cập nhật của bạn với một mật khẩu, cổng sử dụng cố định khác biệt, v.v…

Kiểm tra những tính năng được cung cấp bởi thư viện ArduinoOTA thường xuyên, có thể được nâng cấp khả năng bảo vệ an toàn:

void setPort(uint16_t port);
void setHostname(const char* hostname);
void setPassword(const char* password);

Một số chức năng bảo vệ đã được xây dựng trong và không yêu cầu bất kỳ mã hóa nào cho nhà phát triển. ArduinoOTA và espota.py sử dụng Digest-MD5 để chứng thực việc tải firmware lên. Đơn giản là đảm bảo tính toàn vẹn của firmware bằng việc tính MD5.

Hãy phân tích rủi ro cho riêng ứng dụng của bạn và tùy thuộc vào ứng dụng mà quyết định những chức năng cũng như thư viện để thực hiện. Nếu cần thiết, có thể xem xét việc thực hiện các phương thức bảo vệ khỏi bị hack, ví dụ như cập nhật OTA chỉ cho tải lên chỉ theo lịch trình cụ thể, kích hoạt OTA chỉ được người dùng nhấn nút chuyên dụng “Cập nhật”, v.v…

An toàn

Quá trình OTA tiêu tốn nguồn tài nguyên và băng thông của ESP8266 khi tải lên. Sau đó, ESP8266 được khởi động lại và một Sketch mới được thực thi. Cần phân tích và kiểm tra ảnh hưởng của quá trình này tới các chức năng cũ và sketch mới của ESP module.

Nếu ESP được đặt ở xa và điều khiển một vài thiết bị, ta nên chú ý tới hoạt động của thiết bị nếu thiết bị ngừng hoạt động đột xuất do quá trình cập nhật. Do đó, ta cần phải xác định được trạng thái làm việc an toàn của thiết bị trước quá trình cập nhật. Ví dụ, module được dùng để điều khiển hệ thống tưới nước tự động trong vườn. Nếu trong quá trình hoạt động mà hệ thống điều khiển bị tắt đột ngột và các van bị mở, thì cả vườn sẽ bị ngập nước.

Các hàm sau đây được cung cấp bởi thư viện ArduinoOTA và được dùng để xử lý ứng dụng trong quá trình cập nhật OTA hoặc để xử lý khi OTA gặp lỗi:

void onStart(OTA_CALLBACK(fn));
void onEnd(OTA_CALLBACK(fn));
void onProgress(OTA_CALLBACK_PROGRESS(fn));
void onError(OTA_CALLBACK_ERROR (fn));

Yêu cầu căn bản

Bộ nhớ Flash phải có đủ dung lượng để lưu cả sketch cũ (đang vận hành trên hệ thống) và sketch mới (cập nhật OTA).

Hệ thống File và EEPROM cũng cần dung lượng để lưu trữ.

Hàm ESP.getFreeSketchSpace(); được dùng để kiểm tra dung lượng trống cho sketch mới.

Update process – memory view

  • Sketch mới sẽ được chứa trong dung lượng trống gĩưa sketch cũ và spiff.

  • Trong lần reboot tiếp theo thì “eboot” bootloader kiểm tra các câu lệnh.

  • Sketch mới sẽ được copy.

  • Sketch mới khởi động.

update

OTA sử dụng Arduino IDE

Thưc hiện OTA với Arduino IDE chỉ nên thưc hiện khi:

  • Sử dụng nạp firmware cho module ESP8266 thông qua WiFi mà không dùng cổng Serial

  • ESP8266 và máy tính chạy Arduino IDE sử dụng chung mạng WiFi nội bộ

  • ESP8266 đã kết nối thành công vào mạng WiFi

  • Nạp firmware đã hỗ trợ OTA thông qua cổng Serial trước đó

Trước khi bắt đầu, cần phải chắc chắn Arduino IDE đã được cài đặt phiên bản mới nhất, bao gồm gói ESP8266 cho Arudino, và Python 2.7

Bước 1: nạp firmware hỗ trợ OTA thông qua cổng Serial

Mở sketch ví dụ mẫu BasicOTA.ino. Vào File > Examples > ArduinoOTA.

00
Hình 1. Mở sketch ví dụ mẫu OTA

Cung cấp chính xác SSID và mật khẩu mạng WiFi đang dùng để ESP8266 có thể kết nối vào

Cung cấp SSID và password
const char* ssid = "..........";
const char* password = "..........";
Tùy vào phiên bản và board ESP bạn sử dụng, bạn có thể thấy Upload Using: trong menu. Lựa chọn này sẽ không hoạt động và không ảnh hưởng tới lựa chọn của bạn. Chức năng này tương thích với các phiên bản OTA cũ và bị gỡ bỏ ở platform package version 2.2.0.

Upload sketch Ctrl+U Khi hoàn thành, mở Serial Monitor Ctrl+Shift+M và kiểm tra module đã trup cập được WiFi chưa

basicota
Hình 2. Kiểm tra module ESP8266 đã truy cập được mạng WiFi nội bộ chưa
ESP module nên được reset sau khi Upload xong firmware
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "...";
const char* password = "...";

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.setPassword((const char *)"ota-pass");
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
}

Bước 2: Lựa chọn cổng nạp thông qua OTA

Khi module kết nối tới mạng WiFi thành công, sau vài giây, cổng esp8266-ota sẽ xuất hiện trên Arduino IDE. Lúc này bạn hoàn toàn có thể bỏ kết nối Serial từ board mạch đến máy tính. Arduino IDE có thể nạp firmware mới thông qua WiFi. Chọn port với địa chỉ IP hiện trên cửa sổ Serial Monitor ở bước trước.

select port
Hình 3. Chọn cổng nạp từ Network
Nếu cổng OTA không hiện lên, bạn cần thoát Arduino IDE, và mở lại. Kiểm tra lại port OTA. Nếu vẫn tiếp tục không hiển thị cổng OTA, kiểm tra tưòng lửa của máy và các cài đặt trên router.

Bước 3: Sửa firmware mới và nạp lại thông qua WiFi

Sau khi đã chọn đúng cổng nạp OTA, bạn hoàn toàn có thể sửa lại firmware mới và nạp thông qua WiFi, tuy nhiên cần lưu ý như sau:

  • Firmware mới phải đảm bảo kết nối đến WiFi không bị mất (ví dụ cấp sai mật khẩu)

  • Firmware mới phải có các hàm khởi tạo và xử lý OTA như Bước 1.

ota ok
Hình 4. Nạp firmware thành công thông qua OTA

Sử dụng mật khẩu

Bảo vệ quá trình upload OTA với password là một quá trình khá đơn gỉản. Những việc bạn cần làm là bổ sung đoạn mã nguồn:

ArduinoOTA.setPassword((const char *)"your-password");

Sau đó upload lại sketch một lần nữa (dùng OTA). Sau khi biên dịch và upload xong, cửa sổ sẽ hiện lên yêu cầu nhập password:

ota pass
Hình 5. Password cho OTA

Nhập password, nếu đúng, kết quả là thông báo Authenticating…​OK và quá trình nạp diễn ra bình thường.

Các lần nạp sau Arduino IDE sẽ nhớ mật khẩu và không hỏi lại, trừ khi bạn thay đổi mật khẩu OTA, và các bước xác thực không thành công, Arduino IDE sẽ hỏi lại bạn.

Cần lưu ý là password có thể dễ dàng thấy được, nếu IDE không được đóng sau lần upload cuối cùng. Việc này có thể được thực hiện bằng cách cho phép Show verbose ouput during: upload trong File > Preferences và upload lên module.

Những sự cố thường gặp

Nếu việc cập nhật OTA thất bại, bước đầu tiên bạn cần làm là kiểm tra phần báo lỗi hiện trên cửa sổ Log của Arduino IDE. Nếu việc này không giúp được bạn, hãy upload lại khi kiểm tra các thông tin của ESP hiện trên serial port.

Các nguyên nhân phổ biến gây lỗi OTA như sau:

  • Không đủ dung lượng bộ nhớ trên chip (ví dụ như ESP01 với 512KB bộ nhớ flash không đủ cho OTA).

  • Khu vực dữ liệu cho SPIFFS quá nhiều, không còn đủ để chưa firmware, trong trường hợp bạn có 4MB Flash thì trường hợp này không xảy ra.

  • Khu vực bộ nhớ chưa firmware quá ít, tối thiểu là 512KB

  • Không reset module ESP sau lần upload đầu dùng Serial Port.

Cập nhật Firmware dùng Web Browser

Khi thực hiện cập nhật firmware dùng Web Browser, ESP8266 sẽ khởi động 1 HTTP Server, với 1 form upload. Khi truy cập đúng địa chỉ của nó, bạn sẽ được cung cấp 1 giao diện để chọn file binary, và upload lên Chip. Việc này hữu dụng khi không dùng Arudino IDE cho việc cập nhật, sử dụng luôn trình duyệt sẵn có. Hoặc tích hợp vào 1 ứng dụng mà bạn có thể muốn cập nhật nó trong tương.

Cập nhật với web browser được thực hiện bằng thư viện ESP8266HTTPUpdateServer cùng với 2 thư viện khác ESP8266WebServerESP8266mDNS cho việc nhận diện ESP8266 trong mạng nội bộ.

Thực hiện

Mở ví dụ: File > Examples > ESP8266HTTPUpdateServer > WebUpdater

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>

const char* host = "esp8266-webupdate";
const char* ssid = "...";
const char* password = "...";

ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

void setup(void){

  Serial.begin(115200);
  Serial.println();
  Serial.println("Booting Sketch...");
  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);

  while(WiFi.waitForConnectResult() != WL_CONNECTED){
    WiFi.begin(ssid, password);
    Serial.println("WiFi failed, retrying.");
  }

  MDNS.begin(host);
  httpUpdater.setup(&httpServer);
  httpServer.begin();

  MDNS.addService("http", "tcp", 80);
  Serial.printf("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", host);
}

void loop(void){
  httpServer.handleClient();
}

Cung cấp đúng SSID và mật khẩu mạng WiFi máy tính bạn đang dùng, nạp Firmware WebUpdater vào board NodeMCU

Lưu ý là máy tính phải sử dụng mạng WiFi cùng với board NodeMCU

Khi bạn không thể truy cập vào module ESP8266 theo công Serial, thì để nhận diện được địa chỉ IP của module trong mạng LAN, bạn cần chạy dịch vụ mDNS trên máy tính. Dịch vụ này sẵn có trong MacOS, tuy nhiên, với Linux thì bạn cần cài đặt Avahi: avahi.org/, còn Windows thì Bonjour: support.apple.com/downloads/bonjour_for_windows

Với dịch vụ mDNS chạy trên máy tính, bạn dễ dàng truy cập vào ESP8266 theo đường dẫn esp8266-webupdate.local/update

web updater
Hình 6. Giao diện Web cập nhật firmware

Bằng cách chọn file và nhấn cập nhật, ESP8266 sẽ tiến hành cập nhật firmware mới do bạn gởi lên. File này có thể được xuất ra bằng cách Sketch > Export compiled Binary, và file .bin sẽ nằm trong thư mục của Sketch

export fw
Hình 7. Xuất file Binary
Khi đã nhập esp8266-webupdate.local/update mà không thưc hiện được, hãy thay esp8266-webupdate với địa chỉ IP của module (bạn có thể xem trong modem/router, hay Serial Terminal). Ví dụ, nếu IP của module là 192.168.1.100 thì URL phải là 192.168.1.100/update. Phương pháp này hữu hiệu trong trường hợp host software cài đặt ở bước 1 không hoạt động.

Nếu các bước diễn ra thành công tốt đẹp, thì trên trình duyệt và cửa sổ Serial Terminal (nếu mở) như hình

finish
Hình 8. Thực hiện thành công cập nhật Firmware sử dụng WebUpdater

Bảo mật

Nếu bổ sung WebUpdater vào sản phẩm của mình, dĩ nhiên bạn sẽ không muốn người khác tự do đưa vào thiết bị 1 firmware khác. Hãy sử dụng hàm httpUpdater.setup(&httpServer, update_path, update_username, update_password); với các thông số username, password mà bắt buộc bạn phải nhập đúng mới được phép upload firmware mới.

Mở ví dụ : File > Examples > ESP8266HTTPUpdateServer > SecureWebUpdater để xem chi tiết

HTTP Server

Với 2 phương pháp trước, bạn dễ dàng cập nhật Firmware thông qua mạng WiFi nội bộ. Tuy nhiên, khi triển khai ứng dụng thực tế, chúng ta sẽ cần cập nhật Firmware từ xa thông qua Internet, và cần 1 Server để lưu trữ firmware, quản lý các phiên bản.

Một kịch bản phổ thông thường được làm nhất đó là:

  • Khi ESP8266 khởi động (khoảng sau 1 khoảng thời gian – ví dụ như 1 ngày), nó sẽ kết nối đến Server, cung cấp thông tin phiên bản hiện có của nó

  • Server khi nhận thấy phiên bản hiện tại cần phải được nâng cấp, nó sẽ trả về firmware mới

  • Nếu phiên bản hiện tại của ESP8266 không cần phải cập nhật, nó sẽ trả về mã 304.

Để thực hiện được điều này, chúng ta cần thực hiện trên cả ESP8266 và trên Server side. Thử nghiệm trong mục này, chúng ta sẽ dùng Node.js làm server. Bạn hoàn toàn có thể thực thi đoạn code Server này và gán cho nó domain để có thể truy cập từ bất kỳ đâu.

ESP8266 ESPhttpUpdate

Bằng cách thực thi ESPhttpUpdate.update("your-domain.com", 8000, "/fimrware.bin");, ESP8266 sẽ tự động kết nối tới server ở địa chỉ your-domain.com:8000/fimrware.bin để tải phiên bản firmware mới về. Mã HTTP Status Code:

  • (Mã) 200: Nếu tồn tại firmware mới, và nội dung file sẽ được gởi kèm sau đó

  • (Mã) 304: Thông báo là không có bản update mới.

Đoạn mã có thể dễ dàng tìm thấy ở File > Examples > ESPhttpUpdate > httpUpdate

Bạn cần cung cấp SSID, mật khẩu WiFi chính xác. Nạp Code vào board NodeMCU

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>

const char* ssid = "...";
const char* password = "...";
const char *currentVersion = "1.0"; <i class="conum" data-value="1"></i><b>(1)</b>
const char *serverUrl = "http://192.168.0.106:8000/firmware.bin"; <i class="conum" data-value="2"></i><b>(2)</b>
void setup() {

  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.print("ESP8266 http update, current version: ");
  Serial.println(currentVersion);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  t_httpUpdate_return ret = ESPhttpUpdate.update(serverUrl, currentVersion);
  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;
    case HTTP_UPDATE_OK:
      Serial.println("HTTP_UPDATE_OK");
      break;
  }

}

void loop() {
}
1 Phiên bản firmware của bạn, giả sử bạn thay đổi là 2.0 và đặt lên server, sau đó bạn đổi lại 1.0 và nạp vào board
2 Đường dẫn đến firmware của bạn, là địa chỉ web, ip, hay domain

Node.js OTA Server

Khi ESP8266 kết nối tới Web Server, thì nó sẽ cung cấp các thông tin Header để Server căn cứ vào đó đánh giá firmware có cần phải cập nhật hay không. Ví dụ về các header

[HTTP_USER_AGENT] => ESP8266-http-Update
[HTTP_X_ESP8266_STA_MAC] => 18:FE:AA:AA:AA:AA
[HTTP_X_ESP8266_AP_MAC] => 1A:FE:AA:AA:AA:AA
[HTTP_X_ESP8266_FREE_SPACE] => 671744
[HTTP_X_ESP8266_SKETCH_SIZE] => 373940
[HTTP_X_ESP8266_SKETCH_MD5] => a56f8ef78a0bebd812f62067daf1408a
[HTTP_X_ESP8266_CHIP_SIZE] => 4194304
[HTTP_X_ESP8266_SDK_VERSION] => 1.3.0
[HTTP_X_ESP8266_VERSION] => 1.0

Dựa trên kiến thức phần server-nodejs, chúng ta xây dựng 1 OTA Server dùng Node.js như sau

server.js
var fs = require('fs');
var url = require('url');
var http = require('http');
var querystring = require('querystring');
var crypto = require('crypto');
// function gửi yêu cầu(response) từ phía server hoặc nhận yêu cầu (request) của client gửi lên
function requestHandler(request, response) {

    // Giả sử địa chỉ yêu cầu firmware http://192.168.1.7:8000/firmware.bin
    var uriData = url.parse(request.url);
    var pathname = uriData.pathname;          // /firmware.bin

    if (pathname == '/firmware.bin') {
        var ver = request.headers['x-esp8266-version'];
        console.log('Client request update, version ', ver);
        if(ver == '1.0') {
            console.log('Send firmware 2.0 to client');
            fs.readFile('./esp8266-firmware-2.0.bin', function(error, content) {
                response.writeHead(200, {
                    'Content-Type': 'binary/octet-stream',
                    'Content-Length': Buffer.byteLength(content),
                    'x-MD5': crypto.createHash('md5').update(content).digest("hex")
                });
                response.end(content);
            });
        } else {
            response.statusCode = 304;
            response.end();
        }
    }
}
var server = http.createServer(requestHandler);
server.listen(8000);
console.log('Server listening on port 8000');

Thực thi node server.js để khởi động Server

Khi bạn làm việc với các ngôn ngữ lập trình khác, luôn phải đảm bảo khi gởi về client cần có đầy đủ thông tin header Content-Lengthx-MD5
Bạn cần file esp8266-firware-2.0.bin ở mục [esp8266-ESPhttpUpdate] biên dịch với currentVersion = "2.0" đặt cùng thư mục với file server.js

Nếu bạn thực hiện đầy đủ các bước như trên, kết quả thực thi sẽ như hình

http ota
Hình 9. Kết quả thực hiện OTA sử dụng HTTP Server

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *