Browse Source

Add basic support for OTA & MQTT debugging

rmt-backend
jrhoffa 3 years ago
parent
commit
b7ff7fa8be
14 changed files with 623 additions and 0 deletions
  1. +7
    -0
      .gitignore
  2. +8
    -0
      CMakeLists.txt
  3. +29
    -0
      main/CMakeLists.txt
  4. +20
    -0
      main/Kconfig.projbuild
  5. +24
    -0
      main/http_serv.cpp
  6. +7
    -0
      main/http_serv.hpp
  7. +115
    -0
      main/main.cpp
  8. +108
    -0
      main/mqtt.cpp
  9. +17
    -0
      main/mqtt.hpp
  10. +163
    -0
      main/ota.cpp
  11. +7
    -0
      main/ota.hpp
  12. +107
    -0
      main/wifi.cpp
  13. +3
    -0
      main/wifi.hpp
  14. +8
    -0
      partitions.csv

+ 7
- 0
.gitignore View File

@@ -0,0 +1,7 @@
build
main/wifi_cfg.hpp
CMakeFiles
CMakeCache.txt
sdkconfig
sdkconfig.old
main/broker.pem

+ 8
- 0
CMakeLists.txt View 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.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(blinky)

target_add_binary_data(blinky.elf "main/broker.pem" TEXT)

+ 29
- 0
main/CMakeLists.txt View File

@@ -0,0 +1,29 @@
idf_component_register(
SRCS
"main.cpp"
"wifi.cpp"
"http_serv.cpp"
"ota.cpp"
"mqtt.cpp"

INCLUDE_DIRS "."
)

# Build static library, do not build test executables
option(BUILD_SHARED_LIBS OFF)
option(BUILD_TESTING OFF)

# Unfortunately the library performs install and export. Would
# have been nice if devs made that an option like BUILD_SHARED_LIBS
# and BUILD_TESTING. Override install() and export() to do nothing
# instead.
function(install)
endfunction()

function(export)
endfunction()

# Import tinyxml2 targets
#add_subdirectory(lib/tinyxml2)
# Link tinyxml2 to main component
#target_link_libraries(${COMPONENT_LIB} PUBLIC tinyxml2)

+ 20
- 0
main/Kconfig.projbuild View File

@@ -0,0 +1,20 @@
menu "* Project: MQTT Broker Configuration"

config BROKER_URI
string "Broker URL"
default "mqtts://mqtt.eclipseprojects.io:8883"
help
URL of an mqtt broker which this client connects to.

config BROKER_USER
string "Broker user"
default "blinky"
help
Username for the mqtt broker.

config BROKER_PASSWORD
string "Broker password"
help
Password for the mqtt broker.

endmenu

+ 24
- 0
main/http_serv.cpp View File

@@ -0,0 +1,24 @@
static const char *TAG = "http_serv";
#include "esp_log.h"
#include "http_serv.hpp"
httpd_handle_t start_webserver(void) {
ESP_LOGI(TAG, "Starting HTTP server");
// Default configuration, EXCEPT for the wildcard URI match
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 4096 * 4;
config.uri_match_fn = httpd_uri_match_wildcard;
// Start the httpd server
httpd_handle_t server = NULL;
ESP_ERROR_CHECK(httpd_start(&server, &config));
return server;
}
void stop_webserver(httpd_handle_t server) {
if (server) httpd_stop(server);
}

+ 7
- 0
main/http_serv.hpp View File

@@ -0,0 +1,7 @@
#pragma once
#include <esp_http_server.h>
httpd_handle_t start_webserver(void);
void stop_webserver(httpd_handle_t server);

+ 115
- 0
main/main.cpp View File

@@ -0,0 +1,115 @@
static const char *TAG = "blinky";
#include <esp_log.h>

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include <esp_spiffs.h>
#include <nvs_flash.h>

#include "wifi.hpp"
#include "http_serv.hpp"
#include "ota.hpp"
#include "mqtt.hpp"


// mDNS / NetBIOS

#include <mdns.h>
#include <lwip/apps/netbiosns.h>

static void start_mdns_service(const char *hostname) {
// Initialize mDNS service
ESP_ERROR_CHECK(mdns_init());

// Set hostname
mdns_hostname_set(hostname);

// Set default instance
mdns_instance_name_set("Blinky Lights");

// NetBIOS too
netbiosns_init();
netbiosns_set_name(hostname);
}


// Dummy FS

static void start_filesystem() {
ESP_LOGI(TAG, "Initializing filesystem");

esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = true
};

// Use settings defined above to initialize and mount SPIFFS filesystem.
// Note: esp_vfs_spiffs_register is an all-in-one convenience function.
ESP_ERROR_CHECK(esp_vfs_spiffs_register(&conf));
}

#include <string>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>

static void log_dir(const std::string &path) {
struct stat st;
int err = stat(path.c_str(), &st);
if (err < 0) {
ESP_LOGE(TAG, "%s %d", path.c_str(), errno);
} else {
ESP_LOGI(TAG, "%s %d", path.c_str(), st.st_size);
}

DIR *dir;
if ((dir = opendir(path.c_str())) == NULL) {
//ESP_LOGE(TAG, "Failed to open %s", path.c_str());
return;
}

struct dirent *de;
while ((de = readdir(dir)) != NULL) {
log_dir(path + "/" + de->d_name);
}

closedir(dir);
}


// Entry Point

extern "C" void app_main(void) {
// Initialize NVS for WiFi Data
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

// WiFi
config_wifi();

// mDNS
start_mdns_service("blinky-jr");

// HTTP Server
httpd_handle_t server = start_webserver();

// OTA Server
start_ota_serv(server);

// Dummy Filesystem
start_filesystem();
log_dir("/spiffs");

// TODO: Something useful
esp_mqtt_client_handle_t client = start_mqtt_client(NULL, NULL, NULL);
esp_mqtt_client_subscribe(client, "#", 0);

// Spin
}

+ 108
- 0
main/mqtt.cpp View File

@@ -0,0 +1,108 @@
static const char *TAG = "mqtt";
#include <esp_log.h>

#include "mqtt.hpp"


extern const uint8_t broker_pem_start[] asm("_binary_broker_pem_start");


typedef struct {
mqtt_connect_handler connect;
mqtt_message_handler message;
void* data;
} mqtt_client_data;


static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
mqtt_client_data *client_data = (mqtt_client_data*)handler_args;
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
esp_mqtt_client_handle_t client = event->client;
int msg_id;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
if (client_data && client_data->connect) {
client_data->connect(client, client_data->data);
}
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
if (client_data && client_data->message) {
client_data->message(client, event, client_data->data);
}
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGI(TAG, "Last error code reported from esp-tls: 0x%x", event->error_handle->esp_tls_last_esp_err);
ESP_LOGI(TAG, "Last tls stack error number: 0x%x", event->error_handle->esp_tls_stack_err);
ESP_LOGI(TAG, "Last captured errno : %d (%s)", event->error_handle->esp_transport_sock_errno,
strerror(event->error_handle->esp_transport_sock_errno));
} else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
ESP_LOGI(TAG, "Connection refused error: 0x%x", event->error_handle->connect_return_code);
} else {
ESP_LOGW(TAG, "Unknown error type: 0x%x", event->error_handle->error_type);
}
break;
/*
case MQTT_USER_EVENT:
event->data = (char*)handler_args;
break;
*/
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}

esp_mqtt_client_handle_t start_mqtt_client(
mqtt_connect_handler connect,
mqtt_message_handler message,
void* data
) {
ESP_LOGI(TAG, "Starting MQTT client");

mqtt_client_data* client_data = (mqtt_client_data*)malloc(sizeof(*client_data));
client_data->connect = connect;
client_data->message = message;
client_data->data = data;

const esp_mqtt_client_config_t mqtt_cfg = {
.uri = CONFIG_BROKER_URI,
.cert_pem = (const char *)broker_pem_start,
.username = CONFIG_BROKER_USER,
.password = CONFIG_BROKER_PASSWORD,
};

esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, client_data);
esp_mqtt_client_start(client);

return client;
}

void stop_mqtt_client(esp_mqtt_client_handle_t client) {
/*
// Hijack the user event to grab the client data on the heap
esp_mqtt_event_t event;
esp_mqtt_dispatch_custom_event(client, &event);
esp_mqtt_client_stop(client);
free((mqtt_client_data*)event->data)
*/
}

+ 17
- 0
main/mqtt.hpp View File

@@ -0,0 +1,17 @@
#pragma once

#include <mqtt_client.h>


typedef void (*mqtt_connect_handler)(
esp_mqtt_client_handle_t, void*
);

typedef void (*mqtt_message_handler)(
esp_mqtt_client_handle_t, esp_mqtt_event_handle_t, void*
);

esp_mqtt_client_handle_t start_mqtt_client(
mqtt_connect_handler, mqtt_message_handler, void*
);
void stop_mqtt_client(esp_mqtt_client_handle_t);

+ 163
- 0
main/ota.cpp View File

@@ -0,0 +1,163 @@
static const char *TAG = "ota";
#include <esp_log.h>

#include <mdns.h>
#include <esp_ota_ops.h>

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include "ota.hpp"


// The real version probably lives somewhere else. Too lazy to find it
template <class T> static inline T MIN(T a, T b) { return a < b ? a : b; }

// TODO: Stick this somewhere useful?
static void reboot_soon_task(void*) {
int delay_s = 1;
ESP_LOGI(TAG, "Rebooting in %d second%s",
delay_s, delay_s == 1 ? "" : "s");
vTaskDelay(pdMS_TO_TICKS(delay_s * 1000));
esp_restart();
}

static void reboot_soon() {
xTaskCreate(
reboot_soon_task, "reboot_task",
configMINIMAL_STACK_SIZE * 2, // TODO: Choose the best size
NULL, 1, NULL
);
}

// HTTP Server Helpers.
// TODO: Share these?
static inline esp_err_t serv_err(httpd_req_t *req, const char *msg) {
ESP_LOGE(TAG, "%s", msg);
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg);
}

static inline esp_err_t serv_progress(httpd_req_t *req, int progress) {
ESP_LOGI(TAG, "%d%% complete", progress);
/*
char msg[100];
snprintf(msg, sizeof(msg), "%d%% complete\n", progress);
httpd_resp_set_status(req, "102 Processing");
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
return httpd_resp_send(req, msg, HTTPD_RESP_USE_STRLEN);
*/
return ESP_OK;
}


// OTA POST
static esp_err_t ota_post_handler(httpd_req_t *req)
{
esp_err_t err;

const esp_partition_t *part = esp_ota_get_next_update_partition(NULL);
if (!part) return serv_err(req, "Could not find update partition");

int remaining = req->content_len;
ESP_LOGI(TAG, "Starting OTA: %d bytes", remaining);

esp_ota_handle_t ota;
err = esp_ota_begin(part, remaining, &ota);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin: %d", err);
return serv_err(req, "Could not start OTA");
}

constexpr int progress_step = 10;
int next_progress = 0;
constexpr size_t CHUNK_MAX = 4096;
char *chunk_buf = new char[CHUNK_MAX];
while (remaining > 0) {
int chunk_size = MIN((unsigned int)remaining, CHUNK_MAX);

int nread = httpd_req_recv(req, chunk_buf, chunk_size);
if (nread <= 0) { /* 0 return value indicates connection closed */
ESP_LOGE(TAG, "httpd_req_recv(%d): %d", chunk_size, nread);
esp_ota_abort(ota);
delete[] chunk_buf;

/* Check if timeout occurred */
if (nread == HTTPD_SOCK_ERR_TIMEOUT) {
/* In case of timeout one can choose to retry calling
* httpd_req_recv(), but to keep it simple, here we
* respond with an HTTP 408 (Request Timeout) error */
httpd_resp_send_408(req);
}

/* In case of error, returning ESP_FAIL will
* ensure that the underlying socket is closed */
return ESP_FAIL;
}

err = esp_ota_write(ota, chunk_buf, nread);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_write(%d): %d", nread, err);
esp_ota_abort(ota);
delete[] chunk_buf;
return serv_err(req, "Failed to write OTA chunk");
}

remaining -= nread;

int progress = 100 * (req->content_len - remaining) / req->content_len;
if (progress >= next_progress) {
serv_progress(req, progress);
next_progress += progress_step;
}
}
delete[] chunk_buf;

ESP_LOGI(TAG, "Finalizing OTA");

err = esp_ota_end(ota);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end: %d", err);
esp_ota_abort(ota);
return serv_err(req, "Failed to finalize OTA");
}

ESP_LOGI(TAG, "Setting new boot partition");
err = esp_ota_set_boot_partition(part);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition: %d", err);
return serv_err(req, "Failed to set partition");
}

ESP_LOGI(TAG, "OTA Successful");
httpd_resp_sendstr(req, "OTA Successful\n");

reboot_soon();

return ESP_FAIL;
}


int start_ota_serv(httpd_handle_t server) {
ESP_LOGI(TAG, "Starting OTA server");

// Register handlers (move these to components)
httpd_uri_t uri_ota_post = {
.uri = "/ota",
.method = HTTP_POST,
.handler = ota_post_handler,
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server, &uri_ota_post));

// mDNS Entry
mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0);
mdns_service_instance_name_set("_http", "_tcp", "ESP32S2 OTA");

return 0;
}


int stop_ota_serv() {
// TODO
return -1;
}

+ 7
- 0
main/ota.hpp View File

@@ -0,0 +1,7 @@
#pragma once

#include <esp_http_server.h>


int start_ota_serv(httpd_handle_t);
int stop_ota_serv();

+ 107
- 0
main/wifi.cpp View File

@@ -0,0 +1,107 @@
static const char *TAG = "WiFi";
#include "esp_log.h"
#include "wifi.hpp"
// Defines SSID and PASS
#include "wifi_cfg.hpp"
static const int CONFIG_ESP_MAXIMUM_RETRY = 3;
#include "esp_wifi.h"
#include "freertos/event_groups.h"
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
static int s_retry_num = 0;
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected, retrying connection to AP");
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
int config_wifi() {
ESP_LOGI(TAG, "Connecting to %s", SSID);
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id
));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip
));
wifi_config_t wifi_config = {
.sta = {
.ssid = SSID,
.password = PASS,
},
};
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
/*
// Waiting until either the connection is established (WIFI_CONNECTED_BIT)
// or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT).
// The bits are set by event_handler() (see above)
EventBits_t bits = xEventGroupWaitBits(
s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY
);
// xEventGroupWaitBits() returns the bits before the call returned, hence we
// can test which event actually happened.
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to AP: %s", SSID);
return 0;
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "Failed to connect to SSID %s", SSID);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
*/
return 0;
}

+ 3
- 0
main/wifi.hpp View File

@@ -0,0 +1,3 @@
#pragma once
int config_wifi();

+ 8
- 0
partitions.csv View File

@@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000
otadata, data, ota, , 0x2000
phy_init, data, phy, , 0x1000
factory, app, factory, , 1M
ota_0, app, ota_0, , 1M
ota_1, app, ota_1, , 1M
storage, data, spiffs, , 0xF0000

Loading…
Cancel
Save