mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-18 04:52:18 +02:00
Merge pull request #363 from david-cermak/feat/modem_test_ota
feat(modem): Add OTA test to exercise modem layers
This commit is contained in:
40
.github/workflows/modem__build-host-tests.yml
vendored
40
.github/workflows/modem__build-host-tests.yml
vendored
@ -8,25 +8,19 @@ on:
|
|||||||
types: [opened, synchronize, reopened, labeled]
|
types: [opened, synchronize, reopened, labeled]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_esp_modem:
|
build_esp_modem_examples:
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'
|
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'
|
||||||
name: Build
|
name: Build examples
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
idf_ver: ["latest", "release-v4.2", "release-v4.3", "release-v4.4", "release-v5.0"]
|
idf_ver: ["latest", "release-v4.3", "release-v4.4", "release-v5.0"]
|
||||||
example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"]
|
example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"]
|
||||||
exclude:
|
exclude:
|
||||||
- idf_ver: "release-v4.2"
|
|
||||||
example: simple_cmux_client
|
|
||||||
- idf_ver: "release-v4.2"
|
|
||||||
example: modem_tcp_client
|
|
||||||
- idf_ver: "release-v4.3"
|
- idf_ver: "release-v4.3"
|
||||||
example: modem_tcp_client
|
example: modem_tcp_client
|
||||||
- idf_ver: "release-v4.4"
|
- idf_ver: "release-v4.4"
|
||||||
example: modem_tcp_client
|
example: modem_tcp_client
|
||||||
include:
|
include:
|
||||||
- idf_ver: "release-v4.2"
|
|
||||||
skip_config: usb
|
|
||||||
- idf_ver: "release-v4.3"
|
- idf_ver: "release-v4.3"
|
||||||
skip_config: usb
|
skip_config: usb
|
||||||
- idf_ver: "release-v5.0"
|
- idf_ver: "release-v5.0"
|
||||||
@ -50,7 +44,33 @@ jobs:
|
|||||||
. ${IDF_PATH}/export.sh
|
. ${IDF_PATH}/export.sh
|
||||||
python -m pip install idf-build-apps
|
python -m pip install idf-build-apps
|
||||||
cd $GITHUB_WORKSPACE/protocols
|
cd $GITHUB_WORKSPACE/protocols
|
||||||
python ./ci/build_apps.py components/esp_modem/examples/${{ matrix.example }} -m components/esp_modem/examples/.build-test-rules.yml
|
python ./ci/build_apps.py components/esp_modem/examples/${{ matrix.example }} -m components/esp_modem/.build-test-rules.yml
|
||||||
|
|
||||||
|
build_esp_modem_tests:
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'
|
||||||
|
name: Build tests
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
idf_ver: ["release-v5.0", "release-v5.1", "latest"]
|
||||||
|
test: ["target", "target_ota"]
|
||||||
|
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
container: espressif/idf:${{ matrix.idf_ver }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout esp-protocols
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: protocols
|
||||||
|
- name: Build ${{ matrix.test }} with IDF-${{ matrix.idf_ver }}
|
||||||
|
env:
|
||||||
|
EXPECTED_WARNING: ${{ matrix.warning }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
. ${IDF_PATH}/export.sh
|
||||||
|
python -m pip install idf-build-apps
|
||||||
|
cd $GITHUB_WORKSPACE/protocols
|
||||||
|
python ./ci/build_apps.py components/esp_modem/test/${{ matrix.test }} -m components/esp_modem/.build-test-rules.yml
|
||||||
|
|
||||||
|
|
||||||
host_test_esp_modem:
|
host_test_esp_modem:
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'
|
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'
|
||||||
|
@ -29,6 +29,7 @@ public:
|
|||||||
int read(unsigned char *buf, size_t len);
|
int read(unsigned char *buf, size_t len);
|
||||||
[[nodiscard]] bool set_own_cert(const_buf crt, const_buf key);
|
[[nodiscard]] bool set_own_cert(const_buf crt, const_buf key);
|
||||||
[[nodiscard]] bool set_ca_cert(const_buf crt);
|
[[nodiscard]] bool set_ca_cert(const_buf crt);
|
||||||
|
bool set_hostname(const char *name);
|
||||||
virtual int send(const unsigned char *buf, size_t len) = 0;
|
virtual int send(const unsigned char *buf, size_t len) = 0;
|
||||||
virtual int recv(unsigned char *buf, size_t len) = 0;
|
virtual int recv(unsigned char *buf, size_t len) = 0;
|
||||||
size_t get_available_bytes();
|
size_t get_available_bytes();
|
||||||
|
@ -116,6 +116,16 @@ bool Tls::set_ca_cert(const_buf crt)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Tls::set_hostname(const char *name)
|
||||||
|
{
|
||||||
|
int ret = mbedtls_ssl_set_hostname(&ssl_, name);
|
||||||
|
if (ret < 0) {
|
||||||
|
print_error("mbedtls_ssl_set_hostname", ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Tls::Tls()
|
Tls::Tls()
|
||||||
{
|
{
|
||||||
mbedtls_x509_crt_init(&public_cert_);
|
mbedtls_x509_crt_init(&public_cert_);
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
*/
|
*/
|
||||||
struct esp_modem_vfs_uart_creator {
|
struct esp_modem_vfs_uart_creator {
|
||||||
const char *dev_name; /*!< VFS device name, e.g. /dev/uart/n */
|
const char *dev_name; /*!< VFS device name, e.g. /dev/uart/n */
|
||||||
const struct esp_modem_uart_term_config uart; /*!< UART driver init struct */
|
struct esp_modem_uart_term_config uart; /*!< UART driver init struct */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
8
components/esp_modem/test/target_ota/CMakeLists.txt
Normal file
8
components/esp_modem/test/target_ota/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||||
|
# in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.8)
|
||||||
|
|
||||||
|
set(EXTRA_COMPONENT_DIRS "../.." "../../examples/modem_tcp_client/components")
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(ota_test)
|
27
components/esp_modem/test/target_ota/README.md
Normal file
27
components/esp_modem/test/target_ota/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Target test running OTA update
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The aim of this test is to exercise the most commonly failing scenario, running OTA over PPPoS with https.
|
||||||
|
|
||||||
|
This project opens a data session, runs basic mqtt operations and initiates OTA update.
|
||||||
|
It supports the following test configurations:
|
||||||
|
* Using a real modem device (default config)
|
||||||
|
* Using VFS device (only to exercise VFS DTE)
|
||||||
|
* Using network-only DCE (connecting directly to PPP server) -- needs some configuration
|
||||||
|
|
||||||
|
### Configuring the PPP server
|
||||||
|
|
||||||
|
You need to run these applications on your host machine:
|
||||||
|
* PPP server
|
||||||
|
```bash
|
||||||
|
sudo pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem local noauth debug nocrtscts nodetach +ipv6
|
||||||
|
```
|
||||||
|
* MQTT broker: Running mosquitto in the default config is enough, configuring the broker's URL to the local PPP address: `config.broker.address.uri = "mqtt://192.168.11.1";`
|
||||||
|
* HTTP server: Need to support HTTP/1.1 (to support ranges). You can use the script `http_server.py` and configure the OTA endpoint as `"https://192.168.11.1:1234/esp32.bin"`
|
||||||
|
|
||||||
|
## Potential issues
|
||||||
|
|
||||||
|
When running this test it is expected to experience some buffer overflows or connection interruption.
|
||||||
|
The modem library should recover from these failure modes and continue and complete OTA update.
|
||||||
|
These issues are expected, since UART ISR is deliberately not placed into IRAM in the test configuration to exhibit some minor communication glitches.
|
BIN
components/esp_modem/test/target_ota/bin/blink.bin
Normal file
BIN
components/esp_modem/test/target_ota/bin/blink.bin
Normal file
Binary file not shown.
@ -0,0 +1,3 @@
|
|||||||
|
idf_component_register(SRCS manual_ota.cpp transport_batch_tls.cpp
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
PRIV_REQUIRES extra_tcp_transports esp_http_client app_update)
|
@ -0,0 +1,317 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
#include "manual_ota.hpp"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_app_format.h"
|
||||||
|
#include "esp_http_client.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_transport_tcp.h"
|
||||||
|
#include "transport_batch_tls.hpp"
|
||||||
|
|
||||||
|
static const char *TAG = "manual_ota";
|
||||||
|
|
||||||
|
bool manual_ota::begin()
|
||||||
|
{
|
||||||
|
if (status != state::UNDEF) {
|
||||||
|
ESP_LOGE(TAG, "Invalid state for manual_ota::perform");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const esp_partition_t *configured = esp_ota_get_boot_partition();
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
|
||||||
|
if (configured != running) {
|
||||||
|
ESP_LOGE(TAG, "Configured OTA boot partition at offset 0x%08" PRIx32 ", but running from offset 0x%08" PRIx32, configured->address, running->address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = state::INIT;
|
||||||
|
max_buffer_size_ = size_ * 1024;
|
||||||
|
if (mode_ == mode::BATCH) {
|
||||||
|
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
|
||||||
|
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||||
|
ssl_ = esp_transport_batch_tls_init(tcp, max_buffer_size_);
|
||||||
|
http_.config_.transport = ssl_;
|
||||||
|
if (!esp_transport_batch_set_ca_cert(ssl_, http_.config_.cert_pem, 0)) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
if (!esp_transport_batch_set_cn(ssl_, common_name_)) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ESP_LOGE(TAG, "mode::BATCH Cannot be used without CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!http_.init()) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
image_length_ = http_.get_image_len();
|
||||||
|
if (image_length_ <= 0) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image_length_ > size_) {
|
||||||
|
if (!http_.set_range(0, max_buffer_size_ - 1)) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET);
|
||||||
|
|
||||||
|
partition_ = esp_ota_get_next_update_partition(nullptr);
|
||||||
|
if (partition_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Invalid update partition");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%" PRIx32, partition_->subtype, partition_->address);
|
||||||
|
|
||||||
|
file_length_ = 0;
|
||||||
|
reconnect_attempts_ = 0;
|
||||||
|
buffer_.resize(max_buffer_size_);
|
||||||
|
status = state::IMAGE_CHECK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::perform()
|
||||||
|
{
|
||||||
|
if (status != state::IMAGE_CHECK && status != state::START) {
|
||||||
|
ESP_LOGE(TAG, "Invalid state for manual_ota::perform");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
esp_err_t err = esp_http_client_open(http_.handle_, 0);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
if (image_length_ == file_length_) {
|
||||||
|
status = state::END;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_http_client_close(http_.handle_);
|
||||||
|
ESP_LOGI(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
|
||||||
|
if (reconnect_attempts_++ < max_reconnect_attempts_) {
|
||||||
|
return true; // will retry in the next iteration
|
||||||
|
}
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
esp_http_client_fetch_headers(http_.handle_);
|
||||||
|
|
||||||
|
int batch_len = max_buffer_size_;
|
||||||
|
if (mode_ == mode::BATCH) {
|
||||||
|
batch_len = esp_transport_batch_tls_pre_read(ssl_, max_buffer_size_, timeout_ * 1000);
|
||||||
|
if (batch_len < 0) {
|
||||||
|
ESP_LOGE(TAG, "Error: Failed to pre-read plain text data!");
|
||||||
|
fail();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int data_read = esp_http_client_read(http_.handle_, buffer_.data(), batch_len);
|
||||||
|
|
||||||
|
if (data_read < 0) {
|
||||||
|
ESP_LOGE(TAG, "Error: SSL data read error");
|
||||||
|
return fail();
|
||||||
|
} else if (data_read > 0) {
|
||||||
|
esp_http_client_close(http_.handle_);
|
||||||
|
|
||||||
|
if (status == state::IMAGE_CHECK) {
|
||||||
|
esp_app_desc_t new_app_info;
|
||||||
|
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
|
||||||
|
// check current version with downloading
|
||||||
|
memcpy(&new_app_info, &buffer_[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
|
||||||
|
ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
|
||||||
|
|
||||||
|
esp_app_desc_t running_app_info;
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
const esp_partition_t *last_invalid_app = esp_ota_get_last_invalid_partition();
|
||||||
|
esp_app_desc_t invalid_app_info;
|
||||||
|
if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check current version with last invalid partition
|
||||||
|
if (last_invalid_app != NULL) {
|
||||||
|
if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
|
||||||
|
ESP_LOGW(TAG, "New version is the same as invalid version.");
|
||||||
|
ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
|
||||||
|
ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = state::START;
|
||||||
|
err = esp_ota_begin(partition_, OTA_WITH_SEQUENTIAL_WRITES, &update_handle_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
ota_begin = true;
|
||||||
|
ESP_LOGI(TAG, "esp_ota_begin succeeded");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Received chunk doesn't contain app descriptor");
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = esp_ota_write(update_handle_, (const void *)buffer_.data(), data_read);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
file_length_ += data_read;
|
||||||
|
ESP_LOGI(TAG, "Written image length %d", file_length_);
|
||||||
|
|
||||||
|
if (image_length_ == file_length_) {
|
||||||
|
status = state::END;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prepare_reconnect()) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (data_read == 0) {
|
||||||
|
if (file_length_ == 0) {
|
||||||
|
// Try to handle possible HTTP redirections
|
||||||
|
if (!http_.handle_redirects()) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_http_client_open(http_.handle_, 0);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
esp_http_client_fetch_headers(http_.handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::prepare_reconnect()
|
||||||
|
{
|
||||||
|
esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET);
|
||||||
|
return http_.set_range(file_length_,
|
||||||
|
(image_length_ - file_length_) > max_buffer_size_ ? (file_length_ + max_buffer_size_ - 1) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::fail()
|
||||||
|
{
|
||||||
|
status = state::FAIL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::end()
|
||||||
|
{
|
||||||
|
if (status == state::END) {
|
||||||
|
if (!http_.is_data_complete()) {
|
||||||
|
ESP_LOGE(TAG, "Error in receiving complete file");
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
esp_err_t err = esp_ota_end(update_handle_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
|
||||||
|
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
ota_begin = false;
|
||||||
|
err = esp_ota_set_boot_partition(partition_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
manual_ota::~manual_ota()
|
||||||
|
{
|
||||||
|
if (ota_begin) {
|
||||||
|
esp_ota_abort(update_handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::http_client::handle_redirects()
|
||||||
|
{
|
||||||
|
int status_code = esp_http_client_get_status_code(handle_);
|
||||||
|
ESP_LOGW(TAG, "Status code: %d", status_code);
|
||||||
|
esp_err_t err = esp_http_client_set_redirection(handle_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "URL redirection Failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_http_client_open(handle_, 0);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
esp_http_client_fetch_headers(handle_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::http_client::set_range(size_t from, size_t to)
|
||||||
|
{
|
||||||
|
char *header_val = nullptr;
|
||||||
|
if (to != 0) {
|
||||||
|
asprintf(&header_val, "bytes=%d-%d", from, to);
|
||||||
|
} else {
|
||||||
|
asprintf(&header_val, "bytes=%d-", from);
|
||||||
|
}
|
||||||
|
if (header_val == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate memory for HTTP header");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
esp_http_client_set_header(handle_, "Range", header_val);
|
||||||
|
free(header_val);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::http_client::is_data_complete()
|
||||||
|
{
|
||||||
|
return esp_http_client_is_complete_data_received(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
manual_ota::http_client::~http_client()
|
||||||
|
{
|
||||||
|
if (handle_) {
|
||||||
|
esp_http_client_close(handle_);
|
||||||
|
esp_http_client_cleanup(handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_ota::http_client::init()
|
||||||
|
{
|
||||||
|
handle_ = esp_http_client_init(&config_);
|
||||||
|
return handle_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t manual_ota::http_client::get_image_len()
|
||||||
|
{
|
||||||
|
esp_http_client_set_method(handle_, HTTP_METHOD_HEAD);
|
||||||
|
esp_err_t err = esp_http_client_perform(handle_);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
int http_status = esp_http_client_get_status_code(handle_);
|
||||||
|
if (http_status != HttpStatus_Ok) {
|
||||||
|
ESP_LOGE(TAG, "Received incorrect http status %d", http_status);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int64_t image_length = esp_http_client_get_content_length(handle_);
|
||||||
|
ESP_LOGI(TAG, "image_length = %lld", image_length);
|
||||||
|
esp_http_client_close(handle_);
|
||||||
|
return image_length;
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include "esp_http_client.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_transport_tcp.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
|
||||||
|
class manual_ota {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Set the preferred mode
|
||||||
|
*/
|
||||||
|
enum class mode {
|
||||||
|
BATCH, /**< Read data chunk from TCP and pass it to SSL, restore session on reconnection */
|
||||||
|
NORMAL /**< Use standard partial download, continuously passing data from TCP to mbedTLS */
|
||||||
|
} mode_ {mode::BATCH};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the OTA batch size in kB
|
||||||
|
*
|
||||||
|
* This would allocate two big buffers:
|
||||||
|
* - one for reading from TCP socket and
|
||||||
|
* - one for passing to mbedTLS for description
|
||||||
|
*/
|
||||||
|
size_t size_{32};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set timeout in seconds
|
||||||
|
*
|
||||||
|
* This is the network timeout, so if less data than the batch size received
|
||||||
|
* the timeout (and no EOF) we should proceed with passing the data to mbedtls
|
||||||
|
*/
|
||||||
|
int timeout_{2};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set common name of the server to verify
|
||||||
|
*/
|
||||||
|
const char *common_name_;
|
||||||
|
/**
|
||||||
|
* @brief Wrapper around the http client -- Please set the http config
|
||||||
|
*/
|
||||||
|
class http_client {
|
||||||
|
friend class manual_ota;
|
||||||
|
~http_client();
|
||||||
|
bool init();
|
||||||
|
esp_http_client_handle_t handle_{nullptr};
|
||||||
|
bool handle_redirects();
|
||||||
|
bool set_range(size_t from, size_t to);
|
||||||
|
bool is_data_complete();
|
||||||
|
int64_t get_image_len();
|
||||||
|
public:
|
||||||
|
esp_http_client_config_t config_{}; /**< Configure the http connection parameters */
|
||||||
|
} http_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new manual ota object
|
||||||
|
*/
|
||||||
|
explicit manual_ota() {}
|
||||||
|
|
||||||
|
~manual_ota();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start the manual OTA process
|
||||||
|
*
|
||||||
|
* @return true if started successfully
|
||||||
|
*/
|
||||||
|
bool begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Performs one read-write OTA iteration
|
||||||
|
*
|
||||||
|
* @return true if the process is in progress
|
||||||
|
* @return false if the process finished, call end() to get OTA result
|
||||||
|
*/
|
||||||
|
bool perform();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finishes an OTA update
|
||||||
|
*
|
||||||
|
* @return true if the OTA update completed successfully
|
||||||
|
*/
|
||||||
|
bool end();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class state {
|
||||||
|
UNDEF,
|
||||||
|
INIT,
|
||||||
|
IMAGE_CHECK,
|
||||||
|
START,
|
||||||
|
END,
|
||||||
|
FAIL,
|
||||||
|
};
|
||||||
|
int64_t image_length_;
|
||||||
|
size_t file_length_;
|
||||||
|
size_t max_buffer_size_{size_ * 1024};
|
||||||
|
const esp_partition_t *partition_{nullptr};
|
||||||
|
state status{state::UNDEF};
|
||||||
|
std::vector<char> buffer_{};
|
||||||
|
int reconnect_attempts_;
|
||||||
|
const int max_reconnect_attempts_{3};
|
||||||
|
esp_transport_handle_t ssl_;
|
||||||
|
esp_ota_handle_t update_handle_{0};
|
||||||
|
bool ota_begin;
|
||||||
|
|
||||||
|
bool prepare_reconnect();
|
||||||
|
bool fail();
|
||||||
|
};
|
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
#include <vector>
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "mbedtls_wrap.hpp"
|
||||||
|
#include "esp_transport_tcp.h"
|
||||||
|
|
||||||
|
#define TAG "batch-tls"
|
||||||
|
|
||||||
|
class TlsTransport: public Tls {
|
||||||
|
public:
|
||||||
|
explicit TlsTransport(esp_transport_handle_t parent):
|
||||||
|
Tls(), transport_(parent), last_timeout(0), read_len(0), offset(0) {}
|
||||||
|
int send(const unsigned char *buf, size_t len) override;
|
||||||
|
int recv(unsigned char *buf, size_t len) override;
|
||||||
|
static bool set_func(esp_transport_handle_t tls_transport);
|
||||||
|
int preread(size_t len, int timeout_ms);
|
||||||
|
bool prepare_buffer(size_t max_size);
|
||||||
|
private:
|
||||||
|
esp_transport_handle_t transport_{};
|
||||||
|
int connect(const char *host, int port, int timeout_ms);
|
||||||
|
void delay() override;
|
||||||
|
|
||||||
|
struct transport {
|
||||||
|
static int connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms);
|
||||||
|
static int read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms);
|
||||||
|
static int write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms);
|
||||||
|
static int close(esp_transport_handle_t t);
|
||||||
|
static int poll_read(esp_transport_handle_t t, int timeout_ms);
|
||||||
|
static int poll_write(esp_transport_handle_t t, int timeout_ms);
|
||||||
|
static int destroy(esp_transport_handle_t t);
|
||||||
|
};
|
||||||
|
int last_timeout;
|
||||||
|
std::vector<char> buf;
|
||||||
|
size_t read_len;
|
||||||
|
size_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_transport_handle_t esp_transport_tls_init(esp_transport_handle_t parent)
|
||||||
|
{
|
||||||
|
esp_transport_handle_t transport_handle = esp_transport_init();
|
||||||
|
auto *tls_context = new TlsTransport(parent);
|
||||||
|
esp_transport_set_context_data(transport_handle, tls_context);
|
||||||
|
TlsTransport::set_func(transport_handle);
|
||||||
|
return transport_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::send(const unsigned char *buf, size_t len)
|
||||||
|
{
|
||||||
|
int ret = esp_transport_write(transport_, reinterpret_cast<const char *>(buf), len, 0);
|
||||||
|
ESP_LOGD(TAG, "writing(len=%d) ret=%d", len, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::recv(unsigned char *buffer, size_t len)
|
||||||
|
{
|
||||||
|
ESP_LOGD(TAG, "recv(len=%d)", len);
|
||||||
|
if (read_len != 0) {
|
||||||
|
|
||||||
|
if (read_len > len) {
|
||||||
|
memcpy((char *)buffer, buf.data() + offset, len);
|
||||||
|
read_len -= len;
|
||||||
|
offset += len;
|
||||||
|
ESP_LOGD(TAG, "read %d from batch read_len = %d", len, read_len);
|
||||||
|
return len;
|
||||||
|
} else {
|
||||||
|
int remaining = len = read_len;
|
||||||
|
if (remaining > 0) {
|
||||||
|
memcpy((char *)buffer, buf.data() + offset, remaining);
|
||||||
|
read_len = 0;
|
||||||
|
offset = 0;
|
||||||
|
return remaining;
|
||||||
|
|
||||||
|
}
|
||||||
|
read_len = 0;
|
||||||
|
offset = 0;
|
||||||
|
return ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int ret = esp_transport_read(transport_, reinterpret_cast<char *>(buffer), len, last_timeout);
|
||||||
|
|
||||||
|
if (ret == ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT) {
|
||||||
|
return MBEDTLS_ERR_SSL_WANT_READ;
|
||||||
|
}
|
||||||
|
return ret == ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN ? 0 : ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TlsTransport::set_func(esp_transport_handle_t tls_transport)
|
||||||
|
{
|
||||||
|
return esp_transport_set_func(tls_transport, TlsTransport::transport::connect, TlsTransport::transport::read, TlsTransport::transport::write, TlsTransport::transport::close, TlsTransport::transport::poll_read, TlsTransport::transport::poll_write, TlsTransport::transport::destroy) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::connect(const char *host, int port, int timeout_ms)
|
||||||
|
{
|
||||||
|
return esp_transport_connect(transport_, host, port, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TlsTransport::delay()
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::transport::connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
tls->init(is_server{false}, do_verify{true});
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "TLS-connect");
|
||||||
|
auto ret = tls->connect(host, port, timeout_ms);
|
||||||
|
if (ret < 0) {
|
||||||
|
ESP_LOGI(TAG, "Failed to connect to transport");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (tls->is_session_loaded()) {
|
||||||
|
tls->set_session();
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Before handshake");
|
||||||
|
ret = tls->handshake();
|
||||||
|
if (ret < 0) {
|
||||||
|
ESP_LOGI(TAG, "Failed to handshake");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (!tls->get_session()) {
|
||||||
|
// we're not able to save session, report an error and continue (next connection will be slower)
|
||||||
|
ESP_LOGW(TAG, "Failed to save session");
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "After handshake");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::transport::read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
ESP_LOGD(TAG, "available=%d tls->read_len=%d", tls->get_available_bytes(), tls->read_len);
|
||||||
|
if (tls->get_available_bytes() <= 0 && tls->read_len == 0) {
|
||||||
|
ESP_LOGD(TAG, "red(len=%d, timeout=%d) tls->read_len=%d", len, timeout_ms, tls->read_len);
|
||||||
|
tls->last_timeout = timeout_ms;
|
||||||
|
int poll = esp_transport_poll_read(t, timeout_ms);
|
||||||
|
if (poll == -1) {
|
||||||
|
return ERR_TCP_TRANSPORT_CONNECTION_FAILED;
|
||||||
|
}
|
||||||
|
if (poll == 0) {
|
||||||
|
return ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ret = tls->read(reinterpret_cast<unsigned char *>(buffer), len);
|
||||||
|
if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
|
||||||
|
ret = ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "red(len=%d, timeout=%d) ret=%d", len, timeout_ms, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::transport::write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms)
|
||||||
|
{
|
||||||
|
int poll;
|
||||||
|
if ((poll = esp_transport_poll_write(t, timeout_ms)) <= 0) {
|
||||||
|
ESP_LOGW(TAG, "Poll timeout or error timeout_ms=%d", timeout_ms);
|
||||||
|
return poll;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
int ret = tls->write(reinterpret_cast<const unsigned char *>(buffer), len);
|
||||||
|
ESP_LOGD(TAG, "write ret=%d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::transport::close(esp_transport_handle_t t)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
int ret = esp_transport_close(tls->transport_);
|
||||||
|
tls->deinit();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::transport::poll_read(esp_transport_handle_t t, int timeout_ms)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
return esp_transport_poll_read(tls->transport_, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::transport::poll_write(esp_transport_handle_t t, int timeout_ms)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
return esp_transport_poll_write(tls->transport_, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::transport::destroy(esp_transport_handle_t t)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
return esp_transport_destroy(tls->transport_);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t parent, const size_t max_buffer_size)
|
||||||
|
{
|
||||||
|
esp_transport_handle_t ssl = esp_transport_init();
|
||||||
|
auto *tls = new TlsTransport(parent);
|
||||||
|
esp_transport_set_context_data(ssl, tls);
|
||||||
|
TlsTransport::set_func(ssl);
|
||||||
|
tls->prepare_buffer(max_buffer_size);
|
||||||
|
return ssl;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool esp_transport_batch_set_ca_cert(esp_transport_handle_t t, const char *ca_cert, size_t cert_len = 0)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
const_buf cert((const unsigned char *)ca_cert, cert_len ? cert_len : strlen(ca_cert) + 1);
|
||||||
|
return tls->set_ca_cert(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool esp_transport_batch_set_cn(esp_transport_handle_t t, const char *name)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
return tls->set_hostname(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TlsTransport::preread(size_t len, int timeout_ms)
|
||||||
|
{
|
||||||
|
while (len != read_len) {
|
||||||
|
int l = esp_transport_read(transport_, buf.data() + read_len, len - read_len, timeout_ms);
|
||||||
|
ESP_LOGD(TAG, "need %d read %d already %d", len, l, read_len);
|
||||||
|
if ((l == ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN || l == ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT ) && read_len > 0) {
|
||||||
|
return read_len;
|
||||||
|
}
|
||||||
|
if (l <= 0) {
|
||||||
|
read_len = 0;
|
||||||
|
return read_len;
|
||||||
|
}
|
||||||
|
read_len += l;
|
||||||
|
}
|
||||||
|
return read_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TlsTransport::prepare_buffer(size_t max_size)
|
||||||
|
{
|
||||||
|
buf.resize(max_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int esp_transport_batch_tls_pre_read(esp_transport_handle_t t, size_t len, int timeout_ms)
|
||||||
|
{
|
||||||
|
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
|
||||||
|
return tls->preread(len, timeout_ms);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates batch transport
|
||||||
|
*
|
||||||
|
* @param parent tcp-transport handle to the parent transport
|
||||||
|
* @param max_buffer_size maximum size of one batch
|
||||||
|
* @return created transport handle
|
||||||
|
*/
|
||||||
|
esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t parent, const size_t max_buffer_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Performs batch read operation from the underlying transport
|
||||||
|
*
|
||||||
|
* @param t Transport handle
|
||||||
|
* @param len Batch size
|
||||||
|
* @param timeout_ms Timeout in ms
|
||||||
|
* @return true If read from the parent transport completed successfully
|
||||||
|
*/
|
||||||
|
bool esp_transport_batch_tls_pre_read(esp_transport_handle_t t, size_t len, int timeout_ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the CA Certificate to verify the server
|
||||||
|
*
|
||||||
|
* @param ca_cert Pointer to the CA Cert data
|
||||||
|
* @param cert_len CA Cert data len (set to 0 if null terminated string, i.e. PEM format)
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool esp_transport_batch_set_ca_cert(esp_transport_handle_t t, const char *ca_cert, size_t cert_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set comman name
|
||||||
|
* @param t
|
||||||
|
* @param ca_cert
|
||||||
|
* @param cert_len
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool esp_transport_batch_set_cn(esp_transport_handle_t t, const char *name);
|
15
components/esp_modem/test/target_ota/http_server.py
Normal file
15
components/esp_modem/test/target_ota/http_server.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
import ssl
|
||||||
|
from http.server import HTTPServer
|
||||||
|
|
||||||
|
from RangeHTTPServer import RangeRequestHandler
|
||||||
|
|
||||||
|
server_address = ('0.0.0.0', 1234)
|
||||||
|
httpd = HTTPServer(server_address, RangeRequestHandler)
|
||||||
|
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||||
|
server_side=True,
|
||||||
|
certfile='srv.crt',
|
||||||
|
keyfile='srv.key',
|
||||||
|
ssl_version=ssl.PROTOCOL_TLS)
|
||||||
|
httpd.serve_forever()
|
2
components/esp_modem/test/target_ota/main/CMakeLists.txt
Normal file
2
components/esp_modem/test/target_ota/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS ota_test.cpp
|
||||||
|
INCLUDE_DIRS ".")
|
60
components/esp_modem/test/target_ota/main/Kconfig.projbuild
Normal file
60
components/esp_modem/test/target_ota/main/Kconfig.projbuild
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
menu "Test Configuration"
|
||||||
|
|
||||||
|
choice TEST_DEVICE
|
||||||
|
prompt "Choose supported modem device (DCE)"
|
||||||
|
default TEST_DEVICE_MODEM_GENERIC
|
||||||
|
help
|
||||||
|
Select modem device connected to the ESP DTE.
|
||||||
|
|
||||||
|
config TEST_DEVICE_MODEM_GENERIC
|
||||||
|
bool "Common modem device"
|
||||||
|
help
|
||||||
|
Generic device that could be used with most common modems (BG96, SIM76xx, A76xx).
|
||||||
|
|
||||||
|
config TEST_DEVICE_PPPD_SERVER
|
||||||
|
bool "PPPD Server"
|
||||||
|
help
|
||||||
|
Test device is a pppd service in server mode, running on linux.
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
config TEST_MODEM_APN
|
||||||
|
string "Modem APN"
|
||||||
|
depends on TEST_DEVICE_MODEM_GENERIC
|
||||||
|
default "lpwa.vodafone.com"
|
||||||
|
help
|
||||||
|
Set APN (Access Point Name), a logical name to choose data network
|
||||||
|
|
||||||
|
config TEST_USE_VFS_TERM
|
||||||
|
bool "Use VFS terminal"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Demonstrate use of VFS as a communication terminal of the DTE.
|
||||||
|
VFS driver implements non-block reads, writes and selects to communicate with esp-modem,
|
||||||
|
but this implementation uses UART resource only.
|
||||||
|
|
||||||
|
config TEST_OTA_URI
|
||||||
|
string "URI of the binary"
|
||||||
|
default "https://192.168.11.1/esp32.bin"
|
||||||
|
help
|
||||||
|
HTTPS address of the update binary.
|
||||||
|
|
||||||
|
config TEST_OTA_CA_CERT
|
||||||
|
string "Server certificate"
|
||||||
|
default "---paste the server side certificate here---"
|
||||||
|
help
|
||||||
|
Insert the CA cert of the server side here. copy the base64 text between -----BEGIN CERTIFICATE-----
|
||||||
|
and -----END CERTIFICATE-----.
|
||||||
|
|
||||||
|
config TEST_OTA_CN
|
||||||
|
string "Server common name"
|
||||||
|
default "192.168.11.1"
|
||||||
|
help
|
||||||
|
Insert the server's common name to be checked within verification of the Server side certificat
|
||||||
|
|
||||||
|
config BROKER_URI
|
||||||
|
string "Broker URL"
|
||||||
|
default "mqtt://test.mosquitto.org"
|
||||||
|
help
|
||||||
|
URL of an mqtt broker which this example connects to.
|
||||||
|
|
||||||
|
endmenu
|
55
components/esp_modem/test/target_ota/main/network_dce.hpp
Normal file
55
components/esp_modem/test/target_ota/main/network_dce.hpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cxx_include/esp_modem_dte.hpp"
|
||||||
|
#include "esp_modem_config.h"
|
||||||
|
#include "cxx_include/esp_modem_api.hpp"
|
||||||
|
#include "cxx_include/esp_modem_dce_factory.hpp"
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
class NetModule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Custom factory which can build and create a DCE using a custom module
|
||||||
|
*/
|
||||||
|
class NetDCE_Factory: public esp_modem::dce_factory::Factory {
|
||||||
|
public:
|
||||||
|
template <typename T, typename ...Args>
|
||||||
|
static auto create(const esp_modem::dce_factory::config *cfg, Args &&... args) -> std::shared_ptr<esp_modem::DCE_T<T>>
|
||||||
|
{
|
||||||
|
return build_generic_DCE<T, esp_modem::DCE_T<T>, std::shared_ptr<esp_modem::DCE_T<T>>>(cfg, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This is a null-module, doesn't define any AT commands, just passes everything to pppd
|
||||||
|
*/
|
||||||
|
class NetModule: public esp_modem::ModuleIf {
|
||||||
|
public:
|
||||||
|
explicit NetModule(std::shared_ptr<esp_modem::DTE> dte, const esp_modem_dce_config *cfg):
|
||||||
|
dte(std::move(dte)) {}
|
||||||
|
|
||||||
|
bool setup_data_mode() override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_mode(esp_modem::modem_mode mode) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<esp_modem::DTE> dte;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<esp_modem::DCE_T<NetModule>> create(std::shared_ptr<esp_modem::DTE> dte, esp_netif_t *netif)
|
||||||
|
{
|
||||||
|
const esp_modem::dce_config config = {};
|
||||||
|
return NetDCE_Factory::create<NetModule>(&config, dte, netif);
|
||||||
|
}
|
307
components/esp_modem/test/target_ota/main/ota_test.cpp
Normal file
307
components/esp_modem/test/target_ota/main/ota_test.cpp
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "esp_netif.h"
|
||||||
|
#include "esp_netif_ppp.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "cxx_include/esp_modem_dte.hpp"
|
||||||
|
#include "esp_modem_config.h"
|
||||||
|
#include "cxx_include/esp_modem_api.hpp"
|
||||||
|
#include "esp_vfs_dev.h" // For optional VFS support
|
||||||
|
#include "vfs_resource/vfs_create.hpp"
|
||||||
|
#include "network_dce.hpp"
|
||||||
|
#include "manual_ota.hpp"
|
||||||
|
#include "mqtt_client.h"
|
||||||
|
|
||||||
|
using namespace esp_modem;
|
||||||
|
|
||||||
|
static const char *TAG = "ota_test";
|
||||||
|
|
||||||
|
|
||||||
|
// Wrap event handlers to destruct correctly on returning from main
|
||||||
|
class StatusHandler {
|
||||||
|
public:
|
||||||
|
static constexpr auto IP_Event = SignalGroup::bit0;
|
||||||
|
static constexpr auto MQTT_Connect = SignalGroup::bit1;
|
||||||
|
static constexpr auto MQTT_Data = SignalGroup::bit2;
|
||||||
|
|
||||||
|
StatusHandler()
|
||||||
|
{
|
||||||
|
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
~StatusHandler()
|
||||||
|
{
|
||||||
|
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_mqtt(esp_mqtt_client_handle_t client)
|
||||||
|
{
|
||||||
|
mqtt = client;
|
||||||
|
esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, on_event, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_mqtt()
|
||||||
|
{
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||||
|
esp_mqtt_client_unregister_event(mqtt, MQTT_EVENT_ANY, on_event);
|
||||||
|
#endif
|
||||||
|
mqtt = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t wait_for(decltype(IP_Event) event, int milliseconds)
|
||||||
|
{
|
||||||
|
return signal.wait_any(event, milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_event_t get_ip_event_type()
|
||||||
|
{
|
||||||
|
return ip_event_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void on_event(void *arg, esp_event_base_t base, int32_t event, void *data)
|
||||||
|
{
|
||||||
|
auto *handler = static_cast<StatusHandler *>(arg);
|
||||||
|
if (base == IP_EVENT) {
|
||||||
|
handler->ip_event(event, data);
|
||||||
|
} else {
|
||||||
|
handler->mqtt_event(event, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ip_event(int32_t id, void *data)
|
||||||
|
{
|
||||||
|
if (id == IP_EVENT_PPP_GOT_IP) {
|
||||||
|
auto *event = (ip_event_got_ip_t *)data;
|
||||||
|
ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip));
|
||||||
|
ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask));
|
||||||
|
ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw));
|
||||||
|
signal.set(IP_Event);
|
||||||
|
} else if (id == IP_EVENT_PPP_LOST_IP) {
|
||||||
|
signal.set(IP_Event);
|
||||||
|
}
|
||||||
|
ip_event_type = static_cast<ip_event_t>(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqtt_event(int32_t event, void *data)
|
||||||
|
{
|
||||||
|
if (mqtt && event == MQTT_EVENT_CONNECTED) {
|
||||||
|
signal.set(MQTT_Connect);
|
||||||
|
} else if (mqtt && event == MQTT_EVENT_DATA) {
|
||||||
|
auto event_data = static_cast<esp_mqtt_event_handle_t>(data);
|
||||||
|
ESP_LOGI(TAG, " TOPIC: %.*s", event_data->topic_len, event_data->topic);
|
||||||
|
ESP_LOGI(TAG, " DATA: %.*s", event_data->data_len, event_data->data);
|
||||||
|
signal.set(MQTT_Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_modem::SignalGroup signal{};
|
||||||
|
esp_mqtt_client_handle_t mqtt{};
|
||||||
|
ip_event_t ip_event_type{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap MQTT operations to destroy everything on returning from main
|
||||||
|
struct PublishOnce {
|
||||||
|
esp_mqtt_client_handle_t mqtt_;
|
||||||
|
StatusHandler *events_;
|
||||||
|
|
||||||
|
PublishOnce(StatusHandler *events)
|
||||||
|
{
|
||||||
|
esp_mqtt_client_config_t config = { };
|
||||||
|
config.broker.address.uri = CONFIG_BROKER_URI;
|
||||||
|
mqtt_ = esp_mqtt_client_init(&config);
|
||||||
|
events_ = events;
|
||||||
|
events->handle_mqtt(mqtt_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Connect()
|
||||||
|
{
|
||||||
|
return esp_mqtt_client_start(mqtt_) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SubscribePublish()
|
||||||
|
{
|
||||||
|
return esp_mqtt_client_subscribe(mqtt_, "/topic/esp-modem", 0) >= 0 &&
|
||||||
|
esp_mqtt_client_publish(mqtt_, "/topic/esp-modem", "Hello modem", 0, 0, 0) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~PublishOnce()
|
||||||
|
{
|
||||||
|
events_->remove_mqtt();
|
||||||
|
esp_mqtt_client_destroy(mqtt_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// OTA related
|
||||||
|
static constexpr auto OTA_OK = SignalGroup::bit0;
|
||||||
|
static constexpr auto OTA_FAILED = SignalGroup::bit1;
|
||||||
|
|
||||||
|
void ota_task(void *ctx)
|
||||||
|
{
|
||||||
|
static const char *ca_cert_pem = "-----BEGIN CERTIFICATE-----\n" CONFIG_TEST_OTA_CA_CERT "\n-----END CERTIFICATE-----";
|
||||||
|
auto ota_done = static_cast<esp_modem::SignalGroup *>(ctx);
|
||||||
|
manual_ota ota;
|
||||||
|
ota.http_.config_.url = CONFIG_TEST_OTA_URI;
|
||||||
|
ota.http_.config_.cert_pem = ca_cert_pem;
|
||||||
|
ota.size_ = 32;
|
||||||
|
ota.common_name_ = CONFIG_TEST_OTA_CN;
|
||||||
|
#ifndef CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
|
||||||
|
// will have to use NORMAL mode, before custom transport is supported in IDF
|
||||||
|
ota.mode_ = manual_ota::mode::NORMAL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ota.begin();
|
||||||
|
while (true) {
|
||||||
|
if (!ota.perform()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto ret = ota.end();
|
||||||
|
ota_done->set(ret ? OTA_OK : OTA_FAILED);
|
||||||
|
vTaskDelete(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// App related
|
||||||
|
extern "C" void app_main(void)
|
||||||
|
{
|
||||||
|
esp_log_level_set("*", ESP_LOG_INFO);
|
||||||
|
esp_log_level_set("ota_test", ESP_LOG_DEBUG);
|
||||||
|
|
||||||
|
// Initialize system functions
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
|
ESP_ERROR_CHECK(esp_netif_init());
|
||||||
|
|
||||||
|
// Initialize DTE
|
||||||
|
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||||
|
#ifdef CONFIG_TEST_USE_VFS_TERM
|
||||||
|
// To code-cover the vfs layers
|
||||||
|
struct esp_modem_vfs_uart_creator uart_config = ESP_MODEM_VFS_DEFAULT_UART_CONFIG("/dev/uart/1");
|
||||||
|
assert(vfs_create_uart(&uart_config, &dte_config.vfs_config) == true);
|
||||||
|
|
||||||
|
auto dte = create_vfs_dte(&dte_config);
|
||||||
|
esp_vfs_dev_uart_use_driver(uart_config.uart.port_num);
|
||||||
|
#else
|
||||||
|
auto dte = create_uart_dte(&dte_config);
|
||||||
|
#endif // CONFIG_TEST_USE_VFS_TERM
|
||||||
|
assert(dte);
|
||||||
|
dte->set_error_cb([](terminal_error err) {
|
||||||
|
ESP_LOGE(TAG, "DTE reported terminal error: %d", static_cast<int>(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize PPP netif
|
||||||
|
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
|
||||||
|
esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);
|
||||||
|
assert(esp_netif);
|
||||||
|
|
||||||
|
// Initialize DCE
|
||||||
|
#ifdef CONFIG_TEST_DEVICE_PPPD_SERVER
|
||||||
|
auto dce = create(dte, esp_netif);
|
||||||
|
#else
|
||||||
|
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_TEST_MODEM_APN);
|
||||||
|
auto dce = create_generic_dce(&dce_config, dte, esp_netif);
|
||||||
|
assert(dce);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
StatusHandler handler;
|
||||||
|
|
||||||
|
#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER
|
||||||
|
if (dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) &&
|
||||||
|
dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) &&
|
||||||
|
dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA)) {
|
||||||
|
#else
|
||||||
|
if (dce->set_mode(esp_modem::modem_mode::DATA_MODE)) {
|
||||||
|
#endif
|
||||||
|
ESP_LOGI(TAG, "Modem has correctly entered the desired mode (CMUX/DATA/Manual CMUX)");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Failed to configure multiplexed command mode... exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handler.wait_for(StatusHandler::IP_Event, 60000)) {
|
||||||
|
ESP_LOGE(TAG, "Cannot get IP within specified timeout... exiting");
|
||||||
|
return;
|
||||||
|
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_GOT_IP) {
|
||||||
|
ESP_LOGI(TAG, "Got IP address");
|
||||||
|
|
||||||
|
/* When connected to network, subscribe and publish some MQTT data */
|
||||||
|
PublishOnce publish(&handler);
|
||||||
|
if (!publish.Connect()) {
|
||||||
|
ESP_LOGE(TAG, "Failed to connect to mqtt server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!handler.wait_for(StatusHandler::MQTT_Connect, 60000)) {
|
||||||
|
ESP_LOGE(TAG, "Cannot connect to %s within specified timeout... exiting", CONFIG_BROKER_URI);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Connected");
|
||||||
|
|
||||||
|
if (!publish.SubscribePublish()) {
|
||||||
|
ESP_LOGE(TAG, "Failed to subscribe and publish to mqtt server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handler.wait_for(StatusHandler::MQTT_Data, 60000)) {
|
||||||
|
ESP_LOGE(TAG, "Didn't receive published data within specified timeout... exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Received MQTT data");
|
||||||
|
|
||||||
|
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_LOST_IP) {
|
||||||
|
ESP_LOGE(TAG, "PPP client has lost connection... exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
esp_modem::SignalGroup ota_done{};
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
|
||||||
|
// now stop the LCP keepalive before performing OTA
|
||||||
|
esp_netif_ppp_config_t cfg;
|
||||||
|
ESP_ERROR_CHECK(esp_netif_ppp_get_params(esp_netif, &cfg));
|
||||||
|
cfg.ppp_lcp_echo_disabled = true;
|
||||||
|
ESP_ERROR_CHECK(esp_netif_ppp_set_params(esp_netif, &cfg));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Run the OTA in a separate task to keep sending commands to the modem at the same time
|
||||||
|
xTaskCreate(ota_task, "ota_task", 8192, &ota_done, 5, nullptr);
|
||||||
|
|
||||||
|
#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER
|
||||||
|
while (true) {
|
||||||
|
std::string str;
|
||||||
|
if (dce->get_imsi(str) == esp_modem::command_result::OK) {
|
||||||
|
ESP_LOGI(TAG, "Modem IMSI number: %s", str.c_str());
|
||||||
|
}
|
||||||
|
if (ota_done.wait_any(OTA_OK | OTA_FAILED, 100)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ota_done.wait_any(OTA_OK | OTA_FAILED, portMAX_DELAY);
|
||||||
|
#endif // CONFIG_TEST_DEVICE_PPPD_SERVER
|
||||||
|
|
||||||
|
#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER
|
||||||
|
if (dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT)) {
|
||||||
|
#else
|
||||||
|
if (dce->set_mode(esp_modem::modem_mode::COMMAND_MODE)) {
|
||||||
|
#endif
|
||||||
|
ESP_LOGI(TAG, "Modem CMUX/DATA mode exit");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Failed to configure desired mode... exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ota_done.is_any(OTA_OK)) {
|
||||||
|
ESP_LOGI(TAG, "Prepare to restart system!");
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
2
components/esp_modem/test/target_ota/sdkconfig.ci.1
Normal file
2
components/esp_modem/test/target_ota/sdkconfig.ci.1
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
CONFIG_TEST_DEVICE_PPPD_SERVER=y
|
||||||
|
CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD=n
|
4
components/esp_modem/test/target_ota/sdkconfig.ci.2
Normal file
4
components/esp_modem/test/target_ota/sdkconfig.ci.2
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
CONFIG_TEST_DEVICE_MODEM_GENERIC=y
|
||||||
|
CONFIG_TEST_OTA_URI="https://raw.githubusercontent.com/espressif/esp-protocols/master/components/esp_modem/test/target_ota/bin/blink.bin"
|
||||||
|
CONFIG_TEST_OTA_CA_CERT="MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBSU0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6aqXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddng9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuWraKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGBAfr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21reacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEBMAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IBAQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3zax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7hqG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbCEXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697EA7sKPPcw7+uvTPyLNhBzPvOk"
|
||||||
|
CONFIG_TEST_OTA_CN="github.com"
|
4
components/esp_modem/test/target_ota/sdkconfig.ci.3
Normal file
4
components/esp_modem/test/target_ota/sdkconfig.ci.3
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
CONFIG_TEST_DEVICE_PPPD_SERVER=y
|
||||||
|
CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD=y
|
||||||
|
CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED=y
|
||||||
|
CONFIG_TEST_USE_VFS_TERM=y
|
12
components/esp_modem/test/target_ota/sdkconfig.defaults
Normal file
12
components/esp_modem/test/target_ota/sdkconfig.defaults
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||||
|
CONFIG_PARTITION_TABLE_TWO_OTA=y
|
||||||
|
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||||
|
# This is not supported in IDF yet
|
||||||
|
# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT=y
|
||||||
|
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||||
|
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||||
|
CONFIG_LWIP_PPP_SUPPORT=y
|
||||||
|
CONFIG_LWIP_PPP_ENABLE_IPV6=n
|
||||||
|
CONFIG_LWIP_ENABLE_LCP_ECHO=y
|
||||||
|
CONFIG_LWIP_LCP_ECHOINTERVAL=1
|
||||||
|
CONFIG_LWIP_LCP_MAXECHOFAILS=2
|
Reference in New Issue
Block a user