diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index 38db32b..018080e 100644 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( SRCS "main.cpp" "wifi_provisioner.cpp" INCLUDE_DIRS "" - REQUIRES nvs_flash esp_wifi esp_http_server etlcpp + REQUIRES nvs_flash esp_wifi esp_http_server etlcpp json EMBED_FILES "${CMAKE_CURRENT_LIST_DIR}/static/index.html" ) diff --git a/firmware/main/esp_expected.hpp b/firmware/main/esp_expected.hpp new file mode 100644 index 0000000..af9406a --- /dev/null +++ b/firmware/main/esp_expected.hpp @@ -0,0 +1,23 @@ +// +// Created by erki on 16/01/24. +// + +#pragma once + +#include +#include + +#define TRY(x) ({ \ + const auto& _x = (x); \ + if (!_x) { \ + return std::unexpected(_x.error()); \ + } \ + _x.value(); \ +}) + +#define TRY_ESP(x) ({ \ + const esp_err_t& _x = (x); \ + if (_x != ESP_OK) \ + return std::unexpected(_x); \ + _x; \ +}) diff --git a/firmware/main/main.cpp b/firmware/main/main.cpp index 8eaa3e0..6626ab7 100644 --- a/firmware/main/main.cpp +++ b/firmware/main/main.cpp @@ -25,11 +25,22 @@ extern "C" void app_main(void) ESP_ERROR_CHECK(err); - WifiProvisioner provisioner; - - if (!provisioner.wifiIsConfigured()) + auto* provisioner = new WifiProvisioner("wifi_settings"); + if (!provisioner->addParameter("SSID", "ssid", WifiProvisioner::Parameter::Type::STRING).has_value()) { - auto result = provisioner.startProvisioning(); + printf("Error adding parameter."); + return; + } + + if (!provisioner->addParameter("Password", "password", WifiProvisioner::Parameter::Type::STRING).has_value()) + { + printf("Error adding parameter."); + return; + } + + if (!provisioner->parametersAreConfigured()) + { + auto result = provisioner->startProvisioning(); if (!result.has_value()) printf("Error: %s\n", esp_err_to_name(result.error())); else diff --git a/firmware/main/static/index.html b/firmware/main/static/index.html index 7a1f4b1..7f69e45 100644 --- a/firmware/main/static/index.html +++ b/firmware/main/static/index.html @@ -12,13 +12,49 @@

ESP Captive Portal

Hello World, this is ESP32!

-
-
-
-
-
- + +
+ diff --git a/firmware/main/wifi_provisioner.cpp b/firmware/main/wifi_provisioner.cpp index aba92d5..e5517ec 100644 --- a/firmware/main/wifi_provisioner.cpp +++ b/firmware/main/wifi_provisioner.cpp @@ -10,21 +10,9 @@ #include "esp_log.h" #include +#include -#define TRY(x) ({ \ - const auto& _x = (x); \ - if (!_x) { \ - return std::unexpected(_x.error()); \ - } \ - _x.value(); \ -}) - -#define TRY_ESP(x) ({ \ - const esp_err_t& _x = (x); \ - if (_x != ESP_OK) \ - return std::unexpected(_x); \ - _x; \ -}) +#include "esp_expected.hpp" #define EXAMPLE_ESP_WIFI_SSID "ESP_TEST" #define EXAMPLE_ESP_WIFI_PASS "1234567891" @@ -37,10 +25,8 @@ extern const char index_end[] asm("_binary_index_html_end"); namespace { -const char* NVS_WIFI_NAMESPACE = "wifi_prov"; -const char* NVS_WIFI_IS_INITIALIZED = "is_inited"; - -const char *TAG = "wifi softAP"; +const char* TAG = "wifi softAP"; +const char* NVS_IS_INITED = "_is_inited"; ssize_t indexHtmlLength() { @@ -102,16 +88,65 @@ esp_err_t rootPostHandler_(httpd_req_t* req) return ESP_OK; } +esp_err_t parametersGetHandler_(httpd_req_t* req) +{ + auto* provisioner = static_cast(req->user_ctx); + CJsonPtr data = provisioner->getParameterObjects(); + cJSON* raw_data = data.get(); + + auto stringified = CJsonStrPtr(cJSON_Print(raw_data)); + + if (!stringified) + { + httpd_resp_send_500(req); + return ESP_FAIL; + } + + httpd_resp_set_type(req, "application/json"); + httpd_resp_send(req, stringified.get(), HTTPD_RESP_USE_STRLEN); + + return ESP_OK; } -WifiProvisioner::WifiProvisioner() +} + +std::expected WifiProvisioner::Parameter::getValue(nvs::NVSHandle* file_handle) const +{ + if (type == Parameter::Type::INT) + { + std::int32_t value = 0; + TRY_ESP(file_handle->get_item(nvs_name.c_str(), value)); + + return value; + } + else if (type == Parameter::Type::FLOAT) + { + std::int32_t value_raw = 0; + TRY_ESP(file_handle->get_item(nvs_name.c_str(), value_raw)); + return std::bit_cast(value_raw); + } + else if (type == Parameter::Type::STRING) + { + etl::string<100> value; + value.initialize_free_space(); + + TRY_ESP(file_handle->get_string(nvs_name.c_str(), value.data(), value.size() - 1)); + value.trim_to_terminator(); + + return value; + } + + return ESP_FAIL; +} + +WifiProvisioner::WifiProvisioner(const char* settings_namespace) { esp_err_t err = ESP_OK; - file_handle_ = nvs::open_nvs_handle(NVS_WIFI_NAMESPACE, NVS_READWRITE, &err); + file_handle_ = nvs::open_nvs_handle(settings_namespace, NVS_READWRITE, &err); ESP_ERROR_CHECK(err); - err = file_handle_->get_item(NVS_WIFI_IS_INITIALIZED, wifi_initialized_); + err = file_handle_->get_item(NVS_IS_INITED, settings_initialized_); if (err != ESP_OK) { @@ -119,6 +154,23 @@ WifiProvisioner::WifiProvisioner() } } +bool WifiProvisioner::parametersAreConfigured() +{ + if (params_.empty()) + return false; + + for (const auto& param : params_) + { + if (const auto value = param.getValue(file_handle_.get()); + !value.has_value()) + { + return false; + } + } + + return true; +} + std::expected WifiProvisioner::startProvisioning() { TRY(initializeWifiAp_()); @@ -128,9 +180,37 @@ std::expected WifiProvisioner::startProvisioning() return {}; } +std::expected WifiProvisioner::addParameter(const char* name, const char* nvs_name, const Parameter::Type type) +{ + if (params_.full()) + return std::unexpected(std::make_error_code(std::errc::not_enough_memory)); + + auto new_param = Parameter(name, nvs_name, type); + params_.push_back(new_param); + + return {}; +} + +CJsonPtr WifiProvisioner::getParameterObjects() const +{ + auto root = CJsonPtr(cJSON_CreateArray()); + + for (const Parameter& param : params_) + { + cJSON* element = cJSON_CreateObject(); + cJSON_AddStringToObject(element, "name", param.name.c_str()); + cJSON_AddStringToObject(element, "nvs_name", param.nvs_name.c_str()); + cJSON_AddNumberToObject(element, "type", int(param.type)); + + cJSON_AddItemToArray(root.get(), element); + } + + return root; +} + esp_err_t WifiProvisioner::initializeNvsNamespace_() { - esp_err_t err = file_handle_->set_item(NVS_WIFI_IS_INITIALIZED, false); + esp_err_t err = file_handle_->set_item(NVS_IS_INITED, false); if (err != ESP_OK) return err; @@ -207,6 +287,13 @@ std::expected WifiProvisioner::initializeCaptivePorta .user_ctx = this }; + static httpd_uri_t uri_get_params = { + .uri = "/parameters", + .method = HTTP_GET, + .handler = parametersGetHandler_, + .user_ctx = this + }; + if (const auto err = httpd_register_uri_handler(server, &uri_main); err != ESP_OK) { @@ -221,5 +308,12 @@ std::expected WifiProvisioner::initializeCaptivePorta return std::unexpected(err); } + if (const auto err = httpd_register_uri_handler(server, &uri_get_params); + err != ESP_OK) + { + httpd_stop(server); + return std::unexpected(err); + } + return server; } diff --git a/firmware/main/wifi_provisioner.hpp b/firmware/main/wifi_provisioner.hpp index a12c341..c2228e7 100644 --- a/firmware/main/wifi_provisioner.hpp +++ b/firmware/main/wifi_provisioner.hpp @@ -6,24 +6,86 @@ #include "nvs_handle.hpp" #include +#include +#include #include +#include +#include + +#include + +struct CJsonStringDeleter +{ + void operator()(char* json_str) const + { + if (json_str) + cJSON_free(json_str); + } +}; + +using CJsonStrPtr = std::unique_ptr; + +struct CJsonDeleter +{ + void operator()(cJSON* element) const + { + if (element) + cJSON_Delete(element); + } +}; + +using CJsonPtr = std::unique_ptr; + class WifiProvisioner { public: - WifiProvisioner(); - - bool wifiIsConfigured() const + struct Parameter { - return wifi_initialized_; - } + etl::string<100> name; + etl::string<15> nvs_name; + enum class Type + { + INT, + FLOAT, + STRING + }; + + Type type; + + using Value = std::variant>; + Value data; + + Parameter() = default; + Parameter(const char* name, const char* nvs_name, const Parameter::Type type) + : name(name) + , nvs_name(nvs_name) + , type(type) + { } + + std::expected getValue(nvs::NVSHandle* file_handle) const; + }; + + explicit WifiProvisioner(const char* settings_namespace); + + WifiProvisioner() = delete; + WifiProvisioner(const WifiProvisioner&) = delete; + WifiProvisioner(WifiProvisioner&&) = delete; + WifiProvisioner& operator=(const WifiProvisioner&) = delete; + WifiProvisioner& operator=(WifiProvisioner&&) = delete; + + bool parametersAreConfigured(); std::expected startProvisioning(); + std::expected addParameter(const char* name, const char* nvs_name, const Parameter::Type type); + CJsonPtr getParameterObjects() const; + private: std::unique_ptr file_handle_; - bool wifi_initialized_ = false; + bool settings_initialized_ = false; httpd_handle_t http_server_ = nullptr; + etl::vector params_; esp_err_t initializeNvsNamespace_(); std::expected initializeWifiAp_();