Implemented webserver settings and stringSettings

This commit is contained in:
2021-09-18 20:15:06 +02:00
parent 5844af246e
commit 161abf5ddd
8 changed files with 493 additions and 42 deletions

View File

@ -150,6 +150,7 @@ set(headers
webserver_displaycontrol.h
webserver_ota.h
webserver_settings.h
webserver_stringsettings.h
wifitexthelpers.h
wifi_bobbycar.h
)

View File

@ -93,7 +93,7 @@ void UpdateDisplay::redraw()
m_statusLabel.redraw(toString(asyncOta->status()));
const auto progress = asyncOta->progress();
m_progressLabel.redraw(std::to_string(progress));
if (const auto totalSize = asyncOta->totalSize())
if (const auto totalSize = asyncOta->totalSize(); totalSize && *totalSize > 0)
{
m_totalLabel.redraw(std::to_string(*totalSize));
m_progressBar.redraw(float(progress) / *totalSize * 100);

View File

@ -303,7 +303,7 @@ bool SettingsPersister::load(T &settings)
{
settings.executeForEveryCommonSetting([&](const char *key, auto &value)
{
if (esp_err_t result = nvsGetterHelper<std::remove_reference_t<decltype(value)>>::nvs_get(m_handle, key, &value); result != ESP_OK)
if (esp_err_t result = nvsGetterHelper<std::decay_t<decltype(value)>>::nvs_get(m_handle, key, &value); result != ESP_OK)
{
if (result != ESP_ERR_NVS_NOT_FOUND)
ESP_LOGE("BOBBY", "nvs_get() COMMON %s failed with %s", key, esp_err_to_name(result));
@ -321,7 +321,7 @@ bool SettingsPersister::load(T &settings)
{
settings.executeForEveryProfileSetting([&](const char *key, auto &value)
{
if (esp_err_t result = nvsGetterHelper<std::remove_reference_t<decltype(value)>>::nvs_get(m_profile->handle, key, &value); result != ESP_OK)
if (esp_err_t result = nvsGetterHelper<std::decay_t<decltype(value)>>::nvs_get(m_profile->handle, key, &value); result != ESP_OK)
{
if (result != ESP_ERR_NVS_NOT_FOUND)
ESP_LOGE("BOBBY", "nvs_get() PROFILE %s failed with %s", key, esp_err_to_name(result));
@ -418,9 +418,9 @@ bool SettingsPersister::save(T &settings)
if (m_handle)
{
settings.executeForEveryCommonSetting([&](const char *key, auto value)
settings.executeForEveryCommonSetting([&](const char *key, const auto &value)
{
if (esp_err_t result = nvsSetterHelper<decltype(value)>::nvs_set(m_handle, key, value); result != ESP_OK)
if (esp_err_t result = nvsSetterHelper<std::decay_t<decltype(value)>>::nvs_set(m_handle, key, value); result != ESP_OK)
{
ESP_LOGE("BOBBY", "nvs_set() COMMON %s failed with %s", key, esp_err_to_name(result));
result = false;
@ -435,9 +435,9 @@ bool SettingsPersister::save(T &settings)
if (m_profile)
{
settings.executeForEveryProfileSetting([&](const char *key, auto value)
settings.executeForEveryProfileSetting([&](const char *key, const auto &value)
{
if (esp_err_t result = nvsSetterHelper<decltype(value)>::nvs_set(m_profile->handle, key, value); result != ESP_OK)
if (esp_err_t result = nvsSetterHelper<std::decay_t<decltype(value)>>::nvs_set(m_profile->handle, key, value); result != ESP_OK)
{
ESP_LOGE("BOBBY", "nvs_set() PROFILE %s failed with %s", key, esp_err_to_name(result));
result = false;

View File

@ -20,6 +20,7 @@
#include "webserver_ota.h"
#endif
#include "webserver_settings.h"
#include "webserver_stringsettings.h"
#ifdef FEATURE_WEBSERVER
namespace {
@ -49,17 +50,19 @@ void initWebserver()
}
for (const httpd_uri_t &uri : {
httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/triggerButton", .method = HTTP_GET, .handler = webserver_triggerButton_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/triggerItem", .method = HTTP_GET, .handler = webserver_triggerItem_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/setValue", .method = HTTP_GET, .handler = webserver_setValue_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/triggerButton", .method = HTTP_GET, .handler = webserver_triggerButton_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/triggerItem", .method = HTTP_GET, .handler = webserver_triggerItem_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/setValue", .method = HTTP_GET, .handler = webserver_setValue_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL },
#ifdef FEATURE_OTA
httpd_uri_t { .uri = "/ota", .method = HTTP_GET, .handler = webserver_ota_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/triggerOta", .method = HTTP_GET, .handler = webserver_trigger_ota_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/ota", .method = HTTP_GET, .handler = webserver_ota_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/triggerOta", .method = HTTP_GET, .handler = webserver_trigger_ota_handler, .user_ctx = NULL },
#endif
httpd_uri_t { .uri = "/settings", .method = HTTP_GET, .handler = webserver_settings_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/saveSettings", .method = HTTP_GET, .handler = webserver_save_settings_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/settings", .method = HTTP_GET, .handler = webserver_settings_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/saveSettings", .method = HTTP_GET, .handler = webserver_saveSettings_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/stringSettings", .method = HTTP_GET, .handler = webserver_stringSettings_handler, .user_ctx = NULL },
httpd_uri_t { .uri = "/saveStringSettings", .method = HTTP_GET, .handler = webserver_saveStringSettings_handler, .user_ctx = NULL },
})
{
const auto result = httpd_register_uri_handler(httpdHandle, &uri);

View File

@ -59,12 +59,13 @@ esp_err_t webserver_root_handler(httpd_req_t *req)
{
HtmlTag pTag{"p", body};
body += "<b>Display control</b> "
body += "<b>Display control</b> - "
#ifdef FEATURE_OTA
"<a href=\"/ota\">Update</a> "
"<a href=\"/ota\">Update</a> - "
#endif
"<a href=\"/settings\">Settings</a>";
"<a href=\"/settings\">Settings</a> - "
"<a href=\"/stringSettings\">String Settings</a>";
}
{

View File

@ -54,9 +54,133 @@ esp_err_t webserver_ota_handler(httpd_req_t *req)
{
HtmlTag pTag{"p", body};
body += "<a href=\"/\">Display control</a> "
"<b>Update</b> "
"<a href=\"/settings\">Settings</a>";
body += "<a href=\"/\">Display control</a> - "
"<b>Update</b> - "
"<a href=\"/settings\">Settings</a> - "
"<a href=\"/stringSettings\">String Settings</a>";
}
if (const esp_app_desc_t *app_desc = esp_ota_get_app_description())
{
HtmlTag tableTag{"table", "border=\"1\"", body};
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Current project_name"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->project_name); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Current version"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->version); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Current secure_version"; }
{ HtmlTag tdTag{"td", body}; body += std::to_string(app_desc->secure_version); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Current timestamp"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", app_desc->date, app_desc->time)); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Current idf_ver"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(app_desc->idf_ver); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Current sha256"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({app_desc->app_elf_sha256, 8})); }
}
}
else
{
constexpr const std::string_view msg = "esp_ota_get_app_description() failed";
ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
HtmlTag pTag{"p", "style=\"color: red;\"", body};
body += esphttpdutils::htmlentities(msg);
}
if (asyncOta)
{
HtmlTag tableTag{"table", "border=\"1\"", body};
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Update status"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(toString(asyncOta->status())); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Update progress"; }
{
HtmlTag tdTag{"td", body};
const auto progress = asyncOta->progress();
const auto totalSize = asyncOta->totalSize();
body += fmt::format("{} / {}{}",
progress,
totalSize ? std::to_string(*totalSize) : "?",
(totalSize && *totalSize > 0) ? fmt::format(" ({:.02f}%)", float(progress) / *totalSize * 100) : "");
}
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "Update message"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(asyncOta->message()); }
}
if (const auto &appDesc = asyncOta->appDesc())
{
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "New project_name"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->project_name); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "New version"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->version); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "New secure_version"; }
{ HtmlTag tdTag{"td", body}; body += std::to_string(appDesc->secure_version); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "New timestamp"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(fmt::format("{} {}", appDesc->date, appDesc->time)); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "New idf_ver"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(appDesc->idf_ver); }
}
{
HtmlTag trTag{"tr", body};
{ HtmlTag tdTag{"td", body}; body += "New sha256"; }
{ HtmlTag tdTag{"td", body}; body += esphttpdutils::htmlentities(espcpputils::toHexString({appDesc->app_elf_sha256, 8})); }
}
}
}
else
{
HtmlTag pTag{"p", body};
body += "Updater is sleeping (not constructed)";
}
{
@ -73,6 +197,8 @@ esp_err_t webserver_ota_handler(httpd_req_t *req)
HtmlTag buttonTag{"button", "type=\"submit\"", body};
body += "Go";
}
body += "url is only used temporarely and not persisted in flash";
}
}
}
@ -82,7 +208,50 @@ esp_err_t webserver_ota_handler(httpd_req_t *req)
esp_err_t webserver_trigger_ota_handler(httpd_req_t *req)
{
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "not yet implemented")
std::string query;
if (auto result = esphttpdutils::webserver_get_query(req))
query = *result;
else
{
ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data());
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error());
}
std::string url;
constexpr const std::string_view urlParamName{"url"};
{
char valueBufEncoded[256];
if (const auto result = httpd_query_key_value(query.data(), urlParamName.data(), valueBufEncoded, 256); result != ESP_OK)
{
if (result == ESP_ERR_NOT_FOUND)
{
const auto msg = fmt::format("{} not set", urlParamName);
ESP_LOGW(TAG, "%.*s", msg.size(), msg.data());
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg);
}
else
{
const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", urlParamName, esp_err_to_name(result));
ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", msg);
}
}
char valueBuf[257];
esphttpdutils::urldecode(valueBuf, valueBufEncoded);
url = valueBuf;
}
if (const auto result = triggerOta(url); !result)
{
ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data());
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error());
}
CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/ota")
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::TemporaryRedirect, "text/html", "Ok, continue at <a href=\"/ota\">/</a>")
}
}
#endif

View File

@ -1,5 +1,8 @@
#pragma once
// system includes
#include <limits>
// esp-idf includes
#ifdef FEATURE_WEBSERVER
#include <esp_http_server.h>
@ -11,6 +14,7 @@
#include <fmt/core.h>
#include <espcppmacros.h>
#include <esphttpdutils.h>
#include <numberparsing.h>
// local includes
#include "globals.h"
@ -18,12 +22,45 @@
#ifdef FEATURE_WEBSERVER
namespace {
esp_err_t webserver_settings_handler(httpd_req_t *req);
esp_err_t webserver_save_settings_handler(httpd_req_t *req);
esp_err_t webserver_saveSettings_handler(httpd_req_t *req);
} // namespace
using esphttpdutils::HtmlTag;
namespace {
template<typename T>
typename std::enable_if<!std::is_same<T, bool>::value && !std::is_integral<T>::value, bool>::type
showInputForSetting(std::string_view key, T value, std::string &body)
{
HtmlTag spanTag{"span", "style=\"color: red;\"", body};
body += "Unsupported config type";
return false;
}
template<typename T>
typename std::enable_if<std::is_same<T, bool>::value, bool>::type
showInputForSetting(std::string_view key, T value, std::string &body)
{
body += fmt::format("<input type=\"hidden\" name=\"{}\" value=\"false\" />"
"<input type=\"checkbox\" name=\"{}\" value=\"true\" {}/>",
esphttpdutils::htmlentities(key),
esphttpdutils::htmlentities(key),
value ? "checked " : "");
return true;
}
template<typename T>
typename std::enable_if<!std::is_same<T, bool>::value && std::is_integral<T>::value, bool>::type
showInputForSetting(std::string_view key, T value, std::string &body)
{
body += fmt::format("<input type=\"number\" name=\"{}\" value=\"{}\" min=\"{}\" max=\"{}\" step=\"1\" required />",
esphttpdutils::htmlentities(key),
value,
std::numeric_limits<T>::min(),
std::numeric_limits<T>::max());
return true;
}
esp_err_t webserver_settings_handler(httpd_req_t *req)
{
std::string body;
@ -40,6 +77,22 @@ esp_err_t webserver_settings_handler(httpd_req_t *req)
}
body += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />";
HtmlTag styleTag{"style", "type=\"text/css\"", body};
body +=
".form-table {"
"display: table;"
"border-collapse: separate;"
"border-spacing: 10px 0;"
"}"
".form-table .form-table-row {"
"display: table-row;"
"}"
".form-table .form-table-row .form-table-cell {"
"display: table-cell;"
"}";
}
{
@ -52,26 +105,31 @@ esp_err_t webserver_settings_handler(httpd_req_t *req)
{
HtmlTag pTag{"p", body};
body += "<a href=\"/\">Display control</a> "
body += "<a href=\"/\">Display control</a> - "
#ifdef FEATURE_OTA
"<a href=\"/ota\">Update</a> "
"<a href=\"/ota\">Update</a> - "
#endif
"<b>Settings</b>";
"<b>Settings</b> - "
"<a href=\"/stringSettings\">String Settings</a>";
}
stringSettings.executeForEveryCommonSetting([&](const char *key, const auto &value){
HtmlTag formTag{"form", "action=\"/saveSettings\" method=\"GET\"", body};
HtmlTag fieldsetTag{"fieldset", body};
HtmlTag divTag{"div", "class=\"form-table\"", body};
settings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){
HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveSettings\" method=\"GET\"", body};
{
HtmlTag legendTag{"legend", body};
HtmlTag divTag{"div", "class=\"form-table\"", body};
HtmlTag bTag{"b", body};
body += esphttpdutils::htmlentities(key);
}
body += fmt::format("<input type=\"text\" name=\"{}\" value=\"{}\" required />",
esphttpdutils::htmlentities(key),
esphttpdutils::htmlentities(value));
{
HtmlTag divTag{"div", "class=\"form-table-cell\"", body};
showInputForSetting(key, value, body);
}
{
HtmlTag divTag{"div", "class=\"form-table-cell\"", body};
HtmlTag buttonTag{"button", "type=\"submit\"", body};
body += "Save";
}
@ -82,7 +140,55 @@ esp_err_t webserver_settings_handler(httpd_req_t *req)
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body)
}
esp_err_t webserver_save_settings_handler(httpd_req_t *req)
template<typename T>
typename std::enable_if<!std::is_same<T, bool>::value && !std::is_integral<T>::value, bool>::type
saveSetting(T &value, std::string_view newValue, std::string &body)
{
body += "Unsupported config type";
return false;
}
template<typename T>
typename std::enable_if<std::is_same<T, bool>::value, bool>::type
saveSetting(T &value, std::string_view newValue, std::string &body)
{
if (newValue == "true")
{
value = true;
body += "applied";
return true;
}
else if (newValue == "false")
{
value = false;
body += "applied";
return true;
}
else
{
body += fmt::format("only true and false allowed, not {}", newValue);
return false;
}
}
template<typename T>
typename std::enable_if<!std::is_same<T, bool>::value && std::is_integral<T>::value, bool>::type
saveSetting(T &value, std::string_view newValue, std::string &body)
{
if (auto parsed = cpputils::fromString<T>(newValue))
{
value = *parsed;
body += "applied";
return true;
}
else
{
body += fmt::format("could not parse {}", newValue);
return false;
}
}
esp_err_t webserver_saveSettings_handler(httpd_req_t *req)
{
std::string query;
if (auto result = esphttpdutils::webserver_get_query(req))
@ -96,9 +202,9 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req)
std::string body;
bool success{true};
stringSettings.executeForEveryCommonSetting([&](const char *key, auto &value){
settings.executeForEveryCommonSetting([&](std::string_view key, auto &value){
char valueBufEncoded[256];
if (const auto result = httpd_query_key_value(query.data(), key, valueBufEncoded, 256); result != ESP_OK && result != ESP_ERR_NOT_FOUND)
if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK && result != ESP_ERR_NOT_FOUND)
{
const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result));
ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
@ -111,15 +217,16 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req)
char valueBuf[257];
esphttpdutils::urldecode(valueBuf, valueBufEncoded);
value = valueBuf;
body += fmt::format("{}: applied", key);
body += key;
if (!saveSetting(value, valueBuf, body))
success = false;
body += '\n';
});
if (body.empty())
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!")
if (settingsPersister.save(stringSettings))
if (settingsPersister.save(settings))
body += "settings persisted successfully";
else
{
@ -128,7 +235,10 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req)
}
if (success)
CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/")
{
CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/settings")
body += "\nOk, continue at /settings";
}
CALL_AND_EXIT(esphttpdutils::webserver_resp_send,
req,
@ -139,4 +249,3 @@ esp_err_t webserver_save_settings_handler(httpd_req_t *req)
} // namespace
#endif

View File

@ -0,0 +1,168 @@
#pragma once
// esp-idf includes
#ifdef FEATURE_WEBSERVER
#include <esp_http_server.h>
#endif
#include <esp_log.h>
// 3rdparty lib includes
#include <htmlbuilder.h>
#include <fmt/core.h>
#include <espcppmacros.h>
#include <esphttpdutils.h>
// local includes
#include "globals.h"
#ifdef FEATURE_WEBSERVER
namespace {
esp_err_t webserver_stringSettings_handler(httpd_req_t *req);
esp_err_t webserver_saveStringSettings_handler(httpd_req_t *req);
} // namespace
using esphttpdutils::HtmlTag;
namespace {
esp_err_t webserver_stringSettings_handler(httpd_req_t *req)
{
std::string body;
{
HtmlTag htmlTag{"html", body};
{
HtmlTag headTag{"head", body};
{
HtmlTag titleTag{"title", body};
body += "String Settings";
}
body += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />";
HtmlTag styleTag{"style", "type=\"text/css\"", body};
body +=
".form-table {"
"display: table;"
"border-collapse: separate;"
"border-spacing: 10px 0;"
"}"
".form-table .form-table-row {"
"display: table-row;"
"}"
".form-table .form-table-row .form-table-cell {"
"display: table-cell;"
"}";
}
{
HtmlTag bodyTag{"body", body};
{
HtmlTag h1Tag{"h1", body};
body += "String Settings";
}
{
HtmlTag pTag{"p", body};
body += "<a href=\"/\">Display control</a> - "
#ifdef FEATURE_OTA
"<a href=\"/ota\">Update</a> - "
#endif
"<a href=\"/settings\">Settings</a> - "
"<b>String Settings</b>";
}
HtmlTag divTag{"div", "class=\"form-table\"", body};
stringSettings.executeForEveryCommonSetting([&](std::string_view key, const auto &value){
HtmlTag formTag{"form", "class=\"form-table-row\" action=\"/saveStringSettings\" method=\"GET\"", body};
{
HtmlTag divTag{"div", "class=\"form-table\"", body};
HtmlTag bTag{"b", body};
body += esphttpdutils::htmlentities(key);
}
{
HtmlTag divTag{"div", "class=\"form-table-cell\"", body};
body += fmt::format("<input type=\"text\" name=\"{}\" value=\"{}\" required />",
esphttpdutils::htmlentities(key),
esphttpdutils::htmlentities(value));
}
{
HtmlTag divTag{"div", "class=\"form-table-cell\"", body};
HtmlTag buttonTag{"button", "type=\"submit\"", body};
body += "Save";
}
});
}
}
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/html", body)
}
esp_err_t webserver_saveStringSettings_handler(httpd_req_t *req)
{
std::string query;
if (auto result = esphttpdutils::webserver_get_query(req))
query = *result;
else
{
ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data());
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::BadRequest, "text/plain", result.error());
}
std::string body;
bool success{true};
stringSettings.executeForEveryCommonSetting([&](std::string_view key, auto &value){
char valueBufEncoded[256];
if (const auto result = httpd_query_key_value(query.data(), key.data(), valueBufEncoded, 256); result != ESP_OK && result != ESP_ERR_NOT_FOUND)
{
const auto msg = fmt::format("{}: httpd_query_key_value() failed with {}", key, esp_err_to_name(result));
ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
body += msg;
body += '\n';
success = false;
return;
}
char valueBuf[257];
esphttpdutils::urldecode(valueBuf, valueBufEncoded);
value = valueBuf;
body += fmt::format("{}: applied", key);
body += '\n';
});
if (body.empty())
CALL_AND_EXIT(esphttpdutils::webserver_resp_send, req, esphttpdutils::ResponseStatus::Ok, "text/plain", "nothing changed?!")
if (settingsPersister.save(stringSettings))
body += "string settings persisted successfully";
else
{
body += "error while persisting string settings";
success = false;
}
if (success)
{
CALL_AND_EXIT_ON_ERROR(httpd_resp_set_hdr, req, "Location", "/stringSettings")
body += "\nOk, continue at /stringSettings";
}
CALL_AND_EXIT(esphttpdutils::webserver_resp_send,
req,
success ? esphttpdutils::ResponseStatus::TemporaryRedirect : esphttpdutils::ResponseStatus::BadRequest,
"text/plain",
body)
}
} // namespace
#endif