ESP8266 OTA 更新:自更新的 OTA 固件

作为我最近的项目的一部分,我已经开始包括我的 ESP8266 设备的 OTA 固件更新。(也被称为FOTA)

这样做实际上非常简单,这要归功于 Arduino 开发板支持的包所提供的非常好的支持。最困难的事情实际上变成了设置 Web 服务器端,而不是设备本身所需的变化。

步骤1:固件(Firmware)服务器

固件服务器可以是通过 HTTP 可访问的任何 Web 服务器。(或 HTTPS,稍后会回到)

固件文件夹

对于我的项目,我决定创建一个包含固件镜像的文件夹,并为每个镜像指定一个名称,这个名称来源于它适用的 ESP8266 设备的 MAC 地址。每个固件镜像都是一个简单的文本文件,其中一行包含内部版本号/版本号。

固件版本文件

在我的环境中,我使用一个 32 位整数来标识内部版本号。这并不是因为,我相信我将不会发布超过 65536 个固件版本,但是因为这给了我更多的灵活性,例如我通过直接在数字中表示版本号来格式化版本号。 但是,为了简单起见,为了这个例子,我只是在里面放一个简单的版本号。

当然,组织和命名固件镜像的细节完全取决于您。例如,如果您要执行较大的生产运行,则可以使用设备型号作为固件镜像命名约定的基础。

步骤2:发布固件镜像

在所有的函数外,将一个版本号常量变量添加到您的程序。每当您准备发布时,请按您选择的任何约定来增加此版本号,例如每次简单增加一个,或基于当前日期和增加的内部版本号等更复杂的规则。

const int FW_VERSION = 1244;

编译程序(Ctrl + R),然后导出二进制文件。(Ctrl + Alt + S)导出二进制文件,将生成一个镜像文件到您的程序所在的相同文件夹中。实际名称将取决于您正在编译的特定电路板。对于 Adafruit Huzzah ESP8266 扩展板,名称与主的(main).ino 文件相同,但附加了 .adafruit.bin。

镜像文件

这个文件需要被复制到网络服务器。您如何做到这一点,取决于您使用的 Web 服务器的类型。我在这里展示了一个例子,基于 Apache 2 在我用于编译的计算机上运行。根据您的操作系统和 Web 服务器,您的路径可能会有所不同。

cp WifiTempMonitor.ino.adafruit.bin /var/www/html/fota/5ccf7fXXXXXX.bin

接下来,在固件发布之后,更新版本文件中的版本号并发布。这需要在固件镜像之后完成,否则无线设备可能会看到更新的版本文件并尝试下载新的固件。如果您的新固件,当时没有在服务器上准备好,则设备可能会收到旧版本或不完整的文件。 不完整的文件可能会阻止您的设备。。。

步骤3:增加对 ESP8266 设备的 OTA 支持

如果您尚未包含 OTA 支持库和 HTTP 客户端库,请首先添加 #include 语句。

#include <esp8266httpclient.h>
#include <esp8266httpupdate.h>

为当前的固件版本添加一个全局常量变量:

const int FW_VERSION = 1244;

接下来,我们需要告诉设备:在哪里找到固件更新。我们有一个镜像和一个版本文件,由设备 MAC 组织,所以我们需要定义一个基本的 URL,我们可以从中导出实际的 URL。

const char* fwUrlBase = "http://192.168.0.1/fota/";

确切地说,在您的代码中添加固件检查和更新取决于您。唯一的要求是,由于显而易见的原因,WiFi 必须在启用和连接的时间。在我的气象站设备中,我将在所有测量完成之后进行检查,并在设备恢复深度睡眠之前上载到服务器。 我们将定义一个函数来检查和更新,然后从程序中的适当位置调用它。

我们将构建 URL 以下载当前可用固件映像的版本号。要获取设备的 MAC 地址,请调用 getMAC(),它将以 12 位十六进制数字的形式返回。

void checkForUpdates() {
  String mac = getMAC();
  String fwURL = String( fwUrlBase );
  fwURL.concat( mac );
  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 );

下载版本字符串,并将其转换为 32 位整数:

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();

当连接到本地固件服务器时,此检查需要大约 20 毫秒的额外时间,并使用一点电池电量。就其本身而言,这比能量读数中的背景噪声要小,所以我不会太在意固件更新引起的额外功耗。

我们将新的固件版本号与我们的全局常量变量中定义的当前版本进行比较。如果服务器上的版本比我们运行的版本更新,则构建 URL 以下载固件映像并将其传递给 OTA 更新程序。

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

  String fwImageURL = fwURL;
  fwImageURL.concat( ".bin" );

  t_httpUpdate_return ret = ESPhttpUpdate.update( fwImageURL );

OTA 更新程序下载镜像并将其写入闪存。如果 OTA 更新程序成功,它将永远不会返回,因为它将重新启动 ESP8266。更新者(ESP8266 客户端)可能有失败的原因,所以我们必须准备好处理这样的失败。通常的原因是由于某些原因无法下载固件镜像文件,或者下载时连接丢失。这样的错误不会使您的设备处于不可用状态,因为实际的固件刷新过程还没有开始。

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

   case HTTP_UPDATE_NO_UPDATES:
      Serial.println("HTTP_UPDATE_NO_UPDATES");
      break;
  }
}

如上所述,HTTP_UPDATE_NO_UPDATES 情况不会发生在固件服务器上。它与固件服务器一起使用,可以将当前版本与可用版本进行比较。我将在稍后回顾,但如果您愿意的话,现在可以从代码中删除这 3 行代码。

我将在这里重复整个函数以及一些额外的调试输出,以方便复制和粘贴。

void checkForUpdates() {
  String mac = getMAC();
  String fwURL = String( fwUrlBase );
  fwURL.concat( mac );
  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 &gt; 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();
}

这段代码依赖于一个 getMAC() 函数。 我原本在这个帖子里漏掉了。感谢Akshay指出了这一点,并感谢 Mike Kranidis 指点我在 ESP 上支持的 sprintf()

String getMAC()
{
  uint8_t mac[6];
  char result[14];

 snprintf( result, sizeof( result ), "%02x%02x%02x%02x%02x%02x", mac[ 0 ], mac[ 1 ], mac[ 2 ], mac[ 3 ], mac[ 4 ], mac[ 5 ] );

  return String( result );
}

步骤4:提供当前的固件版本号

知道每个设备上,当前正在运行哪个固件版本是非常有用的。这通常显示在 ESP8266 设备的内置 Web 服务器提供的网页上,或通过串口上的调试输出显示。但是如何处理,在绝大多数时间处于深度睡眠状态的电池供电设备?将没有串行或 WiFi 连接并运行您检查固件版本。

由于我的设备已经使用 MQTT,将传感器读数传输到中央服务器,因此我只是为固件版本定义一个额外的通道。(此代码假定您已经配置并连接了您的 MQTT 连接)

Adafruit_MQTT_Publish publish_version = Adafruit_MQTT_Publish( &amp;mqtt;, MQTT_USERNAME "/feeds/" MQTT_NAME "/fw_version", MQTT_QOS_0, 1 );


 publish_version.publish( FW_VERSION );

上面的发布者构造函数的最后一个参数是一个标志,告诉 MQTT 消息应该保留。

未来的改进

我所描述的更新过程中有一些可以改进的地方。

提供 HTTPS 支持将有助于保持传输中加密的固件图像,并且还可以降低由 ESP8266 设备和固件服务器之间的流量,截获人员提供的固件镜像受损的风险。

还可以构建更智能的固件服务器以避免需要单独的版本文件。 但是,这虽然简化了设备上的代码,但也增加了服务器端的复杂性。

原文链接:https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/

尚未评分
您的评分将帮助我们做出更好的玩法

观光\评论区

Copyright © 2017 玩点什么. All Rights Reserved.