#include "webserver.h" // system includes #include #include #include // esp-idf includes #include // 3rdparty lib includes #include // local includes #include "myconfig.h" #include "feature_lamp.h" #include "feature_switch.h" #include "feature_dht.h" #include "feature_tsl.h" #include "feature_bmp.h" #include "mymqtt.h" #include "espwifistack.h" #include "espcppmacros.h" #include "espstrutils.h" #include "strutils.h" #include "espchrono.h" #include "numberparsing.h" #include "myota.h" #include "espasyncota.h" namespace deckenlampe { httpd_handle_t httpdHandle; namespace { constexpr const char * const TAG = "WEBSERVER"; template T htmlentities(const T &val) { return val; } // TODO template T htmlentities(T &&val) { return val; } // TODO std::atomic shouldReboot; esp_err_t webserver_root_handler(httpd_req_t *req); esp_err_t webserver_on_handler(httpd_req_t *req); esp_err_t webserver_off_handler(httpd_req_t *req); esp_err_t webserver_toggle_handler(httpd_req_t *req); esp_err_t webserver_reboot_handler(httpd_req_t *req); } // namespace void init_webserver() { if (!config::enable_webserver.value()) return; { httpd_config_t httpConfig HTTPD_DEFAULT_CONFIG(); httpConfig.core_id = 1; const auto result = httpd_start(&httpdHandle, &httpConfig); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_start(): %s", esp_err_to_name(result)); if (result != ESP_OK) return; } for (const httpd_uri_t &uri : { httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }, httpd_uri_t { .uri = "/on", .method = HTTP_GET, .handler = webserver_on_handler, .user_ctx = NULL }, httpd_uri_t { .uri = "/off", .method = HTTP_GET, .handler = webserver_off_handler, .user_ctx = NULL }, httpd_uri_t { .uri = "/toggle", .method = HTTP_GET, .handler = webserver_toggle_handler, .user_ctx = NULL }, httpd_uri_t { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL } }) { const auto result = httpd_register_uri_handler(httpdHandle, &uri); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", uri.uri, esp_err_to_name(result)); //if (result != ESP_OK) // return result; } } void update_webserver() { if (!config::enable_webserver.value()) return; if (shouldReboot) { shouldReboot = false; esp_restart(); } } namespace { esp_err_t webserver_dht_display(httpd_req_t *req, std::string &body); esp_err_t webserver_tsl_display(httpd_req_t *req, std::string &body); esp_err_t webserver_bmp_display(httpd_req_t *req, std::string &body); esp_err_t webserver_wifi_display(httpd_req_t *req, std::string &body); esp_err_t webserver_mqtt_display(httpd_req_t *req, std::string &body); esp_err_t webserver_config_display(httpd_req_t *req, std::string &body, std::string_view query); esp_err_t webserver_root_handler(httpd_req_t *req) { std::string query; if (const size_t queryLength = httpd_req_get_url_query_len(req)) { query.resize(queryLength); CALL_AND_EXIT_ON_ERROR(httpd_req_get_url_query_str, req, query.data(), query.size() + 1) } std::string body; if (config::enable_lamp.value()) { body += "on
\n" "off
\n" "toggle
\n"; } body += "reboot
\n" "
\n"; if (config::enable_lamp.value()) body += fmt::format("Lamp: {}
\n", lampState ? "ON" : "OFF"); if (config::enable_switch.value()) body += fmt::format("Switch: {}
\n", switchState ? "ON" : "OFF"); if (const auto result = webserver_dht_display(req, body); result != ESP_OK) return result; if (const auto result = webserver_tsl_display(req, body); result != ESP_OK) return result; if (const auto result = webserver_bmp_display(req, body); result != ESP_OK) return result; body += "
\n"; if (const auto result = webserver_wifi_display(req, body); result != ESP_OK) return result; if (const auto result = webserver_mqtt_display(req, body); result != ESP_OK) return result; if (const auto result = webserver_config_display(req, body, query); result != ESP_OK) return result; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT(httpd_resp_send, req, body.data(), body.size()) } esp_err_t webserver_dht_display(httpd_req_t *req, std::string &body) { if (config::enable_dht.value()) { if (lastDhtValue) { body += fmt::format("DHT11 Temperature: {:.1f} C
\n", lastDhtValue->temperature); body += fmt::format("DHT11 Humidity: {:.1f} %
\n", lastDhtValue->humidity); } else body += "DHT11 not available at the moment
\n"; } return ESP_OK; } esp_err_t webserver_tsl_display(httpd_req_t *req, std::string &body) { if (config::enable_i2c.value() && config::enable_tsl.value()) { if (lastTslValue) { body += fmt::format("TSL2561 Brightness: {:.1f} lux
\n", lastTslValue->lux); } else body += "TSL2561 not available at the moment
\n"; } return ESP_OK; } esp_err_t webserver_bmp_display(httpd_req_t *req, std::string &body) { if (config::enable_i2c.value() && config::enable_bmp.value()) { if (lastBmpValue) { body += fmt::format("BMP085 Pressure: {:.1f} lux
\n", lastBmpValue->pressure); body += fmt::format("BMP085 Temperature: {:.1f} C
\n", lastBmpValue->temperature); body += fmt::format("BMP085 Altitude: {:.1f} m
\n", lastBmpValue->altitude); } else body += "BMP085 not available at the moment
\n"; } return ESP_OK; } esp_err_t webserver_wifi_display(httpd_req_t *req, std::string &body) { body += "

wifi

\n" "\n"; body += fmt::format("\n", wifi_stack::toString(wifi_stack::wifiStateMachineState)); { const auto staStatus = wifi_stack::get_sta_status(); body += fmt::format("\n", wifi_stack::toString(staStatus)); if (staStatus == wifi_stack::WiFiStaStatus::WL_CONNECTED) { if (const auto result = wifi_stack::get_sta_ap_info(); result) { body += fmt::format("\n", result->rssi); body += fmt::format("\n", htmlentities(result->ssid)); body += fmt::format("\n", result->primary); body += fmt::format("\n", wifi_stack::toString(wifi_stack::mac_t{result->bssid})); } else { body += fmt::format("\n", htmlentities(result.error())); } if (const auto result = wifi_stack::get_ip_info(TCPIP_ADAPTER_IF_STA)) { body += fmt::format("\n", wifi_stack::toString(result->ip)); body += fmt::format("\n", wifi_stack::toString(result->netmask)); body += fmt::format("\n", wifi_stack::toString(result->gw)); } else { body += fmt::format("\n", htmlentities(result.error())); } } } body += fmt::format("\n", wifi_stack::toString(wifi_stack::get_scan_status())); body += "
state machine state{}
STA status{}
STA rssi{}dB
STA SSID{}
STA channel{}
STA BSSID{}
get_sta_ap_info() failed: {}
STA ip{}
STA netmask{}
STA gw{}
get_ip_info() failed: {}
scan status{}
\n" "

scan result

\n"; if (const auto &scanResult = wifi_stack::get_scan_result()) { body += fmt::format("scan age: {}s
\n", std::chrono::round(espchrono::ago(scanResult->finished)).count()); body += "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n"; for (const auto &entry : scanResult->entries) { body += fmt::format( "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n", htmlentities(entry.ssid), wifi_stack::toString(entry.authmode), wifi_stack::toString(entry.pairwise_cipher), wifi_stack::toString(entry.group_cipher), entry.rssi, entry.primary, wifi_stack::toString(wifi_stack::mac_t{entry.bssid})); } body += "\n" "
ssidauthmodepairwise_ciphergroup_cipherrssichannelbssid
{}{}{}{}{}{}{}
\n"; } else { body += "no wifi scan result at the moment!
\n"; } return ESP_OK; } esp_err_t webserver_mqtt_display(httpd_req_t *req, std::string &body) { if (config::enable_mqtt.value()) { body += "
\n" "

MQTT

\n" "\n"; body += fmt::format("\n", htmlentities(config::broker_url.value())); body += fmt::format("\n", mqttClient ? "true" : "false"); if (mqttClient) { body += fmt::format("\n", mqttStarted ? "true" : "false"); body += fmt::format("\n", mqttConnected ? "true" : "false"); } body += "
client url{}
client constructed{}
client started{}
client connected{}
\n"; } return ESP_OK; } template std::string webserver_form_for_config(httpd_req_t *req, ConfigWrapper &config, std::string_view query) { return "form not implemented"; } template<> std::string webserver_form_for_config(httpd_req_t *req, ConfigWrapper &config, std::string_view query) { std::string str; { char valueBufEncoded[256]; if (const auto result = httpd_query_key_value(query.data(), config.nvsKey(), valueBufEncoded, 256); result == ESP_OK) { char valueBuf[257]; espcpputils::urldecode(valueBuf, valueBufEncoded); std::string_view value{valueBuf}; if (value == "true" || value == "false") { if (const auto result = config.writeToFlash(value == "true")) str += "Successfully saved"; else str += fmt::format("Error while saving: {}", htmlentities(result.error())); } else { str += fmt::format("Error while saving: Invalid value \"{}\"", htmlentities(value)); } } else if (result != ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "httpd_query_key_value() %s failed with %s", config.nvsKey(), esp_err_to_name(result)); str += fmt::format("Error while saving: httpd_query_key_value() failed with {}", esp_err_to_name(result)); } } str += fmt::format("
" "" "" "" "
", htmlentities(config.nvsKey()), htmlentities(config.nvsKey()), config.value() ? " checked" : ""); return str; } template<> std::string webserver_form_for_config(httpd_req_t *req, ConfigWrapper &config, std::string_view query) { std::string str; { char valueBufEncoded[256]; if (const auto result = httpd_query_key_value(query.data(), config.nvsKey(), valueBufEncoded, 256); result == ESP_OK) { char valueBuf[257]; espcpputils::urldecode(valueBuf, valueBufEncoded); if (const auto result = config.writeToFlash(valueBuf)) str += "Successfully saved"; else str += fmt::format("Error while saving: {}", htmlentities(result.error())); } else if (result != ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "httpd_query_key_value() %s failed with %s", config.nvsKey(), esp_err_to_name(result)); str += fmt::format("Error while saving: httpd_query_key_value() failed with {}", esp_err_to_name(result)); } } str += fmt::format("
" "" "" "
", htmlentities(config.nvsKey()), htmlentities(config.value())); return str; } template<> std::string webserver_form_for_config(httpd_req_t *req, ConfigWrapper &config, std::string_view query) { std::string str; { char valueBufEncoded[256]; if (const auto result = httpd_query_key_value(query.data(), config.nvsKey(), valueBufEncoded, 256); result == ESP_OK) { char valueBuf[257]; espcpputils::urldecode(valueBuf, valueBufEncoded); std::string_view value{valueBuf}; if (auto parsed = cpputils::fromString>(value)) { if (const auto result = config.writeToFlash(gpio_num_t(*parsed))) str += "Successfully saved"; else str += fmt::format("Error while saving: {}", htmlentities(result.error())); } else { str += fmt::format("Error while saving: Invalid value \"{}\"", htmlentities(value)); } } else if (result != ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "httpd_query_key_value() %s failed with %s", config.nvsKey(), esp_err_to_name(result)); } } str += fmt::format("
" "" "" "
", htmlentities(config.nvsKey()), config.value()); return str; } template<> std::string webserver_form_for_config(httpd_req_t *req, ConfigWrapper &config, std::string_view query) { std::string str; { char valueBufEncoded[256]; if (const auto result = httpd_query_key_value(query.data(), config.nvsKey(), valueBufEncoded, 256); result == ESP_OK) { char valueBuf[257]; espcpputils::urldecode(valueBuf, valueBufEncoded); std::string_view value{valueBuf}; if (auto parsed = cpputils::fromString(value)) { if (const auto result = config.writeToFlash(espchrono::seconds32(*parsed))) str += "Successfully saved"; else str += fmt::format("Error while saving: {}", htmlentities(result.error())); } else { str += fmt::format("Error while saving: Invalid value \"{}\"", htmlentities(value)); } } else if (result != ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "httpd_query_key_value() %s failed with %s", config.nvsKey(), esp_err_to_name(result)); } } str += fmt::format("
" "" "" "
", htmlentities(config.nvsKey()), config.value().count()); return str; } esp_err_t webserver_config_display(httpd_req_t *req, std::string &body, std::string_view query) { body += "
\n" "

config

\n" "\n" "\n" "\n" "\n" //"\n" "\n" "\n" "\n" "\n" "\n"; config::foreachConfig([&](auto &config) { using cpputils::toString; using espchrono::toString; body += fmt::format("\n" "\n" //"\n" "\n" "\n" "\n", htmlentities(config.name()), //htmlentities(toString(config.value())), webserver_form_for_config(req, config, query), [&]() -> std::string { if (const auto result = config.readFromFlash()) { if (*result) return htmlentities(toString(**result)); else return "not set"; } else return fmt::format("{}", htmlentities(result.error())); }()); }); body += "\n" "
namecurrent valueset new valuevalue in flash
{}{}{}{}
\n"; return ESP_OK; } esp_err_t webserver_on_handler(httpd_req_t *req) { if (!config::enable_lamp.value()) { ESP_LOGW(TAG, "lamp support not enabled!"); CALL_AND_EXIT(httpd_resp_send_err, req, HTTPD_400_BAD_REQUEST, "lamp support not enabled!") } const bool state = (lampState = true); writeLamp(state); if (mqttConnected) mqttVerbosePub(config::topic_lamp_status.value(), state ? "ON" : "OFF", 0, 1); std::string_view body{"ON called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT(httpd_resp_send, req, body.data(), body.size()) } esp_err_t webserver_off_handler(httpd_req_t *req) { if (!config::enable_lamp.value()) { ESP_LOGW(TAG, "lamp support not enabled!"); CALL_AND_EXIT(httpd_resp_send_err, req, HTTPD_400_BAD_REQUEST, "lamp support not enabled!") } const bool state = (lampState = false); writeLamp(state); if (mqttConnected) mqttVerbosePub(config::topic_lamp_status.value(), state ? "ON" : "OFF", 0, 1); std::string_view body{"OFF called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT(httpd_resp_send, req, body.data(), body.size()) } esp_err_t webserver_toggle_handler(httpd_req_t *req) { if (!config::enable_lamp.value()) { ESP_LOGW(TAG, "lamp support not enabled!"); CALL_AND_EXIT(httpd_resp_send_err, req, HTTPD_400_BAD_REQUEST, "lamp support not enabled!") } const bool state = (lampState = !lampState); writeLamp(state); if (mqttConnected) mqttVerbosePub(config::topic_lamp_status.value(), state ? "ON" : "OFF", 0, 1); std::string_view body{"TOGGLE called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT(httpd_resp_send, req, body.data(), body.size()) } esp_err_t webserver_reboot_handler(httpd_req_t *req) { shouldReboot = true; std::string_view body{"REBOOT called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT(httpd_resp_send, req, body.data(), body.size()) } } // namespace } // namespace deckenlampe