feat(ota): add authenticated firmware update workflow
- report firmware versions at startup and every six hours with a fresh device JWT - support optional HMAC headers and retry once after authorization rejection - handle forced and confirmed updates through managed OTA state - stream images to the inactive partition and verify size, SHA-256, and format - coordinate OTA HTTPS activity with voice teardown and restoration - confirm pending images and expose configuration, status, and integration docs
This commit is contained in:
@@ -8,6 +8,7 @@ idf_component_register(
|
||||
"src/fortune/domain_fortune_store.c"
|
||||
"src/fortune/domain_user_config_server.c"
|
||||
"src/runtime/domain_runtime_adapters.c"
|
||||
"src/runtime/domain_ota.c"
|
||||
"src/runtime/domain_local_command.c"
|
||||
"src/runtime/domain_mini_program_provisioning.c"
|
||||
INCLUDE_DIRS
|
||||
@@ -19,6 +20,7 @@ idf_component_register(
|
||||
esp-sr
|
||||
log
|
||||
json
|
||||
mbedtls
|
||||
EMBED_TXTFILES
|
||||
"assets/user_config.html"
|
||||
)
|
||||
|
||||
@@ -248,6 +248,7 @@ void domain_runtime_read_device_identity(char *device_id,
|
||||
size_t device_id_len,
|
||||
char *serial_number,
|
||||
size_t serial_number_len);
|
||||
esp_err_t domain_runtime_read_firmware_version(char *out, size_t out_len);
|
||||
|
||||
esp_err_t domain_runtime_write_device_identity(const char *device_id, const char *serial_number);
|
||||
|
||||
@@ -404,6 +405,64 @@ esp_err_t domain_runtime_post_json(const char *url,
|
||||
size_t *out_response_body_len,
|
||||
int *out_status);
|
||||
|
||||
#define DOMAIN_OTA_VERSION_MAX_LEN 32
|
||||
#define DOMAIN_OTA_URL_MAX_LEN 2048
|
||||
#define DOMAIN_OTA_SHA256_HEX_LEN 64
|
||||
|
||||
typedef enum {
|
||||
DOMAIN_OTA_REPORT_NO_UPDATE = 0,
|
||||
DOMAIN_OTA_REPORT_UPDATE_AVAILABLE,
|
||||
DOMAIN_OTA_REPORT_SERVER_ERROR,
|
||||
} domain_ota_report_outcome_t;
|
||||
|
||||
typedef struct {
|
||||
const char *report_url;
|
||||
const char *device_id;
|
||||
const char *serial_number;
|
||||
const char *current_version;
|
||||
const char *device_jwt;
|
||||
const char *internal_auth_secret;
|
||||
int timeout_ms;
|
||||
} domain_ota_report_request_t;
|
||||
|
||||
typedef struct {
|
||||
char version[DOMAIN_OTA_VERSION_MAX_LEN];
|
||||
char url[DOMAIN_OTA_URL_MAX_LEN];
|
||||
char sha256[DOMAIN_OTA_SHA256_HEX_LEN + 1];
|
||||
uint64_t size_bytes;
|
||||
bool forced;
|
||||
bool confirmed;
|
||||
} domain_ota_firmware_t;
|
||||
|
||||
typedef struct {
|
||||
domain_ota_report_outcome_t outcome;
|
||||
int http_status;
|
||||
int business_code;
|
||||
domain_ota_firmware_t firmware;
|
||||
} domain_ota_report_response_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t free_internal;
|
||||
uint32_t largest_internal;
|
||||
uint32_t largest_dma;
|
||||
} domain_ota_memory_headroom_t;
|
||||
|
||||
typedef void (*domain_ota_progress_callback_t)(uint64_t bytes_received,
|
||||
uint64_t total_bytes,
|
||||
void *user_data);
|
||||
typedef bool (*domain_ota_cancel_callback_t)(void *user_data);
|
||||
|
||||
esp_err_t domain_runtime_ota_report(const domain_ota_report_request_t *request,
|
||||
domain_ota_report_response_t *response);
|
||||
esp_err_t domain_runtime_ota_install(const domain_ota_firmware_t *firmware,
|
||||
int timeout_ms,
|
||||
domain_ota_progress_callback_t progress_cb,
|
||||
domain_ota_cancel_callback_t cancel_cb,
|
||||
void *user_data);
|
||||
esp_err_t domain_runtime_ota_confirm_running_image(void);
|
||||
esp_err_t domain_runtime_ota_get_memory_headroom(domain_ota_memory_headroom_t *out);
|
||||
void domain_runtime_restart(void);
|
||||
|
||||
/**
|
||||
* @brief Console (UART) line-transport primitives, bridged to the platform.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "domain.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "mbedtls/base64.h"
|
||||
#include "mbedtls/md.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "platform.h"
|
||||
|
||||
#define DOMAIN_OTA_RESPONSE_CAPACITY 4096
|
||||
#define DOMAIN_OTA_NONCE_LEN 36
|
||||
#define DOMAIN_OTA_AUTH_VALUE_MAX_LEN 128
|
||||
#define DOMAIN_OTA_MIN_VALID_EPOCH 1700000000LL
|
||||
|
||||
static const char *TAG = "domain_ota";
|
||||
|
||||
typedef struct {
|
||||
domain_ota_progress_callback_t progress_cb;
|
||||
domain_ota_cancel_callback_t cancel_cb;
|
||||
void *user_data;
|
||||
} domain_ota_install_bridge_t;
|
||||
|
||||
static esp_err_t domain_ota_copy_json_string(cJSON *object,
|
||||
const char *name,
|
||||
char *out,
|
||||
size_t out_len)
|
||||
{
|
||||
cJSON *item = cJSON_GetObjectItemCaseSensitive(object, name);
|
||||
if (!cJSON_IsString(item) || item->valuestring == NULL ||
|
||||
strlcpy(out, item->valuestring, out_len) >= out_len) {
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t domain_ota_base64(const uint8_t *input,
|
||||
size_t input_len,
|
||||
char *out,
|
||||
size_t out_len)
|
||||
{
|
||||
size_t encoded_len = 0U;
|
||||
int ret = mbedtls_base64_encode((uint8_t *)out,
|
||||
out_len,
|
||||
&encoded_len,
|
||||
input,
|
||||
input_len);
|
||||
if (ret != 0 || encoded_len >= out_len) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
out[encoded_len] = '\0';
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t domain_ota_parse_url_target(const char *url,
|
||||
char *path,
|
||||
size_t path_len,
|
||||
char *query,
|
||||
size_t query_len)
|
||||
{
|
||||
const char *authority = strstr(url, "://");
|
||||
if (authority == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
const char *target = strchr(authority + 3, '/');
|
||||
if (target == NULL) {
|
||||
target = "/";
|
||||
}
|
||||
|
||||
const char *query_begin = strchr(target, '?');
|
||||
size_t target_len = query_begin == NULL ? strlen(target) :
|
||||
(size_t)(query_begin - target);
|
||||
if (target_len == 0U || target_len >= path_len) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
memcpy(path, target, target_len);
|
||||
path[target_len] = '\0';
|
||||
|
||||
query[0] = '\0';
|
||||
if (query_begin != NULL && strlcpy(query, query_begin + 1, query_len) >= query_len) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void domain_ota_generate_nonce(char out[DOMAIN_OTA_NONCE_LEN + 1])
|
||||
{
|
||||
uint8_t random_bytes[16];
|
||||
esp_fill_random(random_bytes, sizeof(random_bytes));
|
||||
snprintf(out,
|
||||
DOMAIN_OTA_NONCE_LEN + 1,
|
||||
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
||||
random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3],
|
||||
random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7],
|
||||
random_bytes[8], random_bytes[9], random_bytes[10], random_bytes[11],
|
||||
random_bytes[12], random_bytes[13], random_bytes[14], random_bytes[15]);
|
||||
}
|
||||
|
||||
static esp_err_t domain_ota_build_internal_auth(const char *url,
|
||||
const char *payload,
|
||||
const char *secret,
|
||||
char *timestamp,
|
||||
size_t timestamp_len,
|
||||
char *nonce,
|
||||
size_t nonce_len,
|
||||
char *signature,
|
||||
size_t signature_len)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
if ((int64_t)now < DOMAIN_OTA_MIN_VALID_EPOCH) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (snprintf(timestamp, timestamp_len, "%lld", (long long)now) >= (int)timestamp_len) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
if (nonce_len < DOMAIN_OTA_NONCE_LEN + 1U) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
domain_ota_generate_nonce(nonce);
|
||||
|
||||
uint8_t body_digest[32];
|
||||
if (mbedtls_sha256((const uint8_t *)payload,
|
||||
strlen(payload),
|
||||
body_digest,
|
||||
0) != 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
char body_hash[DOMAIN_OTA_AUTH_VALUE_MAX_LEN];
|
||||
esp_err_t err = domain_ota_base64(body_digest,
|
||||
sizeof(body_digest),
|
||||
body_hash,
|
||||
sizeof(body_hash));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
char path[256];
|
||||
char query[256];
|
||||
err = domain_ota_parse_url_target(url, path, sizeof(path), query, sizeof(query));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
int payload_len = snprintf(NULL,
|
||||
0,
|
||||
"POST\n%s\n%s\n%s\n%s\n%s",
|
||||
path,
|
||||
query,
|
||||
body_hash,
|
||||
timestamp,
|
||||
nonce);
|
||||
if (payload_len <= 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
char *signature_payload = (char *)malloc((size_t)payload_len + 1U);
|
||||
if (signature_payload == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
snprintf(signature_payload,
|
||||
(size_t)payload_len + 1U,
|
||||
"POST\n%s\n%s\n%s\n%s\n%s",
|
||||
path,
|
||||
query,
|
||||
body_hash,
|
||||
timestamp,
|
||||
nonce);
|
||||
|
||||
uint8_t digest[32];
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
int hmac_ret = md_info == NULL ? -1 :
|
||||
mbedtls_md_hmac(md_info,
|
||||
(const uint8_t *)secret,
|
||||
strlen(secret),
|
||||
(const uint8_t *)signature_payload,
|
||||
(size_t)payload_len,
|
||||
digest);
|
||||
free(signature_payload);
|
||||
if (hmac_ret != 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return domain_ota_base64(digest, sizeof(digest), signature, signature_len);
|
||||
}
|
||||
|
||||
static esp_err_t domain_ota_build_report_payload(const domain_ota_report_request_t *request,
|
||||
char **out_payload)
|
||||
{
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
bool ok = cJSON_AddStringToObject(root, "deviceId", request->device_id) != NULL &&
|
||||
cJSON_AddStringToObject(root, "version", request->current_version) != NULL;
|
||||
if (ok && request->serial_number != NULL && request->serial_number[0] != '\0') {
|
||||
ok = cJSON_AddStringToObject(root, "serialNumber", request->serial_number) != NULL;
|
||||
}
|
||||
char *payload = ok ? cJSON_PrintUnformatted(root) : NULL;
|
||||
cJSON_Delete(root);
|
||||
if (payload == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
*out_payload = payload;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t domain_ota_parse_report_response(const char *body,
|
||||
domain_ota_report_response_t *response)
|
||||
{
|
||||
cJSON *root = cJSON_Parse(body);
|
||||
if (root == NULL) {
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
cJSON *code = cJSON_GetObjectItemCaseSensitive(root, "code");
|
||||
response->business_code = cJSON_IsNumber(code) ? code->valueint : 0;
|
||||
cJSON *data = cJSON_GetObjectItemCaseSensitive(root, "data");
|
||||
cJSON *not_uptodate = cJSON_IsObject(data) ?
|
||||
cJSON_GetObjectItemCaseSensitive(data, "notUptodate") : NULL;
|
||||
if (response->business_code != 200 || !cJSON_IsNumber(not_uptodate) ||
|
||||
not_uptodate->valueint != 1) {
|
||||
response->outcome = DOMAIN_OTA_REPORT_SERVER_ERROR;
|
||||
cJSON_Delete(root);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
domain_ota_firmware_t *firmware = &response->firmware;
|
||||
esp_err_t err = domain_ota_copy_json_string(data,
|
||||
"version",
|
||||
firmware->version,
|
||||
sizeof(firmware->version));
|
||||
if (err == ESP_OK) {
|
||||
err = domain_ota_copy_json_string(data,
|
||||
"url",
|
||||
firmware->url,
|
||||
sizeof(firmware->url));
|
||||
}
|
||||
if (err == ESP_OK) {
|
||||
err = domain_ota_copy_json_string(data,
|
||||
"sha256",
|
||||
firmware->sha256,
|
||||
sizeof(firmware->sha256));
|
||||
}
|
||||
|
||||
cJSON *size_bytes = cJSON_GetObjectItemCaseSensitive(data, "sizeBytes");
|
||||
cJSON *forced = cJSON_GetObjectItemCaseSensitive(data, "force");
|
||||
cJSON *confirmed = cJSON_GetObjectItemCaseSensitive(data, "update");
|
||||
if (err != ESP_OK || !cJSON_IsNumber(size_bytes) || size_bytes->valuedouble <= 0 ||
|
||||
size_bytes->valuedouble > (double)UINT32_MAX ||
|
||||
(double)(uint64_t)size_bytes->valuedouble != size_bytes->valuedouble ||
|
||||
strlen(firmware->sha256) != 64U) {
|
||||
cJSON_Delete(root);
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
firmware->size_bytes = (uint64_t)size_bytes->valuedouble;
|
||||
firmware->forced = cJSON_IsNumber(forced) && forced->valueint == 1;
|
||||
firmware->confirmed = cJSON_IsNumber(confirmed) && confirmed->valueint == 1;
|
||||
response->outcome = DOMAIN_OTA_REPORT_UPDATE_AVAILABLE;
|
||||
cJSON_Delete(root);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t domain_runtime_ota_report(const domain_ota_report_request_t *request,
|
||||
domain_ota_report_response_t *response)
|
||||
{
|
||||
if (response != NULL) {
|
||||
memset(response, 0, sizeof(*response));
|
||||
response->http_status = -1;
|
||||
}
|
||||
if (request == NULL || response == NULL || request->report_url == NULL ||
|
||||
strncmp(request->report_url, "https://", 8) != 0 ||
|
||||
request->device_id == NULL || request->device_id[0] == '\0' ||
|
||||
request->current_version == NULL || request->current_version[0] == '\0' ||
|
||||
request->device_jwt == NULL || request->device_jwt[0] == '\0' ||
|
||||
request->timeout_ms <= 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
char *payload = NULL;
|
||||
esp_err_t err = domain_ota_build_report_payload(request, &payload);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
char bearer[1600];
|
||||
if (snprintf(bearer, sizeof(bearer), "Bearer %s", request->device_jwt) >=
|
||||
(int)sizeof(bearer)) {
|
||||
cJSON_free(payload);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
platform_https_header_t headers[4] = {
|
||||
{.name = "Authorization", .value = bearer},
|
||||
};
|
||||
size_t header_count = 1U;
|
||||
char timestamp[32] = {0};
|
||||
char nonce[DOMAIN_OTA_NONCE_LEN + 1] = {0};
|
||||
char signature[DOMAIN_OTA_AUTH_VALUE_MAX_LEN] = {0};
|
||||
if (request->internal_auth_secret != NULL && request->internal_auth_secret[0] != '\0') {
|
||||
err = domain_ota_build_internal_auth(request->report_url,
|
||||
payload,
|
||||
request->internal_auth_secret,
|
||||
timestamp,
|
||||
sizeof(timestamp),
|
||||
nonce,
|
||||
sizeof(nonce),
|
||||
signature,
|
||||
sizeof(signature));
|
||||
if (err != ESP_OK) {
|
||||
cJSON_free(payload);
|
||||
return err;
|
||||
}
|
||||
headers[header_count++] = (platform_https_header_t) {
|
||||
.name = "X-Internal-Timestamp", .value = timestamp,
|
||||
};
|
||||
headers[header_count++] = (platform_https_header_t) {
|
||||
.name = "X-Internal-Nonce", .value = nonce,
|
||||
};
|
||||
headers[header_count++] = (platform_https_header_t) {
|
||||
.name = "X-Internal-Signature", .value = signature,
|
||||
};
|
||||
}
|
||||
|
||||
char *body = (char *)calloc(1, DOMAIN_OTA_RESPONSE_CAPACITY);
|
||||
if (body == NULL) {
|
||||
cJSON_free(payload);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
platform_https_post_json_request_t platform_request = {
|
||||
.url = request->report_url,
|
||||
.payload = payload,
|
||||
.headers = headers,
|
||||
.header_count = header_count,
|
||||
.timeout_ms = request->timeout_ms,
|
||||
.tx_buffer_size = 2048,
|
||||
.rx_buffer_size = 2048,
|
||||
};
|
||||
platform_https_response_t platform_response = {
|
||||
.body = body,
|
||||
.body_capacity = DOMAIN_OTA_RESPONSE_CAPACITY,
|
||||
};
|
||||
err = platform_https_post_json(&platform_request, &platform_response);
|
||||
cJSON_free(payload);
|
||||
if (err != ESP_OK) {
|
||||
free(body);
|
||||
return err;
|
||||
}
|
||||
|
||||
response->http_status = platform_response.status_code;
|
||||
ESP_LOGI(TAG,
|
||||
"OTA version report response: http=%d body_len=%u body=%.*s",
|
||||
platform_response.status_code,
|
||||
(unsigned)platform_response.body_len,
|
||||
(int)platform_response.body_len,
|
||||
body);
|
||||
if (platform_response.status_code == 204) {
|
||||
response->outcome = DOMAIN_OTA_REPORT_NO_UPDATE;
|
||||
free(body);
|
||||
return ESP_OK;
|
||||
}
|
||||
if (platform_response.status_code != 200) {
|
||||
response->outcome = DOMAIN_OTA_REPORT_SERVER_ERROR;
|
||||
free(body);
|
||||
return ESP_OK;
|
||||
}
|
||||
err = domain_ota_parse_report_response(body, response);
|
||||
free(body);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int domain_ota_hex_value(char value)
|
||||
{
|
||||
if (value >= '0' && value <= '9') {
|
||||
return value - '0';
|
||||
}
|
||||
if (value >= 'a' && value <= 'f') {
|
||||
return value - 'a' + 10;
|
||||
}
|
||||
if (value >= 'A' && value <= 'F') {
|
||||
return value - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static esp_err_t domain_ota_parse_sha256(const char *hex, uint8_t out[32])
|
||||
{
|
||||
if (hex == NULL || strlen(hex) != 64U) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
for (size_t i = 0; i < 32U; ++i) {
|
||||
int high = domain_ota_hex_value(hex[i * 2U]);
|
||||
int low = domain_ota_hex_value(hex[i * 2U + 1U]);
|
||||
if (high < 0 || low < 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
out[i] = (uint8_t)((high << 4) | low);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void domain_ota_progress_bridge(uint64_t received, uint64_t total, void *user_data)
|
||||
{
|
||||
domain_ota_install_bridge_t *bridge = (domain_ota_install_bridge_t *)user_data;
|
||||
if (bridge->progress_cb != NULL) {
|
||||
bridge->progress_cb(received, total, bridge->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static bool domain_ota_cancel_bridge(void *user_data)
|
||||
{
|
||||
domain_ota_install_bridge_t *bridge = (domain_ota_install_bridge_t *)user_data;
|
||||
return bridge->cancel_cb != NULL && bridge->cancel_cb(bridge->user_data);
|
||||
}
|
||||
|
||||
esp_err_t domain_runtime_ota_install(const domain_ota_firmware_t *firmware,
|
||||
int timeout_ms,
|
||||
domain_ota_progress_callback_t progress_cb,
|
||||
domain_ota_cancel_callback_t cancel_cb,
|
||||
void *user_data)
|
||||
{
|
||||
if (firmware == NULL || strncmp(firmware->url, "https://", 8) != 0 ||
|
||||
firmware->size_bytes == 0U || timeout_ms <= 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
uint8_t expected_sha256[32];
|
||||
esp_err_t err = domain_ota_parse_sha256(firmware->sha256, expected_sha256);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
domain_ota_install_bridge_t bridge = {
|
||||
.progress_cb = progress_cb,
|
||||
.cancel_cb = cancel_cb,
|
||||
.user_data = user_data,
|
||||
};
|
||||
return platform_ota_install_image(firmware->url,
|
||||
expected_sha256,
|
||||
firmware->size_bytes,
|
||||
timeout_ms,
|
||||
domain_ota_progress_bridge,
|
||||
domain_ota_cancel_bridge,
|
||||
&bridge);
|
||||
}
|
||||
|
||||
esp_err_t domain_runtime_ota_confirm_running_image(void)
|
||||
{
|
||||
return platform_ota_confirm_running_image();
|
||||
}
|
||||
|
||||
esp_err_t domain_runtime_ota_get_memory_headroom(domain_ota_memory_headroom_t *out)
|
||||
{
|
||||
if (out == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return platform_ota_get_memory_headroom(&out->free_internal,
|
||||
&out->largest_internal,
|
||||
&out->largest_dma);
|
||||
}
|
||||
|
||||
void domain_runtime_restart(void)
|
||||
{
|
||||
platform_restart();
|
||||
}
|
||||
@@ -859,6 +859,11 @@ void domain_runtime_read_device_identity(char *device_id,
|
||||
platform_read_device_identity(device_id, device_id_len, serial_number, serial_number_len);
|
||||
}
|
||||
|
||||
esp_err_t domain_runtime_read_firmware_version(char *out, size_t out_len)
|
||||
{
|
||||
return platform_read_firmware_version(out, out_len);
|
||||
}
|
||||
|
||||
esp_err_t domain_runtime_write_device_identity(const char *device_id, const char *serial_number)
|
||||
{
|
||||
return platform_write_device_identity(device_id, serial_number);
|
||||
|
||||
Reference in New Issue
Block a user