0

背景

我有一个在 freeRTOS 上运行的小型电池供电系统。根据任何适当的互联网连接设备,我需要定期运行 OTA 更新。问题在于,在电池供电的情况下,该设备 99.9% 的寿命都处于深度睡眠状态。

当设备唤醒时,可以通过发布到设备的 OTA/更新主题从 AWS 发布 OTA 更新。从控制台,您只能使用 QOS = 0。但从内部,比如 lambda,我相信可以使用 QOS = 1。

{
"state": {
        "desired": {
            "ota_url":"https://s3-ap-southeast-2.amazonaws.com/my.awesome.bucket/signed_binary_v21.bin"
        }
    }
}

问题

  1. 如何修改此方法以成功更新一次睡眠 15 分钟并唤醒可能 10 秒的设备。在唤醒期间,它会发送一条消息。是否有某种方法可以将所需的 OTA/更新隐藏在 AWS 的响应中以某种方式包含在内。我还没有弄清楚阴影是如何真正起作用的。或者你可以指定一个重试周期和时间来继续尝试吗?
  2. 从安全角度来看,这种方法是否与最新的最佳实践基本一致:签名二进制、加密闪存和安全启动等。

非常感谢。

4

2 回答 2

1

IDF 附带了一个非常简单的 OTA 示例。正如您对问题的评论中所建议的那样,该示例下载固件更新,如果更新版本存在导致重置的错误,它将自动恢复到以前的图像。这是示例的操作部分:

extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
esp_err_t _ota_http_event_handler(esp_http_client_event_t* evt);
#define FIRMWARE_UPGRADE_URL "https://yourserver.com/somefolder/somefirmware.bin"

void simple_ota_task(void* pvParameter)
{
    esp_http_client_config_t config = {
        .url = FIRMWARE_UPGRADE_URL,
        .cert_pem = (char*)server_cert_pem_start,
        .event_handler = _ota_http_event_handler,
    };
    esp_err_t ret = esp_https_ota(&config);
    if (ret == ESP_OK)
        ESP_LOGW("ota", "Upgrade success");
    else 
        ESP_LOGE(TAG, "Upgrade failure");

    esp_restart();
}

void foo()
{
    // after initializing WiFi, create a task to execute OTA:
    //
    xTaskCreate(&simple_ota_task, "ota_task", 8192, NULL, 5, NULL);
}

// event handler callback referenced above, it has no functional purpose
// just dumps info log output
esp_err_t _ota_http_event_handler(esp_http_client_event_t* evt)
{
    switch (evt->event_id) {
    case HTTP_EVENT_ERROR:
        ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
        break;
    case HTTP_EVENT_ON_CONNECTED:
        ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
        break;
    case HTTP_EVENT_HEADER_SENT:
        ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
        break;
    case HTTP_EVENT_ON_HEADER:
        ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
        break;
    case HTTP_EVENT_ON_DATA:
        ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
        break;
    case HTTP_EVENT_ON_FINISH:
        ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
        break;
    case HTTP_EVENT_DISCONNECTED:
        ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
        break;
    }
    return ESP_OK;
}

要设置嵌入证书,请通过检查 bin 文件所在站点的 SSL 证书来获取公钥,使用 Web 浏览器保存它,然后将其转换为 Base64,即 PEM 格式。(我使用了一些网页。)在我的例子中,我创建了一个与 /main 目录相同级别的目录,称为 /server_certs,并将 Base64 转换保存在该目录中作为 ca_cert.pem。(这也太迂腐了吧?)

然后将这些行添加到 /main 目录中的 CMakeFiles.txt 中:

# Embed the server root certificate into the final binary
set(COMPONENT_EMBED_TXTFILES ${IDF_PROJECT_PATH}/server_certs/ca_cert.pem)
register_component()

我不清楚的是如何确定是否有更新的版本可用,如果有一个我无法辨别的内置方式,并且不想无缘无故下载更新,永远。所以我推出了自己的版本,在固件中嵌入了一个版本字符串,在我的服务器上创建了一个返回当前版本的 WebAPI 入口点(两者都是手工维护的,直到我能想到更好的方法......我想实现)和当字符串不匹配时调用 OTA 功能。它看起来很像这样:

// The WebAPI returns data in JSON format, 
// so if a method returns a string it is quoted.
#define FW_VERSION "\"00.09.015\""

// the HTTP request has just completed, payload stored in recv_buf
//
// tack a null terminator using an index maintained by the HTTP transfer
recv_buf[pos + 1] = 0;
// test for success
if (strncmp(recv_buf, "HTTP/1.1 200 OK", 15) == 0)
{
    // find the end of the headers
    char *p = strstr(recv_buf, "\r\n\r\n");
    if (p)
    {
        ESP_LOGI("***", "version: %s  content %s", FW_VERSION, (p + 4));
        if (strcmp((p + 4), FW_VERSION) > 0)
        {
            // execute OTA task
            foo();
        }
        else
        {
            // Assumes the new version has run far enough to be 
            // considered working, commits the update.  It doesn't
            // hurt anything to call this any number of times.
            esp_ota_mark_app_valid_cancel_rollback();
        }
    }
}

如果您不使用 CMake/Ninja 构建工具,请考虑检查一下,它比基于 MingW32 的工具集快得多。

于 2020-12-15T14:03:32.987 回答
0

正如您所提到的,您并不完全了解阴影的工作原理,让我解释一下如何在与您类似的场景中使用阴影。

通常,长时间休眠并在短时间内唤醒的设备通常会在唤醒时向设备影子发出请求。如果阴影包含

{"state": {"desired":{"ota_url": "xxx", "do_ota": true, ...}}},

在这种情况下,设备应该在设备内部启动 ota 更新代码。

此外,这里提到的方法与安全性和最佳实践(签名二进制、加密闪存和安全启动)无关。这三件事都应该由ota更新程序处理。阴影仅用于表示有可用的 ota 更新。使用影子可以帮助您避免向 OTA 服务器发出不必要的 GET 请求,从而节省宝贵的电力和计算资源。

于 2021-09-11T15:22:59.773 回答