Firmware: generic configuration.
This commit is contained in:
parent
40f8bb884d
commit
4929fe4caf
@ -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"
|
||||
)
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
|
||||
@ -12,13 +12,49 @@
|
||||
<h1>ESP Captive Portal</h1>
|
||||
<p>Hello World, this is ESP32!</p>
|
||||
|
||||
<form action="/configure" method="post">
|
||||
<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 action="/configure" method="post" id="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>
|
||||
</html>
|
||||
|
||||
@ -10,21 +10,9 @@
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <etl/string.h>
|
||||
#include <cJSON.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; \
|
||||
})
|
||||
#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<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;
|
||||
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<void, esp_err_t> WifiProvisioner::startProvisioning()
|
||||
{
|
||||
TRY(initializeWifiAp_());
|
||||
@ -128,9 +180,37 @@ std::expected<void, esp_err_t> WifiProvisioner::startProvisioning()
|
||||
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 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<httpd_handle_t, esp_err_t> 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<httpd_handle_t, esp_err_t> 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;
|
||||
}
|
||||
|
||||
@ -6,24 +6,86 @@
|
||||
|
||||
#include "nvs_handle.hpp"
|
||||
#include <esp_http_server.h>
|
||||
#include <etl/vector.h>
|
||||
#include <etl/string.h>
|
||||
#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
|
||||
{
|
||||
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<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, std::error_code> addParameter(const char* name, const char* nvs_name, const Parameter::Type type);
|
||||
CJsonPtr getParameterObjects() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<nvs::NVSHandle> file_handle_;
|
||||
bool wifi_initialized_ = false;
|
||||
bool settings_initialized_ = false;
|
||||
httpd_handle_t http_server_ = nullptr;
|
||||
etl::vector<Parameter, 10> params_;
|
||||
|
||||
esp_err_t initializeNvsNamespace_();
|
||||
std::expected<void, esp_err_t> initializeWifiAp_();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user