diff --git a/extra_post.py b/extra_post.py index d573368..867cff3 100644 --- a/extra_post.py +++ b/extra_post.py @@ -74,7 +74,7 @@ def postbuild(source, target, env): uploadfiles = uploadparts[-6:] for i in range(1, len(uploadfiles), 2): if not os.path.isfile(uploadfiles[i]): - print("file %s for combine not found"%uploadfiles[i]) + print("file {} for combine not found".format(uploadfiles[i])) return offset = uploadfiles[0] diff --git a/include/config.h b/include/config.h index 6643776..b0148e7 100644 --- a/include/config.h +++ b/include/config.h @@ -1,9 +1,95 @@ #pragma once +#include +#include +#include +#include +#include "main.h" + +enum class ConfigType { + BYTE, + SHORT, + INT, + BOOL, + FLOAT, + CHAR, + STRING +}; + +using ConfigValue = std::variant< + uint8_t, // BYTE unsigned + int16_t, // SHORT + int32_t, // INT + bool, // BOOL + float, // FLOAT + char, // CHAR + String // STRING +>; + +struct ConfigDef { + const char* key; + ConfigType type; + ConfigValue defval; +}; + +// keys have to match names in config.json +static const ConfigDef configdefs[] = { + {"systemName", ConfigType::STRING, String("OBPkp61")}, + {"systemMode", ConfigType::CHAR, 'K'}, + {"logLevel", ConfigType::BYTE, uint8_t(0)}, + {"adminPassword", ConfigType::STRING, String("obpkp61")}, + {"useAdminPass", ConfigType::BOOL, true}, + {"apEnable", ConfigType::BOOL, true}, + {"apPassword", ConfigType::STRING, String("obpkp61")}, + {"apIp", ConfigType::STRING, String("192.168.15.1")}, + {"apMask", ConfigType::STRING, String("255.255.255.0")}, + {"stopApTime", ConfigType::SHORT, int16_t(0)}, + {"cpuSpeed", ConfigType::SHORT, int16_t(160)}, + {"ledBrightness", ConfigType::BYTE, uint8_t(96)}, + {"ledRgbBrightness", ConfigType::BYTE, uint8_t(96)}, + {"tempFormat", ConfigType::CHAR, 'C'}, + {"switchBank", ConfigType::BYTE, uint8_t(0)}, + {"key1", ConfigType::BYTE, uint8_t(1)}, + {"key1long", ConfigType::BYTE, uint8_t(11)}, + {"key2", ConfigType::BYTE, uint8_t(2)}, + {"key2long", ConfigType::BYTE, uint8_t(12)}, + {"key3", ConfigType::BYTE, uint8_t(3)}, + {"key3long", ConfigType::BYTE, uint8_t(13)}, + {"key4", ConfigType::BYTE, uint8_t(4)}, + {"key4long", ConfigType::BYTE, uint8_t(14)}, + {"key5", ConfigType::BYTE, uint8_t(5)}, + {"key5long", ConfigType::BYTE, uint8_t(15)}, + {"key6", ConfigType::BYTE, uint8_t(6)}, + {"key6long", ConfigType::BYTE, uint8_t(16)}, + {"n2kDestA", ConfigType::STRING, String("")}, + {"n2kDestB", ConfigType::STRING, String("")}, + {"n2kDestC", ConfigType::STRING, String("")}, + {"envInterval", ConfigType::SHORT, int16_t(5000)}, + + // no user access + {"LastNodeId", ConfigType::BYTE, uint8_t(N2K_DEFAULT_NODEID)} +}; +constexpr size_t configdefsCount = sizeof(configdefs) / sizeof(configdefs[0]); + class Config { private: - Preferences *prefs; + Preferences& prefs; + std::map values; + std::map defs; public: - Config(); - bool loadConfig(); + explicit Config(Preferences& prefs); + void load(); + bool save(JsonObject json); + void dump(); + + template + T get(const char* key) const; + + uint8_t getByte(const char* key) const; + int16_t getShort(const char* key) const; + int32_t getInt(const char* key) const; + bool getBool(const char* key) const; + float getFloat(const char* key) const; + char getChar(const char* key) const; + const String& getString(const char* key) const; }; diff --git a/include/main.h b/include/main.h index b947df8..7072124 100644 --- a/include/main.h +++ b/include/main.h @@ -1,4 +1,5 @@ #pragma once +#include #define STRINGIFY_IMPL(x) #x #define STRINGIFY(x) STRINGIFY_IMPL(x) @@ -87,14 +88,24 @@ #define BUTTON_6 5 #define BUTTON_DST 6 -enum class ButtonPressType : uint8_t -{ +enum class ButtonPressType : uint8_t { SHORT, // < 1 second MEDIUM, // >= 1 second and < 3 seconds LONG // >= 3 seconds }; -struct ButtonEvent -{ + +struct ButtonEvent { uint8_t buttonId; ButtonPressType pressType; }; + +extern uint64_t chipid; +extern uint8_t led_brightness; +extern uint8_t rgb_brightness; +extern uint8_t keycode[6]; +extern uint8_t longcode[6]; + +extern float temp; +extern float hum; + +String uptime_with_unit(); diff --git a/include/webserver.h b/include/webserver.h new file mode 100644 index 0000000..3be2ffc --- /dev/null +++ b/include/webserver.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +extern AsyncWebServer server; + +void webserver_init(); +void send_embedded_file(String name, AsyncWebServerRequest *request); + diff --git a/platformio.ini b/platformio.ini index bc58c69..d6c5df1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,7 +36,7 @@ build_flags = -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1 -DCORE_DEBUG_LEVEL=4 # Max. possible loglevel: 0=None, 1=Error, 2=Warning, 3=Info, 4=Debug, 5=Verbose - -DCONFIG_LOG_TX_BUF_SIZE=4096 + -DCONFIG_LOG_TX_BUF_SIZE=8192 -std=gnu++17 build_unflags = -std=gnu++11 diff --git a/src/config.cpp b/src/config.cpp index 352d6bd..ebf5375 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,12 +1,284 @@ -#include -#include "main.h" #include "config.h" -Config::Config() { +// Logging +static const char* TAG = "CFG"; + +Config::Config(Preferences& prefs) + : prefs(prefs) { + + // init defs mapping + for (size_t i = 0; i < configdefsCount; ++i) { + defs[configdefs[i].key] = &configdefs[i]; + } + } -bool Config::loadConfig() { - prefs->begin(PREF_NAME); - prefs->end(); +void Config::load() { + ESP_LOGI(TAG, "Loading configuration"); + prefs.begin(PREF_NAME, true); + for (const auto& def : configdefs) { + if (prefs.isKey(def.key)) { + ESP_LOGI(TAG, "Config option '%s' loaded from NVS", def.key); + switch (def.type) { + case ConfigType::BYTE: + values[def.key] = (uint8_t)prefs.getUChar(def.key, std::get(def.defval)); + break; + case ConfigType::SHORT: + values[def.key] = (int16_t)prefs.getShort(def.key, std::get(def.defval)); + break; + case ConfigType::INT: + values[def.key] = prefs.getInt(def.key, std::get(def.defval)); + break; + case ConfigType::BOOL: + values[def.key] = prefs.getBool(def.key, std::get(def.defval)); + break; + case ConfigType::FLOAT: + values[def.key] = prefs.getFloat(def.key, std::get(def.defval)); + break; + case ConfigType::CHAR: + values[def.key] = (char)prefs.getChar(def.key, std::get(def.defval)); + break; + case ConfigType::STRING: + values[def.key] = prefs.getString(def.key, std::get(def.defval)); + break; + } + } else { + ESP_LOGI(TAG, "Using default for '%s'", def.key); + switch (def.type) { + case ConfigType::BYTE: + values[def.key] = std::get(def.defval); + break; + case ConfigType::SHORT: + values[def.key] = std::get(def.defval); + break; + case ConfigType::INT: + values[def.key] = std::get(def.defval); + break; + case ConfigType::BOOL: + values[def.key] = std::get(def.defval); + break; + case ConfigType::FLOAT: + values[def.key] = std::get(def.defval); + break; + case ConfigType::CHAR: + values[def.key] = std::get(def.defval); + break; + case ConfigType::STRING: + values[def.key] = std::get(def.defval); + break; + } + } + } + prefs.end(); +} + +bool Config::save(JsonObject json) { + + // TODO Not fail if one single value is wrong? + + prefs.begin(PREF_NAME, false); + + int errors = 0; + for (JsonPair kv : json) { + const char* key = kv.key().c_str(); + JsonVariant v = kv.value(); + + auto it = values.find(key); + if (it == values.end()) { + ESP_LOGE(TAG, "Unexpected missing key: %s", key); + return false; + } + + auto itdef = defs.find(key); + if (itdef == defs.end()) { + ESP_LOGE(TAG, "Unexpected missing defs key: %s", key); + return false; + } + + // const ConfigDef* def = itdef->second; + + switch (itdef->second->type) { + case ConfigType::BYTE: { + if (!v.is()) { + errors++; + break; + } + uint8_t newval = v.as(); + uint8_t *curval = std::get_if(&values[key]); + if (newval != *curval) { + values[key] = newval; + //prefs.putUChar(key, newval); + ESP_LOGI(TAG, "changing %s, replacing %d with %d", key, *curval, newval); + } + break; + } +/* case ConfigType::SHORT: + if (!v.is()) return false; + values[key] = v.as(); + prefs.putShort(key, v.as()); + break; + case ConfigType::INT: + if (!v.is()) return false; + values[key] = v.as(); + prefs.putInt(key, v.as()); + break; + case ConfigType::BOOL: + if (!v.is()) return false; + values[key] = v.as(); + prefs.putBool(key, v.as()); + break; + case ConfigType::FLOAT: + if (!v.is()) return false; + values[key] = v.as(); + prefs.putFloat(key, v.as()); + break; + case ConfigType::CHAR: + if (!v.is()) return false; + values[key] = v.as()[0]; + prefs.putChar(key, v.as()[0]); + break; + case ConfigType::STRING: + if (!v.is()) return false; + values[key] = String(v.as()); + prefs.putString(key, v.as()); + break; */ + default: + ESP_LOGE("CFG", "Unsupported type for key %s", key); + } + } + + prefs.end(); return true; } + +void Config::dump() { + // only for debugging purposes, + ESP_LOGI(TAG, "========== Config options =========="); + for (const auto& def : configdefs) { + auto it = values.find(def.key); + if (it == values.end()) { + ESP_LOGW(TAG, "%s = ", def.key); + continue; + } + const ConfigValue& value = it->second; + switch (def.type) { + case ConfigType::BYTE: + if (auto v = std::get_if(&value)) + ESP_LOGI(TAG, "%s = %u", def.key, *v); + break; + case ConfigType::SHORT: + if (auto v = std::get_if(&value)) + ESP_LOGI(TAG, "%s = %d", def.key, *v); + break; + case ConfigType::INT: + if (auto v = std::get_if(&value)) + ESP_LOGI(TAG, "%s = %d", def.key, *v); + break; + case ConfigType::BOOL: + if (auto v = std::get_if(&value)) + ESP_LOGI(TAG, "%s = %s", def.key, *v ? "true" : "false"); + break; + case ConfigType::FLOAT: + if (auto v = std::get_if(&value)) + ESP_LOGI(TAG, "%s = %.3f", def.key, *v); + break; + case ConfigType::CHAR: + if (auto v = std::get_if(&value)) + ESP_LOGI(TAG, "%s = '%c'", def.key, *v); + break; + case ConfigType::STRING: + if (auto v = std::get_if(&value)) + ESP_LOGI(TAG, "%s = \"%s\"", def.key, v->c_str()); + break; + } + } + ESP_LOGI(TAG, "===================================="); +} + +template +T Config::get(const char* key) const { + return std::get(values.at(key)); +} + +uint8_t Config::getByte(const char* key) const { + auto it = values.find(key); + if (it == values.end()) { + ESP_LOGE(TAG, "Missing config key: %s", key); + abort(); + } + if (auto v = std::get_if(&it->second)) + return *v; + ESP_LOGE(TAG, "Type mismatch for key: %s", key); + abort(); +} + +int16_t Config::getShort(const char* key) const { + auto it = values.find(key); + if (it == values.end()) { + ESP_LOGE(TAG, "Missing config key: %s", key); + abort(); + } + if (auto v = std::get_if(&it->second)) + return *v; + ESP_LOGE(TAG, "Type mismatch for key: %s", key); + abort(); +} + +int32_t Config::getInt(const char* key) const { + auto it = values.find(key); + if (it == values.end()) { + ESP_LOGE(TAG, "Missing config key: %s", key); + abort(); + } + if (auto v = std::get_if(&it->second)) + return *v; + ESP_LOGE(TAG, "Type mismatch for key: %s", key); + abort(); +} + +bool Config::getBool(const char* key) const { + auto it = values.find(key); + if (it == values.end()) + ESP_LOGE(TAG, "Missing config key: %s", key); + abort(); + if (auto v = std::get_if(&it->second)) + return *v; + ESP_LOGE(TAG, "Type mismatch for key: %s", key); + abort(); +} + +float Config::getFloat(const char* key) const { + auto it = values.find(key); + if (it == values.end()) { + ESP_LOGE(TAG, "Missing config key: %s", key); + abort(); + } + if (auto v = std::get_if(&it->second)) + return *v; + ESP_LOGE(TAG, "Type mismatch for key: %s", key); + abort(); +} + +char Config::getChar(const char* key) const { + auto it = values.find(key); + if (it == values.end()) { + ESP_LOGE(TAG, "Missing config key: %s", key); + abort(); + } + if (auto v = std::get_if(&it->second)) + return *v; + ESP_LOGE(TAG, "Type mismatch for key: %s", key); + abort(); +} + +const String& Config::getString(const char* key) const { + auto it = values.find(key); + if (it == values.end()) { + ESP_LOGE(TAG, "Missing config key: %s", key); + abort(); + } + if (auto v = std::get_if(&it->second)) + return *v; + ESP_LOGE(TAG, "Type mismatch for key: %s", key); + abort(); +} diff --git a/src/main.cpp b/src/main.cpp index 8235071..c516434 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,8 @@ -#include #include "main.h" -#include +#include "config.h" +#include "webserver.h" #include #include -#include -#include -#include #include #include // temp. sensor #include @@ -65,50 +62,17 @@ String get_sha256(String payload) { } // Logging -static const char* TAG = "main.cpp"; +static const char* TAG = "MAIN"; -Preferences preferences; // persistent storage for configuration - -class EmbeddedFile; -static std::map embeddedFiles; -class EmbeddedFile { - public: - const uint8_t *start; - int len; - String contentType; - EmbeddedFile(String name, String contentType, const uint8_t *start, int len) { - this->start = start; - this->len = len; - this->contentType = contentType; - embeddedFiles[name] = this; - } -} ; - -#define EMBED_GZ_FILE(fileName, binName, contentType) \ - extern const uint8_t binName##_File[] asm("_binary_" #binName "_start"); \ - extern const uint8_t binName##_FileLen[] asm("_binary_" #binName "_size"); \ - const EmbeddedFile binName##_Config(fileName,contentType,(const uint8_t*)binName##_File,(int)binName##_FileLen); -#include "embeddedfiles.h" - -void send_embedded_file(String name, AsyncWebServerRequest *request) -{ - std::map::iterator it = embeddedFiles.find(name); - if (it != embeddedFiles.end()) { - EmbeddedFile* found = it->second; - AsyncWebServerResponse *response = request->beginResponse(200, found->contentType, found->start, found->len); - response->addHeader(F("Content-Encoding"), F("gzip")); - request->send(response); - } else { - request->send(404, "text/plain", "Not found"); - } -} +Preferences preferences; // persistent storage for configuration +Config config(preferences); // configuration object uint64_t chipid = ESP.getEfuseMac(); const char* wifi_ssid = "OBPKP61"; const char* wifi_pass = "keypad61"; bool apEnabled = false; -AsyncWebServer server(80); +// AsyncWebServer server(80); unsigned long firstStart = 0; unsigned long lastSensor = 0; @@ -316,24 +280,9 @@ void cpuFreqTimerCallback(TimerHandle_t xTimer) { void setup() { Serial.begin(115200); - // while (!Serial) delay(10); verhindert Booten ohne USB-Verbindung + while (!Serial) delay(10); // verhindert Booten ohne USB-Verbindung delay(500); - preferences.begin("nvs", false); - keycode[0] = preferences.getInt("key1", 1); - keycode[1] = preferences.getInt("key2", 2); - keycode[2] = preferences.getInt("key3", 3); - keycode[3] = preferences.getInt("key4", 4); - keycode[4] = preferences.getInt("key5", 5); - keycode[5] = preferences.getInt("key6", 6); - longcode[0] = preferences.getInt("key1long", 11); - longcode[1] = preferences.getInt("key2long", 12); - longcode[2] = preferences.getInt("key3long", 13); - longcode[3] = preferences.getInt("key4long", 14); - longcode[4] = preferences.getInt("key5long", 15); - longcode[5] = preferences.getInt("key6long", 16); - preferences.end(); - // Configure I/O pins // internal user led (red) @@ -381,6 +330,22 @@ void setup() { ESP_LOGI(TAG, "Starting ..."); + config.load(); + config.dump(); + + keycode[0] = config.getByte("key1"); + keycode[1] = config.getByte("key2"); + keycode[2] = config.getByte("key3"); + keycode[3] = config.getByte("key4"); + keycode[4] = config.getByte("key5"); + keycode[5] = config.getByte("key6"); + longcode[0] = config.getByte("key1long"); + longcode[1] = config.getByte("key2long"); + longcode[2] = config.getByte("key3long"); + longcode[3] = config.getByte("key4long"); + longcode[4] = config.getByte("key5long"); + longcode[5] = config.getByte("key6long"); + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); if (cause == ESP_SLEEP_WAKEUP_EXT0) { ESP_LOGI(TAG, " Wake up by key"); @@ -391,15 +356,21 @@ void setup() { // N2K basics nodeid = N2K_DEFAULT_NODEID; ESP_LOGI(TAG, "N2K default node is %d", nodeid); - preferences.begin("nvs", false); + preferences.begin(PREF_NAME, false); nodeid = preferences.getInt("LastNodeId", N2K_DEFAULT_NODEID); - cpuspeed = preferences.getInt("cpuSpeed", 160); - int apstoptime = preferences.getInt("stopApTime", 0); - String apip = preferences.getString("apIp", "192.168.15.1"); - String apmask = preferences.getString("apMask", "255.255.255.0"); preferences.end(); ESP_LOGI(TAG, "N2K node id set to %d from preferences", nodeid); + //cpuspeed = preferences.getInt("cpuSpeed", 160); + cpuspeed = config.getShort("cpuSpeed"); + //int apstoptime = preferences.getInt("stopApTime", 0); + int16_t apstoptime = config.getShort("stopApTime"); + //String apip = preferences.getString("apIp", "192.168.15.1"); + //String apmask = preferences.getString("apMask", "255.255.255.0"); + String apip = config.getString("apIp"); + String apmask = config.getString("apMask"); + //preferences.end(); + // Setup webserver WiFi.persistent(false); WiFi.mode(WIFI_MODE_AP); @@ -423,32 +394,9 @@ void setup() { WiFi.softAPConfig(ap_addr, ap_gateway, ap_subnet); apEnabled = true; - // Route for root / web page - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - send_embedded_file("index.html", request); - }); - // Route for all other defined pages - for (auto it = embeddedFiles.begin(); it != embeddedFiles.end(); it++) { - String uri = String("/") + it->first; - server.on(uri.c_str(), HTTP_GET, [it](AsyncWebServerRequest *request) { - send_embedded_file(it->first, request); - }); - } - // API fast hack - server.on("/api/capabilities", HTTP_GET, [](AsyncWebServerRequest *request){ - StaticJsonDocument<100> doc; - doc["apPwChange"] = "true"; - String out; - serializeJson(doc, out); - request->send(200, "application/json", out); - }); - server.on("/api/checkpass", HTTP_GET, [](AsyncWebServerRequest *request){ - StaticJsonDocument<100> doc; - doc["status"] = "FAILED"; - String out; - serializeJson(doc, out); - request->send(200, "application/json", out); - }); + // Initialize WebGUI + webserver_init(); + server.on("/api/devicelist", HTTP_GET, [](AsyncWebServerRequest *request){ // NMEA2000 device list AsyncResponseStream *response = request->beginResponseStream("application/json"); @@ -474,116 +422,8 @@ void setup() { response->print("]"); request->send(response); }); - server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){ - StaticJsonDocument<512> doc; - doc["systemName"] = "Keypad1"; - doc["logLevel"] = 0; - doc["version"] = VERSION; - doc["fwtype"] = "unknown"; - doc["salt"] = "secret"; - doc["AdminPassword"] = "********"; - doc["useAdminPass"] = false; - doc["apEnable"] = true; - doc["apIp"] = "192.168.15.1"; - doc["apMask"] = "255.255.255.0"; - doc["apPassword"] = "********"; - doc["stopApTime"] = 0; - doc["cpuSpeed"] = 160; - doc["tempFormat"] = "C"; - doc["ledBrightness"] = led_brightness; - doc["ledRgbBrightness"] = rgb_brightness; - doc["tempFormat"] = "C"; - doc["switchBank"] = 0; - doc["key1"] = keycode[BUTTON_1]; - doc["key2"] = keycode[BUTTON_2]; - doc["key3"] = keycode[BUTTON_3]; - doc["key4"] = keycode[BUTTON_4]; - doc["key5"] = keycode[BUTTON_5]; - doc["key6"] = keycode[BUTTON_6]; - doc["key1long"] = longcode[BUTTON_1]; - doc["key2long"] = longcode[BUTTON_2]; - doc["key3long"] = longcode[BUTTON_3]; - doc["key4long"] = longcode[BUTTON_4]; - doc["key5long"] = longcode[BUTTON_5]; - doc["key6long"] = longcode[BUTTON_6]; - doc["envInterval"] = 5; - String out; - serializeJson(doc, out); - request->send(200, "application/json", out); - }); - server.on("/api/resetconfig", HTTP_GET, [](AsyncWebServerRequest *request){ - StaticJsonDocument<100> doc; - doc["status"] = "FAILED"; - String out; - serializeJson(doc, out); - request->send(200, "application/json", out); - }); - server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){ - StaticJsonDocument<200> doc; - - doc["version"] = VERSION; - - int cpu_freq = esp_clk_cpu_freq() / 1000000; - doc["cpuspeed"] = String(cpu_freq) + "MHz"; - - char ssid[13]; - snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid); - doc["chipid"] = String(ssid); - - doc["uptime"] = uptime_with_unit(); - doc["heap"]=(long)xPortGetFreeHeapSize(); - doc["temp"] = String(temp, 1); - doc["hum"] = String(hum, 1); - doc["status"] = "OK"; - String out; - serializeJson(doc, out); - request->send(200, "application/json", out); - }); - server.on("/api/fwinfo", HTTP_GET, [](AsyncWebServerRequest *request){ - StaticJsonDocument<200> doc; - doc["version"] = VERSION; - doc["build_date"] = BUILD_DATE; - doc["build_time"] = BUILD_TIME; - }); - server.on("/api/setconfig", HTTP_POST, [](AsyncWebServerRequest *request){ - StaticJsonDocument<100> doc; - doc["status"] = "FAILED"; - String out; - serializeJson(doc, out); - request->send(200, "application/json", out); - }); - server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request){ - // the request handler is triggered after the upload has finished... - // create the response, add header, and send response - - StaticJsonDocument<100> doc; - doc["status"] = "FAILED"; - String out; - serializeJson(doc, out); - request->send(200, "application/json", out); - }, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ - // this is the new image upload part - Serial.print("Retrieving firmware image named: "); - Serial.println(filename); - - if (index == 0) { - if (! Update.begin(UPDATE_SIZE_UNKNOWN)) { - Update.printError(Serial); - } - } - if (Update.write(data, len) != len) { - Update.printError(Serial); - } - if (final) { - if (!Update.end(true)) { - Update.printError(Serial); - } - } - - }); - - // TODO POST vom Client entgegennehmen + // Start HTTP Webserver server.begin(); // NMEA2000 configuration diff --git a/src/webserver.cpp b/src/webserver.cpp new file mode 100644 index 0000000..cae2e91 --- /dev/null +++ b/src/webserver.cpp @@ -0,0 +1,190 @@ +#include "main.h" +#include "webserver.h" +#include +#include +#include +#include // for cpu frequency +#include + +AsyncWebServer server(80); + +class EmbeddedFile; +static std::map embeddedFiles; +class EmbeddedFile { + public: + const uint8_t *start; + int len; + String contentType; + EmbeddedFile(String name, String contentType, const uint8_t *start, int len) { + this->start = start; + this->len = len; + this->contentType = contentType; + embeddedFiles[name] = this; + } +}; + +#define EMBED_GZ_FILE(fileName, binName, contentType) \ + extern const uint8_t binName##_File[] asm("_binary_" #binName "_start"); \ + extern const uint8_t binName##_FileLen[] asm("_binary_" #binName "_size"); \ + const EmbeddedFile binName##_Config(fileName,contentType,(const uint8_t*)binName##_File,(int)binName##_FileLen); +#include "embeddedfiles.h" + +void send_embedded_file(String name, AsyncWebServerRequest *request) +{ + std::map::iterator it = embeddedFiles.find(name); + if (it != embeddedFiles.end()) { + EmbeddedFile* found = it->second; + AsyncWebServerResponse *response = request->beginResponse(200, found->contentType, found->start, found->len); + response->addHeader(F("Content-Encoding"), F("gzip")); + request->send(response); + } else { + request->send(404, "text/plain", "Not found"); + } +} + +void webserver_init() { + + // Route for root / web page + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + send_embedded_file("index.html", request); + }); + + // Route for all other defined pages + for (auto it = embeddedFiles.begin(); it != embeddedFiles.end(); it++) { + String uri = String("/") + it->first; + server.on(uri.c_str(), HTTP_GET, [it](AsyncWebServerRequest *request) { + send_embedded_file(it->first, request); + }); + } + + // API fast hack + server.on("/api/capabilities", HTTP_GET, [](AsyncWebServerRequest *request){ + StaticJsonDocument<100> doc; + doc["apPwChange"] = "true"; + String out; + serializeJson(doc, out); + request->send(200, "application/json", out); + }); + + server.on("/api/checkpass", HTTP_GET, [](AsyncWebServerRequest *request){ + StaticJsonDocument<100> doc; + doc["status"] = "FAILED"; + String out; + serializeJson(doc, out); + request->send(200, "application/json", out); + }); + + server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){ + StaticJsonDocument<512> doc; + doc["systemName"] = "Keypad1"; + doc["logLevel"] = 0; + doc["version"] = VERSION; + doc["fwtype"] = "unknown"; + doc["salt"] = "secret"; + doc["AdminPassword"] = "********"; + doc["useAdminPass"] = false; + doc["apEnable"] = true; + doc["apIp"] = "192.168.15.1"; + doc["apMask"] = "255.255.255.0"; + doc["apPassword"] = "********"; + doc["stopApTime"] = 0; + doc["cpuSpeed"] = 160; + doc["tempFormat"] = "C"; + doc["ledBrightness"] = led_brightness; + doc["ledRgbBrightness"] = rgb_brightness; + doc["tempFormat"] = "C"; + doc["switchBank"] = 0; + doc["key1"] = keycode[BUTTON_1]; + doc["key2"] = keycode[BUTTON_2]; + doc["key3"] = keycode[BUTTON_3]; + doc["key4"] = keycode[BUTTON_4]; + doc["key5"] = keycode[BUTTON_5]; + doc["key6"] = keycode[BUTTON_6]; + doc["key1long"] = longcode[BUTTON_1]; + doc["key2long"] = longcode[BUTTON_2]; + doc["key3long"] = longcode[BUTTON_3]; + doc["key4long"] = longcode[BUTTON_4]; + doc["key5long"] = longcode[BUTTON_5]; + doc["key6long"] = longcode[BUTTON_6]; + doc["envInterval"] = 5; + String out; + serializeJson(doc, out); + request->send(200, "application/json", out); + }); + + server.on("/api/resetconfig", HTTP_GET, [](AsyncWebServerRequest *request){ + StaticJsonDocument<100> doc; + doc["status"] = "FAILED"; + String out; + serializeJson(doc, out); + request->send(200, "application/json", out); + }); + + server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){ + StaticJsonDocument<200> doc; + + doc["version"] = VERSION; + + int cpu_freq = esp_clk_cpu_freq() / 1000000; + doc["cpuspeed"] = String(cpu_freq) + "MHz"; + + char ssid[13]; + snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid); + doc["chipid"] = String(ssid); + + doc["uptime"] = uptime_with_unit(); + doc["heap"]=(long)xPortGetFreeHeapSize(); + doc["temp"] = String(temp, 1); + doc["hum"] = String(hum, 1); + doc["status"] = "OK"; + String out; + serializeJson(doc, out); + request->send(200, "application/json", out); + }); + + server.on("/api/fwinfo", HTTP_GET, [](AsyncWebServerRequest *request){ + StaticJsonDocument<200> doc; + doc["version"] = VERSION; + doc["build_date"] = BUILD_DATE; + doc["build_time"] = BUILD_TIME; + }); + + server.on("/api/setconfig", HTTP_POST, [](AsyncWebServerRequest *request){ + StaticJsonDocument<100> doc; + doc["status"] = "FAILED"; + String out; + serializeJson(doc, out); + request->send(200, "application/json", out); + }); + + server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request){ + // the request handler is triggered after the upload has finished... + // create the response, add header, and send response + + StaticJsonDocument<100> doc; + doc["status"] = "FAILED"; + String out; + serializeJson(doc, out); + request->send(200, "application/json", out); + }, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + // this is the new image upload part + Serial.print("Retrieving firmware image named: "); + Serial.println(filename); + + if (index == 0) { + if (! Update.begin(UPDATE_SIZE_UNKNOWN)) { + Update.printError(Serial); + } + } + if (Update.write(data, len) != len) { + Update.printError(Serial); + } + if (final) { + if (!Update.end(true)) { + Update.printError(Serial); + } + } + + }); + +}