diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f9d84d8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "data"] + path = data + url = https://github.com/btclock/oc-webui.git diff --git a/data b/data new file mode 160000 index 0000000..9df3329 --- /dev/null +++ b/data @@ -0,0 +1 @@ +Subproject commit 9df3329847f3939dc1242136e19746613fb83c4b diff --git a/data/build/index.html b/data/build/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/platformio.ini b/platformio.ini index 643b015..d86dcf0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -data_dir = data/build +data_dir = data/build_gz default_envs = lolin_s2_mini_213epd, lolin_s2_mini_29epd, lolin_s3_mini_213epd, lolin_s3_mini_29epd [btclock_base] @@ -19,6 +19,7 @@ monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize board_build.filesystem = littlefs board_build.partitions = partition.csv +extra_scripts = post:scripts/extra_script.py build_flags = !python scripts/git_rev.py -DLAST_BUILD_TIME=$UNIX_TIME diff --git a/scripts/extra_script.py b/scripts/extra_script.py new file mode 100644 index 0000000..4a1c118 --- /dev/null +++ b/scripts/extra_script.py @@ -0,0 +1,38 @@ +Import("env") +import os +import gzip +from shutil import copyfileobj, rmtree +from pathlib import Path + +def gzip_file(input_file, output_file): + with open(input_file, 'rb') as f_in: + with gzip.open(output_file, 'wb') as f_out: + copyfileobj(f_in, f_out) + +def process_directory(input_dir, output_dir): + if os.path.exists(output_dir): + rmtree(output_dir) + for root, dirs, files in os.walk(input_dir): + relative_path = os.path.relpath(root, input_dir) + output_root = os.path.join(output_dir, relative_path) + + Path(output_root).mkdir(parents=True, exist_ok=True) + + for file in files: + # if file.endswith(('.html', '.css', '.js')): + input_file_path = os.path.join(root, file) + output_file_path = os.path.join(output_root, file + '.gz') + gzip_file(input_file_path, output_file_path) + print(f'Compressed: {input_file_path} -> {output_file_path}') + + + +# Build web interface before building FS +def before_buildfs(source, target, env): + env.Execute("cd data && yarn && yarn postinstall && yarn build") + input_directory = 'data/dist' + output_directory = 'data/build_gz' + process_directory(input_directory, output_directory) + +os.environ["PUBLIC_BASE_URL"] = "" +env.AddPreAction("$BUILD_DIR/littlefs.bin", before_buildfs) diff --git a/src/config.cpp b/src/config.cpp index c7eba0f..fd3e90b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -3,7 +3,7 @@ Preferences preferences; const char *ntpServer = "pool.ntp.org"; -const long gmtOffset_sec = 0; +// const long gmtOffset_sec = 0; const int daylightOffset_sec = 3600; TaskHandle_t OTAHandle = NULL; @@ -14,7 +14,7 @@ bool isUpdating = false; void setupTime() { - configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); + configTime(preferences.getInt(SETTING_TIME_OFFSET_MIN), daylightOffset_sec, ntpServer); } void setupPreferences() @@ -48,10 +48,44 @@ void setupPreferences() { preferences.putString(SETTING_MEMPOOL_INSTANCE, "https://mempool.space"); } + + if (!preferences.isKey(SETTING_TIME_FORMAT)) + { + preferences.putString(SETTING_TIME_FORMAT, "%H:%M:%S"); + } + + if (!preferences.isKey(SETTING_DATE_FORMAT)) + { + preferences.putString(SETTING_DATE_FORMAT, "%d-%m-%Y"); + } + + if (!preferences.isKey(SETTING_DECIMAL_SEPARATOR)) + { + preferences.putChar(SETTING_DECIMAL_SEPARATOR, '.'); + } + + if (!preferences.isKey(SETTING_POWER_SAVE_MODE)) + { + preferences.putBool(SETTING_POWER_SAVE_MODE, false); + } + + if (!preferences.isKey(SETTING_TIME_OFFSET_MIN)) + { + preferences.putInt(SETTING_TIME_OFFSET_MIN, 0); + } } void setupWifi() { + uint8_t mac[6]; + WiFi.macAddress(mac); + unsigned long seed = 0; + for (int i = 0; i < 6; i++) + { + seed += (unsigned long)mac[i] << ((i & 1) * 8); + } + randomSeed(seed); + // WiFi.begin(, "); WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); @@ -75,15 +109,10 @@ void setupWifi() } } - byte mac[6]; - WiFi.macAddress(mac); String softAP_SSID = String("OrangeBTClock"); WiFi.setHostname(softAP_SSID.c_str()); - String softAP_password = - base64::encode(String(mac[2], 16) + String(mac[4], 16) + - String(mac[5], 16) + String(mac[1], 16)) - .substring(2, 10); + String softAP_password = getAPPassword(); // wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", 600)); wm.setWiFiAutoReconnect(false); @@ -214,31 +243,36 @@ void setupOTA() ArduinoOTA.begin(); xTaskCreatePinnedToCore( - OTAUpdateTask, // Task function - "OTAUpdateTask", // Task name - 4096, // Stack size - NULL, // Task parameters - 1, // Priority (higher value means higher priority) - &OTAHandle, // Task handle - 0 // Core to run the task (0 or 1) + OTAUpdateTask, // Task function + "OTAUpdateTask", // Task name + 4096, // Stack size + NULL, // Task parameters + 1, // Priority (higher value means higher priority) + &OTAHandle, // Task handle + 0 // Core to run the task (0 or 1) ); } - -void OTAUpdateTask(void *pvParameters) { - for (;;) { - ArduinoOTA.handle(); // Handle OTA updates +void OTAUpdateTask(void *pvParameters) +{ + for (;;) + { + ArduinoOTA.handle(); // Handle OTA updates vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay to avoid high CPU usage } } -char getCurrencyIcon() { +char getCurrencyIcon() +{ char ret; - const char* currency = preferences.getString(SETTING_CURRENCY).c_str(); - if (strcmp(currency, CURRENCY_USD) == 0) { - ret = ICON_DOLLAR; - } else if(strcmp(currency, CURRENCY_EUR) == 0) { - ret = ICON_EURO; + const char *currency = preferences.getString(SETTING_CURRENCY).c_str(); + if (strcmp(currency, CURRENCY_USD) == 0) + { + ret = ICON_DOLLAR; + } + else if (strcmp(currency, CURRENCY_EUR) == 0) + { + ret = ICON_EURO; } // break; // case CURRENCY_GBP: diff --git a/src/main.cpp b/src/main.cpp index 3b111b2..f136a51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,6 +59,7 @@ void loop() { if (isUpdating) { + delay(1000); return; } @@ -135,22 +136,22 @@ void loop() { icon = getCurrencyIcon(); int64_t marketCap = static_cast(getSupplyAtBlock(getBlock()) * double(getPrice())); - rowContent = String(formatNumberWithSuffix(marketCap, 4)); + rowContent = String(formatNumberWithSuffix(marketCap, 8)); break; } case LINE_TIME: { icon = ICON_GLOBE; - char dateString[10]; - strftime(dateString, 10, "%H:%M:%S", &timeinfo); + char dateString[16]; + strftime(dateString, sizeof(dateString), preferences.getString(SETTING_TIME_FORMAT).c_str(), &timeinfo); rowContent = dateString; break; } case LINE_DATE: { icon = ICON_GLOBE; - char dateString[10]; - strftime(dateString, 10, "%x", &timeinfo); + char dateString[16]; + strftime(dateString, sizeof(dateString), preferences.getString(SETTING_DATE_FORMAT).c_str(), &timeinfo); rowContent = dateString; break; } diff --git a/src/shared.cpp b/src/shared.cpp index 3258a71..2547a42 100644 --- a/src/shared.cpp +++ b/src/shared.cpp @@ -1,13 +1,2 @@ #include "shared.hpp" -String getMyHostname() -{ - uint8_t mac[6]; - // WiFi.macAddress(mac); - esp_efuse_mac_get_default(mac); - char hostname[15]; - String hostnamePrefix = preferences.getString(SETTING_HOSTNAME_PREFIX); - snprintf(hostname, sizeof(hostname), "%s-%02x%02x%02x", hostnamePrefix, - mac[3], mac[4], mac[5]); - return hostname; -} \ No newline at end of file diff --git a/src/shared.hpp b/src/shared.hpp index fff9a85..ec281c6 100644 --- a/src/shared.hpp +++ b/src/shared.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "utils.hpp" #include "fonts/fonts.hpp" #ifdef VERSION_EPD_2_13 @@ -62,6 +63,12 @@ #define SETTING_CURRENCY "currency" #define SETTING_HOSTNAME_PREFIX "hostnamePrefix" #define SETTING_MEMPOOL_INSTANCE "mempoolInstance" +#define SETTING_POWER_SAVE_MODE "powerSaveMode" +#define SETTING_TIME_OFFSET_MIN "timeOffsetMin" +#define SETTING_DECIMAL_SEPARATOR "decSeparator" +#define SETTING_TIME_FORMAT "timeFormat" +#define SETTING_DATE_FORMAT "dateFormat" + const int LINE_BLOCKHEIGHT = 0; const int LINE_MEMPOOL_FEES = 1; @@ -94,4 +101,3 @@ extern char currentIcon1; extern char currentIcon2; extern char currentIcon3; -String getMyHostname(); diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..38731ea --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,32 @@ +#include "utils.hpp" + +String getAPPassword() +{ + byte mac[6]; + WiFi.macAddress(mac); + const char charset[] = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789"; + char password[9]; // 8 characters + null terminator + snprintf(password, sizeof(password), "%c%c%c%c%c%c%c%c", + charset[mac[0] % (sizeof(charset) - 1)], + charset[mac[1] % (sizeof(charset) - 1)], + charset[mac[2] % (sizeof(charset) - 1)], + charset[mac[3] % (sizeof(charset) - 1)], + charset[mac[4] % (sizeof(charset) - 1)], + charset[mac[5] % (sizeof(charset) - 1)], + charset[(mac[0] + mac[1] + mac[2] + mac[3] + mac[4] + mac[5]) % (sizeof(charset) - 1)], + charset[(mac[0] * mac[1] * mac[2] * mac[3] * mac[4] * mac[5]) % (sizeof(charset) - 1)]); + + return password; +} + +String getMyHostname() +{ + uint8_t mac[6]; + // WiFi.macAddress(mac); + esp_efuse_mac_get_default(mac); + char hostname[15]; + String hostnamePrefix = preferences.getString(SETTING_HOSTNAME_PREFIX); + snprintf(hostname, sizeof(hostname), "%s-%02x%02x%02x", hostnamePrefix, + mac[3], mac[4], mac[5]); + return hostname; +} \ No newline at end of file diff --git a/src/utils.hpp b/src/utils.hpp new file mode 100644 index 0000000..8c32d47 --- /dev/null +++ b/src/utils.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include "shared.hpp" + +namespace ArduinoJson { +template <> +struct Converter { + static void toJson(char c, JsonVariant var) { + var.set(static_cast(c)); + } + + static char fromJson(JsonVariantConst src) { + return static_cast(src.as()); + } + + static bool checkJson(JsonVariantConst src) { + return src.is(); + } +}; +} + + +String getAPPassword(); +String getMyHostname(); diff --git a/src/webserver.cpp b/src/webserver.cpp index 159b85b..758d4b7 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -3,8 +3,10 @@ AsyncWebServer server(80); const String uintSettings[] = {SETTING_ROW1_CONTENT, SETTING_ROW2_CONTENT, SETTING_ROW3_CONTENT}; -const String stringSettings[] = {SETTING_CURRENCY,SETTING_MEMPOOL_INSTANCE}; -const String boolSettings[] = {}; +const String intSettings[] = {SETTING_TIME_OFFSET_MIN}; +const String stringSettings[] = {SETTING_CURRENCY, SETTING_MEMPOOL_INSTANCE, SETTING_TIME_FORMAT, SETTING_DATE_FORMAT}; +const String charSettings[] = {SETTING_DECIMAL_SEPARATOR}; +const String boolSettings[] = {SETTING_POWER_SAVE_MODE}; void setupWebserver() { @@ -68,11 +70,21 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root[setting] = preferences.getUInt(setting.c_str()); } + for (String setting : intSettings) + { + root[setting] = preferences.getInt(setting.c_str()); + } + for (String setting : stringSettings) { root[setting] = preferences.getString(setting.c_str()); } + for (String setting : charSettings) + { + root[setting] = preferences.getChar(setting.c_str()); + } + for (String setting : boolSettings) { root[setting] = preferences.getBool(setting.c_str()); @@ -110,6 +122,16 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } + for (String setting : intSettings) + { + if (settings.containsKey(setting)) + { + preferences.putInt(setting.c_str(), settings[setting].as()); + Serial.printf("Setting %s to %d\r\n", setting.c_str(), + settings[setting].as()); + } + } + for (String setting : stringSettings) { if (settings.containsKey(setting)) @@ -120,6 +142,16 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } + for (String setting : charSettings) + { + if (settings.containsKey(setting)) + { + preferences.putChar(setting.c_str(), settings[setting].as()); + Serial.printf("Setting %s to %s\r\n", setting.c_str(), + settings[setting].as()); + } + } + for (String setting : boolSettings) { if (settings.containsKey(setting))