我之前介紹了兩篇關(guān)于ESP8266的OTA更新方法:
- 基于 Arduino OTA "ESP8266 OTA之Arduino IDE更新"
- 基于 瀏覽器的 OTA "ESP8266 OTA之瀏覽器更新"
說實(shí)話以上這兩種方案除有點(diǎn)好玩其實(shí)沒有什么實(shí)用性质欲,由其在開發(fā)產(chǎn)品時(shí)上述的兩種方案就是一種雞肋隔披,甚至可以說是不可用的饶氏。那現(xiàn)在市面上的各種智能設(shè)備是怎么更新固件的呢?
我使用過的絕大多數(shù)會(huì)進(jìn)行固件更新的IoT設(shè)備都是先在手機(jī)上提示有新的固件可以更新鬼雀,如果一確定以后就會(huì)自動(dòng)下載并進(jìn)行更新。那我們的ESP8266是否也能做到這樣的效果呢?
答案顯然是肯定的桨菜。本文所講述的OTA做法其實(shí)才是做產(chǎn)品的正道。
首先你要準(zhǔn)備一臺(tái)靜態(tài)文件服務(wù)器捉偏,并將某個(gè)目錄作為更新文件存儲(chǔ)倒得。當(dāng)然,如果你希望以后更方便地管理你發(fā)布的更新夭禽,那么你的更新文件就需要有良好的命名規(guī)則霞掺,關(guān)于這方面可以到百度上去搜一下找個(gè)命名規(guī)范或者按自己需要定義一套就OK了。
然后將自更新邏輯寫入固件:
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#define USE_SERIAL Serial
ESP8266WiFiMulti WiFiMulti;
void setup() {
USE_SERIAL.begin(115200);
// USE_SERIAL.setDebugOutput(true);
USE_SERIAL.println();
USE_SERIAL.println();
USE_SERIAL.println();
for(uint8_t t = 4; t > 0; t--) {
USE_SERIAL.printf("[SETUP] WAIT %d...\n", t);
USE_SERIAL.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP("SSID", "PASSWORD");
}
void loop() {
// wait for WiFi connection
if((WiFiMulti.run() == WL_CONNECTED)) {
t_httpUpdate_return ret = ESPhttpUpdate.update("http://server/file.bin"); // 編譯好的固件文件
switch(ret) {
case HTTP_UPDATE_FAILED:
USE_SERIAL.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
USE_SERIAL.println("HTTP_UPDATE_NO_UPDATES");
break;
case HTTP_UPDATE_OK:
USE_SERIAL.println("HTTP_UPDATE_OK");
break;
}
}
}
看完以上代碼是否是會(huì)有種想罵人的沖動(dòng)讹躯?我當(dāng)時(shí)找資料看ESP的OTA使用的正確姿勢時(shí)也有種想罵娘的沖動(dòng):太TMD簡單了菩彬!
原理就只是ESPhttpUpdate.update
這個(gè)方法,調(diào)用一下就可以從服務(wù)器下載固件并自動(dòng)更新了潮梯!
這個(gè)update
方法是有多個(gè)重載版本的骗灶,上面只是用了其中一個(gè)最簡單的重載版本,因此只有一個(gè)參數(shù)指向更新文件酷麦。
// This function is deprecated, use rebootOnUpdate and the next one instead
t_httpUpdate_return update(const String& url, const String& currentVersion,
const String& httpsFingerprint, bool reboot) __attribute__((deprecated));
t_httpUpdate_return update(const String& url, const String& currentVersion = "");
t_httpUpdate_return update(const String& url, const String& currentVersion,
const String& httpsFingerprint);
t_httpUpdate_return update(const String& url, const String& currentVersion,
const uint8_t httpsFingerprint[20]); // BearSSL
// This function is deprecated, use one of the overloads below along with rebootOnUpdate
t_httpUpdate_return update(const String& host, uint16_t port, const String& uri, const String& currentVersion,
bool https, const String& httpsFingerprint, bool reboot) __attribute__((deprecated));
t_httpUpdate_return update(const String& host, uint16_t port, const String& uri = "/",
const String& currentVersion = "");
t_httpUpdate_return update(const String& host, uint16_t port, const String& url,
const String& currentVersion, const String& httpsFingerprint);
t_httpUpdate_return update(const String& host, uint16_t port, const String& url,
const String& currentVersion, const uint8_t httpsFingerprint[20]); // BearSSL
以下是ESP8266httpUpdate
的其它方法:
void rebootOnUpdate(bool reboot) // 更新后重啟
int getLastError(void); // 獲取最后出錯(cuò)的錯(cuò)誤編號(hào)
String getLastErrorString(void); // 獲取出錯(cuò)的信息描述
固件更新服務(wù)器
以下是ESPhttpUpdate產(chǎn)生的請(qǐng)求頭內(nèi)容:
[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_CHIP_SIZE] => 524288 [HTTP_X_ESP8266_SDK_VERSION] => 1.3.0 [HTTP_X_ESP8266_VERSION] => DOOR-7-g14f53a19
你可以在更新服務(wù)器中檢測這些請(qǐng)求頭是符合法矿卑,同時(shí)也可以檢測當(dāng)前發(fā)出請(qǐng)求的ESP所使用的固件版本是最新的,如果服務(wù)端有新的版本可用就重定向到該文件然后就使ESP進(jìn)行自更新沃饶。
以下是用PHP來寫一個(gè)最簡單的更新服務(wù)器:
<?PHP
header('Content-type: text/plain; charset=utf8', true);
function check_header($name, $value = false) {
if(!isset($_SERVER[$name])) {
return false;
}
if($value && $_SERVER[$name] != $value) {
return false;
}
return true;
}
function sendFile($path) {
header($_SERVER["SERVER_PROTOCOL"].' 200 OK', true, 200);
header('Content-Type: application/octet-stream', true);
header('Content-Disposition: attachment; filename='.basename($path));
header('Content-Length: '.filesize($path), true);
header('x-MD5: '.md5_file($path), true);
readfile($path);
}
if(!check_header('HTTP_USER_AGENT', 'ESP8266-http-Update')) {
header($_SERVER["SERVER_PROTOCOL"].' 403 Forbidden', true, 403);
echo "only for ESP8266 updater!\n";
exit();
}
if(
!check_header('HTTP_X_ESP8266_STA_MAC') ||
!check_header('HTTP_X_ESP8266_AP_MAC') ||
!check_header('HTTP_X_ESP8266_FREE_SPACE') ||
!check_header('HTTP_X_ESP8266_SKETCH_SIZE') ||
!check_header('HTTP_X_ESP8266_CHIP_SIZE') ||
!check_header('HTTP_X_ESP8266_SDK_VERSION') ||
!check_header('HTTP_X_ESP8266_VERSION')
) {
header($_SERVER["SERVER_PROTOCOL"].' 403 Forbidden', true, 403);
echo "only for ESP8266 updater! (header)\n";
exit();
}
$db = array(
"18:FE:AA:AA:AA:AA" => "DOOR-7-g14f53a19",
"18:FE:AA:AA:AA:BB" => "TEMP-1.0.0"
);
if(isset($db[$_SERVER['HTTP_X_ESP8266_STA_MAC']])) {
if($db[$_SERVER['HTTP_X_ESP8266_STA_MAC']] != $_SERVER['HTTP_X_ESP8266_VERSION']) ) {
sendFile("./bin/".$db[$_SERVER['HTTP_X_ESP8266_STA_MAC']]."bin");
} else {
header($_SERVER["SERVER_PROTOCOL"].' 304 Not Modified', true, 304);
}
exit();
}
header($_SERVER["SERVER_PROTOCOL"].' 500 no version for ESP MAC', true, 500);