Compare commits
11 Commits
e458256193
...
41516374f0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41516374f0 | ||
|
|
4cda7d2de3 | ||
|
|
a801caaae6 | ||
|
|
89e8014798 | ||
|
|
1d12dc5160 | ||
|
|
0b26e0c7c9 | ||
|
|
89daff2267 | ||
|
|
18ac21e257 | ||
|
|
821fb0d9d7 | ||
|
|
01c36669cf | ||
|
|
d339a1f4c3 |
@@ -1,5 +1,6 @@
|
||||
set(requires esp-tls spi_flash nvs_flash esp_event esp_netif esp_http_client esp_wifi)
|
||||
file(GLOB SRCS "main.cpp" "*.cpp" "*.c" "**/*.cpp" "**/*.c")
|
||||
|
||||
idf_component_register(SRCS ${SRCS}
|
||||
PRIV_REQUIRES
|
||||
spi_flash
|
||||
PRIV_REQUIRES ${requires}
|
||||
INCLUDE_DIRS "." "display" "touch" "network" "ui" "io" "common")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
// 800x480 = 384,000 pixels
|
||||
|
||||
#define EINK_WIDTH 800
|
||||
@@ -12,3 +13,5 @@
|
||||
//
|
||||
#define DISPLAY_READY_BIT (1 << 1)
|
||||
#define TOUCH_CALIBRATED_BIT (1 << 2)
|
||||
#define STORAGE_READY_BIT (1 << 3)
|
||||
#define NETWORK_READY_BIT (1 << 4)
|
||||
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
#include <indev/lv_indev.h>
|
||||
|
||||
typedef enum {
|
||||
|
||||
28
main/common/semaphore_guard.h
Normal file
28
main/common/semaphore_guard.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/portmacro.h"
|
||||
|
||||
struct SemaphoreGuard {
|
||||
public:
|
||||
const SemaphoreHandle_t semaphore;
|
||||
|
||||
SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore(semaphore) { }
|
||||
|
||||
portBASE_TYPE take(TickType_t ticks_to_wait = portMAX_DELAY) {
|
||||
portBASE_TYPE result = xSemaphoreTake(this->semaphore, ticks_to_wait);
|
||||
taken = (result == pdTRUE);
|
||||
return result;
|
||||
}
|
||||
|
||||
~SemaphoreGuard() {
|
||||
if (taken) {
|
||||
xSemaphoreGive(this->semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// prevent copying
|
||||
SemaphoreGuard(const SemaphoreGuard&) = delete;
|
||||
SemaphoreGuard& operator=(const SemaphoreGuard&) = delete;
|
||||
bool taken = false;
|
||||
};
|
||||
27
main/io/io.h
27
main/io/io.h
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include <memory>
|
||||
|
||||
typedef bool(*FilterFunc)(const char* const& key);
|
||||
typedef void (*KeyValueProcessor)(void* arg, const char* const& key, const char* const& value);
|
||||
|
||||
class KVStorageHandler {
|
||||
public:
|
||||
virtual ~KVStorageHandler() = default;
|
||||
|
||||
virtual void init(const EventGroupHandle_t& system_event_group) = 0;
|
||||
|
||||
// Store a key-value pair
|
||||
virtual void put(const char* const& key, const char* const& value) = 0;
|
||||
|
||||
// Retrieve a value by key, returns nullptr if key not found
|
||||
// The caller is responsible for freeing the returned memory
|
||||
virtual std::unique_ptr<char[]> get(const char* const& key) const = 0;
|
||||
virtual esp_err_t process_all(KeyValueProcessor processor, void* arg) const = 0;
|
||||
virtual esp_err_t process_filtered(const char* const& key_prefix, KeyValueProcessor processor, void* arg) const = 0;
|
||||
virtual esp_err_t process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const = 0;
|
||||
|
||||
// Delete a key-value pair
|
||||
virtual void remove(const char* const& key) = 0;
|
||||
};
|
||||
183
main/io/nvs_handler.cpp
Normal file
183
main/io/nvs_handler.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "common/constants.h"
|
||||
#include "io/nvs_handler.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "string.h"
|
||||
|
||||
NVSStorageHandler::NVSStorageHandler(
|
||||
const char* name_space
|
||||
) : name_space(name_space) { }
|
||||
|
||||
NVSStorageHandler::~NVSStorageHandler() {
|
||||
if (this->nvsHandle != 0) {
|
||||
nvs_close(this->nvsHandle);
|
||||
this->nvsHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NVSStorageHandler::init(const EventGroupHandle_t& system_event_group) {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
nvs_flash_erase();
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
err = nvs_open(this->name_space, NVS_READWRITE, &this->nvsHandle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
|
||||
} else {
|
||||
xEventGroupSetBits(system_event_group, STORAGE_READY_BIT);
|
||||
printf("NVS Storage initialized.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void NVSStorageHandler::put(const char* const& key, const char* const& value) {
|
||||
if (this->nvsHandle == 0) {
|
||||
printf("NVS handle is not initialized.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_set_str(this->nvsHandle, key, value);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) setting key-value pair in NVS!\n", esp_err_to_name(err));
|
||||
} else {
|
||||
nvs_commit(this->nvsHandle);
|
||||
printf("Key-value pair (%s, %s) stored in NVS.\n", key, value);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> NVSStorageHandler::get(const char* const& key) const {
|
||||
if (this->nvsHandle == 0) {
|
||||
printf("NVS handle is not initialized.\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t required_size = 0;
|
||||
esp_err_t err = nvs_get_str(this->nvsHandle, key, nullptr, &required_size);
|
||||
if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
printf("Key %s not found in NVS.\n", key);
|
||||
return nullptr;
|
||||
} else if (err != ESP_OK) {
|
||||
printf("Error (%s) getting size for key %s from NVS!\n", esp_err_to_name(err), key);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> value(new char[required_size]);
|
||||
err = nvs_get_str(this->nvsHandle, key, value.get(), &required_size);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting value for key %s from NVS!\n", esp_err_to_name(err), key);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
NVSIteratorGuard NVSStorageHandler::create_iterator() const {
|
||||
nvs_iterator_t it = nullptr;
|
||||
esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, this->name_space, NVS_TYPE_ANY, &it);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) creating NVS iterator!\n", esp_err_to_name(err));
|
||||
return NVSIteratorGuard(nullptr, err);
|
||||
}
|
||||
|
||||
return NVSIteratorGuard(it, ESP_OK);
|
||||
}
|
||||
|
||||
esp_err_t NVSStorageHandler::process_all(KeyValueProcessor processor, void* arg) const {
|
||||
NVSIteratorGuard iterator_guard = this->create_iterator();
|
||||
if (!iterator_guard.is_valid()) {
|
||||
return iterator_guard.get_error();
|
||||
}
|
||||
const nvs_iterator_t& it = iterator_guard.get_iterator();
|
||||
|
||||
for (; it != NULL; iterator_guard.advance_iter()) {
|
||||
nvs_entry_info_t info;
|
||||
esp_err_t err = nvs_entry_info(it, &info);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting NVS entry info!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
nvs_handle_t temp_handle;
|
||||
err = nvs_open(this->name_space, NVS_READONLY, &temp_handle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle for reading!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// call the processor with the key and value
|
||||
processor(arg, info.key, this->get(info.key).get());
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
esp_err_t NVSStorageHandler::process_filtered(const char* const& key_prefix, KeyValueProcessor processor, void* arg) const {
|
||||
NVSIteratorGuard iterator_guard = this->create_iterator();
|
||||
if (!iterator_guard.is_valid()) {
|
||||
return iterator_guard.get_error();
|
||||
}
|
||||
const nvs_iterator_t& it = iterator_guard.get_iterator();
|
||||
|
||||
for (; it != NULL; iterator_guard.advance_iter()) {
|
||||
nvs_entry_info_t info;
|
||||
esp_err_t err = nvs_entry_info(it, &info);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting NVS entry info!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// check if the key matches the prefix
|
||||
if (strncmp(info.key, key_prefix, strlen(key_prefix)) == 0) {
|
||||
nvs_handle_t temp_handle;
|
||||
err = nvs_open(this->name_space, NVS_READONLY, &temp_handle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle for reading!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// call the processor with the key and value
|
||||
processor(arg, info.key, this->get(info.key).get());
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t NVSStorageHandler::process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const {
|
||||
NVSIteratorGuard iterator_guard = this->create_iterator();
|
||||
if (!iterator_guard.is_valid()) {
|
||||
return iterator_guard.get_error();
|
||||
}
|
||||
const nvs_iterator_t& it = iterator_guard.get_iterator();
|
||||
|
||||
for (; it != NULL; iterator_guard.advance_iter()) {
|
||||
nvs_entry_info_t info;
|
||||
esp_err_t err = nvs_entry_info(it, &info);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting NVS entry info!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// check if the key matches the filter function
|
||||
if (filter_func(info.key)) {
|
||||
nvs_handle_t temp_handle;
|
||||
err = nvs_open(this->name_space, NVS_READONLY, &temp_handle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle for reading!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// call the processor with the key and value
|
||||
processor(arg, info.key, this->get(info.key).get());
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void NVSStorageHandler::remove(const char* const& key) {
|
||||
if (this->nvsHandle == 0) {
|
||||
printf("NVS handle is not initialized.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_erase_key(this->nvsHandle, key);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) deleting key %s from NVS!\n", esp_err_to_name(err), key);
|
||||
} else {
|
||||
nvs_commit(this->nvsHandle);
|
||||
printf("Key %s deleted from NVS.\n", key);
|
||||
}
|
||||
}
|
||||
70
main/io/nvs_handler.h
Normal file
70
main/io/nvs_handler.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "io/io.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "nvs.h"
|
||||
|
||||
struct NVSIteratorGuard {
|
||||
public:
|
||||
~NVSIteratorGuard() {
|
||||
if (iterator) {
|
||||
nvs_release_iterator(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
// accessors to the iterator, the internal state should not be modified directly
|
||||
// The iterator is advanced using advance_iter(), and is changed to nullptr on error or end
|
||||
// Caller MUST NOT release the iterator manually nor call get_iterator after advance_iter
|
||||
const nvs_iterator_t& get_iterator() const {
|
||||
return iterator;
|
||||
}
|
||||
|
||||
void advance_iter() {
|
||||
if (iterator) {
|
||||
// advance the iterator and update the internal state
|
||||
esp_err_t err = nvs_entry_next(&iterator);
|
||||
if (err != ESP_OK) {
|
||||
error = err;
|
||||
iterator = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t get_error() const {
|
||||
return error;
|
||||
}
|
||||
bool is_valid() const {
|
||||
return iterator != nullptr && error == ESP_OK;
|
||||
}
|
||||
friend class NVSStorageHandler;
|
||||
private:
|
||||
NVSIteratorGuard(nvs_iterator_t it
|
||||
, esp_err_t err
|
||||
) : iterator(it), error(err) { }
|
||||
nvs_iterator_t iterator;
|
||||
esp_err_t error;
|
||||
};
|
||||
|
||||
class NVSStorageHandler : public KVStorageHandler {
|
||||
public:
|
||||
NVSStorageHandler(
|
||||
const char* name_space
|
||||
);
|
||||
~NVSStorageHandler() override;
|
||||
|
||||
void init(const EventGroupHandle_t& system_event_group) override;
|
||||
|
||||
void put(const char* const& key, const char* const& value) override;
|
||||
|
||||
std::unique_ptr<char[]> get(const char* const& key) const override;
|
||||
esp_err_t process_all(KeyValueProcessor processor, void* arg) const override;
|
||||
esp_err_t process_filtered(const char* const& key_prefix, KeyValueProcessor processor, void* arg) const override;
|
||||
esp_err_t process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const override;
|
||||
|
||||
void remove(const char* const& key) override;
|
||||
|
||||
private:
|
||||
NVSIteratorGuard create_iterator() const;
|
||||
|
||||
nvs_handle_t nvsHandle = 0;
|
||||
const char* name_space;
|
||||
};
|
||||
@@ -18,10 +18,15 @@
|
||||
//
|
||||
#include "common/constants.h"
|
||||
#include "common/queue_defs.h"
|
||||
#include "io/nvs_handler.h"
|
||||
#include "info/info.h"
|
||||
#include "display/display.h"
|
||||
#include "touch/touch.h"
|
||||
#include <tick/lv_tick.h>
|
||||
#include "network.h"
|
||||
|
||||
#define DEFAULT_STORAGE_NAMESPACE "storage"
|
||||
#define WIFI_CREDENTIALS_STORAGE_NAMESPACE "wifi_credentials"
|
||||
|
||||
extern "C" void app_main(void);
|
||||
|
||||
@@ -49,9 +54,18 @@ void app_main(void) {
|
||||
throw std::runtime_error("Failed to create LVGL mutex");
|
||||
}
|
||||
//
|
||||
WifiHandler wifi_handler(
|
||||
new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE)
|
||||
);
|
||||
NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler));
|
||||
KVStorageHandler* kv_storage_handler = new NVSStorageHandler(
|
||||
DEFAULT_STORAGE_NAMESPACE
|
||||
);
|
||||
DisplayHandler* display_handler = new EInkDisplayHandler(touch_event_queue, lvgl_mutex);
|
||||
TouchHandler* touch_handler = new EInkTouchHandler(touch_event_queue);
|
||||
//
|
||||
network_handler->init(system_event_group);
|
||||
kv_storage_handler->init(system_event_group);
|
||||
display_handler->init(system_event_group);
|
||||
touch_handler->init(system_event_group);
|
||||
//
|
||||
@@ -75,7 +89,8 @@ void app_main(void) {
|
||||
printf("Waiting for system to be ready...\n");
|
||||
xEventGroupWaitBits(
|
||||
system_event_group,
|
||||
DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT,
|
||||
DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT | STORAGE_READY_BIT | NETWORK_READY_BIT,
|
||||
// do not clear on exit, require explicit reset
|
||||
pdFALSE,
|
||||
pdTRUE,
|
||||
portMAX_DELAY
|
||||
@@ -86,7 +101,7 @@ void app_main(void) {
|
||||
touch_handler->start_event_loop();
|
||||
// wait for shutdown signal
|
||||
EventBits_t bits = xEventGroupWaitBits(
|
||||
system_event_group,
|
||||
system_lifecycle_event_group,
|
||||
SYSTEM_SHUTDOWN_BIT | SYSTEM_RESTART_BIT,
|
||||
// do not clear on exit, require explicit reset
|
||||
pdFALSE,
|
||||
@@ -103,7 +118,6 @@ void app_main(void) {
|
||||
vSemaphoreDelete(lvgl_mutex);
|
||||
vEventGroupDelete(system_event_group);
|
||||
vQueueDelete(touch_event_queue);
|
||||
|
||||
printf("Cleanup complete.\n");
|
||||
|
||||
// handle shutdown or restart
|
||||
@@ -124,6 +138,14 @@ void app_main(void) {
|
||||
pdFALSE,
|
||||
portMAX_DELAY
|
||||
);
|
||||
if (bits & SYSTEM_START_BIT) {
|
||||
printf("SYSTEM_START_BIT received, restarting system.\n");
|
||||
} else {
|
||||
printf("No restart signal received, waiting for manual power cycle.\n");
|
||||
while (true) {
|
||||
vTaskDelay(portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
} else if (bits & SYSTEM_RESTART_BIT) {
|
||||
if (restart_display_handler != nullptr) {
|
||||
printf("Calling display restart handler...\n");
|
||||
|
||||
51
main/network/http_handler.cpp
Normal file
51
main/network/http_handler.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "network/http_handler.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_log.h"
|
||||
#include "string.h"
|
||||
|
||||
HttpHandler::HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler)
|
||||
: wifiHandler(wifiHandler) {
|
||||
this->client = esp_http_client_init(&config);
|
||||
}
|
||||
|
||||
HttpHandler::~HttpHandler() {
|
||||
if (this->client) {
|
||||
esp_http_client_cleanup(this->client);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::set_method(esp_http_client_method_t method) {
|
||||
return esp_http_client_set_method(this->client, method);
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::set_header(const char* header, const char* value) {
|
||||
return esp_http_client_set_header(this->client, header, value);
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::set_post_field(const char* field, size_t len) {
|
||||
return esp_http_client_set_post_field(this->client, field, len);
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::perform_request() {
|
||||
return esp_http_client_perform(this->client);
|
||||
}
|
||||
|
||||
void HttpHandler::get_body(
|
||||
char*& buffer,
|
||||
int& total_len
|
||||
) {
|
||||
total_len = esp_http_client_get_content_length(this->client);
|
||||
buffer = new char[total_len + 1]; // +1 for null-terminator
|
||||
if (buffer) {
|
||||
int read_len = esp_http_client_read(this->client, buffer, total_len);
|
||||
if (read_len >= 0) {
|
||||
buffer[read_len] = '\0'; // null-terminate
|
||||
} else {
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
total_len = 0;
|
||||
}
|
||||
} else {
|
||||
total_len = 0;
|
||||
}
|
||||
}
|
||||
55
main/network/http_handler.h
Normal file
55
main/network/http_handler.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "esp_http_client.h"
|
||||
#include "network/wifi_handler.h"
|
||||
|
||||
// forward declare NetworkHandler to avoid circular include with network.h
|
||||
class NetworkHandler;
|
||||
|
||||
// default config values for esp_http_client_config_t
|
||||
// disable Wmissing-field-initializers warning for these structs
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG = {
|
||||
.timeout_ms = 10000,
|
||||
};
|
||||
|
||||
static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG_HTTPS = {
|
||||
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||
//
|
||||
.use_global_ca_store = true,
|
||||
.skip_cert_common_name_check = false,
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// esp http client wrapper with automatic initialization and cleanup
|
||||
class HttpHandler {
|
||||
public:
|
||||
~HttpHandler();
|
||||
|
||||
esp_err_t set_method(esp_http_client_method_t method);
|
||||
esp_err_t set_header(const char* header, const char* value);
|
||||
esp_err_t set_post_field(const char* field, size_t len);
|
||||
//
|
||||
esp_err_t perform_request();
|
||||
// buffer is allocated inside the method, caller must free it
|
||||
void get_body(
|
||||
char*& buffer,
|
||||
int& total_len
|
||||
);
|
||||
|
||||
|
||||
// only NetworkHandler can create HttpHandler instances
|
||||
friend class NetworkHandler;
|
||||
// disable copy constructor and assignment operator
|
||||
HttpHandler(const HttpHandler&) = delete;
|
||||
HttpHandler& operator=(const HttpHandler&) = delete;
|
||||
|
||||
private:
|
||||
// private constructor, only NetworkHandler can create instances
|
||||
HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler);
|
||||
|
||||
esp_http_client_handle_t client;
|
||||
// backreference to WifiHandler to ensure WiFi is connected, DO NOT DELETE
|
||||
WifiHandler* wifiHandler;
|
||||
};
|
||||
32
main/network/network.cpp
Normal file
32
main/network/network.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "esp_log.h"
|
||||
#include "network/network.h"
|
||||
#include "network/http_handler.h"
|
||||
#include "common/constants.h"
|
||||
|
||||
NetworkHandler::NetworkHandler(
|
||||
WifiHandler&& wifiHandler
|
||||
) : wifiHandler(std::move(wifiHandler)) { }
|
||||
|
||||
NetworkHandler::~NetworkHandler() { }
|
||||
|
||||
void NetworkHandler::init(EventGroupHandle_t system_event_group) {
|
||||
if (this->initialized) {
|
||||
ESP_LOGW("NetworkHandler", "Already initialized, skipping");
|
||||
return;
|
||||
}
|
||||
this->wifiHandler.init();
|
||||
this->initialized = true;
|
||||
xEventGroupSetBits(
|
||||
system_event_group,
|
||||
NETWORK_READY_BIT
|
||||
);
|
||||
}
|
||||
|
||||
WifiHandler& NetworkHandler::get_wifi_handler() {
|
||||
return this->wifiHandler;
|
||||
}
|
||||
|
||||
std::unique_ptr<HttpHandler> NetworkHandler::get_http_handler(const esp_http_client_config_t&& config) {
|
||||
return std::unique_ptr<HttpHandler>(new HttpHandler(std::move(config), &this->wifiHandler));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_system.h"
|
||||
#include "network/wifi_handler.h"
|
||||
#include "esp_http_client.h"
|
||||
|
||||
// forward declare HttpHandler to avoid circular include with http_handler.h
|
||||
class HttpHandler;
|
||||
|
||||
class NetworkHandler {
|
||||
public:
|
||||
NetworkHandler(
|
||||
WifiHandler&& wifiHandler
|
||||
);
|
||||
~NetworkHandler();
|
||||
|
||||
void init(EventGroupHandle_t system_event_group);
|
||||
WifiHandler& get_wifi_handler();
|
||||
// factory method to create HttpHandler instances
|
||||
std::unique_ptr<HttpHandler> get_http_handler(const esp_http_client_config_t&& config);
|
||||
|
||||
|
||||
private:
|
||||
WifiHandler wifiHandler;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
301
main/network/wifi_handler.cpp
Normal file
301
main/network/wifi_handler.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
#include "wifi_handler.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "common/semaphore_guard.h"
|
||||
|
||||
static const char* TAG = "WifiHandler";
|
||||
static const char* WIFI_SSID_KEY = "wifi_ssid";
|
||||
static const char* WIFI_PASSWORD_KEY = "wifi_password";
|
||||
|
||||
WifiHandler::WifiHandler(
|
||||
// this handler is used to store/retrieve WiFi credentials
|
||||
// should have a unique namespace for WiFi credentials
|
||||
// it will be owned by WifiHandler and deleted in its destructor
|
||||
KVStorageHandler* kvs
|
||||
) : kvs(kvs) {
|
||||
this->s_wifi_event_group = xEventGroupCreate();
|
||||
this->scan_mutex = xSemaphoreCreateMutex();
|
||||
this->connection_mutex = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
// Move constructor: transfer ownership of resources
|
||||
WifiHandler::WifiHandler(WifiHandler&& other) noexcept
|
||||
: initialized(other.initialized),
|
||||
kvs(other.kvs),
|
||||
s_wifi_event_group(other.s_wifi_event_group),
|
||||
scan_mutex(other.scan_mutex),
|
||||
connection_mutex(other.connection_mutex),
|
||||
current_ssid(other.current_ssid),
|
||||
expect_disconnected(other.expect_disconnected) {
|
||||
other.kvs = nullptr;
|
||||
other.initialized = false;
|
||||
other.s_wifi_event_group = 0;
|
||||
other.scan_mutex = nullptr;
|
||||
other.connection_mutex = nullptr;
|
||||
other.current_ssid = nullptr;
|
||||
other.expect_disconnected = false;
|
||||
}
|
||||
|
||||
WifiHandler::~WifiHandler() {
|
||||
if (this->initialized) {
|
||||
esp_wifi_stop();
|
||||
// Check if it should be called
|
||||
esp_wifi_deinit();
|
||||
vEventGroupDelete(this->s_wifi_event_group);
|
||||
if (this->current_ssid) {
|
||||
delete[] this->current_ssid;
|
||||
}
|
||||
vSemaphoreDelete(this->scan_mutex);
|
||||
vSemaphoreDelete(this->connection_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void WifiHandler::init() {
|
||||
if (this->initialized) {
|
||||
ESP_LOGW(TAG, "Already initialized, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// get WiFi credentials from KV storage if available
|
||||
char* ssid = nullptr;
|
||||
char* password = nullptr;
|
||||
this->get_wifi_credentials(ssid, password);
|
||||
|
||||
if (ssid && password) {
|
||||
ESP_LOGI(TAG, "Found stored WiFi credentials, connecting to SSID: %s", ssid);
|
||||
esp_err_t err = this->connect(ssid, password);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to connect to stored WiFi credentials: %s", esp_err_to_name(err));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "No stored WiFi credentials found, not connecting");
|
||||
}
|
||||
|
||||
delete[] ssid;
|
||||
delete[] password;
|
||||
// TODO: setup WiFi event handlers
|
||||
// TODO: add auto-reconnect logic
|
||||
//
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::connect(const char* ssid, const char* password) {
|
||||
SemaphoreGuard guard(this->connection_mutex);
|
||||
// wait up to 5 seconds to take the mutex
|
||||
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGE(TAG, "Failed to take connection mutex");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
expect_disconnected = false;
|
||||
if (this->current_ssid) {
|
||||
delete[] this->current_ssid;
|
||||
}
|
||||
size_t ssid_len = strlen(ssid);
|
||||
this->current_ssid = new char[ssid_len + 1];
|
||||
strncpy(this->current_ssid, ssid, ssid_len + 1);
|
||||
this->current_ssid[ssid_len] = '\0';
|
||||
|
||||
//
|
||||
wifi_config_t wifi_config = {};
|
||||
strncpy((char*)wifi_config.sta.ssid, this->current_ssid, sizeof(wifi_config.sta.ssid));
|
||||
wifi_config.sta.ssid[sizeof(wifi_config.sta.ssid) - 1] = '\0';
|
||||
strncpy((char*)wifi_config.sta.password, password, sizeof(wifi_config.sta.password));
|
||||
wifi_config.sta.password[sizeof(wifi_config.sta.password) - 1] = '\0';
|
||||
// set auth mode to WPA2_PSK minimum
|
||||
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to SSID: %s", this->current_ssid);
|
||||
esp_err_t err = esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &wifi_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set WiFi config: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = esp_wifi_connect();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initiate WiFi connection: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// store credentials
|
||||
this->kvs->put(WIFI_SSID_KEY, this->current_ssid);
|
||||
// store password under key derived from SSID
|
||||
char* password_key = this->build_password_key(this->current_ssid);
|
||||
this->kvs->put(password_key, password);
|
||||
delete[] password_key;
|
||||
|
||||
// set connected bit on successful connection
|
||||
xEventGroupSetBits(
|
||||
this->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::connect(const char* ssid) {
|
||||
char* stored_ssid = nullptr;
|
||||
char* stored_password = nullptr;
|
||||
this->get_wifi_credentials(stored_ssid, stored_password);
|
||||
if (!stored_ssid || strcmp(stored_ssid, ssid) != 0) {
|
||||
ESP_LOGE(TAG, "No stored credentials for SSID: %s", ssid);
|
||||
delete[] stored_ssid;
|
||||
delete[] stored_password;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
esp_err_t err = this->connect(stored_ssid, stored_password ? stored_password : "");
|
||||
delete[] stored_ssid;
|
||||
delete[] stored_password;
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::reconnect() {
|
||||
if (!this->current_ssid) {
|
||||
ESP_LOGE(TAG, "No current SSID set, cannot reconnect");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return this->connect(this->current_ssid);
|
||||
}
|
||||
|
||||
void WifiHandler::disconnect() {
|
||||
SemaphoreGuard guard(this->connection_mutex);
|
||||
// wait up to 5 seconds to take the mutex
|
||||
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGE(TAG, "Failed to take connection mutex");
|
||||
return;
|
||||
}
|
||||
|
||||
expect_disconnected = true;
|
||||
esp_wifi_disconnect();
|
||||
xEventGroupClearBits(
|
||||
this->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::scan_networks(
|
||||
wifi_ap_record_t*& ap_records,
|
||||
uint16_t& ap_count
|
||||
) {
|
||||
SemaphoreGuard guard(this->scan_mutex);
|
||||
// wait up to 5 seconds to take the mutex
|
||||
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGE(TAG, "Failed to take scan mutex");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ap_records = nullptr;
|
||||
ap_count = 0;
|
||||
// start scan
|
||||
|
||||
esp_err_t err = esp_wifi_scan_start(nullptr, true); // block until done
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// get number of APs found
|
||||
uint16_t ap_count_local = 0;
|
||||
err = esp_wifi_scan_get_ap_num(&ap_count_local);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get number of APs found: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
wifi_ap_record_t* ap_records_local = new wifi_ap_record_t[ap_count_local];
|
||||
err = esp_wifi_scan_get_ap_records(&ap_count_local, ap_records_local);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get AP records: %s", esp_err_to_name(err));
|
||||
delete[] ap_records_local;
|
||||
return err;
|
||||
}
|
||||
|
||||
ap_records = ap_records_local;
|
||||
ap_count = ap_count_local;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void WifiHandler::wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
||||
WifiHandler* self = static_cast<WifiHandler*>(arg);
|
||||
if (self == nullptr) {
|
||||
ESP_LOGE(TAG, "wifi_event_handler received null WifiHandler pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event_id) {
|
||||
case WIFI_EVENT_STA_START:
|
||||
// When the station starts, attempt to connect
|
||||
ESP_LOGI(TAG, "WIFI_EVENT_STA_START");
|
||||
if (!self->expect_disconnected && self->current_ssid) {
|
||||
ESP_LOGI(TAG, "Station started, attempting to connect to SSID: %s", self->current_ssid);
|
||||
self->reconnect();
|
||||
}
|
||||
break;
|
||||
case WIFI_EVENT_STA_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED");
|
||||
if (!self->expect_disconnected) {
|
||||
ESP_LOGI(TAG, "Unexpected disconnection, attempting to reconnect");
|
||||
self->reconnect();
|
||||
}
|
||||
xEventGroupClearBits(
|
||||
self->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
break;
|
||||
case IP_EVENT_STA_GOT_IP:
|
||||
{
|
||||
ip_event_got_ip_t* event = static_cast<ip_event_got_ip_t*>(event_data);
|
||||
ESP_LOGI(TAG, "WIFI_EVENT_STA_GOT_IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
xEventGroupSetBits(
|
||||
self->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unhandled WiFi event: %d", event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// private methods
|
||||
//
|
||||
|
||||
char* WifiHandler::build_password_key(const char* ssid) {
|
||||
// `{WIFI_PASSWORD_KEY}_{ssid}`
|
||||
size_t password_key_len = strlen(WIFI_PASSWORD_KEY) + 1 + strlen(ssid) + 1;
|
||||
char* password_key_buff = new char[password_key_len];
|
||||
snprintf(password_key_buff, password_key_len, "%s_%s", WIFI_PASSWORD_KEY, ssid);
|
||||
return password_key_buff;
|
||||
}
|
||||
|
||||
void WifiHandler::get_wifi_credentials(char*& ssid, char*& password) {
|
||||
if (!kvs) {
|
||||
ESP_LOGW(TAG, "KVStorageHandler not set, cannot get WiFi credentials");
|
||||
return;
|
||||
}
|
||||
ssid = kvs->get(WIFI_SSID_KEY).get();
|
||||
if (!ssid) {
|
||||
ssid = nullptr;
|
||||
password = nullptr;
|
||||
return;
|
||||
}
|
||||
// password is from KV storage, may be nullptr
|
||||
char* password_key = this->build_password_key(ssid);
|
||||
password = kvs->get(password_key).get();
|
||||
delete[] password_key;
|
||||
}
|
||||
|
||||
EventBits_t WifiHandler::wait_for_connection(TickType_t ticks_to_wait) {
|
||||
return xEventGroupWaitBits(
|
||||
s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT,
|
||||
pdFALSE,
|
||||
pdTRUE,
|
||||
ticks_to_wait
|
||||
);
|
||||
}
|
||||
54
main/network/wifi_handler.h
Normal file
54
main/network/wifi_handler.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "io/io.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#define WIFI_CONNECTED_BIT (1 << 0)
|
||||
|
||||
class WifiHandler {
|
||||
public:
|
||||
WifiHandler(
|
||||
// this handler is used to store/retrieve WiFi credentials
|
||||
// should have a unique namespace for WiFi credentials
|
||||
// it will be owned by WifiHandler and deleted in its destructor
|
||||
KVStorageHandler* kvs
|
||||
);
|
||||
~WifiHandler();
|
||||
|
||||
// move semantics
|
||||
WifiHandler(WifiHandler&& other) noexcept;
|
||||
|
||||
void init();
|
||||
esp_err_t connect(const char* ssid, const char* password);
|
||||
esp_err_t connect(const char* ssid); // connect using stored password
|
||||
esp_err_t reconnect(); // reconnect to current SSID
|
||||
void disconnect();
|
||||
EventBits_t wait_for_connection(TickType_t ticks_to_wait);
|
||||
// returns list of available networks, caller is responsible for freeing the returned memory
|
||||
// returns nullptr if scan failed
|
||||
esp_err_t scan_networks(
|
||||
wifi_ap_record_t*& ap_records,
|
||||
uint16_t& ap_count
|
||||
);
|
||||
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
|
||||
private:
|
||||
// prevent copying
|
||||
WifiHandler(const WifiHandler&) = delete;
|
||||
WifiHandler& operator=(const WifiHandler&) = delete;
|
||||
|
||||
char* build_password_key(const char* ssid);
|
||||
void get_wifi_credentials(char*& ssid, char*& password);
|
||||
|
||||
bool initialized = false;
|
||||
KVStorageHandler* kvs = nullptr;
|
||||
EventGroupHandle_t s_wifi_event_group = 0;
|
||||
SemaphoreHandle_t scan_mutex = nullptr;
|
||||
SemaphoreHandle_t connection_mutex = nullptr;
|
||||
// current connected / preferred SSID
|
||||
char* current_ssid = nullptr;
|
||||
// prevent auto-reconnect on expected disconnection, e.g. when user calls disconnect()
|
||||
// should be reset to false after connect()
|
||||
bool expect_disconnected = false;
|
||||
};
|
||||
Reference in New Issue
Block a user