mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-19 05:22:21 +02:00
308 lines
9.4 KiB
C++
308 lines
9.4 KiB
C++
/*
|
|
* 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();
|
|
}
|
|
|
|
}
|