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.
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 SerialESP8266 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.
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
const char* ssid = "..........";
const char* password = "..........";
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
#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.
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.
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:
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 ESP8266WebServer
và
ESP8266mDNS
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
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
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
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
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ớiNế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";
const char *serverUrl = "http://192.168.0.106:8000/firmware.bin";
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() {
}
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ại1.0
và nạp vào boardĐườ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-Length
và x-MD5
Bạn cần file esp8266-firware-2.0.bin
ở mục
??? 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
Latest comments (0)