Firmware: generic configuration.
This commit is contained in:
parent
40f8bb884d
commit
4929fe4caf
@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
23
firmware/main/esp_expected.hpp
Normal file
23
firmware/main/esp_expected.hpp
Normal 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; \
|
||||||
|
})
|
||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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* TAG = "wifi softAP";
|
||||||
const char* NVS_WIFI_IS_INITIALIZED = "is_inited";
|
const char* NVS_IS_INITED = "_is_inited";
|
||||||
|
|
||||||
const char *TAG = "wifi softAP";
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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_();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user