#include ESPMegaWebServer::ESPMegaWebServer(uint16_t port, ESPMegaIoT *iot) { this->port = port; this->iot = iot; this->server = new AsyncWebServer(port); this->saveConfigHandler = new AsyncCallbackJsonWebHandler("/save_config", std::bind(&ESPMegaWebServer::saveConfigJSONHandler, this, std::placeholders::_1, std::placeholders::_2)); } ESPMegaWebServer::~ESPMegaWebServer() { delete this->server; } void ESPMegaWebServer::begin() { this->loadCredentialsFromFRAM(); this->server->begin(); auto bindedDashboardHandler = std::bind(&ESPMegaWebServer::dashboardHandler, this, std::placeholders::_1); this->server->on("/", HTTP_GET, bindedDashboardHandler); auto bindedConfigHandler = std::bind(&ESPMegaWebServer::configHandler, this, std::placeholders::_1); this->server->on("/config", HTTP_GET, bindedConfigHandler); this->server->addHandler(saveConfigHandler); auto bindedOtaRequestHandler = std::bind(&ESPMegaWebServer::otaRequestHandler, this, std::placeholders::_1); auto bindedOtaUploadHandler = std::bind(&ESPMegaWebServer::otaUploadHandler, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6); this->server->on("/ota_update", HTTP_POST, bindedOtaRequestHandler, bindedOtaUploadHandler); } void ESPMegaWebServer::loop() { // AsyncWebServer doesn't have a loop function } void ESPMegaWebServer::bindFRAM(FRAM *fram) { this->fram = fram; } void ESPMegaWebServer::loadCredentialsFromFRAM() { this->fram->read(301, (uint8_t *)this->webUsername, 32); this->fram->read(333, (uint8_t *)this->webPassword, 32); // Verify if credentials are valid // A valid username and password is null terminated // Scan for null terminator bool validUsername = false; bool validPassword = false; for (int i = 0; i < 32; i++) { if (this->webUsername[i] == '\0') { validUsername = true; break; } } for (int i = 0; i < 32; i++) { if (this->webPassword[i] == '\0') { validPassword = true; break; } } if (!validUsername || !validPassword) { this->resetCredentials(); return; } // A valid username and password is at least 1 character long if (strlen(this->webUsername) == 0 || strlen(this->webPassword) == 0) { this->resetCredentials(); return; } } void ESPMegaWebServer::saveCredentialsToFRAM() { this->fram->write(301, (uint8_t *)this->webUsername, 32); this->fram->write(333, (uint8_t *)this->webPassword, 32); } void ESPMegaWebServer::resetCredentials() { // The default username and password is "admin" strcpy(this->webUsername, "admin"); strcpy(this->webPassword, "admin"); this->saveCredentialsToFRAM(); } char *ESPMegaWebServer::getWebUsername() { return this->webUsername; } char *ESPMegaWebServer::getWebPassword() { return this->webPassword; } void ESPMegaWebServer::setWebUsername(const char *username) { strcpy(this->webUsername, username); } void ESPMegaWebServer::setWebPassword(const char *password) { strcpy(this->webPassword, password); } void ESPMegaWebServer::dashboardHandler(AsyncWebServerRequest *request) { if (!request->authenticate(this->webUsername, this->webPassword)) { return request->requestAuthentication(); } auto bindedDashboardProcessor = std::bind(&ESPMegaWebServer::dashboardProcessor, this, std::placeholders::_1); request->send_P(200, "text/html", ota_html, bindedDashboardProcessor); } String ESPMegaWebServer::dashboardProcessor(const String &var) { if (var == "hostname") { return String(this->iot->getNetworkConfig()->hostname); } else if (var == "ip_address") { return this->iot->getIp().toString(); } else if (var == "mac_address") { return this->iot->getMac(); } else if (var == "model") { return String("ESPMega PRO R3.3c"); } else if (var == "mqtt_connection_string") { MqttConfig *mqttConfig = this->iot->getMqttConfig(); String connectionString; connectionString += mqttConfig->mqtt_server; connectionString += ":"; connectionString += mqttConfig->mqtt_port; return connectionString; } else if (var == "base_topic") { return String(this->iot->getMqttConfig()->base_topic); } else if (var == "mqtt_connected") { return this->iot->mqttConnected() ? "Connected" : "Standalone"; } return ""; } void ESPMegaWebServer::configHandler(AsyncWebServerRequest *request) { if (!request->authenticate(this->webUsername, this->webPassword)) { return request->requestAuthentication(); } auto bindedConfigProcessor = std::bind(&ESPMegaWebServer::configProcessor, this, std::placeholders::_1); request->send_P(200, "text/html", config_html, bindedConfigProcessor); } String ESPMegaWebServer::configProcessor(const String &var) { MqttConfig *mqttConfig = this->iot->getMqttConfig(); NetworkConfig *networkConfig = this->iot->getNetworkConfig(); if (var == "ip_address") { return networkConfig->ip.toString(); } else if (var == "netmask") { return networkConfig->subnet.toString(); } else if (var == "gateway") { return networkConfig->gateway.toString(); } else if (var == "dns") { return networkConfig->dns1.toString(); } else if (var == "hostname") { return String(networkConfig->hostname); } else if (var == "bms_ip") { return String(mqttConfig->mqtt_server); } else if (var == "bms_port") { return String(mqttConfig->mqtt_port); } else if (var == "bms_useauth") { return mqttConfig->mqtt_useauth ? "checked=\"checked\"" : ""; } else if (var == "bms_username") { return String(mqttConfig->mqtt_user); } else if (var == "bms_password") { return String(mqttConfig->mqtt_password); } else if (var == "bms_endpoint") { return String(mqttConfig->base_topic); } else if (var == "web_username") { return String(this->webUsername); } else if (var == "web_password") { return String(this->webPassword); } return ""; } void ESPMegaWebServer::otaRequestHandler(AsyncWebServerRequest *request) { // Prepare to receive firmware if (!request->authenticate(this->webUsername, this->webPassword)) { return request->requestAuthentication(); } AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); response->addHeader("Connection", "close"); request->send(response); // Restart ESPMega ESP.restart(); } void ESPMegaWebServer::otaUploadHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { // Receive firmware if (!request->authenticate(this->webUsername, this->webPassword)) { return request->requestAuthentication(); } if (index == 0) { ESP_LOGI("ESPMegaWebServer", "OTA Update Start"); if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { // start with max available size ESP_LOGE("ESPMegaWebServer", "OTA Update Start Error"); Update.printError(Serial); } } if (Update.write(data, len) != len) { ESP_LOGE("ESPMegaWebServer", "OTA Update Write Error"); Update.printError(Serial); } else { ESP_LOGI("ESPMegaWebServer", "OTA Update Write Success: %uB", index + len); } if (final) { if (Update.end(true)) { // true to set the size to the current progress ESP_LOGI("ESPMegaWebServer", "OTA Update Success: %uB", index + len); } else { ESP_LOGE("ESPMegaWebServer", "OTA Update End Error"); Update.printError(Serial); } } } void ESPMegaWebServer::saveConfigJSONHandler(AsyncWebServerRequest *request, JsonVariant &json) { /** * Request POST body should be a JSON object * containing the following fields: * ip_address: String, the IP address of the device * netmask: String, the netmask of the device * gateway: String, the gateway of the device * dns: String, the DNS of the device * hostname: String, the hostname of the device * bms_ip: String, the IP address of the MQTT broker * bms_port: int, the port of the MQTT broker * bms_useauth: Boolean, true if the MQTT broker requires authentication * bms_username: String, the username of the MQTT broker * bms_password: String, the password of the MQTT broker * bms_endpoint: String, the base topic of the MQTT broker * web_username: String, the username of the web server * web_password: String, the password of the web server */ ESP_LOGD("ESPMegaWebServer", "Saving config"); JsonObject root = json.as(); // Network Config NetworkConfig networkConfig; networkConfig.useStaticIp = true; networkConfig.useWifi = false; IPAddress ip; ESP_LOGD("ESPMegaWebServer", "Checking IP Address"); if (!ip.fromString(root["ip_address"].as())) { ESP_LOGE("ESPMegaWebServer", "Invalid Config IP Address"); request->send(400, "text/plain", "Invalid IP Address"); return; } networkConfig.ip = ip; ESP_LOGD("ESPMegaWebServer", "Checking Netmask"); if (!ip.fromString(root["netmask"].as())) { ESP_LOGE("ESPMegaWebServer", "Invalid Config Netmask"); request->send(400, "text/plain", "Invalid Netmask"); return; } networkConfig.subnet = ip; ESP_LOGD("ESPMegaWebServer", "Checking Gateway"); if (!ip.fromString(root["gateway"].as())) { ESP_LOGE("ESPMegaWebServer", "Invalid Config Gateway"); request->send(400, "text/plain", "Invalid Gateway"); return; } networkConfig.gateway = ip; ESP_LOGD("ESPMegaWebServer", "Checking DNS"); if (!ip.fromString(root["dns"].as())) { ESP_LOGE("ESPMegaWebServer", "Invalid Config DNS"); request->send(400, "text/plain", "Invalid DNS"); return; } networkConfig.dns1 = ip; ESP_LOGD("ESPMegaWebServer", "Setting Hostname"); strcpy(networkConfig.hostname, root["hostname"].as().c_str()); // MQTT Config MqttConfig mqttConfig; ESP_LOGD("ESPMegaWebServer", "Setting MQTT Server"); strcpy(mqttConfig.mqtt_server, root["bms_ip"].as().c_str()); ESP_LOGD("ESPMegaWebServer", "Checking MQTT Port"); uint16_t mqttPort = root["bms_port"].as(); if (mqttConfig.mqtt_port <= 0 || mqttConfig.mqtt_port > 65535) { ESP_LOGE("ESPMegaWebServer", "Invalid Config MQTT Port"); request->send(400, "text/plain", "Invalid MQTT Port"); return; } mqttConfig.mqtt_port = mqttPort; ESP_LOGD("ESPMegaWebServer", "Checking MQTT Use Auth"); mqttConfig.mqtt_useauth = root["bms_useauth"].as(); ESP_LOGD("ESPMegaWebServer", "Setting MQTT Username"); strcpy(mqttConfig.mqtt_user, root["bms_username"].as().c_str()); ESP_LOGD("ESPMegaWebServer", "Setting MQTT Password"); strcpy(mqttConfig.mqtt_password, root["bms_password"].as().c_str()); ESP_LOGD("ESPMegaWebServer", "Setting MQTT Base Topic"); strcpy(mqttConfig.base_topic, root["bms_endpoint"].as().c_str()); // Web Server Config ESP_LOGD("ESPMegaWebServer", "Setting Web Username"); strcpy(this->webUsername, root["web_username"].as().c_str()); ESP_LOGD("ESPMegaWebServer", "Setting Web Password"); strcpy(this->webPassword, root["web_password"].as().c_str()); // Commit changes to FRAM ESP_LOGD("ESPMegaWebServer", "Committing Network Config to FRAM"); this->iot->setNetworkConfig(networkConfig); this->iot->saveNetworkConfig(); ESP_LOGD("ESPMegaWebServer", "Committing MQTT Config to FRAM"); this->iot->setMqttConfig(mqttConfig); this->iot->saveMqttConfig(); ESP_LOGD("ESPMegaWebServer", "Committing Web Server Config to FRAM"); this->saveCredentialsToFRAM(); ESP_LOGD("ESPMegaWebServer", "Config saved"); // Send response request->send(200, "text/plain", "OK"); ESP.restart(); }