Firmware: generic configuration.

This commit is contained in:
erki 2024-01-16 16:39:30 +02:00
parent 40f8bb884d
commit 4929fe4caf
6 changed files with 265 additions and 39 deletions

View File

@ -1,6 +1,6 @@
idf_component_register( idf_component_register(
SRCS "main.cpp" "wifi_provisioner.cpp" SRCS "main.cpp" "wifi_provisioner.cpp"
INCLUDE_DIRS "" 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" EMBED_FILES "${CMAKE_CURRENT_LIST_DIR}/static/index.html"
) )

View File

@ -0,0 +1,23 @@
//
// Created by erki on 16/01/24.
//
#pragma once
#include <expected>
#include <esp_err.h>
#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; \
})

View File

@ -25,11 +25,22 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK(err); ESP_ERROR_CHECK(err);
WifiProvisioner provisioner; auto* provisioner = new WifiProvisioner("wifi_settings");
if (!provisioner->addParameter("SSID", "ssid", WifiProvisioner::Parameter::Type::STRING).has_value())
if (!provisioner.wifiIsConfigured())
{ {
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()) if (!result.has_value())
printf("Error: %s\n", esp_err_to_name(result.error())); printf("Error: %s\n", esp_err_to_name(result.error()));
else else

View File

@ -12,13 +12,49 @@
<h1>ESP Captive Portal</h1> <h1>ESP Captive Portal</h1>
<p>Hello World, this is ESP32!</p> <p>Hello World, this is ESP32!</p>
<form action="/configure" method="post"> <form action="/configure" method="post" id="submit_form">
<label for="ssid">WiFi SSID:</label><br>
<input type="text" id="ssid" name="ssid"><br>
<label for="password">WiFi Password:</label><br>
<input type="password" id="password" name="password"><br>
<input type="submit" value="Submit">
</form> </form>
<script>
async function getParameterFields() {
const response = await fetch("parameters", {
method: "GET",
})
return response.json();
}
async function populateSubmitForm(form_root) {
const data = await getParameterFields();
for (let param of data) {
let input = document.createElement("input");
input.setAttribute("id", param.nvs_name);
input.setAttribute("name", param.nvs_name);
if (param.type === 0 || param.type === 1) {
input.setAttribute("type", "number");
} else {
input.setAttribute("type", "text");
}
let label = document.createElement("label");
label.setAttribute("for", param.nvs_name);
label.innerHTML = param.name + ":";
form_root.appendChild(label);
form_root.appendChild(document.createElement("br"));
form_root.appendChild(input);
form_root.appendChild(document.createElement("br"));
}
let submit = document.createElement("input");
submit.setAttribute("type", "submit");
submit.setAttribute("value", "Configure");
form_root.appendChild(submit);
}
populateSubmitForm(document.getElementById("submit_form"));
</script>
</body> </body>
</html> </html>

View File

@ -10,21 +10,9 @@
#include "esp_log.h" #include "esp_log.h"
#include <etl/string.h> #include <etl/string.h>
#include <cJSON.h>
#define TRY(x) ({ \ #include "esp_expected.hpp"
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; \
})
#define EXAMPLE_ESP_WIFI_SSID "ESP_TEST" #define EXAMPLE_ESP_WIFI_SSID "ESP_TEST"
#define EXAMPLE_ESP_WIFI_PASS "1234567891" #define EXAMPLE_ESP_WIFI_PASS "1234567891"
@ -37,10 +25,8 @@ extern const char index_end[] asm("_binary_index_html_end");
namespace 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() ssize_t indexHtmlLength()
{ {
@ -102,16 +88,65 @@ esp_err_t rootPostHandler_(httpd_req_t* req)
return ESP_OK; return ESP_OK;
} }
esp_err_t parametersGetHandler_(httpd_req_t* req)
{
auto* provisioner = static_cast<WifiProvisioner*>(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;
} }
WifiProvisioner::WifiProvisioner() httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, stringified.get(), HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
}
std::expected<WifiProvisioner::Parameter::Value, esp_err_t> 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<float>(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; 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); 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) 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<void, esp_err_t> WifiProvisioner::startProvisioning() std::expected<void, esp_err_t> WifiProvisioner::startProvisioning()
{ {
TRY(initializeWifiAp_()); TRY(initializeWifiAp_());
@ -128,9 +180,37 @@ std::expected<void, esp_err_t> WifiProvisioner::startProvisioning()
return {}; return {};
} }
std::expected<void, std::error_code> 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 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) if (err != ESP_OK)
return err; return err;
@ -207,6 +287,13 @@ std::expected<httpd_handle_t, esp_err_t> WifiProvisioner::initializeCaptivePorta
.user_ctx = this .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); if (const auto err = httpd_register_uri_handler(server, &uri_main);
err != ESP_OK) err != ESP_OK)
{ {
@ -221,5 +308,12 @@ std::expected<httpd_handle_t, esp_err_t> WifiProvisioner::initializeCaptivePorta
return std::unexpected(err); 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; return server;
} }

View File

@ -6,24 +6,86 @@
#include "nvs_handle.hpp" #include "nvs_handle.hpp"
#include <esp_http_server.h> #include <esp_http_server.h>
#include <etl/vector.h>
#include <etl/string.h>
#include <expected> #include <expected>
#include <variant>
#include <memory>
#include <cJSON.h>
struct CJsonStringDeleter
{
void operator()(char* json_str) const
{
if (json_str)
cJSON_free(json_str);
}
};
using CJsonStrPtr = std::unique_ptr<char, CJsonStringDeleter>;
struct CJsonDeleter
{
void operator()(cJSON* element) const
{
if (element)
cJSON_Delete(element);
}
};
using CJsonPtr = std::unique_ptr<cJSON, CJsonDeleter>;
class WifiProvisioner class WifiProvisioner
{ {
public: public:
WifiProvisioner(); struct Parameter
bool wifiIsConfigured() const
{ {
return wifi_initialized_; etl::string<100> name;
} etl::string<15> nvs_name;
enum class Type
{
INT,
FLOAT,
STRING
};
Type type;
using Value = std::variant<std::int32_t, float, etl::string<100>>;
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<Value, esp_err_t> 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<void, esp_err_t> startProvisioning(); std::expected<void, esp_err_t> startProvisioning();
std::expected<void, std::error_code> addParameter(const char* name, const char* nvs_name, const Parameter::Type type);
CJsonPtr getParameterObjects() const;
private: private:
std::unique_ptr<nvs::NVSHandle> file_handle_; std::unique_ptr<nvs::NVSHandle> file_handle_;
bool wifi_initialized_ = false; bool settings_initialized_ = false;
httpd_handle_t http_server_ = nullptr; httpd_handle_t http_server_ = nullptr;
etl::vector<Parameter, 10> params_;
esp_err_t initializeNvsNamespace_(); esp_err_t initializeNvsNamespace_();
std::expected<void, esp_err_t> initializeWifiAp_(); std::expected<void, esp_err_t> initializeWifiAp_();