#include "webui.h" AsyncWebServer webServer(80); const char *PARAM_MESSAGE = "message"; String processor(const String &var); void WebserverPOST_Callback(AsyncWebServerRequest *request); void WebserverNotFound_Callback(AsyncWebServerRequest *request); void Webserver_Callback(AsyncWebServerRequest *request); void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); void WebServerEEJSON_Callback(AsyncWebServerRequest *request); void GetFlashVersion(char *buff, size_t buff_size); AsyncWebSocket webSocket("/ws"); void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len); void initWebUI() { if (!LittleFS.begin()) { Debug_pushMessage("An Error has occurred while mounting LittleFS\n"); MaintainDTC(DTC_FLASHFS_ERROR, DTC_CRITICAL, true); return; } GetFlashVersion(globals.FlashVersion, sizeof(globals.FlashVersion)); char buffer[6]; snprintf(buffer, sizeof(buffer), "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); if (strcmp(globals.FlashVersion, buffer)) { MaintainDTC(DTC_FLASHFS_VERSION_ERROR, DTC_WARN, true); } MDNS.begin(globals.DeviceName); MDNS.addService("http", "tcp", 80); webSocket.onEvent(WebsocketEvent_Callback); webServer.addHandler(&webSocket); webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000"); webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->redirect("/index.htm"); }); webServer.onNotFound(WebserverNotFound_Callback); webServer.on("/index.htm", HTTP_GET, Webserver_Callback); webServer.on("/post.htm", HTTP_POST, WebserverPOST_Callback); webServer.on("/eejson", HTTP_GET, WebServerEEJSON_Callback); webServer.on( "/doUpdate", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverFirmwareUpdate_Callback); webServer.on( "/eeRestore", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverEERestore_Callback); webServer.begin(); } void Webserver_Process() { webSocket.cleanupClients(); } String processor(const String &var) { if (var == "HOSTNAME") return String(globals.DeviceName); if (var == "SYSTEM_STATUS") return String(globals.systemStatustxt); if (var == "SW_VERSION") { char buffer[6]; snprintf(buffer, sizeof(buffer), "%d.%02d", constants.FW_Version_major, constants.FW_Version_minor); return String(buffer); } if (var == "FS_VERSION") return String(globals.FlashVersion); if (var == "GIT_REV") return String(constants.GitHash); if (var == "SHOW_DTC_TABLE") return globals.systemStatus == sysStat_Error ? "" : "hidden"; if (var == "BAT_REMAIN_CAPACITY") return String(globals.battery_level); if (var == "DEVICENAME") return String(globals.DeviceName); if (var == "BATTERY_TYPE") return String(ConfigData.batteryType); if (var == "BAT_VOLTAGE") return String((float)globals.loadvoltage_mV / 1000.0); if (var == "PERSISTANCE_CHECKSUM") { char buffer[7]; sprintf(buffer, "0x%04X", PersistenceData.checksum); return String(buffer); } if (var == "WRITE_CYCLE_COUNT") return String(PersistenceData.writeCycleCounter); if (var == "PERSISTENCE_MARKER") return String(globals.eePersistanceAdress); if (var == "EEPROM_VERSION") return String(ConfigData.EEPROM_Version); if (var == "CONFIG_CHECKSUM") { char buffer[7]; sprintf(buffer, "0x%04X", ConfigData.checksum); return String(buffer); } if (var == "DTC_TABLE") { String temp = ""; char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) { if (DTCStorage[i].Number < DTC_LAST_DTC) { sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", DTCStorage[i].timestamp / 86400000, // Days DTCStorage[i].timestamp / 360000 % 24, // Hours DTCStorage[i].timestamp / 60000 % 60, // Minutes DTCStorage[i].timestamp / 1000 % 60, // Seconds DTCStorage[i].timestamp % 1000); // milliseconds temp = temp + "" + String(buff_timestamp); temp = temp + "" + String(DTCStorage[i].Number) + ""; temp = temp + ""; if (DTCStorage[i].active == DTC_ACTIVE) temp = temp + "active"; else if (DTCStorage[i].active == DTC_PREVIOUS) temp = temp + "previous"; else temp = temp + "none"; temp = temp + ""; } } return temp; } if (var == "PLACEHOLDER") return "placeholder"; if (var == "POINTS_FAC_1") { char buff[12]; snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60); return String(buff); } if (var == "POINTS_FAC_2") { char buff[12]; snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60); return String(buff); } if (var == "POINTS_FAC_3") { char buff[12]; snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60); return String(buff); } if (var == "ACTIVE_FACTION") return String(PersistenceData.activeFaction); if (var == "NAME_FAC_1") return FACTION_1_NAME; if (var == "NAME_FAC_2") return FACTION_2_NAME; if (var == "NAME_FAC_3") return FACTION_3_NAME; if (var == "BATTERY_SELECT_OPTIONS") { String temp; for (uint32_t i = 0; i < BatteryString_Elements; i++) { String selected = ConfigData.batteryType == i ? " selected " : ""; temp = temp + ""; } return temp; } return String(); } void Webserver_Callback(AsyncWebServerRequest *request) { request->send(LittleFS, "/index.htm", "text/html", false, processor); } void WebserverPOST_Callback(AsyncWebServerRequest *request) { request->send(LittleFS, "/post.htm", "text/html", false, processor); Debug_pushMessage("POST:\n"); int paramsNr = request->params(); for (int i = 0; i < paramsNr; i++) { AsyncWebParameter *p = request->getParam(i); Debug_pushMessage("%s : %s\n", p->name().c_str(), p->value().c_str()); // begin: POST Form Maintenance if (p->name() == "reset_ee_btn") { if (request->hasParam("reset_ee_pds", true)) { AsyncWebParameter *param = request->getParam("reset_ee_pds", true); if (param->value() == "on") globals.requestEEAction = globals.requestEEAction == EE_CFG_FORMAT ? EE_FORMAT_ALL : EE_PDS_FORMAT; } if (request->hasParam("reset_ee_cfg", true)) { AsyncWebParameter *param = request->getParam("reset_ee_cfg", true); if (param->value() == "on") globals.requestEEAction = globals.requestEEAction == EE_PDS_FORMAT ? EE_FORMAT_ALL : EE_CFG_FORMAT; } } if (p->name() == "reboot") { globals.systemStatus = sysStat_Shutdown; } if (p->name() == "resetpoints") { PersistenceData.faction_1_timer = 0; PersistenceData.faction_2_timer = 0; PersistenceData.faction_3_timer = 0; PersistenceData.activeFaction = NONE; globals.requestEEAction == EE_PDS_SAVE; } // end: POST Form Maintenance // begin: POST Form Settings if (p->name() == "battery_select") { batteryType_t temp = (batteryType_t)p->value().toInt(); ConfigData.batteryType = temp; } if (p->name() == "settingssave") globals.requestEEAction = EE_CFG_SAVE; // end: POST Form Settings } } void WebserverNotFound_Callback(AsyncWebServerRequest *request) { request->send(404, "text/html", "Not found"); } void GetFlashVersion(char *buff, size_t buff_size) { File this_file = LittleFS.open("version", "r"); if (!this_file) { // failed to open the file, retrn empty result buff[0] = '\0'; return; } if (this_file.available()) { int bytes_read; bytes_read = this_file.readBytesUntil('\r', buff, buff_size - 1); buff[bytes_read] = '\0'; } this_file.close(); } void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) { if (!index) { Debug_pushMessage("Update"); size_t content_len = request->contentLength(); int cmd = (filename.indexOf(".fs") > -1) ? U_FS : U_FLASH; Update.runAsync(true); if (!Update.begin(content_len, cmd)) { Update.printError(Serial); } } if (Update.write(data, len) != len) { Update.printError(Serial); } else { Debug_pushMessage("Progress: %d%%\n", (Update.progress() * 100) / Update.size()); } if (final) { AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots"); response->addHeader("Refresh", "20"); response->addHeader("Location", "/"); request->send(response); if (!Update.end(true)) { Update.printError(Serial); } else { Debug_pushMessage("Update complete\n"); globals.systemStatus = sysStat_Shutdown; } } } void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) { bool ee_done = false; static bool validext = false; static char *buffer = NULL; static uint32_t read_ptr = 0; DeserializationError error; if (!index) { validext = (filename.indexOf(".ee.json") > -1); if (validext) { buffer = (char *)malloc(1536); read_ptr = 0; if (buffer == NULL) Debug_pushMessage("malloc() failed for EEPROM-Restore"); } } if (buffer != NULL) { memcpy(buffer + read_ptr, data, len); read_ptr = read_ptr + len; } if (final) { if (buffer != NULL) { Serial.print(buffer); StaticJsonDocument<1536> doc; error = deserializeJson(doc, buffer); if (error) { Debug_pushMessage("deserializeJson() failed: %s\n", error.f_str()); } else { ConfigData.batteryType = (batteryType_t)doc["config"]["batteryType"].as(); ConfigData.EEPROM_Version = doc["config"]["EEPROM_Version"].as(); PersistenceData.writeCycleCounter = doc["persis"]["writeCycleCounter"].as(); PersistenceData.activeFaction = (factions_t)doc["persis"]["activeFaction"].as(); PersistenceData.faction_1_timer = doc["persis"]["faction_1_timer"].as(); PersistenceData.faction_2_timer = doc["persis"]["faction_2_timer"].as(); PersistenceData.faction_3_timer = doc["persis"]["faction_3_timer"].as(); PersistenceData.checksum = doc["persis"]["checksum"].as(); ee_done = true; } } free(buffer); AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots"); response->addHeader("Refresh", "20"); response->addHeader("Location", "/"); request->send(response); if (ee_done) { Debug_pushMessage("Update complete"); globals.systemStatus = sysStat_Shutdown; } else { } } } void WebServerEEJSON_Callback(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); DynamicJsonDocument json(1024); JsonObject fwinfo = json.createNestedObject("info"); char buffer[16]; fwinfo["DeviceName"] = globals.DeviceName; sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); fwinfo["FW-Version"] = buffer; fwinfo["FS-Version"] = globals.FlashVersion; snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash); fwinfo["Git-Hash"] = buffer; JsonObject config = json.createNestedObject("config"); config["EEPROM_Version"] = ConfigData.EEPROM_Version; config["batteryType"] = ConfigData.batteryType; sprintf(buffer, "0x%08X", ConfigData.checksum); config["checksum"] = buffer; JsonObject eepart = json.createNestedObject("eepart"); sprintf(buffer, "0x%04X", globals.eePersistanceAdress); eepart["PersistanceAddress"] = buffer; JsonObject persis = json.createNestedObject("persis"); persis["writeCycleCounter"] = PersistenceData.writeCycleCounter; persis["activeFaction"] = PersistenceData.activeFaction; persis["faction_1_timer"] = PersistenceData.faction_1_timer; persis["faction_2_timer"] = PersistenceData.faction_2_timer; persis["faction_3_timer"] = PersistenceData.faction_3_timer; sprintf(buffer, "0x%08X", PersistenceData.checksum); persis["checksum"] = buffer; serializeJsonPretty(json, *response); response->addHeader("Content-disposition", "attachment; filename=backup.ee.json"); request->send(response); } void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: Debug_pushMessage("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); break; case WS_EVT_DISCONNECT: Debug_pushMessage("WebSocket client #%u disconnected\n", client->id()); break; case WS_EVT_DATA: Websocket_HandleMessage(arg, data, len); break; case WS_EVT_PONG: case WS_EVT_ERROR: break; } } void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len) { AwsFrameInfo *info = (AwsFrameInfo *)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { data[len] = 0; Debug_pushMessage("Got WebSocket Message: %s \n", (char *)data); if (strcmp((char *)data, "start") == 0) { SetDebugportStatus(dbg_Webui, enabled); } else if (strcmp((char *)data, "stop") == 0) { SetDebugportStatus(dbg_Webui, disabled); } else if (strcmp((char *)data, "foo") == 0) { Debug_pushMessage("Got WebSocket Message 'foo' from client\n"); } } } void Websocket_PushLiveDebug(String Message) { webSocket.textAll(Message + "\n"); }