From e4a39de5fc8a91f17878cb98d65831a71ff46fee Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 31 Jan 2024 23:45:26 +0100 Subject: [PATCH 001/188] Fix lost data connection detection --- src/lib/block_notify.cpp | 12 ++++++++++++ src/lib/block_notify.hpp | 3 ++- src/lib/led_handler.cpp | 6 ++++++ src/lib/led_handler.hpp | 5 +++++ src/lib/price_notify.cpp | 11 ++++++++++- src/lib/price_notify.hpp | 3 ++- src/lib/webserver.cpp | 26 ++++++++++++++++++++++++++ src/lib/webserver.hpp | 5 ++++- src/main.cpp | 19 +++++++++++++------ 9 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 10900e8..56a2df2 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -3,6 +3,7 @@ char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; uint currentBlockHeight = 816000; +bool blockNotifyInit = false; // const char *mempoolWsCert = R"(-----BEGIN CERTIFICATE----- // MIIHfTCCBmWgAwIBAgIRANFX3mhqRYDt1NFuENoSyaAwDQYJKoZIhvcNAQELBQAw @@ -105,6 +106,8 @@ void onWebsocketEvent(void *handler_args, esp_event_base_t base, const String sub = "{\"action\": \"want\", \"data\":[\"blocks\"]}"; switch (event_id) { case WEBSOCKET_EVENT_CONNECTED: + blockNotifyInit = true; + Serial.println(F("Connected to Mempool.space WebSocket")); Serial.println(sub); @@ -181,7 +184,16 @@ bool isBlockNotifyConnected() { return esp_websocket_client_is_connected(blockNotifyClient); } +bool getBlockNotifyInit() { + return blockNotifyInit; +} + void stopBlockNotify() { + if (blockNotifyClient == NULL) return; + + esp_websocket_client_close(blockNotifyClient, portMAX_DELAY); esp_websocket_client_stop(blockNotifyClient); esp_websocket_client_destroy(blockNotifyClient); + + blockNotifyClient = NULL; } \ No newline at end of file diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index 3603846..f0d903d 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -24,4 +24,5 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data); void setBlockHeight(uint newBlockHeight); uint getBlockHeight(); bool isBlockNotifyConnected(); -void stopBlockNotify(); \ No newline at end of file +void stopBlockNotify(); +bool getBlockNotifyInit(); \ No newline at end of file diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index f07fbc2..56b9b69 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -32,6 +32,12 @@ void ledTask(void *parameter) { case LED_EFFECT_HEARTBEAT: blinkDelayColor(150, 2, 0, 0, 255); break; + case LED_DATA_BLOCK_ERROR: + blinkDelayColor(150, 2, 128, 0, 128); + break; + case LED_DATA_PRICE_ERROR: + blinkDelayColor(150, 2, 177, 90, 31); + break; case LED_EFFECT_WIFI_CONNECT_SUCCESS: case LED_FLASH_SUCCESS: blinkDelayColor(150, 3, 0, 255, 0); diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index c5b3780..8e064f5 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -27,10 +27,15 @@ const int LED_EFFECT_WIFI_CONNECTING = 101; const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103; const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104; + const int LED_PROGRESS_25 = 200; const int LED_PROGRESS_50 = 201; const int LED_PROGRESS_75 = 202; const int LED_PROGRESS_100 = 203; + +const int LED_DATA_PRICE_ERROR = 300; +const int LED_DATA_BLOCK_ERROR = 301; + const int LED_POWER_TEST = 999; extern TaskHandle_t ledTaskHandle; extern Adafruit_NeoPixel pixels; diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 00ff2a7..a380942 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -51,7 +51,6 @@ void setupPriceNotify() { esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, onWebsocketPriceEvent, clientPrice); esp_websocket_client_start(clientPrice); - priceNotifyInit = true; } void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, @@ -61,6 +60,8 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, switch (event_id) { case WEBSOCKET_EVENT_CONNECTED: Serial.println(F("Connected to CoinCap.io WebSocket")); + priceNotifyInit = true; + break; case WEBSOCKET_EVENT_DATA: onWebsocketPriceMessage(data); @@ -113,7 +114,15 @@ bool isPriceNotifyConnected() { return esp_websocket_client_is_connected(clientPrice); } +bool getPriceNotifyInit() { + return priceNotifyInit; +} + void stopPriceNotify() { + if (clientPrice == NULL) return; + esp_websocket_client_close(clientPrice, portMAX_DELAY); esp_websocket_client_stop(clientPrice); esp_websocket_client_destroy(clientPrice); + + clientPrice = NULL; } \ No newline at end of file diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index d320b2a..8bcb207 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -18,4 +18,5 @@ uint getPrice(); void setPrice(uint newPrice); bool isPriceNotifyConnected(); -void stopPriceNotify(); \ No newline at end of file +void stopPriceNotify(); +bool getPriceNotifyInit(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 8bb1a73..bc57f06 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -24,6 +24,10 @@ void setupWebserver() { server.on("/api/full_refresh", HTTP_GET, onApiFullRefresh); + server.on("/api/stop_datasources", HTTP_GET, onApiStopDataSources); + server.on("/api/restart_datasources", HTTP_GET, onApiRestartDataSources); + + server.on("/api/action/pause", HTTP_GET, onApiActionPause); server.on("/api/action/timer_restart", HTTP_GET, onApiActionTimerRestart); @@ -694,6 +698,28 @@ void onApiLightsStatus(AsyncWebServerRequest *request) { request->send(response); } +void onApiStopDataSources(AsyncWebServerRequest *request) { + AsyncResponseStream *response = + request->beginResponseStream("application/json"); + + stopPriceNotify(); + stopBlockNotify(); + + request->send(response); +} + +void onApiRestartDataSources(AsyncWebServerRequest *request) { + AsyncResponseStream *response = + request->beginResponseStream("application/json"); + + stopPriceNotify(); + stopBlockNotify(); + setupPriceNotify(); + setupBlockNotify(); + + request->send(response); +} + void onApiLightsOff(AsyncWebServerRequest *request) { setLights(0, 0, 0); request->send(200); diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index f9dc01f..f8afa98 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -49,4 +49,7 @@ void onNotFound(AsyncWebServerRequest *request); StaticJsonDocument<512> getLedStatusObject(); StaticJsonDocument<768> getStatusObject(); void eventSourceUpdate(); -void eventSourceTask(void *pvParameters); \ No newline at end of file +void eventSourceTask(void *pvParameters); + +void onApiStopDataSources(AsyncWebServerRequest *request); +void onApiRestartDataSources(AsyncWebServerRequest *request); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index de96dc8..012bf5f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,20 +55,27 @@ extern "C" void app_main() { } else if (wifiLostConnection) { wifiLostConnection = 0; Serial.println("Connection restored, reset timer."); - } else if (preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) { + } else if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) { priceNotifyLostConnection++; + Serial.println("Lost price data connection..."); + queueLedEffect(LED_DATA_PRICE_ERROR); + + // if price WS connection does not come back after 6*5 seconds, destroy and recreate + if (priceNotifyLostConnection > 6) { + Serial.println("Restarting price handler..."); - // if price WS connection does not come back after 60 seconds, destroy and recreate - if (priceNotifyLostConnection > 12) { stopPriceNotify(); setupPriceNotify(); priceNotifyLostConnection = 0; } - } else if (!isBlockNotifyConnected()) { + } else if (getBlockNotifyInit() && !isBlockNotifyConnected()) { blockNotifyLostConnection++; + Serial.println("Lost block data connection..."); + queueLedEffect(LED_DATA_BLOCK_ERROR); + // if mempool WS connection does not come back after 6*5 seconds, destroy and recreate + if (blockNotifyLostConnection > 6) { + Serial.println("Restarting block handler..."); - // if mempool WS connection does not come back after 60 seconds, destroy and recreate - if (blockNotifyLostConnection > 12) { stopBlockNotify(); setupBlockNotify(); blockNotifyLostConnection = 0; From c49b8edcb8264a55876a33bbe20c2a4a10fa4b7e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 10 Mar 2024 12:35:20 +0100 Subject: [PATCH 002/188] Add sats symbol option, add countdown in blocks, add decimal point for market cap, add hostname to setup screen --- data | 2 +- dependencies.lock | 4 +- lib/btclock/data_handler.cpp | 61 +++- lib/btclock/data_handler.hpp | 4 +- lib/btclock/utils.cpp | 75 +++-- lib/btclock/utils.hpp | 4 +- src/fonts/antonio-semibold90.h | 2 +- src/fonts/fonts.hpp | 2 + src/fonts/sats-symbol.h | 201 ++++++++++++ src/lib/block_notify.cpp | 3 +- src/lib/config.cpp | 474 ++++++++++++++++------------ src/lib/epd.cpp | 247 +++++++++++---- src/lib/screen_handler.cpp | 4 +- src/lib/webserver.cpp | 9 +- test/test_datahandler/test_main.cpp | 51 ++- 15 files changed, 822 insertions(+), 321 deletions(-) create mode 100644 src/fonts/sats-symbol.h diff --git a/data b/data index 3f20d67..1b8ab93 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 3f20d67f1abc10b20ddecfb5aa0ff4eb78c4c149 +Subproject commit 1b8ab93da64dd7383a2fb34c967b75fdab072718 diff --git a/dependencies.lock b/dependencies.lock index 7d35282..345aa6f 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -1,11 +1,11 @@ dependencies: esp_littlefs: - component_hash: 0e4812e62ac02a17f2b6a2bb3b6daf9b39e9599a6e319c745ef2a2817c15a073 + component_hash: 66d5afe7ad323d0159d04e296c14ffa0e39156502bc15e0520eff2af392d3aa4 source: git: https://github.com/joltwallet/esp_littlefs.git path: . type: git - version: 5a0a9a0a39549ac55fd51d55bd3f895ffe700a0a + version: 41873c20fb5cdbcf28d7d6cc04e4bcb4a1305317 idf: component_hash: null source: diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 28f13d0..8719626 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -5,7 +5,7 @@ std::array parsePriceData(std::uint32_t price, char cu std::array ret; std::string priceString; if (std::to_string(price).length() >= NUM_SCREENS) { - priceString = formatNumberWithSuffix(price); + priceString = formatNumberWithSuffix(price, NUM_SCREENS-2); } else { priceString = currencySymbol + std::to_string(price); } @@ -32,15 +32,18 @@ std::array parsePriceData(std::uint32_t price, char cu return ret; } -std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol) +std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol) { std::array ret; std::string priceString = std::to_string(int(round(1 / float(price) * 10e7))); std::uint32_t firstIndex = 0; + uint insertSatSymbol = NUM_SCREENS - priceString.length() - 1; if (priceString.length() < (NUM_SCREENS)) { priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); + + if (currencySymbol == '[') { ret[0] = "SATS/EUR"; @@ -55,6 +58,10 @@ std::array parseSatsPerCurrency(std::uint32_t price, c { ret[i] = priceString[i]; } + + if (withSatsSymbol) { + ret[insertSatSymbol] = "STS"; + } } return ret; } @@ -80,24 +87,43 @@ std::array parseBlockHeight(std::uint32_t blockHeight) return ret; } -std::array parseHalvingCountdown(std::uint32_t blockHeight) +std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks) { std::array ret; - const std::uint32_t nextHalvingBlock = 210000 - (blockHeight % 210000); const std::uint32_t minutesToHalving = nextHalvingBlock * 10; - const int years = floor(minutesToHalving / 525600); - const int days = floor((minutesToHalving - (years * 525600)) / (24 * 60)); - const int hours = floor((minutesToHalving - (years * 525600) - (days * (24 * 60))) / 60); - const int mins = floor(minutesToHalving - (years * 525600) - (days * (24 * 60)) - (hours * 60)); - ret[0] = "BIT/COIN"; - ret[1] = "HALV/ING"; - ret[(NUM_SCREENS - 5)] = std::to_string(years) + "/YRS"; - ret[(NUM_SCREENS - 4)] = std::to_string(days) + "/DAYS"; - ret[(NUM_SCREENS - 3)] = std::to_string(hours) + "/HRS"; - ret[(NUM_SCREENS - 2)] = std::to_string(mins) + "/MINS"; - ret[(NUM_SCREENS - 1)] = "TO/GO"; + if (asBlocks) { + std::string blockNrString = std::to_string(nextHalvingBlock); + std::uint32_t firstIndex = 0; + + if (blockNrString.length() < NUM_SCREENS) + { + blockNrString.insert(blockNrString.begin(), NUM_SCREENS - blockNrString.length(), ' '); + ret[0] = "HAL/VING"; + firstIndex = 1; + } + + for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++) + { + ret[i] = blockNrString[i]; + } + + } else { + + + const int years = floor(minutesToHalving / 525600); + const int days = floor((minutesToHalving - (years * 525600)) / (24 * 60)); + const int hours = floor((minutesToHalving - (years * 525600) - (days * (24 * 60))) / 60); + const int mins = floor(minutesToHalving - (years * 525600) - (days * (24 * 60)) - (hours * 60)); + ret[0] = "BIT/COIN"; + ret[1] = "HAL/VING"; + ret[(NUM_SCREENS - 5)] = std::to_string(years) + "/YRS"; + ret[(NUM_SCREENS - 4)] = std::to_string(days) + "/DAYS"; + ret[(NUM_SCREENS - 3)] = std::to_string(hours) + "/HRS"; + ret[(NUM_SCREENS - 2)] = std::to_string(mins) + "/MINS"; + ret[(NUM_SCREENS - 1)] = "TO/GO"; + } return ret; } @@ -120,8 +146,9 @@ std::array parseMarketCap(std::uint32_t blockHeight, s if (bigChars) { firstIndex = 1; - - std::string priceString = currencySymbol + formatNumberWithSuffix(marketCap); + // Serial.print("Market cap: "); + // Serial.println(marketCap); + std::string priceString = currencySymbol + formatNumberWithSuffix(marketCap, (NUM_SCREENS-2)); priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++) diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index 3ac52c6..d385cef 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -6,7 +6,7 @@ #include "utils.hpp" std::array parsePriceData(std::uint32_t price, char currencySymbol); -std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol); +std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array parseBlockHeight(std::uint32_t blockHeight); -std::array parseHalvingCountdown(std::uint32_t blockHeight); +std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks); std::array parseMarketCap(std::uint32_t blockHeight, std::uint32_t price, char currencySymbol, bool bigChars); \ No newline at end of file diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index 2234ebb..b801fc2 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -5,18 +5,21 @@ int modulo(int x, int N) return (x % N + N) % N; } -double getSupplyAtBlock(std::uint32_t blockNr) { - if (blockNr >= 33 * 210000) { +double getSupplyAtBlock(std::uint32_t blockNr) +{ + if (blockNr >= 33 * 210000) + { return 20999999.9769; - } + } const int initialBlockReward = 50; // Initial block reward - const int halvingInterval = 210000; // Number of blocks before halving + const int halvingInterval = 210000; // Number of blocks before halving int halvingCount = blockNr / halvingInterval; double totalBitcoinInCirculation = 0; - for (int i = 0; i < halvingCount; ++i) { + for (int i = 0; i < halvingCount; ++i) + { totalBitcoinInCirculation += halvingInterval * initialBlockReward * std::pow(0.5, i); } @@ -25,24 +28,58 @@ double getSupplyAtBlock(std::uint32_t blockNr) { return totalBitcoinInCirculation; } -std::string formatNumberWithSuffix(std::uint64_t num) { +std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters) +{ + static char result[20]; // Adjust size as needed const long long quadrillion = 1000000000000000LL; const long long trillion = 1000000000000LL; const long long billion = 1000000000; const long long million = 1000000; const long long thousand = 1000; - if (num >= quadrillion) { - return std::to_string(num / quadrillion) + "Q"; - } else if (num >= trillion) { - return std::to_string(num / trillion) + "T"; - } else if (num >= billion) { - return std::to_string(num / billion) + "B"; - } else if (num >= million) { - return std::to_string(num / million) + "M"; - } else if (num >= thousand) { - return std::to_string(num / thousand) + "K"; - } else { - return std::to_string(num); + double numDouble = (double)num; + int numDigits = (int)log10(num) + 1; + char suffix; + + if (num >= quadrillion || numDigits > 15) + { + numDouble /= quadrillion; + suffix = 'Q'; } -} + else if (num >= trillion || numDigits > 12) + { + numDouble /= trillion; + suffix = 'T'; + } + else if (num >= billion || numDigits > 9) + { + numDouble /= billion; + suffix = 'B'; + } + else if (num >= million || numDigits > 6) + { + numDouble /= million; + suffix = 'M'; + } + else if (num >= thousand || numDigits > 3) + { + numDouble /= thousand; + suffix = 'K'; + } + else + { + sprintf(result, "%llu", (unsigned long long)num); + return result; + } + + // Add suffix + int len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix); + + // If there's room, add decimal places + if (len < numCharacters) + { + snprintf(result, sizeof(result), "%.*f%c", numCharacters - len - 1, numDouble, suffix); + } + + return result; +} \ No newline at end of file diff --git a/lib/btclock/utils.hpp b/lib/btclock/utils.hpp index b44c978..db338c0 100644 --- a/lib/btclock/utils.hpp +++ b/lib/btclock/utils.hpp @@ -3,9 +3,11 @@ #include #include #include +#include +#include int modulo(int x,int N); double getSupplyAtBlock(std::uint32_t blockNr); -std::string formatNumberWithSuffix(std::uint64_t num); \ No newline at end of file +std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4); \ No newline at end of file diff --git a/src/fonts/antonio-semibold90.h b/src/fonts/antonio-semibold90.h index 123e4ab..b55d5df 100644 --- a/src/fonts/antonio-semibold90.h +++ b/src/fonts/antonio-semibold90.h @@ -5046,7 +5046,7 @@ const GFXglyph Antonio_SemiBold90pt7bGlyphs[] PROGMEM = { {9988, 53, 55, 63, 5, -94}, // 0x2B '+' {10353, 23, 50, 41, 10, -20}, // 0x2C ',' {10497, 39, 14, 60, 8, -75}, // 0x2D '-' - {10566, 17, 19, 46, 14, -18}, // 0x2E '.' + {10566, 17, 19, 46, 14, 106}, // 0x2E '.' {10607, 55, 152, 66, 6, 105}, // 0x2F '/' {11652, 59, 155, 79, 10, 104}, // 0x30 '0' {12796, 41, 151, 67, 8, 106}, // 0x31 '1' diff --git a/src/fonts/fonts.hpp b/src/fonts/fonts.hpp index 01ef327..1504d45 100644 --- a/src/fonts/fonts.hpp +++ b/src/fonts/fonts.hpp @@ -4,6 +4,8 @@ #include "antonio-semibold30.h" #include "antonio-semibold40.h" #include "antonio-semibold90.h" +#include "sats-symbol.h" + // #include "oswald-20.h" // #include "oswald-30.h" // #include "oswald-90.h" diff --git a/src/fonts/sats-symbol.h b/src/fonts/sats-symbol.h new file mode 100644 index 0000000..d21a9a5 --- /dev/null +++ b/src/fonts/sats-symbol.h @@ -0,0 +1,201 @@ +const uint8_t Satoshi_Symbol90pt7bBitmaps[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, + 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE0 }; + +const GFXglyph Satoshi_Symbol90pt7bGlyphs[] PROGMEM = { + { 0, 82, 127, 99, 8, -126 }, { 1302, 71, 109, 93, 0, -117 } }; // 0x53 'S' + +const GFXfont Satoshi_Symbol90pt7b PROGMEM = { + (uint8_t *)Satoshi_Symbol90pt7bBitmaps, + (GFXglyph *)Satoshi_Symbol90pt7bGlyphs, + 0x53, 0x53, 192 }; + +// Approx. 2284 bytes diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 56a2df2..897d7dd 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -162,8 +162,7 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data) { } } - if (getCurrentScreen() == SCREEN_BLOCK_HEIGHT && - preferences.getBool("ledFlashOnUpd", false)) { + if (preferences.getBool("ledFlashOnUpd", false)) { vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated queueLedEffect(LED_FLASH_BLOCK_NOTIFY); } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 09cf4fc..068f8fb 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -10,16 +10,19 @@ Adafruit_MCP23X17 mcp2; std::vector screenNameMap(SCREEN_COUNT); std::mutex mcpMutex; -void setup() { +void setup() +{ setupPreferences(); setupHardware(); setupDisplays(); - if (preferences.getBool("ledTestOnPower", true)) { + if (preferences.getBool("ledTestOnPower", true)) + { queueLedEffect(LED_POWER_TEST); } { std::lock_guard lockMcp(mcpMutex); - if (mcp1.digitalRead(3) == LOW) { + if (mcp1.digitalRead(3) == LOW) + { preferences.putBool("wifiConfigured", false); preferences.remove("txPower"); @@ -49,12 +52,25 @@ void setup() { forceFullRefresh(); } -void tryImprovSetup() { +void tryImprovSetup() +{ WiFi.onEvent(WiFiEvent); + WiFi.setAutoConnect(true); + WiFi.setAutoReconnect(true); + WiFi.begin(); + if (preferences.getInt("txPower", 0)) + { + if (WiFi.setTxPower( + static_cast(preferences.getInt("txPower", 0)))) + { + Serial.printf("WiFi max tx power set to %d\n", + preferences.getInt("txPower", 0)); + } + } + + // if (!preferences.getBool("wifiConfigured", false)) + { - if (!preferences.getBool("wifiConfigured", false)) { - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); uint8_t x_buffer[16]; @@ -84,13 +100,15 @@ void tryImprovSetup() { wm.setDebugOutput(false); wm.setConfigPortalBlocking(true); - wm.setAPCallback([&](WiFiManager *wifiManager) { + wm.setAPCallback([&](WiFiManager *wifiManager) + { // Serial.printf("Entered config mode:ip=%s, ssid='%s', pass='%s'\n", // WiFi.softAPIP().toString().c_str(), // wifiManager->getConfigPortalSSID().c_str(), // softAP_password.c_str()); // delay(6000); - + setFgColor(GxEPD_BLACK); + setBgColor(GxEPD_WHITE); const String qrText = "qrWIFI:S:" + wifiManager->getConfigPortalSSID() + ";T:WPA;P:" + softAP_password.c_str() + ";;"; const String explainText = "*SSID: *\r\n" + @@ -102,18 +120,17 @@ void tryImprovSetup() { "To setup\r\nscan QR or\r\nconnect\r\nmanually", "Para\r\nconfigurar\r\nescanear QR\r\no conectar\r\nmanualmente", explainText, - " ", + "*Hostname*:\r\n" + getMyHostname(), qrText}; - setEpdContent(epdContent); - }); + setEpdContent(epdContent); }); - wm.setSaveConfigCallback([]() { + wm.setSaveConfigCallback([]() + { preferences.putBool("wifiConfigured", true); delay(1000); // just restart after succes - ESP.restart(); - }); + ESP.restart(); }); bool ac = wm.autoConnect(softAP_SSID.c_str(), softAP_password.c_str()); @@ -154,31 +171,26 @@ void tryImprovSetup() { } setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); - } else { - WiFi.setAutoConnect(true); - WiFi.setAutoReconnect(true); - WiFi.begin(); - if (preferences.getInt("txPower", 0)) { - if (WiFi.setTxPower( - static_cast(preferences.getInt("txPower", 0)))) { - Serial.printf("WiFi max tx power set to %d\n", - preferences.getInt("txPower", 0)); - } - } - - while (WiFi.status() != WL_CONNECTED) { - vTaskDelay(pdMS_TO_TICKS(400)); - } } + // else + // { + + // while (WiFi.status() != WL_CONNECTED) + // { + // vTaskDelay(pdMS_TO_TICKS(400)); + // } + // } // queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); } -void setupTime() { +void setupTime() +{ configTime(preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS), 0, NTP_SERVER); struct tm timeinfo; - while (!getLocalTime(&timeinfo)) { + while (!getLocalTime(&timeinfo)) + { configTime(preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS), 0, NTP_SERVER); delay(500); @@ -186,7 +198,8 @@ void setupTime() { } } -void setupPreferences() { +void setupPreferences() +{ preferences.begin("btclock", false); setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); @@ -202,36 +215,46 @@ void setupPreferences() { screenNameMap[SCREEN_MARKET_CAP] = "Market Cap"; } -void setupWebsocketClients(void *pvParameters) { +void setupWebsocketClients(void *pvParameters) +{ setupBlockNotify(); - if (preferences.getBool("fetchEurPrice", false)) { + if (preferences.getBool("fetchEurPrice", false)) + { setupPriceFetchTask(); - } else { + } + else + { setupPriceNotify(); } vTaskDelete(NULL); } -void setupTimers() { +void setupTimers() +{ xTaskCreate(setupTimeUpdateTimer, "setupTimeUpdateTimer", 2048, NULL, tskIDLE_PRIORITY, NULL); xTaskCreate(setupScreenRotateTimer, "setupScreenRotateTimer", 2048, NULL, tskIDLE_PRIORITY, NULL); } -void finishSetup() { - if (preferences.getBool("ledStatus", false)) { +void finishSetup() +{ + if (preferences.getBool("ledStatus", false)) + { restoreLedState(); - } else { + } + else + { clearLeds(); } } std::vector getScreenNameMap() { return screenNameMap; } -void setupMcp() { +void setupMcp() +{ #ifdef IS_BTCLOCK_S3 const int mcp1AddrPins[] = {MCP1_A0_PIN, MCP1_A1_PIN, MCP1_A2_PIN}; const int mcp1AddrValues[] = {LOW, LOW, LOW}; @@ -242,7 +265,8 @@ void setupMcp() { pinMode(MCP_RESET_PIN, OUTPUT); digitalWrite(MCP_RESET_PIN, HIGH); - for (int i = 0; i < 3; ++i) { + for (int i = 0; i < 3; ++i) + { pinMode(mcp1AddrPins[i], OUTPUT); digitalWrite(mcp1AddrPins[i], mcp1AddrValues[i]); @@ -256,19 +280,23 @@ void setupMcp() { #endif } -void setupHardware() { - if (!LittleFS.begin(true)) { +void setupHardware() +{ + if (!LittleFS.begin(true)) + { Serial.println(F("An Error has occurred while mounting LittleFS")); } - if (!LittleFS.open("/index.html.gz", "r")) { + if (!LittleFS.open("/index.html.gz", "r")) + { Serial.println("Error loading WebUI"); } setupLeds(); WiFi.setHostname(getMyHostname().c_str()); - if (!psramInit()) { + if (!psramInit()) + { Serial.println(F("PSRAM not available")); } @@ -276,28 +304,34 @@ void setupHardware() { Wire.begin(I2C_SDA_PIN, I2C_SCK_PIN, 400000); - if (!mcp1.begin_I2C(0x20)) { + if (!mcp1.begin_I2C(0x20)) + { Serial.println(F("Error MCP23017")); // while (1) // ; - } else { + } + else + { pinMode(MCP_INT_PIN, INPUT_PULLUP); mcp1.setupInterrupts(false, false, LOW); - for (int i = 0; i < 4; i++) { + for (int i = 0; i < 4; i++) + { mcp1.pinMode(i, INPUT_PULLUP); mcp1.setupInterruptPin(i, LOW); } #ifndef IS_BTCLOCK_S3 - for (int i = 8; i <= 14; i++) { + for (int i = 8; i <= 14; i++) + { mcp1.pinMode(i, OUTPUT); } #endif } #ifdef IS_BTCLOCK_S3 - if (!mcp2.begin_I2C(0x21)) { + if (!mcp2.begin_I2C(0x21)) + { Serial.println(F("Error MCP23017")); // while (1) @@ -306,10 +340,12 @@ void setupHardware() { #endif } -void improvGetAvailableWifiNetworks() { +void improvGetAvailableWifiNetworks() +{ int networkNum = WiFi.scanNetworks(); - for (int id = 0; id < networkNum; ++id) { + for (int id = 0; id < networkNum; ++id) + { std::vector data = improv::build_rpc_response( improv::GET_WIFI_NETWORKS, {WiFi.SSID(id), String(WiFi.RSSI(id)), @@ -323,15 +359,18 @@ void improvGetAvailableWifiNetworks() { improv_send_response(data); } -bool improv_connectWifi(std::string ssid, std::string password) { +bool improv_connectWifi(std::string ssid, std::string password) +{ uint8_t count = 0; WiFi.begin(ssid.c_str(), password.c_str()); - while (WiFi.status() != WL_CONNECTED) { + while (WiFi.status() != WL_CONNECTED) + { blinkDelay(500, 2); - if (count > MAX_ATTEMPTS_WIFI_CONNECTION) { + if (count > MAX_ATTEMPTS_WIFI_CONNECTION) + { WiFi.disconnect(); return false; } @@ -341,7 +380,8 @@ bool improv_connectWifi(std::string ssid, std::string password) { return true; } -void onImprovErrorCallback(improv::Error err) { +void onImprovErrorCallback(improv::Error err) +{ blinkDelayColor(100, 1, 255, 0, 0); // pixels.setPixelColor(0, pixels.Color(255, 0, 0)); // pixels.setPixelColor(1, pixels.Color(255, 0, 0)); @@ -355,94 +395,110 @@ void onImprovErrorCallback(improv::Error err) { // vTaskDelay(pdMS_TO_TICKS(100)); } -std::vector getLocalUrl() { +std::vector getLocalUrl() +{ return {// URL where user can finish onboarding or use device // Recommended to use website hosted by device String("http://" + WiFi.localIP().toString()).c_str()}; } -bool onImprovCommandCallback(improv::ImprovCommand cmd) { - switch (cmd.command) { - case improv::Command::GET_CURRENT_STATE: { - if ((WiFi.status() == WL_CONNECTED)) { - improv_set_state(improv::State::STATE_PROVISIONED); - std::vector data = improv::build_rpc_response( - improv::GET_CURRENT_STATE, getLocalUrl(), false); - improv_send_response(data); - } else { - improv_set_state(improv::State::STATE_AUTHORIZED); - } - - break; - } - - case improv::Command::WIFI_SETTINGS: { - if (cmd.ssid.length() == 0) { - improv_set_error(improv::Error::ERROR_INVALID_RPC); - break; - } - - improv_set_state(improv::STATE_PROVISIONING); - queueLedEffect(LED_EFFECT_WIFI_CONNECTING); - - if (improv_connectWifi(cmd.ssid, cmd.password)) { - queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); - - // std::array epdContent = {"S", "U", "C", "C", - // "E", "S", "S"}; setEpdContent(epdContent); - - preferences.putBool("wifiConfigured", true); - - improv_set_state(improv::STATE_PROVISIONED); - std::vector data = improv::build_rpc_response( - improv::WIFI_SETTINGS, getLocalUrl(), false); - improv_send_response(data); - - delay(2500); - ESP.restart(); - setupWebserver(); - } else { - queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); - - improv_set_state(improv::STATE_STOPPED); - improv_set_error(improv::Error::ERROR_UNABLE_TO_CONNECT); - } - - break; - } - - case improv::Command::GET_DEVICE_INFO: { - std::vector infos = {// Firmware name - "BTClock", - // Firmware version - "1.0.0", - // Hardware chip/variant - "ESP32S3", - // Device name - "BTClock"}; - std::vector data = - improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false); +bool onImprovCommandCallback(improv::ImprovCommand cmd) +{ + switch (cmd.command) + { + case improv::Command::GET_CURRENT_STATE: + { + if ((WiFi.status() == WL_CONNECTED)) + { + improv_set_state(improv::State::STATE_PROVISIONED); + std::vector data = improv::build_rpc_response( + improv::GET_CURRENT_STATE, getLocalUrl(), false); improv_send_response(data); + } + else + { + improv_set_state(improv::State::STATE_AUTHORIZED); + } + + break; + } + + case improv::Command::WIFI_SETTINGS: + { + if (cmd.ssid.length() == 0) + { + improv_set_error(improv::Error::ERROR_INVALID_RPC); break; } - case improv::Command::GET_WIFI_NETWORKS: { - improvGetAvailableWifiNetworks(); - // std::array epdContent = {"W", "E", "B", "W", "I", - // "F", "I"}; setEpdContent(epdContent); - break; + improv_set_state(improv::STATE_PROVISIONING); + queueLedEffect(LED_EFFECT_WIFI_CONNECTING); + + if (improv_connectWifi(cmd.ssid, cmd.password)) + { + queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); + + // std::array epdContent = {"S", "U", "C", "C", + // "E", "S", "S"}; setEpdContent(epdContent); + + preferences.putBool("wifiConfigured", true); + + improv_set_state(improv::STATE_PROVISIONED); + std::vector data = improv::build_rpc_response( + improv::WIFI_SETTINGS, getLocalUrl(), false); + improv_send_response(data); + + delay(2500); + ESP.restart(); + setupWebserver(); + } + else + { + queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + + improv_set_state(improv::STATE_STOPPED); + improv_set_error(improv::Error::ERROR_UNABLE_TO_CONNECT); } - default: { - improv_set_error(improv::ERROR_UNKNOWN_RPC); - return false; - } + break; + } + + case improv::Command::GET_DEVICE_INFO: + { + std::vector infos = {// Firmware name + "BTClock", + // Firmware version + "1.0.0", + // Hardware chip/variant + "ESP32S3", + // Device name + "BTClock"}; + std::vector data = + improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false); + improv_send_response(data); + break; + } + + case improv::Command::GET_WIFI_NETWORKS: + { + improvGetAvailableWifiNetworks(); + // std::array epdContent = {"W", "E", "B", "W", "I", + // "F", "I"}; setEpdContent(epdContent); + break; + } + + default: + { + improv_set_error(improv::ERROR_UNKNOWN_RPC); + return false; + } } return true; } -void improv_set_state(improv::State state) { +void improv_set_state(improv::State state) +{ std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; data.resize(11); data[6] = improv::IMPROV_SERIAL_VERSION; @@ -451,13 +507,15 @@ void improv_set_state(improv::State state) { data[9] = state; uint8_t checksum = 0x00; - for (uint8_t d : data) checksum += d; + for (uint8_t d : data) + checksum += d; data[10] = checksum; Serial.write(data.data(), data.size()); } -void improv_send_response(std::vector &response) { +void improv_send_response(std::vector &response) +{ std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; data.resize(9); data[6] = improv::IMPROV_SERIAL_VERSION; @@ -466,13 +524,15 @@ void improv_send_response(std::vector &response) { data.insert(data.end(), response.begin(), response.end()); uint8_t checksum = 0x00; - for (uint8_t d : data) checksum += d; + for (uint8_t d : data) + checksum += d; data.push_back(checksum); Serial.write(data.data(), data.size()); } -void improv_set_error(improv::Error error) { +void improv_set_error(improv::Error error) +{ std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; data.resize(11); data[6] = improv::IMPROV_SERIAL_VERSION; @@ -481,89 +541,97 @@ void improv_set_error(improv::Error error) { data[9] = error; uint8_t checksum = 0x00; - for (uint8_t d : data) checksum += d; + for (uint8_t d : data) + checksum += d; data[10] = checksum; Serial.write(data.data(), data.size()); } -void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { +void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) +{ static bool first_connect = true; Serial.printf("[WiFi-event] event: %d\n", event); - switch (event) { - case ARDUINO_EVENT_WIFI_READY: - Serial.println("WiFi interface ready"); - break; - case ARDUINO_EVENT_WIFI_SCAN_DONE: - Serial.println("Completed scan for access points"); - break; - case ARDUINO_EVENT_WIFI_STA_START: - Serial.println("WiFi client started"); - break; - case ARDUINO_EVENT_WIFI_STA_STOP: - Serial.println("WiFi clients stopped"); - break; - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - Serial.println("Connected to access point"); - break; - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: { - if (!first_connect) { - Serial.println("Disconnected from WiFi access point"); - queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); - uint8_t reason = info.wifi_sta_disconnected.reason; - if (reason) - Serial.printf("Disconnect reason: %s, ", - WiFi.disconnectReasonName((wifi_err_reason_t)reason)); - } - break; - } - case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - Serial.println("Authentication mode of access point has changed"); - break; - case ARDUINO_EVENT_WIFI_STA_GOT_IP: { - Serial.print("Obtained IP address: "); - Serial.println(WiFi.localIP()); - if (!first_connect) queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); - first_connect = false; - break; - } - case ARDUINO_EVENT_WIFI_STA_LOST_IP: - Serial.println("Lost IP address and IP address is reset to 0"); + switch (event) + { + case ARDUINO_EVENT_WIFI_READY: + Serial.println("WiFi interface ready"); + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + Serial.println("Completed scan for access points"); + break; + case ARDUINO_EVENT_WIFI_STA_START: + Serial.println("WiFi client started"); + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + Serial.println("WiFi clients stopped"); + break; + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + Serial.println("Connected to access point"); + break; + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + { + if (!first_connect) + { + Serial.println("Disconnected from WiFi access point"); queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); - WiFi.reconnect(); - break; - case ARDUINO_EVENT_WIFI_AP_START: - Serial.println("WiFi access point started"); - break; - case ARDUINO_EVENT_WIFI_AP_STOP: - Serial.println("WiFi access point stopped"); - break; - case ARDUINO_EVENT_WIFI_AP_STACONNECTED: - Serial.println("Client connected"); - break; - case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: - Serial.println("Client disconnected"); - break; - case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: - Serial.println("Assigned IP address to client"); - break; - case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: - Serial.println("Received probe request"); - break; - case ARDUINO_EVENT_WIFI_AP_GOT_IP6: - Serial.println("AP IPv6 is preferred"); - break; - case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - Serial.println("STA IPv6 is preferred"); - break; - default: - break; + uint8_t reason = info.wifi_sta_disconnected.reason; + if (reason) + Serial.printf("Disconnect reason: %s, ", + WiFi.disconnectReasonName((wifi_err_reason_t)reason)); + } + break; + } + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + Serial.println("Authentication mode of access point has changed"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + { + Serial.print("Obtained IP address: "); + Serial.println(WiFi.localIP()); + if (!first_connect) + queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); + first_connect = false; + break; + } + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + Serial.println("Lost IP address and IP address is reset to 0"); + queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + WiFi.reconnect(); + break; + case ARDUINO_EVENT_WIFI_AP_START: + Serial.println("WiFi access point started"); + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + Serial.println("WiFi access point stopped"); + break; + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + Serial.println("Client connected"); + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + Serial.println("Client disconnected"); + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + Serial.println("Assigned IP address to client"); + break; + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + Serial.println("Received probe request"); + break; + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + Serial.println("AP IPv6 is preferred"); + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + Serial.println("STA IPv6 is preferred"); + break; + default: + break; } } -String getMyHostname() { +String getMyHostname() +{ uint8_t mac[6]; // WiFi.macAddress(mac); esp_efuse_mac_get_default(mac); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 921cc44..92d13d3 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -16,12 +16,21 @@ Native_Pin EPD_CS[NUM_SCREENS] = { #endif }; Native_Pin EPD_BUSY[NUM_SCREENS] = { - Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9), - Native_Pin(37), Native_Pin(18), Native_Pin(16), + Native_Pin(3), + Native_Pin(5), + Native_Pin(7), + Native_Pin(9), + Native_Pin(37), + Native_Pin(18), + Native_Pin(16), }; MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 8), + MCP23X17_Pin(mcp1, 9), + MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), + MCP23X17_Pin(mcp1, 12), + MCP23X17_Pin(mcp1, 13), MCP23X17_Pin(mcp1, 14), }; @@ -30,20 +39,30 @@ Native_Pin EPD_DC = Native_Pin(14); Native_Pin EPD_DC = Native_Pin(38); MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), MCP23X17_Pin(mcp1, 4), + MCP23X17_Pin(mcp1, 8), + MCP23X17_Pin(mcp1, 9), + MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), + MCP23X17_Pin(mcp1, 12), + MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), + MCP23X17_Pin(mcp1, 4), }; MCP23X17_Pin EPD_CS[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), - MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), - MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; + MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), + MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), + MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 9), MCP23X17_Pin(mcp2, 11), MCP23X17_Pin(mcp2, 13), - MCP23X17_Pin(mcp2, 15), MCP23X17_Pin(mcp2, 1), MCP23X17_Pin(mcp2, 3), - MCP23X17_Pin(mcp2, 5), MCP23X17_Pin(mcp2, 7), + MCP23X17_Pin(mcp2, 9), + MCP23X17_Pin(mcp2, 11), + MCP23X17_Pin(mcp2, 13), + MCP23X17_Pin(mcp2, 15), + MCP23X17_Pin(mcp2, 1), + MCP23X17_Pin(mcp2, 3), + MCP23X17_Pin(mcp2, 5), + MCP23X17_Pin(mcp2, 7), }; #endif @@ -78,24 +97,30 @@ int bgColor = GxEPD_BLACK; #define FONT_SMALL Antonio_SemiBold20pt7b #define FONT_BIG Antonio_SemiBold90pt7b #define FONT_MEDIUM Antonio_SemiBold40pt7b +#define FONT_SATSYMBOL Satoshi_Symbol90pt7b std::mutex epdUpdateMutex; std::mutex epdMutex[NUM_SCREENS]; uint8_t qrcode[800]; -void forceFullRefresh() { - for (uint i = 0; i < NUM_SCREENS; i++) { +void forceFullRefresh() +{ + for (uint i = 0; i < NUM_SCREENS; i++) + { lastFullRefresh[i] = NULL; } } -void refreshFromMemory() { - for (uint i = 0; i < NUM_SCREENS; i++) { +void refreshFromMemory() +{ + for (uint i = 0; i < NUM_SCREENS; i++) + { int *taskParam = new int; *taskParam = i; xTaskCreate( - [](void *pvParameters) { + [](void *pvParameters) + { const int epdIndex = *(int *)pvParameters; delete (int *)pvParameters; displays[epdIndex].refresh(false); @@ -105,10 +130,12 @@ void refreshFromMemory() { } } -void setupDisplays() { +void setupDisplays() +{ std::lock_guard lockMcp(mcpMutex); - for (uint i = 0; i < NUM_SCREENS; i++) { + for (uint i = 0; i < NUM_SCREENS; i++) + { displays[i].init(0, true, 30); } @@ -116,7 +143,8 @@ void setupDisplays() { xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 4096, NULL, 11, NULL); - for (uint i = 0; i < NUM_SCREENS; i++) { + for (uint i = 0; i < NUM_SCREENS; i++) + { // epdUpdateSemaphore[i] = xSemaphoreCreateBinary(); // xSemaphoreGive(epdUpdateSemaphore[i]); @@ -124,7 +152,7 @@ void setupDisplays() { *taskParam = i; xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 2048, taskParam, - 11, &tasks[i]); // create task + 11, &tasks[i]); // create task } epdContent = {"B", "T", "C", "L", "O", "C", "K"}; @@ -132,14 +160,17 @@ void setupDisplays() { setEpdContent(epdContent); } -void setEpdContent(std::array newEpdContent) { +void setEpdContent(std::array newEpdContent) +{ setEpdContent(newEpdContent, false); } -void setEpdContent(std::array newEpdContent) { +void setEpdContent(std::array newEpdContent) +{ std::array conv; - for (size_t i = 0; i < newEpdContent.size(); ++i) { + for (size_t i = 0; i < newEpdContent.size(); ++i) + { conv[i] = String(newEpdContent[i].c_str()); } @@ -147,13 +178,16 @@ void setEpdContent(std::array newEpdContent) { } void setEpdContent(std::array newEpdContent, - bool forceUpdate) { + bool forceUpdate) +{ std::lock_guard lock(epdUpdateMutex); waitUntilNoneBusy(); - for (uint i = 0; i < NUM_SCREENS; i++) { - if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) { + for (uint i = 0; i < NUM_SCREENS; i++) + { + if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) + { epdContent[i] = newEpdContent[i]; UpdateDisplayTaskItem dispUpdate = {i}; xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY); @@ -161,12 +195,15 @@ void setEpdContent(std::array newEpdContent, } } -void prepareDisplayUpdateTask(void *pvParameters) { +void prepareDisplayUpdateTask(void *pvParameters) +{ UpdateDisplayTaskItem receivedItem; - while (1) { + while (1) + { // Wait for a work item to be available in the queue - if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) { + if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) + { uint epdIndex = receivedItem.dispNum; std::lock_guard lock(epdMutex[epdIndex]); // displays[epdIndex].init(0, false); // Little longer reset duration @@ -174,23 +211,43 @@ void prepareDisplayUpdateTask(void *pvParameters) { bool updatePartial = true; - if (strstr(epdContent[epdIndex].c_str(), "/") != NULL) { + if (strstr(epdContent[epdIndex].c_str(), "/") != NULL) + { String top = epdContent[epdIndex].substring( 0, epdContent[epdIndex].indexOf("/")); String bottom = epdContent[epdIndex].substring( epdContent[epdIndex].indexOf("/") + 1); splitText(epdIndex, top, bottom, updatePartial); - } else if (epdContent[epdIndex].startsWith(F("qr"))) { + } + else if (epdContent[epdIndex].startsWith(F("qr"))) + { renderQr(epdIndex, epdContent[epdIndex], updatePartial); - } else if (epdContent[epdIndex].length() > 5) { + } + else if (epdContent[epdIndex].length() > 5) + { renderText(epdIndex, epdContent[epdIndex], updatePartial); - } else { - if (epdContent[epdIndex].length() > 1) { - showChars(epdIndex, epdContent[epdIndex], updatePartial, - &FONT_MEDIUM); - } else { + } + else + { + if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) + { + if (epdContent[epdIndex].equals("STS")) + { + showDigit(epdIndex, 'S', updatePartial, + &FONT_SATSYMBOL); + } + else + { + showChars(epdIndex, epdContent[epdIndex], updatePartial, + &FONT_MEDIUM); + } + } + else + { + showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, &FONT_BIG); + } } @@ -199,11 +256,13 @@ void prepareDisplayUpdateTask(void *pvParameters) { } } -extern "C" void updateDisplay(void *pvParameters) noexcept { +extern "C" void updateDisplay(void *pvParameters) noexcept +{ const int epdIndex = *(int *)pvParameters; delete (int *)pvParameters; - for (;;) { + for (;;) + { // Wait for the task notification ulTaskNotifyTake(pdTRUE, portMAX_DELAY); @@ -215,7 +274,8 @@ extern "C" void updateDisplay(void *pvParameters) noexcept { displays[epdIndex].init(0, false, 40); } uint count = 0; - while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) { + while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) + { vTaskDelay(pdMS_TO_TICKS(100)); count++; } @@ -227,16 +287,20 @@ extern "C" void updateDisplay(void *pvParameters) noexcept { (millis() - lastFullRefresh[epdIndex]) > (preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH) * - 60 * 1000)) { + 60 * 1000)) + { updatePartial = false; } char tries = 0; - while (tries < 3) { - if (displays[epdIndex].displayWithReturn(updatePartial)) { + while (tries < 3) + { + if (displays[epdIndex].displayWithReturn(updatePartial)) + { displays[epdIndex].powerOff(); currentEpdContent[epdIndex] = epdContent[epdIndex]; - if (!updatePartial) lastFullRefresh[epdIndex] = millis(); + if (!updatePartial) + lastFullRefresh[epdIndex] = millis(); if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); @@ -251,7 +315,8 @@ extern "C" void updateDisplay(void *pvParameters) noexcept { } void splitText(const uint dispNum, const String &top, const String &bottom, - bool partial) { + bool partial) +{ displays[dispNum].setRotation(2); displays[dispNum].setFont(&FONT_SMALL); displays[dispNum].setTextColor(getFgColor()); @@ -290,24 +355,67 @@ void splitText(const uint dispNum, const String &top, const String &bottom, } void showDigit(const uint dispNum, char chr, bool partial, - const GFXfont *font) { + const GFXfont *font) +{ String str(chr); + + if (chr == '.') + { + str = "!"; + } displays[dispNum].setRotation(2); displays[dispNum].setFont(font); displays[dispNum].setTextColor(getFgColor()); int16_t tbx, tby; uint16_t tbw, tbh; + displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); + // center the bounding box by transposition of the origin: uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; + + // if (str.equals(".")) + // { + // // int16_t yAdvance = font->yAdvance; + // // uint8_t charIndex = 46 - font->first; + // // GFXglyph *glyph = (&font->glyph)[charIndex]; + // int16_t tbx2, tby2; + // uint16_t tbw2, tbh2; + // displays[dispNum].getTextBounds(".!", 0, 0, &tbx2, &tby2, &tbw2, &tbh2); + + // y = ((displays[dispNum].height() - tbh2) / 2) - tby2; + // // Serial.print("yAdvance"); + // // Serial.println(yAdvance); + // // if (glyph != nullptr) { + // // Serial.print("height"); + // // Serial.println(glyph->height); + // // Serial.print("yOffset"); + // // Serial.println(glyph->yOffset); + // // } + + // // y = 250-99+18+19; + // } + displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].setCursor(x, y); displays[dispNum].print(str); + + if (chr == '.') + { + displays[dispNum].fillRect(x,y,displays[dispNum].width(),round(displays[dispNum].height() * 0.9), getBgColor()); + } + + // displays[dispNum].setCursor(10, 3); + // displays[dispNum].setFont(&FONT_SMALL); + // displays[dispNum].setTextColor(getFgColor()); + // displays[dispNum].println("Y = " + y); } void showChars(const uint dispNum, const String &chars, bool partial, - const GFXfont *font) { + const GFXfont *font) +{ displays[dispNum].setRotation(2); displays[dispNum].setFont(font); displays[dispNum].setTextColor(getFgColor()); @@ -330,10 +438,12 @@ void setBgColor(int color) { bgColor = color; } void setFgColor(int color) { fgColor = color; } -std::array getCurrentEpdContent() { +std::array getCurrentEpdContent() +{ return currentEpdContent; } -void renderText(const uint dispNum, const String &text, bool partial) { +void renderText(const uint dispNum, const String &text, bool partial) +{ displays[dispNum].setRotation(2); displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), displays[dispNum].height()); @@ -346,20 +456,25 @@ void renderText(const uint dispNum, const String &text, bool partial) { std::string line; - while (std::getline(ss, line, '\n')) { - if (line.rfind("*", 0) == 0) { + while (std::getline(ss, line, '\n')) + { + if (line.rfind("*", 0) == 0) + { line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); displays[dispNum].setFont(&FreeSansBold9pt7b); displays[dispNum].println(line.c_str()); - } else { + } + else + { displays[dispNum].setFont(&FreeSans9pt7b); displays[dispNum].println(line.c_str()); } } } -void renderQr(const uint dispNum, const String &text, bool partial) { +void renderQr(const uint dispNum, const String &text, bool partial) +{ #ifdef USE_QR uint8_t tempBuffer[800]; @@ -379,8 +494,10 @@ void renderQr(const uint dispNum, const String &text, bool partial) { displays[dispNum].fillScreen(GxEPD_WHITE); const int border = 0; - for (int y = -border; y < size * 4 + border; y++) { - for (int x = -border; x < size * 4 + border; x++) { + for (int y = -border; y < size * 4 + border; y++) + { + for (int x = -border; x < size * 4 + border; x++) + { displays[dispNum].drawPixel( padding + x, paddingY + y, qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) @@ -391,16 +508,22 @@ void renderQr(const uint dispNum, const String &text, bool partial) { #endif } -void waitUntilNoneBusy() { - for (int i = 0; i < NUM_SCREENS; i++) { +void waitUntilNoneBusy() +{ + for (int i = 0; i < NUM_SCREENS; i++) + { uint count = 0; - while (EPD_BUSY[i].digitalRead()) { + while (EPD_BUSY[i].digitalRead()) + { count++; vTaskDelay(10); - if (count == 200) { + if (count == 200) + { // displays[i].init(0, false); vTaskDelay(100); - } else if (count > 205) { + } + else if (count > 205) + { Serial.printf("Busy timeout %d", i); break; } diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index dd30233..be2e8ca 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -36,7 +36,7 @@ void workerTask(void *pvParameters) { if (getCurrentScreen() == SCREEN_BTC_TICKER) { taskEpdContent = parsePriceData(price, priceSymbol); } else if (getCurrentScreen() == SCREEN_MSCW_TIME) { - taskEpdContent = parseSatsPerCurrency(price, priceSymbol); + taskEpdContent = parseSatsPerCurrency(price, priceSymbol, preferences.getBool("useSatsSymbol", false)); } else { taskEpdContent = parseMarketCap(getBlockHeight(), price, priceSymbol, @@ -50,7 +50,7 @@ void workerTask(void *pvParameters) { if (getCurrentScreen() != SCREEN_HALVING_COUNTDOWN) { taskEpdContent = parseBlockHeight(getBlockHeight()); } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight()); + taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", false)); } if (getCurrentScreen() == SCREEN_HALVING_COUNTDOWN || diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index bc57f06..69f3bc9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -11,7 +11,7 @@ void setupWebserver() { server.addHandler(&events); // server.serveStatic("/css", LittleFS, "/css/"); - // server.serveStatic("/js", LittleFS, "/js/"); + server.serveStatic("/fonts", LittleFS, "/fonts/"); server.serveStatic("/build", LittleFS, "/build"); server.serveStatic("/swagger.json", LittleFS, "/swagger.json"); server.serveStatic("/api.html", LittleFS, "/api.html"); @@ -320,7 +320,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", - "mcapBigChar"}; + "mcapBigChar", "useSatsSymbol", "useBlkCountdown"}; for (String setting : boolSettings) { if (settings.containsKey(setting)) { @@ -402,11 +402,14 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", true); root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", false); root["ledBrightness"] = preferences.getUInt("ledBrightness", 128); - root["stealFocus"] = preferences.getBool("stealFocus", true); + root["stealFocus"] = preferences.getBool("stealFocus", false); root["mcapBigChar"] = preferences.getBool("mcapBigChar", true); root["mdnsEnabled"] = preferences.getBool("mdnsEnabled", true); root["otaEnabled"] = preferences.getBool("otaEnabled", true); root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", false); + root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", false); + root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", false); + root["hostnamePrefix"] = preferences.getString("hostnamePrefix", "btclock"); root["hostname"] = getMyHostname(); root["ip"] = WiFi.localIP(); diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index ba6b011..f863551 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -10,7 +10,7 @@ void tearDown(void) { } void test_CorrectSatsPerDollarConversion(void) { - std::array output = parseSatsPerCurrency(37253, '$'); + std::array output = parseSatsPerCurrency(37253, '$', false); TEST_ASSERT_EQUAL_STRING("MSCW/TIME", output[0].c_str()); TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-4].c_str()); TEST_ASSERT_EQUAL_STRING("6", output[NUM_SCREENS-3].c_str()); @@ -40,13 +40,49 @@ void test_PriceOf100kusd(void) { void test_PriceOf1MillionUsd(void) { std::array output = parsePriceData(1000000, '$'); TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); - for (int i = 1; i <= NUM_SCREENS-3; i++) { - TEST_ASSERT_EQUAL_STRING(" ", output[i].c_str()); - } - TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-2].c_str()); + + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-2].c_str()); TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS-1].c_str()); } +void test_McapLowerUsd(void) { + std::array output = parseMarketCap(810000, 26000, '$', true); + TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str()); + +// TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str()); + TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-5].c_str()); + TEST_ASSERT_EQUAL_STRING("5", output[NUM_SCREENS-4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); + TEST_ASSERT_EQUAL_STRING("7", output[NUM_SCREENS-2].c_str()); + TEST_ASSERT_EQUAL_STRING("B", output[NUM_SCREENS-1].c_str()); +} + +void test_Mcap1TrillionUsd(void) { + std::array output = parseMarketCap(831000, 52000, '$', true); + TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str()); + + TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str()); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-2].c_str()); + TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS-1].c_str()); +} + +void test_Mcap1TrillionEur(void) { + std::array output = parseMarketCap(831000, 52000, '[', true); + TEST_ASSERT_EQUAL_STRING("EUR/MCAP", output[0].c_str()); + TEST_ASSERT_EQUAL_STRING("[", output[NUM_SCREENS-6].c_str()); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-2].c_str()); + TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS-1].c_str()); +} + // not needed when using generate_test_runner.rb int runUnityTests(void) { UNITY_BEGIN(); @@ -54,7 +90,10 @@ int runUnityTests(void) { RUN_TEST(test_SixCharacterBlockHeight); RUN_TEST(test_SevenCharacterBlockHeight); RUN_TEST(test_PriceOf100kusd); - RUN_TEST(test_PriceOf1MillionUsd); + RUN_TEST(test_McapLowerUsd); + RUN_TEST(test_Mcap1TrillionUsd); + RUN_TEST(test_Mcap1TrillionEur); + //RUN_TEST(test_Mcap1MillionEur); return UNITY_END(); } From 2ca85ff479705a559fcea768765fb7a077f7327d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 10 Mar 2024 20:24:55 +0100 Subject: [PATCH 003/188] Upgrade ArduinoJson to version 7, add Block Fee Rate screen --- dependencies.lock | 4 +-- lib/btclock/data_handler.cpp | 22 +++++++++++++++ lib/btclock/data_handler.hpp | 3 +- platformio.ini | 4 +-- src/lib/block_notify.cpp | 43 ++++++++++++++++++++++++++--- src/lib/block_notify.hpp | 4 +++ src/lib/config.cpp | 1 + src/lib/config.hpp | 3 +- src/lib/ota.cpp | 6 ++-- src/lib/price_fetch.cpp | 2 +- src/lib/price_notify.cpp | 2 +- src/lib/screen_handler.cpp | 7 +++++ src/lib/screen_handler.hpp | 1 + src/lib/shared.hpp | 20 ++++++++------ src/lib/webserver.cpp | 34 +++++++++++------------ src/lib/webserver.hpp | 4 +-- test/test_datahandler/test_main.cpp | 11 ++++++++ 17 files changed, 128 insertions(+), 43 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index 345aa6f..b815904 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -1,11 +1,11 @@ dependencies: esp_littlefs: - component_hash: 66d5afe7ad323d0159d04e296c14ffa0e39156502bc15e0520eff2af392d3aa4 + component_hash: 6ae78edac4f81c605d6d7bff75f4f9a45d25938e4796347f91ea975ed3123326 source: git: https://github.com/joltwallet/esp_littlefs.git path: . type: git - version: 41873c20fb5cdbcf28d7d6cc04e4bcb4a1305317 + version: fd64733cdf248c7a7eb207db7d28124f8857fe0b idf: component_hash: null source: diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 8719626..fc5f4e8 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -87,6 +87,28 @@ std::array parseBlockHeight(std::uint32_t blockHeight) return ret; } +std::array parseBlockFees(std::uint16_t blockFees) { + std::array ret; + std::string blockFeesString = std::to_string(blockFees); + std::uint32_t firstIndex = 0; + + if (blockFeesString.length() < NUM_SCREENS) + { + blockFeesString.insert(blockFeesString.begin(), NUM_SCREENS - blockFeesString.length() - 1, ' '); + ret[0] = "FEE/RATE"; + firstIndex = 1; + } + + for (uint i = firstIndex; i < NUM_SCREENS-1; i++) + { + ret[i] = blockFeesString[i]; + } + + ret[NUM_SCREENS-1] = "sat/vB"; + + return ret; +} + std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks) { std::array ret; diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index d385cef..1531c96 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -9,4 +9,5 @@ std::array parsePriceData(std::uint32_t price, char cu std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array parseBlockHeight(std::uint32_t blockHeight); std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks); -std::array parseMarketCap(std::uint32_t blockHeight, std::uint32_t price, char currencySymbol, bool bigChars); \ No newline at end of file +std::array parseMarketCap(std::uint32_t blockHeight, std::uint32_t price, char currencySymbol, bool bigChars); +std::array parseBlockFees(std::uint16_t blockFees); diff --git a/platformio.ini b/platformio.ini index e0924a9..ce953dd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,9 +30,9 @@ build_unflags = -Werror=all -fno-exceptions lib_deps = - bblanchon/ArduinoJson@^6.21.5 + bblanchon/ArduinoJson@^7.0.3 esphome/Improv@^1.2.3 - esphome/ESPAsyncWebServer-esphome@^3.1.0 + mathieucarbou/ESP Async WebServer adafruit/Adafruit BusIO@^1.15.0 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.0 diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 897d7dd..904b6f3 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -3,6 +3,7 @@ char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; uint currentBlockHeight = 816000; +uint blockMedianFee = 1; bool blockNotifyInit = false; // const char *mempoolWsCert = R"(-----BEGIN CERTIFICATE----- @@ -103,7 +104,7 @@ void setupBlockNotify() { void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - const String sub = "{\"action\": \"want\", \"data\":[\"blocks\"]}"; + const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}"; switch (event_id) { case WEBSOCKET_EVENT_CONNECTED: blockNotifyInit = true; @@ -130,16 +131,27 @@ void onWebsocketEvent(void *handler_args, esp_event_base_t base, } void onWebsocketMessage(esp_websocket_event_data_t *event_data) { - SpiRamJsonDocument doc(event_data->data_len); + JsonDocument doc; - deserializeJson(doc, (char *)event_data->data_ptr); + + JsonDocument filter; + filter["block"]["height"] = true; + filter["mempool-blocks"][0]["medianFee"] = true; + + DeserializationError error = deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter)); + + // if (error) { + // Serial.print("deserializeJson() failed: "); + // Serial.println(error.c_str()); + // return; + // } if (doc.containsKey("block")) { JsonObject block = doc["block"]; currentBlockHeight = block["height"].as(); - Serial.printf("New block found: %d\r\n", block["height"].as()); + //Serial.printf("New block found: %d\r\n", block["height"].as()); preferences.putUInt("blockHeight", currentBlockHeight); if (workQueue != nullptr) { @@ -167,6 +179,23 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data) { queueLedEffect(LED_FLASH_BLOCK_NOTIFY); } } + } else if (doc.containsKey("mempool-blocks")) { + JsonArray blockInfo = doc["mempool-blocks"].as(); + + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + + if (blockMedianFee == medianFee) { + doc.clear(); + return; + } + + // Serial.printf("New median fee: %d\r\n", medianFee); + blockMedianFee = medianFee; + + if (workQueue != nullptr) { + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } } doc.clear(); @@ -178,6 +207,12 @@ void setBlockHeight(uint newBlockHeight) { currentBlockHeight = newBlockHeight; } +uint getBlockMedianFee() { return blockMedianFee; } + +void setBlockMedianFee(uint newBlockMedianFee) { + blockMedianFee = newBlockMedianFee; +} + bool isBlockNotifyConnected() { if (blockNotifyClient == NULL) return false; return esp_websocket_client_is_connected(blockNotifyClient); diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index f0d903d..72d9500 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -23,6 +23,10 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data); void setBlockHeight(uint newBlockHeight); uint getBlockHeight(); + +void setBlockMedianFee(uint blockMedianFee); +uint getBlockMedianFee(); + bool isBlockNotifyConnected(); void stopBlockNotify(); bool getBlockNotifyInit(); \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 068f8fb..8f18a84 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -208,6 +208,7 @@ void setupPreferences() setPrice(preferences.getUInt("lastPrice", 30000)); screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height"; + screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate"; screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar"; screenNameMap[SCREEN_BTC_TICKER] = "Ticker"; screenNameMap[SCREEN_TIME] = "Time"; diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 228beea..cd4b262 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -1,4 +1,5 @@ -#pragma once; +#pragma once + #include #include #include diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index d23929f..945a4d4 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -85,13 +85,13 @@ void downloadUpdate() { if (httpCode == 200) { // WiFiClient * stream = http->getStreamPtr(); - StaticJsonDocument<64> filter; + JsonDocument filter; - JsonObject filter_assets_0 = filter["assets"].createNestedObject(); + JsonObject filter_assets_0 = filter["assets"].add(); filter_assets_0["name"] = true; filter_assets_0["browser_download_url"] = true; - SpiRamJsonDocument doc(1536); + JsonDocument doc; DeserializationError error = deserializeJson( doc, http.getStream(), DeserializationOption::Filter(filter)); diff --git a/src/lib/price_fetch.cpp b/src/lib/price_fetch.cpp index 73606a2..d5e6e10 100644 --- a/src/lib/price_fetch.cpp +++ b/src/lib/price_fetch.cpp @@ -24,7 +24,7 @@ void taskPriceFetch(void *pvParameters) { uint usdPrice, eurPrice; if (httpCode == 200) { String payload = http->getString(); - StaticJsonDocument<96> doc; + JsonDocument doc; deserializeJson(doc, payload); // usdPrice = doc["bitcoin"]["usd"]; eurPrice = doc["bitcoin"]["eur"].as(); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index a380942..c07143e 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -76,7 +76,7 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, } void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { - SpiRamJsonDocument doc(event_data->data_len); + JsonDocument doc; deserializeJson(doc, (char *)event_data->data_ptr); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index be2e8ca..da175de 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -46,6 +46,13 @@ void workerTask(void *pvParameters) { setEpdContent(taskEpdContent); break; } + case TASK_FEE_UPDATE: { + if (getCurrentScreen() == SCREEN_BLOCK_FEE_RATE) { + taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); + setEpdContent(taskEpdContent); + } + break; + } case TASK_BLOCK_UPDATE: { if (getCurrentScreen() != SCREEN_HALVING_COUNTDOWN) { taskEpdContent = parseBlockHeight(getBlockHeight()); diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index 464969b..6cbb2d0 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -24,6 +24,7 @@ extern QueueHandle_t workQueue; typedef enum { TASK_PRICE_UPDATE, TASK_BLOCK_UPDATE, + TASK_FEE_UPDATE, TASK_TIME_UPDATE } TaskType; diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index c4c6fab..9f99c19 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -22,26 +22,28 @@ const PROGMEM int SCREEN_BTC_TICKER = 2; const PROGMEM int SCREEN_TIME = 3; const PROGMEM int SCREEN_HALVING_COUNTDOWN = 4; const PROGMEM int SCREEN_MARKET_CAP = 5; +const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6; const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_CUSTOM = 99; -const int SCREEN_COUNT = 6; +const int SCREEN_COUNT = 7; const PROGMEM int screens[SCREEN_COUNT] = { SCREEN_BLOCK_HEIGHT, SCREEN_MSCW_TIME, SCREEN_BTC_TICKER, - SCREEN_TIME, SCREEN_HALVING_COUNTDOWN, SCREEN_MARKET_CAP}; + SCREEN_TIME, SCREEN_HALVING_COUNTDOWN, SCREEN_MARKET_CAP, + SCREEN_BLOCK_FEE_RATE}; const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; -struct SpiRamAllocator { - void *allocate(size_t size) { +struct SpiRamAllocator : ArduinoJson::Allocator { + void* allocate(size_t size) override { return heap_caps_malloc(size, MALLOC_CAP_SPIRAM); } - void deallocate(void *pointer) { heap_caps_free(pointer); } + void deallocate(void* pointer) override { + heap_caps_free(pointer); + } - void *reallocate(void *ptr, size_t new_size) { + void* reallocate(void* ptr, size_t new_size) override { return heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM); } -}; - -using SpiRamJsonDocument = BasicJsonDocument; +}; \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 69f3bc9..eded90f 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -94,8 +94,8 @@ void setupWebserver() { void stopWebServer() { server.end(); } -StaticJsonDocument<768> getStatusObject() { - StaticJsonDocument<768> root; +JsonDocument getStatusObject() { + JsonDocument root; root["currentScreen"] = getCurrentScreen(); root["numScreens"] = NUM_SCREENS; @@ -108,7 +108,7 @@ StaticJsonDocument<768> getStatusObject() { // root["espFreePsram"] = ESP.getFreePsram(); // root["espPsramSize"] = ESP.getPsramSize(); - JsonObject conStatus = root.createNestedObject("connectionStatus"); + JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected(); @@ -117,9 +117,9 @@ StaticJsonDocument<768> getStatusObject() { return root; } -StaticJsonDocument<512> getLedStatusObject() { - StaticJsonDocument<512> root; - JsonArray colors = root.createNestedArray("data"); +JsonDocument getLedStatusObject() { + JsonDocument root; + JsonArray colors = root["data"].to(); // Adafruit_NeoPixel pix = getPixels(); for (uint i = 0; i < pixels.numPixels(); i++) { @@ -131,7 +131,7 @@ StaticJsonDocument<512> getLedStatusObject() { char hexColor[8]; sprintf(hexColor, "#%02X%02X%02X", red, green, blue); - JsonObject object = colors.createNestedObject(); + JsonObject object = colors.add(); object["red"] = red; object["green"] = green; object["blue"] = blue; @@ -143,8 +143,8 @@ StaticJsonDocument<512> getLedStatusObject() { void eventSourceUpdate() { if (!events.count()) return; - StaticJsonDocument<768> root = getStatusObject(); - JsonArray data = root.createNestedArray("data"); + JsonDocument root = getStatusObject(); + JsonArray data = root["data"].to(); root["leds"] = getLedStatusObject()["data"]; @@ -168,9 +168,9 @@ void onApiStatus(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); - StaticJsonDocument<1024> root = getStatusObject(); - JsonArray data = root.createNestedArray("data"); - JsonArray rendered = root.createNestedArray("rendered"); + JsonDocument root = getStatusObject(); + JsonArray data = root["data"].to(); + JsonArray rendered = root["rendered"].to(); String epdContent[NUM_SCREENS]; root["leds"] = getLedStatusObject()["data"]; @@ -384,7 +384,7 @@ void onApiRestart(AsyncWebServerRequest *request) { * @Path("/api/settings") */ void onApiSettingsGet(AsyncWebServerRequest *request) { - StaticJsonDocument<1536> root; + JsonDocument root; root["numScreens"] = NUM_SCREENS; root["fgColor"] = getFgColor(); root["bgColor"] = getBgColor(); @@ -421,12 +421,12 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { #ifdef LAST_BUILD_TIME root["lastBuildTime"] = String(LAST_BUILD_TIME); #endif - JsonArray screens = root.createNestedArray("screens"); + JsonArray screens = root["screens"].to(); std::vector screenNameMap = getScreenNameMap(); for (int i = 0; i < screenNameMap.size(); i++) { - JsonObject o = screens.createNestedObject(); + JsonObject o = screens.add(); String key = "screen" + String(i) + "Visible"; o["id"] = i; o["name"] = screenNameMap[i]; @@ -650,7 +650,7 @@ void onApiSystemStatus(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); - StaticJsonDocument<128> root; + JsonDocument root; root["espFreeHeap"] = ESP.getFreeHeap(); root["espHeapSize"] = ESP.getHeapSize(); @@ -743,7 +743,7 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) { setLights(r, g, b); } - StaticJsonDocument<48> doc; + JsonDocument doc; doc["result"] = rgbColor; serializeJson(getLedStatusObject()["data"], *response); diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index f8afa98..c910c3e 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -46,8 +46,8 @@ void onApiRestart(AsyncWebServerRequest *request); void onIndex(AsyncWebServerRequest *request); void onNotFound(AsyncWebServerRequest *request); -StaticJsonDocument<512> getLedStatusObject(); -StaticJsonDocument<768> getStatusObject(); +JsonDocument getLedStatusObject(); +JsonDocument getStatusObject(); void eventSourceUpdate(); void eventSourceTask(void *pvParameters); diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index f863551..2c9ffb2 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -31,6 +31,16 @@ void test_SevenCharacterBlockHeight(void) { TEST_ASSERT_EQUAL_STRING("0", output[1].c_str()); } +void test_FeeRateDisplay(void) { + uint testValue = 21; + std::array output = parseBlockFees(static_cast(testValue)); + TEST_ASSERT_EQUAL_STRING("FEE/RATE", output[0].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-3].c_str()); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-2].c_str()); + TEST_ASSERT_EQUAL_STRING("sat/vB", output[NUM_SCREENS-1].c_str()); +} + + void test_PriceOf100kusd(void) { std::array output = parsePriceData(100000, '$'); TEST_ASSERT_EQUAL_STRING("$", output[0].c_str()); @@ -89,6 +99,7 @@ int runUnityTests(void) { RUN_TEST(test_CorrectSatsPerDollarConversion); RUN_TEST(test_SixCharacterBlockHeight); RUN_TEST(test_SevenCharacterBlockHeight); + RUN_TEST(test_FeeRateDisplay); RUN_TEST(test_PriceOf100kusd); RUN_TEST(test_McapLowerUsd); RUN_TEST(test_Mcap1TrillionUsd); From 969d2137c3917f4a595fa88e6bab24082da2b640 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 10 Mar 2024 20:37:45 +0100 Subject: [PATCH 004/188] Fix for fee rate screen --- src/lib/screen_handler.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index da175de..7007d11 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -240,6 +240,11 @@ void setCurrentScreen(uint newScreen) { // xTaskNotifyGive(priceUpdateTaskHandle); break; } + case SCREEN_BLOCK_FEE_RATE: { + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + break; + } } if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); From 3e00f1b4a6122e3a5140e5400ed75a7f1167cce1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 11 Mar 2024 21:21:15 +0100 Subject: [PATCH 005/188] Add functionality to disable all LEDs --- data | 2 +- lib/btclock/data_handler.cpp | 6 +++--- lib/btclock/data_handler.hpp | 2 +- src/lib/led_handler.cpp | 5 +++++ src/lib/screen_handler.cpp | 2 +- src/lib/webserver.cpp | 5 ++++- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/data b/data index 1b8ab93..dcdf989 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 1b8ab93da64dd7383a2fb34c967b75fdab072718 +Subproject commit dcdf98964a42ccd83b2d2501bbed46a640bbc300 diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index fc5f4e8..764417f 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -1,11 +1,11 @@ #include "data_handler.hpp" -std::array parsePriceData(std::uint32_t price, char currencySymbol) +std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat) { std::array ret; std::string priceString; - if (std::to_string(price).length() >= NUM_SCREENS) { - priceString = formatNumberWithSuffix(price, NUM_SCREENS-2); + if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) { + priceString = currencySymbol + formatNumberWithSuffix(price, NUM_SCREENS-2); } else { priceString = currencySymbol + std::to_string(price); } diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index 1531c96..b31cfe7 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -5,7 +5,7 @@ #include "utils.hpp" -std::array parsePriceData(std::uint32_t price, char currencySymbol); +std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat = false); std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array parseBlockHeight(std::uint32_t blockHeight); std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks); diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 56b9b69..a809e75 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -10,6 +10,11 @@ void ledTask(void *parameter) { if (ledTaskQueue != NULL) { if (xQueueReceive(ledTaskQueue, &ledTaskParams, portMAX_DELAY) == pdPASS) { + + if (preferences.getBool("disableLeds", false)) { + continue; + } + uint32_t oldLights[NEOPIXEL_COUNT]; // get current state diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 7007d11..d1215f6 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -34,7 +34,7 @@ void workerTask(void *pvParameters) { priceSymbol = '['; } if (getCurrentScreen() == SCREEN_BTC_TICKER) { - taskEpdContent = parsePriceData(price, priceSymbol); + taskEpdContent = parsePriceData(price, priceSymbol, preferences.getBool("suffixPrice", false)); } else if (getCurrentScreen() == SCREEN_MSCW_TIME) { taskEpdContent = parseSatsPerCurrency(price, priceSymbol, preferences.getBool("useSatsSymbol", false)); } else { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index eded90f..6255416 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -320,7 +320,8 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", - "mcapBigChar", "useSatsSymbol", "useBlkCountdown"}; + "mcapBigChar", "useSatsSymbol", "useBlkCountdown", + "suffixPrice", "disableLeds"}; for (String setting : boolSettings) { if (settings.containsKey(setting)) { @@ -409,6 +410,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", false); root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", false); root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", false); + root["suffixPrice"] = preferences.getBool("suffixPrice", false); + root["disableLeds"] = preferences.getBool("disableLeds", false); root["hostnamePrefix"] = preferences.getString("hostnamePrefix", "btclock"); root["hostname"] = getMyHostname(); From 37c57b7d97e2aa6b21f22bec8a76eccef10d5218 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 17 Mar 2024 23:16:15 +0100 Subject: [PATCH 006/188] Add extra check for missing price updates --- src/lib/price_notify.cpp | 4 ++++ src/lib/price_notify.hpp | 3 ++- src/main.cpp | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index c07143e..b6ab9a3 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -105,6 +105,10 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { } } +uint getLastPriceUpdate() { + return lastPriceUpdate; +} + uint getPrice() { return currentPrice; } void setPrice(uint newPrice) { currentPrice = newPrice; } diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 8bcb207..acdd898 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -19,4 +19,5 @@ void setPrice(uint newPrice); bool isPriceNotifyConnected(); void stopPriceNotify(); -bool getPriceNotifyInit(); \ No newline at end of file +bool getPriceNotifyInit(); +uint getLastPriceUpdate(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 012bf5f..47e73fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,13 +39,15 @@ extern "C" void app_main() { if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); + int64_t currentUptime = esp_timer_get_time() / 1000000;; + if (!WiFi.isConnected()) { if (!wifiLostConnection) { - wifiLostConnection = esp_timer_get_time() / 1000000; + wifiLostConnection = currentUptime; Serial.println("Lost WiFi connection, trying to reconnect..."); } - if (((esp_timer_get_time() / 1000000) - wifiLostConnection) > 600) { + if ((currentUptime - wifiLostConnection) > 600) { Serial.println("Still no connection after 10 minutes, restarting..."); delay(2000); ESP.restart(); @@ -85,6 +87,14 @@ extern "C" void app_main() { priceNotifyLostConnection = 0; } + // if more than 5 price updates are missed, there is probably something wrong, reconnect + if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE)*5)) { + stopPriceNotify(); + setupPriceNotify(); + + priceNotifyLostConnection = 0; + } + vTaskDelay(pdMS_TO_TICKS(5000)); } } \ No newline at end of file From 23ef2a64cc8a3ea4ff9e8a2e4bffa96e8f4d4833 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 17:17:04 +0100 Subject: [PATCH 007/188] Add "storage mode" to protect EPDs (lowest button while booting) --- src/lib/config.cpp | 10 ++++++++++ src/lib/epd.cpp | 21 ++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 8f18a84..98911e4 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -31,6 +31,16 @@ void setup() } } + { + if (mcp1.digitalRead(0) == LOW) + { + // Then loop forever to prevent anything else from writing to the screen + while (true) { + delay(1000); + } + } + } + tryImprovSetup(); setupWebserver(); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 92d13d3..d4d579b 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -155,7 +155,19 @@ void setupDisplays() 11, &tasks[i]); // create task } - epdContent = {"B", "T", "C", "L", "O", "C", "K"}; + // Hold lower button to enable "storage mode" (prevents burn-in of ePaper displays) + if (mcp1.digitalRead(0) == LOW) + { + setFgColor(GxEPD_BLACK); + setBgColor(GxEPD_WHITE); + + epdContent = {" ", " ", " ", " ", " ", " ", " "}; + } + else + { + + epdContent = {"B", "T", "C", "L", "O", "C", "K"}; + } setEpdContent(epdContent); } @@ -244,10 +256,9 @@ void prepareDisplayUpdateTask(void *pvParameters) } else { - + showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, &FONT_BIG); - } } @@ -402,9 +413,9 @@ void showDigit(const uint dispNum, char chr, bool partial, displays[dispNum].setCursor(x, y); displays[dispNum].print(str); - if (chr == '.') + if (chr == '.') { - displays[dispNum].fillRect(x,y,displays[dispNum].width(),round(displays[dispNum].height() * 0.9), getBgColor()); + displays[dispNum].fillRect(x, y, displays[dispNum].width(), round(displays[dispNum].height() * 0.9), getBgColor()); } // displays[dispNum].setCursor(10, 3); From d58c38c8c4fe5efee5d736d4b8003204a9bebb6d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 19:32:34 +0100 Subject: [PATCH 008/188] Add 2.9 inch EPD support --- .github/actions/install-build/action.yml | 4 +- .github/workflows/tagging.yml | 78 ++++++++++++++++++------ platformio.ini | 12 +++- src/lib/epd.cpp | 18 +++--- src/lib/shared.hpp | 10 +++ 5 files changed, 93 insertions(+), 29 deletions(-) diff --git a/.github/actions/install-build/action.yml b/.github/actions/install-build/action.yml index ef50cc5..bf67cab 100644 --- a/.github/actions/install-build/action.yml +++ b/.github/actions/install-build/action.yml @@ -37,7 +37,7 @@ runs: detailed_summary: true - name: Build BTClock firmware shell: bash - run: pio run -e lolin_s3_mini_qr + run: pio run - name: Build BTClock filesystem shell: bash - run: pio run -e lolin_s3_mini_qr --target buildfs \ No newline at end of file + run: pio run --target buildfs \ No newline at end of file diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index e0554d3..3c028a0 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -17,46 +17,90 @@ jobs: submodules: recursive - name: "Install and build" uses: ./.github/actions/install-build - + - name: Copy bootloader to output folder + run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin .pio + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + retention-days: 1 + name: prepared-outputs + path: .pio/**/*.bin + merge: + runs-on: ubuntu-latest + permissions: + contents: write + checks: write + needs: build + continue-on-error: true + strategy: + matrix: + chip: + - name: lolin_s3_mini + version: esp32s3 + epd_variant: [213epd, 29epd] + steps: + - uses: actions/download-artifact@v4 + with: + name: prepared-outputs + path: .pio - name: Install esptools.py run: pip install --upgrade esptool - name: Create merged firmware binary - run: mkdir -p output && esptool.py --chip esp32s3 merge_bin -o output/full-firmware.bin --flash_mode dio 0x0000 .pio/build/lolin_s3_mini_qr/bootloader.bin 0x8000 .pio/build/lolin_s3_mini_qr/partitions.bin 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/lolin_s3_mini_qr/firmware.bin 0x369000 .pio/build/lolin_s3_mini_qr/littlefs.bin + run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin - name: Create checksum for merged binary - run: shasum -a 256 output/full-firmware.bin | awk '{print $1}' > output/full-firmware.sha256 + run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.sha256 - - name: Write commit hash to file - run: echo $GITHUB_SHA > output/commit.txt - - - name: Write build date to file - run: echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" > output/date.txt - - - name: Copy all artifacts to output folder - run: cp .pio/build/lolin_s3_mini_qr/*.bin ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin output + # - name: Copy all artifacts to output folder + # run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: + name: build-${{ matrix.chip.name }}-${{ matrix.epd_variant }} path: | - .pio/build/lolin_s3_mini_qr/*.bin - output/full-firmware.bin - output/full-firmware.sha256 + ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin + ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.sha256 + release: + runs-on: ubuntu-latest + permissions: + contents: write + checks: write + needs: build + steps: + - name: Download matrix outputs + uses: actions/download-artifact@v4 + with: + pattern: build-* + merge-multiple: true + - name: Write commit hash to file + run: mkdir -p lolin_s3_mini_213epd && echo $GITHUB_SHA > lolin_s3_mini_213epd/commit.txt + + - name: Write build date to file + run: echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" > lolin_s3_mini_213epd/date.txt + - name: Create release uses: ncipollo/release-action@v1 with: - artifacts: "output/full-firmware.bin,output/full-firmware.sha256,.pio/build/lolin_s3_mini_qr/*.bin" + artifacts: "**/*.bin,**/*.sha256" allowUpdates: true removeArtifacts: true makeLatest: true + # - name: Create release + # uses: ncipollo/release-action@v1 + # with: + # artifacts: "output/full-firmware.bin,output/full-firmware.sha256,.pio/build/lolin_s3_mini_qr/*.bin" + # allowUpdates: true + # removeArtifacts: true + # makeLatest: true - name: Pushes full-firmware.bin to web flasher id: push_directory uses: cpina/github-action-push-to-another-repository@main env: SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} with: - source-directory: output/ + source-directory: lolin_s3_mini_213epd/ target-directory: firmware_v3/ destination-github-username: 'btclock' destination-repository-name: 'web-flasher' diff --git a/platformio.ini b/platformio.ini index ce953dd..117ff4f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,6 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] data_dir = data/build_gz +default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd [env] @@ -55,12 +56,21 @@ build_flags = build_unflags = ${btclock_base.build_unflags} -[env:lolin_s3_mini_qr] +[env:lolin_s3_mini_213epd] extends = env:lolin_s3_mini test_framework = unity build_flags = ${env:lolin_s3_mini.build_flags} -D USE_QR + -D VERSION_EPD_2_13 + +[env:lolin_s3_mini_29epd] +extends = env:lolin_s3_mini +test_framework = unity +build_flags = + ${env:lolin_s3_mini.build_flags} + -D USE_QR + -D VERSION_EPD_2_9 [env:btclock_s3] extends = btclock_base diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index d4d579b..0f4a229 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -67,16 +67,16 @@ MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { #endif -GxEPD2_BW displays[NUM_SCREENS] = { - GxEPD2_213_B74(&EPD_CS[0], &EPD_DC, &EPD_RESET_MPD[0], &EPD_BUSY[0]), - GxEPD2_213_B74(&EPD_CS[1], &EPD_DC, &EPD_RESET_MPD[1], &EPD_BUSY[1]), - GxEPD2_213_B74(&EPD_CS[2], &EPD_DC, &EPD_RESET_MPD[2], &EPD_BUSY[2]), - GxEPD2_213_B74(&EPD_CS[3], &EPD_DC, &EPD_RESET_MPD[3], &EPD_BUSY[3]), - GxEPD2_213_B74(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]), - GxEPD2_213_B74(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]), - GxEPD2_213_B74(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]), +GxEPD2_BW displays[NUM_SCREENS] = { + EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET_MPD[0], &EPD_BUSY[0]), + EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET_MPD[1], &EPD_BUSY[1]), + EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET_MPD[2], &EPD_BUSY[2]), + EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET_MPD[3], &EPD_BUSY[3]), + EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]), + EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]), + EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]), #ifdef IS_BTCLOCK_S3 - GxEPD2_213_B74(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[7]), + EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[7]), #endif }; diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 9f99c19..65e1009 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -16,6 +18,14 @@ extern Adafruit_MCP23X17 mcp2; extern Preferences preferences; extern std::mutex mcpMutex; +#ifdef VERSION_EPD_2_13 + #define EPD_CLASS GxEPD2_213_B74 +#endif + +#ifdef VERSION_EPD_2_9 + #define EPD_CLASS GxEPD2_290_T94 +#endif + const PROGMEM int SCREEN_BLOCK_HEIGHT = 0; const PROGMEM int SCREEN_MSCW_TIME = 1; const PROGMEM int SCREEN_BTC_TICKER = 2; From 3521d8c8567b0d963c1d9f48de67f25c5e433afa Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 19:34:25 +0100 Subject: [PATCH 009/188] Correct job dependencies in workflow --- .github/workflows/tagging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 3c028a0..fe9fc45 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -67,7 +67,7 @@ jobs: permissions: contents: write checks: write - needs: build + needs: merge steps: - name: Download matrix outputs uses: actions/download-artifact@v4 From 55accd07f36f061c79fd63814f3af8fa07819075 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 20:22:58 +0100 Subject: [PATCH 010/188] Fix LitteFS dependency error --- CMakeLists.txt | 2 +- components/esp_littlefs | 1 + dependencies.lock | 9 +-------- esp_littlefs | 1 + platformio.ini | 1 + src/idf_component.yml | 5 +---- 6 files changed, 6 insertions(+), 13 deletions(-) create mode 160000 components/esp_littlefs create mode 160000 esp_littlefs diff --git a/CMakeLists.txt b/CMakeLists.txt index 36d853f..8f4c56f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ cmake_minimum_required(VERSION 3.16.0) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -list(APPEND EXTRA_COMPONENT_DIRS managed_components) +list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/.pio/libdeps/${configName}/esp_littlefs") project(btclock_espidf) diff --git a/components/esp_littlefs b/components/esp_littlefs new file mode 160000 index 0000000..fd64733 --- /dev/null +++ b/components/esp_littlefs @@ -0,0 +1 @@ +Subproject commit fd64733cdf248c7a7eb207db7d28124f8857fe0b diff --git a/dependencies.lock b/dependencies.lock index b815904..c645bdf 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -1,16 +1,9 @@ dependencies: - esp_littlefs: - component_hash: 6ae78edac4f81c605d6d7bff75f4f9a45d25938e4796347f91ea975ed3123326 - source: - git: https://github.com/joltwallet/esp_littlefs.git - path: . - type: git - version: fd64733cdf248c7a7eb207db7d28124f8857fe0b idf: component_hash: null source: type: idf version: 4.4.6 -manifest_hash: 4b13ff241ec4d36ca2303b885c7088c32d74d090ef8e0ca6ea4c7d53047011d6 +manifest_hash: f4c10dfb616cf7e24f85cb263b8c89ef7d6d8eee64860fd27097b1a83ba56960 target: esp32s3 version: 1.0.0 diff --git a/esp_littlefs b/esp_littlefs new file mode 160000 index 0000000..fd64733 --- /dev/null +++ b/esp_littlefs @@ -0,0 +1 @@ +Subproject commit fd64733cdf248c7a7eb207db7d28124f8857fe0b diff --git a/platformio.ini b/platformio.ini index 117ff4f..dffad22 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,6 +31,7 @@ build_unflags = -Werror=all -fno-exceptions lib_deps = + https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.0.3 esphome/Improv@^1.2.3 mathieucarbou/ESP Async WebServer diff --git a/src/idf_component.yml b/src/idf_component.yml index f3bf4e7..de8a1d9 100644 --- a/src/idf_component.yml +++ b/src/idf_component.yml @@ -1,6 +1,3 @@ dependencies: # Required IDF version - idf: ">=4.4" - - esp_littlefs: - git: https://github.com/joltwallet/esp_littlefs.git + idf: ">=4.4" \ No newline at end of file From 46133c2a42db035a6662cd43d78f5652996e27b1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 20:24:10 +0100 Subject: [PATCH 011/188] Add missing files --- components/esp_littlefs | 1 - dependencies.lock | 2 +- src/lib/littlefs/lfs.c | 6457 +++++++++++++++++++++++++++++++++++ src/lib/littlefs/lfs.h | 795 +++++ src/lib/littlefs/lfs_util.c | 37 + src/lib/littlefs/lfs_util.h | 255 ++ 6 files changed, 7545 insertions(+), 2 deletions(-) delete mode 160000 components/esp_littlefs create mode 100644 src/lib/littlefs/lfs.c create mode 100644 src/lib/littlefs/lfs.h create mode 100644 src/lib/littlefs/lfs_util.c create mode 100644 src/lib/littlefs/lfs_util.h diff --git a/components/esp_littlefs b/components/esp_littlefs deleted file mode 160000 index fd64733..0000000 --- a/components/esp_littlefs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fd64733cdf248c7a7eb207db7d28124f8857fe0b diff --git a/dependencies.lock b/dependencies.lock index c645bdf..e899dde 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.6 -manifest_hash: f4c10dfb616cf7e24f85cb263b8c89ef7d6d8eee64860fd27097b1a83ba56960 +manifest_hash: ba1e2b36b26ea70c5dc2f8e8eaa05905e8d5374788d5958d051531d768a8e6da target: esp32s3 version: 1.0.0 diff --git a/src/lib/littlefs/lfs.c b/src/lib/littlefs/lfs.c new file mode 100644 index 0000000..a0bd76f --- /dev/null +++ b/src/lib/littlefs/lfs.c @@ -0,0 +1,6457 @@ +/* + * The little filesystem + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs.h" +#include "lfs_util.h" + + +// some constants used throughout the code +#define LFS_BLOCK_NULL ((lfs_block_t)-1) +#define LFS_BLOCK_INLINE ((lfs_block_t)-2) + +enum { + LFS_OK_RELOCATED = 1, + LFS_OK_DROPPED = 2, + LFS_OK_ORPHANED = 3, +}; + +enum { + LFS_CMP_EQ = 0, + LFS_CMP_LT = 1, + LFS_CMP_GT = 2, +}; + + +/// Caching block device operations /// + +static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { + // do not zero, cheaper if cache is readonly or only going to be + // written with identical data (during relocates) + (void)lfs; + rcache->block = LFS_BLOCK_NULL; +} + +static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { + // zero to avoid information leak + memset(pcache->buffer, 0xff, lfs->cfg->cache_size); + pcache->block = LFS_BLOCK_NULL; +} + +static int lfs_bd_read(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (off+size > lfs->cfg->block_size + || (lfs->block_count && block >= lfs->block_count)) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && block == pcache->block && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (block == rcache->block && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // rcache takes priority + diff = lfs_min(diff, rcache->off-off); + } + + if (size >= hint && off % lfs->cfg->read_size == 0 && + size >= lfs->cfg->read_size) { + // bypass cache? + diff = lfs_aligndown(diff, lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + LFS_ASSERT(!lfs->block_count || block < lfs->block_count); + rcache->block = block; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min( + lfs_min( + lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->block_size) + - rcache->off, + lfs->cfg->cache_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, + rcache->off, rcache->buffer, rcache->size); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_bd_cmp(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t diff = 0; + + for (lfs_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + + diff = lfs_min(size-i, sizeof(dat)); + int err = lfs_bd_read(lfs, + pcache, rcache, hint-i, + block, off+i, &dat, diff); + if (err) { + return err; + } + + int res = memcmp(dat, data + i, diff); + if (res) { + return res < 0 ? LFS_CMP_LT : LFS_CMP_GT; + } + } + + return LFS_CMP_EQ; +} + +static int lfs_bd_crc(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) { + lfs_size_t diff = 0; + + for (lfs_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + diff = lfs_min(size-i, sizeof(dat)); + int err = lfs_bd_read(lfs, + pcache, rcache, hint-i, + block, off+i, &dat, diff); + if (err) { + return err; + } + + *crc = lfs_crc(*crc, &dat, diff); + } + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_bd_flush(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) { + LFS_ASSERT(pcache->block < lfs->block_count); + lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, pcache->block, + pcache->off, pcache->buffer, diff); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + if (validate) { + // check data on disk + lfs_cache_drop(lfs, rcache); + int res = lfs_bd_cmp(lfs, + NULL, rcache, diff, + pcache->block, pcache->off, pcache->buffer, diff); + if (res < 0) { + return res; + } + + if (res != LFS_CMP_EQ) { + return LFS_ERR_CORRUPT; + } + } + + lfs_cache_zero(lfs, pcache); + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_bd_sync(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + lfs_cache_drop(lfs, rcache); + + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + + err = lfs->cfg->sync(lfs->cfg); + LFS_ASSERT(err <= 0); + return err; +} +#endif + +#ifndef LFS_READONLY +static int lfs_bd_prog(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count); + LFS_ASSERT(off + size <= lfs->cfg->block_size); + + while (size > 0) { + if (block == pcache->block && + off >= pcache->off && + off < pcache->off + lfs->cfg->cache_size) { + // already fits in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->cache_size - (off-pcache->off)); + memcpy(&pcache->buffer[off-pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + pcache->size = lfs_max(pcache->size, off - pcache->off); + if (pcache->size == lfs->cfg->cache_size) { + // eagerly flush out pcache if we fill up + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + } + + continue; + } + + // pcache must have been flushed, either by programming and + // entire block or manually flushing the pcache + LFS_ASSERT(pcache->block == LFS_BLOCK_NULL); + + // prepare pcache, first condition can no longer fail + pcache->block = block; + pcache->off = lfs_aligndown(off, lfs->cfg->prog_size); + pcache->size = 0; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + LFS_ASSERT(block < lfs->block_count); + int err = lfs->cfg->erase(lfs->cfg, block); + LFS_ASSERT(err <= 0); + return err; +} +#endif + + +/// Small type-level utilities /// +// operations on block pairs +static inline void lfs_pair_swap(lfs_block_t pair[2]) { + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pair_isnull(const lfs_block_t pair[2]) { + return pair[0] == LFS_BLOCK_NULL || pair[1] == LFS_BLOCK_NULL; +} + +static inline int lfs_pair_cmp( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pair_issync( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static inline void lfs_pair_fromle32(lfs_block_t pair[2]) { + pair[0] = lfs_fromle32(pair[0]); + pair[1] = lfs_fromle32(pair[1]); +} + +#ifndef LFS_READONLY +static inline void lfs_pair_tole32(lfs_block_t pair[2]) { + pair[0] = lfs_tole32(pair[0]); + pair[1] = lfs_tole32(pair[1]); +} +#endif + +// operations on 32-bit entry tags +typedef uint32_t lfs_tag_t; +typedef int32_t lfs_stag_t; + +#define LFS_MKTAG(type, id, size) \ + (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size)) + +#define LFS_MKTAG_IF(cond, type, id, size) \ + ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0)) + +#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ + ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2)) + +static inline bool lfs_tag_isvalid(lfs_tag_t tag) { + return !(tag & 0x80000000); +} + +static inline bool lfs_tag_isdelete(lfs_tag_t tag) { + return ((int32_t)(tag << 22) >> 22) == -1; +} + +static inline uint16_t lfs_tag_type1(lfs_tag_t tag) { + return (tag & 0x70000000) >> 20; +} + +static inline uint16_t lfs_tag_type2(lfs_tag_t tag) { + return (tag & 0x78000000) >> 20; +} + +static inline uint16_t lfs_tag_type3(lfs_tag_t tag) { + return (tag & 0x7ff00000) >> 20; +} + +static inline uint8_t lfs_tag_chunk(lfs_tag_t tag) { + return (tag & 0x0ff00000) >> 20; +} + +static inline int8_t lfs_tag_splice(lfs_tag_t tag) { + return (int8_t)lfs_tag_chunk(tag); +} + +static inline uint16_t lfs_tag_id(lfs_tag_t tag) { + return (tag & 0x000ffc00) >> 10; +} + +static inline lfs_size_t lfs_tag_size(lfs_tag_t tag) { + return tag & 0x000003ff; +} + +static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) { + return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag)); +} + +// operations on attributes in attribute lists +struct lfs_mattr { + lfs_tag_t tag; + const void *buffer; +}; + +struct lfs_diskoff { + lfs_block_t block; + lfs_off_t off; +}; + +#define LFS_MKATTRS(...) \ + (struct lfs_mattr[]){__VA_ARGS__}, \ + sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr) + +// operations on global state +static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { + for (int i = 0; i < 3; i++) { + ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; + } +} + +static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) { + for (int i = 0; i < 3; i++) { + if (((uint32_t*)a)[i] != 0) { + return false; + } + } + return true; +} + +#ifndef LFS_READONLY +static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag); +} + +static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag) & 0x1ff; +} + +static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { + return lfs_tag_type1(a->tag); +} +#endif + +static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag) >> 9; +} + +static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, + const lfs_block_t *pair) { + return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; +} + +static inline void lfs_gstate_fromle32(lfs_gstate_t *a) { + a->tag = lfs_fromle32(a->tag); + a->pair[0] = lfs_fromle32(a->pair[0]); + a->pair[1] = lfs_fromle32(a->pair[1]); +} + +#ifndef LFS_READONLY +static inline void lfs_gstate_tole32(lfs_gstate_t *a) { + a->tag = lfs_tole32(a->tag); + a->pair[0] = lfs_tole32(a->pair[0]); + a->pair[1] = lfs_tole32(a->pair[1]); +} +#endif + +// operations on forward-CRCs used to track erased state +struct lfs_fcrc { + lfs_size_t size; + uint32_t crc; +}; + +static void lfs_fcrc_fromle32(struct lfs_fcrc *fcrc) { + fcrc->size = lfs_fromle32(fcrc->size); + fcrc->crc = lfs_fromle32(fcrc->crc); +} + +#ifndef LFS_READONLY +static void lfs_fcrc_tole32(struct lfs_fcrc *fcrc) { + fcrc->size = lfs_tole32(fcrc->size); + fcrc->crc = lfs_tole32(fcrc->crc); +} +#endif + +// other endianness operations +static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { + ctz->head = lfs_fromle32(ctz->head); + ctz->size = lfs_fromle32(ctz->size); +} + +#ifndef LFS_READONLY +static void lfs_ctz_tole32(struct lfs_ctz *ctz) { + ctz->head = lfs_tole32(ctz->head); + ctz->size = lfs_tole32(ctz->size); +} +#endif + +static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) { + superblock->version = lfs_fromle32(superblock->version); + superblock->block_size = lfs_fromle32(superblock->block_size); + superblock->block_count = lfs_fromle32(superblock->block_count); + superblock->name_max = lfs_fromle32(superblock->name_max); + superblock->file_max = lfs_fromle32(superblock->file_max); + superblock->attr_max = lfs_fromle32(superblock->attr_max); +} + +#ifndef LFS_READONLY +static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) { + superblock->version = lfs_tole32(superblock->version); + superblock->block_size = lfs_tole32(superblock->block_size); + superblock->block_count = lfs_tole32(superblock->block_count); + superblock->name_max = lfs_tole32(superblock->name_max); + superblock->file_max = lfs_tole32(superblock->file_max); + superblock->attr_max = lfs_tole32(superblock->attr_max); +} +#endif + +#ifndef LFS_NO_ASSERT +static bool lfs_mlist_isopen(struct lfs_mlist *head, + struct lfs_mlist *node) { + for (struct lfs_mlist **p = &head; *p; p = &(*p)->next) { + if (*p == (struct lfs_mlist*)node) { + return true; + } + } + + return false; +} +#endif + +static void lfs_mlist_remove(lfs_t *lfs, struct lfs_mlist *mlist) { + for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { + if (*p == mlist) { + *p = (*p)->next; + break; + } + } +} + +static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) { + mlist->next = lfs->mlist; + lfs->mlist = mlist; +} + +// some other filesystem operations +static uint32_t lfs_fs_disk_version(lfs_t *lfs) { + (void)lfs; +#ifdef LFS_MULTIVERSION + if (lfs->cfg->disk_version) { + return lfs->cfg->disk_version; + } else +#endif + { + return LFS_DISK_VERSION; + } +} + +static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 16); + +} + +static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 0); +} + + +/// Internal operations predeclared here /// +#ifndef LFS_READONLY +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount); +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end); +static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file); +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file); +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); + +static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss); +static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]); +static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *pdir); +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *parent); +static int lfs_fs_forceconsistency(lfs_t *lfs); +#endif + +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock); + +#ifdef LFS_MIGRATE +static int lfs1_traverse(lfs_t *lfs, + int (*cb)(void*, lfs_block_t), void *data); +#endif + +static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir); + +static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); +static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); +static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file); +static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file); + +static lfs_ssize_t lfs_fs_size_(lfs_t *lfs); +static int lfs_fs_traverse_(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans); + +static int lfs_deinit(lfs_t *lfs); +static int lfs_unmount_(lfs_t *lfs); + + +/// Block allocator /// + +// allocations should call this when all allocated blocks are committed to +// the filesystem +// +// after a checkpoint, the block allocator may realloc any untracked blocks +static void lfs_alloc_ckpoint(lfs_t *lfs) { + lfs->lookahead.ckpoint = lfs->block_count; +} + +// drop the lookahead buffer, this is done during mounting and failed +// traversals in order to avoid invalid lookahead state +static void lfs_alloc_drop(lfs_t *lfs) { + lfs->lookahead.size = 0; + lfs->lookahead.next = 0; + lfs_alloc_ckpoint(lfs); +} + +#ifndef LFS_READONLY +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = (lfs_t*)p; + lfs_block_t off = ((block - lfs->lookahead.start) + + lfs->block_count) % lfs->block_count; + + if (off < lfs->lookahead.size) { + lfs->lookahead.buffer[off / 8] |= 1U << (off % 8); + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_alloc_scan(lfs_t *lfs) { + // move lookahead buffer to the first unused block + // + // note we limit the lookahead buffer to at most the amount of blocks + // checkpointed, this prevents the math in lfs_alloc from underflowing + lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count; + lfs->lookahead.next = 0; + lfs->lookahead.size = lfs_min( + 8*lfs->cfg->lookahead_size, + lfs->lookahead.ckpoint); + + // find mask of free blocks from tree + memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); + int err = lfs_fs_traverse_(lfs, lfs_alloc_lookahead, lfs, true); + if (err) { + lfs_alloc_drop(lfs); + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + while (true) { + // scan our lookahead buffer for free blocks + while (lfs->lookahead.next < lfs->lookahead.size) { + if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8] + & (1U << (lfs->lookahead.next % 8)))) { + // found a free block + *block = (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count; + + // eagerly find next free block to maximize how many blocks + // lfs_alloc_ckpoint makes available for scanning + while (true) { + lfs->lookahead.next += 1; + lfs->lookahead.ckpoint -= 1; + + if (lfs->lookahead.next >= lfs->lookahead.size + || !(lfs->lookahead.buffer[lfs->lookahead.next / 8] + & (1U << (lfs->lookahead.next % 8)))) { + return 0; + } + } + } + + lfs->lookahead.next += 1; + lfs->lookahead.ckpoint -= 1; + } + + // In order to keep our block allocator from spinning forever when our + // filesystem is full, we mark points where there are no in-flight + // allocations with a checkpoint before starting a set of allocations. + // + // If we've looked at all blocks since the last checkpoint, we report + // the filesystem as out of storage. + // + if (lfs->lookahead.ckpoint <= 0) { + LFS_ERROR("No more free space 0x%"PRIx32, + (lfs->lookahead.start + lfs->lookahead.next) + % lfs->cfg->block_count); + return LFS_ERR_NOSPC; + } + + // No blocks in our lookahead buffer, we need to scan the filesystem for + // unused blocks in the next lookahead window. + int err = lfs_alloc_scan(lfs); + if(err) { + return err; + } + } +} +#endif + +/// Metadata pair and directory operations /// +static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t goff, void *gbuffer, lfs_size_t gsize) { + lfs_off_t off = dir->off; + lfs_tag_t ntag = dir->etag; + lfs_stag_t gdiff = 0; + + // synthetic moves + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) && + lfs_tag_id(gmask) != 0) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(gtag)) { + return LFS_ERR_NOENT; + } else if (lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(gtag)) { + gdiff -= LFS_MKTAG(0, 1, 0); + } + } + + // iterate over dir block backwards (for faster lookups) + while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) { + off -= lfs_tag_dsize(ntag); + lfs_tag_t tag = ntag; + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(ntag), + dir->pair[0], off, &ntag, sizeof(ntag)); + if (err) { + return err; + } + + ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff; + + if (lfs_tag_id(gmask) != 0 && + lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) { + if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) { + // found where we were created + return LFS_ERR_NOENT; + } + + // move around splices + gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + if ((gmask & tag) == (gmask & (gtag - gdiff))) { + if (lfs_tag_isdelete(tag)) { + return LFS_ERR_NOENT; + } + + lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize); + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, diff, + dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff); + if (err) { + return err; + } + + memset((uint8_t*)gbuffer + diff, 0, gsize - diff); + + return tag + gdiff; + } + } + + return LFS_ERR_NOENT; +} + +static lfs_stag_t lfs_dir_get(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, void *buffer) { + return lfs_dir_getslice(lfs, dir, + gmask, gtag, + 0, buffer, lfs_tag_size(gtag)); +} + +static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t off, void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (off+size > lfs->cfg->block_size) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && pcache->block == LFS_BLOCK_INLINE && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (rcache->block == LFS_BLOCK_INLINE && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // rcache takes priority + diff = lfs_min(diff, rcache->off-off); + } + + // load to cache, first condition can no longer fail + rcache->block = LFS_BLOCK_INLINE; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min(lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->cache_size); + int err = lfs_dir_getslice(lfs, dir, gmask, gtag, + rcache->off, rcache->buffer, rcache->size); + if (err < 0) { + return err; + } + } + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_dir_traverse_filter(void *p, + lfs_tag_t tag, const void *buffer) { + lfs_tag_t *filtertag = p; + (void)buffer; + + // which mask depends on unique bit in tag structure + uint32_t mask = (tag & LFS_MKTAG(0x100, 0, 0)) + ? LFS_MKTAG(0x7ff, 0x3ff, 0) + : LFS_MKTAG(0x700, 0x3ff, 0); + + // check for redundancy + if ((mask & tag) == (mask & *filtertag) || + lfs_tag_isdelete(*filtertag) || + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( + LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { + *filtertag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0); + return true; + } + + // check if we need to adjust for created/deleted tags + if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(*filtertag)) { + *filtertag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + return false; +} +#endif + +#ifndef LFS_READONLY +// maximum recursive depth of lfs_dir_traverse, the deepest call: +// +// traverse with commit +// '-> traverse with move +// '-> traverse with filter +// +#define LFS_DIR_TRAVERSE_DEPTH 3 + +struct lfs_dir_traverse { + const lfs_mdir_t *dir; + lfs_off_t off; + lfs_tag_t ptag; + const struct lfs_mattr *attrs; + int attrcount; + + lfs_tag_t tmask; + lfs_tag_t ttag; + uint16_t begin; + uint16_t end; + int16_t diff; + + int (*cb)(void *data, lfs_tag_t tag, const void *buffer); + void *data; + + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk; +}; + +static int lfs_dir_traverse(lfs_t *lfs, + const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, + const struct lfs_mattr *attrs, int attrcount, + lfs_tag_t tmask, lfs_tag_t ttag, + uint16_t begin, uint16_t end, int16_t diff, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // This function in inherently recursive, but bounded. To allow tool-based + // analysis without unnecessary code-cost we use an explicit stack + struct lfs_dir_traverse stack[LFS_DIR_TRAVERSE_DEPTH-1]; + unsigned sp = 0; + int res; + + // iterate over directory and attrs + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk = {0}; + while (true) { + { + if (off+lfs_tag_dsize(ptag) < dir->off) { + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(tag), + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + return err; + } + + tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000; + disk.block = dir->pair[0]; + disk.off = off+sizeof(lfs_tag_t); + buffer = &disk; + ptag = tag; + } else if (attrcount > 0) { + tag = attrs[0].tag; + buffer = attrs[0].buffer; + attrs += 1; + attrcount -= 1; + } else { + // finished traversal, pop from stack? + res = 0; + break; + } + + // do we need to filter? + lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); + if ((mask & tmask & tag) != (mask & tmask & ttag)) { + continue; + } + + if (lfs_tag_id(tmask) != 0) { + LFS_ASSERT(sp < LFS_DIR_TRAVERSE_DEPTH); + // recurse, scan for duplicates, and update tag based on + // creates/deletes + stack[sp] = (struct lfs_dir_traverse){ + .dir = dir, + .off = off, + .ptag = ptag, + .attrs = attrs, + .attrcount = attrcount, + .tmask = tmask, + .ttag = ttag, + .begin = begin, + .end = end, + .diff = diff, + .cb = cb, + .data = data, + .tag = tag, + .buffer = buffer, + .disk = disk, + }; + sp += 1; + + tmask = 0; + ttag = 0; + begin = 0; + end = 0; + diff = 0; + cb = lfs_dir_traverse_filter; + data = &stack[sp-1].tag; + continue; + } + } + +popped: + // in filter range? + if (lfs_tag_id(tmask) != 0 && + !(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { + continue; + } + + // handle special cases for mcu-side operations + if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { + // do nothing + } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { + // Without this condition, lfs_dir_traverse can exhibit an + // extremely expensive O(n^3) of nested loops when renaming. + // This happens because lfs_dir_traverse tries to filter tags by + // the tags in the source directory, triggering a second + // lfs_dir_traverse with its own filter operation. + // + // traverse with commit + // '-> traverse with filter + // '-> traverse with move + // '-> traverse with filter + // + // However we don't actually care about filtering the second set of + // tags, since duplicate tags have no effect when filtering. + // + // This check skips this unnecessary recursive filtering explicitly, + // reducing this runtime from O(n^3) to O(n^2). + if (cb == lfs_dir_traverse_filter) { + continue; + } + + // recurse into move + stack[sp] = (struct lfs_dir_traverse){ + .dir = dir, + .off = off, + .ptag = ptag, + .attrs = attrs, + .attrcount = attrcount, + .tmask = tmask, + .ttag = ttag, + .begin = begin, + .end = end, + .diff = diff, + .cb = cb, + .data = data, + .tag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0), + }; + sp += 1; + + uint16_t fromid = lfs_tag_size(tag); + uint16_t toid = lfs_tag_id(tag); + dir = buffer; + off = 0; + ptag = 0xffffffff; + attrs = NULL; + attrcount = 0; + tmask = LFS_MKTAG(0x600, 0x3ff, 0); + ttag = LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0); + begin = fromid; + end = fromid+1; + diff = toid-fromid+diff; + } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { + for (unsigned i = 0; i < lfs_tag_size(tag); i++) { + const struct lfs_attr *a = buffer; + res = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, + lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); + if (res < 0) { + return res; + } + + if (res) { + break; + } + } + } else { + res = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); + if (res < 0) { + return res; + } + + if (res) { + break; + } + } + } + + if (sp > 0) { + // pop from the stack and return, fortunately all pops share + // a destination + dir = stack[sp-1].dir; + off = stack[sp-1].off; + ptag = stack[sp-1].ptag; + attrs = stack[sp-1].attrs; + attrcount = stack[sp-1].attrcount; + tmask = stack[sp-1].tmask; + ttag = stack[sp-1].ttag; + begin = stack[sp-1].begin; + end = stack[sp-1].end; + diff = stack[sp-1].diff; + cb = stack[sp-1].cb; + data = stack[sp-1].data; + tag = stack[sp-1].tag; + buffer = stack[sp-1].buffer; + disk = stack[sp-1].disk; + sp -= 1; + goto popped; + } else { + return res; + } +} +#endif + +static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2], + lfs_tag_t fmask, lfs_tag_t ftag, uint16_t *id, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // we can find tag very efficiently during a fetch, since we're already + // scanning the entire directory + lfs_stag_t besttag = -1; + + // if either block address is invalid we return LFS_ERR_CORRUPT here, + // otherwise later writes to the pair could fail + if (lfs->block_count + && (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count)) { + return LFS_ERR_CORRUPT; + } + + // find the block with the most recent revision + uint32_t revs[2] = {0, 0}; + int r = 0; + for (int i = 0; i < 2; i++) { + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(revs[i]), + pair[i], 0, &revs[i], sizeof(revs[i])); + revs[i] = lfs_fromle32(revs[i]); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + if (err != LFS_ERR_CORRUPT && + lfs_scmp(revs[i], revs[(i+1)%2]) > 0) { + r = i; + } + } + + dir->pair[0] = pair[(r+0)%2]; + dir->pair[1] = pair[(r+1)%2]; + dir->rev = revs[(r+0)%2]; + dir->off = 0; // nonzero = found some commits + + // now scan tags to fetch the actual dir and find possible match + for (int i = 0; i < 2; i++) { + lfs_off_t off = 0; + lfs_tag_t ptag = 0xffffffff; + + uint16_t tempcount = 0; + lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + bool tempsplit = false; + lfs_stag_t tempbesttag = besttag; + + // assume not erased until proven otherwise + bool maybeerased = false; + bool hasfcrc = false; + struct lfs_fcrc fcrc; + + dir->rev = lfs_tole32(dir->rev); + uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + + while (true) { + // extract next tag + lfs_tag_t tag; + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + // can't continue? + break; + } + return err; + } + + crc = lfs_crc(crc, &tag, sizeof(tag)); + tag = lfs_frombe32(tag) ^ ptag; + + // next commit not yet programmed? + if (!lfs_tag_isvalid(tag)) { + // we only might be erased if the last tag was a crc + maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC); + break; + // out of range? + } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { + break; + } + + ptag = tag; + + if (lfs_tag_type2(tag) == LFS_TYPE_CCRC) { + // check the crc attr + uint32_t dcrc; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + return err; + } + dcrc = lfs_fromle32(dcrc); + + if (crc != dcrc) { + break; + } + + // reset the next bit if we need to + ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31; + + // toss our crc into the filesystem seed for + // pseudorandom numbers, note we use another crc here + // as a collection function because it is sufficiently + // random and convenient + lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(crc)); + + // update with what's found so far + besttag = tempbesttag; + dir->off = off + lfs_tag_dsize(tag); + dir->etag = ptag; + dir->count = tempcount; + dir->tail[0] = temptail[0]; + dir->tail[1] = temptail[1]; + dir->split = tempsplit; + + // reset crc, hasfcrc + crc = 0xffffffff; + continue; + } + + // crc the entry first, hopefully leaving it in the cache + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), + lfs_tag_dsize(tag)-sizeof(tag), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + return err; + } + + // directory modification tags? + if (lfs_tag_type1(tag) == LFS_TYPE_NAME) { + // increase count of files if necessary + if (lfs_tag_id(tag) >= tempcount) { + tempcount = lfs_tag_id(tag) + 1; + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE) { + tempcount += lfs_tag_splice(tag); + + if (tag == (LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & tempbesttag))) { + tempbesttag |= 0x80000000; + } else if (tempbesttag != -1 && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + tempbesttag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_TAIL) { + tempsplit = (lfs_tag_chunk(tag) & 1); + + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &temptail, 8); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + return err; + } + lfs_pair_fromle32(temptail); + } else if (lfs_tag_type3(tag) == LFS_TYPE_FCRC) { + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), + &fcrc, sizeof(fcrc)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + } + + lfs_fcrc_fromle32(&fcrc); + hasfcrc = true; + } + + // found a match for our fetcher? + if ((fmask & tag) == (fmask & ftag)) { + int res = cb(data, tag, &(struct lfs_diskoff){ + dir->pair[0], off+sizeof(tag)}); + if (res < 0) { + if (res == LFS_ERR_CORRUPT) { + break; + } + return res; + } + + if (res == LFS_CMP_EQ) { + // found a match + tempbesttag = tag; + } else if ((LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tempbesttag)) { + // found an identical tag, but contents didn't match + // this must mean that our besttag has been overwritten + tempbesttag = -1; + } else if (res == LFS_CMP_GT && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + // found a greater match, keep track to keep things sorted + tempbesttag = tag | 0x80000000; + } + } + } + + // found no valid commits? + if (dir->off == 0) { + // try the other block? + lfs_pair_swap(dir->pair); + dir->rev = revs[(r+1)%2]; + continue; + } + + // did we end on a valid commit? we may have an erased block + dir->erased = false; + if (maybeerased && dir->off % lfs->cfg->prog_size == 0) { + #ifdef LFS_MULTIVERSION + // note versions < lfs2.1 did not have fcrc tags, if + // we're < lfs2.1 treat missing fcrc as erased data + // + // we don't strictly need to do this, but otherwise writing + // to lfs2.0 disks becomes very inefficient + if (lfs_fs_disk_version(lfs) < 0x00020001) { + dir->erased = true; + + } else + #endif + if (hasfcrc) { + // check for an fcrc matching the next prog's erased state, if + // this failed most likely a previous prog was interrupted, we + // need a new erase + uint32_t fcrc_ = 0xffffffff; + int err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], dir->off, fcrc.size, &fcrc_); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // found beginning of erased part? + dir->erased = (fcrc_ == fcrc.crc); + } + } + + // synthetic move + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) { + besttag |= 0x80000000; + } else if (besttag != -1 && + lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) { + besttag -= LFS_MKTAG(0, 1, 0); + } + } + + // found tag? or found best id? + if (id) { + *id = lfs_min(lfs_tag_id(besttag), dir->count); + } + + if (lfs_tag_isvalid(besttag)) { + return besttag; + } else if (lfs_tag_id(besttag) < dir->count) { + return LFS_ERR_NOENT; + } else { + return 0; + } + } + + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + dir->pair[0], dir->pair[1]); + return LFS_ERR_CORRUPT; +} + +static int lfs_dir_fetch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2]) { + // note, mask=-1, tag=-1 can never match a tag since this + // pattern has the invalid bit set + return (int)lfs_dir_fetchmatch(lfs, dir, pair, + (lfs_tag_t)-1, (lfs_tag_t)-1, NULL, NULL, NULL); +} + +static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_gstate_t *gstate) { + lfs_gstate_t temp; + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0), + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); + if (res < 0 && res != LFS_ERR_NOENT) { + return res; + } + + if (res != LFS_ERR_NOENT) { + // xor together to find resulting gstate + lfs_gstate_fromle32(&temp); + lfs_gstate_xor(gstate, &temp); + } + + return 0; +} + +static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir, + uint16_t id, struct lfs_info *info) { + if (id == 0x3ff) { + // special case for root + strcpy(info->name, "/"); + info->type = LFS_TYPE_DIR; + return 0; + } + + lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name); + if (tag < 0) { + return (int)tag; + } + + info->type = lfs_tag_type3(tag); + + struct lfs_ctz ctz; + tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + return (int)tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + info->size = ctz.size; + } else if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + info->size = lfs_tag_size(tag); + } + + return 0; +} + +struct lfs_dir_find_match { + lfs_t *lfs; + const void *name; + lfs_size_t size; +}; + +static int lfs_dir_find_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_dir_find_match *name = data; + lfs_t *lfs = name->lfs; + const struct lfs_diskoff *disk = buffer; + + // compare with disk + lfs_size_t diff = lfs_min(name->size, lfs_tag_size(tag)); + int res = lfs_bd_cmp(lfs, + NULL, &lfs->rcache, diff, + disk->block, disk->off, name->name, diff); + if (res != LFS_CMP_EQ) { + return res; + } + + // only equal if our size is still the same + if (name->size != lfs_tag_size(tag)) { + return (name->size < lfs_tag_size(tag)) ? LFS_CMP_LT : LFS_CMP_GT; + } + + // found a match! + return LFS_CMP_EQ; +} + +static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, + const char **path, uint16_t *id) { + // we reduce path to a single name if we can find it + const char *name = *path; + if (id) { + *id = 0x3ff; + } + + // default to root dir + lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0); + dir->tail[0] = lfs->root[0]; + dir->tail[1] = lfs->root[1]; + + while (true) { +nextname: + // skip slashes + name += strspn(name, "/"); + lfs_size_t namelen = strcspn(name, "/"); + + // skip '.' and root '..' + if ((namelen == 1 && memcmp(name, ".", 1) == 0) || + (namelen == 2 && memcmp(name, "..", 2) == 0)) { + name += namelen; + goto nextname; + } + + // skip if matched by '..' in name + const char *suffix = name + namelen; + lfs_size_t sufflen; + int depth = 1; + while (true) { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) { + break; + } + + if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + depth -= 1; + if (depth == 0) { + name = suffix + sufflen; + goto nextname; + } + } else { + depth += 1; + } + + suffix += sufflen; + } + + // found path + if (name[0] == '\0') { + return tag; + } + + // update what we've found so far + *path = name; + + // only continue if we hit a directory + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + // grab the entry data + if (lfs_tag_id(tag) != 0x3ff) { + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), dir->tail); + if (res < 0) { + return res; + } + lfs_pair_fromle32(dir->tail); + } + + // find entry matching name + while (true) { + tag = lfs_dir_fetchmatch(lfs, dir, dir->tail, + LFS_MKTAG(0x780, 0, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), + // are we last name? + (strchr(name, '/') == NULL) ? id : NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, name, namelen}); + if (tag < 0) { + return tag; + } + + if (tag) { + break; + } + + if (!dir->split) { + return LFS_ERR_NOENT; + } + } + + // to next name + name += namelen; + } +} + +// commit logic +struct lfs_commit { + lfs_block_t block; + lfs_off_t off; + lfs_tag_t ptag; + uint32_t crc; + + lfs_off_t begin; + lfs_off_t end; +}; + +#ifndef LFS_READONLY +static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit, + const void *buffer, lfs_size_t size) { + int err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off , + (const uint8_t*)buffer, size); + if (err) { + return err; + } + + commit->crc = lfs_crc(commit->crc, buffer, size); + commit->off += size; + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, + lfs_tag_t tag, const void *buffer) { + // check if we fit + lfs_size_t dsize = lfs_tag_dsize(tag); + if (commit->off + dsize > commit->end) { + return LFS_ERR_NOSPC; + } + + // write out tag + lfs_tag_t ntag = lfs_tobe32((tag & 0x7fffffff) ^ commit->ptag); + int err = lfs_dir_commitprog(lfs, commit, &ntag, sizeof(ntag)); + if (err) { + return err; + } + + if (!(tag & 0x80000000)) { + // from memory + err = lfs_dir_commitprog(lfs, commit, buffer, dsize-sizeof(tag)); + if (err) { + return err; + } + } else { + // from disk + const struct lfs_diskoff *disk = buffer; + for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) { + // rely on caching to make this efficient + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dsize-sizeof(tag)-i, + disk->block, disk->off+i, &dat, 1); + if (err) { + return err; + } + + err = lfs_dir_commitprog(lfs, commit, &dat, 1); + if (err) { + return err; + } + } + } + + commit->ptag = tag & 0x7fffffff; + return 0; +} +#endif + +#ifndef LFS_READONLY + +static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { + // align to program units + // + // this gets a bit complex as we have two types of crcs: + // - 5-word crc with fcrc to check following prog (middle of block) + // - 2-word crc with no following prog (end of block) + const lfs_off_t end = lfs_alignup( + lfs_min(commit->off + 5*sizeof(uint32_t), lfs->cfg->block_size), + lfs->cfg->prog_size); + + lfs_off_t off1 = 0; + uint32_t crc1 = 0; + + // create crc tags to fill up remainder of commit, note that + // padding is not crced, which lets fetches skip padding but + // makes committing a bit more complicated + while (commit->off < end) { + lfs_off_t noff = ( + lfs_min(end - (commit->off+sizeof(lfs_tag_t)), 0x3fe) + + (commit->off+sizeof(lfs_tag_t))); + // too large for crc tag? need padding commits + if (noff < end) { + noff = lfs_min(noff, end - 5*sizeof(uint32_t)); + } + + // space for fcrc? + uint8_t eperturb = (uint8_t)-1; + if (noff >= end && noff <= lfs->cfg->block_size - lfs->cfg->prog_size) { + // first read the leading byte, this always contains a bit + // we can perturb to avoid writes that don't change the fcrc + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->prog_size, + commit->block, noff, &eperturb, 1); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + #ifdef LFS_MULTIVERSION + // unfortunately fcrcs break mdir fetching < lfs2.1, so only write + // these if we're a >= lfs2.1 filesystem + if (lfs_fs_disk_version(lfs) <= 0x00020000) { + // don't write fcrc + } else + #endif + { + // find the expected fcrc, don't bother avoiding a reread + // of the eperturb, it should still be in our cache + struct lfs_fcrc fcrc = { + .size = lfs->cfg->prog_size, + .crc = 0xffffffff + }; + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->prog_size, + commit->block, noff, fcrc.size, &fcrc.crc); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + lfs_fcrc_tole32(&fcrc); + err = lfs_dir_commitattr(lfs, commit, + LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)), + &fcrc); + if (err) { + return err; + } + } + } + + // build commit crc + struct { + lfs_tag_t tag; + uint32_t crc; + } ccrc; + lfs_tag_t ntag = LFS_MKTAG( + LFS_TYPE_CCRC + (((uint8_t)~eperturb) >> 7), 0x3ff, + noff - (commit->off+sizeof(lfs_tag_t))); + ccrc.tag = lfs_tobe32(ntag ^ commit->ptag); + commit->crc = lfs_crc(commit->crc, &ccrc.tag, sizeof(lfs_tag_t)); + ccrc.crc = lfs_tole32(commit->crc); + + int err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off, &ccrc, sizeof(ccrc)); + if (err) { + return err; + } + + // keep track of non-padding checksum to verify + if (off1 == 0) { + off1 = commit->off + sizeof(lfs_tag_t); + crc1 = commit->crc; + } + + commit->off = noff; + // perturb valid bit? + commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24); + // reset crc for next commit + commit->crc = 0xffffffff; + + // manually flush here since we don't prog the padding, this confuses + // the caching layer + if (noff >= end || noff >= lfs->pcache.off + lfs->cfg->cache_size) { + // flush buffers + int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); + if (err) { + return err; + } + } + } + + // successful commit, check checksums to make sure + // + // note that we don't need to check padding commits, worst + // case if they are corrupted we would have had to compact anyways + lfs_off_t off = commit->begin; + uint32_t crc = 0xffffffff; + int err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, off1+sizeof(uint32_t), + commit->block, off, off1-off, &crc); + if (err) { + return err; + } + + // check non-padding commits against known crc + if (crc != crc1) { + return LFS_ERR_CORRUPT; + } + + // make sure to check crc in case we happen to pick + // up an unrelated crc (frozen block?) + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, sizeof(uint32_t), + commit->block, off1, sizeof(uint32_t), &crc); + if (err) { + return err; + } + + if (crc != 0) { + return LFS_ERR_CORRUPT; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { + // allocate pair of dir blocks (backwards, so we write block 1 first) + for (int i = 0; i < 2; i++) { + int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]); + if (err) { + return err; + } + } + + // zero for reproducibility in case initial block is unreadable + dir->rev = 0; + + // rather than clobbering one of the blocks we just pretend + // the revision may be valid + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(dir->rev), + dir->pair[0], 0, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // to make sure we don't immediately evict, align the new revision count + // to our block_cycles modulus, see lfs_dir_compact for why our modulus + // is tweaked this way + if (lfs->cfg->block_cycles > 0) { + dir->rev = lfs_alignup(dir->rev, ((lfs->cfg->block_cycles+1)|1)); + } + + // set defaults + dir->off = sizeof(dir->rev); + dir->etag = 0xffffffff; + dir->count = 0; + dir->tail[0] = LFS_BLOCK_NULL; + dir->tail[1] = LFS_BLOCK_NULL; + dir->erased = false; + dir->split = false; + + // don't write out yet, let caller take care of that + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) { + // steal state + int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail + lfs_pair_tole32(tail->tail); + err = lfs_dir_commit(lfs, dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail})); + lfs_pair_fromle32(tail->tail); + if (err) { + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_split(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t split, uint16_t end) { + // create tail metadata pair + lfs_mdir_t tail; + int err = lfs_dir_alloc(lfs, &tail); + if (err) { + return err; + } + + tail.split = dir->split; + tail.tail[0] = dir->tail[0]; + tail.tail[1] = dir->tail[1]; + + // note we don't care about LFS_OK_RELOCATED + int res = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end); + if (res < 0) { + return res; + } + + dir->tail[0] = tail.pair[0]; + dir->tail[1] = tail.pair[1]; + dir->split = true; + + // update root if needed + if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && split == 0) { + lfs->root[0] = tail.pair[0]; + lfs->root[1] = tail.pair[1]; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) { + lfs_size_t *size = p; + (void)buffer; + + *size += lfs_tag_dsize(tag); + return 0; +} +#endif + +#ifndef LFS_READONLY +struct lfs_dir_commit_commit { + lfs_t *lfs; + struct lfs_commit *commit; +}; +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) { + struct lfs_dir_commit_commit *commit = p; + return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer); +} +#endif + +#ifndef LFS_READONLY +static bool lfs_dir_needsrelocation(lfs_t *lfs, lfs_mdir_t *dir) { + // If our revision count == n * block_cycles, we should force a relocation, + // this is how littlefs wear-levels at the metadata-pair level. Note that we + // actually use (block_cycles+1)|1, this is to avoid two corner cases: + // 1. block_cycles = 1, which would prevent relocations from terminating + // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate + // one metadata block in the pair, effectively making this useless + return (lfs->cfg->block_cycles > 0 + && ((dir->rev + 1) % ((lfs->cfg->block_cycles+1)|1) == 0)); +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end) { + // save some state in case block is bad + bool relocated = false; + bool tired = lfs_dir_needsrelocation(lfs, dir); + + // increment revision count + dir->rev += 1; + + // do not proactively relocate blocks during migrations, this + // can cause a number of failure states such: clobbering the + // v1 superblock if we relocate root, and invalidating directory + // pointers if we relocate the head of a directory. On top of + // this, relocations increase the overall complexity of + // lfs_migration, which is already a delicate operation. +#ifdef LFS_MIGRATE + if (lfs->lfs1) { + tired = false; + } +#endif + + if (tired && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) != 0) { + // we're writing too much, time to relocate + goto relocate; + } + + // begin loop to commit compaction to blocks until a compact sticks + while (true) { + { + // setup commit state + struct lfs_commit commit = { + .block = dir->pair[1], + .off = 0, + .ptag = 0xffffffff, + .crc = 0xffffffff, + + .begin = 0, + .end = (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, + }; + + // erase block to write to + int err = lfs_bd_erase(lfs, dir->pair[1]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // write out header + dir->rev = lfs_tole32(dir->rev); + err = lfs_dir_commitprog(lfs, &commit, + &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // traverse the directory, this time writing out all unique tags + err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + begin, end, -begin, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // commit tail, which may be new after last size check + if (!lfs_pair_isnull(dir->tail)) { + lfs_pair_tole32(dir->tail); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), + dir->tail); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // bring over gstate? + lfs_gstate_t delta = {0}; + if (!relocated) { + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + } + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + if (!lfs_gstate_iszero(&delta)) { + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // complete commit with crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // successful compaction, swap dir pair to indicate most recent + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + lfs_pair_swap(dir->pair); + dir->count = end - begin; + dir->off = commit.off; + dir->etag = commit.ptag; + // update gstate + lfs->gdelta = (lfs_gstate_t){0}; + if (!relocated) { + lfs->gdisk = lfs->gstate; + } + } + break; + +relocate: + // commit was corrupted, drop caches and prepare to relocate block + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + if (!tired) { + LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); + } + + // can't relocate superblock, filesystem is now frozen + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock 0x%"PRIx32" has become unwritable", + dir->pair[1]); + return LFS_ERR_NOSPC; + } + + // relocate half of pair + int err = lfs_alloc(lfs, &dir->pair[1]); + if (err && (err != LFS_ERR_NOSPC || !tired)) { + return err; + } + + tired = false; + continue; + } + + return relocated ? LFS_OK_RELOCATED : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end) { + while (true) { + // find size of first split, we do this by halving the split until + // the metadata is guaranteed to fit + // + // Note that this isn't a true binary search, we never increase the + // split size. This may result in poorly distributed metadata but isn't + // worth the extra code size or performance hit to fix. + lfs_size_t split = begin; + while (end - split > 1) { + lfs_size_t size = 0; + int err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + split, end, -split, + lfs_dir_commit_size, &size); + if (err) { + return err; + } + + // space is complicated, we need room for: + // + // - tail: 4+2*4 = 12 bytes + // - gstate: 4+3*4 = 16 bytes + // - move delete: 4 = 4 bytes + // - crc: 4+4 = 8 bytes + // total = 40 bytes + // + // And we cap at half a block to avoid degenerate cases with + // nearly-full metadata blocks. + // + if (end - split < 0xff + && size <= lfs_min( + lfs->cfg->block_size - 40, + lfs_alignup( + (lfs->cfg->metadata_max + ? lfs->cfg->metadata_max + : lfs->cfg->block_size)/2, + lfs->cfg->prog_size))) { + break; + } + + split = split + ((end - split) / 2); + } + + if (split == begin) { + // no split needed + break; + } + + // split into two metadata pairs and continue + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, split, end); + if (err && err != LFS_ERR_NOSPC) { + return err; + } + + if (err) { + // we can't allocate a new block, try to compact with degraded + // performance + LFS_WARN("Unable to split {0x%"PRIx32", 0x%"PRIx32"}", + dir->pair[0], dir->pair[1]); + break; + } else { + end = split; + } + } + + if (lfs_dir_needsrelocation(lfs, dir) + && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + // oh no! we're writing too much to the superblock, + // should we expand? + lfs_ssize_t size = lfs_fs_size_(lfs); + if (size < 0) { + return size; + } + + // littlefs cannot reclaim expanded superblocks, so expand cautiously + // + // if our filesystem is more than ~88% full, don't expand, this is + // somewhat arbitrary + if (lfs->block_count - size > lfs->block_count/8) { + LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, begin, end); + if (err && err != LFS_ERR_NOSPC) { + return err; + } + + if (err) { + // welp, we tried, if we ran out of space there's not much + // we can do, we'll error later if we've become frozen + LFS_WARN("Unable to expand superblock"); + } else { + end = begin; + } + } + } + + return lfs_dir_compact(lfs, dir, attrs, attrcount, source, begin, end); +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir, + const lfs_block_t pair[2], + const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *pdir) { + int state = 0; + + // calculate changes to the directory + bool hasdelete = false; + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { + dir->count += 1; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) { + LFS_ASSERT(dir->count > 0); + dir->count -= 1; + hasdelete = true; + } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) { + dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0]; + dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1]; + dir->split = (lfs_tag_chunk(attrs[i].tag) & 1); + lfs_pair_fromle32(dir->tail); + } + } + + // should we actually drop the directory block? + if (hasdelete && dir->count == 0) { + LFS_ASSERT(pdir); + int err = lfs_fs_pred(lfs, dir->pair, pdir); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err != LFS_ERR_NOENT && pdir->split) { + state = LFS_OK_DROPPED; + goto fixmlist; + } + } + + if (dir->erased) { + // try to commit + struct lfs_commit commit = { + .block = dir->pair[0], + .off = dir->off, + .ptag = dir->etag, + .crc = 0xffffffff, + + .begin = dir->off, + .end = (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, + }; + + // traverse attrs that need to be written out + lfs_pair_tole32(dir->tail); + int err = lfs_dir_traverse(lfs, + dir, dir->off, dir->etag, attrs, attrcount, + 0, 0, 0, 0, 0, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + return err; + } + + // commit any global diffs if we have any + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gstate); + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + if (!lfs_gstate_iszero(&delta)) { + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + return err; + } + } + + // finalize commit with the crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + return err; + } + + // successful commit, update dir + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + dir->off = commit.off; + dir->etag = commit.ptag; + // and update gstate + lfs->gdisk = lfs->gstate; + lfs->gdelta = (lfs_gstate_t){0}; + + goto fixmlist; + } + +compact: + // fall back to compaction + lfs_cache_drop(lfs, &lfs->pcache); + + state = lfs_dir_splittingcompact(lfs, dir, attrs, attrcount, + dir, 0, dir->count); + if (state < 0) { + return state; + } + + goto fixmlist; + +fixmlist:; + // this complicated bit of logic is for fixing up any active + // metadata-pairs that we may have affected + // + // note we have to make two passes since the mdir passed to + // lfs_dir_commit could also be in this list, and even then + // we need to copy the pair so they don't get clobbered if we refetch + // our mdir. + lfs_block_t oldpair[2] = {pair[0], pair[1]}; + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(d->m.pair, oldpair) == 0) { + d->m = *dir; + if (d->m.pair != pair) { + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id == lfs_tag_id(attrs[i].tag)) { + d->m.pair[0] = LFS_BLOCK_NULL; + d->m.pair[1] = LFS_BLOCK_NULL; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id > lfs_tag_id(attrs[i].tag)) { + d->id -= 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos -= 1; + } + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE && + d->id >= lfs_tag_id(attrs[i].tag)) { + d->id += 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos += 1; + } + } + } + } + + while (d->id >= d->m.count && d->m.split) { + // we split and id is on tail now + d->id -= d->m.count; + int err = lfs_dir_fetch(lfs, &d->m, d->m.tail); + if (err) { + return err; + } + } + } + } + + return state; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_orphaningcommit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + // check for any inline files that aren't RAM backed and + // forcefully evict them, needed for filesystem consistency + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && + f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) && + f->ctz.size > lfs->cfg->cache_size) { + int err = lfs_file_outline(lfs, f); + if (err) { + return err; + } + + err = lfs_file_flush(lfs, f); + if (err) { + return err; + } + } + } + + lfs_block_t lpair[2] = {dir->pair[0], dir->pair[1]}; + lfs_mdir_t ldir = *dir; + lfs_mdir_t pdir; + int state = lfs_dir_relocatingcommit(lfs, &ldir, dir->pair, + attrs, attrcount, &pdir); + if (state < 0) { + return state; + } + + // update if we're not in mlist, note we may have already been + // updated if we are in mlist + if (lfs_pair_cmp(dir->pair, lpair) == 0) { + *dir = ldir; + } + + // commit was successful, but may require other changes in the + // filesystem, these would normally be tail recursive, but we have + // flattened them here avoid unbounded stack usage + + // need to drop? + if (state == LFS_OK_DROPPED) { + // steal state + int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail, note that this can't create a recursive drop + lpair[0] = pdir.pair[0]; + lpair[1] = pdir.pair[1]; + lfs_pair_tole32(dir->tail); + state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), + dir->tail}), + NULL); + lfs_pair_fromle32(dir->tail); + if (state < 0) { + return state; + } + + ldir = pdir; + } + + // need to relocate? + bool orphans = false; + while (state == LFS_OK_RELOCATED) { + LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + lpair[0], lpair[1], ldir.pair[0], ldir.pair[1]); + state = 0; + + // update internal root + if (lfs_pair_cmp(lpair, lfs->root) == 0) { + lfs->root[0] = ldir.pair[0]; + lfs->root[1] = ldir.pair[1]; + } + + // update internally tracked dirs + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(lpair, d->m.pair) == 0) { + d->m.pair[0] = ldir.pair[0]; + d->m.pair[1] = ldir.pair[1]; + } + + if (d->type == LFS_TYPE_DIR && + lfs_pair_cmp(lpair, ((lfs_dir_t*)d)->head) == 0) { + ((lfs_dir_t*)d)->head[0] = ldir.pair[0]; + ((lfs_dir_t*)d)->head[1] = ldir.pair[1]; + } + } + + // find parent + lfs_stag_t tag = lfs_fs_parent(lfs, lpair, &pdir); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + bool hasparent = (tag != LFS_ERR_NOENT); + if (tag != LFS_ERR_NOENT) { + // note that if we have a parent, we must have a pred, so this will + // always create an orphan + int err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + if (moveid < lfs_tag_id(tag)) { + tag -= LFS_MKTAG(0, 1, 0); + } + } + + lfs_block_t ppair[2] = {pdir.pair[0], pdir.pair[1]}; + lfs_pair_tole32(ldir.pair); + state = lfs_dir_relocatingcommit(lfs, &pdir, ppair, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {tag, ldir.pair}), + NULL); + lfs_pair_fromle32(ldir.pair); + if (state < 0) { + return state; + } + + if (state == LFS_OK_RELOCATED) { + lpair[0] = ppair[0]; + lpair[1] = ppair[1]; + ldir = pdir; + orphans = true; + continue; + } + } + + // find pred + int err = lfs_fs_pred(lfs, lpair, &pdir); + if (err && err != LFS_ERR_NOENT) { + return err; + } + LFS_ASSERT(!(hasparent && err == LFS_ERR_NOENT)); + + // if we can't find dir, it must be new + if (err != LFS_ERR_NOENT) { + if (lfs_gstate_hasorphans(&lfs->gstate)) { + // next step, clean up orphans + err = lfs_fs_preporphans(lfs, -hasparent); + if (err) { + return err; + } + } + + // fix pending move in this pair? this looks like an optimization + // but is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + + // replace bad pair, either we clean up desync, or no desync occured + lpair[0] = pdir.pair[0]; + lpair[1] = pdir.pair[1]; + lfs_pair_tole32(ldir.pair); + state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_TAIL + pdir.split, 0x3ff, 8), + ldir.pair}), + NULL); + lfs_pair_fromle32(ldir.pair); + if (state < 0) { + return state; + } + + ldir = pdir; + } + } + + return orphans ? LFS_OK_ORPHANED : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + int orphans = lfs_dir_orphaningcommit(lfs, dir, attrs, attrcount); + if (orphans < 0) { + return orphans; + } + + if (orphans) { + // make sure we've removed all orphans, this is a noop if there + // are none, but if we had nested blocks failures we may have + // created some + int err = lfs_fs_deorphan(lfs, false); + if (err) { + return err; + } + } + + return 0; +} +#endif + + +/// Top level directory operations /// +#ifndef LFS_READONLY +static int lfs_mkdir_(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + struct lfs_mlist cwd; + cwd.next = lfs->mlist; + uint16_t id; + err = lfs_dir_find(lfs, &cwd.m, &path, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + return (err < 0) ? err : LFS_ERR_EXIST; + } + + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_max) { + return LFS_ERR_NAMETOOLONG; + } + + // build up new directory + lfs_alloc_ckpoint(lfs); + lfs_mdir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + return err; + } + + // find end of list + lfs_mdir_t pred = cwd.m; + while (pred.split) { + err = lfs_dir_fetch(lfs, &pred, pred.tail); + if (err) { + return err; + } + } + + // setup dir + lfs_pair_tole32(pred.tail); + err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); + lfs_pair_fromle32(pred.tail); + if (err) { + return err; + } + + // current block not end of list? + if (cwd.m.split) { + // update tails, this creates a desync + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // it's possible our predecessor has to be relocated, and if + // our parent is our predecessor's predecessor, this could have + // caused our parent to go out of date, fortunately we can hook + // ourselves into littlefs to catch this + cwd.type = 0; + cwd.id = 0; + lfs->mlist = &cwd; + + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + lfs->mlist = cwd.next; + return err; + } + + lfs->mlist = cwd.next; + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } + } + + // now insert into our parent block + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, + {LFS_MKTAG_IF(!cwd.m.split, + LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + return err; + } + + return 0; +} +#endif + +static int lfs_dir_open_(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); + if (tag < 0) { + return tag; + } + + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + lfs_block_t pair[2]; + if (lfs_tag_id(tag) == 0x3ff) { + // handle root dir separately + pair[0] = lfs->root[0]; + pair[1] = lfs->root[1]; + } else { + // get dir pair from parent + lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + return res; + } + lfs_pair_fromle32(pair); + } + + // fetch first pair + int err = lfs_dir_fetch(lfs, &dir->m, pair); + if (err) { + return err; + } + + // setup entry + dir->head[0] = dir->m.pair[0]; + dir->head[1] = dir->m.pair[1]; + dir->id = 0; + dir->pos = 0; + + // add to list of mdirs + dir->type = LFS_TYPE_DIR; + lfs_mlist_append(lfs, (struct lfs_mlist *)dir); + + return 0; +} + +static int lfs_dir_close_(lfs_t *lfs, lfs_dir_t *dir) { + // remove from list of mdirs + lfs_mlist_remove(lfs, (struct lfs_mlist *)dir); + + return 0; +} + +static int lfs_dir_read_(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + memset(info, 0, sizeof(*info)); + + // special offset for '.' and '..' + if (dir->pos == 0) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + return true; + } else if (dir->pos == 1) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + return true; + } + + while (true) { + if (dir->id == dir->m.count) { + if (!dir->m.split) { + return false; + } + + int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + return err; + } + + dir->id = 0; + } + + int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + dir->id += 1; + if (err != LFS_ERR_NOENT) { + break; + } + } + + dir->pos += 1; + return true; +} + +static int lfs_dir_seek_(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + // simply walk from head dir + int err = lfs_dir_rewind_(lfs, dir); + if (err) { + return err; + } + + // first two for ./.. + dir->pos = lfs_min(2, off); + off -= dir->pos; + + // skip superblock entry + dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0); + + while (off > 0) { + if (dir->id == dir->m.count) { + if (!dir->m.split) { + return LFS_ERR_INVAL; + } + + err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + return err; + } + + dir->id = 0; + } + + int diff = lfs_min(dir->m.count - dir->id, off); + dir->id += diff; + dir->pos += diff; + off -= diff; + } + + return 0; +} + +static lfs_soff_t lfs_dir_tell_(lfs_t *lfs, lfs_dir_t *dir) { + (void)lfs; + return dir->pos; +} + +static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir) { + // reload the head dir + int err = lfs_dir_fetch(lfs, &dir->m, dir->head); + if (err) { + return err; + } + + dir->id = 0; + dir->pos = 0; + return 0; +} + + +/// File index list operations /// +static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t i = size / b; + if (i == 0) { + return 0; + } + + i = (size - 4*(lfs_popc(i-1)+2)) / b; + *off = size - b*i - 4*lfs_popc(i); + return i; +} + +static int lfs_ctz_find(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { + if (size == 0) { + *block = LFS_BLOCK_NULL; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); + + while (current > target) { + lfs_size_t skip = lfs_min( + lfs_npw2(current-target+1) - 1, + lfs_ctz(current)); + + int err = lfs_bd_read(lfs, + pcache, rcache, sizeof(head), + head, 4*skip, &head, sizeof(head)); + head = lfs_fromle32(head); + if (err) { + return err; + } + + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +#ifndef LFS_READONLY +static int lfs_ctz_extend(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_block_t *block, lfs_off_t *off) { + while (true) { + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + { + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *block = nblock; + *off = 0; + return 0; + } + + lfs_size_t noff = size - 1; + lfs_off_t index = lfs_ctz_index(lfs, &noff); + noff = noff + 1; + + // just copy out the last block if it is incomplete + if (noff != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < noff; i++) { + uint8_t data; + err = lfs_bd_read(lfs, + NULL, rcache, noff-i, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_bd_prog(lfs, + pcache, rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *block = nblock; + *off = noff; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + lfs_block_t nhead = head; + for (lfs_off_t i = 0; i < skips; i++) { + nhead = lfs_tole32(nhead); + err = lfs_bd_prog(lfs, pcache, rcache, true, + nblock, 4*i, &nhead, 4); + nhead = lfs_fromle32(nhead); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_bd_read(lfs, + NULL, rcache, sizeof(nhead), + nhead, 4*i, &nhead, sizeof(nhead)); + nhead = lfs_fromle32(nhead); + if (err) { + return err; + } + } + } + + *block = nblock; + *off = 4*skips; + return 0; + } + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, pcache); + } +} +#endif + +static int lfs_ctz_traverse(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + int (*cb)(void*, lfs_block_t), void *data) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + lfs_block_t heads[2]; + int count = 2 - (index & 1); + err = lfs_bd_read(lfs, + pcache, rcache, count*sizeof(head), + head, 0, &heads, count*sizeof(head)); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) { + return err; + } + + for (int i = 0; i < count-1; i++) { + err = cb(data, heads[i]); + if (err) { + return err; + } + } + + head = heads[count-1]; + index -= count; + } +} + + +/// Top level file operations /// +static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { +#ifndef LFS_READONLY + // deorphan if we haven't yet, needed at most once after poweron + if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) { + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + } +#else + LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY); +#endif + + // setup simple file details + int err; + file->cfg = cfg; + file->flags = flags; + file->pos = 0; + file->off = 0; + file->cache.buffer = NULL; + + // allocate entry for file if it doesn't exist + lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); + if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) { + err = tag; + goto cleanup; + } + + // get id, add to list of mdirs to catch update changes + file->type = LFS_TYPE_REG; + lfs_mlist_append(lfs, (struct lfs_mlist *)file); + +#ifdef LFS_READONLY + if (tag == LFS_ERR_NOENT) { + err = LFS_ERR_NOENT; + goto cleanup; +#else + if (tag == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + err = LFS_ERR_NOENT; + goto cleanup; + } + + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_max) { + err = LFS_ERR_NAMETOOLONG; + goto cleanup; + } + + // get next slot and create entry to remember name + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL})); + + // it may happen that the file name doesn't fit in the metadata blocks, e.g., a 256 byte file name will + // not fit in a 128 byte block. + err = (err == LFS_ERR_NOSPC) ? LFS_ERR_NAMETOOLONG : err; + if (err) { + goto cleanup; + } + + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); + } else if (flags & LFS_O_EXCL) { + err = LFS_ERR_EXIST; + goto cleanup; +#endif + } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { + err = LFS_ERR_ISDIR; + goto cleanup; +#ifndef LFS_READONLY + } else if (flags & LFS_O_TRUNC) { + // truncate if requested + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); + file->flags |= LFS_F_DIRTY; +#endif + } else { + // try to load what's on disk, if it's inlined we'll fix it later + tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_ctz_fromle32(&file->ctz); + } + + // fetch attrs + for (unsigned i = 0; i < file->cfg->attr_count; i++) { + // if opened for read / read-write operations + if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, + file->id, file->cfg->attrs[i].size), + file->cfg->attrs[i].buffer); + if (res < 0 && res != LFS_ERR_NOENT) { + err = res; + goto cleanup; + } + } + +#ifndef LFS_READONLY + // if opened for write / read-write operations + if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) { + if (file->cfg->attrs[i].size > lfs->attr_max) { + err = LFS_ERR_NOSPC; + goto cleanup; + } + + file->flags |= LFS_F_DIRTY; + } +#endif + } + + // allocate buffer if needed + if (file->cfg->buffer) { + file->cache.buffer = file->cfg->buffer; + } else { + file->cache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!file->cache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leak + lfs_cache_zero(lfs, &file->cache); + + if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + // load inline files + file->ctz.head = LFS_BLOCK_INLINE; + file->ctz.size = lfs_tag_size(tag); + file->flags |= LFS_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs->cfg->cache_size; + + // don't always read (may be new/trunc file) + if (file->ctz.size > 0) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, + lfs_min(file->cache.size, 0x3fe)), + file->cache.buffer); + if (res < 0) { + err = res; + goto cleanup; + } + } + } + + return 0; + +cleanup: + // clean up lingering resources +#ifndef LFS_READONLY + file->flags |= LFS_F_ERRED; +#endif + lfs_file_close_(lfs, file); + return err; +} + +#ifndef LFS_NO_MALLOC +static int lfs_file_open_(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + static const struct lfs_file_config defaults = {0}; + int err = lfs_file_opencfg_(lfs, file, path, flags, &defaults); + return err; +} +#endif + +static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file) { +#ifndef LFS_READONLY + int err = lfs_file_sync_(lfs, file); +#else + int err = 0; +#endif + + // remove from list of mdirs + lfs_mlist_remove(lfs, (struct lfs_mlist*)file); + + // clean up memory + if (!file->cfg->buffer) { + lfs_free(file->cache.buffer); + } + + return err; +} + + +#ifndef LFS_READONLY +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { + while (true) { + // just relocate what exists into new block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // either read from dirty cache or disk + for (lfs_off_t i = 0; i < file->off; i++) { + uint8_t data; + if (file->flags & LFS_F_INLINE) { + err = lfs_dir_getread(lfs, &file->m, + // note we evict inline files before they can be dirty + NULL, &file->cache, file->off-i, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + i, &data, 1); + if (err) { + return err; + } + } else { + err = lfs_bd_read(lfs, + &file->cache, &lfs->rcache, file->off-i, + file->block, i, &data, 1); + if (err) { + return err; + } + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // copy over new state of file + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + file->cache.size = lfs->pcache.size; + lfs_cache_zero(lfs, &lfs->pcache); + + file->block = nblock; + file->flags |= LFS_F_WRITING; + return 0; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, &lfs->pcache); + } +} +#endif + +#ifndef LFS_READONLY +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) { + file->off = file->pos; + lfs_alloc_ckpoint(lfs); + int err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + + file->flags &= ~LFS_F_INLINE; + return 0; +} +#endif + +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_READING) { + if (!(file->flags & LFS_F_INLINE)) { + lfs_cache_drop(lfs, &file->cache); + } + file->flags &= ~LFS_F_READING; + } + +#ifndef LFS_READONLY + if (file->flags & LFS_F_WRITING) { + lfs_off_t pos = file->pos; + + if (!(file->flags & LFS_F_INLINE)) { + // copy over anything after current branch + lfs_file_t orig = { + .ctz.head = file->ctz.head, + .ctz.size = file->ctz.size, + .flags = LFS_O_RDONLY, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs_cache_drop(lfs, &lfs->rcache); + + while (file->pos < file->ctz.size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_flushedread(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } + + res = lfs_file_flushedwrite(lfs, file, &data, 1); + if (res < 0) { + return res; + } + + // keep our reference to the rcache in sync + if (lfs->rcache.block != LFS_BLOCK_NULL) { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + // write out what we have + while (true) { + int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, file->block); + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + } else { + file->pos = lfs_max(file->pos, file->ctz.size); + } + + // actual file updates + file->ctz.head = file->block; + file->ctz.size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } +#endif + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_ERRED) { + // it's not safe to do anything if our file errored + return 0; + } + + int err = lfs_file_flush(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + + if ((file->flags & LFS_F_DIRTY) && + !lfs_pair_isnull(file->m.pair)) { + // before we commit metadata, we need sync the disk to make sure + // data writes don't complete after metadata writes + if (!(file->flags & LFS_F_INLINE)) { + err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); + if (err) { + return err; + } + } + + // update dir entry + uint16_t type; + const void *buffer; + lfs_size_t size; + struct lfs_ctz ctz; + if (file->flags & LFS_F_INLINE) { + // inline the whole file + type = LFS_TYPE_INLINESTRUCT; + buffer = file->cache.buffer; + size = file->ctz.size; + } else { + // update the ctz reference + type = LFS_TYPE_CTZSTRUCT; + // copy ctz so alloc will work during a relocate + ctz = file->ctz; + lfs_ctz_tole32(&ctz); + buffer = &ctz; + size = sizeof(ctz); + } + + // commit file data and attributes + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(type, file->id, size), buffer}, + {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, + file->cfg->attr_count), file->cfg->attrs})); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + return 0; +} +#endif + +static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + lfs_size_t nsize = size; + + if (file->pos >= file->ctz.size) { + // eof if past end + return 0; + } + + size = lfs_min(size, file->ctz.size - file->pos); + nsize = size; + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_READING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos, &file->block, &file->off); + if (err) { + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_READING; + } + + // read as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + if (file->flags & LFS_F_INLINE) { + int err = lfs_dir_getread(lfs, &file->m, + NULL, &file->cache, lfs->cfg->block_size, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + file->off, data, diff); + if (err) { + return err; + } + } else { + int err = lfs_bd_read(lfs, + NULL, &file->cache, lfs->cfg->block_size, + file->block, file->off, data, diff); + if (err) { + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + return size; +} + +static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY); + +#ifndef LFS_READONLY + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } +#endif + + return lfs_file_flushedread(lfs, file, buffer, size); +} + + +#ifndef LFS_READONLY +static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & LFS_F_INLINE) && + lfs_max(file->pos+nsize, file->ctz.size) > lfs->inline_max) { + // inline file doesn't fit anymore + int err = lfs_file_outline(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_WRITING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos-1, &file->block, &(lfs_off_t){0}); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + // mark cache as dirty since we may have read data into it + lfs_cache_zero(lfs, &file->cache); + } + + // extend file with new blocks + lfs_alloc_ckpoint(lfs); + int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_WRITING; + } + + // program as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) { + int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true, + file->block, file->off, data, diff); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + file->flags |= LFS_F_ERRED; + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ckpoint(lfs); + } + + return size; +} + +static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); + + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) { + file->pos = file->ctz.size; + } + + if (file->pos + size > lfs->file_max) { + // Larger than file limit? + return LFS_ERR_FBIG; + } + + if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->ctz.size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_flushedwrite(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + } + + lfs_ssize_t nsize = lfs_file_flushedwrite(lfs, file, buffer, size); + if (nsize < 0) { + return nsize; + } + + file->flags &= ~LFS_F_ERRED; + return nsize; +} +#endif + +static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + // find new pos + lfs_off_t npos = file->pos; + if (whence == LFS_SEEK_SET) { + npos = off; + } else if (whence == LFS_SEEK_CUR) { + if ((lfs_soff_t)file->pos + off < 0) { + return LFS_ERR_INVAL; + } else { + npos = file->pos + off; + } + } else if (whence == LFS_SEEK_END) { + lfs_soff_t res = lfs_file_size_(lfs, file) + off; + if (res < 0) { + return LFS_ERR_INVAL; + } else { + npos = res; + } + } + + if (npos > lfs->file_max) { + // file position out of range + return LFS_ERR_INVAL; + } + + if (file->pos == npos) { + // noop - position has not changed + return npos; + } + + // if we're only reading and our new offset is still in the file's cache + // we can avoid flushing and needing to reread the data + if ( +#ifndef LFS_READONLY + !(file->flags & LFS_F_WRITING) +#else + true +#endif + ) { + int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos}); + lfs_off_t noff = npos; + int nindex = lfs_ctz_index(lfs, &noff); + if (oindex == nindex + && noff >= file->cache.off + && noff < file->cache.off + file->cache.size) { + file->pos = npos; + file->off = noff; + return npos; + } + } + + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // update pos + file->pos = npos; + return npos; +} + +#ifndef LFS_READONLY +static int lfs_file_truncate_(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); + + if (size > LFS_FILE_MAX) { + return LFS_ERR_INVAL; + } + + lfs_off_t pos = file->pos; + lfs_off_t oldsize = lfs_file_size_(lfs, file); + if (size < oldsize) { + // revert to inline file? + if (size <= lfs->inline_max) { + // flush+seek to head + lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return (int)res; + } + + // read our data into rcache temporarily + lfs_cache_drop(lfs, &lfs->rcache); + res = lfs_file_flushedread(lfs, file, + lfs->rcache.buffer, size); + if (res < 0) { + return (int)res; + } + + file->ctz.head = LFS_BLOCK_INLINE; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING | LFS_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs->cfg->cache_size; + memcpy(file->cache.buffer, lfs->rcache.buffer, size); + + } else { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + size-1, &file->block, &(lfs_off_t){0}); + if (err) { + return err; + } + + // need to set pos/block/off consistently so seeking back to + // the old position does not get confused + file->pos = size; + file->ctz.head = file->block; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING; + } + } else if (size > oldsize) { + // flush+seek if not already at end + lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_END); + if (res < 0) { + return (int)res; + } + + // fill with zeros + while (file->pos < size) { + res = lfs_file_write_(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return (int)res; + } + } + } + + // restore pos + lfs_soff_t res = lfs_file_seek_(lfs, file, pos, LFS_SEEK_SET); + if (res < 0) { + return (int)res; + } + + return 0; +} +#endif + +static lfs_soff_t lfs_file_tell_(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + return file->pos; +} + +static int lfs_file_rewind_(lfs_t *lfs, lfs_file_t *file) { + lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return (int)res; + } + + return 0; +} + +static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + +#ifndef LFS_READONLY + if (file->flags & LFS_F_WRITING) { + return lfs_max(file->pos, file->ctz.size); + } +#endif + + return file->ctz.size; +} + + +/// General fs operations /// +static int lfs_stat_(lfs_t *lfs, const char *path, struct lfs_info *info) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return (int)tag; + } + + return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); +} + +#ifndef LFS_READONLY +static int lfs_remove_(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0 || lfs_tag_id(tag) == 0x3ff) { + return (tag < 0) ? (int)tag : LFS_ERR_INVAL; + } + + struct lfs_mlist dir; + dir.next = lfs->mlist; + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t pair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + return (int)res; + } + lfs_pair_fromle32(pair); + + err = lfs_dir_fetch(lfs, &dir.m, pair); + if (err) { + return err; + } + + if (dir.m.count > 0 || dir.m.split) { + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + dir.type = 0; + dir.id = 0; + lfs->mlist = &dir; + } + + // delete the entry + err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL})); + if (err) { + lfs->mlist = dir.next; + return err; + } + + lfs->mlist = dir.next; + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + // fix orphan + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } + + err = lfs_fs_pred(lfs, dir.m.pair, &cwd); + if (err) { + return err; + } + + err = lfs_dir_drop(lfs, &cwd, &dir.m); + if (err) { + return err; + } + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) { + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // find old entry + lfs_mdir_t oldcwd; + lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL); + if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) { + return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL; + } + + // find new entry + lfs_mdir_t newcwd; + uint16_t newid; + lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); + if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && + !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) { + return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; + } + + // if we're in the same pair there's a few special cases... + bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0); + uint16_t newoldid = lfs_tag_id(oldtag); + + struct lfs_mlist prevdir; + prevdir.next = lfs->mlist; + if (prevtag == LFS_ERR_NOENT) { + // check that name fits + lfs_size_t nlen = strlen(newpath); + if (nlen > lfs->name_max) { + return LFS_ERR_NAMETOOLONG; + } + + // there is a small chance we are being renamed in the same + // directory/ to an id less than our old id, the global update + // to handle this is a bit messy + if (samepair && newid <= newoldid) { + newoldid += 1; + } + } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) { + return (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) + ? LFS_ERR_ISDIR + : LFS_ERR_NOTDIR; + } else if (samepair && newid == newoldid) { + // we're renaming to ourselves?? + return 0; + } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t prevpair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair); + if (res < 0) { + return (int)res; + } + lfs_pair_fromle32(prevpair); + + // must be empty before removal + err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); + if (err) { + return err; + } + + if (prevdir.m.count > 0 || prevdir.m.split) { + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + prevdir.type = 0; + prevdir.id = 0; + lfs->mlist = &prevdir; + } + + if (!samepair) { + lfs_fs_prepmove(lfs, newoldid, oldcwd.pair); + } + + // move over all attributes + err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS( + {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, + LFS_TYPE_DELETE, newid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, + {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, + {LFS_MKTAG_IF(samepair, + LFS_TYPE_DELETE, newoldid, 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + return err; + } + + // let commit clean up after move (if we're different! otherwise move + // logic already fixed it for us) + if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) { + // prep gstate and delete move id + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + return err; + } + } + + lfs->mlist = prevdir.next; + if (prevtag != LFS_ERR_NOENT + && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + // fix orphan + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } + + err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); + if (err) { + return err; + } + + err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); + if (err) { + return err; + } + } + + return 0; +} +#endif + +static lfs_ssize_t lfs_getattr_(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + } + + tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + type, + id, lfs_min(size, lfs->attr_max)), + buffer); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + return LFS_ERR_NOATTR; + } + + return tag; + } + + return lfs_tag_size(tag); +} + +#ifndef LFS_READONLY +static int lfs_commitattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + } + + return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer})); +} +#endif + +#ifndef LFS_READONLY +static int lfs_setattr_(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + if (size > lfs->attr_max) { + return LFS_ERR_NOSPC; + } + + return lfs_commitattr(lfs, path, type, buffer, size); +} +#endif + +#ifndef LFS_READONLY +static int lfs_removeattr_(lfs_t *lfs, const char *path, uint8_t type) { + return lfs_commitattr(lfs, path, type, NULL, 0x3ff); +} +#endif + + +/// Filesystem operations /// + +// compile time checks, see lfs.h for why these limits exist +#if LFS_NAME_MAX > 1022 +#error "Invalid LFS_NAME_MAX, must be <= 1022" +#endif + +#if LFS_FILE_MAX > 2147483647 +#error "Invalid LFS_FILE_MAX, must be <= 2147483647" +#endif + +#if LFS_ATTR_MAX > 1022 +#error "Invalid LFS_ATTR_MAX, must be <= 1022" +#endif + +// common filesystem initialization +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + lfs->block_count = cfg->block_count; // May be 0 + int err = 0; + +#ifdef LFS_MULTIVERSION + // this driver only supports minor version < current minor version + LFS_ASSERT(!lfs->cfg->disk_version || ( + (0xffff & (lfs->cfg->disk_version >> 16)) + == LFS_DISK_VERSION_MAJOR + && (0xffff & (lfs->cfg->disk_version >> 0)) + <= LFS_DISK_VERSION_MINOR)); +#endif + + // check that bool is a truthy-preserving type + // + // note the most common reason for this failure is a before-c99 compiler, + // which littlefs currently does not support + LFS_ASSERT((bool)0x80000000); + + // validate that the lfs-cfg sizes were initiated properly before + // performing any arithmetic logics with them + LFS_ASSERT(lfs->cfg->read_size != 0); + LFS_ASSERT(lfs->cfg->prog_size != 0); + LFS_ASSERT(lfs->cfg->cache_size != 0); + + // check that block size is a multiple of cache size is a multiple + // of prog and read sizes + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); + + // check that the block size is large enough to fit all ctz pointers + LFS_ASSERT(lfs->cfg->block_size >= 128); + // this is the exact calculation for all ctz pointers, if this fails + // and the simpler assert above does not, math must be broken + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) + <= lfs->cfg->block_size); + + // block_cycles = 0 is no longer supported. + // + // block_cycles is the number of erase cycles before littlefs evicts + // metadata logs as a part of wear leveling. Suggested values are in the + // range of 100-1000, or set block_cycles to -1 to disable block-level + // wear-leveling. + LFS_ASSERT(lfs->cfg->block_cycles != 0); + + // check that compact_thresh makes sense + // + // metadata can't be compacted below block_size/2, and metadata can't + // exceed a block_size + LFS_ASSERT(lfs->cfg->compact_thresh == 0 + || lfs->cfg->compact_thresh >= lfs->cfg->block_size/2); + LFS_ASSERT(lfs->cfg->compact_thresh == (lfs_size_t)-1 + || lfs->cfg->compact_thresh <= lfs->cfg->block_size); + + // setup read cache + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->rcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // setup program cache + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->pcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leaks + lfs_cache_zero(lfs, &lfs->rcache); + lfs_cache_zero(lfs, &lfs->pcache); + + // setup lookahead buffer, note mount finishes initializing this after + // we establish a decent pseudo-random seed + LFS_ASSERT(lfs->cfg->lookahead_size > 0); + if (lfs->cfg->lookahead_buffer) { + lfs->lookahead.buffer = lfs->cfg->lookahead_buffer; + } else { + lfs->lookahead.buffer = lfs_malloc(lfs->cfg->lookahead_size); + if (!lfs->lookahead.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // check that the size limits are sane + LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX); + lfs->name_max = lfs->cfg->name_max; + if (!lfs->name_max) { + lfs->name_max = LFS_NAME_MAX; + } + + LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX); + lfs->file_max = lfs->cfg->file_max; + if (!lfs->file_max) { + lfs->file_max = LFS_FILE_MAX; + } + + LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX); + lfs->attr_max = lfs->cfg->attr_max; + if (!lfs->attr_max) { + lfs->attr_max = LFS_ATTR_MAX; + } + + LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size); + + LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 + || lfs->cfg->inline_max <= lfs->cfg->cache_size); + LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 + || lfs->cfg->inline_max <= lfs->attr_max); + LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 + || lfs->cfg->inline_max <= ((lfs->cfg->metadata_max) + ? lfs->cfg->metadata_max + : lfs->cfg->block_size)/8); + lfs->inline_max = lfs->cfg->inline_max; + if (lfs->inline_max == (lfs_size_t)-1) { + lfs->inline_max = 0; + } else if (lfs->inline_max == 0) { + lfs->inline_max = lfs_min( + lfs->cfg->cache_size, + lfs_min( + lfs->attr_max, + ((lfs->cfg->metadata_max) + ? lfs->cfg->metadata_max + : lfs->cfg->block_size)/8)); + } + + // setup default state + lfs->root[0] = LFS_BLOCK_NULL; + lfs->root[1] = LFS_BLOCK_NULL; + lfs->mlist = NULL; + lfs->seed = 0; + lfs->gdisk = (lfs_gstate_t){0}; + lfs->gstate = (lfs_gstate_t){0}; + lfs->gdelta = (lfs_gstate_t){0}; +#ifdef LFS_MIGRATE + lfs->lfs1 = NULL; +#endif + + return 0; + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + lfs_free(lfs->lookahead.buffer); + } + + return 0; +} + + + +#ifndef LFS_READONLY +static int lfs_format_(lfs_t *lfs, const struct lfs_config *cfg) { + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + LFS_ASSERT(cfg->block_count != 0); + + // create free lookahead + memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); + lfs->lookahead.start = 0; + lfs->lookahead.size = lfs_min(8*lfs->cfg->lookahead_size, + lfs->block_count); + lfs->lookahead.next = 0; + lfs_alloc_ckpoint(lfs); + + // create root dir + lfs_mdir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + goto cleanup; + } + + // write one superblock + lfs_superblock_t superblock = { + .version = lfs_fs_disk_version(lfs), + .block_size = lfs->cfg->block_size, + .block_count = lfs->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting any + // older version of littlefs that may live on disk + root.erased = false; + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs_deinit(lfs); + return err; + +} +#endif + +static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // scan directory blocks for superblock and any global updates + lfs_mdir_t dir = {.tail = {0, 1}}; + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; + while (!lfs_pair_isnull(dir.tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(dir.tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = dir.tail[0]; + tortoise[1] = dir.tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + + // fetch next block in tail list + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), + NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, "littlefs", 8}); + if (tag < 0) { + err = tag; + goto cleanup; + } + + // has superblock? + if (tag && !lfs_tag_isdelete(tag)) { + // update root + lfs->root[0] = dir.pair[0]; + lfs->root[1] = dir.pair[1]; + + // grab superblock + lfs_superblock_t superblock; + tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_superblock_fromle32(&superblock); + + // check version + uint16_t major_version = (0xffff & (superblock.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.version >> 0)); + if (major_version != lfs_fs_disk_version_major(lfs) + || minor_version > lfs_fs_disk_version_minor(lfs)) { + LFS_ERROR("Invalid version " + "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); + err = LFS_ERR_INVAL; + goto cleanup; + } + + // found older minor version? set an in-device only bit in the + // gstate so we know we need to rewrite the superblock before + // the first write + if (minor_version < lfs_fs_disk_version_minor(lfs)) { + LFS_DEBUG("Found older minor version " + "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); + // note this bit is reserved on disk, so fetching more gstate + // will not interfere here + lfs_fs_prepsuperblock(lfs, true); + } + + // check superblock configuration + if (superblock.name_max) { + if (superblock.name_max > lfs->name_max) { + LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", + superblock.name_max, lfs->name_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->name_max = superblock.name_max; + } + + if (superblock.file_max) { + if (superblock.file_max > lfs->file_max) { + LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", + superblock.file_max, lfs->file_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->file_max = superblock.file_max; + } + + if (superblock.attr_max) { + if (superblock.attr_max > lfs->attr_max) { + LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", + superblock.attr_max, lfs->attr_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->attr_max = superblock.attr_max; + + // we also need to update inline_max in case attr_max changed + lfs->inline_max = lfs_min(lfs->inline_max, lfs->attr_max); + } + + // this is where we get the block_count from disk if block_count=0 + if (lfs->cfg->block_count + && superblock.block_count != lfs->cfg->block_count) { + LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")", + superblock.block_count, lfs->cfg->block_count); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->block_count = superblock.block_count; + + if (superblock.block_size != lfs->cfg->block_size) { + LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")", + superblock.block_size, lfs->cfg->block_size); + err = LFS_ERR_INVAL; + goto cleanup; + } + } + + // has gstate? + err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); + if (err) { + goto cleanup; + } + } + + // update littlefs with gstate + if (!lfs_gstate_iszero(&lfs->gstate)) { + LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, + lfs->gstate.tag, + lfs->gstate.pair[0], + lfs->gstate.pair[1]); + } + lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag); + lfs->gdisk = lfs->gstate; + + // setup free lookahead, to distribute allocations uniformly across + // boots, we start the allocator at a random location + lfs->lookahead.start = lfs->seed % lfs->block_count; + lfs_alloc_drop(lfs); + + return 0; + +cleanup: + lfs_unmount_(lfs); + return err; +} + +static int lfs_unmount_(lfs_t *lfs) { + return lfs_deinit(lfs); +} + + +/// Filesystem filesystem operations /// +static int lfs_fs_stat_(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + // if the superblock is up-to-date, we must be on the most recent + // minor version of littlefs + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + fsinfo->disk_version = lfs_fs_disk_version(lfs); + + // otherwise we need to read the minor version on disk + } else { + // fetch the superblock + lfs_mdir_t dir; + int err = lfs_dir_fetch(lfs, &dir, lfs->root); + if (err) { + return err; + } + + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + // read the on-disk version + fsinfo->disk_version = superblock.version; + } + + // filesystem geometry + fsinfo->block_size = lfs->cfg->block_size; + fsinfo->block_count = lfs->block_count; + + // other on-disk configuration, we cache all of these for internal use + fsinfo->name_max = lfs->name_max; + fsinfo->file_max = lfs->file_max; + fsinfo->attr_max = lfs->attr_max; + + return 0; +} + +int lfs_fs_traverse_(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans) { + // iterate over metadata pairs + lfs_mdir_t dir = {.tail = {0, 1}}; + +#ifdef LFS_MIGRATE + // also consider v1 blocks during migration + if (lfs->lfs1) { + int err = lfs1_traverse(lfs, cb, data); + if (err) { + return err; + } + + dir.tail[0] = lfs->root[0]; + dir.tail[1] = lfs->root[1]; + } +#endif + + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; + while (!lfs_pair_isnull(dir.tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(dir.tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + return LFS_ERR_CORRUPT; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = dir.tail[0]; + tortoise[1] = dir.tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + + for (int i = 0; i < 2; i++) { + int err = cb(data, dir.tail[i]); + if (err) { + return err; + } + } + + // iterate through ids in directory + int err = lfs_dir_fetch(lfs, &dir, dir.tail); + if (err) { + return err; + } + + for (uint16_t id = 0; id < dir.count; id++) { + struct lfs_ctz ctz; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + continue; + } + return tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + ctz.head, ctz.size, cb, data); + if (err) { + return err; + } + } else if (includeorphans && + lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { + for (int i = 0; i < 2; i++) { + err = cb(data, (&ctz.head)[i]); + if (err) { + return err; + } + } + } + } + } + +#ifndef LFS_READONLY + // iterate over any open files + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (f->type != LFS_TYPE_REG) { + continue; + } + + if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->ctz.head, f->ctz.size, cb, data); + if (err) { + return err; + } + } + + if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->block, f->pos, cb, data); + if (err) { + return err; + } + } + } +#endif + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_fs_pred(lfs_t *lfs, + const lfs_block_t pair[2], lfs_mdir_t *pdir) { + // iterate over all directory directory entries + pdir->tail[0] = 0; + pdir->tail[1] = 1; + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; + while (!lfs_pair_isnull(pdir->tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(pdir->tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + return LFS_ERR_CORRUPT; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = pdir->tail[0]; + tortoise[1] = pdir->tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + + if (lfs_pair_cmp(pdir->tail, pair) == 0) { + return 0; + } + + int err = lfs_dir_fetch(lfs, pdir, pdir->tail); + if (err) { + return err; + } + } + + return LFS_ERR_NOENT; +} +#endif + +#ifndef LFS_READONLY +struct lfs_fs_parent_match { + lfs_t *lfs; + const lfs_block_t pair[2]; +}; +#endif + +#ifndef LFS_READONLY +static int lfs_fs_parent_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_fs_parent_match *find = data; + lfs_t *lfs = find->lfs; + const struct lfs_diskoff *disk = buffer; + (void)tag; + + lfs_block_t child[2]; + int err = lfs_bd_read(lfs, + &lfs->pcache, &lfs->rcache, lfs->cfg->block_size, + disk->block, disk->off, &child, sizeof(child)); + if (err) { + return err; + } + + lfs_pair_fromle32(child); + return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT; +} +#endif + +#ifndef LFS_READONLY +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], + lfs_mdir_t *parent) { + // use fetchmatch with callback to find pairs + parent->tail[0] = 0; + parent->tail[1] = 1; + lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + lfs_size_t tortoise_i = 1; + lfs_size_t tortoise_period = 1; + while (!lfs_pair_isnull(parent->tail)) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(parent->tail, tortoise)) { + LFS_WARN("Cycle detected in tail list"); + return LFS_ERR_CORRUPT; + } + if (tortoise_i == tortoise_period) { + tortoise[0] = parent->tail[0]; + tortoise[1] = parent->tail[1]; + tortoise_i = 0; + tortoise_period *= 2; + } + tortoise_i += 1; + + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, + LFS_MKTAG(0x7ff, 0, 0x3ff), + LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), + NULL, + lfs_fs_parent_match, &(struct lfs_fs_parent_match){ + lfs, {pair[0], pair[1]}}); + if (tag && tag != LFS_ERR_NOENT) { + return tag; + } + } + + return LFS_ERR_NOENT; +} +#endif + +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) { + lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200)) + | (uint32_t)needssuperblock << 9; +} + +#ifndef LFS_READONLY +static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0); + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x1ff || orphans <= 0); + lfs->gstate.tag += orphans; + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) | + ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31)); + + return 0; +} +#endif + +#ifndef LFS_READONLY +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]) { + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) | + ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); + lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; + lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_desuperblock(lfs_t *lfs) { + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + return 0; + } + + LFS_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}", + lfs->root[0], + lfs->root[1]); + + lfs_mdir_t root; + int err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + // write a new superblock + lfs_superblock_t superblock = { + .version = lfs_fs_disk_version(lfs), + .block_size = lfs->cfg->block_size, + .block_count = lfs->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + return err; + } + + lfs_fs_prepsuperblock(lfs, false); + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_demove(lfs_t *lfs) { + if (!lfs_gstate_hasmove(&lfs->gdisk)) { + return 0; + } + + // Fix bad moves + LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, + lfs->gdisk.pair[0], + lfs->gdisk.pair[1], + lfs_tag_id(lfs->gdisk.tag)); + + // no other gstate is supported at this time, so if we found something else + // something most likely went wrong in gstate calculation + LFS_ASSERT(lfs_tag_type3(lfs->gdisk.tag) == LFS_TYPE_DELETE); + + // fetch and delete the moved entry + lfs_mdir_t movedir; + int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair); + if (err) { + return err; + } + + // prep gstate and delete move id + uint16_t moveid = lfs_tag_id(lfs->gdisk.tag); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL})); + if (err) { + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) { + if (!lfs_gstate_hasorphans(&lfs->gstate)) { + return 0; + } + + // Check for orphans in two separate passes: + // - 1 for half-orphans (relocations) + // - 2 for full-orphans (removes/renames) + // + // Two separate passes are needed as half-orphans can contain outdated + // references to full-orphans, effectively hiding them from the deorphan + // search. + // + int pass = 0; + while (pass < 2) { + // Fix any orphans + lfs_mdir_t pdir = {.split = true, .tail = {0, 1}}; + lfs_mdir_t dir; + bool moreorphans = false; + + // iterate over all directory directory entries + while (!lfs_pair_isnull(pdir.tail)) { + int err = lfs_dir_fetch(lfs, &dir, pdir.tail); + if (err) { + return err; + } + + // check head blocks for orphans + if (!pdir.split) { + // check if we have a parent + lfs_mdir_t parent; + lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + if (pass == 0 && tag != LFS_ERR_NOENT) { + lfs_block_t pair[2]; + lfs_stag_t state = lfs_dir_get(lfs, &parent, + LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair); + if (state < 0) { + return state; + } + lfs_pair_fromle32(pair); + + if (!lfs_pair_issync(pair, pdir.tail)) { + // we have desynced + LFS_DEBUG("Fixing half-orphan " + "{0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1], pair[0], pair[1]); + + // fix pending move in this pair? this looks like an + // optimization but is in fact _required_ since + // relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while fixing orphans " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + + lfs_pair_tole32(pair); + state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), + pair})); + lfs_pair_fromle32(pair); + if (state < 0) { + return state; + } + + // did our commit create more orphans? + if (state == LFS_OK_ORPHANED) { + moreorphans = true; + } + + // refetch tail + continue; + } + } + + // note we only check for full orphans if we may have had a + // power-loss, otherwise orphans are created intentionally + // during operations such as lfs_mkdir + if (pass == 1 && tag == LFS_ERR_NOENT && powerloss) { + // we are an orphan + LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1]); + + // steal state + err = lfs_dir_getgstate(lfs, &dir, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail + lfs_pair_tole32(dir.tail); + int state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + dir.split, 0x3ff, 8), + dir.tail})); + lfs_pair_fromle32(dir.tail); + if (state < 0) { + return state; + } + + // did our commit create more orphans? + if (state == LFS_OK_ORPHANED) { + moreorphans = true; + } + + // refetch tail + continue; + } + } + + pdir = dir; + } + + pass = moreorphans ? 0 : pass+1; + } + + // mark orphans as fixed + return lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_forceconsistency(lfs_t *lfs) { + int err = lfs_fs_desuperblock(lfs); + if (err) { + return err; + } + + err = lfs_fs_demove(lfs); + if (err) { + return err; + } + + err = lfs_fs_deorphan(lfs, true); + if (err) { + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_mkconsistent_(lfs_t *lfs) { + // lfs_fs_forceconsistency does most of the work here + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // do we have any pending gstate? + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + if (!lfs_gstate_iszero(&delta)) { + // lfs_dir_commit will implicitly write out any pending gstate + lfs_mdir_t root; + err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} +#endif + +static int lfs_fs_size_count(void *p, lfs_block_t block) { + (void)block; + lfs_size_t *size = p; + *size += 1; + return 0; +} + +static lfs_ssize_t lfs_fs_size_(lfs_t *lfs) { + lfs_size_t size = 0; + int err = lfs_fs_traverse_(lfs, lfs_fs_size_count, &size, false); + if (err) { + return err; + } + + return size; +} + +// explicit garbage collection +#ifndef LFS_READONLY +static int lfs_fs_gc_(lfs_t *lfs) { + // force consistency, even if we're not necessarily going to write, + // because this function is supposed to take care of janitorial work + // isn't it? + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // try to compact metadata pairs, note we can't really accomplish + // anything if compact_thresh doesn't at least leave a prog_size + // available + if (lfs->cfg->compact_thresh + < lfs->cfg->block_size - lfs->cfg->prog_size) { + // iterate over all mdirs + lfs_mdir_t mdir = {.tail = {0, 1}}; + while (!lfs_pair_isnull(mdir.tail)) { + err = lfs_dir_fetch(lfs, &mdir, mdir.tail); + if (err) { + return err; + } + + // not erased? exceeds our compaction threshold? + if (!mdir.erased || ((lfs->cfg->compact_thresh == 0) + ? mdir.off > lfs->cfg->block_size - lfs->cfg->block_size/8 + : mdir.off > lfs->cfg->compact_thresh)) { + // the easiest way to trigger a compaction is to mark + // the mdir as unerased and add an empty commit + mdir.erased = false; + err = lfs_dir_commit(lfs, &mdir, NULL, 0); + if (err) { + return err; + } + } + } + } + + // try to populate the lookahead buffer, unless it's already full + if (lfs->lookahead.size < 8*lfs->cfg->lookahead_size) { + err = lfs_alloc_scan(lfs); + if (err) { + return err; + } + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) { + // shrinking is not supported + LFS_ASSERT(block_count >= lfs->block_count); + + if (block_count > lfs->block_count) { + lfs->block_count = block_count; + + // fetch the root + lfs_mdir_t root; + int err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + // update the superblock + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + superblock.block_count = lfs->block_count; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } + } + + return 0; +} +#endif + +#ifdef LFS_MIGRATE +////// Migration from littelfs v1 below this ////// + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_VERSION 0x00010007 +#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16)) +#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_DISK_VERSION 0x00010001 +#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16)) +#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >> 0)) + + +/// v1 Definitions /// + +// File types +enum lfs1_type { + LFS1_TYPE_REG = 0x11, + LFS1_TYPE_DIR = 0x22, + LFS1_TYPE_SUPERBLOCK = 0x2e, +}; + +typedef struct lfs1 { + lfs_block_t root[2]; +} lfs1_t; + +typedef struct lfs1_entry { + lfs_off_t off; + + struct lfs1_disk_entry { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + union { + struct { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs1_entry_t; + +typedef struct lfs1_dir { + struct lfs1_dir *next; + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs1_disk_dir { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs1_dir_t; + +typedef struct lfs1_superblock { + lfs_off_t off; + + struct lfs1_disk_superblock { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs1_superblock_t; + + +/// Low-level wrappers v1->v2 /// +static void lfs1_crc(uint32_t *crc, const void *buffer, size_t size) { + *crc = lfs_crc(*crc, buffer, size); +} + +static int lfs1_bd_read(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + // if we ever do more than writes to alternating pairs, + // this may need to consider pcache + return lfs_bd_read(lfs, &lfs->pcache, &lfs->rcache, size, + block, off, buffer, size); +} + +static int lfs1_bd_crc(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs1_bd_read(lfs, block, off+i, &c, 1); + if (err) { + return err; + } + + lfs1_crc(crc, &c, 1); + } + + return 0; +} + + +/// Endian swapping functions /// +static void lfs1_dir_fromle32(struct lfs1_disk_dir *d) { + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs1_dir_tole32(struct lfs1_disk_dir *d) { + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs1_entry_fromle32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs1_entry_tole32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs1_superblock_fromle32(struct lfs1_disk_superblock *d) { + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + + +///// Metadata pair and directory operations /// +static inline lfs_size_t lfs1_entry_size(const lfs1_entry_t *entry) { + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + +static int lfs1_dir_fetch(lfs_t *lfs, + lfs1_dir_t *dir, const lfs_block_t pair[2]) { + // copy out pair, otherwise may be aliasing dir + const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + // check both blocks for the most recent revision + for (int i = 0; i < 2; i++) { + struct lfs1_disk_dir test; + int err = lfs1_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test)+4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) { + continue; + } + + uint32_t crc = 0xffffffff; + lfs1_dir_tole32(&test); + lfs1_crc(&crc, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + err = lfs1_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (crc != 0) { + continue; + } + + valid = true; + + // setup dir in case it's valid + dir->pair[0] = tpair[(i+0) % 2]; + dir->pair[1] = tpair[(i+1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) { + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + tpair[0], tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +static int lfs1_dir_next(lfs_t *lfs, lfs1_dir_t *dir, lfs1_entry_t *entry) { + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + if (!(0x80000000 & dir->d.size)) { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs1_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs1_bd_read(lfs, dir->pair[0], dir->off, + &entry->d, sizeof(entry->d)); + lfs1_entry_fromle32(&entry->d); + if (err) { + return err; + } + + entry->off = dir->off; + dir->off += lfs1_entry_size(entry); + dir->pos += lfs1_entry_size(entry); + return 0; +} + +/// littlefs v1 specific operations /// +int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // iterate over metadata pairs + lfs1_dir_t dir; + lfs1_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + + while (true) { + for (int i = 0; i < 2; i++) { + int err = cb(data, cwd[i]); + if (err) { + return err; + } + } + + int err = lfs1_dir_fetch(lfs, &dir, cwd); + if (err) { + return err; + } + + // iterate over contents + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { + err = lfs1_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + lfs1_entry_fromle32(&entry.d); + if (err) { + return err; + } + + dir.off += lfs1_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS1_TYPE_REG)) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + entry.d.u.file.head, entry.d.u.file.size, cb, data); + if (err) { + return err; + } + } + } + + // we also need to check if we contain a threaded v2 directory + lfs_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}}; + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + break; + } + + for (int i = 0; i < 2; i++) { + err = cb(data, dir2.pair[i]); + if (err) { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pair_isnull(cwd)) { + break; + } + } + + return 0; +} + +static int lfs1_moved(lfs_t *lfs, const void *e) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // skip superblock + lfs1_dir_t cwd; + int err = lfs1_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directory directory entries + lfs1_entry_t entry; + while (!lfs_pair_isnull(cwd.d.tail)) { + err = lfs1_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs1_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (!(0x80 & entry.d.type) && + memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { + return true; + } + } + } + + return false; +} + +/// Filesystem operations /// +static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, + const struct lfs_config *cfg) { + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + lfs->lfs1 = lfs1; + lfs->lfs1->root[0] = LFS_BLOCK_NULL; + lfs->lfs1->root[1] = LFS_BLOCK_NULL; + + // setup free lookahead + lfs->lookahead.start = 0; + lfs->lookahead.size = 0; + lfs->lookahead.next = 0; + lfs_alloc_ckpoint(lfs); + + // load superblock + lfs1_dir_t dir; + lfs1_superblock_t superblock; + err = lfs1_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + if (!err) { + err = lfs1_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); + lfs1_superblock_fromle32(&superblock.d); + if (err) { + goto cleanup; + } + + lfs->lfs1->root[0] = superblock.d.root[0]; + lfs->lfs1->root[1] = superblock.d.root[1]; + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", + 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + uint16_t major_version = (0xffff & (superblock.d.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS1_DISK_VERSION_MAJOR || + minor_version > LFS1_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version v%d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + return 0; + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs1_unmount(lfs_t *lfs) { + return lfs_deinit(lfs); +} + +/// v1 migration /// +static int lfs_migrate_(lfs_t *lfs, const struct lfs_config *cfg) { + struct lfs1 lfs1; + + // Indeterminate filesystem size not allowed for migration. + LFS_ASSERT(cfg->block_count != 0); + + int err = lfs1_mount(lfs, &lfs1, cfg); + if (err) { + return err; + } + + { + // iterate through each directory, copying over entries + // into new directory + lfs1_dir_t dir1; + lfs_mdir_t dir2; + dir1.d.tail[0] = lfs->lfs1->root[0]; + dir1.d.tail[1] = lfs->lfs1->root[1]; + while (!lfs_pair_isnull(dir1.d.tail)) { + // iterate old dir + err = lfs1_dir_fetch(lfs, &dir1, dir1.d.tail); + if (err) { + goto cleanup; + } + + // create new dir and bind as temporary pretend root + err = lfs_dir_alloc(lfs, &dir2); + if (err) { + goto cleanup; + } + + dir2.rev = dir1.d.rev; + dir1.head[0] = dir1.pair[0]; + dir1.head[1] = dir1.pair[1]; + lfs->root[0] = dir2.pair[0]; + lfs->root[1] = dir2.pair[1]; + + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + + while (true) { + lfs1_entry_t entry1; + err = lfs1_dir_next(lfs, &dir1, &entry1); + if (err && err != LFS_ERR_NOENT) { + goto cleanup; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + // check that entry has not been moved + if (entry1.d.type & 0x80) { + int moved = lfs1_moved(lfs, &entry1.d.u); + if (moved < 0) { + err = moved; + goto cleanup; + } + + if (moved) { + continue; + } + + entry1.d.type &= ~0x80; + } + + // also fetch name + char name[LFS_NAME_MAX+1]; + memset(name, 0, sizeof(name)); + err = lfs1_bd_read(lfs, dir1.pair[0], + entry1.off + 4+entry1.d.elen+entry1.d.alen, + name, entry1.d.nlen); + if (err) { + goto cleanup; + } + + bool isdir = (entry1.d.type == LFS1_TYPE_DIR); + + // create entry in new dir + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + uint16_t id; + err = lfs_dir_find(lfs, &dir2, &(const char*){name}, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + err = (err < 0) ? err : LFS_ERR_EXIST; + goto cleanup; + } + + lfs1_entry_tole32(&entry1.d); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIR, id, entry1.d.nlen, + LFS_TYPE_REG, id, entry1.d.nlen), + name}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), + LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), + &entry1.d.u})); + lfs1_entry_fromle32(&entry1.d); + if (err) { + goto cleanup; + } + } + + if (!lfs_pair_isnull(dir1.d.tail)) { + // find last block and update tail to thread into fs + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + goto cleanup; + } + } + + lfs_pair_tole32(dir2.pair); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); + lfs_pair_fromle32(dir2.pair); + if (err) { + goto cleanup; + } + } + + // Copy over first block to thread into fs. Unfortunately + // if this fails there is not much we can do. + LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]); + + err = lfs_bd_erase(lfs, dir1.head[1]); + if (err) { + goto cleanup; + } + + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + for (lfs_off_t i = 0; i < dir2.off; i++) { + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dir2.off, + dir2.pair[0], i, &dat, 1); + if (err) { + goto cleanup; + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + dir1.head[1], i, &dat, 1); + if (err) { + goto cleanup; + } + } + + err = lfs_bd_flush(lfs, &lfs->pcache, &lfs->rcache, true); + if (err) { + goto cleanup; + } + } + + // Create new superblock. This marks a successful migration! + err = lfs1_dir_fetch(lfs, &dir1, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + dir2.pair[0] = dir1.pair[0]; + dir2.pair[1] = dir1.pair[1]; + dir2.rev = dir1.d.rev; + dir2.off = sizeof(dir2.rev); + dir2.etag = 0xffffffff; + dir2.count = 0; + dir2.tail[0] = lfs->lfs1->root[0]; + dir2.tail[1] = lfs->lfs1->root[1]; + dir2.erased = false; + dir2.split = true; + + lfs_superblock_t superblock = { + .version = LFS_DISK_VERSION, + .block_size = lfs->cfg->block_size, + .block_count = lfs->cfg->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &dir2, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting v1 + dir2.erased = false; + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs1_unmount(lfs); + return err; +} + +#endif + + +/// Public API wrappers /// + +// Here we can add tracing/thread safety easily + +// Thread-safe wrappers if enabled +#ifdef LFS_THREADSAFE +#define LFS_LOCK(cfg) cfg->lock(cfg) +#define LFS_UNLOCK(cfg) cfg->unlock(cfg) +#else +#define LFS_LOCK(cfg) ((void)cfg, 0) +#define LFS_UNLOCK(cfg) ((void)cfg) +#endif + +// Public API +#ifndef LFS_READONLY +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_format(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_format_(lfs, cfg); + + LFS_TRACE("lfs_format -> %d", err); + LFS_UNLOCK(cfg); + return err; +} +#endif + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_mount(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_mount_(lfs, cfg); + + LFS_TRACE("lfs_mount -> %d", err); + LFS_UNLOCK(cfg); + return err; +} + +int lfs_unmount(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_unmount(%p)", (void*)lfs); + + err = lfs_unmount_(lfs); + + LFS_TRACE("lfs_unmount -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_remove(lfs_t *lfs, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); + + err = lfs_remove_(lfs, path); + + LFS_TRACE("lfs_remove -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); + + err = lfs_rename_(lfs, oldpath, newpath); + + LFS_TRACE("lfs_rename -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); + + err = lfs_stat_(lfs, path, info); + + LFS_TRACE("lfs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + + lfs_ssize_t res = lfs_getattr_(lfs, path, type, buffer, size); + + LFS_TRACE("lfs_getattr -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + + err = lfs_setattr_(lfs, path, type, buffer, size); + + LFS_TRACE("lfs_setattr -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); + + err = lfs_removeattr_(lfs, path, type); + + LFS_TRACE("lfs_removeattr -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_NO_MALLOC +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", + (void*)lfs, (void*)file, path, flags); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_open_(lfs, file, path, flags); + + LFS_TRACE("lfs_file_open -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {" + ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", + (void*)lfs, (void*)file, path, flags, + (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_opencfg_(lfs, file, path, flags, cfg); + + LFS_TRACE("lfs_file_opencfg -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_close_(lfs, file); + + LFS_TRACE("lfs_file_close -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_sync_(lfs, file); + + LFS_TRACE("lfs_file_sync -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_ssize_t res = lfs_file_read_(lfs, file, buffer, size); + + LFS_TRACE("lfs_file_read -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_ssize_t res = lfs_file_write_(lfs, file, buffer, size); + + LFS_TRACE("lfs_file_write -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} +#endif + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", + (void*)lfs, (void*)file, off, whence); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_seek_(lfs, file, off, whence); + + LFS_TRACE("lfs_file_seek -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", + (void*)lfs, (void*)file, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_truncate_(lfs, file, size); + + LFS_TRACE("lfs_file_truncate -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_tell_(lfs, file); + + LFS_TRACE("lfs_file_tell -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); + + err = lfs_file_rewind_(lfs, file); + + LFS_TRACE("lfs_file_rewind -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_size_(lfs, file); + + LFS_TRACE("lfs_file_size -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_mkdir(lfs_t *lfs, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); + + err = lfs_mkdir_(lfs, path); + + LFS_TRACE("lfs_mkdir -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)dir)); + + err = lfs_dir_open_(lfs, dir, path); + + LFS_TRACE("lfs_dir_open -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); + + err = lfs_dir_close_(lfs, dir); + + LFS_TRACE("lfs_dir_close -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_read(%p, %p, %p)", + (void*)lfs, (void*)dir, (void*)info); + + err = lfs_dir_read_(lfs, dir, info); + + LFS_TRACE("lfs_dir_read -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", + (void*)lfs, (void*)dir, off); + + err = lfs_dir_seek_(lfs, dir, off); + + LFS_TRACE("lfs_dir_seek -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); + + lfs_soff_t res = lfs_dir_tell_(lfs, dir); + + LFS_TRACE("lfs_dir_tell -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); + + err = lfs_dir_rewind_(lfs, dir); + + LFS_TRACE("lfs_dir_rewind -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo); + + err = lfs_fs_stat_(lfs, fsinfo); + + LFS_TRACE("lfs_fs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_ssize_t lfs_fs_size(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); + + lfs_ssize_t res = lfs_fs_size_(lfs); + + LFS_TRACE("lfs_fs_size -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", + (void*)lfs, (void*)(uintptr_t)cb, data); + + err = lfs_fs_traverse_(lfs, cb, data, true); + + LFS_TRACE("lfs_fs_traverse -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_fs_mkconsistent(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs); + + err = lfs_fs_mkconsistent_(lfs); + + LFS_TRACE("lfs_fs_mkconsistent -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_fs_gc(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs); + + err = lfs_fs_gc_(lfs); + + LFS_TRACE("lfs_fs_gc -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_grow(%p, %"PRIu32")", (void*)lfs, block_count); + + err = lfs_fs_grow_(lfs, block_count); + + LFS_TRACE("lfs_fs_grow -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifdef LFS_MIGRATE +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_migrate(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_migrate_(lfs, cfg); + + LFS_TRACE("lfs_migrate -> %d", err); + LFS_UNLOCK(cfg); + return err; +} +#endif + diff --git a/src/lib/littlefs/lfs.h b/src/lib/littlefs/lfs.h new file mode 100644 index 0000000..9914502 --- /dev/null +++ b/src/lib/littlefs/lfs.h @@ -0,0 +1,795 @@ +/* + * The little filesystem + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_H +#define LFS_H + +#include "lfs_util.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_VERSION 0x00020009 +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_DISK_VERSION 0x00020001 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + + +/// Definitions /// + +// Type definitions +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +// Maximum name size in bytes, may be redefined to reduce the size of the +// info struct. Limited to <= 1022. Stored in superblock and must be +// respected by other littlefs drivers. +#ifndef LFS_NAME_MAX +#define LFS_NAME_MAX 255 +#endif + +// Maximum size of a file in bytes, may be redefined to limit to support other +// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be +// respected by other littlefs drivers. +#ifndef LFS_FILE_MAX +#define LFS_FILE_MAX 2147483647 +#endif + +// Maximum size of custom attributes in bytes, may be redefined, but there is +// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. +#ifndef LFS_ATTR_MAX +#define LFS_ATTR_MAX 1022 +#endif + +// Possible error codes, these are negative to allow +// valid positive return values +enum lfs_error { + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -84, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_FBIG = -27, // File too large + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_NOATTR = -61, // No data/attr available + LFS_ERR_NAMETOOLONG = -36, // File name too long +}; + +// File types +enum lfs_type { + // file types + LFS_TYPE_REG = 0x001, + LFS_TYPE_DIR = 0x002, + + // internally used types + LFS_TYPE_SPLICE = 0x400, + LFS_TYPE_NAME = 0x000, + LFS_TYPE_STRUCT = 0x200, + LFS_TYPE_USERATTR = 0x300, + LFS_TYPE_FROM = 0x100, + LFS_TYPE_TAIL = 0x600, + LFS_TYPE_GLOBALS = 0x700, + LFS_TYPE_CRC = 0x500, + + // internally used type specializations + LFS_TYPE_CREATE = 0x401, + LFS_TYPE_DELETE = 0x4ff, + LFS_TYPE_SUPERBLOCK = 0x0ff, + LFS_TYPE_DIRSTRUCT = 0x200, + LFS_TYPE_CTZSTRUCT = 0x202, + LFS_TYPE_INLINESTRUCT = 0x201, + LFS_TYPE_SOFTTAIL = 0x600, + LFS_TYPE_HARDTAIL = 0x601, + LFS_TYPE_MOVESTATE = 0x7ff, + LFS_TYPE_CCRC = 0x500, + LFS_TYPE_FCRC = 0x5ff, + + // internal chip sources + LFS_FROM_NOOP = 0x000, + LFS_FROM_MOVE = 0x101, + LFS_FROM_USERATTRS = 0x102, +}; + +// File open flags +enum lfs_open_flags { + // open flags + LFS_O_RDONLY = 1, // Open a file as read only +#ifndef LFS_READONLY + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write +#endif + + // internally used flags +#ifndef LFS_READONLY + LFS_F_DIRTY = 0x010000, // File does not match storage + LFS_F_WRITING = 0x020000, // File has been written since last flush +#endif + LFS_F_READING = 0x040000, // File has been read since last flush +#ifndef LFS_READONLY + LFS_F_ERRED = 0x080000, // An error occurred during write +#endif + LFS_F_INLINE = 0x100000, // Currently inlined in directory entry +}; + +// File seek flags +enum lfs_whence_flags { + LFS_SEEK_SET = 0, // Seek relative to an absolute position + LFS_SEEK_CUR = 1, // Seek relative to the current file position + LFS_SEEK_END = 2, // Seek relative to the end of the file +}; + + +// Configuration provided during initialization of the littlefs +struct lfs_config { + // Opaque user provided context that can be used to pass + // information to the block device operations + void *context; + + // Read a region in a block. Negative error codes are propagated + // to the user. + int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + + // Program a region in a block. The block must have previously + // been erased. Negative error codes are propagated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*prog)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + // Erase a block. A block must be erased before being programmed. + // The state of an erased block is undefined. Negative error codes + // are propagated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*erase)(const struct lfs_config *c, lfs_block_t block); + + // Sync the state of the underlying block device. Negative error codes + // are propagated to the user. + int (*sync)(const struct lfs_config *c); + +#ifdef LFS_THREADSAFE + // Lock the underlying block device. Negative error codes + // are propagated to the user. + int (*lock)(const struct lfs_config *c); + + // Unlock the underlying block device. Negative error codes + // are propagated to the user. + int (*unlock)(const struct lfs_config *c); +#endif + + // Minimum size of a block read in bytes. All read operations will be a + // multiple of this value. + lfs_size_t read_size; + + // Minimum size of a block program in bytes. All program operations will be + // a multiple of this value. + lfs_size_t prog_size; + + // Size of an erasable block in bytes. This does not impact ram consumption + // and may be larger than the physical erase size. However, non-inlined + // files take up at minimum one block. Must be a multiple of the read and + // program sizes. + lfs_size_t block_size; + + // Number of erasable blocks on the device. + lfs_size_t block_count; + + // Number of erase cycles before littlefs evicts metadata logs and moves + // the metadata to another block. Suggested values are in the + // range 100-1000, with large values having better performance at the cost + // of less consistent wear distribution. + // + // Set to -1 to disable block-level wear-leveling. + int32_t block_cycles; + + // Size of block caches in bytes. Each cache buffers a portion of a block in + // RAM. The littlefs needs a read cache, a program cache, and one additional + // cache per file. Larger caches can improve performance by storing more + // data and reducing the number of disk accesses. Must be a multiple of the + // read and program sizes, and a factor of the block size. + lfs_size_t cache_size; + + // Size of the lookahead buffer in bytes. A larger lookahead buffer + // increases the number of blocks found during an allocation pass. The + // lookahead buffer is stored as a compact bitmap, so each byte of RAM + // can track 8 blocks. + lfs_size_t lookahead_size; + + // Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata + // pairs that exceed this threshold will be compacted during lfs_fs_gc. + // Defaults to ~88% block_size when zero, though the default may change + // in the future. + // + // Note this only affects lfs_fs_gc. Normal compactions still only occur + // when full. + // + // Set to -1 to disable metadata compaction during lfs_fs_gc. + lfs_size_t compact_thresh; + + // Optional statically allocated read buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *read_buffer; + + // Optional statically allocated program buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *prog_buffer; + + // Optional statically allocated lookahead buffer. Must be lookahead_size. + // By default lfs_malloc is used to allocate this buffer. + void *lookahead_buffer; + + // Optional upper limit on length of file names in bytes. No downside for + // larger names except the size of the info struct which is controlled by + // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in + // superblock and must be respected by other littlefs drivers. + lfs_size_t name_max; + + // Optional upper limit on files in bytes. No downside for larger files + // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored + // in superblock and must be respected by other littlefs drivers. + lfs_size_t file_max; + + // Optional upper limit on custom attributes in bytes. No downside for + // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to + // LFS_ATTR_MAX when zero. + lfs_size_t attr_max; + + // Optional upper limit on total space given to metadata pairs in bytes. On + // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) + // can help bound the metadata compaction time. Must be <= block_size. + // Defaults to block_size when zero. + lfs_size_t metadata_max; + + // Optional upper limit on inlined files in bytes. Inlined files live in + // metadata and decrease storage requirements, but may be limited to + // improve metadata-related performance. Must be <= cache_size, <= + // attr_max, and <= block_size/8. Defaults to the largest possible + // inline_max when zero. + // + // Set to -1 to disable inlined files. + lfs_size_t inline_max; + +#ifdef LFS_MULTIVERSION + // On-disk version to use when writing in the form of 16-bit major version + // + 16-bit minor version. This limiting metadata to what is supported by + // older minor versions. Note that some features will be lost. Defaults to + // to the most recent minor version when zero. + uint32_t disk_version; +#endif +}; + +// File info structure +struct lfs_info { + // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR + uint8_t type; + + // Size of the file, only valid for REG files. Limited to 32-bits. + lfs_size_t size; + + // Name of the file stored as a null-terminated string. Limited to + // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to + // reduce RAM. LFS_NAME_MAX is stored in superblock and must be + // respected by other littlefs drivers. + char name[LFS_NAME_MAX+1]; +}; + +// Filesystem info structure +struct lfs_fsinfo { + // On-disk version. + uint32_t disk_version; + + // Size of a logical block in bytes. + lfs_size_t block_size; + + // Number of logical blocks in filesystem. + lfs_size_t block_count; + + // Upper limit on the length of file names in bytes. + lfs_size_t name_max; + + // Upper limit on the size of files in bytes. + lfs_size_t file_max; + + // Upper limit on the size of custom attributes in bytes. + lfs_size_t attr_max; +}; + +// Custom attribute structure, used to describe custom attributes +// committed atomically during file writes. +struct lfs_attr { + // 8-bit type of attribute, provided by user and used to + // identify the attribute + uint8_t type; + + // Pointer to buffer containing the attribute + void *buffer; + + // Size of attribute in bytes, limited to LFS_ATTR_MAX + lfs_size_t size; +}; + +// Optional configuration provided during lfs_file_opencfg +struct lfs_file_config { + // Optional statically allocated file buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *buffer; + + // Optional list of custom attributes related to the file. If the file + // is opened with read access, these attributes will be read from disk + // during the open call. If the file is opened with write access, the + // attributes will be written to disk every file sync or close. This + // write occurs atomically with update to the file's contents. + // + // Custom attributes are uniquely identified by an 8-bit type and limited + // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller + // than the buffer, it will be padded with zeros. If the stored attribute + // is larger, then it will be silently truncated. If the attribute is not + // found, it will be created implicitly. + struct lfs_attr *attrs; + + // Number of custom attributes in the list + lfs_size_t attr_count; +}; + + +/// internal littlefs data structures /// +typedef struct lfs_cache { + lfs_block_t block; + lfs_off_t off; + lfs_size_t size; + uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_mdir { + lfs_block_t pair[2]; + uint32_t rev; + lfs_off_t off; + uint32_t etag; + uint16_t count; + bool erased; + bool split; + lfs_block_t tail[2]; +} lfs_mdir_t; + +// littlefs directory type +typedef struct lfs_dir { + struct lfs_dir *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + lfs_off_t pos; + lfs_block_t head[2]; +} lfs_dir_t; + +// littlefs file type +typedef struct lfs_file { + struct lfs_file *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + struct lfs_ctz { + lfs_block_t head; + lfs_size_t size; + } ctz; + + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; + + const struct lfs_file_config *cfg; +} lfs_file_t; + +typedef struct lfs_superblock { + uint32_t version; + lfs_size_t block_size; + lfs_size_t block_count; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; +} lfs_superblock_t; + +typedef struct lfs_gstate { + uint32_t tag; + lfs_block_t pair[2]; +} lfs_gstate_t; + +// The littlefs filesystem type +typedef struct lfs { + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_block_t root[2]; + struct lfs_mlist { + struct lfs_mlist *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + } *mlist; + uint32_t seed; + + lfs_gstate_t gstate; + lfs_gstate_t gdisk; + lfs_gstate_t gdelta; + + struct lfs_lookahead { + lfs_block_t start; + lfs_block_t size; + lfs_block_t next; + lfs_block_t ckpoint; + uint8_t *buffer; + } lookahead; + + const struct lfs_config *cfg; + lfs_size_t block_count; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; + lfs_size_t inline_max; + +#ifdef LFS_MIGRATE + struct lfs1 *lfs1; +#endif +} lfs_t; + + +/// Filesystem functions /// + +#ifndef LFS_READONLY +// Format a block device with the littlefs +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_format(lfs_t *lfs, const struct lfs_config *config); +#endif + +// Mounts a littlefs +// +// Requires a littlefs object and config struct. Multiple filesystems +// may be mounted simultaneously with multiple littlefs objects. Both +// lfs and config must be allocated while mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_mount(lfs_t *lfs, const struct lfs_config *config); + +// Unmounts a littlefs +// +// Does nothing besides releasing any allocated resources. +// Returns a negative error code on failure. +int lfs_unmount(lfs_t *lfs); + +/// General operations /// + +#ifndef LFS_READONLY +// Removes a file or directory +// +// If removing a directory, the directory must be empty. +// Returns a negative error code on failure. +int lfs_remove(lfs_t *lfs, const char *path); +#endif + +#ifndef LFS_READONLY +// Rename or move a file or directory +// +// If the destination exists, it must match the source in type. +// If the destination is a directory, the directory must be empty. +// +// Returns a negative error code on failure. +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); +#endif + +// Find info about a file or directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); + +// Get a custom attribute +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than +// the buffer, it will be padded with zeros. If the stored attribute is larger, +// then it will be silently truncated. If no attribute is found, the error +// LFS_ERR_NOATTR is returned and the buffer is filled with zeros. +// +// Returns the size of the attribute, or a negative error code on failure. +// Note, the returned size is the size of the attribute on disk, irrespective +// of the size of the buffer. This can be used to dynamically allocate a buffer +// or check for existence. +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size); + +#ifndef LFS_READONLY +// Set custom attributes +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be +// implicitly created. +// +// Returns a negative error code on failure. +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size); +#endif + +#ifndef LFS_READONLY +// Removes a custom attribute +// +// If an attribute is not found, nothing happens. +// +// Returns a negative error code on failure. +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); +#endif + + +/// File operations /// + +#ifndef LFS_NO_MALLOC +// Open a file +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// Returns a negative error code on failure. +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags); + +// if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM +// thus use lfs_file_opencfg() with config.buffer set. +#endif + +// Open a file with extra configuration +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// The config struct provides additional config options per file as described +// above. The config struct must remain allocated while the file is open, and +// the config struct must be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *config); + +// Close a file +// +// Any pending writes are written out to storage as though +// sync had been called and releases any allocated resources. +// +// Returns a negative error code on failure. +int lfs_file_close(lfs_t *lfs, lfs_file_t *file); + +// Synchronize a file on storage +// +// Any pending writes are written out to storage. +// Returns a negative error code on failure. +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); + +// Read data from file +// +// Takes a buffer and size indicating where to store the read data. +// Returns the number of bytes read, or a negative error code on failure. +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); + +#ifndef LFS_READONLY +// Write data to file +// +// Takes a buffer and size indicating the data to write. The file will not +// actually be updated on the storage until either sync or close is called. +// +// Returns the number of bytes written, or a negative error code on failure. +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +#endif + +// Change the position of the file +// +// The change in position is determined by the offset and whence flag. +// Returns the new position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); + +#ifndef LFS_READONLY +// Truncates the size of the file to the specified size +// +// Returns a negative error code on failure. +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); +#endif + +// Return the position of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns the position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); + +// Change the position of the file to the beginning of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) +// Returns a negative error code on failure. +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); + +// Return the size of the file +// +// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) +// Returns the size of the file, or a negative error code on failure. +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); + + +/// Directory operations /// + +#ifndef LFS_READONLY +// Create a directory +// +// Returns a negative error code on failure. +int lfs_mkdir(lfs_t *lfs, const char *path); +#endif + +// Open a directory +// +// Once open a directory can be used with read to iterate over files. +// Returns a negative error code on failure. +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); + +// Close a directory +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); + +// Read an entry in the directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a positive value on success, 0 at the end of directory, +// or a negative error code on failure. +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); + +// Change the position of the directory +// +// The new off must be a value previous returned from tell and specifies +// an absolute offset in the directory seek. +// +// Returns a negative error code on failure. +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); + +// Return the position of the directory +// +// The returned offset is only meant to be consumed by seek and may not make +// sense, but does indicate the current position in the directory iteration. +// +// Returns the position of the directory, or a negative error code on failure. +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); + +// Change the position of the directory to the beginning of the directory +// +// Returns a negative error code on failure. +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); + + +/// Filesystem-level filesystem operations + +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); + +// Finds the current size of the filesystem +// +// Note: Result is best effort. If files share COW structures, the returned +// size may be larger than the filesystem actually is. +// +// Returns the number of allocated blocks, or a negative error code on failure. +lfs_ssize_t lfs_fs_size(lfs_t *lfs); + +// Traverse through all blocks in use by the filesystem +// +// The provided callback will be called with each block address that is +// currently in use by the filesystem. This can be used to determine which +// blocks are in use or how much of the storage is available. +// +// Returns a negative error code on failure. +int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); + +#ifndef LFS_READONLY +// Attempt to make the filesystem consistent and ready for writing +// +// Calling this function is not required, consistency will be implicitly +// enforced on the first operation that writes to the filesystem, but this +// function allows the work to be performed earlier and without other +// filesystem changes. +// +// Returns a negative error code on failure. +int lfs_fs_mkconsistent(lfs_t *lfs); +#endif + +#ifndef LFS_READONLY +// Attempt any janitorial work +// +// This currently: +// 1. Calls mkconsistent if not already consistent +// 2. Compacts metadata > compact_thresh +// 3. Populates the block allocator +// +// Though additional janitorial work may be added in the future. +// +// Calling this function is not required, but may allow the offloading of +// expensive janitorial work to a less time-critical code path. +// +// Returns a negative error code on failure. Accomplishing nothing is not +// an error. +int lfs_fs_gc(lfs_t *lfs); +#endif + +#ifndef LFS_READONLY +// Grows the filesystem to a new size, updating the superblock with the new +// block count. +// +// Note: This is irreversible. +// +// Returns a negative error code on failure. +int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count); +#endif + +#ifndef LFS_READONLY +#ifdef LFS_MIGRATE +// Attempts to migrate a previous version of littlefs +// +// Behaves similarly to the lfs_format function. Attempts to mount +// the previous version of littlefs and update the filesystem so it can be +// mounted with the current version of littlefs. +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); +#endif +#endif + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/lib/littlefs/lfs_util.c b/src/lib/littlefs/lfs_util.c new file mode 100644 index 0000000..dac72ab --- /dev/null +++ b/src/lib/littlefs/lfs_util.c @@ -0,0 +1,37 @@ +/* + * lfs util functions + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs_util.h" + +// Only compile if user does not provide custom config +#ifndef LFS_CONFIG + + +// If user provides their own CRC impl we don't need this +#ifndef LFS_CRC +// Software CRC implementation with small lookup table +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { + static const uint32_t rtable[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + const uint8_t *data = buffer; + + for (size_t i = 0; i < size; i++) { + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; + } + + return crc; +} +#endif + + +#endif diff --git a/src/lib/littlefs/lfs_util.h b/src/lib/littlefs/lfs_util.h new file mode 100644 index 0000000..4e57700 --- /dev/null +++ b/src/lib/littlefs/lfs_util.h @@ -0,0 +1,255 @@ +/* + * lfs utility functions + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start, I would suggest copying lfs_util.h +// and modifying as needed. +#ifdef LFS_CONFIG +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// System includes +#include +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif +#if !defined(LFS_NO_DEBUG) || \ + !defined(LFS_NO_WARN) || \ + !defined(LFS_NO_ERROR) || \ + defined(LFS_YES_TRACE) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +// Logging functions +#ifndef LFS_TRACE +#ifdef LFS_YES_TRACE +#define LFS_TRACE_(fmt, ...) \ + printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") +#else +#define LFS_TRACE(...) +#endif +#endif + +#ifndef LFS_DEBUG +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG_(fmt, ...) \ + printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") +#else +#define LFS_DEBUG(...) +#endif +#endif + +#ifndef LFS_WARN +#ifndef LFS_NO_WARN +#define LFS_WARN_(fmt, ...) \ + printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") +#else +#define LFS_WARN(...) +#endif +#endif + +#ifndef LFS_ERROR +#ifndef LFS_NO_ERROR +#define LFS_ERROR_(fmt, ...) \ + printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") +#else +#define LFS_ERROR(...) +#endif +#endif + +// Runtime assertions +#ifndef LFS_ASSERT +#ifndef LFS_NO_ASSERT +#define LFS_ASSERT(test) assert(test) +#else +#define LFS_ASSERT(test) +#endif +#endif + + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +// Align to nearest multiple of a size +static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { + return a - (a % alignment); +} + +static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { + return lfs_aligndown(a + alignment-1, alignment); +} + +// Find the smallest power of 2 greater than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert between 32-bit little-endian and native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + return a; +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t*)&a)[0] << 0) | + (((uint8_t*)&a)[1] << 8) | + (((uint8_t*)&a)[2] << 16) | + (((uint8_t*)&a)[3] << 24); +#endif +} + +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Convert between 32-bit big-endian and native order +static inline uint32_t lfs_frombe32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return __builtin_bswap32(a); +#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return a; +#else + return (((uint8_t*)&a)[0] << 24) | + (((uint8_t*)&a)[1] << 16) | + (((uint8_t*)&a)[2] << 8) | + (((uint8_t*)&a)[3] << 0); +#endif +} + +static inline uint32_t lfs_tobe32(uint32_t a) { + return lfs_frombe32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +#ifdef LFS_CRC +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { + return LFS_CRC(crc, buffer, size) +} +#else +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); +#endif + +// Allocate memory, only used if buffers are not provided to littlefs +// +// littlefs current has no alignment requirements, as it only allocates +// byte-level buffers. +static inline void *lfs_malloc(size_t size) { +#if defined(LFS_MALLOC) + return LFS_MALLOC(size); +#elif !defined(LFS_NO_MALLOC) + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#if defined(LFS_FREE) + LFS_FREE(p); +#elif !defined(LFS_NO_MALLOC) + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif From 8068716f51f7f43faef8847e1d7ce75d5e7787b6 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 20:28:41 +0100 Subject: [PATCH 012/188] More LittleFS fixes --- CMakeLists.txt | 3 +++ dependencies.lock | 2 +- esp_littlefs | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 160000 esp_littlefs diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f4c56f..49ba156 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,7 @@ cmake_minimum_required(VERSION 3.16.0) include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +get_filename_component(configName "${CMAKE_BINARY_DIR}" NAME) list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/.pio/libdeps/${configName}/esp_littlefs") + project(btclock_espidf) diff --git a/dependencies.lock b/dependencies.lock index e899dde..c645bdf 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.6 -manifest_hash: ba1e2b36b26ea70c5dc2f8e8eaa05905e8d5374788d5958d051531d768a8e6da +manifest_hash: f4c10dfb616cf7e24f85cb263b8c89ef7d6d8eee64860fd27097b1a83ba56960 target: esp32s3 version: 1.0.0 diff --git a/esp_littlefs b/esp_littlefs deleted file mode 160000 index fd64733..0000000 --- a/esp_littlefs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fd64733cdf248c7a7eb207db7d28124f8857fe0b From 7310e8509a9b93cf753f42fcf45e7013e5a44082 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 20:50:27 +0100 Subject: [PATCH 013/188] Fix web flasher files --- .github/workflows/tagging.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index fe9fc45..7c0f3d5 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -52,8 +52,8 @@ jobs: - name: Create checksum for merged binary run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.sha256 - # - name: Copy all artifacts to output folder - # run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} + - name: Copy all artifacts to output folder + run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} - name: Upload artifacts uses: actions/upload-artifact@v4 @@ -73,7 +73,7 @@ jobs: uses: actions/download-artifact@v4 with: pattern: build-* - merge-multiple: true + merge-multiple: false - name: Write commit hash to file run: mkdir -p lolin_s3_mini_213epd && echo $GITHUB_SHA > lolin_s3_mini_213epd/commit.txt From a3c31da42534b2fa331d5216741f76d0a4d33782 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 21:01:55 +0100 Subject: [PATCH 014/188] Fix paths in workflow --- .github/workflows/tagging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 7c0f3d5..51c3294 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -53,7 +53,7 @@ jobs: run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.sha256 - name: Copy all artifacts to output folder - run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} + run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} - name: Upload artifacts uses: actions/upload-artifact@v4 From f84ae969d4a3dcb16275d4e7a9a189bdd41e3b29 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 18 Mar 2024 21:23:25 +0100 Subject: [PATCH 015/188] Upload all to webflasher --- .github/workflows/tagging.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 51c3294..9d2a0c3 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -75,10 +75,10 @@ jobs: pattern: build-* merge-multiple: false - name: Write commit hash to file - run: mkdir -p lolin_s3_mini_213epd && echo $GITHUB_SHA > lolin_s3_mini_213epd/commit.txt + run: echo $GITHUB_SHA > commit.txt - name: Write build date to file - run: echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" > lolin_s3_mini_213epd/date.txt + run: echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" > date.txt - name: Create release uses: ncipollo/release-action@v1 @@ -100,7 +100,7 @@ jobs: env: SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} with: - source-directory: lolin_s3_mini_213epd/ + source-directory: . target-directory: firmware_v3/ destination-github-username: 'btclock' destination-repository-name: 'web-flasher' From 91fc474a1f5fcd0155edfa18de17ef79f842d2a3 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 30 Mar 2024 11:40:58 +0100 Subject: [PATCH 016/188] Add second block source check --- src/lib/block_notify.cpp | 184 +++++++++++++++++++++++++-------------- src/lib/block_notify.hpp | 5 +- src/main.cpp | 72 +++++++++++---- 3 files changed, 177 insertions(+), 84 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 904b6f3..41e571d 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -5,6 +5,7 @@ esp_websocket_client_handle_t blockNotifyClient = NULL; uint currentBlockHeight = 816000; uint blockMedianFee = 1; bool blockNotifyInit = false; +unsigned long int lastBlockUpdate; // const char *mempoolWsCert = R"(-----BEGIN CERTIFICATE----- // MIIHfTCCBmWgAwIBAgIRANFX3mhqRYDt1NFuENoSyaAwDQYJKoZIhvcNAQELBQAw @@ -50,19 +51,20 @@ bool blockNotifyInit = false; // ew== // -----END CERTIFICATE-----)"; -void setupBlockNotify() { - // currentBlockHeight = preferences.getUInt("blockHeight", 816000); - +void setupBlockNotify() +{ IPAddress result; int dnsErr = -1; String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - while (dnsErr != 1) { + while (dnsErr != 1) + { dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); - if (dnsErr != 1) { + if (dnsErr != 1) + { Serial.print(mempoolInstance); Serial.println(F("mempool DNS could not be resolved")); WiFi.reconnect(); @@ -71,18 +73,18 @@ void setupBlockNotify() { } // Get current block height through regular API - HTTPClient *http = new HTTPClient(); - http->begin("https://" + mempoolInstance + "/api/blocks/tip/height"); - int httpCode = http->GET(); - if (httpCode > 0 && httpCode == HTTP_CODE_OK) { - String blockHeightStr = http->getString(); - currentBlockHeight = blockHeightStr.toInt(); - // xTaskNotifyGive(blockUpdateTaskHandle); - if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - } + currentBlockHeight = getBlockFetch(); + + if (currentBlockHeight != -1) + { + lastBlockUpdate = esp_timer_get_time() / 1000000; + } + + if (workQueue != nullptr) + { + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } // std::strcpy(wsServer, String("wss://" + mempoolInstance + @@ -102,38 +104,41 @@ void setupBlockNotify() { } void onWebsocketEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) { + int32_t event_id, void *event_data) +{ esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}"; - switch (event_id) { - case WEBSOCKET_EVENT_CONNECTED: - blockNotifyInit = true; + switch (event_id) + { + case WEBSOCKET_EVENT_CONNECTED: + blockNotifyInit = true; - Serial.println(F("Connected to Mempool.space WebSocket")); + Serial.println(F("Connected to Mempool.space WebSocket")); - Serial.println(sub); - if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(), - sub.length(), portMAX_DELAY) == -1) { - Serial.println(F("Mempool.space WS Block Subscribe Error")); - } + Serial.println(sub); + if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(), + sub.length(), portMAX_DELAY) == -1) + { + Serial.println(F("Mempool.space WS Block Subscribe Error")); + } - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Mempool.space WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Mempool.space WS Connnection Closed")); - break; + break; + case WEBSOCKET_EVENT_DATA: + onWebsocketMessage(data); + break; + case WEBSOCKET_EVENT_ERROR: + Serial.println(F("Mempool.space WS Connnection error")); + break; + case WEBSOCKET_EVENT_DISCONNECTED: + Serial.println(F("Mempool.space WS Connnection Closed")); + break; } } -void onWebsocketMessage(esp_websocket_event_data_t *event_data) { +void onWebsocketMessage(esp_websocket_event_data_t *event_data) +{ JsonDocument doc; - JsonDocument filter; filter["block"]["height"] = true; filter["mempool-blocks"][0]["medianFee"] = true; @@ -146,56 +151,67 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data) { // return; // } - if (doc.containsKey("block")) { + if (doc.containsKey("block")) + { JsonObject block = doc["block"]; currentBlockHeight = block["height"].as(); - //Serial.printf("New block found: %d\r\n", block["height"].as()); + // Serial.printf("New block found: %d\r\n", block["height"].as()); preferences.putUInt("blockHeight", currentBlockHeight); + lastBlockUpdate = esp_timer_get_time() / 1000000; - if (workQueue != nullptr) { + if (workQueue != nullptr) + { WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); // xTaskNotifyGive(blockUpdateTaskHandle); if (getCurrentScreen() != SCREEN_BLOCK_HEIGHT && - preferences.getBool("stealFocus", true)) { + preferences.getBool("stealFocus", true)) + { uint64_t timerPeriod = 0; - if (isTimerActive()) { + if (isTimerActive()) + { // store timer periode before making inactive to prevent artifacts timerPeriod = getTimerSeconds(); esp_timer_stop(screenRotateTimer); } setCurrentScreen(SCREEN_BLOCK_HEIGHT); - if (timerPeriod > 0) { + if (timerPeriod > 0) + { esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } } - if (preferences.getBool("ledFlashOnUpd", false)) { - vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated + if (preferences.getBool("ledFlashOnUpd", false)) + { + vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated queueLedEffect(LED_FLASH_BLOCK_NOTIFY); } } - } else if (doc.containsKey("mempool-blocks")) { - JsonArray blockInfo = doc["mempool-blocks"].as(); + } + else if (doc.containsKey("mempool-blocks")) + { + JsonArray blockInfo = doc["mempool-blocks"].as(); - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); - if (blockMedianFee == medianFee) { - doc.clear(); - return; - } + if (blockMedianFee == medianFee) + { + doc.clear(); + return; + } - // Serial.printf("New median fee: %d\r\n", medianFee); - blockMedianFee = medianFee; + // Serial.printf("New median fee: %d\r\n", medianFee); + blockMedianFee = medianFee; - if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - } + if (workQueue != nullptr) + { + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } } doc.clear(); @@ -203,31 +219,67 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data) { uint getBlockHeight() { return currentBlockHeight; } -void setBlockHeight(uint newBlockHeight) { +void setBlockHeight(uint newBlockHeight) +{ currentBlockHeight = newBlockHeight; } uint getBlockMedianFee() { return blockMedianFee; } -void setBlockMedianFee(uint newBlockMedianFee) { +void setBlockMedianFee(uint newBlockMedianFee) +{ blockMedianFee = newBlockMedianFee; } -bool isBlockNotifyConnected() { - if (blockNotifyClient == NULL) return false; +bool isBlockNotifyConnected() +{ + if (blockNotifyClient == NULL) + return false; return esp_websocket_client_is_connected(blockNotifyClient); } -bool getBlockNotifyInit() { +bool getBlockNotifyInit() +{ return blockNotifyInit; } -void stopBlockNotify() { - if (blockNotifyClient == NULL) return; +void stopBlockNotify() +{ + if (blockNotifyClient == NULL) + return; esp_websocket_client_close(blockNotifyClient, portMAX_DELAY); esp_websocket_client_stop(blockNotifyClient); esp_websocket_client_destroy(blockNotifyClient); blockNotifyClient = NULL; +} + +int getBlockFetch() +{ + String mempoolInstance = + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + + // Get current block height through regular API + HTTPClient *http = new HTTPClient(); + http->begin("https://" + mempoolInstance + "/api/blocks/tip/height"); + int httpCode = http->GET(); + + if (httpCode > 0 && httpCode == HTTP_CODE_OK) + { + String blockHeightStr = http->getString(); + return blockHeightStr.toInt(); + } + + return -1; +} + +uint getLastBlockUpdate() +{ + return lastBlockUpdate; +} + +void setLastBlockUpdate(uint lastUpdate) +{ + lastBlockUpdate = lastUpdate; } \ No newline at end of file diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index 72d9500..bfcc518 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -29,4 +29,7 @@ uint getBlockMedianFee(); bool isBlockNotifyConnected(); void stopBlockNotify(); -bool getBlockNotifyInit(); \ No newline at end of file +bool getBlockNotifyInit(); +uint getLastBlockUpdate(); +int getBlockFetch(); +void setLastBlockUpdate(uint lastUpdate); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 47e73fb..fb7148a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2023 Djuri Baars + * Copyright 2023-2024 Djuri Baars * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,13 +23,15 @@ uint wifiLostConnection; uint priceNotifyLostConnection = 0; uint blockNotifyLostConnection = 0; -extern "C" void app_main() { +extern "C" void app_main() +{ initArduino(); Serial.begin(115200); setup(); - while (true) { + while (true) + { // vTaskList(ptrTaskList); // Serial.println(F("**********************************")); // Serial.println(F("Task State Prio Stack Num")); @@ -39,60 +41,96 @@ extern "C" void app_main() { if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); - int64_t currentUptime = esp_timer_get_time() / 1000000;; + int64_t currentUptime = esp_timer_get_time() / 1000000; + ; - if (!WiFi.isConnected()) { - if (!wifiLostConnection) { + if (!WiFi.isConnected()) + { + if (!wifiLostConnection) + { wifiLostConnection = currentUptime; Serial.println("Lost WiFi connection, trying to reconnect..."); } - if ((currentUptime - wifiLostConnection) > 600) { + if ((currentUptime - wifiLostConnection) > 600) + { Serial.println("Still no connection after 10 minutes, restarting..."); delay(2000); ESP.restart(); } WiFi.begin(); - } else if (wifiLostConnection) { + } + else if (wifiLostConnection) + { wifiLostConnection = 0; Serial.println("Connection restored, reset timer."); - } else if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) { + } + else if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) + { priceNotifyLostConnection++; Serial.println("Lost price data connection..."); queueLedEffect(LED_DATA_PRICE_ERROR); // if price WS connection does not come back after 6*5 seconds, destroy and recreate - if (priceNotifyLostConnection > 6) { + if (priceNotifyLostConnection > 6) + { Serial.println("Restarting price handler..."); stopPriceNotify(); setupPriceNotify(); priceNotifyLostConnection = 0; } - } else if (getBlockNotifyInit() && !isBlockNotifyConnected()) { + } + else if (getBlockNotifyInit() && !isBlockNotifyConnected()) + { blockNotifyLostConnection++; Serial.println("Lost block data connection..."); queueLedEffect(LED_DATA_BLOCK_ERROR); // if mempool WS connection does not come back after 6*5 seconds, destroy and recreate - if (blockNotifyLostConnection > 6) { + if (blockNotifyLostConnection > 6) + { Serial.println("Restarting block handler..."); stopBlockNotify(); setupBlockNotify(); blockNotifyLostConnection = 0; } - } else if (blockNotifyLostConnection > 0 || priceNotifyLostConnection > 0) { + } + else if (blockNotifyLostConnection > 0 || priceNotifyLostConnection > 0) + { blockNotifyLostConnection = 0; priceNotifyLostConnection = 0; } // if more than 5 price updates are missed, there is probably something wrong, reconnect - if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE)*5)) { - stopPriceNotify(); - setupPriceNotify(); + if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) + { + Serial.println("Detected 5 missed price updates... restarting price handler."); - priceNotifyLostConnection = 0; + stopPriceNotify(); + setupPriceNotify(); + + priceNotifyLostConnection = 0; + } + + // If after 45 minutes no mempool blocks, check the rest API + if ((getLastBlockUpdate() - currentUptime) > 45 * 60) + { + Serial.println("Long time (45 min) since last block, checking if I missed anything..."); + int currentBlock = getBlockFetch(); + if (currentBlock != -1) + { + if (currentBlock != getBlockHeight()) + { + Serial.println("Detected stuck block height... restarting block handler."); + // Mempool source stuck, restart + stopBlockNotify(); + setupBlockNotify(); + } + // set last block update so it doesn't fetch for 45 minutes + setLastBlockUpdate(currentUptime); + } } vTaskDelay(pdMS_TO_TICKS(5000)); From 2ef56c1938d0d4584c0474720cdec38e0176ac54 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 12 Apr 2024 00:17:40 +0200 Subject: [PATCH 017/188] Improve lost data detection --- src/lib/config.cpp | 45 ++++++++++++++++++++++++++------------------- src/lib/config.hpp | 3 ++- src/lib/ota.cpp | 4 ++-- src/main.cpp | 31 +++++++++++++++++++------------ 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 98911e4..19b8132 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -9,6 +9,7 @@ Adafruit_MCP23X17 mcp2; #endif std::vector screenNameMap(SCREEN_COUNT); std::mutex mcpMutex; +uint lastTimeSync; void setup() { @@ -46,7 +47,7 @@ void setup() setupWebserver(); // setupWifi(); - setupTime(); + syncTime(); finishSetup(); setupTasks(); @@ -193,7 +194,7 @@ void tryImprovSetup() // queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); } -void setupTime() +void syncTime() { configTime(preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS), 0, NTP_SERVER); @@ -206,6 +207,8 @@ void setupTime() delay(500); Serial.println(F("Retry set time")); } + + lastTimeSync = esp_timer_get_time() / 1000000; } void setupPreferences() @@ -300,7 +303,7 @@ void setupHardware() if (!LittleFS.open("/index.html.gz", "r")) { - Serial.println("Error loading WebUI"); + Serial.println(F("Error loading WebUI")); } setupLeds(); @@ -568,25 +571,25 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) switch (event) { case ARDUINO_EVENT_WIFI_READY: - Serial.println("WiFi interface ready"); + Serial.println(F("WiFi interface ready")); break; case ARDUINO_EVENT_WIFI_SCAN_DONE: - Serial.println("Completed scan for access points"); + Serial.println(F("Completed scan for access points")); break; case ARDUINO_EVENT_WIFI_STA_START: - Serial.println("WiFi client started"); + Serial.println(F("WiFi client started")); break; case ARDUINO_EVENT_WIFI_STA_STOP: - Serial.println("WiFi clients stopped"); + Serial.println(F("WiFi clients stopped")); break; case ARDUINO_EVENT_WIFI_STA_CONNECTED: - Serial.println("Connected to access point"); + Serial.println(F("Connected to access point")); break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: { if (!first_connect) { - Serial.println("Disconnected from WiFi access point"); + Serial.println(F("Disconnected from WiFi access point")); queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); uint8_t reason = info.wifi_sta_disconnected.reason; if (reason) @@ -596,7 +599,7 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) break; } case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - Serial.println("Authentication mode of access point has changed"); + Serial.println(F("Authentication mode of access point has changed")); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP: { @@ -608,33 +611,33 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) break; } case ARDUINO_EVENT_WIFI_STA_LOST_IP: - Serial.println("Lost IP address and IP address is reset to 0"); + Serial.println(F("Lost IP address and IP address is reset to 0")); queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); WiFi.reconnect(); break; case ARDUINO_EVENT_WIFI_AP_START: - Serial.println("WiFi access point started"); + Serial.println(F("WiFi access point started")); break; case ARDUINO_EVENT_WIFI_AP_STOP: - Serial.println("WiFi access point stopped"); + Serial.println(F("WiFi access point stopped")); break; case ARDUINO_EVENT_WIFI_AP_STACONNECTED: - Serial.println("Client connected"); + Serial.println(F("Client connected")); break; case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: - Serial.println("Client disconnected"); + Serial.println(F("Client disconnected")); break; case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: - Serial.println("Assigned IP address to client"); + Serial.println(F("Assigned IP address to client")); break; case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: - Serial.println("Received probe request"); + Serial.println(F("Received probe request")); break; case ARDUINO_EVENT_WIFI_AP_GOT_IP6: - Serial.println("AP IPv6 is preferred"); + Serial.println(F("AP IPv6 is preferred")); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - Serial.println("STA IPv6 is preferred"); + Serial.println(F("STA IPv6 is preferred")); break; default: break; @@ -651,4 +654,8 @@ String getMyHostname() snprintf(hostname, sizeof(hostname), "%s-%02x%02x%02x", hostnamePrefix, mac[3], mac[4], mac[5]); return hostname; +} + +uint getLastTimeSync() { + return lastTimeSync; } \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index cd4b262..4f5a83c 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -33,7 +33,8 @@ #define DEFAULT_BG_COLOR GxEPD_BLACK void setup(); -void setupTime(); +void syncTime(); +uint getLastTimeSync(); void setupPreferences(); void setupWebsocketClients(void *pvParameters); void setupHardware(); diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 945a4d4..ea82348 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -127,7 +127,7 @@ void downloadUpdate() { } void onOTAError(ota_error_t error) { - Serial.println("\nOTA update error, restarting"); + Serial.println(F("\nOTA update error, restarting")); Wire.end(); SPI.end(); delay(1000); @@ -135,7 +135,7 @@ void onOTAError(ota_error_t error) { } void onOTAComplete() { - Serial.println("\nOTA update finished"); + Serial.println(F("\nOTA update finished")); Wire.end(); SPI.end(); delay(1000); diff --git a/src/main.cpp b/src/main.cpp index fb7148a..4968eda 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,12 +49,12 @@ extern "C" void app_main() if (!wifiLostConnection) { wifiLostConnection = currentUptime; - Serial.println("Lost WiFi connection, trying to reconnect..."); + Serial.println(F("Lost WiFi connection, trying to reconnect...")); } if ((currentUptime - wifiLostConnection) > 600) { - Serial.println("Still no connection after 10 minutes, restarting..."); + Serial.println(F("Still no connection after 10 minutes, restarting...")); delay(2000); ESP.restart(); } @@ -64,33 +64,35 @@ extern "C" void app_main() else if (wifiLostConnection) { wifiLostConnection = 0; - Serial.println("Connection restored, reset timer."); + Serial.println(F("Connection restored, reset timer.")); } - else if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) + + if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) { priceNotifyLostConnection++; - Serial.println("Lost price data connection..."); + Serial.println(F("Lost price data connection...")); queueLedEffect(LED_DATA_PRICE_ERROR); // if price WS connection does not come back after 6*5 seconds, destroy and recreate if (priceNotifyLostConnection > 6) { - Serial.println("Restarting price handler..."); + Serial.println(F("Restarting price handler...")); stopPriceNotify(); setupPriceNotify(); priceNotifyLostConnection = 0; } } - else if (getBlockNotifyInit() && !isBlockNotifyConnected()) + + if (getBlockNotifyInit() && !isBlockNotifyConnected()) { blockNotifyLostConnection++; - Serial.println("Lost block data connection..."); + Serial.println(F("Lost block data connection...")); queueLedEffect(LED_DATA_BLOCK_ERROR); // if mempool WS connection does not come back after 6*5 seconds, destroy and recreate if (blockNotifyLostConnection > 6) { - Serial.println("Restarting block handler..."); + Serial.println(F("Restarting block handler...")); stopBlockNotify(); setupBlockNotify(); @@ -106,7 +108,7 @@ extern "C" void app_main() // if more than 5 price updates are missed, there is probably something wrong, reconnect if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) { - Serial.println("Detected 5 missed price updates... restarting price handler."); + Serial.println(F("Detected 5 missed price updates... restarting price handler.")); stopPriceNotify(); setupPriceNotify(); @@ -117,13 +119,13 @@ extern "C" void app_main() // If after 45 minutes no mempool blocks, check the rest API if ((getLastBlockUpdate() - currentUptime) > 45 * 60) { - Serial.println("Long time (45 min) since last block, checking if I missed anything..."); + Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); int currentBlock = getBlockFetch(); if (currentBlock != -1) { if (currentBlock != getBlockHeight()) { - Serial.println("Detected stuck block height... restarting block handler."); + Serial.println(F("Detected stuck block height... restarting block handler.")); // Mempool source stuck, restart stopBlockNotify(); setupBlockNotify(); @@ -133,6 +135,11 @@ extern "C" void app_main() } } + if (currentUptime - getLastTimeSync() > 24 * 60 * 60) { + Serial.println(F("Last time update is longer than 24 hours ago, sync again")); + syncTime(); + }; + vTaskDelay(pdMS_TO_TICKS(5000)); } } \ No newline at end of file From ad0800c233834eb2ddb8b1b5584190e0666280f3 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 16 Apr 2024 15:17:34 +0200 Subject: [PATCH 018/188] Further improvements for connection recovery --- src/lib/block_notify.cpp | 17 +- src/lib/block_notify.hpp | 2 + src/lib/button_handler.cpp | 2 +- src/lib/littlefs/lfs.c | 6457 ----------------------------------- src/lib/littlefs/lfs.h | 795 ----- src/lib/littlefs/lfs_util.c | 37 - src/lib/littlefs/lfs_util.h | 255 -- src/lib/price_notify.cpp | 13 +- src/lib/price_notify.hpp | 2 + src/lib/webserver.cpp | 8 +- src/main.cpp | 24 +- 11 files changed, 51 insertions(+), 7561 deletions(-) delete mode 100644 src/lib/littlefs/lfs.c delete mode 100644 src/lib/littlefs/lfs.h delete mode 100644 src/lib/littlefs/lfs_util.c delete mode 100644 src/lib/littlefs/lfs_util.h diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 41e571d..d01b3d6 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -248,13 +248,28 @@ void stopBlockNotify() if (blockNotifyClient == NULL) return; - esp_websocket_client_close(blockNotifyClient, portMAX_DELAY); + esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); esp_websocket_client_stop(blockNotifyClient); esp_websocket_client_destroy(blockNotifyClient); blockNotifyClient = NULL; } +void restartBlockNotify() +{ + stopBlockNotify(); + + if (blockNotifyClient == NULL) { + setupBlockNotify(); + return; + } + + // esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); + // esp_websocket_client_stop(blockNotifyClient); + // esp_websocket_client_start(blockNotifyClient); +} + + int getBlockFetch() { String mempoolInstance = diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index bfcc518..b44060f 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -29,6 +29,8 @@ uint getBlockMedianFee(); bool isBlockNotifyConnected(); void stopBlockNotify(); +void restartBlockNotify(); + bool getBlockNotifyInit(); uint getLastBlockUpdate(); int getBlockFetch(); diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index a74b37b..7b27a64 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -50,7 +50,7 @@ void IRAM_ATTR handleButtonInterrupt() { } void setupButtonTask() { - xTaskCreate(buttonTask, "ButtonTask", 4096, NULL, tskIDLE_PRIORITY, + xTaskCreate(buttonTask, "ButtonTask", 3072, NULL, tskIDLE_PRIORITY, &buttonTaskHandle); // Create the FreeRTOS task // Use interrupt instead of task attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, CHANGE); diff --git a/src/lib/littlefs/lfs.c b/src/lib/littlefs/lfs.c deleted file mode 100644 index a0bd76f..0000000 --- a/src/lib/littlefs/lfs.c +++ /dev/null @@ -1,6457 +0,0 @@ -/* - * The little filesystem - * - * Copyright (c) 2022, The littlefs authors. - * Copyright (c) 2017, Arm Limited. All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - */ -#include "lfs.h" -#include "lfs_util.h" - - -// some constants used throughout the code -#define LFS_BLOCK_NULL ((lfs_block_t)-1) -#define LFS_BLOCK_INLINE ((lfs_block_t)-2) - -enum { - LFS_OK_RELOCATED = 1, - LFS_OK_DROPPED = 2, - LFS_OK_ORPHANED = 3, -}; - -enum { - LFS_CMP_EQ = 0, - LFS_CMP_LT = 1, - LFS_CMP_GT = 2, -}; - - -/// Caching block device operations /// - -static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { - // do not zero, cheaper if cache is readonly or only going to be - // written with identical data (during relocates) - (void)lfs; - rcache->block = LFS_BLOCK_NULL; -} - -static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { - // zero to avoid information leak - memset(pcache->buffer, 0xff, lfs->cfg->cache_size); - pcache->block = LFS_BLOCK_NULL; -} - -static int lfs_bd_read(lfs_t *lfs, - const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, - lfs_block_t block, lfs_off_t off, - void *buffer, lfs_size_t size) { - uint8_t *data = buffer; - if (off+size > lfs->cfg->block_size - || (lfs->block_count && block >= lfs->block_count)) { - return LFS_ERR_CORRUPT; - } - - while (size > 0) { - lfs_size_t diff = size; - - if (pcache && block == pcache->block && - off < pcache->off + pcache->size) { - if (off >= pcache->off) { - // is already in pcache? - diff = lfs_min(diff, pcache->size - (off-pcache->off)); - memcpy(data, &pcache->buffer[off-pcache->off], diff); - - data += diff; - off += diff; - size -= diff; - continue; - } - - // pcache takes priority - diff = lfs_min(diff, pcache->off-off); - } - - if (block == rcache->block && - off < rcache->off + rcache->size) { - if (off >= rcache->off) { - // is already in rcache? - diff = lfs_min(diff, rcache->size - (off-rcache->off)); - memcpy(data, &rcache->buffer[off-rcache->off], diff); - - data += diff; - off += diff; - size -= diff; - continue; - } - - // rcache takes priority - diff = lfs_min(diff, rcache->off-off); - } - - if (size >= hint && off % lfs->cfg->read_size == 0 && - size >= lfs->cfg->read_size) { - // bypass cache? - diff = lfs_aligndown(diff, lfs->cfg->read_size); - int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); - if (err) { - return err; - } - - data += diff; - off += diff; - size -= diff; - continue; - } - - // load to cache, first condition can no longer fail - LFS_ASSERT(!lfs->block_count || block < lfs->block_count); - rcache->block = block; - rcache->off = lfs_aligndown(off, lfs->cfg->read_size); - rcache->size = lfs_min( - lfs_min( - lfs_alignup(off+hint, lfs->cfg->read_size), - lfs->cfg->block_size) - - rcache->off, - lfs->cfg->cache_size); - int err = lfs->cfg->read(lfs->cfg, rcache->block, - rcache->off, rcache->buffer, rcache->size); - LFS_ASSERT(err <= 0); - if (err) { - return err; - } - } - - return 0; -} - -static int lfs_bd_cmp(lfs_t *lfs, - const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, - lfs_block_t block, lfs_off_t off, - const void *buffer, lfs_size_t size) { - const uint8_t *data = buffer; - lfs_size_t diff = 0; - - for (lfs_off_t i = 0; i < size; i += diff) { - uint8_t dat[8]; - - diff = lfs_min(size-i, sizeof(dat)); - int err = lfs_bd_read(lfs, - pcache, rcache, hint-i, - block, off+i, &dat, diff); - if (err) { - return err; - } - - int res = memcmp(dat, data + i, diff); - if (res) { - return res < 0 ? LFS_CMP_LT : LFS_CMP_GT; - } - } - - return LFS_CMP_EQ; -} - -static int lfs_bd_crc(lfs_t *lfs, - const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, - lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) { - lfs_size_t diff = 0; - - for (lfs_off_t i = 0; i < size; i += diff) { - uint8_t dat[8]; - diff = lfs_min(size-i, sizeof(dat)); - int err = lfs_bd_read(lfs, - pcache, rcache, hint-i, - block, off+i, &dat, diff); - if (err) { - return err; - } - - *crc = lfs_crc(*crc, &dat, diff); - } - - return 0; -} - -#ifndef LFS_READONLY -static int lfs_bd_flush(lfs_t *lfs, - lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { - if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) { - LFS_ASSERT(pcache->block < lfs->block_count); - lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size); - int err = lfs->cfg->prog(lfs->cfg, pcache->block, - pcache->off, pcache->buffer, diff); - LFS_ASSERT(err <= 0); - if (err) { - return err; - } - - if (validate) { - // check data on disk - lfs_cache_drop(lfs, rcache); - int res = lfs_bd_cmp(lfs, - NULL, rcache, diff, - pcache->block, pcache->off, pcache->buffer, diff); - if (res < 0) { - return res; - } - - if (res != LFS_CMP_EQ) { - return LFS_ERR_CORRUPT; - } - } - - lfs_cache_zero(lfs, pcache); - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_bd_sync(lfs_t *lfs, - lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { - lfs_cache_drop(lfs, rcache); - - int err = lfs_bd_flush(lfs, pcache, rcache, validate); - if (err) { - return err; - } - - err = lfs->cfg->sync(lfs->cfg); - LFS_ASSERT(err <= 0); - return err; -} -#endif - -#ifndef LFS_READONLY -static int lfs_bd_prog(lfs_t *lfs, - lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate, - lfs_block_t block, lfs_off_t off, - const void *buffer, lfs_size_t size) { - const uint8_t *data = buffer; - LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count); - LFS_ASSERT(off + size <= lfs->cfg->block_size); - - while (size > 0) { - if (block == pcache->block && - off >= pcache->off && - off < pcache->off + lfs->cfg->cache_size) { - // already fits in pcache? - lfs_size_t diff = lfs_min(size, - lfs->cfg->cache_size - (off-pcache->off)); - memcpy(&pcache->buffer[off-pcache->off], data, diff); - - data += diff; - off += diff; - size -= diff; - - pcache->size = lfs_max(pcache->size, off - pcache->off); - if (pcache->size == lfs->cfg->cache_size) { - // eagerly flush out pcache if we fill up - int err = lfs_bd_flush(lfs, pcache, rcache, validate); - if (err) { - return err; - } - } - - continue; - } - - // pcache must have been flushed, either by programming and - // entire block or manually flushing the pcache - LFS_ASSERT(pcache->block == LFS_BLOCK_NULL); - - // prepare pcache, first condition can no longer fail - pcache->block = block; - pcache->off = lfs_aligndown(off, lfs->cfg->prog_size); - pcache->size = 0; - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { - LFS_ASSERT(block < lfs->block_count); - int err = lfs->cfg->erase(lfs->cfg, block); - LFS_ASSERT(err <= 0); - return err; -} -#endif - - -/// Small type-level utilities /// -// operations on block pairs -static inline void lfs_pair_swap(lfs_block_t pair[2]) { - lfs_block_t t = pair[0]; - pair[0] = pair[1]; - pair[1] = t; -} - -static inline bool lfs_pair_isnull(const lfs_block_t pair[2]) { - return pair[0] == LFS_BLOCK_NULL || pair[1] == LFS_BLOCK_NULL; -} - -static inline int lfs_pair_cmp( - const lfs_block_t paira[2], - const lfs_block_t pairb[2]) { - return !(paira[0] == pairb[0] || paira[1] == pairb[1] || - paira[0] == pairb[1] || paira[1] == pairb[0]); -} - -static inline bool lfs_pair_issync( - const lfs_block_t paira[2], - const lfs_block_t pairb[2]) { - return (paira[0] == pairb[0] && paira[1] == pairb[1]) || - (paira[0] == pairb[1] && paira[1] == pairb[0]); -} - -static inline void lfs_pair_fromle32(lfs_block_t pair[2]) { - pair[0] = lfs_fromle32(pair[0]); - pair[1] = lfs_fromle32(pair[1]); -} - -#ifndef LFS_READONLY -static inline void lfs_pair_tole32(lfs_block_t pair[2]) { - pair[0] = lfs_tole32(pair[0]); - pair[1] = lfs_tole32(pair[1]); -} -#endif - -// operations on 32-bit entry tags -typedef uint32_t lfs_tag_t; -typedef int32_t lfs_stag_t; - -#define LFS_MKTAG(type, id, size) \ - (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size)) - -#define LFS_MKTAG_IF(cond, type, id, size) \ - ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0)) - -#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ - ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2)) - -static inline bool lfs_tag_isvalid(lfs_tag_t tag) { - return !(tag & 0x80000000); -} - -static inline bool lfs_tag_isdelete(lfs_tag_t tag) { - return ((int32_t)(tag << 22) >> 22) == -1; -} - -static inline uint16_t lfs_tag_type1(lfs_tag_t tag) { - return (tag & 0x70000000) >> 20; -} - -static inline uint16_t lfs_tag_type2(lfs_tag_t tag) { - return (tag & 0x78000000) >> 20; -} - -static inline uint16_t lfs_tag_type3(lfs_tag_t tag) { - return (tag & 0x7ff00000) >> 20; -} - -static inline uint8_t lfs_tag_chunk(lfs_tag_t tag) { - return (tag & 0x0ff00000) >> 20; -} - -static inline int8_t lfs_tag_splice(lfs_tag_t tag) { - return (int8_t)lfs_tag_chunk(tag); -} - -static inline uint16_t lfs_tag_id(lfs_tag_t tag) { - return (tag & 0x000ffc00) >> 10; -} - -static inline lfs_size_t lfs_tag_size(lfs_tag_t tag) { - return tag & 0x000003ff; -} - -static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) { - return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag)); -} - -// operations on attributes in attribute lists -struct lfs_mattr { - lfs_tag_t tag; - const void *buffer; -}; - -struct lfs_diskoff { - lfs_block_t block; - lfs_off_t off; -}; - -#define LFS_MKATTRS(...) \ - (struct lfs_mattr[]){__VA_ARGS__}, \ - sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr) - -// operations on global state -static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { - for (int i = 0; i < 3; i++) { - ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; - } -} - -static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) { - for (int i = 0; i < 3; i++) { - if (((uint32_t*)a)[i] != 0) { - return false; - } - } - return true; -} - -#ifndef LFS_READONLY -static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) { - return lfs_tag_size(a->tag); -} - -static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { - return lfs_tag_size(a->tag) & 0x1ff; -} - -static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { - return lfs_tag_type1(a->tag); -} -#endif - -static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) { - return lfs_tag_size(a->tag) >> 9; -} - -static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, - const lfs_block_t *pair) { - return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; -} - -static inline void lfs_gstate_fromle32(lfs_gstate_t *a) { - a->tag = lfs_fromle32(a->tag); - a->pair[0] = lfs_fromle32(a->pair[0]); - a->pair[1] = lfs_fromle32(a->pair[1]); -} - -#ifndef LFS_READONLY -static inline void lfs_gstate_tole32(lfs_gstate_t *a) { - a->tag = lfs_tole32(a->tag); - a->pair[0] = lfs_tole32(a->pair[0]); - a->pair[1] = lfs_tole32(a->pair[1]); -} -#endif - -// operations on forward-CRCs used to track erased state -struct lfs_fcrc { - lfs_size_t size; - uint32_t crc; -}; - -static void lfs_fcrc_fromle32(struct lfs_fcrc *fcrc) { - fcrc->size = lfs_fromle32(fcrc->size); - fcrc->crc = lfs_fromle32(fcrc->crc); -} - -#ifndef LFS_READONLY -static void lfs_fcrc_tole32(struct lfs_fcrc *fcrc) { - fcrc->size = lfs_tole32(fcrc->size); - fcrc->crc = lfs_tole32(fcrc->crc); -} -#endif - -// other endianness operations -static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { - ctz->head = lfs_fromle32(ctz->head); - ctz->size = lfs_fromle32(ctz->size); -} - -#ifndef LFS_READONLY -static void lfs_ctz_tole32(struct lfs_ctz *ctz) { - ctz->head = lfs_tole32(ctz->head); - ctz->size = lfs_tole32(ctz->size); -} -#endif - -static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) { - superblock->version = lfs_fromle32(superblock->version); - superblock->block_size = lfs_fromle32(superblock->block_size); - superblock->block_count = lfs_fromle32(superblock->block_count); - superblock->name_max = lfs_fromle32(superblock->name_max); - superblock->file_max = lfs_fromle32(superblock->file_max); - superblock->attr_max = lfs_fromle32(superblock->attr_max); -} - -#ifndef LFS_READONLY -static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) { - superblock->version = lfs_tole32(superblock->version); - superblock->block_size = lfs_tole32(superblock->block_size); - superblock->block_count = lfs_tole32(superblock->block_count); - superblock->name_max = lfs_tole32(superblock->name_max); - superblock->file_max = lfs_tole32(superblock->file_max); - superblock->attr_max = lfs_tole32(superblock->attr_max); -} -#endif - -#ifndef LFS_NO_ASSERT -static bool lfs_mlist_isopen(struct lfs_mlist *head, - struct lfs_mlist *node) { - for (struct lfs_mlist **p = &head; *p; p = &(*p)->next) { - if (*p == (struct lfs_mlist*)node) { - return true; - } - } - - return false; -} -#endif - -static void lfs_mlist_remove(lfs_t *lfs, struct lfs_mlist *mlist) { - for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { - if (*p == mlist) { - *p = (*p)->next; - break; - } - } -} - -static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) { - mlist->next = lfs->mlist; - lfs->mlist = mlist; -} - -// some other filesystem operations -static uint32_t lfs_fs_disk_version(lfs_t *lfs) { - (void)lfs; -#ifdef LFS_MULTIVERSION - if (lfs->cfg->disk_version) { - return lfs->cfg->disk_version; - } else -#endif - { - return LFS_DISK_VERSION; - } -} - -static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) { - return 0xffff & (lfs_fs_disk_version(lfs) >> 16); - -} - -static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) { - return 0xffff & (lfs_fs_disk_version(lfs) >> 0); -} - - -/// Internal operations predeclared here /// -#ifndef LFS_READONLY -static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, - const struct lfs_mattr *attrs, int attrcount); -static int lfs_dir_compact(lfs_t *lfs, - lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, - lfs_mdir_t *source, uint16_t begin, uint16_t end); -static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, - const void *buffer, lfs_size_t size); -static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file, - const void *buffer, lfs_size_t size); -static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file); -static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file); -static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); - -static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss); -static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); -static void lfs_fs_prepmove(lfs_t *lfs, - uint16_t id, const lfs_block_t pair[2]); -static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2], - lfs_mdir_t *pdir); -static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], - lfs_mdir_t *parent); -static int lfs_fs_forceconsistency(lfs_t *lfs); -#endif - -static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock); - -#ifdef LFS_MIGRATE -static int lfs1_traverse(lfs_t *lfs, - int (*cb)(void*, lfs_block_t), void *data); -#endif - -static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir); - -static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, - void *buffer, lfs_size_t size); -static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file, - void *buffer, lfs_size_t size); -static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file); -static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file); - -static lfs_ssize_t lfs_fs_size_(lfs_t *lfs); -static int lfs_fs_traverse_(lfs_t *lfs, - int (*cb)(void *data, lfs_block_t block), void *data, - bool includeorphans); - -static int lfs_deinit(lfs_t *lfs); -static int lfs_unmount_(lfs_t *lfs); - - -/// Block allocator /// - -// allocations should call this when all allocated blocks are committed to -// the filesystem -// -// after a checkpoint, the block allocator may realloc any untracked blocks -static void lfs_alloc_ckpoint(lfs_t *lfs) { - lfs->lookahead.ckpoint = lfs->block_count; -} - -// drop the lookahead buffer, this is done during mounting and failed -// traversals in order to avoid invalid lookahead state -static void lfs_alloc_drop(lfs_t *lfs) { - lfs->lookahead.size = 0; - lfs->lookahead.next = 0; - lfs_alloc_ckpoint(lfs); -} - -#ifndef LFS_READONLY -static int lfs_alloc_lookahead(void *p, lfs_block_t block) { - lfs_t *lfs = (lfs_t*)p; - lfs_block_t off = ((block - lfs->lookahead.start) - + lfs->block_count) % lfs->block_count; - - if (off < lfs->lookahead.size) { - lfs->lookahead.buffer[off / 8] |= 1U << (off % 8); - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_alloc_scan(lfs_t *lfs) { - // move lookahead buffer to the first unused block - // - // note we limit the lookahead buffer to at most the amount of blocks - // checkpointed, this prevents the math in lfs_alloc from underflowing - lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) - % lfs->block_count; - lfs->lookahead.next = 0; - lfs->lookahead.size = lfs_min( - 8*lfs->cfg->lookahead_size, - lfs->lookahead.ckpoint); - - // find mask of free blocks from tree - memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); - int err = lfs_fs_traverse_(lfs, lfs_alloc_lookahead, lfs, true); - if (err) { - lfs_alloc_drop(lfs); - return err; - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { - while (true) { - // scan our lookahead buffer for free blocks - while (lfs->lookahead.next < lfs->lookahead.size) { - if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8] - & (1U << (lfs->lookahead.next % 8)))) { - // found a free block - *block = (lfs->lookahead.start + lfs->lookahead.next) - % lfs->block_count; - - // eagerly find next free block to maximize how many blocks - // lfs_alloc_ckpoint makes available for scanning - while (true) { - lfs->lookahead.next += 1; - lfs->lookahead.ckpoint -= 1; - - if (lfs->lookahead.next >= lfs->lookahead.size - || !(lfs->lookahead.buffer[lfs->lookahead.next / 8] - & (1U << (lfs->lookahead.next % 8)))) { - return 0; - } - } - } - - lfs->lookahead.next += 1; - lfs->lookahead.ckpoint -= 1; - } - - // In order to keep our block allocator from spinning forever when our - // filesystem is full, we mark points where there are no in-flight - // allocations with a checkpoint before starting a set of allocations. - // - // If we've looked at all blocks since the last checkpoint, we report - // the filesystem as out of storage. - // - if (lfs->lookahead.ckpoint <= 0) { - LFS_ERROR("No more free space 0x%"PRIx32, - (lfs->lookahead.start + lfs->lookahead.next) - % lfs->cfg->block_count); - return LFS_ERR_NOSPC; - } - - // No blocks in our lookahead buffer, we need to scan the filesystem for - // unused blocks in the next lookahead window. - int err = lfs_alloc_scan(lfs); - if(err) { - return err; - } - } -} -#endif - -/// Metadata pair and directory operations /// -static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, - lfs_tag_t gmask, lfs_tag_t gtag, - lfs_off_t goff, void *gbuffer, lfs_size_t gsize) { - lfs_off_t off = dir->off; - lfs_tag_t ntag = dir->etag; - lfs_stag_t gdiff = 0; - - // synthetic moves - if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) && - lfs_tag_id(gmask) != 0) { - if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(gtag)) { - return LFS_ERR_NOENT; - } else if (lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(gtag)) { - gdiff -= LFS_MKTAG(0, 1, 0); - } - } - - // iterate over dir block backwards (for faster lookups) - while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) { - off -= lfs_tag_dsize(ntag); - lfs_tag_t tag = ntag; - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, sizeof(ntag), - dir->pair[0], off, &ntag, sizeof(ntag)); - if (err) { - return err; - } - - ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff; - - if (lfs_tag_id(gmask) != 0 && - lfs_tag_type1(tag) == LFS_TYPE_SPLICE && - lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) { - if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) | - (LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) { - // found where we were created - return LFS_ERR_NOENT; - } - - // move around splices - gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0); - } - - if ((gmask & tag) == (gmask & (gtag - gdiff))) { - if (lfs_tag_isdelete(tag)) { - return LFS_ERR_NOENT; - } - - lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize); - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, diff, - dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff); - if (err) { - return err; - } - - memset((uint8_t*)gbuffer + diff, 0, gsize - diff); - - return tag + gdiff; - } - } - - return LFS_ERR_NOENT; -} - -static lfs_stag_t lfs_dir_get(lfs_t *lfs, const lfs_mdir_t *dir, - lfs_tag_t gmask, lfs_tag_t gtag, void *buffer) { - return lfs_dir_getslice(lfs, dir, - gmask, gtag, - 0, buffer, lfs_tag_size(gtag)); -} - -static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir, - const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, - lfs_tag_t gmask, lfs_tag_t gtag, - lfs_off_t off, void *buffer, lfs_size_t size) { - uint8_t *data = buffer; - if (off+size > lfs->cfg->block_size) { - return LFS_ERR_CORRUPT; - } - - while (size > 0) { - lfs_size_t diff = size; - - if (pcache && pcache->block == LFS_BLOCK_INLINE && - off < pcache->off + pcache->size) { - if (off >= pcache->off) { - // is already in pcache? - diff = lfs_min(diff, pcache->size - (off-pcache->off)); - memcpy(data, &pcache->buffer[off-pcache->off], diff); - - data += diff; - off += diff; - size -= diff; - continue; - } - - // pcache takes priority - diff = lfs_min(diff, pcache->off-off); - } - - if (rcache->block == LFS_BLOCK_INLINE && - off < rcache->off + rcache->size) { - if (off >= rcache->off) { - // is already in rcache? - diff = lfs_min(diff, rcache->size - (off-rcache->off)); - memcpy(data, &rcache->buffer[off-rcache->off], diff); - - data += diff; - off += diff; - size -= diff; - continue; - } - - // rcache takes priority - diff = lfs_min(diff, rcache->off-off); - } - - // load to cache, first condition can no longer fail - rcache->block = LFS_BLOCK_INLINE; - rcache->off = lfs_aligndown(off, lfs->cfg->read_size); - rcache->size = lfs_min(lfs_alignup(off+hint, lfs->cfg->read_size), - lfs->cfg->cache_size); - int err = lfs_dir_getslice(lfs, dir, gmask, gtag, - rcache->off, rcache->buffer, rcache->size); - if (err < 0) { - return err; - } - } - - return 0; -} - -#ifndef LFS_READONLY -static int lfs_dir_traverse_filter(void *p, - lfs_tag_t tag, const void *buffer) { - lfs_tag_t *filtertag = p; - (void)buffer; - - // which mask depends on unique bit in tag structure - uint32_t mask = (tag & LFS_MKTAG(0x100, 0, 0)) - ? LFS_MKTAG(0x7ff, 0x3ff, 0) - : LFS_MKTAG(0x700, 0x3ff, 0); - - // check for redundancy - if ((mask & tag) == (mask & *filtertag) || - lfs_tag_isdelete(*filtertag) || - (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( - LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | - (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { - *filtertag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0); - return true; - } - - // check if we need to adjust for created/deleted tags - if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE && - lfs_tag_id(tag) <= lfs_tag_id(*filtertag)) { - *filtertag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); - } - - return false; -} -#endif - -#ifndef LFS_READONLY -// maximum recursive depth of lfs_dir_traverse, the deepest call: -// -// traverse with commit -// '-> traverse with move -// '-> traverse with filter -// -#define LFS_DIR_TRAVERSE_DEPTH 3 - -struct lfs_dir_traverse { - const lfs_mdir_t *dir; - lfs_off_t off; - lfs_tag_t ptag; - const struct lfs_mattr *attrs; - int attrcount; - - lfs_tag_t tmask; - lfs_tag_t ttag; - uint16_t begin; - uint16_t end; - int16_t diff; - - int (*cb)(void *data, lfs_tag_t tag, const void *buffer); - void *data; - - lfs_tag_t tag; - const void *buffer; - struct lfs_diskoff disk; -}; - -static int lfs_dir_traverse(lfs_t *lfs, - const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, - const struct lfs_mattr *attrs, int attrcount, - lfs_tag_t tmask, lfs_tag_t ttag, - uint16_t begin, uint16_t end, int16_t diff, - int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { - // This function in inherently recursive, but bounded. To allow tool-based - // analysis without unnecessary code-cost we use an explicit stack - struct lfs_dir_traverse stack[LFS_DIR_TRAVERSE_DEPTH-1]; - unsigned sp = 0; - int res; - - // iterate over directory and attrs - lfs_tag_t tag; - const void *buffer; - struct lfs_diskoff disk = {0}; - while (true) { - { - if (off+lfs_tag_dsize(ptag) < dir->off) { - off += lfs_tag_dsize(ptag); - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, sizeof(tag), - dir->pair[0], off, &tag, sizeof(tag)); - if (err) { - return err; - } - - tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000; - disk.block = dir->pair[0]; - disk.off = off+sizeof(lfs_tag_t); - buffer = &disk; - ptag = tag; - } else if (attrcount > 0) { - tag = attrs[0].tag; - buffer = attrs[0].buffer; - attrs += 1; - attrcount -= 1; - } else { - // finished traversal, pop from stack? - res = 0; - break; - } - - // do we need to filter? - lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); - if ((mask & tmask & tag) != (mask & tmask & ttag)) { - continue; - } - - if (lfs_tag_id(tmask) != 0) { - LFS_ASSERT(sp < LFS_DIR_TRAVERSE_DEPTH); - // recurse, scan for duplicates, and update tag based on - // creates/deletes - stack[sp] = (struct lfs_dir_traverse){ - .dir = dir, - .off = off, - .ptag = ptag, - .attrs = attrs, - .attrcount = attrcount, - .tmask = tmask, - .ttag = ttag, - .begin = begin, - .end = end, - .diff = diff, - .cb = cb, - .data = data, - .tag = tag, - .buffer = buffer, - .disk = disk, - }; - sp += 1; - - tmask = 0; - ttag = 0; - begin = 0; - end = 0; - diff = 0; - cb = lfs_dir_traverse_filter; - data = &stack[sp-1].tag; - continue; - } - } - -popped: - // in filter range? - if (lfs_tag_id(tmask) != 0 && - !(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { - continue; - } - - // handle special cases for mcu-side operations - if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { - // do nothing - } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { - // Without this condition, lfs_dir_traverse can exhibit an - // extremely expensive O(n^3) of nested loops when renaming. - // This happens because lfs_dir_traverse tries to filter tags by - // the tags in the source directory, triggering a second - // lfs_dir_traverse with its own filter operation. - // - // traverse with commit - // '-> traverse with filter - // '-> traverse with move - // '-> traverse with filter - // - // However we don't actually care about filtering the second set of - // tags, since duplicate tags have no effect when filtering. - // - // This check skips this unnecessary recursive filtering explicitly, - // reducing this runtime from O(n^3) to O(n^2). - if (cb == lfs_dir_traverse_filter) { - continue; - } - - // recurse into move - stack[sp] = (struct lfs_dir_traverse){ - .dir = dir, - .off = off, - .ptag = ptag, - .attrs = attrs, - .attrcount = attrcount, - .tmask = tmask, - .ttag = ttag, - .begin = begin, - .end = end, - .diff = diff, - .cb = cb, - .data = data, - .tag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0), - }; - sp += 1; - - uint16_t fromid = lfs_tag_size(tag); - uint16_t toid = lfs_tag_id(tag); - dir = buffer; - off = 0; - ptag = 0xffffffff; - attrs = NULL; - attrcount = 0; - tmask = LFS_MKTAG(0x600, 0x3ff, 0); - ttag = LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0); - begin = fromid; - end = fromid+1; - diff = toid-fromid+diff; - } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { - for (unsigned i = 0; i < lfs_tag_size(tag); i++) { - const struct lfs_attr *a = buffer; - res = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, - lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); - if (res < 0) { - return res; - } - - if (res) { - break; - } - } - } else { - res = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); - if (res < 0) { - return res; - } - - if (res) { - break; - } - } - } - - if (sp > 0) { - // pop from the stack and return, fortunately all pops share - // a destination - dir = stack[sp-1].dir; - off = stack[sp-1].off; - ptag = stack[sp-1].ptag; - attrs = stack[sp-1].attrs; - attrcount = stack[sp-1].attrcount; - tmask = stack[sp-1].tmask; - ttag = stack[sp-1].ttag; - begin = stack[sp-1].begin; - end = stack[sp-1].end; - diff = stack[sp-1].diff; - cb = stack[sp-1].cb; - data = stack[sp-1].data; - tag = stack[sp-1].tag; - buffer = stack[sp-1].buffer; - disk = stack[sp-1].disk; - sp -= 1; - goto popped; - } else { - return res; - } -} -#endif - -static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, - lfs_mdir_t *dir, const lfs_block_t pair[2], - lfs_tag_t fmask, lfs_tag_t ftag, uint16_t *id, - int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { - // we can find tag very efficiently during a fetch, since we're already - // scanning the entire directory - lfs_stag_t besttag = -1; - - // if either block address is invalid we return LFS_ERR_CORRUPT here, - // otherwise later writes to the pair could fail - if (lfs->block_count - && (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count)) { - return LFS_ERR_CORRUPT; - } - - // find the block with the most recent revision - uint32_t revs[2] = {0, 0}; - int r = 0; - for (int i = 0; i < 2; i++) { - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, sizeof(revs[i]), - pair[i], 0, &revs[i], sizeof(revs[i])); - revs[i] = lfs_fromle32(revs[i]); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err != LFS_ERR_CORRUPT && - lfs_scmp(revs[i], revs[(i+1)%2]) > 0) { - r = i; - } - } - - dir->pair[0] = pair[(r+0)%2]; - dir->pair[1] = pair[(r+1)%2]; - dir->rev = revs[(r+0)%2]; - dir->off = 0; // nonzero = found some commits - - // now scan tags to fetch the actual dir and find possible match - for (int i = 0; i < 2; i++) { - lfs_off_t off = 0; - lfs_tag_t ptag = 0xffffffff; - - uint16_t tempcount = 0; - lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; - bool tempsplit = false; - lfs_stag_t tempbesttag = besttag; - - // assume not erased until proven otherwise - bool maybeerased = false; - bool hasfcrc = false; - struct lfs_fcrc fcrc; - - dir->rev = lfs_tole32(dir->rev); - uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); - dir->rev = lfs_fromle32(dir->rev); - - while (true) { - // extract next tag - lfs_tag_t tag; - off += lfs_tag_dsize(ptag); - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], off, &tag, sizeof(tag)); - if (err) { - if (err == LFS_ERR_CORRUPT) { - // can't continue? - break; - } - return err; - } - - crc = lfs_crc(crc, &tag, sizeof(tag)); - tag = lfs_frombe32(tag) ^ ptag; - - // next commit not yet programmed? - if (!lfs_tag_isvalid(tag)) { - // we only might be erased if the last tag was a crc - maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC); - break; - // out of range? - } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { - break; - } - - ptag = tag; - - if (lfs_tag_type2(tag) == LFS_TYPE_CCRC) { - // check the crc attr - uint32_t dcrc; - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); - if (err) { - if (err == LFS_ERR_CORRUPT) { - break; - } - return err; - } - dcrc = lfs_fromle32(dcrc); - - if (crc != dcrc) { - break; - } - - // reset the next bit if we need to - ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31; - - // toss our crc into the filesystem seed for - // pseudorandom numbers, note we use another crc here - // as a collection function because it is sufficiently - // random and convenient - lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(crc)); - - // update with what's found so far - besttag = tempbesttag; - dir->off = off + lfs_tag_dsize(tag); - dir->etag = ptag; - dir->count = tempcount; - dir->tail[0] = temptail[0]; - dir->tail[1] = temptail[1]; - dir->split = tempsplit; - - // reset crc, hasfcrc - crc = 0xffffffff; - continue; - } - - // crc the entry first, hopefully leaving it in the cache - err = lfs_bd_crc(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], off+sizeof(tag), - lfs_tag_dsize(tag)-sizeof(tag), &crc); - if (err) { - if (err == LFS_ERR_CORRUPT) { - break; - } - return err; - } - - // directory modification tags? - if (lfs_tag_type1(tag) == LFS_TYPE_NAME) { - // increase count of files if necessary - if (lfs_tag_id(tag) >= tempcount) { - tempcount = lfs_tag_id(tag) + 1; - } - } else if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE) { - tempcount += lfs_tag_splice(tag); - - if (tag == (LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | - (LFS_MKTAG(0, 0x3ff, 0) & tempbesttag))) { - tempbesttag |= 0x80000000; - } else if (tempbesttag != -1 && - lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { - tempbesttag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); - } - } else if (lfs_tag_type1(tag) == LFS_TYPE_TAIL) { - tempsplit = (lfs_tag_chunk(tag) & 1); - - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], off+sizeof(tag), &temptail, 8); - if (err) { - if (err == LFS_ERR_CORRUPT) { - break; - } - return err; - } - lfs_pair_fromle32(temptail); - } else if (lfs_tag_type3(tag) == LFS_TYPE_FCRC) { - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], off+sizeof(tag), - &fcrc, sizeof(fcrc)); - if (err) { - if (err == LFS_ERR_CORRUPT) { - break; - } - } - - lfs_fcrc_fromle32(&fcrc); - hasfcrc = true; - } - - // found a match for our fetcher? - if ((fmask & tag) == (fmask & ftag)) { - int res = cb(data, tag, &(struct lfs_diskoff){ - dir->pair[0], off+sizeof(tag)}); - if (res < 0) { - if (res == LFS_ERR_CORRUPT) { - break; - } - return res; - } - - if (res == LFS_CMP_EQ) { - // found a match - tempbesttag = tag; - } else if ((LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == - (LFS_MKTAG(0x7ff, 0x3ff, 0) & tempbesttag)) { - // found an identical tag, but contents didn't match - // this must mean that our besttag has been overwritten - tempbesttag = -1; - } else if (res == LFS_CMP_GT && - lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { - // found a greater match, keep track to keep things sorted - tempbesttag = tag | 0x80000000; - } - } - } - - // found no valid commits? - if (dir->off == 0) { - // try the other block? - lfs_pair_swap(dir->pair); - dir->rev = revs[(r+1)%2]; - continue; - } - - // did we end on a valid commit? we may have an erased block - dir->erased = false; - if (maybeerased && dir->off % lfs->cfg->prog_size == 0) { - #ifdef LFS_MULTIVERSION - // note versions < lfs2.1 did not have fcrc tags, if - // we're < lfs2.1 treat missing fcrc as erased data - // - // we don't strictly need to do this, but otherwise writing - // to lfs2.0 disks becomes very inefficient - if (lfs_fs_disk_version(lfs) < 0x00020001) { - dir->erased = true; - - } else - #endif - if (hasfcrc) { - // check for an fcrc matching the next prog's erased state, if - // this failed most likely a previous prog was interrupted, we - // need a new erase - uint32_t fcrc_ = 0xffffffff; - int err = lfs_bd_crc(lfs, - NULL, &lfs->rcache, lfs->cfg->block_size, - dir->pair[0], dir->off, fcrc.size, &fcrc_); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - // found beginning of erased part? - dir->erased = (fcrc_ == fcrc.crc); - } - } - - // synthetic move - if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) { - if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) { - besttag |= 0x80000000; - } else if (besttag != -1 && - lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) { - besttag -= LFS_MKTAG(0, 1, 0); - } - } - - // found tag? or found best id? - if (id) { - *id = lfs_min(lfs_tag_id(besttag), dir->count); - } - - if (lfs_tag_isvalid(besttag)) { - return besttag; - } else if (lfs_tag_id(besttag) < dir->count) { - return LFS_ERR_NOENT; - } else { - return 0; - } - } - - LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", - dir->pair[0], dir->pair[1]); - return LFS_ERR_CORRUPT; -} - -static int lfs_dir_fetch(lfs_t *lfs, - lfs_mdir_t *dir, const lfs_block_t pair[2]) { - // note, mask=-1, tag=-1 can never match a tag since this - // pattern has the invalid bit set - return (int)lfs_dir_fetchmatch(lfs, dir, pair, - (lfs_tag_t)-1, (lfs_tag_t)-1, NULL, NULL, NULL); -} - -static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir, - lfs_gstate_t *gstate) { - lfs_gstate_t temp; - lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0), - LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); - if (res < 0 && res != LFS_ERR_NOENT) { - return res; - } - - if (res != LFS_ERR_NOENT) { - // xor together to find resulting gstate - lfs_gstate_fromle32(&temp); - lfs_gstate_xor(gstate, &temp); - } - - return 0; -} - -static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir, - uint16_t id, struct lfs_info *info) { - if (id == 0x3ff) { - // special case for root - strcpy(info->name, "/"); - info->type = LFS_TYPE_DIR; - return 0; - } - - lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name); - if (tag < 0) { - return (int)tag; - } - - info->type = lfs_tag_type3(tag); - - struct lfs_ctz ctz; - tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); - if (tag < 0) { - return (int)tag; - } - lfs_ctz_fromle32(&ctz); - - if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { - info->size = ctz.size; - } else if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { - info->size = lfs_tag_size(tag); - } - - return 0; -} - -struct lfs_dir_find_match { - lfs_t *lfs; - const void *name; - lfs_size_t size; -}; - -static int lfs_dir_find_match(void *data, - lfs_tag_t tag, const void *buffer) { - struct lfs_dir_find_match *name = data; - lfs_t *lfs = name->lfs; - const struct lfs_diskoff *disk = buffer; - - // compare with disk - lfs_size_t diff = lfs_min(name->size, lfs_tag_size(tag)); - int res = lfs_bd_cmp(lfs, - NULL, &lfs->rcache, diff, - disk->block, disk->off, name->name, diff); - if (res != LFS_CMP_EQ) { - return res; - } - - // only equal if our size is still the same - if (name->size != lfs_tag_size(tag)) { - return (name->size < lfs_tag_size(tag)) ? LFS_CMP_LT : LFS_CMP_GT; - } - - // found a match! - return LFS_CMP_EQ; -} - -static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, - const char **path, uint16_t *id) { - // we reduce path to a single name if we can find it - const char *name = *path; - if (id) { - *id = 0x3ff; - } - - // default to root dir - lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0); - dir->tail[0] = lfs->root[0]; - dir->tail[1] = lfs->root[1]; - - while (true) { -nextname: - // skip slashes - name += strspn(name, "/"); - lfs_size_t namelen = strcspn(name, "/"); - - // skip '.' and root '..' - if ((namelen == 1 && memcmp(name, ".", 1) == 0) || - (namelen == 2 && memcmp(name, "..", 2) == 0)) { - name += namelen; - goto nextname; - } - - // skip if matched by '..' in name - const char *suffix = name + namelen; - lfs_size_t sufflen; - int depth = 1; - while (true) { - suffix += strspn(suffix, "/"); - sufflen = strcspn(suffix, "/"); - if (sufflen == 0) { - break; - } - - if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { - depth -= 1; - if (depth == 0) { - name = suffix + sufflen; - goto nextname; - } - } else { - depth += 1; - } - - suffix += sufflen; - } - - // found path - if (name[0] == '\0') { - return tag; - } - - // update what we've found so far - *path = name; - - // only continue if we hit a directory - if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { - return LFS_ERR_NOTDIR; - } - - // grab the entry data - if (lfs_tag_id(tag) != 0x3ff) { - lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), dir->tail); - if (res < 0) { - return res; - } - lfs_pair_fromle32(dir->tail); - } - - // find entry matching name - while (true) { - tag = lfs_dir_fetchmatch(lfs, dir, dir->tail, - LFS_MKTAG(0x780, 0, 0), - LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), - // are we last name? - (strchr(name, '/') == NULL) ? id : NULL, - lfs_dir_find_match, &(struct lfs_dir_find_match){ - lfs, name, namelen}); - if (tag < 0) { - return tag; - } - - if (tag) { - break; - } - - if (!dir->split) { - return LFS_ERR_NOENT; - } - } - - // to next name - name += namelen; - } -} - -// commit logic -struct lfs_commit { - lfs_block_t block; - lfs_off_t off; - lfs_tag_t ptag; - uint32_t crc; - - lfs_off_t begin; - lfs_off_t end; -}; - -#ifndef LFS_READONLY -static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit, - const void *buffer, lfs_size_t size) { - int err = lfs_bd_prog(lfs, - &lfs->pcache, &lfs->rcache, false, - commit->block, commit->off , - (const uint8_t*)buffer, size); - if (err) { - return err; - } - - commit->crc = lfs_crc(commit->crc, buffer, size); - commit->off += size; - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, - lfs_tag_t tag, const void *buffer) { - // check if we fit - lfs_size_t dsize = lfs_tag_dsize(tag); - if (commit->off + dsize > commit->end) { - return LFS_ERR_NOSPC; - } - - // write out tag - lfs_tag_t ntag = lfs_tobe32((tag & 0x7fffffff) ^ commit->ptag); - int err = lfs_dir_commitprog(lfs, commit, &ntag, sizeof(ntag)); - if (err) { - return err; - } - - if (!(tag & 0x80000000)) { - // from memory - err = lfs_dir_commitprog(lfs, commit, buffer, dsize-sizeof(tag)); - if (err) { - return err; - } - } else { - // from disk - const struct lfs_diskoff *disk = buffer; - for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) { - // rely on caching to make this efficient - uint8_t dat; - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, dsize-sizeof(tag)-i, - disk->block, disk->off+i, &dat, 1); - if (err) { - return err; - } - - err = lfs_dir_commitprog(lfs, commit, &dat, 1); - if (err) { - return err; - } - } - } - - commit->ptag = tag & 0x7fffffff; - return 0; -} -#endif - -#ifndef LFS_READONLY - -static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { - // align to program units - // - // this gets a bit complex as we have two types of crcs: - // - 5-word crc with fcrc to check following prog (middle of block) - // - 2-word crc with no following prog (end of block) - const lfs_off_t end = lfs_alignup( - lfs_min(commit->off + 5*sizeof(uint32_t), lfs->cfg->block_size), - lfs->cfg->prog_size); - - lfs_off_t off1 = 0; - uint32_t crc1 = 0; - - // create crc tags to fill up remainder of commit, note that - // padding is not crced, which lets fetches skip padding but - // makes committing a bit more complicated - while (commit->off < end) { - lfs_off_t noff = ( - lfs_min(end - (commit->off+sizeof(lfs_tag_t)), 0x3fe) - + (commit->off+sizeof(lfs_tag_t))); - // too large for crc tag? need padding commits - if (noff < end) { - noff = lfs_min(noff, end - 5*sizeof(uint32_t)); - } - - // space for fcrc? - uint8_t eperturb = (uint8_t)-1; - if (noff >= end && noff <= lfs->cfg->block_size - lfs->cfg->prog_size) { - // first read the leading byte, this always contains a bit - // we can perturb to avoid writes that don't change the fcrc - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, lfs->cfg->prog_size, - commit->block, noff, &eperturb, 1); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - #ifdef LFS_MULTIVERSION - // unfortunately fcrcs break mdir fetching < lfs2.1, so only write - // these if we're a >= lfs2.1 filesystem - if (lfs_fs_disk_version(lfs) <= 0x00020000) { - // don't write fcrc - } else - #endif - { - // find the expected fcrc, don't bother avoiding a reread - // of the eperturb, it should still be in our cache - struct lfs_fcrc fcrc = { - .size = lfs->cfg->prog_size, - .crc = 0xffffffff - }; - err = lfs_bd_crc(lfs, - NULL, &lfs->rcache, lfs->cfg->prog_size, - commit->block, noff, fcrc.size, &fcrc.crc); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - lfs_fcrc_tole32(&fcrc); - err = lfs_dir_commitattr(lfs, commit, - LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)), - &fcrc); - if (err) { - return err; - } - } - } - - // build commit crc - struct { - lfs_tag_t tag; - uint32_t crc; - } ccrc; - lfs_tag_t ntag = LFS_MKTAG( - LFS_TYPE_CCRC + (((uint8_t)~eperturb) >> 7), 0x3ff, - noff - (commit->off+sizeof(lfs_tag_t))); - ccrc.tag = lfs_tobe32(ntag ^ commit->ptag); - commit->crc = lfs_crc(commit->crc, &ccrc.tag, sizeof(lfs_tag_t)); - ccrc.crc = lfs_tole32(commit->crc); - - int err = lfs_bd_prog(lfs, - &lfs->pcache, &lfs->rcache, false, - commit->block, commit->off, &ccrc, sizeof(ccrc)); - if (err) { - return err; - } - - // keep track of non-padding checksum to verify - if (off1 == 0) { - off1 = commit->off + sizeof(lfs_tag_t); - crc1 = commit->crc; - } - - commit->off = noff; - // perturb valid bit? - commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24); - // reset crc for next commit - commit->crc = 0xffffffff; - - // manually flush here since we don't prog the padding, this confuses - // the caching layer - if (noff >= end || noff >= lfs->pcache.off + lfs->cfg->cache_size) { - // flush buffers - int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); - if (err) { - return err; - } - } - } - - // successful commit, check checksums to make sure - // - // note that we don't need to check padding commits, worst - // case if they are corrupted we would have had to compact anyways - lfs_off_t off = commit->begin; - uint32_t crc = 0xffffffff; - int err = lfs_bd_crc(lfs, - NULL, &lfs->rcache, off1+sizeof(uint32_t), - commit->block, off, off1-off, &crc); - if (err) { - return err; - } - - // check non-padding commits against known crc - if (crc != crc1) { - return LFS_ERR_CORRUPT; - } - - // make sure to check crc in case we happen to pick - // up an unrelated crc (frozen block?) - err = lfs_bd_crc(lfs, - NULL, &lfs->rcache, sizeof(uint32_t), - commit->block, off1, sizeof(uint32_t), &crc); - if (err) { - return err; - } - - if (crc != 0) { - return LFS_ERR_CORRUPT; - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { - // allocate pair of dir blocks (backwards, so we write block 1 first) - for (int i = 0; i < 2; i++) { - int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]); - if (err) { - return err; - } - } - - // zero for reproducibility in case initial block is unreadable - dir->rev = 0; - - // rather than clobbering one of the blocks we just pretend - // the revision may be valid - int err = lfs_bd_read(lfs, - NULL, &lfs->rcache, sizeof(dir->rev), - dir->pair[0], 0, &dir->rev, sizeof(dir->rev)); - dir->rev = lfs_fromle32(dir->rev); - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - // to make sure we don't immediately evict, align the new revision count - // to our block_cycles modulus, see lfs_dir_compact for why our modulus - // is tweaked this way - if (lfs->cfg->block_cycles > 0) { - dir->rev = lfs_alignup(dir->rev, ((lfs->cfg->block_cycles+1)|1)); - } - - // set defaults - dir->off = sizeof(dir->rev); - dir->etag = 0xffffffff; - dir->count = 0; - dir->tail[0] = LFS_BLOCK_NULL; - dir->tail[1] = LFS_BLOCK_NULL; - dir->erased = false; - dir->split = false; - - // don't write out yet, let caller take care of that - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) { - // steal state - int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta); - if (err) { - return err; - } - - // steal tail - lfs_pair_tole32(tail->tail); - err = lfs_dir_commit(lfs, dir, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail})); - lfs_pair_fromle32(tail->tail); - if (err) { - return err; - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_split(lfs_t *lfs, - lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, - lfs_mdir_t *source, uint16_t split, uint16_t end) { - // create tail metadata pair - lfs_mdir_t tail; - int err = lfs_dir_alloc(lfs, &tail); - if (err) { - return err; - } - - tail.split = dir->split; - tail.tail[0] = dir->tail[0]; - tail.tail[1] = dir->tail[1]; - - // note we don't care about LFS_OK_RELOCATED - int res = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end); - if (res < 0) { - return res; - } - - dir->tail[0] = tail.pair[0]; - dir->tail[1] = tail.pair[1]; - dir->split = true; - - // update root if needed - if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && split == 0) { - lfs->root[0] = tail.pair[0]; - lfs->root[1] = tail.pair[1]; - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) { - lfs_size_t *size = p; - (void)buffer; - - *size += lfs_tag_dsize(tag); - return 0; -} -#endif - -#ifndef LFS_READONLY -struct lfs_dir_commit_commit { - lfs_t *lfs; - struct lfs_commit *commit; -}; -#endif - -#ifndef LFS_READONLY -static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) { - struct lfs_dir_commit_commit *commit = p; - return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer); -} -#endif - -#ifndef LFS_READONLY -static bool lfs_dir_needsrelocation(lfs_t *lfs, lfs_mdir_t *dir) { - // If our revision count == n * block_cycles, we should force a relocation, - // this is how littlefs wear-levels at the metadata-pair level. Note that we - // actually use (block_cycles+1)|1, this is to avoid two corner cases: - // 1. block_cycles = 1, which would prevent relocations from terminating - // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate - // one metadata block in the pair, effectively making this useless - return (lfs->cfg->block_cycles > 0 - && ((dir->rev + 1) % ((lfs->cfg->block_cycles+1)|1) == 0)); -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_compact(lfs_t *lfs, - lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, - lfs_mdir_t *source, uint16_t begin, uint16_t end) { - // save some state in case block is bad - bool relocated = false; - bool tired = lfs_dir_needsrelocation(lfs, dir); - - // increment revision count - dir->rev += 1; - - // do not proactively relocate blocks during migrations, this - // can cause a number of failure states such: clobbering the - // v1 superblock if we relocate root, and invalidating directory - // pointers if we relocate the head of a directory. On top of - // this, relocations increase the overall complexity of - // lfs_migration, which is already a delicate operation. -#ifdef LFS_MIGRATE - if (lfs->lfs1) { - tired = false; - } -#endif - - if (tired && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) != 0) { - // we're writing too much, time to relocate - goto relocate; - } - - // begin loop to commit compaction to blocks until a compact sticks - while (true) { - { - // setup commit state - struct lfs_commit commit = { - .block = dir->pair[1], - .off = 0, - .ptag = 0xffffffff, - .crc = 0xffffffff, - - .begin = 0, - .end = (lfs->cfg->metadata_max ? - lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, - }; - - // erase block to write to - int err = lfs_bd_erase(lfs, dir->pair[1]); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - // write out header - dir->rev = lfs_tole32(dir->rev); - err = lfs_dir_commitprog(lfs, &commit, - &dir->rev, sizeof(dir->rev)); - dir->rev = lfs_fromle32(dir->rev); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - // traverse the directory, this time writing out all unique tags - err = lfs_dir_traverse(lfs, - source, 0, 0xffffffff, attrs, attrcount, - LFS_MKTAG(0x400, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_NAME, 0, 0), - begin, end, -begin, - lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ - lfs, &commit}); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - // commit tail, which may be new after last size check - if (!lfs_pair_isnull(dir->tail)) { - lfs_pair_tole32(dir->tail); - err = lfs_dir_commitattr(lfs, &commit, - LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), - dir->tail); - lfs_pair_fromle32(dir->tail); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - } - - // bring over gstate? - lfs_gstate_t delta = {0}; - if (!relocated) { - lfs_gstate_xor(&delta, &lfs->gdisk); - lfs_gstate_xor(&delta, &lfs->gstate); - } - lfs_gstate_xor(&delta, &lfs->gdelta); - delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); - - err = lfs_dir_getgstate(lfs, dir, &delta); - if (err) { - return err; - } - - if (!lfs_gstate_iszero(&delta)) { - lfs_gstate_tole32(&delta); - err = lfs_dir_commitattr(lfs, &commit, - LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, - sizeof(delta)), &delta); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - } - - // complete commit with crc - err = lfs_dir_commitcrc(lfs, &commit); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - // successful compaction, swap dir pair to indicate most recent - LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); - lfs_pair_swap(dir->pair); - dir->count = end - begin; - dir->off = commit.off; - dir->etag = commit.ptag; - // update gstate - lfs->gdelta = (lfs_gstate_t){0}; - if (!relocated) { - lfs->gdisk = lfs->gstate; - } - } - break; - -relocate: - // commit was corrupted, drop caches and prepare to relocate block - relocated = true; - lfs_cache_drop(lfs, &lfs->pcache); - if (!tired) { - LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); - } - - // can't relocate superblock, filesystem is now frozen - if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { - LFS_WARN("Superblock 0x%"PRIx32" has become unwritable", - dir->pair[1]); - return LFS_ERR_NOSPC; - } - - // relocate half of pair - int err = lfs_alloc(lfs, &dir->pair[1]); - if (err && (err != LFS_ERR_NOSPC || !tired)) { - return err; - } - - tired = false; - continue; - } - - return relocated ? LFS_OK_RELOCATED : 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir, - const struct lfs_mattr *attrs, int attrcount, - lfs_mdir_t *source, uint16_t begin, uint16_t end) { - while (true) { - // find size of first split, we do this by halving the split until - // the metadata is guaranteed to fit - // - // Note that this isn't a true binary search, we never increase the - // split size. This may result in poorly distributed metadata but isn't - // worth the extra code size or performance hit to fix. - lfs_size_t split = begin; - while (end - split > 1) { - lfs_size_t size = 0; - int err = lfs_dir_traverse(lfs, - source, 0, 0xffffffff, attrs, attrcount, - LFS_MKTAG(0x400, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_NAME, 0, 0), - split, end, -split, - lfs_dir_commit_size, &size); - if (err) { - return err; - } - - // space is complicated, we need room for: - // - // - tail: 4+2*4 = 12 bytes - // - gstate: 4+3*4 = 16 bytes - // - move delete: 4 = 4 bytes - // - crc: 4+4 = 8 bytes - // total = 40 bytes - // - // And we cap at half a block to avoid degenerate cases with - // nearly-full metadata blocks. - // - if (end - split < 0xff - && size <= lfs_min( - lfs->cfg->block_size - 40, - lfs_alignup( - (lfs->cfg->metadata_max - ? lfs->cfg->metadata_max - : lfs->cfg->block_size)/2, - lfs->cfg->prog_size))) { - break; - } - - split = split + ((end - split) / 2); - } - - if (split == begin) { - // no split needed - break; - } - - // split into two metadata pairs and continue - int err = lfs_dir_split(lfs, dir, attrs, attrcount, - source, split, end); - if (err && err != LFS_ERR_NOSPC) { - return err; - } - - if (err) { - // we can't allocate a new block, try to compact with degraded - // performance - LFS_WARN("Unable to split {0x%"PRIx32", 0x%"PRIx32"}", - dir->pair[0], dir->pair[1]); - break; - } else { - end = split; - } - } - - if (lfs_dir_needsrelocation(lfs, dir) - && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { - // oh no! we're writing too much to the superblock, - // should we expand? - lfs_ssize_t size = lfs_fs_size_(lfs); - if (size < 0) { - return size; - } - - // littlefs cannot reclaim expanded superblocks, so expand cautiously - // - // if our filesystem is more than ~88% full, don't expand, this is - // somewhat arbitrary - if (lfs->block_count - size > lfs->block_count/8) { - LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); - int err = lfs_dir_split(lfs, dir, attrs, attrcount, - source, begin, end); - if (err && err != LFS_ERR_NOSPC) { - return err; - } - - if (err) { - // welp, we tried, if we ran out of space there's not much - // we can do, we'll error later if we've become frozen - LFS_WARN("Unable to expand superblock"); - } else { - end = begin; - } - } - } - - return lfs_dir_compact(lfs, dir, attrs, attrcount, source, begin, end); -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir, - const lfs_block_t pair[2], - const struct lfs_mattr *attrs, int attrcount, - lfs_mdir_t *pdir) { - int state = 0; - - // calculate changes to the directory - bool hasdelete = false; - for (int i = 0; i < attrcount; i++) { - if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { - dir->count += 1; - } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) { - LFS_ASSERT(dir->count > 0); - dir->count -= 1; - hasdelete = true; - } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) { - dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0]; - dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1]; - dir->split = (lfs_tag_chunk(attrs[i].tag) & 1); - lfs_pair_fromle32(dir->tail); - } - } - - // should we actually drop the directory block? - if (hasdelete && dir->count == 0) { - LFS_ASSERT(pdir); - int err = lfs_fs_pred(lfs, dir->pair, pdir); - if (err && err != LFS_ERR_NOENT) { - return err; - } - - if (err != LFS_ERR_NOENT && pdir->split) { - state = LFS_OK_DROPPED; - goto fixmlist; - } - } - - if (dir->erased) { - // try to commit - struct lfs_commit commit = { - .block = dir->pair[0], - .off = dir->off, - .ptag = dir->etag, - .crc = 0xffffffff, - - .begin = dir->off, - .end = (lfs->cfg->metadata_max ? - lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, - }; - - // traverse attrs that need to be written out - lfs_pair_tole32(dir->tail); - int err = lfs_dir_traverse(lfs, - dir, dir->off, dir->etag, attrs, attrcount, - 0, 0, 0, 0, 0, - lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ - lfs, &commit}); - lfs_pair_fromle32(dir->tail); - if (err) { - if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { - goto compact; - } - return err; - } - - // commit any global diffs if we have any - lfs_gstate_t delta = {0}; - lfs_gstate_xor(&delta, &lfs->gstate); - lfs_gstate_xor(&delta, &lfs->gdisk); - lfs_gstate_xor(&delta, &lfs->gdelta); - delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); - if (!lfs_gstate_iszero(&delta)) { - err = lfs_dir_getgstate(lfs, dir, &delta); - if (err) { - return err; - } - - lfs_gstate_tole32(&delta); - err = lfs_dir_commitattr(lfs, &commit, - LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, - sizeof(delta)), &delta); - if (err) { - if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { - goto compact; - } - return err; - } - } - - // finalize commit with the crc - err = lfs_dir_commitcrc(lfs, &commit); - if (err) { - if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { - goto compact; - } - return err; - } - - // successful commit, update dir - LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); - dir->off = commit.off; - dir->etag = commit.ptag; - // and update gstate - lfs->gdisk = lfs->gstate; - lfs->gdelta = (lfs_gstate_t){0}; - - goto fixmlist; - } - -compact: - // fall back to compaction - lfs_cache_drop(lfs, &lfs->pcache); - - state = lfs_dir_splittingcompact(lfs, dir, attrs, attrcount, - dir, 0, dir->count); - if (state < 0) { - return state; - } - - goto fixmlist; - -fixmlist:; - // this complicated bit of logic is for fixing up any active - // metadata-pairs that we may have affected - // - // note we have to make two passes since the mdir passed to - // lfs_dir_commit could also be in this list, and even then - // we need to copy the pair so they don't get clobbered if we refetch - // our mdir. - lfs_block_t oldpair[2] = {pair[0], pair[1]}; - for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { - if (lfs_pair_cmp(d->m.pair, oldpair) == 0) { - d->m = *dir; - if (d->m.pair != pair) { - for (int i = 0; i < attrcount; i++) { - if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && - d->id == lfs_tag_id(attrs[i].tag)) { - d->m.pair[0] = LFS_BLOCK_NULL; - d->m.pair[1] = LFS_BLOCK_NULL; - } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && - d->id > lfs_tag_id(attrs[i].tag)) { - d->id -= 1; - if (d->type == LFS_TYPE_DIR) { - ((lfs_dir_t*)d)->pos -= 1; - } - } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE && - d->id >= lfs_tag_id(attrs[i].tag)) { - d->id += 1; - if (d->type == LFS_TYPE_DIR) { - ((lfs_dir_t*)d)->pos += 1; - } - } - } - } - - while (d->id >= d->m.count && d->m.split) { - // we split and id is on tail now - d->id -= d->m.count; - int err = lfs_dir_fetch(lfs, &d->m, d->m.tail); - if (err) { - return err; - } - } - } - } - - return state; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_orphaningcommit(lfs_t *lfs, lfs_mdir_t *dir, - const struct lfs_mattr *attrs, int attrcount) { - // check for any inline files that aren't RAM backed and - // forcefully evict them, needed for filesystem consistency - for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { - if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && - f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) && - f->ctz.size > lfs->cfg->cache_size) { - int err = lfs_file_outline(lfs, f); - if (err) { - return err; - } - - err = lfs_file_flush(lfs, f); - if (err) { - return err; - } - } - } - - lfs_block_t lpair[2] = {dir->pair[0], dir->pair[1]}; - lfs_mdir_t ldir = *dir; - lfs_mdir_t pdir; - int state = lfs_dir_relocatingcommit(lfs, &ldir, dir->pair, - attrs, attrcount, &pdir); - if (state < 0) { - return state; - } - - // update if we're not in mlist, note we may have already been - // updated if we are in mlist - if (lfs_pair_cmp(dir->pair, lpair) == 0) { - *dir = ldir; - } - - // commit was successful, but may require other changes in the - // filesystem, these would normally be tail recursive, but we have - // flattened them here avoid unbounded stack usage - - // need to drop? - if (state == LFS_OK_DROPPED) { - // steal state - int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); - if (err) { - return err; - } - - // steal tail, note that this can't create a recursive drop - lpair[0] = pdir.pair[0]; - lpair[1] = pdir.pair[1]; - lfs_pair_tole32(dir->tail); - state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), - dir->tail}), - NULL); - lfs_pair_fromle32(dir->tail); - if (state < 0) { - return state; - } - - ldir = pdir; - } - - // need to relocate? - bool orphans = false; - while (state == LFS_OK_RELOCATED) { - LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " - "-> {0x%"PRIx32", 0x%"PRIx32"}", - lpair[0], lpair[1], ldir.pair[0], ldir.pair[1]); - state = 0; - - // update internal root - if (lfs_pair_cmp(lpair, lfs->root) == 0) { - lfs->root[0] = ldir.pair[0]; - lfs->root[1] = ldir.pair[1]; - } - - // update internally tracked dirs - for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { - if (lfs_pair_cmp(lpair, d->m.pair) == 0) { - d->m.pair[0] = ldir.pair[0]; - d->m.pair[1] = ldir.pair[1]; - } - - if (d->type == LFS_TYPE_DIR && - lfs_pair_cmp(lpair, ((lfs_dir_t*)d)->head) == 0) { - ((lfs_dir_t*)d)->head[0] = ldir.pair[0]; - ((lfs_dir_t*)d)->head[1] = ldir.pair[1]; - } - } - - // find parent - lfs_stag_t tag = lfs_fs_parent(lfs, lpair, &pdir); - if (tag < 0 && tag != LFS_ERR_NOENT) { - return tag; - } - - bool hasparent = (tag != LFS_ERR_NOENT); - if (tag != LFS_ERR_NOENT) { - // note that if we have a parent, we must have a pred, so this will - // always create an orphan - int err = lfs_fs_preporphans(lfs, +1); - if (err) { - return err; - } - - // fix pending move in this pair? this looks like an optimization but - // is in fact _required_ since relocating may outdate the move. - uint16_t moveid = 0x3ff; - if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { - moveid = lfs_tag_id(lfs->gstate.tag); - LFS_DEBUG("Fixing move while relocating " - "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", - pdir.pair[0], pdir.pair[1], moveid); - lfs_fs_prepmove(lfs, 0x3ff, NULL); - if (moveid < lfs_tag_id(tag)) { - tag -= LFS_MKTAG(0, 1, 0); - } - } - - lfs_block_t ppair[2] = {pdir.pair[0], pdir.pair[1]}; - lfs_pair_tole32(ldir.pair); - state = lfs_dir_relocatingcommit(lfs, &pdir, ppair, LFS_MKATTRS( - {LFS_MKTAG_IF(moveid != 0x3ff, - LFS_TYPE_DELETE, moveid, 0), NULL}, - {tag, ldir.pair}), - NULL); - lfs_pair_fromle32(ldir.pair); - if (state < 0) { - return state; - } - - if (state == LFS_OK_RELOCATED) { - lpair[0] = ppair[0]; - lpair[1] = ppair[1]; - ldir = pdir; - orphans = true; - continue; - } - } - - // find pred - int err = lfs_fs_pred(lfs, lpair, &pdir); - if (err && err != LFS_ERR_NOENT) { - return err; - } - LFS_ASSERT(!(hasparent && err == LFS_ERR_NOENT)); - - // if we can't find dir, it must be new - if (err != LFS_ERR_NOENT) { - if (lfs_gstate_hasorphans(&lfs->gstate)) { - // next step, clean up orphans - err = lfs_fs_preporphans(lfs, -hasparent); - if (err) { - return err; - } - } - - // fix pending move in this pair? this looks like an optimization - // but is in fact _required_ since relocating may outdate the move. - uint16_t moveid = 0x3ff; - if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { - moveid = lfs_tag_id(lfs->gstate.tag); - LFS_DEBUG("Fixing move while relocating " - "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", - pdir.pair[0], pdir.pair[1], moveid); - lfs_fs_prepmove(lfs, 0x3ff, NULL); - } - - // replace bad pair, either we clean up desync, or no desync occured - lpair[0] = pdir.pair[0]; - lpair[1] = pdir.pair[1]; - lfs_pair_tole32(ldir.pair); - state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( - {LFS_MKTAG_IF(moveid != 0x3ff, - LFS_TYPE_DELETE, moveid, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_TAIL + pdir.split, 0x3ff, 8), - ldir.pair}), - NULL); - lfs_pair_fromle32(ldir.pair); - if (state < 0) { - return state; - } - - ldir = pdir; - } - } - - return orphans ? LFS_OK_ORPHANED : 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, - const struct lfs_mattr *attrs, int attrcount) { - int orphans = lfs_dir_orphaningcommit(lfs, dir, attrs, attrcount); - if (orphans < 0) { - return orphans; - } - - if (orphans) { - // make sure we've removed all orphans, this is a noop if there - // are none, but if we had nested blocks failures we may have - // created some - int err = lfs_fs_deorphan(lfs, false); - if (err) { - return err; - } - } - - return 0; -} -#endif - - -/// Top level directory operations /// -#ifndef LFS_READONLY -static int lfs_mkdir_(lfs_t *lfs, const char *path) { - // deorphan if we haven't yet, needed at most once after poweron - int err = lfs_fs_forceconsistency(lfs); - if (err) { - return err; - } - - struct lfs_mlist cwd; - cwd.next = lfs->mlist; - uint16_t id; - err = lfs_dir_find(lfs, &cwd.m, &path, &id); - if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { - return (err < 0) ? err : LFS_ERR_EXIST; - } - - // check that name fits - lfs_size_t nlen = strlen(path); - if (nlen > lfs->name_max) { - return LFS_ERR_NAMETOOLONG; - } - - // build up new directory - lfs_alloc_ckpoint(lfs); - lfs_mdir_t dir; - err = lfs_dir_alloc(lfs, &dir); - if (err) { - return err; - } - - // find end of list - lfs_mdir_t pred = cwd.m; - while (pred.split) { - err = lfs_dir_fetch(lfs, &pred, pred.tail); - if (err) { - return err; - } - } - - // setup dir - lfs_pair_tole32(pred.tail); - err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); - lfs_pair_fromle32(pred.tail); - if (err) { - return err; - } - - // current block not end of list? - if (cwd.m.split) { - // update tails, this creates a desync - err = lfs_fs_preporphans(lfs, +1); - if (err) { - return err; - } - - // it's possible our predecessor has to be relocated, and if - // our parent is our predecessor's predecessor, this could have - // caused our parent to go out of date, fortunately we can hook - // ourselves into littlefs to catch this - cwd.type = 0; - cwd.id = 0; - lfs->mlist = &cwd; - - lfs_pair_tole32(dir.pair); - err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); - lfs_pair_fromle32(dir.pair); - if (err) { - lfs->mlist = cwd.next; - return err; - } - - lfs->mlist = cwd.next; - err = lfs_fs_preporphans(lfs, -1); - if (err) { - return err; - } - } - - // now insert into our parent block - lfs_pair_tole32(dir.pair); - err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, - {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, - {LFS_MKTAG_IF(!cwd.m.split, - LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); - lfs_pair_fromle32(dir.pair); - if (err) { - return err; - } - - return 0; -} -#endif - -static int lfs_dir_open_(lfs_t *lfs, lfs_dir_t *dir, const char *path) { - lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); - if (tag < 0) { - return tag; - } - - if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { - return LFS_ERR_NOTDIR; - } - - lfs_block_t pair[2]; - if (lfs_tag_id(tag) == 0x3ff) { - // handle root dir separately - pair[0] = lfs->root[0]; - pair[1] = lfs->root[1]; - } else { - // get dir pair from parent - lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); - if (res < 0) { - return res; - } - lfs_pair_fromle32(pair); - } - - // fetch first pair - int err = lfs_dir_fetch(lfs, &dir->m, pair); - if (err) { - return err; - } - - // setup entry - dir->head[0] = dir->m.pair[0]; - dir->head[1] = dir->m.pair[1]; - dir->id = 0; - dir->pos = 0; - - // add to list of mdirs - dir->type = LFS_TYPE_DIR; - lfs_mlist_append(lfs, (struct lfs_mlist *)dir); - - return 0; -} - -static int lfs_dir_close_(lfs_t *lfs, lfs_dir_t *dir) { - // remove from list of mdirs - lfs_mlist_remove(lfs, (struct lfs_mlist *)dir); - - return 0; -} - -static int lfs_dir_read_(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { - memset(info, 0, sizeof(*info)); - - // special offset for '.' and '..' - if (dir->pos == 0) { - info->type = LFS_TYPE_DIR; - strcpy(info->name, "."); - dir->pos += 1; - return true; - } else if (dir->pos == 1) { - info->type = LFS_TYPE_DIR; - strcpy(info->name, ".."); - dir->pos += 1; - return true; - } - - while (true) { - if (dir->id == dir->m.count) { - if (!dir->m.split) { - return false; - } - - int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); - if (err) { - return err; - } - - dir->id = 0; - } - - int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info); - if (err && err != LFS_ERR_NOENT) { - return err; - } - - dir->id += 1; - if (err != LFS_ERR_NOENT) { - break; - } - } - - dir->pos += 1; - return true; -} - -static int lfs_dir_seek_(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { - // simply walk from head dir - int err = lfs_dir_rewind_(lfs, dir); - if (err) { - return err; - } - - // first two for ./.. - dir->pos = lfs_min(2, off); - off -= dir->pos; - - // skip superblock entry - dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0); - - while (off > 0) { - if (dir->id == dir->m.count) { - if (!dir->m.split) { - return LFS_ERR_INVAL; - } - - err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); - if (err) { - return err; - } - - dir->id = 0; - } - - int diff = lfs_min(dir->m.count - dir->id, off); - dir->id += diff; - dir->pos += diff; - off -= diff; - } - - return 0; -} - -static lfs_soff_t lfs_dir_tell_(lfs_t *lfs, lfs_dir_t *dir) { - (void)lfs; - return dir->pos; -} - -static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir) { - // reload the head dir - int err = lfs_dir_fetch(lfs, &dir->m, dir->head); - if (err) { - return err; - } - - dir->id = 0; - dir->pos = 0; - return 0; -} - - -/// File index list operations /// -static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { - lfs_off_t size = *off; - lfs_off_t b = lfs->cfg->block_size - 2*4; - lfs_off_t i = size / b; - if (i == 0) { - return 0; - } - - i = (size - 4*(lfs_popc(i-1)+2)) / b; - *off = size - b*i - 4*lfs_popc(i); - return i; -} - -static int lfs_ctz_find(lfs_t *lfs, - const lfs_cache_t *pcache, lfs_cache_t *rcache, - lfs_block_t head, lfs_size_t size, - lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { - if (size == 0) { - *block = LFS_BLOCK_NULL; - *off = 0; - return 0; - } - - lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); - lfs_off_t target = lfs_ctz_index(lfs, &pos); - - while (current > target) { - lfs_size_t skip = lfs_min( - lfs_npw2(current-target+1) - 1, - lfs_ctz(current)); - - int err = lfs_bd_read(lfs, - pcache, rcache, sizeof(head), - head, 4*skip, &head, sizeof(head)); - head = lfs_fromle32(head); - if (err) { - return err; - } - - current -= 1 << skip; - } - - *block = head; - *off = pos; - return 0; -} - -#ifndef LFS_READONLY -static int lfs_ctz_extend(lfs_t *lfs, - lfs_cache_t *pcache, lfs_cache_t *rcache, - lfs_block_t head, lfs_size_t size, - lfs_block_t *block, lfs_off_t *off) { - while (true) { - // go ahead and grab a block - lfs_block_t nblock; - int err = lfs_alloc(lfs, &nblock); - if (err) { - return err; - } - - { - err = lfs_bd_erase(lfs, nblock); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - if (size == 0) { - *block = nblock; - *off = 0; - return 0; - } - - lfs_size_t noff = size - 1; - lfs_off_t index = lfs_ctz_index(lfs, &noff); - noff = noff + 1; - - // just copy out the last block if it is incomplete - if (noff != lfs->cfg->block_size) { - for (lfs_off_t i = 0; i < noff; i++) { - uint8_t data; - err = lfs_bd_read(lfs, - NULL, rcache, noff-i, - head, i, &data, 1); - if (err) { - return err; - } - - err = lfs_bd_prog(lfs, - pcache, rcache, true, - nblock, i, &data, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - } - - *block = nblock; - *off = noff; - return 0; - } - - // append block - index += 1; - lfs_size_t skips = lfs_ctz(index) + 1; - lfs_block_t nhead = head; - for (lfs_off_t i = 0; i < skips; i++) { - nhead = lfs_tole32(nhead); - err = lfs_bd_prog(lfs, pcache, rcache, true, - nblock, 4*i, &nhead, 4); - nhead = lfs_fromle32(nhead); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - if (i != skips-1) { - err = lfs_bd_read(lfs, - NULL, rcache, sizeof(nhead), - nhead, 4*i, &nhead, sizeof(nhead)); - nhead = lfs_fromle32(nhead); - if (err) { - return err; - } - } - } - - *block = nblock; - *off = 4*skips; - return 0; - } - -relocate: - LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); - - // just clear cache and try a new block - lfs_cache_drop(lfs, pcache); - } -} -#endif - -static int lfs_ctz_traverse(lfs_t *lfs, - const lfs_cache_t *pcache, lfs_cache_t *rcache, - lfs_block_t head, lfs_size_t size, - int (*cb)(void*, lfs_block_t), void *data) { - if (size == 0) { - return 0; - } - - lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); - - while (true) { - int err = cb(data, head); - if (err) { - return err; - } - - if (index == 0) { - return 0; - } - - lfs_block_t heads[2]; - int count = 2 - (index & 1); - err = lfs_bd_read(lfs, - pcache, rcache, count*sizeof(head), - head, 0, &heads, count*sizeof(head)); - heads[0] = lfs_fromle32(heads[0]); - heads[1] = lfs_fromle32(heads[1]); - if (err) { - return err; - } - - for (int i = 0; i < count-1; i++) { - err = cb(data, heads[i]); - if (err) { - return err; - } - } - - head = heads[count-1]; - index -= count; - } -} - - -/// Top level file operations /// -static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file, - const char *path, int flags, - const struct lfs_file_config *cfg) { -#ifndef LFS_READONLY - // deorphan if we haven't yet, needed at most once after poweron - if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) { - int err = lfs_fs_forceconsistency(lfs); - if (err) { - return err; - } - } -#else - LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY); -#endif - - // setup simple file details - int err; - file->cfg = cfg; - file->flags = flags; - file->pos = 0; - file->off = 0; - file->cache.buffer = NULL; - - // allocate entry for file if it doesn't exist - lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); - if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) { - err = tag; - goto cleanup; - } - - // get id, add to list of mdirs to catch update changes - file->type = LFS_TYPE_REG; - lfs_mlist_append(lfs, (struct lfs_mlist *)file); - -#ifdef LFS_READONLY - if (tag == LFS_ERR_NOENT) { - err = LFS_ERR_NOENT; - goto cleanup; -#else - if (tag == LFS_ERR_NOENT) { - if (!(flags & LFS_O_CREAT)) { - err = LFS_ERR_NOENT; - goto cleanup; - } - - // check that name fits - lfs_size_t nlen = strlen(path); - if (nlen > lfs->name_max) { - err = LFS_ERR_NAMETOOLONG; - goto cleanup; - } - - // get next slot and create entry to remember name - err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, - {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL})); - - // it may happen that the file name doesn't fit in the metadata blocks, e.g., a 256 byte file name will - // not fit in a 128 byte block. - err = (err == LFS_ERR_NOSPC) ? LFS_ERR_NAMETOOLONG : err; - if (err) { - goto cleanup; - } - - tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); - } else if (flags & LFS_O_EXCL) { - err = LFS_ERR_EXIST; - goto cleanup; -#endif - } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { - err = LFS_ERR_ISDIR; - goto cleanup; -#ifndef LFS_READONLY - } else if (flags & LFS_O_TRUNC) { - // truncate if requested - tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); - file->flags |= LFS_F_DIRTY; -#endif - } else { - // try to load what's on disk, if it's inlined we'll fix it later - tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); - if (tag < 0) { - err = tag; - goto cleanup; - } - lfs_ctz_fromle32(&file->ctz); - } - - // fetch attrs - for (unsigned i = 0; i < file->cfg->attr_count; i++) { - // if opened for read / read-write operations - if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { - lfs_stag_t res = lfs_dir_get(lfs, &file->m, - LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, - file->id, file->cfg->attrs[i].size), - file->cfg->attrs[i].buffer); - if (res < 0 && res != LFS_ERR_NOENT) { - err = res; - goto cleanup; - } - } - -#ifndef LFS_READONLY - // if opened for write / read-write operations - if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) { - if (file->cfg->attrs[i].size > lfs->attr_max) { - err = LFS_ERR_NOSPC; - goto cleanup; - } - - file->flags |= LFS_F_DIRTY; - } -#endif - } - - // allocate buffer if needed - if (file->cfg->buffer) { - file->cache.buffer = file->cfg->buffer; - } else { - file->cache.buffer = lfs_malloc(lfs->cfg->cache_size); - if (!file->cache.buffer) { - err = LFS_ERR_NOMEM; - goto cleanup; - } - } - - // zero to avoid information leak - lfs_cache_zero(lfs, &file->cache); - - if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { - // load inline files - file->ctz.head = LFS_BLOCK_INLINE; - file->ctz.size = lfs_tag_size(tag); - file->flags |= LFS_F_INLINE; - file->cache.block = file->ctz.head; - file->cache.off = 0; - file->cache.size = lfs->cfg->cache_size; - - // don't always read (may be new/trunc file) - if (file->ctz.size > 0) { - lfs_stag_t res = lfs_dir_get(lfs, &file->m, - LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, file->id, - lfs_min(file->cache.size, 0x3fe)), - file->cache.buffer); - if (res < 0) { - err = res; - goto cleanup; - } - } - } - - return 0; - -cleanup: - // clean up lingering resources -#ifndef LFS_READONLY - file->flags |= LFS_F_ERRED; -#endif - lfs_file_close_(lfs, file); - return err; -} - -#ifndef LFS_NO_MALLOC -static int lfs_file_open_(lfs_t *lfs, lfs_file_t *file, - const char *path, int flags) { - static const struct lfs_file_config defaults = {0}; - int err = lfs_file_opencfg_(lfs, file, path, flags, &defaults); - return err; -} -#endif - -static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file) { -#ifndef LFS_READONLY - int err = lfs_file_sync_(lfs, file); -#else - int err = 0; -#endif - - // remove from list of mdirs - lfs_mlist_remove(lfs, (struct lfs_mlist*)file); - - // clean up memory - if (!file->cfg->buffer) { - lfs_free(file->cache.buffer); - } - - return err; -} - - -#ifndef LFS_READONLY -static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { - while (true) { - // just relocate what exists into new block - lfs_block_t nblock; - int err = lfs_alloc(lfs, &nblock); - if (err) { - return err; - } - - err = lfs_bd_erase(lfs, nblock); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - // either read from dirty cache or disk - for (lfs_off_t i = 0; i < file->off; i++) { - uint8_t data; - if (file->flags & LFS_F_INLINE) { - err = lfs_dir_getread(lfs, &file->m, - // note we evict inline files before they can be dirty - NULL, &file->cache, file->off-i, - LFS_MKTAG(0xfff, 0x1ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), - i, &data, 1); - if (err) { - return err; - } - } else { - err = lfs_bd_read(lfs, - &file->cache, &lfs->rcache, file->off-i, - file->block, i, &data, 1); - if (err) { - return err; - } - } - - err = lfs_bd_prog(lfs, - &lfs->pcache, &lfs->rcache, true, - nblock, i, &data, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - } - - // copy over new state of file - memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size); - file->cache.block = lfs->pcache.block; - file->cache.off = lfs->pcache.off; - file->cache.size = lfs->pcache.size; - lfs_cache_zero(lfs, &lfs->pcache); - - file->block = nblock; - file->flags |= LFS_F_WRITING; - return 0; - -relocate: - LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); - - // just clear cache and try a new block - lfs_cache_drop(lfs, &lfs->pcache); - } -} -#endif - -#ifndef LFS_READONLY -static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) { - file->off = file->pos; - lfs_alloc_ckpoint(lfs); - int err = lfs_file_relocate(lfs, file); - if (err) { - return err; - } - - file->flags &= ~LFS_F_INLINE; - return 0; -} -#endif - -static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { - if (file->flags & LFS_F_READING) { - if (!(file->flags & LFS_F_INLINE)) { - lfs_cache_drop(lfs, &file->cache); - } - file->flags &= ~LFS_F_READING; - } - -#ifndef LFS_READONLY - if (file->flags & LFS_F_WRITING) { - lfs_off_t pos = file->pos; - - if (!(file->flags & LFS_F_INLINE)) { - // copy over anything after current branch - lfs_file_t orig = { - .ctz.head = file->ctz.head, - .ctz.size = file->ctz.size, - .flags = LFS_O_RDONLY, - .pos = file->pos, - .cache = lfs->rcache, - }; - lfs_cache_drop(lfs, &lfs->rcache); - - while (file->pos < file->ctz.size) { - // copy over a byte at a time, leave it up to caching - // to make this efficient - uint8_t data; - lfs_ssize_t res = lfs_file_flushedread(lfs, &orig, &data, 1); - if (res < 0) { - return res; - } - - res = lfs_file_flushedwrite(lfs, file, &data, 1); - if (res < 0) { - return res; - } - - // keep our reference to the rcache in sync - if (lfs->rcache.block != LFS_BLOCK_NULL) { - lfs_cache_drop(lfs, &orig.cache); - lfs_cache_drop(lfs, &lfs->rcache); - } - } - - // write out what we have - while (true) { - int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - - break; - -relocate: - LFS_DEBUG("Bad block at 0x%"PRIx32, file->block); - err = lfs_file_relocate(lfs, file); - if (err) { - return err; - } - } - } else { - file->pos = lfs_max(file->pos, file->ctz.size); - } - - // actual file updates - file->ctz.head = file->block; - file->ctz.size = file->pos; - file->flags &= ~LFS_F_WRITING; - file->flags |= LFS_F_DIRTY; - - file->pos = pos; - } -#endif - - return 0; -} - -#ifndef LFS_READONLY -static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file) { - if (file->flags & LFS_F_ERRED) { - // it's not safe to do anything if our file errored - return 0; - } - - int err = lfs_file_flush(lfs, file); - if (err) { - file->flags |= LFS_F_ERRED; - return err; - } - - - if ((file->flags & LFS_F_DIRTY) && - !lfs_pair_isnull(file->m.pair)) { - // before we commit metadata, we need sync the disk to make sure - // data writes don't complete after metadata writes - if (!(file->flags & LFS_F_INLINE)) { - err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); - if (err) { - return err; - } - } - - // update dir entry - uint16_t type; - const void *buffer; - lfs_size_t size; - struct lfs_ctz ctz; - if (file->flags & LFS_F_INLINE) { - // inline the whole file - type = LFS_TYPE_INLINESTRUCT; - buffer = file->cache.buffer; - size = file->ctz.size; - } else { - // update the ctz reference - type = LFS_TYPE_CTZSTRUCT; - // copy ctz so alloc will work during a relocate - ctz = file->ctz; - lfs_ctz_tole32(&ctz); - buffer = &ctz; - size = sizeof(ctz); - } - - // commit file data and attributes - err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( - {LFS_MKTAG(type, file->id, size), buffer}, - {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, - file->cfg->attr_count), file->cfg->attrs})); - if (err) { - file->flags |= LFS_F_ERRED; - return err; - } - - file->flags &= ~LFS_F_DIRTY; - } - - return 0; -} -#endif - -static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, - void *buffer, lfs_size_t size) { - uint8_t *data = buffer; - lfs_size_t nsize = size; - - if (file->pos >= file->ctz.size) { - // eof if past end - return 0; - } - - size = lfs_min(size, file->ctz.size - file->pos); - nsize = size; - - while (nsize > 0) { - // check if we need a new block - if (!(file->flags & LFS_F_READING) || - file->off == lfs->cfg->block_size) { - if (!(file->flags & LFS_F_INLINE)) { - int err = lfs_ctz_find(lfs, NULL, &file->cache, - file->ctz.head, file->ctz.size, - file->pos, &file->block, &file->off); - if (err) { - return err; - } - } else { - file->block = LFS_BLOCK_INLINE; - file->off = file->pos; - } - - file->flags |= LFS_F_READING; - } - - // read as much as we can in current block - lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); - if (file->flags & LFS_F_INLINE) { - int err = lfs_dir_getread(lfs, &file->m, - NULL, &file->cache, lfs->cfg->block_size, - LFS_MKTAG(0xfff, 0x1ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), - file->off, data, diff); - if (err) { - return err; - } - } else { - int err = lfs_bd_read(lfs, - NULL, &file->cache, lfs->cfg->block_size, - file->block, file->off, data, diff); - if (err) { - return err; - } - } - - file->pos += diff; - file->off += diff; - data += diff; - nsize -= diff; - } - - return size; -} - -static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file, - void *buffer, lfs_size_t size) { - LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY); - -#ifndef LFS_READONLY - if (file->flags & LFS_F_WRITING) { - // flush out any writes - int err = lfs_file_flush(lfs, file); - if (err) { - return err; - } - } -#endif - - return lfs_file_flushedread(lfs, file, buffer, size); -} - - -#ifndef LFS_READONLY -static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, - const void *buffer, lfs_size_t size) { - const uint8_t *data = buffer; - lfs_size_t nsize = size; - - if ((file->flags & LFS_F_INLINE) && - lfs_max(file->pos+nsize, file->ctz.size) > lfs->inline_max) { - // inline file doesn't fit anymore - int err = lfs_file_outline(lfs, file); - if (err) { - file->flags |= LFS_F_ERRED; - return err; - } - } - - while (nsize > 0) { - // check if we need a new block - if (!(file->flags & LFS_F_WRITING) || - file->off == lfs->cfg->block_size) { - if (!(file->flags & LFS_F_INLINE)) { - if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { - // find out which block we're extending from - int err = lfs_ctz_find(lfs, NULL, &file->cache, - file->ctz.head, file->ctz.size, - file->pos-1, &file->block, &(lfs_off_t){0}); - if (err) { - file->flags |= LFS_F_ERRED; - return err; - } - - // mark cache as dirty since we may have read data into it - lfs_cache_zero(lfs, &file->cache); - } - - // extend file with new blocks - lfs_alloc_ckpoint(lfs); - int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache, - file->block, file->pos, - &file->block, &file->off); - if (err) { - file->flags |= LFS_F_ERRED; - return err; - } - } else { - file->block = LFS_BLOCK_INLINE; - file->off = file->pos; - } - - file->flags |= LFS_F_WRITING; - } - - // program as much as we can in current block - lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); - while (true) { - int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true, - file->block, file->off, data, diff); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - file->flags |= LFS_F_ERRED; - return err; - } - - break; -relocate: - err = lfs_file_relocate(lfs, file); - if (err) { - file->flags |= LFS_F_ERRED; - return err; - } - } - - file->pos += diff; - file->off += diff; - data += diff; - nsize -= diff; - - lfs_alloc_ckpoint(lfs); - } - - return size; -} - -static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file, - const void *buffer, lfs_size_t size) { - LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); - - if (file->flags & LFS_F_READING) { - // drop any reads - int err = lfs_file_flush(lfs, file); - if (err) { - return err; - } - } - - if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) { - file->pos = file->ctz.size; - } - - if (file->pos + size > lfs->file_max) { - // Larger than file limit? - return LFS_ERR_FBIG; - } - - if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) { - // fill with zeros - lfs_off_t pos = file->pos; - file->pos = file->ctz.size; - - while (file->pos < pos) { - lfs_ssize_t res = lfs_file_flushedwrite(lfs, file, &(uint8_t){0}, 1); - if (res < 0) { - return res; - } - } - } - - lfs_ssize_t nsize = lfs_file_flushedwrite(lfs, file, buffer, size); - if (nsize < 0) { - return nsize; - } - - file->flags &= ~LFS_F_ERRED; - return nsize; -} -#endif - -static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file, - lfs_soff_t off, int whence) { - // find new pos - lfs_off_t npos = file->pos; - if (whence == LFS_SEEK_SET) { - npos = off; - } else if (whence == LFS_SEEK_CUR) { - if ((lfs_soff_t)file->pos + off < 0) { - return LFS_ERR_INVAL; - } else { - npos = file->pos + off; - } - } else if (whence == LFS_SEEK_END) { - lfs_soff_t res = lfs_file_size_(lfs, file) + off; - if (res < 0) { - return LFS_ERR_INVAL; - } else { - npos = res; - } - } - - if (npos > lfs->file_max) { - // file position out of range - return LFS_ERR_INVAL; - } - - if (file->pos == npos) { - // noop - position has not changed - return npos; - } - - // if we're only reading and our new offset is still in the file's cache - // we can avoid flushing and needing to reread the data - if ( -#ifndef LFS_READONLY - !(file->flags & LFS_F_WRITING) -#else - true -#endif - ) { - int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos}); - lfs_off_t noff = npos; - int nindex = lfs_ctz_index(lfs, &noff); - if (oindex == nindex - && noff >= file->cache.off - && noff < file->cache.off + file->cache.size) { - file->pos = npos; - file->off = noff; - return npos; - } - } - - // write out everything beforehand, may be noop if rdonly - int err = lfs_file_flush(lfs, file); - if (err) { - return err; - } - - // update pos - file->pos = npos; - return npos; -} - -#ifndef LFS_READONLY -static int lfs_file_truncate_(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { - LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); - - if (size > LFS_FILE_MAX) { - return LFS_ERR_INVAL; - } - - lfs_off_t pos = file->pos; - lfs_off_t oldsize = lfs_file_size_(lfs, file); - if (size < oldsize) { - // revert to inline file? - if (size <= lfs->inline_max) { - // flush+seek to head - lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET); - if (res < 0) { - return (int)res; - } - - // read our data into rcache temporarily - lfs_cache_drop(lfs, &lfs->rcache); - res = lfs_file_flushedread(lfs, file, - lfs->rcache.buffer, size); - if (res < 0) { - return (int)res; - } - - file->ctz.head = LFS_BLOCK_INLINE; - file->ctz.size = size; - file->flags |= LFS_F_DIRTY | LFS_F_READING | LFS_F_INLINE; - file->cache.block = file->ctz.head; - file->cache.off = 0; - file->cache.size = lfs->cfg->cache_size; - memcpy(file->cache.buffer, lfs->rcache.buffer, size); - - } else { - // need to flush since directly changing metadata - int err = lfs_file_flush(lfs, file); - if (err) { - return err; - } - - // lookup new head in ctz skip list - err = lfs_ctz_find(lfs, NULL, &file->cache, - file->ctz.head, file->ctz.size, - size-1, &file->block, &(lfs_off_t){0}); - if (err) { - return err; - } - - // need to set pos/block/off consistently so seeking back to - // the old position does not get confused - file->pos = size; - file->ctz.head = file->block; - file->ctz.size = size; - file->flags |= LFS_F_DIRTY | LFS_F_READING; - } - } else if (size > oldsize) { - // flush+seek if not already at end - lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_END); - if (res < 0) { - return (int)res; - } - - // fill with zeros - while (file->pos < size) { - res = lfs_file_write_(lfs, file, &(uint8_t){0}, 1); - if (res < 0) { - return (int)res; - } - } - } - - // restore pos - lfs_soff_t res = lfs_file_seek_(lfs, file, pos, LFS_SEEK_SET); - if (res < 0) { - return (int)res; - } - - return 0; -} -#endif - -static lfs_soff_t lfs_file_tell_(lfs_t *lfs, lfs_file_t *file) { - (void)lfs; - return file->pos; -} - -static int lfs_file_rewind_(lfs_t *lfs, lfs_file_t *file) { - lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET); - if (res < 0) { - return (int)res; - } - - return 0; -} - -static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file) { - (void)lfs; - -#ifndef LFS_READONLY - if (file->flags & LFS_F_WRITING) { - return lfs_max(file->pos, file->ctz.size); - } -#endif - - return file->ctz.size; -} - - -/// General fs operations /// -static int lfs_stat_(lfs_t *lfs, const char *path, struct lfs_info *info) { - lfs_mdir_t cwd; - lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); - if (tag < 0) { - return (int)tag; - } - - return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); -} - -#ifndef LFS_READONLY -static int lfs_remove_(lfs_t *lfs, const char *path) { - // deorphan if we haven't yet, needed at most once after poweron - int err = lfs_fs_forceconsistency(lfs); - if (err) { - return err; - } - - lfs_mdir_t cwd; - lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); - if (tag < 0 || lfs_tag_id(tag) == 0x3ff) { - return (tag < 0) ? (int)tag : LFS_ERR_INVAL; - } - - struct lfs_mlist dir; - dir.next = lfs->mlist; - if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { - // must be empty before removal - lfs_block_t pair[2]; - lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); - if (res < 0) { - return (int)res; - } - lfs_pair_fromle32(pair); - - err = lfs_dir_fetch(lfs, &dir.m, pair); - if (err) { - return err; - } - - if (dir.m.count > 0 || dir.m.split) { - return LFS_ERR_NOTEMPTY; - } - - // mark fs as orphaned - err = lfs_fs_preporphans(lfs, +1); - if (err) { - return err; - } - - // I know it's crazy but yes, dir can be changed by our parent's - // commit (if predecessor is child) - dir.type = 0; - dir.id = 0; - lfs->mlist = &dir; - } - - // delete the entry - err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL})); - if (err) { - lfs->mlist = dir.next; - return err; - } - - lfs->mlist = dir.next; - if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { - // fix orphan - err = lfs_fs_preporphans(lfs, -1); - if (err) { - return err; - } - - err = lfs_fs_pred(lfs, dir.m.pair, &cwd); - if (err) { - return err; - } - - err = lfs_dir_drop(lfs, &cwd, &dir.m); - if (err) { - return err; - } - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) { - // deorphan if we haven't yet, needed at most once after poweron - int err = lfs_fs_forceconsistency(lfs); - if (err) { - return err; - } - - // find old entry - lfs_mdir_t oldcwd; - lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL); - if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) { - return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL; - } - - // find new entry - lfs_mdir_t newcwd; - uint16_t newid; - lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); - if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && - !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) { - return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; - } - - // if we're in the same pair there's a few special cases... - bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0); - uint16_t newoldid = lfs_tag_id(oldtag); - - struct lfs_mlist prevdir; - prevdir.next = lfs->mlist; - if (prevtag == LFS_ERR_NOENT) { - // check that name fits - lfs_size_t nlen = strlen(newpath); - if (nlen > lfs->name_max) { - return LFS_ERR_NAMETOOLONG; - } - - // there is a small chance we are being renamed in the same - // directory/ to an id less than our old id, the global update - // to handle this is a bit messy - if (samepair && newid <= newoldid) { - newoldid += 1; - } - } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) { - return (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) - ? LFS_ERR_ISDIR - : LFS_ERR_NOTDIR; - } else if (samepair && newid == newoldid) { - // we're renaming to ourselves?? - return 0; - } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { - // must be empty before removal - lfs_block_t prevpair[2]; - lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair); - if (res < 0) { - return (int)res; - } - lfs_pair_fromle32(prevpair); - - // must be empty before removal - err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); - if (err) { - return err; - } - - if (prevdir.m.count > 0 || prevdir.m.split) { - return LFS_ERR_NOTEMPTY; - } - - // mark fs as orphaned - err = lfs_fs_preporphans(lfs, +1); - if (err) { - return err; - } - - // I know it's crazy but yes, dir can be changed by our parent's - // commit (if predecessor is child) - prevdir.type = 0; - prevdir.id = 0; - lfs->mlist = &prevdir; - } - - if (!samepair) { - lfs_fs_prepmove(lfs, newoldid, oldcwd.pair); - } - - // move over all attributes - err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS( - {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, - LFS_TYPE_DELETE, newid, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, - {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath}, - {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, - {LFS_MKTAG_IF(samepair, - LFS_TYPE_DELETE, newoldid, 0), NULL})); - if (err) { - lfs->mlist = prevdir.next; - return err; - } - - // let commit clean up after move (if we're different! otherwise move - // logic already fixed it for us) - if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) { - // prep gstate and delete move id - lfs_fs_prepmove(lfs, 0x3ff, NULL); - err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})); - if (err) { - lfs->mlist = prevdir.next; - return err; - } - } - - lfs->mlist = prevdir.next; - if (prevtag != LFS_ERR_NOENT - && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { - // fix orphan - err = lfs_fs_preporphans(lfs, -1); - if (err) { - return err; - } - - err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); - if (err) { - return err; - } - - err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); - if (err) { - return err; - } - } - - return 0; -} -#endif - -static lfs_ssize_t lfs_getattr_(lfs_t *lfs, const char *path, - uint8_t type, void *buffer, lfs_size_t size) { - lfs_mdir_t cwd; - lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); - if (tag < 0) { - return tag; - } - - uint16_t id = lfs_tag_id(tag); - if (id == 0x3ff) { - // special case for root - id = 0; - int err = lfs_dir_fetch(lfs, &cwd, lfs->root); - if (err) { - return err; - } - } - - tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_USERATTR + type, - id, lfs_min(size, lfs->attr_max)), - buffer); - if (tag < 0) { - if (tag == LFS_ERR_NOENT) { - return LFS_ERR_NOATTR; - } - - return tag; - } - - return lfs_tag_size(tag); -} - -#ifndef LFS_READONLY -static int lfs_commitattr(lfs_t *lfs, const char *path, - uint8_t type, const void *buffer, lfs_size_t size) { - lfs_mdir_t cwd; - lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); - if (tag < 0) { - return tag; - } - - uint16_t id = lfs_tag_id(tag); - if (id == 0x3ff) { - // special case for root - id = 0; - int err = lfs_dir_fetch(lfs, &cwd, lfs->root); - if (err) { - return err; - } - } - - return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer})); -} -#endif - -#ifndef LFS_READONLY -static int lfs_setattr_(lfs_t *lfs, const char *path, - uint8_t type, const void *buffer, lfs_size_t size) { - if (size > lfs->attr_max) { - return LFS_ERR_NOSPC; - } - - return lfs_commitattr(lfs, path, type, buffer, size); -} -#endif - -#ifndef LFS_READONLY -static int lfs_removeattr_(lfs_t *lfs, const char *path, uint8_t type) { - return lfs_commitattr(lfs, path, type, NULL, 0x3ff); -} -#endif - - -/// Filesystem operations /// - -// compile time checks, see lfs.h for why these limits exist -#if LFS_NAME_MAX > 1022 -#error "Invalid LFS_NAME_MAX, must be <= 1022" -#endif - -#if LFS_FILE_MAX > 2147483647 -#error "Invalid LFS_FILE_MAX, must be <= 2147483647" -#endif - -#if LFS_ATTR_MAX > 1022 -#error "Invalid LFS_ATTR_MAX, must be <= 1022" -#endif - -// common filesystem initialization -static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { - lfs->cfg = cfg; - lfs->block_count = cfg->block_count; // May be 0 - int err = 0; - -#ifdef LFS_MULTIVERSION - // this driver only supports minor version < current minor version - LFS_ASSERT(!lfs->cfg->disk_version || ( - (0xffff & (lfs->cfg->disk_version >> 16)) - == LFS_DISK_VERSION_MAJOR - && (0xffff & (lfs->cfg->disk_version >> 0)) - <= LFS_DISK_VERSION_MINOR)); -#endif - - // check that bool is a truthy-preserving type - // - // note the most common reason for this failure is a before-c99 compiler, - // which littlefs currently does not support - LFS_ASSERT((bool)0x80000000); - - // validate that the lfs-cfg sizes were initiated properly before - // performing any arithmetic logics with them - LFS_ASSERT(lfs->cfg->read_size != 0); - LFS_ASSERT(lfs->cfg->prog_size != 0); - LFS_ASSERT(lfs->cfg->cache_size != 0); - - // check that block size is a multiple of cache size is a multiple - // of prog and read sizes - LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0); - LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0); - LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); - - // check that the block size is large enough to fit all ctz pointers - LFS_ASSERT(lfs->cfg->block_size >= 128); - // this is the exact calculation for all ctz pointers, if this fails - // and the simpler assert above does not, math must be broken - LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) - <= lfs->cfg->block_size); - - // block_cycles = 0 is no longer supported. - // - // block_cycles is the number of erase cycles before littlefs evicts - // metadata logs as a part of wear leveling. Suggested values are in the - // range of 100-1000, or set block_cycles to -1 to disable block-level - // wear-leveling. - LFS_ASSERT(lfs->cfg->block_cycles != 0); - - // check that compact_thresh makes sense - // - // metadata can't be compacted below block_size/2, and metadata can't - // exceed a block_size - LFS_ASSERT(lfs->cfg->compact_thresh == 0 - || lfs->cfg->compact_thresh >= lfs->cfg->block_size/2); - LFS_ASSERT(lfs->cfg->compact_thresh == (lfs_size_t)-1 - || lfs->cfg->compact_thresh <= lfs->cfg->block_size); - - // setup read cache - if (lfs->cfg->read_buffer) { - lfs->rcache.buffer = lfs->cfg->read_buffer; - } else { - lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size); - if (!lfs->rcache.buffer) { - err = LFS_ERR_NOMEM; - goto cleanup; - } - } - - // setup program cache - if (lfs->cfg->prog_buffer) { - lfs->pcache.buffer = lfs->cfg->prog_buffer; - } else { - lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size); - if (!lfs->pcache.buffer) { - err = LFS_ERR_NOMEM; - goto cleanup; - } - } - - // zero to avoid information leaks - lfs_cache_zero(lfs, &lfs->rcache); - lfs_cache_zero(lfs, &lfs->pcache); - - // setup lookahead buffer, note mount finishes initializing this after - // we establish a decent pseudo-random seed - LFS_ASSERT(lfs->cfg->lookahead_size > 0); - if (lfs->cfg->lookahead_buffer) { - lfs->lookahead.buffer = lfs->cfg->lookahead_buffer; - } else { - lfs->lookahead.buffer = lfs_malloc(lfs->cfg->lookahead_size); - if (!lfs->lookahead.buffer) { - err = LFS_ERR_NOMEM; - goto cleanup; - } - } - - // check that the size limits are sane - LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX); - lfs->name_max = lfs->cfg->name_max; - if (!lfs->name_max) { - lfs->name_max = LFS_NAME_MAX; - } - - LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX); - lfs->file_max = lfs->cfg->file_max; - if (!lfs->file_max) { - lfs->file_max = LFS_FILE_MAX; - } - - LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX); - lfs->attr_max = lfs->cfg->attr_max; - if (!lfs->attr_max) { - lfs->attr_max = LFS_ATTR_MAX; - } - - LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size); - - LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 - || lfs->cfg->inline_max <= lfs->cfg->cache_size); - LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 - || lfs->cfg->inline_max <= lfs->attr_max); - LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 - || lfs->cfg->inline_max <= ((lfs->cfg->metadata_max) - ? lfs->cfg->metadata_max - : lfs->cfg->block_size)/8); - lfs->inline_max = lfs->cfg->inline_max; - if (lfs->inline_max == (lfs_size_t)-1) { - lfs->inline_max = 0; - } else if (lfs->inline_max == 0) { - lfs->inline_max = lfs_min( - lfs->cfg->cache_size, - lfs_min( - lfs->attr_max, - ((lfs->cfg->metadata_max) - ? lfs->cfg->metadata_max - : lfs->cfg->block_size)/8)); - } - - // setup default state - lfs->root[0] = LFS_BLOCK_NULL; - lfs->root[1] = LFS_BLOCK_NULL; - lfs->mlist = NULL; - lfs->seed = 0; - lfs->gdisk = (lfs_gstate_t){0}; - lfs->gstate = (lfs_gstate_t){0}; - lfs->gdelta = (lfs_gstate_t){0}; -#ifdef LFS_MIGRATE - lfs->lfs1 = NULL; -#endif - - return 0; - -cleanup: - lfs_deinit(lfs); - return err; -} - -static int lfs_deinit(lfs_t *lfs) { - // free allocated memory - if (!lfs->cfg->read_buffer) { - lfs_free(lfs->rcache.buffer); - } - - if (!lfs->cfg->prog_buffer) { - lfs_free(lfs->pcache.buffer); - } - - if (!lfs->cfg->lookahead_buffer) { - lfs_free(lfs->lookahead.buffer); - } - - return 0; -} - - - -#ifndef LFS_READONLY -static int lfs_format_(lfs_t *lfs, const struct lfs_config *cfg) { - int err = 0; - { - err = lfs_init(lfs, cfg); - if (err) { - return err; - } - - LFS_ASSERT(cfg->block_count != 0); - - // create free lookahead - memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); - lfs->lookahead.start = 0; - lfs->lookahead.size = lfs_min(8*lfs->cfg->lookahead_size, - lfs->block_count); - lfs->lookahead.next = 0; - lfs_alloc_ckpoint(lfs); - - // create root dir - lfs_mdir_t root; - err = lfs_dir_alloc(lfs, &root); - if (err) { - goto cleanup; - } - - // write one superblock - lfs_superblock_t superblock = { - .version = lfs_fs_disk_version(lfs), - .block_size = lfs->cfg->block_size, - .block_count = lfs->block_count, - .name_max = lfs->name_max, - .file_max = lfs->file_max, - .attr_max = lfs->attr_max, - }; - - lfs_superblock_tole32(&superblock); - err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, - {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock})); - if (err) { - goto cleanup; - } - - // force compaction to prevent accidentally mounting any - // older version of littlefs that may live on disk - root.erased = false; - err = lfs_dir_commit(lfs, &root, NULL, 0); - if (err) { - goto cleanup; - } - - // sanity check that fetch works - err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); - if (err) { - goto cleanup; - } - } - -cleanup: - lfs_deinit(lfs); - return err; - -} -#endif - -static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) { - int err = lfs_init(lfs, cfg); - if (err) { - return err; - } - - // scan directory blocks for superblock and any global updates - lfs_mdir_t dir = {.tail = {0, 1}}; - lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; - lfs_size_t tortoise_i = 1; - lfs_size_t tortoise_period = 1; - while (!lfs_pair_isnull(dir.tail)) { - // detect cycles with Brent's algorithm - if (lfs_pair_issync(dir.tail, tortoise)) { - LFS_WARN("Cycle detected in tail list"); - err = LFS_ERR_CORRUPT; - goto cleanup; - } - if (tortoise_i == tortoise_period) { - tortoise[0] = dir.tail[0]; - tortoise[1] = dir.tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; - - // fetch next block in tail list - lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, - LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), - NULL, - lfs_dir_find_match, &(struct lfs_dir_find_match){ - lfs, "littlefs", 8}); - if (tag < 0) { - err = tag; - goto cleanup; - } - - // has superblock? - if (tag && !lfs_tag_isdelete(tag)) { - // update root - lfs->root[0] = dir.pair[0]; - lfs->root[1] = dir.pair[1]; - - // grab superblock - lfs_superblock_t superblock; - tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock); - if (tag < 0) { - err = tag; - goto cleanup; - } - lfs_superblock_fromle32(&superblock); - - // check version - uint16_t major_version = (0xffff & (superblock.version >> 16)); - uint16_t minor_version = (0xffff & (superblock.version >> 0)); - if (major_version != lfs_fs_disk_version_major(lfs) - || minor_version > lfs_fs_disk_version_minor(lfs)) { - LFS_ERROR("Invalid version " - "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16, - major_version, - minor_version, - lfs_fs_disk_version_major(lfs), - lfs_fs_disk_version_minor(lfs)); - err = LFS_ERR_INVAL; - goto cleanup; - } - - // found older minor version? set an in-device only bit in the - // gstate so we know we need to rewrite the superblock before - // the first write - if (minor_version < lfs_fs_disk_version_minor(lfs)) { - LFS_DEBUG("Found older minor version " - "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, - major_version, - minor_version, - lfs_fs_disk_version_major(lfs), - lfs_fs_disk_version_minor(lfs)); - // note this bit is reserved on disk, so fetching more gstate - // will not interfere here - lfs_fs_prepsuperblock(lfs, true); - } - - // check superblock configuration - if (superblock.name_max) { - if (superblock.name_max > lfs->name_max) { - LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", - superblock.name_max, lfs->name_max); - err = LFS_ERR_INVAL; - goto cleanup; - } - - lfs->name_max = superblock.name_max; - } - - if (superblock.file_max) { - if (superblock.file_max > lfs->file_max) { - LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", - superblock.file_max, lfs->file_max); - err = LFS_ERR_INVAL; - goto cleanup; - } - - lfs->file_max = superblock.file_max; - } - - if (superblock.attr_max) { - if (superblock.attr_max > lfs->attr_max) { - LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", - superblock.attr_max, lfs->attr_max); - err = LFS_ERR_INVAL; - goto cleanup; - } - - lfs->attr_max = superblock.attr_max; - - // we also need to update inline_max in case attr_max changed - lfs->inline_max = lfs_min(lfs->inline_max, lfs->attr_max); - } - - // this is where we get the block_count from disk if block_count=0 - if (lfs->cfg->block_count - && superblock.block_count != lfs->cfg->block_count) { - LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")", - superblock.block_count, lfs->cfg->block_count); - err = LFS_ERR_INVAL; - goto cleanup; - } - - lfs->block_count = superblock.block_count; - - if (superblock.block_size != lfs->cfg->block_size) { - LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")", - superblock.block_size, lfs->cfg->block_size); - err = LFS_ERR_INVAL; - goto cleanup; - } - } - - // has gstate? - err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); - if (err) { - goto cleanup; - } - } - - // update littlefs with gstate - if (!lfs_gstate_iszero(&lfs->gstate)) { - LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, - lfs->gstate.tag, - lfs->gstate.pair[0], - lfs->gstate.pair[1]); - } - lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag); - lfs->gdisk = lfs->gstate; - - // setup free lookahead, to distribute allocations uniformly across - // boots, we start the allocator at a random location - lfs->lookahead.start = lfs->seed % lfs->block_count; - lfs_alloc_drop(lfs); - - return 0; - -cleanup: - lfs_unmount_(lfs); - return err; -} - -static int lfs_unmount_(lfs_t *lfs) { - return lfs_deinit(lfs); -} - - -/// Filesystem filesystem operations /// -static int lfs_fs_stat_(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { - // if the superblock is up-to-date, we must be on the most recent - // minor version of littlefs - if (!lfs_gstate_needssuperblock(&lfs->gstate)) { - fsinfo->disk_version = lfs_fs_disk_version(lfs); - - // otherwise we need to read the minor version on disk - } else { - // fetch the superblock - lfs_mdir_t dir; - int err = lfs_dir_fetch(lfs, &dir, lfs->root); - if (err) { - return err; - } - - lfs_superblock_t superblock; - lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock); - if (tag < 0) { - return tag; - } - lfs_superblock_fromle32(&superblock); - - // read the on-disk version - fsinfo->disk_version = superblock.version; - } - - // filesystem geometry - fsinfo->block_size = lfs->cfg->block_size; - fsinfo->block_count = lfs->block_count; - - // other on-disk configuration, we cache all of these for internal use - fsinfo->name_max = lfs->name_max; - fsinfo->file_max = lfs->file_max; - fsinfo->attr_max = lfs->attr_max; - - return 0; -} - -int lfs_fs_traverse_(lfs_t *lfs, - int (*cb)(void *data, lfs_block_t block), void *data, - bool includeorphans) { - // iterate over metadata pairs - lfs_mdir_t dir = {.tail = {0, 1}}; - -#ifdef LFS_MIGRATE - // also consider v1 blocks during migration - if (lfs->lfs1) { - int err = lfs1_traverse(lfs, cb, data); - if (err) { - return err; - } - - dir.tail[0] = lfs->root[0]; - dir.tail[1] = lfs->root[1]; - } -#endif - - lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; - lfs_size_t tortoise_i = 1; - lfs_size_t tortoise_period = 1; - while (!lfs_pair_isnull(dir.tail)) { - // detect cycles with Brent's algorithm - if (lfs_pair_issync(dir.tail, tortoise)) { - LFS_WARN("Cycle detected in tail list"); - return LFS_ERR_CORRUPT; - } - if (tortoise_i == tortoise_period) { - tortoise[0] = dir.tail[0]; - tortoise[1] = dir.tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; - - for (int i = 0; i < 2; i++) { - int err = cb(data, dir.tail[i]); - if (err) { - return err; - } - } - - // iterate through ids in directory - int err = lfs_dir_fetch(lfs, &dir, dir.tail); - if (err) { - return err; - } - - for (uint16_t id = 0; id < dir.count; id++) { - struct lfs_ctz ctz; - lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); - if (tag < 0) { - if (tag == LFS_ERR_NOENT) { - continue; - } - return tag; - } - lfs_ctz_fromle32(&ctz); - - if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { - err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, - ctz.head, ctz.size, cb, data); - if (err) { - return err; - } - } else if (includeorphans && - lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { - for (int i = 0; i < 2; i++) { - err = cb(data, (&ctz.head)[i]); - if (err) { - return err; - } - } - } - } - } - -#ifndef LFS_READONLY - // iterate over any open files - for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { - if (f->type != LFS_TYPE_REG) { - continue; - } - - if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) { - int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, - f->ctz.head, f->ctz.size, cb, data); - if (err) { - return err; - } - } - - if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) { - int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, - f->block, f->pos, cb, data); - if (err) { - return err; - } - } - } -#endif - - return 0; -} - -#ifndef LFS_READONLY -static int lfs_fs_pred(lfs_t *lfs, - const lfs_block_t pair[2], lfs_mdir_t *pdir) { - // iterate over all directory directory entries - pdir->tail[0] = 0; - pdir->tail[1] = 1; - lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; - lfs_size_t tortoise_i = 1; - lfs_size_t tortoise_period = 1; - while (!lfs_pair_isnull(pdir->tail)) { - // detect cycles with Brent's algorithm - if (lfs_pair_issync(pdir->tail, tortoise)) { - LFS_WARN("Cycle detected in tail list"); - return LFS_ERR_CORRUPT; - } - if (tortoise_i == tortoise_period) { - tortoise[0] = pdir->tail[0]; - tortoise[1] = pdir->tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; - - if (lfs_pair_cmp(pdir->tail, pair) == 0) { - return 0; - } - - int err = lfs_dir_fetch(lfs, pdir, pdir->tail); - if (err) { - return err; - } - } - - return LFS_ERR_NOENT; -} -#endif - -#ifndef LFS_READONLY -struct lfs_fs_parent_match { - lfs_t *lfs; - const lfs_block_t pair[2]; -}; -#endif - -#ifndef LFS_READONLY -static int lfs_fs_parent_match(void *data, - lfs_tag_t tag, const void *buffer) { - struct lfs_fs_parent_match *find = data; - lfs_t *lfs = find->lfs; - const struct lfs_diskoff *disk = buffer; - (void)tag; - - lfs_block_t child[2]; - int err = lfs_bd_read(lfs, - &lfs->pcache, &lfs->rcache, lfs->cfg->block_size, - disk->block, disk->off, &child, sizeof(child)); - if (err) { - return err; - } - - lfs_pair_fromle32(child); - return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT; -} -#endif - -#ifndef LFS_READONLY -static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], - lfs_mdir_t *parent) { - // use fetchmatch with callback to find pairs - parent->tail[0] = 0; - parent->tail[1] = 1; - lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; - lfs_size_t tortoise_i = 1; - lfs_size_t tortoise_period = 1; - while (!lfs_pair_isnull(parent->tail)) { - // detect cycles with Brent's algorithm - if (lfs_pair_issync(parent->tail, tortoise)) { - LFS_WARN("Cycle detected in tail list"); - return LFS_ERR_CORRUPT; - } - if (tortoise_i == tortoise_period) { - tortoise[0] = parent->tail[0]; - tortoise[1] = parent->tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; - - lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, - LFS_MKTAG(0x7ff, 0, 0x3ff), - LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), - NULL, - lfs_fs_parent_match, &(struct lfs_fs_parent_match){ - lfs, {pair[0], pair[1]}}); - if (tag && tag != LFS_ERR_NOENT) { - return tag; - } - } - - return LFS_ERR_NOENT; -} -#endif - -static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) { - lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200)) - | (uint32_t)needssuperblock << 9; -} - -#ifndef LFS_READONLY -static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { - LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0); - LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x1ff || orphans <= 0); - lfs->gstate.tag += orphans; - lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) | - ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31)); - - return 0; -} -#endif - -#ifndef LFS_READONLY -static void lfs_fs_prepmove(lfs_t *lfs, - uint16_t id, const lfs_block_t pair[2]) { - lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) | - ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); - lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; - lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_fs_desuperblock(lfs_t *lfs) { - if (!lfs_gstate_needssuperblock(&lfs->gstate)) { - return 0; - } - - LFS_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}", - lfs->root[0], - lfs->root[1]); - - lfs_mdir_t root; - int err = lfs_dir_fetch(lfs, &root, lfs->root); - if (err) { - return err; - } - - // write a new superblock - lfs_superblock_t superblock = { - .version = lfs_fs_disk_version(lfs), - .block_size = lfs->cfg->block_size, - .block_count = lfs->block_count, - .name_max = lfs->name_max, - .file_max = lfs->file_max, - .attr_max = lfs->attr_max, - }; - - lfs_superblock_tole32(&superblock); - err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock})); - if (err) { - return err; - } - - lfs_fs_prepsuperblock(lfs, false); - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_fs_demove(lfs_t *lfs) { - if (!lfs_gstate_hasmove(&lfs->gdisk)) { - return 0; - } - - // Fix bad moves - LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, - lfs->gdisk.pair[0], - lfs->gdisk.pair[1], - lfs_tag_id(lfs->gdisk.tag)); - - // no other gstate is supported at this time, so if we found something else - // something most likely went wrong in gstate calculation - LFS_ASSERT(lfs_tag_type3(lfs->gdisk.tag) == LFS_TYPE_DELETE); - - // fetch and delete the moved entry - lfs_mdir_t movedir; - int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair); - if (err) { - return err; - } - - // prep gstate and delete move id - uint16_t moveid = lfs_tag_id(lfs->gdisk.tag); - lfs_fs_prepmove(lfs, 0x3ff, NULL); - err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL})); - if (err) { - return err; - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) { - if (!lfs_gstate_hasorphans(&lfs->gstate)) { - return 0; - } - - // Check for orphans in two separate passes: - // - 1 for half-orphans (relocations) - // - 2 for full-orphans (removes/renames) - // - // Two separate passes are needed as half-orphans can contain outdated - // references to full-orphans, effectively hiding them from the deorphan - // search. - // - int pass = 0; - while (pass < 2) { - // Fix any orphans - lfs_mdir_t pdir = {.split = true, .tail = {0, 1}}; - lfs_mdir_t dir; - bool moreorphans = false; - - // iterate over all directory directory entries - while (!lfs_pair_isnull(pdir.tail)) { - int err = lfs_dir_fetch(lfs, &dir, pdir.tail); - if (err) { - return err; - } - - // check head blocks for orphans - if (!pdir.split) { - // check if we have a parent - lfs_mdir_t parent; - lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent); - if (tag < 0 && tag != LFS_ERR_NOENT) { - return tag; - } - - if (pass == 0 && tag != LFS_ERR_NOENT) { - lfs_block_t pair[2]; - lfs_stag_t state = lfs_dir_get(lfs, &parent, - LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair); - if (state < 0) { - return state; - } - lfs_pair_fromle32(pair); - - if (!lfs_pair_issync(pair, pdir.tail)) { - // we have desynced - LFS_DEBUG("Fixing half-orphan " - "{0x%"PRIx32", 0x%"PRIx32"} " - "-> {0x%"PRIx32", 0x%"PRIx32"}", - pdir.tail[0], pdir.tail[1], pair[0], pair[1]); - - // fix pending move in this pair? this looks like an - // optimization but is in fact _required_ since - // relocating may outdate the move. - uint16_t moveid = 0x3ff; - if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { - moveid = lfs_tag_id(lfs->gstate.tag); - LFS_DEBUG("Fixing move while fixing orphans " - "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", - pdir.pair[0], pdir.pair[1], moveid); - lfs_fs_prepmove(lfs, 0x3ff, NULL); - } - - lfs_pair_tole32(pair); - state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( - {LFS_MKTAG_IF(moveid != 0x3ff, - LFS_TYPE_DELETE, moveid, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), - pair})); - lfs_pair_fromle32(pair); - if (state < 0) { - return state; - } - - // did our commit create more orphans? - if (state == LFS_OK_ORPHANED) { - moreorphans = true; - } - - // refetch tail - continue; - } - } - - // note we only check for full orphans if we may have had a - // power-loss, otherwise orphans are created intentionally - // during operations such as lfs_mkdir - if (pass == 1 && tag == LFS_ERR_NOENT && powerloss) { - // we are an orphan - LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", - pdir.tail[0], pdir.tail[1]); - - // steal state - err = lfs_dir_getgstate(lfs, &dir, &lfs->gdelta); - if (err) { - return err; - } - - // steal tail - lfs_pair_tole32(dir.tail); - int state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_TAIL + dir.split, 0x3ff, 8), - dir.tail})); - lfs_pair_fromle32(dir.tail); - if (state < 0) { - return state; - } - - // did our commit create more orphans? - if (state == LFS_OK_ORPHANED) { - moreorphans = true; - } - - // refetch tail - continue; - } - } - - pdir = dir; - } - - pass = moreorphans ? 0 : pass+1; - } - - // mark orphans as fixed - return lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); -} -#endif - -#ifndef LFS_READONLY -static int lfs_fs_forceconsistency(lfs_t *lfs) { - int err = lfs_fs_desuperblock(lfs); - if (err) { - return err; - } - - err = lfs_fs_demove(lfs); - if (err) { - return err; - } - - err = lfs_fs_deorphan(lfs, true); - if (err) { - return err; - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_fs_mkconsistent_(lfs_t *lfs) { - // lfs_fs_forceconsistency does most of the work here - int err = lfs_fs_forceconsistency(lfs); - if (err) { - return err; - } - - // do we have any pending gstate? - lfs_gstate_t delta = {0}; - lfs_gstate_xor(&delta, &lfs->gdisk); - lfs_gstate_xor(&delta, &lfs->gstate); - if (!lfs_gstate_iszero(&delta)) { - // lfs_dir_commit will implicitly write out any pending gstate - lfs_mdir_t root; - err = lfs_dir_fetch(lfs, &root, lfs->root); - if (err) { - return err; - } - - err = lfs_dir_commit(lfs, &root, NULL, 0); - if (err) { - return err; - } - } - - return 0; -} -#endif - -static int lfs_fs_size_count(void *p, lfs_block_t block) { - (void)block; - lfs_size_t *size = p; - *size += 1; - return 0; -} - -static lfs_ssize_t lfs_fs_size_(lfs_t *lfs) { - lfs_size_t size = 0; - int err = lfs_fs_traverse_(lfs, lfs_fs_size_count, &size, false); - if (err) { - return err; - } - - return size; -} - -// explicit garbage collection -#ifndef LFS_READONLY -static int lfs_fs_gc_(lfs_t *lfs) { - // force consistency, even if we're not necessarily going to write, - // because this function is supposed to take care of janitorial work - // isn't it? - int err = lfs_fs_forceconsistency(lfs); - if (err) { - return err; - } - - // try to compact metadata pairs, note we can't really accomplish - // anything if compact_thresh doesn't at least leave a prog_size - // available - if (lfs->cfg->compact_thresh - < lfs->cfg->block_size - lfs->cfg->prog_size) { - // iterate over all mdirs - lfs_mdir_t mdir = {.tail = {0, 1}}; - while (!lfs_pair_isnull(mdir.tail)) { - err = lfs_dir_fetch(lfs, &mdir, mdir.tail); - if (err) { - return err; - } - - // not erased? exceeds our compaction threshold? - if (!mdir.erased || ((lfs->cfg->compact_thresh == 0) - ? mdir.off > lfs->cfg->block_size - lfs->cfg->block_size/8 - : mdir.off > lfs->cfg->compact_thresh)) { - // the easiest way to trigger a compaction is to mark - // the mdir as unerased and add an empty commit - mdir.erased = false; - err = lfs_dir_commit(lfs, &mdir, NULL, 0); - if (err) { - return err; - } - } - } - } - - // try to populate the lookahead buffer, unless it's already full - if (lfs->lookahead.size < 8*lfs->cfg->lookahead_size) { - err = lfs_alloc_scan(lfs); - if (err) { - return err; - } - } - - return 0; -} -#endif - -#ifndef LFS_READONLY -static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) { - // shrinking is not supported - LFS_ASSERT(block_count >= lfs->block_count); - - if (block_count > lfs->block_count) { - lfs->block_count = block_count; - - // fetch the root - lfs_mdir_t root; - int err = lfs_dir_fetch(lfs, &root, lfs->root); - if (err) { - return err; - } - - // update the superblock - lfs_superblock_t superblock; - lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock); - if (tag < 0) { - return tag; - } - lfs_superblock_fromle32(&superblock); - - superblock.block_count = lfs->block_count; - - lfs_superblock_tole32(&superblock); - err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( - {tag, &superblock})); - if (err) { - return err; - } - } - - return 0; -} -#endif - -#ifdef LFS_MIGRATE -////// Migration from littelfs v1 below this ////// - -/// Version info /// - -// Software library version -// Major (top-nibble), incremented on backwards incompatible changes -// Minor (bottom-nibble), incremented on feature additions -#define LFS1_VERSION 0x00010007 -#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16)) -#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >> 0)) - -// Version of On-disk data structures -// Major (top-nibble), incremented on backwards incompatible changes -// Minor (bottom-nibble), incremented on feature additions -#define LFS1_DISK_VERSION 0x00010001 -#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16)) -#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >> 0)) - - -/// v1 Definitions /// - -// File types -enum lfs1_type { - LFS1_TYPE_REG = 0x11, - LFS1_TYPE_DIR = 0x22, - LFS1_TYPE_SUPERBLOCK = 0x2e, -}; - -typedef struct lfs1 { - lfs_block_t root[2]; -} lfs1_t; - -typedef struct lfs1_entry { - lfs_off_t off; - - struct lfs1_disk_entry { - uint8_t type; - uint8_t elen; - uint8_t alen; - uint8_t nlen; - union { - struct { - lfs_block_t head; - lfs_size_t size; - } file; - lfs_block_t dir[2]; - } u; - } d; -} lfs1_entry_t; - -typedef struct lfs1_dir { - struct lfs1_dir *next; - lfs_block_t pair[2]; - lfs_off_t off; - - lfs_block_t head[2]; - lfs_off_t pos; - - struct lfs1_disk_dir { - uint32_t rev; - lfs_size_t size; - lfs_block_t tail[2]; - } d; -} lfs1_dir_t; - -typedef struct lfs1_superblock { - lfs_off_t off; - - struct lfs1_disk_superblock { - uint8_t type; - uint8_t elen; - uint8_t alen; - uint8_t nlen; - lfs_block_t root[2]; - uint32_t block_size; - uint32_t block_count; - uint32_t version; - char magic[8]; - } d; -} lfs1_superblock_t; - - -/// Low-level wrappers v1->v2 /// -static void lfs1_crc(uint32_t *crc, const void *buffer, size_t size) { - *crc = lfs_crc(*crc, buffer, size); -} - -static int lfs1_bd_read(lfs_t *lfs, lfs_block_t block, - lfs_off_t off, void *buffer, lfs_size_t size) { - // if we ever do more than writes to alternating pairs, - // this may need to consider pcache - return lfs_bd_read(lfs, &lfs->pcache, &lfs->rcache, size, - block, off, buffer, size); -} - -static int lfs1_bd_crc(lfs_t *lfs, lfs_block_t block, - lfs_off_t off, lfs_size_t size, uint32_t *crc) { - for (lfs_off_t i = 0; i < size; i++) { - uint8_t c; - int err = lfs1_bd_read(lfs, block, off+i, &c, 1); - if (err) { - return err; - } - - lfs1_crc(crc, &c, 1); - } - - return 0; -} - - -/// Endian swapping functions /// -static void lfs1_dir_fromle32(struct lfs1_disk_dir *d) { - d->rev = lfs_fromle32(d->rev); - d->size = lfs_fromle32(d->size); - d->tail[0] = lfs_fromle32(d->tail[0]); - d->tail[1] = lfs_fromle32(d->tail[1]); -} - -static void lfs1_dir_tole32(struct lfs1_disk_dir *d) { - d->rev = lfs_tole32(d->rev); - d->size = lfs_tole32(d->size); - d->tail[0] = lfs_tole32(d->tail[0]); - d->tail[1] = lfs_tole32(d->tail[1]); -} - -static void lfs1_entry_fromle32(struct lfs1_disk_entry *d) { - d->u.dir[0] = lfs_fromle32(d->u.dir[0]); - d->u.dir[1] = lfs_fromle32(d->u.dir[1]); -} - -static void lfs1_entry_tole32(struct lfs1_disk_entry *d) { - d->u.dir[0] = lfs_tole32(d->u.dir[0]); - d->u.dir[1] = lfs_tole32(d->u.dir[1]); -} - -static void lfs1_superblock_fromle32(struct lfs1_disk_superblock *d) { - d->root[0] = lfs_fromle32(d->root[0]); - d->root[1] = lfs_fromle32(d->root[1]); - d->block_size = lfs_fromle32(d->block_size); - d->block_count = lfs_fromle32(d->block_count); - d->version = lfs_fromle32(d->version); -} - - -///// Metadata pair and directory operations /// -static inline lfs_size_t lfs1_entry_size(const lfs1_entry_t *entry) { - return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; -} - -static int lfs1_dir_fetch(lfs_t *lfs, - lfs1_dir_t *dir, const lfs_block_t pair[2]) { - // copy out pair, otherwise may be aliasing dir - const lfs_block_t tpair[2] = {pair[0], pair[1]}; - bool valid = false; - - // check both blocks for the most recent revision - for (int i = 0; i < 2; i++) { - struct lfs1_disk_dir test; - int err = lfs1_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); - lfs1_dir_fromle32(&test); - if (err) { - if (err == LFS_ERR_CORRUPT) { - continue; - } - return err; - } - - if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { - continue; - } - - if ((0x7fffffff & test.size) < sizeof(test)+4 || - (0x7fffffff & test.size) > lfs->cfg->block_size) { - continue; - } - - uint32_t crc = 0xffffffff; - lfs1_dir_tole32(&test); - lfs1_crc(&crc, &test, sizeof(test)); - lfs1_dir_fromle32(&test); - err = lfs1_bd_crc(lfs, tpair[i], sizeof(test), - (0x7fffffff & test.size) - sizeof(test), &crc); - if (err) { - if (err == LFS_ERR_CORRUPT) { - continue; - } - return err; - } - - if (crc != 0) { - continue; - } - - valid = true; - - // setup dir in case it's valid - dir->pair[0] = tpair[(i+0) % 2]; - dir->pair[1] = tpair[(i+1) % 2]; - dir->off = sizeof(dir->d); - dir->d = test; - } - - if (!valid) { - LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", - tpair[0], tpair[1]); - return LFS_ERR_CORRUPT; - } - - return 0; -} - -static int lfs1_dir_next(lfs_t *lfs, lfs1_dir_t *dir, lfs1_entry_t *entry) { - while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { - if (!(0x80000000 & dir->d.size)) { - entry->off = dir->off; - return LFS_ERR_NOENT; - } - - int err = lfs1_dir_fetch(lfs, dir, dir->d.tail); - if (err) { - return err; - } - - dir->off = sizeof(dir->d); - dir->pos += sizeof(dir->d) + 4; - } - - int err = lfs1_bd_read(lfs, dir->pair[0], dir->off, - &entry->d, sizeof(entry->d)); - lfs1_entry_fromle32(&entry->d); - if (err) { - return err; - } - - entry->off = dir->off; - dir->off += lfs1_entry_size(entry); - dir->pos += lfs1_entry_size(entry); - return 0; -} - -/// littlefs v1 specific operations /// -int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { - if (lfs_pair_isnull(lfs->lfs1->root)) { - return 0; - } - - // iterate over metadata pairs - lfs1_dir_t dir; - lfs1_entry_t entry; - lfs_block_t cwd[2] = {0, 1}; - - while (true) { - for (int i = 0; i < 2; i++) { - int err = cb(data, cwd[i]); - if (err) { - return err; - } - } - - int err = lfs1_dir_fetch(lfs, &dir, cwd); - if (err) { - return err; - } - - // iterate over contents - while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { - err = lfs1_bd_read(lfs, dir.pair[0], dir.off, - &entry.d, sizeof(entry.d)); - lfs1_entry_fromle32(&entry.d); - if (err) { - return err; - } - - dir.off += lfs1_entry_size(&entry); - if ((0x70 & entry.d.type) == (0x70 & LFS1_TYPE_REG)) { - err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, - entry.d.u.file.head, entry.d.u.file.size, cb, data); - if (err) { - return err; - } - } - } - - // we also need to check if we contain a threaded v2 directory - lfs_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}}; - while (dir2.split) { - err = lfs_dir_fetch(lfs, &dir2, dir2.tail); - if (err) { - break; - } - - for (int i = 0; i < 2; i++) { - err = cb(data, dir2.pair[i]); - if (err) { - return err; - } - } - } - - cwd[0] = dir.d.tail[0]; - cwd[1] = dir.d.tail[1]; - - if (lfs_pair_isnull(cwd)) { - break; - } - } - - return 0; -} - -static int lfs1_moved(lfs_t *lfs, const void *e) { - if (lfs_pair_isnull(lfs->lfs1->root)) { - return 0; - } - - // skip superblock - lfs1_dir_t cwd; - int err = lfs1_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); - if (err) { - return err; - } - - // iterate over all directory directory entries - lfs1_entry_t entry; - while (!lfs_pair_isnull(cwd.d.tail)) { - err = lfs1_dir_fetch(lfs, &cwd, cwd.d.tail); - if (err) { - return err; - } - - while (true) { - err = lfs1_dir_next(lfs, &cwd, &entry); - if (err && err != LFS_ERR_NOENT) { - return err; - } - - if (err == LFS_ERR_NOENT) { - break; - } - - if (!(0x80 & entry.d.type) && - memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { - return true; - } - } - } - - return false; -} - -/// Filesystem operations /// -static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, - const struct lfs_config *cfg) { - int err = 0; - { - err = lfs_init(lfs, cfg); - if (err) { - return err; - } - - lfs->lfs1 = lfs1; - lfs->lfs1->root[0] = LFS_BLOCK_NULL; - lfs->lfs1->root[1] = LFS_BLOCK_NULL; - - // setup free lookahead - lfs->lookahead.start = 0; - lfs->lookahead.size = 0; - lfs->lookahead.next = 0; - lfs_alloc_ckpoint(lfs); - - // load superblock - lfs1_dir_t dir; - lfs1_superblock_t superblock; - err = lfs1_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); - if (err && err != LFS_ERR_CORRUPT) { - goto cleanup; - } - - if (!err) { - err = lfs1_bd_read(lfs, dir.pair[0], sizeof(dir.d), - &superblock.d, sizeof(superblock.d)); - lfs1_superblock_fromle32(&superblock.d); - if (err) { - goto cleanup; - } - - lfs->lfs1->root[0] = superblock.d.root[0]; - lfs->lfs1->root[1] = superblock.d.root[1]; - } - - if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { - LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", - 0, 1); - err = LFS_ERR_CORRUPT; - goto cleanup; - } - - uint16_t major_version = (0xffff & (superblock.d.version >> 16)); - uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); - if ((major_version != LFS1_DISK_VERSION_MAJOR || - minor_version > LFS1_DISK_VERSION_MINOR)) { - LFS_ERROR("Invalid version v%d.%d", major_version, minor_version); - err = LFS_ERR_INVAL; - goto cleanup; - } - - return 0; - } - -cleanup: - lfs_deinit(lfs); - return err; -} - -static int lfs1_unmount(lfs_t *lfs) { - return lfs_deinit(lfs); -} - -/// v1 migration /// -static int lfs_migrate_(lfs_t *lfs, const struct lfs_config *cfg) { - struct lfs1 lfs1; - - // Indeterminate filesystem size not allowed for migration. - LFS_ASSERT(cfg->block_count != 0); - - int err = lfs1_mount(lfs, &lfs1, cfg); - if (err) { - return err; - } - - { - // iterate through each directory, copying over entries - // into new directory - lfs1_dir_t dir1; - lfs_mdir_t dir2; - dir1.d.tail[0] = lfs->lfs1->root[0]; - dir1.d.tail[1] = lfs->lfs1->root[1]; - while (!lfs_pair_isnull(dir1.d.tail)) { - // iterate old dir - err = lfs1_dir_fetch(lfs, &dir1, dir1.d.tail); - if (err) { - goto cleanup; - } - - // create new dir and bind as temporary pretend root - err = lfs_dir_alloc(lfs, &dir2); - if (err) { - goto cleanup; - } - - dir2.rev = dir1.d.rev; - dir1.head[0] = dir1.pair[0]; - dir1.head[1] = dir1.pair[1]; - lfs->root[0] = dir2.pair[0]; - lfs->root[1] = dir2.pair[1]; - - err = lfs_dir_commit(lfs, &dir2, NULL, 0); - if (err) { - goto cleanup; - } - - while (true) { - lfs1_entry_t entry1; - err = lfs1_dir_next(lfs, &dir1, &entry1); - if (err && err != LFS_ERR_NOENT) { - goto cleanup; - } - - if (err == LFS_ERR_NOENT) { - break; - } - - // check that entry has not been moved - if (entry1.d.type & 0x80) { - int moved = lfs1_moved(lfs, &entry1.d.u); - if (moved < 0) { - err = moved; - goto cleanup; - } - - if (moved) { - continue; - } - - entry1.d.type &= ~0x80; - } - - // also fetch name - char name[LFS_NAME_MAX+1]; - memset(name, 0, sizeof(name)); - err = lfs1_bd_read(lfs, dir1.pair[0], - entry1.off + 4+entry1.d.elen+entry1.d.alen, - name, entry1.d.nlen); - if (err) { - goto cleanup; - } - - bool isdir = (entry1.d.type == LFS1_TYPE_DIR); - - // create entry in new dir - err = lfs_dir_fetch(lfs, &dir2, lfs->root); - if (err) { - goto cleanup; - } - - uint16_t id; - err = lfs_dir_find(lfs, &dir2, &(const char*){name}, &id); - if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { - err = (err < 0) ? err : LFS_ERR_EXIST; - goto cleanup; - } - - lfs1_entry_tole32(&entry1.d); - err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, - {LFS_MKTAG_IF_ELSE(isdir, - LFS_TYPE_DIR, id, entry1.d.nlen, - LFS_TYPE_REG, id, entry1.d.nlen), - name}, - {LFS_MKTAG_IF_ELSE(isdir, - LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), - LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), - &entry1.d.u})); - lfs1_entry_fromle32(&entry1.d); - if (err) { - goto cleanup; - } - } - - if (!lfs_pair_isnull(dir1.d.tail)) { - // find last block and update tail to thread into fs - err = lfs_dir_fetch(lfs, &dir2, lfs->root); - if (err) { - goto cleanup; - } - - while (dir2.split) { - err = lfs_dir_fetch(lfs, &dir2, dir2.tail); - if (err) { - goto cleanup; - } - } - - lfs_pair_tole32(dir2.pair); - err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); - lfs_pair_fromle32(dir2.pair); - if (err) { - goto cleanup; - } - } - - // Copy over first block to thread into fs. Unfortunately - // if this fails there is not much we can do. - LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " - "-> {0x%"PRIx32", 0x%"PRIx32"}", - lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]); - - err = lfs_bd_erase(lfs, dir1.head[1]); - if (err) { - goto cleanup; - } - - err = lfs_dir_fetch(lfs, &dir2, lfs->root); - if (err) { - goto cleanup; - } - - for (lfs_off_t i = 0; i < dir2.off; i++) { - uint8_t dat; - err = lfs_bd_read(lfs, - NULL, &lfs->rcache, dir2.off, - dir2.pair[0], i, &dat, 1); - if (err) { - goto cleanup; - } - - err = lfs_bd_prog(lfs, - &lfs->pcache, &lfs->rcache, true, - dir1.head[1], i, &dat, 1); - if (err) { - goto cleanup; - } - } - - err = lfs_bd_flush(lfs, &lfs->pcache, &lfs->rcache, true); - if (err) { - goto cleanup; - } - } - - // Create new superblock. This marks a successful migration! - err = lfs1_dir_fetch(lfs, &dir1, (const lfs_block_t[2]){0, 1}); - if (err) { - goto cleanup; - } - - dir2.pair[0] = dir1.pair[0]; - dir2.pair[1] = dir1.pair[1]; - dir2.rev = dir1.d.rev; - dir2.off = sizeof(dir2.rev); - dir2.etag = 0xffffffff; - dir2.count = 0; - dir2.tail[0] = lfs->lfs1->root[0]; - dir2.tail[1] = lfs->lfs1->root[1]; - dir2.erased = false; - dir2.split = true; - - lfs_superblock_t superblock = { - .version = LFS_DISK_VERSION, - .block_size = lfs->cfg->block_size, - .block_count = lfs->cfg->block_count, - .name_max = lfs->name_max, - .file_max = lfs->file_max, - .attr_max = lfs->attr_max, - }; - - lfs_superblock_tole32(&superblock); - err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( - {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, - {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, - {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock})); - if (err) { - goto cleanup; - } - - // sanity check that fetch works - err = lfs_dir_fetch(lfs, &dir2, (const lfs_block_t[2]){0, 1}); - if (err) { - goto cleanup; - } - - // force compaction to prevent accidentally mounting v1 - dir2.erased = false; - err = lfs_dir_commit(lfs, &dir2, NULL, 0); - if (err) { - goto cleanup; - } - } - -cleanup: - lfs1_unmount(lfs); - return err; -} - -#endif - - -/// Public API wrappers /// - -// Here we can add tracing/thread safety easily - -// Thread-safe wrappers if enabled -#ifdef LFS_THREADSAFE -#define LFS_LOCK(cfg) cfg->lock(cfg) -#define LFS_UNLOCK(cfg) cfg->unlock(cfg) -#else -#define LFS_LOCK(cfg) ((void)cfg, 0) -#define LFS_UNLOCK(cfg) ((void)cfg) -#endif - -// Public API -#ifndef LFS_READONLY -int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { - int err = LFS_LOCK(cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_format(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); - - err = lfs_format_(lfs, cfg); - - LFS_TRACE("lfs_format -> %d", err); - LFS_UNLOCK(cfg); - return err; -} -#endif - -int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { - int err = LFS_LOCK(cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_mount(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); - - err = lfs_mount_(lfs, cfg); - - LFS_TRACE("lfs_mount -> %d", err); - LFS_UNLOCK(cfg); - return err; -} - -int lfs_unmount(lfs_t *lfs) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_unmount(%p)", (void*)lfs); - - err = lfs_unmount_(lfs); - - LFS_TRACE("lfs_unmount -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -#ifndef LFS_READONLY -int lfs_remove(lfs_t *lfs, const char *path) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); - - err = lfs_remove_(lfs, path); - - LFS_TRACE("lfs_remove -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -#ifndef LFS_READONLY -int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); - - err = lfs_rename_(lfs, oldpath, newpath); - - LFS_TRACE("lfs_rename -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); - - err = lfs_stat_(lfs, path, info); - - LFS_TRACE("lfs_stat -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, - uint8_t type, void *buffer, lfs_size_t size) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", - (void*)lfs, path, type, buffer, size); - - lfs_ssize_t res = lfs_getattr_(lfs, path, type, buffer, size); - - LFS_TRACE("lfs_getattr -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} - -#ifndef LFS_READONLY -int lfs_setattr(lfs_t *lfs, const char *path, - uint8_t type, const void *buffer, lfs_size_t size) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", - (void*)lfs, path, type, buffer, size); - - err = lfs_setattr_(lfs, path, type, buffer, size); - - LFS_TRACE("lfs_setattr -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -#ifndef LFS_READONLY -int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); - - err = lfs_removeattr_(lfs, path, type); - - LFS_TRACE("lfs_removeattr -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -#ifndef LFS_NO_MALLOC -int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", - (void*)lfs, (void*)file, path, flags); - LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - err = lfs_file_open_(lfs, file, path, flags); - - LFS_TRACE("lfs_file_open -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, - const char *path, int flags, - const struct lfs_file_config *cfg) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {" - ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", - (void*)lfs, (void*)file, path, flags, - (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); - LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - err = lfs_file_opencfg_(lfs, file, path, flags, cfg); - - LFS_TRACE("lfs_file_opencfg -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - err = lfs_file_close_(lfs, file); - - LFS_TRACE("lfs_file_close -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -#ifndef LFS_READONLY -int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - err = lfs_file_sync_(lfs, file); - - LFS_TRACE("lfs_file_sync -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, - void *buffer, lfs_size_t size) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", - (void*)lfs, (void*)file, buffer, size); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - lfs_ssize_t res = lfs_file_read_(lfs, file, buffer, size); - - LFS_TRACE("lfs_file_read -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} - -#ifndef LFS_READONLY -lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, - const void *buffer, lfs_size_t size) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", - (void*)lfs, (void*)file, buffer, size); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - lfs_ssize_t res = lfs_file_write_(lfs, file, buffer, size); - - LFS_TRACE("lfs_file_write -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} -#endif - -lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, - lfs_soff_t off, int whence) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", - (void*)lfs, (void*)file, off, whence); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - lfs_soff_t res = lfs_file_seek_(lfs, file, off, whence); - - LFS_TRACE("lfs_file_seek -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} - -#ifndef LFS_READONLY -int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", - (void*)lfs, (void*)file, size); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - err = lfs_file_truncate_(lfs, file, size); - - LFS_TRACE("lfs_file_truncate -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - lfs_soff_t res = lfs_file_tell_(lfs, file); - - LFS_TRACE("lfs_file_tell -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} - -int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); - - err = lfs_file_rewind_(lfs, file); - - LFS_TRACE("lfs_file_rewind -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); - - lfs_soff_t res = lfs_file_size_(lfs, file); - - LFS_TRACE("lfs_file_size -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} - -#ifndef LFS_READONLY -int lfs_mkdir(lfs_t *lfs, const char *path) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); - - err = lfs_mkdir_(lfs, path); - - LFS_TRACE("lfs_mkdir -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); - LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)dir)); - - err = lfs_dir_open_(lfs, dir, path); - - LFS_TRACE("lfs_dir_open -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); - - err = lfs_dir_close_(lfs, dir); - - LFS_TRACE("lfs_dir_close -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_dir_read(%p, %p, %p)", - (void*)lfs, (void*)dir, (void*)info); - - err = lfs_dir_read_(lfs, dir, info); - - LFS_TRACE("lfs_dir_read -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", - (void*)lfs, (void*)dir, off); - - err = lfs_dir_seek_(lfs, dir, off); - - LFS_TRACE("lfs_dir_seek -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); - - lfs_soff_t res = lfs_dir_tell_(lfs, dir); - - LFS_TRACE("lfs_dir_tell -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} - -int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); - - err = lfs_dir_rewind_(lfs, dir); - - LFS_TRACE("lfs_dir_rewind -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo); - - err = lfs_fs_stat_(lfs, fsinfo); - - LFS_TRACE("lfs_fs_stat -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -lfs_ssize_t lfs_fs_size(lfs_t *lfs) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); - - lfs_ssize_t res = lfs_fs_size_(lfs); - - LFS_TRACE("lfs_fs_size -> %"PRId32, res); - LFS_UNLOCK(lfs->cfg); - return res; -} - -int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", - (void*)lfs, (void*)(uintptr_t)cb, data); - - err = lfs_fs_traverse_(lfs, cb, data, true); - - LFS_TRACE("lfs_fs_traverse -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} - -#ifndef LFS_READONLY -int lfs_fs_mkconsistent(lfs_t *lfs) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs); - - err = lfs_fs_mkconsistent_(lfs); - - LFS_TRACE("lfs_fs_mkconsistent -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -#ifndef LFS_READONLY -int lfs_fs_gc(lfs_t *lfs) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs); - - err = lfs_fs_gc_(lfs); - - LFS_TRACE("lfs_fs_gc -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -#ifndef LFS_READONLY -int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) { - int err = LFS_LOCK(lfs->cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_fs_grow(%p, %"PRIu32")", (void*)lfs, block_count); - - err = lfs_fs_grow_(lfs, block_count); - - LFS_TRACE("lfs_fs_grow -> %d", err); - LFS_UNLOCK(lfs->cfg); - return err; -} -#endif - -#ifdef LFS_MIGRATE -int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { - int err = LFS_LOCK(cfg); - if (err) { - return err; - } - LFS_TRACE("lfs_migrate(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); - - err = lfs_migrate_(lfs, cfg); - - LFS_TRACE("lfs_migrate -> %d", err); - LFS_UNLOCK(cfg); - return err; -} -#endif - diff --git a/src/lib/littlefs/lfs.h b/src/lib/littlefs/lfs.h deleted file mode 100644 index 9914502..0000000 --- a/src/lib/littlefs/lfs.h +++ /dev/null @@ -1,795 +0,0 @@ -/* - * The little filesystem - * - * Copyright (c) 2022, The littlefs authors. - * Copyright (c) 2017, Arm Limited. All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - */ -#ifndef LFS_H -#define LFS_H - -#include "lfs_util.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - -/// Version info /// - -// Software library version -// Major (top-nibble), incremented on backwards incompatible changes -// Minor (bottom-nibble), incremented on feature additions -#define LFS_VERSION 0x00020009 -#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) -#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) - -// Version of On-disk data structures -// Major (top-nibble), incremented on backwards incompatible changes -// Minor (bottom-nibble), incremented on feature additions -#define LFS_DISK_VERSION 0x00020001 -#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) -#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) - - -/// Definitions /// - -// Type definitions -typedef uint32_t lfs_size_t; -typedef uint32_t lfs_off_t; - -typedef int32_t lfs_ssize_t; -typedef int32_t lfs_soff_t; - -typedef uint32_t lfs_block_t; - -// Maximum name size in bytes, may be redefined to reduce the size of the -// info struct. Limited to <= 1022. Stored in superblock and must be -// respected by other littlefs drivers. -#ifndef LFS_NAME_MAX -#define LFS_NAME_MAX 255 -#endif - -// Maximum size of a file in bytes, may be redefined to limit to support other -// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be -// respected by other littlefs drivers. -#ifndef LFS_FILE_MAX -#define LFS_FILE_MAX 2147483647 -#endif - -// Maximum size of custom attributes in bytes, may be redefined, but there is -// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. -#ifndef LFS_ATTR_MAX -#define LFS_ATTR_MAX 1022 -#endif - -// Possible error codes, these are negative to allow -// valid positive return values -enum lfs_error { - LFS_ERR_OK = 0, // No error - LFS_ERR_IO = -5, // Error during device operation - LFS_ERR_CORRUPT = -84, // Corrupted - LFS_ERR_NOENT = -2, // No directory entry - LFS_ERR_EXIST = -17, // Entry already exists - LFS_ERR_NOTDIR = -20, // Entry is not a dir - LFS_ERR_ISDIR = -21, // Entry is a dir - LFS_ERR_NOTEMPTY = -39, // Dir is not empty - LFS_ERR_BADF = -9, // Bad file number - LFS_ERR_FBIG = -27, // File too large - LFS_ERR_INVAL = -22, // Invalid parameter - LFS_ERR_NOSPC = -28, // No space left on device - LFS_ERR_NOMEM = -12, // No more memory available - LFS_ERR_NOATTR = -61, // No data/attr available - LFS_ERR_NAMETOOLONG = -36, // File name too long -}; - -// File types -enum lfs_type { - // file types - LFS_TYPE_REG = 0x001, - LFS_TYPE_DIR = 0x002, - - // internally used types - LFS_TYPE_SPLICE = 0x400, - LFS_TYPE_NAME = 0x000, - LFS_TYPE_STRUCT = 0x200, - LFS_TYPE_USERATTR = 0x300, - LFS_TYPE_FROM = 0x100, - LFS_TYPE_TAIL = 0x600, - LFS_TYPE_GLOBALS = 0x700, - LFS_TYPE_CRC = 0x500, - - // internally used type specializations - LFS_TYPE_CREATE = 0x401, - LFS_TYPE_DELETE = 0x4ff, - LFS_TYPE_SUPERBLOCK = 0x0ff, - LFS_TYPE_DIRSTRUCT = 0x200, - LFS_TYPE_CTZSTRUCT = 0x202, - LFS_TYPE_INLINESTRUCT = 0x201, - LFS_TYPE_SOFTTAIL = 0x600, - LFS_TYPE_HARDTAIL = 0x601, - LFS_TYPE_MOVESTATE = 0x7ff, - LFS_TYPE_CCRC = 0x500, - LFS_TYPE_FCRC = 0x5ff, - - // internal chip sources - LFS_FROM_NOOP = 0x000, - LFS_FROM_MOVE = 0x101, - LFS_FROM_USERATTRS = 0x102, -}; - -// File open flags -enum lfs_open_flags { - // open flags - LFS_O_RDONLY = 1, // Open a file as read only -#ifndef LFS_READONLY - LFS_O_WRONLY = 2, // Open a file as write only - LFS_O_RDWR = 3, // Open a file as read and write - LFS_O_CREAT = 0x0100, // Create a file if it does not exist - LFS_O_EXCL = 0x0200, // Fail if a file already exists - LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size - LFS_O_APPEND = 0x0800, // Move to end of file on every write -#endif - - // internally used flags -#ifndef LFS_READONLY - LFS_F_DIRTY = 0x010000, // File does not match storage - LFS_F_WRITING = 0x020000, // File has been written since last flush -#endif - LFS_F_READING = 0x040000, // File has been read since last flush -#ifndef LFS_READONLY - LFS_F_ERRED = 0x080000, // An error occurred during write -#endif - LFS_F_INLINE = 0x100000, // Currently inlined in directory entry -}; - -// File seek flags -enum lfs_whence_flags { - LFS_SEEK_SET = 0, // Seek relative to an absolute position - LFS_SEEK_CUR = 1, // Seek relative to the current file position - LFS_SEEK_END = 2, // Seek relative to the end of the file -}; - - -// Configuration provided during initialization of the littlefs -struct lfs_config { - // Opaque user provided context that can be used to pass - // information to the block device operations - void *context; - - // Read a region in a block. Negative error codes are propagated - // to the user. - int (*read)(const struct lfs_config *c, lfs_block_t block, - lfs_off_t off, void *buffer, lfs_size_t size); - - // Program a region in a block. The block must have previously - // been erased. Negative error codes are propagated to the user. - // May return LFS_ERR_CORRUPT if the block should be considered bad. - int (*prog)(const struct lfs_config *c, lfs_block_t block, - lfs_off_t off, const void *buffer, lfs_size_t size); - - // Erase a block. A block must be erased before being programmed. - // The state of an erased block is undefined. Negative error codes - // are propagated to the user. - // May return LFS_ERR_CORRUPT if the block should be considered bad. - int (*erase)(const struct lfs_config *c, lfs_block_t block); - - // Sync the state of the underlying block device. Negative error codes - // are propagated to the user. - int (*sync)(const struct lfs_config *c); - -#ifdef LFS_THREADSAFE - // Lock the underlying block device. Negative error codes - // are propagated to the user. - int (*lock)(const struct lfs_config *c); - - // Unlock the underlying block device. Negative error codes - // are propagated to the user. - int (*unlock)(const struct lfs_config *c); -#endif - - // Minimum size of a block read in bytes. All read operations will be a - // multiple of this value. - lfs_size_t read_size; - - // Minimum size of a block program in bytes. All program operations will be - // a multiple of this value. - lfs_size_t prog_size; - - // Size of an erasable block in bytes. This does not impact ram consumption - // and may be larger than the physical erase size. However, non-inlined - // files take up at minimum one block. Must be a multiple of the read and - // program sizes. - lfs_size_t block_size; - - // Number of erasable blocks on the device. - lfs_size_t block_count; - - // Number of erase cycles before littlefs evicts metadata logs and moves - // the metadata to another block. Suggested values are in the - // range 100-1000, with large values having better performance at the cost - // of less consistent wear distribution. - // - // Set to -1 to disable block-level wear-leveling. - int32_t block_cycles; - - // Size of block caches in bytes. Each cache buffers a portion of a block in - // RAM. The littlefs needs a read cache, a program cache, and one additional - // cache per file. Larger caches can improve performance by storing more - // data and reducing the number of disk accesses. Must be a multiple of the - // read and program sizes, and a factor of the block size. - lfs_size_t cache_size; - - // Size of the lookahead buffer in bytes. A larger lookahead buffer - // increases the number of blocks found during an allocation pass. The - // lookahead buffer is stored as a compact bitmap, so each byte of RAM - // can track 8 blocks. - lfs_size_t lookahead_size; - - // Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata - // pairs that exceed this threshold will be compacted during lfs_fs_gc. - // Defaults to ~88% block_size when zero, though the default may change - // in the future. - // - // Note this only affects lfs_fs_gc. Normal compactions still only occur - // when full. - // - // Set to -1 to disable metadata compaction during lfs_fs_gc. - lfs_size_t compact_thresh; - - // Optional statically allocated read buffer. Must be cache_size. - // By default lfs_malloc is used to allocate this buffer. - void *read_buffer; - - // Optional statically allocated program buffer. Must be cache_size. - // By default lfs_malloc is used to allocate this buffer. - void *prog_buffer; - - // Optional statically allocated lookahead buffer. Must be lookahead_size. - // By default lfs_malloc is used to allocate this buffer. - void *lookahead_buffer; - - // Optional upper limit on length of file names in bytes. No downside for - // larger names except the size of the info struct which is controlled by - // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in - // superblock and must be respected by other littlefs drivers. - lfs_size_t name_max; - - // Optional upper limit on files in bytes. No downside for larger files - // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored - // in superblock and must be respected by other littlefs drivers. - lfs_size_t file_max; - - // Optional upper limit on custom attributes in bytes. No downside for - // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to - // LFS_ATTR_MAX when zero. - lfs_size_t attr_max; - - // Optional upper limit on total space given to metadata pairs in bytes. On - // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) - // can help bound the metadata compaction time. Must be <= block_size. - // Defaults to block_size when zero. - lfs_size_t metadata_max; - - // Optional upper limit on inlined files in bytes. Inlined files live in - // metadata and decrease storage requirements, but may be limited to - // improve metadata-related performance. Must be <= cache_size, <= - // attr_max, and <= block_size/8. Defaults to the largest possible - // inline_max when zero. - // - // Set to -1 to disable inlined files. - lfs_size_t inline_max; - -#ifdef LFS_MULTIVERSION - // On-disk version to use when writing in the form of 16-bit major version - // + 16-bit minor version. This limiting metadata to what is supported by - // older minor versions. Note that some features will be lost. Defaults to - // to the most recent minor version when zero. - uint32_t disk_version; -#endif -}; - -// File info structure -struct lfs_info { - // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR - uint8_t type; - - // Size of the file, only valid for REG files. Limited to 32-bits. - lfs_size_t size; - - // Name of the file stored as a null-terminated string. Limited to - // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to - // reduce RAM. LFS_NAME_MAX is stored in superblock and must be - // respected by other littlefs drivers. - char name[LFS_NAME_MAX+1]; -}; - -// Filesystem info structure -struct lfs_fsinfo { - // On-disk version. - uint32_t disk_version; - - // Size of a logical block in bytes. - lfs_size_t block_size; - - // Number of logical blocks in filesystem. - lfs_size_t block_count; - - // Upper limit on the length of file names in bytes. - lfs_size_t name_max; - - // Upper limit on the size of files in bytes. - lfs_size_t file_max; - - // Upper limit on the size of custom attributes in bytes. - lfs_size_t attr_max; -}; - -// Custom attribute structure, used to describe custom attributes -// committed atomically during file writes. -struct lfs_attr { - // 8-bit type of attribute, provided by user and used to - // identify the attribute - uint8_t type; - - // Pointer to buffer containing the attribute - void *buffer; - - // Size of attribute in bytes, limited to LFS_ATTR_MAX - lfs_size_t size; -}; - -// Optional configuration provided during lfs_file_opencfg -struct lfs_file_config { - // Optional statically allocated file buffer. Must be cache_size. - // By default lfs_malloc is used to allocate this buffer. - void *buffer; - - // Optional list of custom attributes related to the file. If the file - // is opened with read access, these attributes will be read from disk - // during the open call. If the file is opened with write access, the - // attributes will be written to disk every file sync or close. This - // write occurs atomically with update to the file's contents. - // - // Custom attributes are uniquely identified by an 8-bit type and limited - // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller - // than the buffer, it will be padded with zeros. If the stored attribute - // is larger, then it will be silently truncated. If the attribute is not - // found, it will be created implicitly. - struct lfs_attr *attrs; - - // Number of custom attributes in the list - lfs_size_t attr_count; -}; - - -/// internal littlefs data structures /// -typedef struct lfs_cache { - lfs_block_t block; - lfs_off_t off; - lfs_size_t size; - uint8_t *buffer; -} lfs_cache_t; - -typedef struct lfs_mdir { - lfs_block_t pair[2]; - uint32_t rev; - lfs_off_t off; - uint32_t etag; - uint16_t count; - bool erased; - bool split; - lfs_block_t tail[2]; -} lfs_mdir_t; - -// littlefs directory type -typedef struct lfs_dir { - struct lfs_dir *next; - uint16_t id; - uint8_t type; - lfs_mdir_t m; - - lfs_off_t pos; - lfs_block_t head[2]; -} lfs_dir_t; - -// littlefs file type -typedef struct lfs_file { - struct lfs_file *next; - uint16_t id; - uint8_t type; - lfs_mdir_t m; - - struct lfs_ctz { - lfs_block_t head; - lfs_size_t size; - } ctz; - - uint32_t flags; - lfs_off_t pos; - lfs_block_t block; - lfs_off_t off; - lfs_cache_t cache; - - const struct lfs_file_config *cfg; -} lfs_file_t; - -typedef struct lfs_superblock { - uint32_t version; - lfs_size_t block_size; - lfs_size_t block_count; - lfs_size_t name_max; - lfs_size_t file_max; - lfs_size_t attr_max; -} lfs_superblock_t; - -typedef struct lfs_gstate { - uint32_t tag; - lfs_block_t pair[2]; -} lfs_gstate_t; - -// The littlefs filesystem type -typedef struct lfs { - lfs_cache_t rcache; - lfs_cache_t pcache; - - lfs_block_t root[2]; - struct lfs_mlist { - struct lfs_mlist *next; - uint16_t id; - uint8_t type; - lfs_mdir_t m; - } *mlist; - uint32_t seed; - - lfs_gstate_t gstate; - lfs_gstate_t gdisk; - lfs_gstate_t gdelta; - - struct lfs_lookahead { - lfs_block_t start; - lfs_block_t size; - lfs_block_t next; - lfs_block_t ckpoint; - uint8_t *buffer; - } lookahead; - - const struct lfs_config *cfg; - lfs_size_t block_count; - lfs_size_t name_max; - lfs_size_t file_max; - lfs_size_t attr_max; - lfs_size_t inline_max; - -#ifdef LFS_MIGRATE - struct lfs1 *lfs1; -#endif -} lfs_t; - - -/// Filesystem functions /// - -#ifndef LFS_READONLY -// Format a block device with the littlefs -// -// Requires a littlefs object and config struct. This clobbers the littlefs -// object, and does not leave the filesystem mounted. The config struct must -// be zeroed for defaults and backwards compatibility. -// -// Returns a negative error code on failure. -int lfs_format(lfs_t *lfs, const struct lfs_config *config); -#endif - -// Mounts a littlefs -// -// Requires a littlefs object and config struct. Multiple filesystems -// may be mounted simultaneously with multiple littlefs objects. Both -// lfs and config must be allocated while mounted. The config struct must -// be zeroed for defaults and backwards compatibility. -// -// Returns a negative error code on failure. -int lfs_mount(lfs_t *lfs, const struct lfs_config *config); - -// Unmounts a littlefs -// -// Does nothing besides releasing any allocated resources. -// Returns a negative error code on failure. -int lfs_unmount(lfs_t *lfs); - -/// General operations /// - -#ifndef LFS_READONLY -// Removes a file or directory -// -// If removing a directory, the directory must be empty. -// Returns a negative error code on failure. -int lfs_remove(lfs_t *lfs, const char *path); -#endif - -#ifndef LFS_READONLY -// Rename or move a file or directory -// -// If the destination exists, it must match the source in type. -// If the destination is a directory, the directory must be empty. -// -// Returns a negative error code on failure. -int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); -#endif - -// Find info about a file or directory -// -// Fills out the info structure, based on the specified file or directory. -// Returns a negative error code on failure. -int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); - -// Get a custom attribute -// -// Custom attributes are uniquely identified by an 8-bit type and limited -// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than -// the buffer, it will be padded with zeros. If the stored attribute is larger, -// then it will be silently truncated. If no attribute is found, the error -// LFS_ERR_NOATTR is returned and the buffer is filled with zeros. -// -// Returns the size of the attribute, or a negative error code on failure. -// Note, the returned size is the size of the attribute on disk, irrespective -// of the size of the buffer. This can be used to dynamically allocate a buffer -// or check for existence. -lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, - uint8_t type, void *buffer, lfs_size_t size); - -#ifndef LFS_READONLY -// Set custom attributes -// -// Custom attributes are uniquely identified by an 8-bit type and limited -// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be -// implicitly created. -// -// Returns a negative error code on failure. -int lfs_setattr(lfs_t *lfs, const char *path, - uint8_t type, const void *buffer, lfs_size_t size); -#endif - -#ifndef LFS_READONLY -// Removes a custom attribute -// -// If an attribute is not found, nothing happens. -// -// Returns a negative error code on failure. -int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); -#endif - - -/// File operations /// - -#ifndef LFS_NO_MALLOC -// Open a file -// -// The mode that the file is opened in is determined by the flags, which -// are values from the enum lfs_open_flags that are bitwise-ored together. -// -// Returns a negative error code on failure. -int lfs_file_open(lfs_t *lfs, lfs_file_t *file, - const char *path, int flags); - -// if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM -// thus use lfs_file_opencfg() with config.buffer set. -#endif - -// Open a file with extra configuration -// -// The mode that the file is opened in is determined by the flags, which -// are values from the enum lfs_open_flags that are bitwise-ored together. -// -// The config struct provides additional config options per file as described -// above. The config struct must remain allocated while the file is open, and -// the config struct must be zeroed for defaults and backwards compatibility. -// -// Returns a negative error code on failure. -int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, - const char *path, int flags, - const struct lfs_file_config *config); - -// Close a file -// -// Any pending writes are written out to storage as though -// sync had been called and releases any allocated resources. -// -// Returns a negative error code on failure. -int lfs_file_close(lfs_t *lfs, lfs_file_t *file); - -// Synchronize a file on storage -// -// Any pending writes are written out to storage. -// Returns a negative error code on failure. -int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); - -// Read data from file -// -// Takes a buffer and size indicating where to store the read data. -// Returns the number of bytes read, or a negative error code on failure. -lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, - void *buffer, lfs_size_t size); - -#ifndef LFS_READONLY -// Write data to file -// -// Takes a buffer and size indicating the data to write. The file will not -// actually be updated on the storage until either sync or close is called. -// -// Returns the number of bytes written, or a negative error code on failure. -lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, - const void *buffer, lfs_size_t size); -#endif - -// Change the position of the file -// -// The change in position is determined by the offset and whence flag. -// Returns the new position of the file, or a negative error code on failure. -lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, - lfs_soff_t off, int whence); - -#ifndef LFS_READONLY -// Truncates the size of the file to the specified size -// -// Returns a negative error code on failure. -int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); -#endif - -// Return the position of the file -// -// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) -// Returns the position of the file, or a negative error code on failure. -lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); - -// Change the position of the file to the beginning of the file -// -// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) -// Returns a negative error code on failure. -int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); - -// Return the size of the file -// -// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) -// Returns the size of the file, or a negative error code on failure. -lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); - - -/// Directory operations /// - -#ifndef LFS_READONLY -// Create a directory -// -// Returns a negative error code on failure. -int lfs_mkdir(lfs_t *lfs, const char *path); -#endif - -// Open a directory -// -// Once open a directory can be used with read to iterate over files. -// Returns a negative error code on failure. -int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); - -// Close a directory -// -// Releases any allocated resources. -// Returns a negative error code on failure. -int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); - -// Read an entry in the directory -// -// Fills out the info structure, based on the specified file or directory. -// Returns a positive value on success, 0 at the end of directory, -// or a negative error code on failure. -int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); - -// Change the position of the directory -// -// The new off must be a value previous returned from tell and specifies -// an absolute offset in the directory seek. -// -// Returns a negative error code on failure. -int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); - -// Return the position of the directory -// -// The returned offset is only meant to be consumed by seek and may not make -// sense, but does indicate the current position in the directory iteration. -// -// Returns the position of the directory, or a negative error code on failure. -lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); - -// Change the position of the directory to the beginning of the directory -// -// Returns a negative error code on failure. -int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); - - -/// Filesystem-level filesystem operations - -// Find on-disk info about the filesystem -// -// Fills out the fsinfo structure based on the filesystem found on-disk. -// Returns a negative error code on failure. -int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); - -// Finds the current size of the filesystem -// -// Note: Result is best effort. If files share COW structures, the returned -// size may be larger than the filesystem actually is. -// -// Returns the number of allocated blocks, or a negative error code on failure. -lfs_ssize_t lfs_fs_size(lfs_t *lfs); - -// Traverse through all blocks in use by the filesystem -// -// The provided callback will be called with each block address that is -// currently in use by the filesystem. This can be used to determine which -// blocks are in use or how much of the storage is available. -// -// Returns a negative error code on failure. -int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); - -#ifndef LFS_READONLY -// Attempt to make the filesystem consistent and ready for writing -// -// Calling this function is not required, consistency will be implicitly -// enforced on the first operation that writes to the filesystem, but this -// function allows the work to be performed earlier and without other -// filesystem changes. -// -// Returns a negative error code on failure. -int lfs_fs_mkconsistent(lfs_t *lfs); -#endif - -#ifndef LFS_READONLY -// Attempt any janitorial work -// -// This currently: -// 1. Calls mkconsistent if not already consistent -// 2. Compacts metadata > compact_thresh -// 3. Populates the block allocator -// -// Though additional janitorial work may be added in the future. -// -// Calling this function is not required, but may allow the offloading of -// expensive janitorial work to a less time-critical code path. -// -// Returns a negative error code on failure. Accomplishing nothing is not -// an error. -int lfs_fs_gc(lfs_t *lfs); -#endif - -#ifndef LFS_READONLY -// Grows the filesystem to a new size, updating the superblock with the new -// block count. -// -// Note: This is irreversible. -// -// Returns a negative error code on failure. -int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count); -#endif - -#ifndef LFS_READONLY -#ifdef LFS_MIGRATE -// Attempts to migrate a previous version of littlefs -// -// Behaves similarly to the lfs_format function. Attempts to mount -// the previous version of littlefs and update the filesystem so it can be -// mounted with the current version of littlefs. -// -// Requires a littlefs object and config struct. This clobbers the littlefs -// object, and does not leave the filesystem mounted. The config struct must -// be zeroed for defaults and backwards compatibility. -// -// Returns a negative error code on failure. -int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); -#endif -#endif - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/lib/littlefs/lfs_util.c b/src/lib/littlefs/lfs_util.c deleted file mode 100644 index dac72ab..0000000 --- a/src/lib/littlefs/lfs_util.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - * lfs util functions - * - * Copyright (c) 2022, The littlefs authors. - * Copyright (c) 2017, Arm Limited. All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - */ -#include "lfs_util.h" - -// Only compile if user does not provide custom config -#ifndef LFS_CONFIG - - -// If user provides their own CRC impl we don't need this -#ifndef LFS_CRC -// Software CRC implementation with small lookup table -uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { - static const uint32_t rtable[16] = { - 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, - 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, - 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, - }; - - const uint8_t *data = buffer; - - for (size_t i = 0; i < size; i++) { - crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; - crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; - } - - return crc; -} -#endif - - -#endif diff --git a/src/lib/littlefs/lfs_util.h b/src/lib/littlefs/lfs_util.h deleted file mode 100644 index 4e57700..0000000 --- a/src/lib/littlefs/lfs_util.h +++ /dev/null @@ -1,255 +0,0 @@ -/* - * lfs utility functions - * - * Copyright (c) 2022, The littlefs authors. - * Copyright (c) 2017, Arm Limited. All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - */ -#ifndef LFS_UTIL_H -#define LFS_UTIL_H - -// Users can override lfs_util.h with their own configuration by defining -// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). -// -// If LFS_CONFIG is used, none of the default utils will be emitted and must be -// provided by the config file. To start, I would suggest copying lfs_util.h -// and modifying as needed. -#ifdef LFS_CONFIG -#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) -#define LFS_STRINGIZE2(x) #x -#include LFS_STRINGIZE(LFS_CONFIG) -#else - -// System includes -#include -#include -#include -#include - -#ifndef LFS_NO_MALLOC -#include -#endif -#ifndef LFS_NO_ASSERT -#include -#endif -#if !defined(LFS_NO_DEBUG) || \ - !defined(LFS_NO_WARN) || \ - !defined(LFS_NO_ERROR) || \ - defined(LFS_YES_TRACE) -#include -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - - -// Macros, may be replaced by system specific wrappers. Arguments to these -// macros must not have side-effects as the macros can be removed for a smaller -// code footprint - -// Logging functions -#ifndef LFS_TRACE -#ifdef LFS_YES_TRACE -#define LFS_TRACE_(fmt, ...) \ - printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) -#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") -#else -#define LFS_TRACE(...) -#endif -#endif - -#ifndef LFS_DEBUG -#ifndef LFS_NO_DEBUG -#define LFS_DEBUG_(fmt, ...) \ - printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) -#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") -#else -#define LFS_DEBUG(...) -#endif -#endif - -#ifndef LFS_WARN -#ifndef LFS_NO_WARN -#define LFS_WARN_(fmt, ...) \ - printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) -#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") -#else -#define LFS_WARN(...) -#endif -#endif - -#ifndef LFS_ERROR -#ifndef LFS_NO_ERROR -#define LFS_ERROR_(fmt, ...) \ - printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) -#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") -#else -#define LFS_ERROR(...) -#endif -#endif - -// Runtime assertions -#ifndef LFS_ASSERT -#ifndef LFS_NO_ASSERT -#define LFS_ASSERT(test) assert(test) -#else -#define LFS_ASSERT(test) -#endif -#endif - - -// Builtin functions, these may be replaced by more efficient -// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more -// expensive basic C implementation for debugging purposes - -// Min/max functions for unsigned 32-bit numbers -static inline uint32_t lfs_max(uint32_t a, uint32_t b) { - return (a > b) ? a : b; -} - -static inline uint32_t lfs_min(uint32_t a, uint32_t b) { - return (a < b) ? a : b; -} - -// Align to nearest multiple of a size -static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { - return a - (a % alignment); -} - -static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { - return lfs_aligndown(a + alignment-1, alignment); -} - -// Find the smallest power of 2 greater than or equal to a -static inline uint32_t lfs_npw2(uint32_t a) { -#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) - return 32 - __builtin_clz(a-1); -#else - uint32_t r = 0; - uint32_t s; - a -= 1; - s = (a > 0xffff) << 4; a >>= s; r |= s; - s = (a > 0xff ) << 3; a >>= s; r |= s; - s = (a > 0xf ) << 2; a >>= s; r |= s; - s = (a > 0x3 ) << 1; a >>= s; r |= s; - return (r | (a >> 1)) + 1; -#endif -} - -// Count the number of trailing binary zeros in a -// lfs_ctz(0) may be undefined -static inline uint32_t lfs_ctz(uint32_t a) { -#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) - return __builtin_ctz(a); -#else - return lfs_npw2((a & -a) + 1) - 1; -#endif -} - -// Count the number of binary ones in a -static inline uint32_t lfs_popc(uint32_t a) { -#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) - return __builtin_popcount(a); -#else - a = a - ((a >> 1) & 0x55555555); - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); - return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; -#endif -} - -// Find the sequence comparison of a and b, this is the distance -// between a and b ignoring overflow -static inline int lfs_scmp(uint32_t a, uint32_t b) { - return (int)(unsigned)(a - b); -} - -// Convert between 32-bit little-endian and native order -static inline uint32_t lfs_fromle32(uint32_t a) { -#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - return a; -#elif !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) - return __builtin_bswap32(a); -#else - return (((uint8_t*)&a)[0] << 0) | - (((uint8_t*)&a)[1] << 8) | - (((uint8_t*)&a)[2] << 16) | - (((uint8_t*)&a)[3] << 24); -#endif -} - -static inline uint32_t lfs_tole32(uint32_t a) { - return lfs_fromle32(a); -} - -// Convert between 32-bit big-endian and native order -static inline uint32_t lfs_frombe32(uint32_t a) { -#if !defined(LFS_NO_INTRINSICS) && ( \ - (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) - return __builtin_bswap32(a); -#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ - (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) - return a; -#else - return (((uint8_t*)&a)[0] << 24) | - (((uint8_t*)&a)[1] << 16) | - (((uint8_t*)&a)[2] << 8) | - (((uint8_t*)&a)[3] << 0); -#endif -} - -static inline uint32_t lfs_tobe32(uint32_t a) { - return lfs_frombe32(a); -} - -// Calculate CRC-32 with polynomial = 0x04c11db7 -#ifdef LFS_CRC -uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { - return LFS_CRC(crc, buffer, size) -} -#else -uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); -#endif - -// Allocate memory, only used if buffers are not provided to littlefs -// -// littlefs current has no alignment requirements, as it only allocates -// byte-level buffers. -static inline void *lfs_malloc(size_t size) { -#if defined(LFS_MALLOC) - return LFS_MALLOC(size); -#elif !defined(LFS_NO_MALLOC) - return malloc(size); -#else - (void)size; - return NULL; -#endif -} - -// Deallocate memory, only used if buffers are not provided to littlefs -static inline void lfs_free(void *p) { -#if defined(LFS_FREE) - LFS_FREE(p); -#elif !defined(LFS_NO_MALLOC) - free(p); -#else - (void)p; -#endif -} - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif -#endif diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index b6ab9a3..05eea96 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -124,9 +124,20 @@ bool getPriceNotifyInit() { void stopPriceNotify() { if (clientPrice == NULL) return; - esp_websocket_client_close(clientPrice, portMAX_DELAY); + esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); esp_websocket_client_stop(clientPrice); esp_websocket_client_destroy(clientPrice); clientPrice = NULL; +} + +void restartPriceNotify() { + stopPriceNotify(); + if (clientPrice == NULL) { + setupPriceNotify(); + return; + } + // esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); + // esp_websocket_client_stop(clientPrice); + // esp_websocket_client_start(clientPrice); } \ No newline at end of file diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index acdd898..0382a8c 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -19,5 +19,7 @@ void setPrice(uint newPrice); bool isPriceNotifyConnected(); void stopPriceNotify(); +void restartPriceNotify(); + bool getPriceNotifyInit(); uint getLastPriceUpdate(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 6255416..01f8c60 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -718,10 +718,10 @@ void onApiRestartDataSources(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); - stopPriceNotify(); - stopBlockNotify(); - setupPriceNotify(); - setupBlockNotify(); + restartPriceNotify(); + restartBlockNotify(); +// setupPriceNotify(); +// setupBlockNotify(); request->send(response); } diff --git a/src/main.cpp b/src/main.cpp index 4968eda..095a8a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,8 @@ uint wifiLostConnection; uint priceNotifyLostConnection = 0; uint blockNotifyLostConnection = 0; +//char ptrTaskList[1500]; + extern "C" void app_main() { @@ -78,10 +80,13 @@ extern "C" void app_main() { Serial.println(F("Restarting price handler...")); - stopPriceNotify(); - setupPriceNotify(); + restartPriceNotify(); + // setupPriceNotify(); priceNotifyLostConnection = 0; } + } else if (priceNotifyLostConnection > 0 && isPriceNotifyConnected()) + { + priceNotifyLostConnection = 0; } if (getBlockNotifyInit() && !isBlockNotifyConnected()) @@ -94,15 +99,14 @@ extern "C" void app_main() { Serial.println(F("Restarting block handler...")); - stopBlockNotify(); - setupBlockNotify(); + restartBlockNotify(); + //setupBlockNotify(); blockNotifyLostConnection = 0; } } - else if (blockNotifyLostConnection > 0 || priceNotifyLostConnection > 0) + else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) { blockNotifyLostConnection = 0; - priceNotifyLostConnection = 0; } // if more than 5 price updates are missed, there is probably something wrong, reconnect @@ -110,8 +114,8 @@ extern "C" void app_main() { Serial.println(F("Detected 5 missed price updates... restarting price handler.")); - stopPriceNotify(); - setupPriceNotify(); + restartPriceNotify(); + // setupPriceNotify(); priceNotifyLostConnection = 0; } @@ -127,8 +131,8 @@ extern "C" void app_main() { Serial.println(F("Detected stuck block height... restarting block handler.")); // Mempool source stuck, restart - stopBlockNotify(); - setupBlockNotify(); + restartBlockNotify(); + // setupBlockNotify(); } // set last block update so it doesn't fetch for 45 minutes setLastBlockUpdate(currentUptime); From 4da04ca3ee176d0eeec929878f6638984ce7a895 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 27 Apr 2024 16:48:06 +0200 Subject: [PATCH 019/188] Fix turn off LEDs --- boards/btclock_rev_b.json | 60 +++++++++++++++++++++++++ partition_8mb.csv | 7 +++ platformio.ini | 33 +++++++++++++- src/lib/config.cpp | 28 +++++++++++- src/lib/config.hpp | 8 ++++ src/lib/epd.cpp | 93 +++++++++++++++++++++++++-------------- src/lib/led_handler.cpp | 38 +++++++++++++++- src/lib/led_handler.hpp | 9 +++- src/lib/webserver.cpp | 15 ++++++- 9 files changed, 254 insertions(+), 37 deletions(-) create mode 100644 boards/btclock_rev_b.json create mode 100644 partition_8mb.csv diff --git a/boards/btclock_rev_b.json b/boards/btclock_rev_b.json new file mode 100644 index 0000000..446576e --- /dev/null +++ b/boards/btclock_rev_b.json @@ -0,0 +1,60 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_BTCLOCK_REV_B", + "-DARDUINO_ESP32S3_DEV", + "-DIS_BTCLOCK_REV_B", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1", + "-DARDUINO_USB_CDC_ON_BOOT=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "espidf": { + "sdkconfig_path": "boards" + }, + "hwids": [ + [ + "0x303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "bluetooth", + "wifi" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "BTClock (rev. B)", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 460800 + }, + "url": "http://github.com/btclock", + "vendor": "BTClock" +} \ No newline at end of file diff --git a/partition_8mb.csv b/partition_8mb.csv new file mode 100644 index 0000000..4ca1357 --- /dev/null +++ b/partition_8mb.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 36K, 20K, +otadata, data, ota, 56K, 8K, +app0, app, ota_0, 64K, 1700K, +app1, app, ota_1, , 1700K, +spiffs, data, spiffs, , 400K, +coredump, data, coredump,, 64K, diff --git a/platformio.ini b/platformio.ini index dffad22..9ec55c0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] data_dir = data/build_gz -default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd +default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd [env] @@ -57,6 +57,28 @@ build_flags = build_unflags = ${btclock_base.build_unflags} + +[env:btclock_rev_b] +extends = btclock_base +board = btclock_rev_b +board_build.partitions = partition.csv +build_flags = + ${btclock_base.build_flags} + -D MCP_INT_PIN=8 + -D NEOPIXEL_PIN=15 + -D NEOPIXEL_COUNT=4 + -D NUM_SCREENS=7 + -D I2C_SDA_PIN=35 + -D I2C_SCK_PIN=36 + -D HAS_FRONTLIGHT + -D PCA_OE_PIN=45 + -D PCA_I2C_ADDR=0x42 +lib_deps = + ${btclock_base.lib_deps} + robtillaart/PCA9685@^0.7.1 +build_unflags = + ${btclock_base.build_unflags} + [env:lolin_s3_mini_213epd] extends = env:lolin_s3_mini test_framework = unity @@ -65,6 +87,15 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 + +[env:btclock_rev_b_213epd] +extends = env:btclock_rev_b +test_framework = unity +build_flags = + ${env:btclock_rev_b.build_flags} + -D USE_QR + -D VERSION_EPD_2_13 + [env:lolin_s3_mini_29epd] extends = env:lolin_s3_mini test_framework = unity diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 19b8132..eb420dc 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -7,6 +7,11 @@ Adafruit_MCP23X17 mcp1; #ifdef IS_BTCLOCK_S3 Adafruit_MCP23X17 mcp2; #endif + +#ifdef HAS_FRONTLIGHT +PCA9685 flArray(PCA_I2C_ADDR); +#endif + std::vector screenNameMap(SCREEN_COUNT); std::mutex mcpMutex; uint lastTimeSync; @@ -352,6 +357,10 @@ void setupHardware() // ; } #endif + +#ifdef HAS_FRONTLIGHT + setupFrontlight(); +#endif } void improvGetAvailableWifiNetworks() @@ -658,4 +667,21 @@ String getMyHostname() uint getLastTimeSync() { return lastTimeSync; -} \ No newline at end of file +} + +#ifdef HAS_FRONTLIGHT +void setupFrontlight() { + flArray.begin(); + flArray.setFrequency(1000); + flArray.setOutputEnablePin(PCA_OE_PIN); + + if (!preferences.isKey("flMaxBrightness")) { + preferences.putUInt("flMaxBrightness", 4095); + } + // Initialize all LEDs to off + // for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + // flArray.setPWM(ledPin, 0, 0); // Turn off LED + // } + flArray.allOFF(); +} +#endif \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 4f5a83c..e5a59f1 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -20,6 +20,9 @@ #include "lib/screen_handler.hpp" #include "lib/shared.hpp" #include "lib/webserver.hpp" +#ifdef HAS_FRONTLIGHT +#include "PCA9685.h" +#endif #define NTP_SERVER "pool.ntp.org" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" @@ -42,6 +45,11 @@ void tryImprovSetup(); void setupTimers(); void finishSetup(); void setupMcp(); +#ifdef HAS_FRONTLIGHT +void setupFrontlight(); +extern PCA9685 flArray; +#endif + String getMyHostname(); std::vector getScreenNameMap(); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 0f4a229..9fef2eb 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -1,6 +1,65 @@ #include "epd.hpp" -#ifndef IS_BTCLOCK_S3 +#ifdef IS_BTCLOCK_REV_B +Native_Pin EPD_CS[NUM_SCREENS] = { + Native_Pin(2), + Native_Pin(4), + Native_Pin(6), + Native_Pin(10), + Native_Pin(38), + Native_Pin(21), + Native_Pin(17), +}; +Native_Pin EPD_BUSY[NUM_SCREENS] = { + Native_Pin(3), + Native_Pin(5), + Native_Pin(7), + Native_Pin(9), + Native_Pin(37), + Native_Pin(18), + Native_Pin(16), +}; +MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { + MCP23X17_Pin(mcp1, 8), + MCP23X17_Pin(mcp1, 9), + MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), + MCP23X17_Pin(mcp1, 12), + MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), +}; + +Native_Pin EPD_DC = Native_Pin(14); +#elif IS_BTCLOCK_S3 +Native_Pin EPD_DC = Native_Pin(38); + +MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { + MCP23X17_Pin(mcp1, 8), + MCP23X17_Pin(mcp1, 9), + MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), + MCP23X17_Pin(mcp1, 12), + MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), + MCP23X17_Pin(mcp1, 4), +}; + +MCP23X17_Pin EPD_CS[NUM_SCREENS] = { + MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), + MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), + MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; + +MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { + MCP23X17_Pin(mcp2, 9), + MCP23X17_Pin(mcp2, 11), + MCP23X17_Pin(mcp2, 13), + MCP23X17_Pin(mcp2, 15), + MCP23X17_Pin(mcp2, 1), + MCP23X17_Pin(mcp2, 3), + MCP23X17_Pin(mcp2, 5), + MCP23X17_Pin(mcp2, 7), +}; +#else Native_Pin EPD_CS[NUM_SCREENS] = { Native_Pin(2), Native_Pin(4), @@ -35,36 +94,6 @@ MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { }; Native_Pin EPD_DC = Native_Pin(14); -#else -Native_Pin EPD_DC = Native_Pin(38); - -MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), - MCP23X17_Pin(mcp1, 4), -}; - -MCP23X17_Pin EPD_CS[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), - MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), - MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; - -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 9), - MCP23X17_Pin(mcp2, 11), - MCP23X17_Pin(mcp2, 13), - MCP23X17_Pin(mcp2, 15), - MCP23X17_Pin(mcp2, 1), - MCP23X17_Pin(mcp2, 3), - MCP23X17_Pin(mcp2, 5), - MCP23X17_Pin(mcp2, 7), -}; - #endif GxEPD2_BW displays[NUM_SCREENS] = { @@ -141,7 +170,7 @@ void setupDisplays() updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 4096, NULL, 11, NULL); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 2048, NULL, 11, NULL); for (uint i = 0; i < NUM_SCREENS; i++) { diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index a809e75..8efe6d9 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -329,4 +329,40 @@ void ledTheaterChaseRainbow(int wait) { } } -Adafruit_NeoPixel getPixels() { return pixels; } \ No newline at end of file +Adafruit_NeoPixel getPixels() { return pixels; } + +#ifdef HAS_FRONTLIGHT +int flDelayTime = 10; + +void frontlightFadeInAll() { + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); + } + delay(flDelayTime); + } +} + +void frontlightFadeOutAll() { + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); + } + delay(flDelayTime); + } +} + +void frontlightFadeIn(uint num) { + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { + flArray.setPWM(num, 0, dutyCycle); + delay(flDelayTime); + } +} + +void frontlightFadeOut(uint num) { + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { + flArray.setPWM(num, 0, dutyCycle); + delay(flDelayTime); + } +} +#endif \ No newline at end of file diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 8e064f5..c7771b2 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -56,4 +56,11 @@ void setLights(uint32_t color); void ledRainbow(int wait); void ledTheaterChaseRainbow(int wait); void ledTheaterChase(uint32_t color, int wait); -Adafruit_NeoPixel getPixels(); \ No newline at end of file +Adafruit_NeoPixel getPixels(); + +#ifdef HAS_FRONTLIGHT +void frontlightFadeInAll(); +void frontlightFadeOutAll(); +void frontlightFadeIn(uint num); +void frontlightFadeOut(uint num); +#endif \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 01f8c60..74e8db1 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -45,6 +45,7 @@ void setupWebserver() { "/api/show/custom", onApiShowTextAdvanced); server.addHandler(handler); + AsyncCallbackJsonWebHandler *lightsJsonHandler = new AsyncCallbackJsonWebHandler("/api/lights", onApiLightsSetJson); server.addHandler(lightsJsonHandler); @@ -301,7 +302,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { } } - String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness"}; + String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness"}; for (String setting : uintSettings) { if (settings.containsKey(setting)) { @@ -418,6 +419,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); + #ifdef HAS_FRONTLIGHT + root["hasFrontlight"] = true; + root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", 4095); + #else + root["hasFrontlight"] = false; + #endif + #ifdef GIT_REV root["gitRev"] = String(GIT_REV); #endif @@ -761,6 +769,11 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { JsonArray lights = json.as(); if (lights.size() != pixels.numPixels()) { + if (!lights.size()) { + // if empty, assume off request + return onApiLightsOff(request); + } + Serial.printf("Invalid values for LED set %d\n", lights.size()); request->send(400); return; From a2fa0a12a8ccba68cc4befe475bbef4613e79626 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 28 Apr 2024 16:47:57 +0200 Subject: [PATCH 020/188] Dependency updates --- .github/workflows/tagging.yml | 5 +++++ dependencies.lock | 2 +- platformio.ini | 16 +++++++++---- src/lib/webserver.cpp | 42 ++++++++++++++++++++++++++++++++++- src/lib/webserver.hpp | 8 ++++++- 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 9d2a0c3..43222d0 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -37,7 +37,12 @@ jobs: chip: - name: lolin_s3_mini version: esp32s3 + - name: btclock_rev_b + version: esp32s3 epd_variant: [213epd, 29epd] + exclude: + - chip: btclock_rev_b + epd_variant: 29epd steps: - uses: actions/download-artifact@v4 with: diff --git a/dependencies.lock b/dependencies.lock index c645bdf..27f0397 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.6 -manifest_hash: f4c10dfb616cf7e24f85cb263b8c89ef7d6d8eee64860fd27097b1a83ba56960 +manifest_hash: c799a313787fb26f19e5716786e23d7a9f10f1cd0bbd7bc379c518bb5e67be3e target: esp32s3 version: 1.0.0 diff --git a/platformio.ini b/platformio.ini index 9ec55c0..e693e8a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd [btclock_base] -platform = espressif32 @ ^6.5.0 +platform = espressif32 @ ^6.6.0 framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize @@ -32,15 +32,15 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.0.3 + bblanchon/ArduinoJson@^7.0.4 esphome/Improv@^1.2.3 mathieucarbou/ESP Async WebServer - adafruit/Adafruit BusIO@^1.15.0 + adafruit/Adafruit BusIO@^1.16.0 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.0 https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin - https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2 + https://github.com/tzapu/WiFiManager.git#v2.0.17 [env:lolin_s3_mini] extends = btclock_base @@ -104,6 +104,14 @@ build_flags = -D USE_QR -D VERSION_EPD_2_9 +[env:btclock_rev_b_29epd] +extends = env:btclock_rev_b +test_framework = unity +build_flags = + ${env:btclock_rev_b.build_flags} + -D USE_QR + -D VERSION_EPD_2_9 + [env:btclock_s3] extends = btclock_base board = btclock diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 74e8db1..230fae3 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -54,6 +54,13 @@ void setupWebserver() { server.on("/api/lights/color", HTTP_GET, onApiLightsSetColor); server.on("/api/lights", HTTP_GET, onApiLightsStatus); + + #ifdef HAS_FRONTLIGHT + server.on("/api/frontlight/on", HTTP_GET, onApiFrontlightOn); + server.on("/api/frontlight/status", HTTP_GET, onApiFrontlightStatus); + server.on("/api/frontlight/off", HTTP_GET, onApiFrontlightOff); + #endif + // server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, // onApiLightsSetColor); @@ -867,4 +874,37 @@ void eventSourceTask(void *pvParameters) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); eventSourceUpdate(); } -} \ No newline at end of file +} + +#ifdef HAS_FRONTLIGHT +void onApiFrontlightOn(AsyncWebServerRequest *request) { + frontlightFadeInAll(); + + request->send(200); +} + +void onApiFrontlightStatus(AsyncWebServerRequest *request) { + AsyncResponseStream *response = + request->beginResponseStream("application/json"); + + JsonDocument root; + JsonArray ledStates = root["data"].to(); + + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + uint16_t onTime, offTime; + flArray.getPWM(ledPin, &onTime, &offTime); + + ledStates.add(onTime); + } + + serializeJson(ledStates, *response); + + request->send(response); +} + +void onApiFrontlightOff(AsyncWebServerRequest *request) { + frontlightFadeOutAll(); + + request->send(200); +} +#endif \ No newline at end of file diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index c910c3e..95fcda2 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -52,4 +52,10 @@ void eventSourceUpdate(); void eventSourceTask(void *pvParameters); void onApiStopDataSources(AsyncWebServerRequest *request); -void onApiRestartDataSources(AsyncWebServerRequest *request); \ No newline at end of file +void onApiRestartDataSources(AsyncWebServerRequest *request); + +#ifdef HAS_FRONTLIGHT +void onApiFrontlightOn(AsyncWebServerRequest *request); +void onApiFrontlightStatus(AsyncWebServerRequest *request); +void onApiFrontlightOff(AsyncWebServerRequest *request); +#endif \ No newline at end of file From efaab00fb41328100e3021ca8b5e4f57754e846a Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 8 May 2024 23:54:17 +0200 Subject: [PATCH 021/188] WebUI: Add German translation, switch to own price source --- data | 2 +- src/lib/config.cpp | 48 +++++++++++++++++---- src/lib/led_handler.cpp | 8 ++-- src/lib/price_notify.cpp | 92 +++++++++++++++++++++++++--------------- src/lib/shared.cpp | 10 +++++ src/lib/webserver.cpp | 5 ++- 6 files changed, 117 insertions(+), 48 deletions(-) create mode 100644 src/lib/shared.cpp diff --git a/data b/data index dcdf989..e47fc06 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit dcdf98964a42ccd83b2d2501bbed46a640bbc300 +Subproject commit e47fc066b0608132c44a35f7041be80b263adcff diff --git a/src/lib/config.cpp b/src/lib/config.cpp index eb420dc..893d773 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -41,7 +41,8 @@ void setup() if (mcp1.digitalRead(0) == LOW) { // Then loop forever to prevent anything else from writing to the screen - while (true) { + while (true) + { delay(1000); } } @@ -84,7 +85,7 @@ void tryImprovSetup() } } - // if (!preferences.getBool("wifiConfigured", false)) + // if (!preferences.getBool("wifiConfigured", false)) { queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); @@ -130,13 +131,23 @@ void tryImprovSetup() const String explainText = "*SSID: *\r\n" + wifiManager->getConfigPortalSSID() + "\r\n\r\n*Password:*\r\n" + softAP_password; + // Set the UNIX timestamp + time_t timestamp = LAST_BUILD_TIME; // Example timestamp: March 7, 2021 00:00:00 UTC + + // Convert the timestamp to a struct tm in UTC + struct tm *timeinfo = gmtime(×tamp); + + // Format the date + char formattedDate[20]; + strftime(formattedDate, sizeof(formattedDate), "%y-%m-%d\r\n%H:%M:%S", timeinfo); + std::array epdContent = { "Welcome!", "Bienvenidos!", "To setup\r\nscan QR or\r\nconnect\r\nmanually", "Para\r\nconfigurar\r\nescanear QR\r\no conectar\r\nmanualmente", explainText, - "*Hostname*:\r\n" + getMyHostname(), + "*Hostname*:\r\n" + getMyHostname() + "\r\n\r\n" + "*FW build date:*\r\n" + formattedDate, qrText}; setEpdContent(epdContent); }); @@ -311,6 +322,17 @@ void setupHardware() Serial.println(F("Error loading WebUI")); } + // if (!LittleFS.exists("/qr.txt")) + // { + // File f = LittleFS.open("/qr.txt", "w"); + + // if(f) { + + // } else { + // Serial.println(F("Can't write QR to FS")); + // } + // } + setupLeds(); WiFi.setHostname(getMyHostname().c_str()); @@ -665,17 +687,27 @@ String getMyHostname() return hostname; } -uint getLastTimeSync() { +uint getLastTimeSync() +{ return lastTimeSync; } #ifdef HAS_FRONTLIGHT -void setupFrontlight() { - flArray.begin(); +void setupFrontlight() +{ + if (!flArray.begin(PCA9685_MODE1_AUTOINCR, PCA9685_MODE2_INVERT)) + { + Serial.println(F("FL driver error")); + return; + } + Serial.println(F("FL driver active")); flArray.setFrequency(1000); flArray.setOutputEnablePin(PCA_OE_PIN); - - if (!preferences.isKey("flMaxBrightness")) { + flArray.setOutputEnable(true); + delay(1000); + flArray.setOutputEnable(false); + if (!preferences.isKey("flMaxBrightness")) + { preferences.putUInt("flMaxBrightness", 4095); } // Initialize all LEDs to off diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 8efe6d9..5580451 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -339,7 +339,7 @@ void frontlightFadeInAll() { for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { flArray.setPWM(ledPin, 0, dutyCycle); } - delay(flDelayTime); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } @@ -348,21 +348,21 @@ void frontlightFadeOutAll() { for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { flArray.setPWM(ledPin, 0, dutyCycle); } - delay(flDelayTime); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } void frontlightFadeIn(uint num) { for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { flArray.setPWM(num, 0, dutyCycle); - delay(flDelayTime); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } void frontlightFadeOut(uint num) { for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { flArray.setPWM(num, 0, dutyCycle); - delay(flDelayTime); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } #endif \ No newline at end of file diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 05eea96..0fe80d2 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -1,5 +1,6 @@ #include "price_notify.hpp" +const char *wsOwnServerPrice = "wss://ws.btclock.store/ws?assets=bitcoin"; const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; // const char* coinCapWsCert = R"(-----BEGIN CERTIFICATE----- @@ -35,17 +36,25 @@ const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; // WebsocketsClient client; esp_websocket_client_handle_t clientPrice = NULL; -uint currentPrice = 30000; +esp_websocket_client_config_t config; +uint currentPrice = 50000; unsigned long int lastPriceUpdate; bool priceNotifyInit = false; -void setupPriceNotify() { +void setupPriceNotify() +{ // currentPrice = preferences.get("lastPrice", 30000); - esp_websocket_client_config_t config = {.uri = wsServerPrice, - // .task_stack = (7*1024), - // .cert_pem = coinCapWsCert, - .user_agent = USER_AGENT}; + if (preferences.getBool("ownPriceSource", true)) + { + config = {.uri = wsOwnServerPrice, + .user_agent = USER_AGENT}; + } + else + { + config = {.uri = wsServerPrice, + .user_agent = USER_AGENT}; + } clientPrice = esp_websocket_client_init(&config); esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, @@ -54,40 +63,46 @@ void setupPriceNotify() { } void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) { + int32_t event_id, void *event_data) +{ esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - switch (event_id) { - case WEBSOCKET_EVENT_CONNECTED: - Serial.println(F("Connected to CoinCap.io WebSocket")); - priceNotifyInit = true; + switch (event_id) + { + case WEBSOCKET_EVENT_CONNECTED: + Serial.println("Connected to " + String(config.uri) + " WebSocket"); + priceNotifyInit = true; - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketPriceMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Price WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Price WS Connnection Closed")); - break; + break; + case WEBSOCKET_EVENT_DATA: + onWebsocketPriceMessage(data); + break; + case WEBSOCKET_EVENT_ERROR: + Serial.println(F("Price WS Connnection error")); + break; + case WEBSOCKET_EVENT_DISCONNECTED: + Serial.println(F("Price WS Connnection Closed")); + break; } } -void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { +void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) +{ JsonDocument doc; deserializeJson(doc, (char *)event_data->data_ptr); - if (doc.containsKey("bitcoin")) { - if (currentPrice != doc["bitcoin"].as()) { + if (doc.containsKey("bitcoin")) + { + if (currentPrice != doc["bitcoin"].as()) + { uint minSecPriceUpd = preferences.getUInt( "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); uint currentTime = esp_timer_get_time() / 1000000; if (lastPriceUpdate == 0 || - (currentTime - lastPriceUpdate) > minSecPriceUpd) { + (currentTime - lastPriceUpdate) > minSecPriceUpd) + { // const unsigned long oldPrice = currentPrice; currentPrice = doc["bitcoin"].as(); preferences.putUInt("lastPrice", currentPrice); @@ -95,7 +110,8 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || getCurrentScreen() == SCREEN_MSCW_TIME || - getCurrentScreen() == SCREEN_MARKET_CAP)) { + getCurrentScreen() == SCREEN_MARKET_CAP)) + { WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } @@ -105,7 +121,8 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { } } -uint getLastPriceUpdate() { +uint getLastPriceUpdate() +{ return lastPriceUpdate; } @@ -113,17 +130,22 @@ uint getPrice() { return currentPrice; } void setPrice(uint newPrice) { currentPrice = newPrice; } -bool isPriceNotifyConnected() { - if (clientPrice == NULL) return false; +bool isPriceNotifyConnected() +{ + if (clientPrice == NULL) + return false; return esp_websocket_client_is_connected(clientPrice); } -bool getPriceNotifyInit() { +bool getPriceNotifyInit() +{ return priceNotifyInit; } -void stopPriceNotify() { - if (clientPrice == NULL) return; +void stopPriceNotify() +{ + if (clientPrice == NULL) + return; esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); esp_websocket_client_stop(clientPrice); esp_websocket_client_destroy(clientPrice); @@ -131,9 +153,11 @@ void stopPriceNotify() { clientPrice = NULL; } -void restartPriceNotify() { +void restartPriceNotify() +{ stopPriceNotify(); - if (clientPrice == NULL) { + if (clientPrice == NULL) + { setupPriceNotify(); return; } diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp new file mode 100644 index 0000000..e6acfab --- /dev/null +++ b/src/lib/shared.cpp @@ -0,0 +1,10 @@ +#include "shared.hpp" + +#ifdef TEST_SCREENS +uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24 +uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits +uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits +uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w +uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w +uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display +#endif \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 230fae3..5e6877b 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -329,7 +329,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds"}; + "suffixPrice", "disableLeds", "ownPriceSource", "flAlwaysOn"}; for (String setting : boolSettings) { if (settings.containsKey(setting)) { @@ -425,10 +425,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["hostname"] = getMyHostname(); root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); + root["ownPriceSource"] = preferences.getBool("ownPriceSource", true); #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", 4095); + root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", false); + #else root["hasFrontlight"] = false; #endif From e0283d98caa9de778bbf4647f33c40346fb4ddcf Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 9 May 2024 01:02:40 +0200 Subject: [PATCH 022/188] Use own data source for price and block data --- data | 2 +- src/lib/block_notify.cpp | 13 +++++++++---- src/lib/block_notify.hpp | 4 ++-- src/lib/price_notify.cpp | 6 +++++- src/lib/price_notify.hpp | 2 +- src/lib/webserver.cpp | 4 ++-- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/data b/data index e47fc06..6ecb482 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e47fc066b0608132c44a35f7041be80b263adcff +Subproject commit 6ecb4826000e4a34f22d98541d28448f844340f7 diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index d01b3d6..cc927bc 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -87,6 +87,11 @@ void setupBlockNotify() xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } + if (preferences.getBool("ownDataSource", true)) + { + return; + } + // std::strcpy(wsServer, String("wss://" + mempoolInstance + // "/api/v1/ws").c_str()); @@ -99,11 +104,11 @@ void setupBlockNotify() blockNotifyClient = esp_websocket_client_init(&config); esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY, - onWebsocketEvent, blockNotifyClient); + onWebsocketBlockEvent, blockNotifyClient); esp_websocket_client_start(blockNotifyClient); } -void onWebsocketEvent(void *handler_args, esp_event_base_t base, +void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; @@ -124,7 +129,7 @@ void onWebsocketEvent(void *handler_args, esp_event_base_t base, break; case WEBSOCKET_EVENT_DATA: - onWebsocketMessage(data); + onWebsocketBlockMessage(data); break; case WEBSOCKET_EVENT_ERROR: Serial.println(F("Mempool.space WS Connnection error")); @@ -135,7 +140,7 @@ void onWebsocketEvent(void *handler_args, esp_event_base_t base, } } -void onWebsocketMessage(esp_websocket_event_data_t *event_data) +void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) { JsonDocument doc; diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index b44060f..c233b2e 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -17,9 +17,9 @@ void setupBlockNotify(); -void onWebsocketEvent(void *handler_args, esp_event_base_t base, +void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); -void onWebsocketMessage(esp_websocket_event_data_t *event_data); +void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data); void setBlockHeight(uint newBlockHeight); uint getBlockHeight(); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 0fe80d2..bcd091a 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -45,7 +45,7 @@ void setupPriceNotify() { // currentPrice = preferences.get("lastPrice", 30000); - if (preferences.getBool("ownPriceSource", true)) + if (preferences.getBool("ownDataSource", true)) { config = {.uri = wsOwnServerPrice, .user_agent = USER_AGENT}; @@ -76,6 +76,10 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, break; case WEBSOCKET_EVENT_DATA: onWebsocketPriceMessage(data); + if (preferences.getBool("ownDataSource", true)) + { + onWebsocketBlockMessage(data); + } break; case WEBSOCKET_EVENT_ERROR: Serial.println(F("Price WS Connnection error")); diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 0382a8c..44c343d 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -3,7 +3,7 @@ #include #include #include - +#include "block_notify.hpp" #include #include "lib/screen_handler.hpp" diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 5e6877b..371f17b 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -329,7 +329,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownPriceSource", "flAlwaysOn"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn"}; for (String setting : boolSettings) { if (settings.containsKey(setting)) { @@ -425,7 +425,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["hostname"] = getMyHostname(); root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); - root["ownPriceSource"] = preferences.getBool("ownPriceSource", true); + root["ownDataSource"] = preferences.getBool("ownDataSource", true); #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; From d4a05e2f362559beb11cac75b6f91030ea8d7dc9 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 9 May 2024 15:43:44 +0200 Subject: [PATCH 023/188] Add Emscripten support to data handler --- lib/btclock/data_handler.cpp | 50 +++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 764417f..3100ef8 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -1,4 +1,8 @@ #include "data_handler.hpp" +#ifdef __EMSCRIPTEN__ +#include +#include +#endif std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat) { @@ -37,7 +41,7 @@ std::array parseSatsPerCurrency(std::uint32_t price, c std::array ret; std::string priceString = std::to_string(int(round(1 / float(price) * 10e7))); std::uint32_t firstIndex = 0; - uint insertSatSymbol = NUM_SCREENS - priceString.length() - 1; + std::uint8_t insertSatSymbol = NUM_SCREENS - priceString.length() - 1; if (priceString.length() < (NUM_SCREENS)) { @@ -99,7 +103,7 @@ std::array parseBlockFees(std::uint16_t blockFees) { firstIndex = 1; } - for (uint i = firstIndex; i < NUM_SCREENS-1; i++) + for (std::uint8_t i = firstIndex; i < NUM_SCREENS-1; i++) { ret[i] = blockFeesString[i]; } @@ -205,4 +209,44 @@ std::array parseMarketCap(std::uint32_t blockHeight, s } return ret; -} \ No newline at end of file +} + +#ifdef __EMSCRIPTEN__ +emscripten::val arrayToStringArray(const std::array& arr) { + emscripten::val jsArray = emscripten::val::array(); + for (const auto& str : arr) { + jsArray.call("push", str); + } + return jsArray; +} + +emscripten::val vectorToStringArray(const std::vector& vec) { + emscripten::val jsArray = emscripten::val::array(); + for (size_t i = 0; i < vec.size(); ++i) { + jsArray.set(i, vec[i]); + } + return jsArray; +} + +emscripten::val parseBlockHeightArray(std::uint32_t blockHeight) { + return arrayToStringArray(parseBlockHeight(blockHeight)); +} + +emscripten::val parsePriceDataArray(std::uint32_t price, const std::string& currencySymbol, bool useSuffixFormat = false) { + return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat)); +} + +EMSCRIPTEN_BINDINGS(my_module) { +// emscripten::register_vector("StringList"); + + emscripten::function("parseBlockHeight", &parseBlockHeightArray); + // emscripten::function("parseHalvingCountdown", &parseBlockHeightArray); + // emscripten::function("parseMarketCap", &parseBlockHeightArray); + // emscripten::function("parseBlockFees", &parseBlockHeightArray); + // emscripten::function("parseSatsPerCurrency", &parseBlockHeightArray); + emscripten::function("parsePriceData", &parsePriceDataArray); + + emscripten::function("arrayToStringArray", &arrayToStringArray); + emscripten::function("vectorToStringArray", &vectorToStringArray); +} +#endif \ No newline at end of file From 9cdbcc60462232798c13cb3f95f87d67d3d48378 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 10 May 2024 01:09:07 +0200 Subject: [PATCH 024/188] More emscripten methods --- lib/btclock/data_handler.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 3100ef8..7d8e9d0 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -236,14 +236,30 @@ emscripten::val parsePriceDataArray(std::uint32_t price, const std::string& curr return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat)); } +emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks) { + return arrayToStringArray(parseHalvingCountdown(blockHeight, asBlocks)); +} + +emscripten::val parseMarketCapArray(std::uint32_t blockHeight, std::uint32_t price, const std::string& currencySymbol, bool bigChars) { + return arrayToStringArray(parseMarketCap(blockHeight, price, currencySymbol[0], bigChars)); +} + +emscripten::val parseBlockFeesArray(std::uint16_t blockFees) { + return arrayToStringArray(parseBlockFees(blockFees)); +} + +emscripten::val parseSatsPerCurrencyArray(std::uint32_t price, const std::string& currencySymbol, bool withSatsSymbol) { + return arrayToStringArray(parseSatsPerCurrency(price, currencySymbol[0], withSatsSymbol)); +} + EMSCRIPTEN_BINDINGS(my_module) { // emscripten::register_vector("StringList"); emscripten::function("parseBlockHeight", &parseBlockHeightArray); - // emscripten::function("parseHalvingCountdown", &parseBlockHeightArray); - // emscripten::function("parseMarketCap", &parseBlockHeightArray); - // emscripten::function("parseBlockFees", &parseBlockHeightArray); - // emscripten::function("parseSatsPerCurrency", &parseBlockHeightArray); + emscripten::function("parseHalvingCountdown", &parseHalvingCountdownArray); + emscripten::function("parseMarketCap", &parseMarketCapArray); + emscripten::function("parseBlockFees", &parseBlockFeesArray); + emscripten::function("parseSatsPerCurrency", &parseSatsPerCurrencyArray); emscripten::function("parsePriceData", &parsePriceDataArray); emscripten::function("arrayToStringArray", &arrayToStringArray); From ef7d629e8cc420bc1f7045168e9140492e491f9e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 13 May 2024 11:59:02 +0200 Subject: [PATCH 025/188] Switch own data source domain, fix small block notification bug --- src/lib/block_notify.cpp | 4 ++++ src/lib/price_notify.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index cc927bc..c2fb092 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -160,6 +160,10 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) { JsonObject block = doc["block"]; + if (block["height"].as() == currentBlockHeight) { + return; + } + currentBlockHeight = block["height"].as(); // Serial.printf("New block found: %d\r\n", block["height"].as()); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index bcd091a..0a07f39 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -1,6 +1,6 @@ #include "price_notify.hpp" -const char *wsOwnServerPrice = "wss://ws.btclock.store/ws?assets=bitcoin"; +const char *wsOwnServerPrice = "wss://ws.btclock.dev/ws?assets=bitcoin"; const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; // const char* coinCapWsCert = R"(-----BEGIN CERTIFICATE----- From 858241bd570a1be7d7e2f0a5d500d214d19d6c0e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 18 May 2024 23:00:08 +0200 Subject: [PATCH 026/188] Add HW revision to mDNS for updates --- src/lib/config.cpp | 10 +++++++++- src/lib/config.hpp | 3 ++- src/lib/webserver.cpp | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 893d773..95ca5ab 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -716,4 +716,12 @@ void setupFrontlight() // } flArray.allOFF(); } -#endif \ No newline at end of file +#endif + +String getHwRev() { + #ifndef HW_REV + return "REV_0"; + #else + return HW_REV; + #endif +} \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index e5a59f1..f7ca85b 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -62,4 +62,5 @@ void improv_set_state(improv::State state); void improv_send_response(std::vector &response); void improv_set_error(improv::Error error); -void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); \ No newline at end of file +void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); +String getHwRev(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 371f17b..425c7fc 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -94,6 +94,7 @@ void setupWebserver() { MDNS.addServiceTxt("http", "tcp", "model", "BTClock"); MDNS.addServiceTxt("http", "tcp", "version", "3.0"); MDNS.addServiceTxt("http", "tcp", "rev", GIT_REV); + MDNS.addServiceTxt("http", "tcp", "hw_rev", getHwRev()); } xTaskCreate(eventSourceTask, "eventSourceTask", 4096, NULL, tskIDLE_PRIORITY, From e008383ab173df7cb1e62efa9a5e13c3fd22add3 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 18 May 2024 23:15:54 +0200 Subject: [PATCH 027/188] Update GH workflow --- .github/workflows/tagging.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 43222d0..c9d10ea 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -60,6 +60,9 @@ jobs: - name: Copy all artifacts to output folder run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} + - name: Create OTA binary file + run: mv ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_fw_only.bin + - name: Upload artifacts uses: actions/upload-artifact@v4 with: From 18139a9907159d0e78baa9b010061882e14bd745 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 18 May 2024 23:32:06 +0200 Subject: [PATCH 028/188] Better artifact file naming in workflow --- .github/workflows/tagging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index c9d10ea..8898ebc 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -61,7 +61,7 @@ jobs: run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} - name: Create OTA binary file - run: mv ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_fw_only.bin + run: mv ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin - name: Upload artifacts uses: actions/upload-artifact@v4 From db1523bef1c20bf61c4c8b64b31dd7b35575ad0f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 19 May 2024 00:11:08 +0200 Subject: [PATCH 029/188] Missing defines --- platformio.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platformio.ini b/platformio.ini index e693e8a..6fd28bf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -54,6 +54,7 @@ build_flags = -D NUM_SCREENS=7 -D I2C_SDA_PIN=35 -D I2C_SCK_PIN=36 + -D IS_HW_REV_A build_unflags = ${btclock_base.build_unflags} @@ -73,6 +74,7 @@ build_flags = -D HAS_FRONTLIGHT -D PCA_OE_PIN=45 -D PCA_I2C_ADDR=0x42 + -D IS_HW_REV_B lib_deps = ${btclock_base.lib_deps} robtillaart/PCA9685@^0.7.1 @@ -86,6 +88,7 @@ build_flags = ${env:lolin_s3_mini.build_flags} -D USE_QR -D VERSION_EPD_2_13 + -D HW_REV=\"REV_A_EPD_2_13\" [env:btclock_rev_b_213epd] @@ -95,6 +98,7 @@ build_flags = ${env:btclock_rev_b.build_flags} -D USE_QR -D VERSION_EPD_2_13 + -D HW_REV=\"REV_B_EPD_2_13\" [env:lolin_s3_mini_29epd] extends = env:lolin_s3_mini @@ -103,6 +107,7 @@ build_flags = ${env:lolin_s3_mini.build_flags} -D USE_QR -D VERSION_EPD_2_9 + -D HW_REV=\"REV_A_EPD_2_9\" [env:btclock_rev_b_29epd] extends = env:btclock_rev_b @@ -111,6 +116,7 @@ build_flags = ${env:btclock_rev_b.build_flags} -D USE_QR -D VERSION_EPD_2_9 + -D HW_REV=\"REV_B_EPD_2_9\" [env:btclock_s3] extends = btclock_base From ba0594959e11c2ba07dbd57ae3211d9a69557f73 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 19 May 2024 01:32:45 +0200 Subject: [PATCH 030/188] Add filesystem commit identification and identify api call --- scripts/extra_script.py | 11 +- scripts/find_btclocks.py | 6 +- src/lib/led_handler.cpp | 6 + src/lib/led_handler.hpp | 2 +- src/lib/webserver.cpp | 392 ++++++++++++++++++++++++++------------- src/lib/webserver.hpp | 2 + 6 files changed, 283 insertions(+), 136 deletions(-) diff --git a/scripts/extra_script.py b/scripts/extra_script.py index 4a1c118..d7e4b31 100644 --- a/scripts/extra_script.py +++ b/scripts/extra_script.py @@ -3,6 +3,13 @@ import os import gzip from shutil import copyfileobj, rmtree from pathlib import Path +import subprocess + +revision = ( + subprocess.check_output(["git", "rev-parse", "HEAD"]) + .strip() + .decode("utf-8") +) def gzip_file(input_file, output_file): with open(input_file, 'rb') as f_in: @@ -24,7 +31,9 @@ def process_directory(input_dir, output_dir): 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}') - + file_path = os.path.join(output_dir, "fs_hash.txt") + with open(file_path, "w") as file: + file.write(revision) # Build web interface before building FS diff --git a/scripts/find_btclocks.py b/scripts/find_btclocks.py index 79aa3de..ab1977a 100644 --- a/scripts/find_btclocks.py +++ b/scripts/find_btclocks.py @@ -48,8 +48,8 @@ class Listener(ServiceListener): #arguments = [f"-i {str()} -f -r"] namespace = argparse.Namespace( esp_ip=info.parsed_addresses()[0], - image=f"{os.getcwd()}/.pio/build/lolin_s3_mini_qr/firmware.bin", - littlefs=f"{os.getcwd()}/.pio/build/lolin_s3_mini_qr/littlefs.bin", + image=f"{os.getcwd()}/.pio/build/lolin_s3_mini_213epd/firmware.bin", + littlefs=f"{os.getcwd()}/.pio/build/lolin_s3_mini_213epd/littlefs.bin", progress=True ) if (str(info.properties.get(b"version").decode())) != "3.0": @@ -64,7 +64,7 @@ class Listener(ServiceListener): print("Different version, going to update") #espota.serve(namespace.esp_ip, "0.0.0.0", 3232, random.randint(10000,60000), "", namespace.littlefs, SPIFFS) - #espota.serve(namespace.esp_ip, "0.0.0.0", 3232, random.randint(10000,60000), "", namespace.image, FLASH) + espota.serve(namespace.esp_ip, "0.0.0.0", 3232, random.randint(10000,60000), "", namespace.image, FLASH) #print(arguments) #logging.basicConfig(level = logging.DEBUG, format = '%(asctime)-8s [%(levelname)s]: %(message)s', datefmt = '%H:%M:%S') diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 5580451..2c31e4f 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -43,6 +43,12 @@ void ledTask(void *parameter) { case LED_DATA_PRICE_ERROR: blinkDelayColor(150, 2, 177, 90, 31); break; + case LED_FLASH_IDENTIFY: + blinkDelayTwoColor(100, 2, pixels.Color(255, 0, 0), + pixels.Color(0, 255, 255)); + blinkDelayTwoColor(100, 2, pixels.Color(0, 255, 0), + pixels.Color(0, 0, 255)); + break; case LED_EFFECT_WIFI_CONNECT_SUCCESS: case LED_FLASH_SUCCESS: blinkDelayColor(150, 3, 0, 255, 0); diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index c7771b2..8fba3bd 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -35,7 +35,7 @@ const int LED_PROGRESS_100 = 203; const int LED_DATA_PRICE_ERROR = 300; const int LED_DATA_BLOCK_ERROR = 301; - +const int LED_FLASH_IDENTIFY = 990; const int LED_POWER_TEST = 999; extern TaskHandle_t ledTaskHandle; extern Adafruit_NeoPixel pixels; diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 425c7fc..3c9269a 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -4,10 +4,10 @@ AsyncWebServer server(80); AsyncEventSource events("/events"); TaskHandle_t eventSourceTaskHandle; -void setupWebserver() { - events.onConnect([](AsyncEventSourceClient *client) { - client->send("welcome", NULL, millis(), 1000); - }); +void setupWebserver() +{ + events.onConnect([](AsyncEventSourceClient *client) + { client->send("welcome", NULL, millis(), 1000); }); server.addHandler(&events); // server.serveStatic("/css", LittleFS, "/css/"); @@ -15,6 +15,7 @@ void setupWebserver() { server.serveStatic("/build", LittleFS, "/build"); server.serveStatic("/swagger.json", LittleFS, "/swagger.json"); server.serveStatic("/api.html", LittleFS, "/api.html"); + server.serveStatic("/fs_hash.txt", LittleFS, "/fs_hash.txt"); server.on("/", HTTP_GET, onIndex); @@ -27,7 +28,6 @@ void setupWebserver() { server.on("/api/stop_datasources", HTTP_GET, onApiStopDataSources); server.on("/api/restart_datasources", HTTP_GET, onApiRestartDataSources); - server.on("/api/action/pause", HTTP_GET, onApiActionPause); server.on("/api/action/timer_restart", HTTP_GET, onApiActionTimerRestart); @@ -45,7 +45,6 @@ void setupWebserver() { "/api/show/custom", onApiShowTextAdvanced); server.addHandler(handler); - AsyncCallbackJsonWebHandler *lightsJsonHandler = new AsyncCallbackJsonWebHandler("/api/lights", onApiLightsSetJson); server.addHandler(lightsJsonHandler); @@ -53,13 +52,13 @@ void setupWebserver() { server.on("/api/lights/off", HTTP_GET, onApiLightsOff); server.on("/api/lights/color", HTTP_GET, onApiLightsSetColor); server.on("/api/lights", HTTP_GET, onApiLightsStatus); + server.on("/api/identify", HTTP_GET, onApiIdentify); - - #ifdef HAS_FRONTLIGHT +#ifdef HAS_FRONTLIGHT server.on("/api/frontlight/on", HTTP_GET, onApiFrontlightOn); server.on("/api/frontlight/status", HTTP_GET, onApiFrontlightStatus); server.on("/api/frontlight/off", HTTP_GET, onApiFrontlightOff); - #endif +#endif // server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, // onApiLightsSetColor); @@ -83,10 +82,13 @@ void setupWebserver() { server.begin(); - if (preferences.getBool("mdnsEnabled", true)) { - if (!MDNS.begin(getMyHostname())) { + if (preferences.getBool("mdnsEnabled", true)) + { + if (!MDNS.begin(getMyHostname())) + { Serial.println(F("Error setting up MDNS responder!")); - while (1) { + while (1) + { delay(1000); } } @@ -103,7 +105,8 @@ void setupWebserver() { void stopWebServer() { server.end(); } -JsonDocument getStatusObject() { +JsonDocument getStatusObject() +{ JsonDocument root; root["currentScreen"] = getCurrentScreen(); @@ -126,12 +129,14 @@ JsonDocument getStatusObject() { return root; } -JsonDocument getLedStatusObject() { +JsonDocument getLedStatusObject() +{ JsonDocument root; JsonArray colors = root["data"].to(); // Adafruit_NeoPixel pix = getPixels(); - for (uint i = 0; i < pixels.numPixels(); i++) { + for (uint i = 0; i < pixels.numPixels(); i++) + { uint32_t pixColor = pixels.getPixelColor(pixels.numPixels() - i - 1); uint alpha = (pixColor >> 24) & 0xFF; uint red = (pixColor >> 16) & 0xFF; @@ -150,8 +155,10 @@ JsonDocument getLedStatusObject() { return root; } -void eventSourceUpdate() { - if (!events.count()) return; +void eventSourceUpdate() +{ + if (!events.count()) + return; JsonDocument root = getStatusObject(); JsonArray data = root["data"].to(); @@ -173,7 +180,8 @@ void eventSourceUpdate() { * @Api * @Path("/api/status") */ -void onApiStatus(AsyncWebServerRequest *request) { +void onApiStatus(AsyncWebServerRequest *request) +{ AsyncResponseStream *response = request->beginResponseStream("application/json"); @@ -199,7 +207,8 @@ void onApiStatus(AsyncWebServerRequest *request) { * @Api * @Path("/api/action/pause") */ -void onApiActionPause(AsyncWebServerRequest *request) { +void onApiActionPause(AsyncWebServerRequest *request) +{ setTimerActive(false); request->send(200); }; @@ -208,7 +217,8 @@ void onApiActionPause(AsyncWebServerRequest *request) { * @Api * @Path("/api/action/timer_restart") */ -void onApiActionTimerRestart(AsyncWebServerRequest *request) { +void onApiActionTimerRestart(AsyncWebServerRequest *request) +{ setTimerActive(true); request->send(200); } @@ -217,7 +227,8 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request) { * @Api * @Path("/api/full_refresh") */ -void onApiFullRefresh(AsyncWebServerRequest *request) { +void onApiFullRefresh(AsyncWebServerRequest *request) +{ forceFullRefresh(); std::array newEpdContent = getCurrentEpdContent(); @@ -230,8 +241,10 @@ void onApiFullRefresh(AsyncWebServerRequest *request) { * @Api * @Path("/api/show/screen") */ -void onApiShowScreen(AsyncWebServerRequest *request) { - if (request->hasParam("s")) { +void onApiShowScreen(AsyncWebServerRequest *request) +{ + if (request->hasParam("s")) + { AsyncWebParameter *p = request->getParam("s"); uint currentScreen = p->value().toInt(); setCurrentScreen(currentScreen); @@ -239,14 +252,17 @@ void onApiShowScreen(AsyncWebServerRequest *request) { request->send(200); } -void onApiShowText(AsyncWebServerRequest *request) { - if (request->hasParam("t")) { +void onApiShowText(AsyncWebServerRequest *request) +{ + if (request->hasParam("t")) + { AsyncWebParameter *p = request->getParam("t"); String t = p->value(); - t.toUpperCase(); // This is needed as long as lowercase letters are glitchy + t.toUpperCase(); // This is needed as long as lowercase letters are glitchy std::array textEpdContent; - for (uint i = 0; i < NUM_SCREENS; i++) { + for (uint i = 0; i < NUM_SCREENS; i++) + { textEpdContent[i] = t[i]; } @@ -256,12 +272,14 @@ void onApiShowText(AsyncWebServerRequest *request) { request->send(200); } -void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) { +void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) +{ JsonArray screens = json.as(); std::array epdContent; int i = 0; - for (JsonVariant s : screens) { + for (JsonVariant s : screens) + { epdContent[i] = s.as(); i++; } @@ -272,12 +290,14 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) { request->send(200); } -void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { +void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) +{ JsonObject settings = json.as(); bool settingsChanged = true; - if (settings.containsKey("fgColor")) { + if (settings.containsKey("fgColor")) + { String fgColor = settings["fgColor"].as(); preferences.putUInt("fgColor", strtol(fgColor.c_str(), NULL, 16)); setFgColor(int(strtol(fgColor.c_str(), NULL, 16))); @@ -285,7 +305,8 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { Serial.println(strtol(fgColor.c_str(), NULL, 16)); settingsChanged = true; } - if (settings.containsKey("bgColor")) { + if (settings.containsKey("bgColor")) + { String bgColor = settings["bgColor"].as(); preferences.putUInt("bgColor", strtol(bgColor.c_str(), NULL, 16)); @@ -295,15 +316,18 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { settingsChanged = true; } - if (settings.containsKey("timePerScreen")) { + if (settings.containsKey("timePerScreen")) + { preferences.putUInt("timerSeconds", settings["timePerScreen"].as() * 60); } String strSettings[] = {"hostnamePrefix", "mempoolInstance"}; - for (String setting : strSettings) { - if (settings.containsKey(setting)) { + for (String setting : strSettings) + { + if (settings.containsKey(setting)) + { preferences.putString(setting.c_str(), settings[setting].as()); Serial.printf("Setting %s to %s\r\n", setting.c_str(), settings[setting].as()); @@ -312,15 +336,18 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness"}; - for (String setting : uintSettings) { - if (settings.containsKey(setting)) { + for (String setting : uintSettings) + { + if (settings.containsKey(setting)) + { preferences.putUInt(setting.c_str(), settings[setting].as()); Serial.printf("Setting %s to %d\r\n", setting.c_str(), settings[setting].as()); } } - if (settings.containsKey("tzOffset")) { + if (settings.containsKey("tzOffset")) + { int gmtOffset = settings["tzOffset"].as() * 60; size_t written = preferences.putInt("gmtOffset", gmtOffset); Serial.printf("Setting %s to %d (%d minutes, written %d)\r\n", "gmtOffset", @@ -328,20 +355,24 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { } String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", - "mdnsEnabled", "otaEnabled", "stealFocus", + "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn"}; - for (String setting : boolSettings) { - if (settings.containsKey(setting)) { + for (String setting : boolSettings) + { + if (settings.containsKey(setting)) + { preferences.putBool(setting.c_str(), settings[setting].as()); Serial.printf("Setting %s to %d\r\n", setting.c_str(), settings[setting].as()); } } - if (settings.containsKey("screens")) { - for (JsonVariant screen : settings["screens"].as()) { + if (settings.containsKey("screens")) + { + for (JsonVariant screen : settings["screens"].as()) + { JsonObject s = screen.as(); uint id = s["id"].as(); String key = "screen[" + String(id) + "]"; @@ -351,20 +382,26 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { } } - if (settings.containsKey("txPower")) { + if (settings.containsKey("txPower")) + { int txPower = settings["txPower"].as(); - if (txPower == 80) { + if (txPower == 80) + { preferences.remove("txPower"); - if (WiFi.getTxPower() != 80) { + if (WiFi.getTxPower() != 80) + { ESP.restart(); } - } else if (static_cast(wifi_power_t::WIFI_POWER_MINUS_1dBm) <= - txPower && - txPower <= static_cast(wifi_power_t::WIFI_POWER_19_5dBm)) { + } + else if (static_cast(wifi_power_t::WIFI_POWER_MINUS_1dBm) <= + txPower && + txPower <= static_cast(wifi_power_t::WIFI_POWER_19_5dBm)) + { // is valid value - if (WiFi.setTxPower(static_cast(txPower))) { + if (WiFi.setTxPower(static_cast(txPower))) + { Serial.printf("Set WiFi Tx power to: %d\n", txPower); preferences.putInt("txPower", txPower); settingsChanged = true; @@ -373,27 +410,38 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { } request->send(200); - if (settingsChanged) { + if (settingsChanged) + { queueLedEffect(LED_FLASH_SUCCESS); } } -void onApiRestart(AsyncWebServerRequest *request) { +void onApiRestart(AsyncWebServerRequest *request) +{ request->send(200); - if (events.count()) events.send("closing"); + if (events.count()) + events.send("closing"); delay(500); esp_restart(); } +void onApiIdentify(AsyncWebServerRequest *request) +{ + queueLedEffect(LED_FLASH_IDENTIFY); + + request->send(200); +} + /** * @Api * @Method GET * @Path("/api/settings") */ -void onApiSettingsGet(AsyncWebServerRequest *request) { +void onApiSettingsGet(AsyncWebServerRequest *request) +{ JsonDocument root; root["numScreens"] = NUM_SCREENS; root["fgColor"] = getFgColor(); @@ -428,14 +476,14 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["txPower"] = WiFi.getTxPower(); root["ownDataSource"] = preferences.getBool("ownDataSource", true); - #ifdef HAS_FRONTLIGHT +#ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", 4095); root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", false); - #else +#else root["hasFrontlight"] = false; - #endif +#endif #ifdef GIT_REV root["gitRev"] = String(GIT_REV); @@ -447,7 +495,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { std::vector screenNameMap = getScreenNameMap(); - for (int i = 0; i < screenNameMap.size(); i++) { + for (int i = 0; i < screenNameMap.size(); i++) + { JsonObject o = screens.add(); String key = "screen" + String(i) + "Visible"; o["id"] = i; @@ -462,9 +511,11 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { request->send(response); } -bool processEpdColorSettings(AsyncWebServerRequest *request) { +bool processEpdColorSettings(AsyncWebServerRequest *request) +{ bool settingsChanged = false; - if (request->hasParam("fgColor", true)) { + if (request->hasParam("fgColor", true)) + { AsyncWebParameter *fgColor = request->getParam("fgColor", true); preferences.putUInt("fgColor", strtol(fgColor->value().c_str(), NULL, 16)); setFgColor(int(strtol(fgColor->value().c_str(), NULL, 16))); @@ -472,7 +523,8 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) { // Serial.println(fgColor->value().c_str()); settingsChanged = true; } - if (request->hasParam("bgColor", true)) { + if (request->hasParam("bgColor", true)) + { AsyncWebParameter *bgColor = request->getParam("bgColor", true); preferences.putUInt("bgColor", strtol(bgColor->value().c_str(), NULL, 16)); @@ -485,105 +537,135 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) { return settingsChanged; } -void onApiSettingsPost(AsyncWebServerRequest *request) { +void onApiSettingsPost(AsyncWebServerRequest *request) +{ bool settingsChanged = false; settingsChanged = processEpdColorSettings(request); int headers = request->headers(); int i; - for (i = 0; i < headers; i++) { + for (i = 0; i < headers; i++) + { AsyncWebHeader *h = request->getHeader(i); Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); } int params = request->params(); - for (int i = 0; i < params; i++) { + for (int i = 0; i < params; i++) + { AsyncWebParameter *p = request->getParam(i); - if (p->isFile()) { // p->isPost() is also true + if (p->isFile()) + { // p->isPost() is also true Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); - } else if (p->isPost()) { + } + else if (p->isPost()) + { Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } else { + } + else + { Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); } } - if (request->hasParam("fetchEurPrice", true)) { + if (request->hasParam("fetchEurPrice", true)) + { AsyncWebParameter *fetchEurPrice = request->getParam("fetchEurPrice", true); preferences.putBool("fetchEurPrice", fetchEurPrice->value().toInt()); settingsChanged = true; - } else { + } + else + { preferences.putBool("fetchEurPrice", 0); settingsChanged = true; } - if (request->hasParam("ledTestOnPower", true)) { + if (request->hasParam("ledTestOnPower", true)) + { AsyncWebParameter *ledTestOnPower = request->getParam("ledTestOnPower", true); preferences.putBool("ledTestOnPower", ledTestOnPower->value().toInt()); settingsChanged = true; - } else { + } + else + { preferences.putBool("ledTestOnPower", 0); settingsChanged = true; } - if (request->hasParam("ledFlashOnUpd", true)) { + if (request->hasParam("ledFlashOnUpd", true)) + { AsyncWebParameter *ledFlashOnUpdate = request->getParam("ledFlashOnUpd", true); preferences.putBool("ledFlashOnUpd", ledFlashOnUpdate->value().toInt()); settingsChanged = true; - } else { + } + else + { preferences.putBool("ledFlashOnUpd", 0); settingsChanged = true; } - if (request->hasParam("mdnsEnabled", true)) { + if (request->hasParam("mdnsEnabled", true)) + { AsyncWebParameter *mdnsEnabled = request->getParam("mdnsEnabled", true); preferences.putBool("mdnsEnabled", mdnsEnabled->value().toInt()); settingsChanged = true; - } else { + } + else + { preferences.putBool("mdnsEnabled", 0); settingsChanged = true; } - if (request->hasParam("otaEnabled", true)) { + if (request->hasParam("otaEnabled", true)) + { AsyncWebParameter *otaEnabled = request->getParam("otaEnabled", true); preferences.putBool("otaEnabled", otaEnabled->value().toInt()); settingsChanged = true; - } else { + } + else + { preferences.putBool("otaEnabled", 0); settingsChanged = true; } - if (request->hasParam("stealFocusOnBlock", true)) { + if (request->hasParam("stealFocusOnBlock", true)) + { AsyncWebParameter *stealFocusOnBlock = request->getParam("stealFocusOnBlock", true); preferences.putBool("stealFocus", stealFocusOnBlock->value().toInt()); settingsChanged = true; - } else { + } + else + { preferences.putBool("stealFocus", 0); settingsChanged = true; } - if (request->hasParam("mcapBigChar", true)) { + if (request->hasParam("mcapBigChar", true)) + { AsyncWebParameter *mcapBigChar = request->getParam("mcapBigChar", true); preferences.putBool("mcapBigChar", mcapBigChar->value().toInt()); settingsChanged = true; - } else { + } + else + { preferences.putBool("mcapBigChar", 0); settingsChanged = true; } - if (request->hasParam("mempoolInstance", true)) { + if (request->hasParam("mempoolInstance", true)) + { AsyncWebParameter *mempoolInstance = request->getParam("mempoolInstance", true); @@ -591,7 +673,8 @@ void onApiSettingsPost(AsyncWebServerRequest *request) { settingsChanged = true; } - if (request->hasParam("hostnamePrefix", true)) { + if (request->hasParam("hostnamePrefix", true)) + { AsyncWebParameter *hostnamePrefix = request->getParam("hostnamePrefix", true); @@ -599,14 +682,16 @@ void onApiSettingsPost(AsyncWebServerRequest *request) { settingsChanged = true; } - if (request->hasParam("ledBrightness", true)) { + if (request->hasParam("ledBrightness", true)) + { AsyncWebParameter *ledBrightness = request->getParam("ledBrightness", true); preferences.putUInt("ledBrightness", ledBrightness->value().toInt()); settingsChanged = true; } - if (request->hasParam("fullRefreshMin", true)) { + if (request->hasParam("fullRefreshMin", true)) + { AsyncWebParameter *fullRefreshMin = request->getParam("fullRefreshMin", true); @@ -614,7 +699,8 @@ void onApiSettingsPost(AsyncWebServerRequest *request) { settingsChanged = true; } - if (request->hasParam("wpTimeout", true)) { + if (request->hasParam("wpTimeout", true)) + { AsyncWebParameter *wpTimeout = request->getParam("wpTimeout", true); preferences.putUInt("wpTimeout", wpTimeout->value().toInt()); @@ -623,17 +709,20 @@ void onApiSettingsPost(AsyncWebServerRequest *request) { std::vector screenNameMap = getScreenNameMap(); - if (request->hasParam("screens")) { + if (request->hasParam("screens")) + { AsyncWebParameter *screenParam = request->getParam("screens", true); Serial.printf(screenParam->value().c_str()); } - for (int i = 0; i < screenNameMap.size(); i++) { + for (int i = 0; i < screenNameMap.size(); i++) + { String key = "screen[" + String(i) + "]"; String prefKey = "screen" + String(i) + "Visible"; bool visible = false; - if (request->hasParam(key, true)) { + if (request->hasParam(key, true)) + { AsyncWebParameter *screenParam = request->getParam(key, true); visible = screenParam->value().toInt(); } @@ -641,21 +730,24 @@ void onApiSettingsPost(AsyncWebServerRequest *request) { preferences.putBool(prefKey.c_str(), visible); } - if (request->hasParam("tzOffset", true)) { + if (request->hasParam("tzOffset", true)) + { AsyncWebParameter *p = request->getParam("tzOffset", true); int tzOffsetSeconds = p->value().toInt() * 60; preferences.putInt("gmtOffset", tzOffsetSeconds); settingsChanged = true; } - if (request->hasParam("minSecPriceUpd", true)) { + if (request->hasParam("minSecPriceUpd", true)) + { AsyncWebParameter *p = request->getParam("minSecPriceUpd", true); int minSecPriceUpd = p->value().toInt(); preferences.putUInt("minSecPriceUpd", minSecPriceUpd); settingsChanged = true; } - if (request->hasParam("timePerScreen", true)) { + if (request->hasParam("timePerScreen", true)) + { AsyncWebParameter *p = request->getParam("timePerScreen", true); uint timerSeconds = p->value().toInt() * 60; preferences.putUInt("timerSeconds", timerSeconds); @@ -663,12 +755,14 @@ void onApiSettingsPost(AsyncWebServerRequest *request) { } request->send(200); - if (settingsChanged) { + if (settingsChanged) + { queueLedEffect(LED_FLASH_SUCCESS); } } -void onApiSystemStatus(AsyncWebServerRequest *request) { +void onApiSystemStatus(AsyncWebServerRequest *request) +{ AsyncResponseStream *response = request->beginResponseStream("application/json"); @@ -689,12 +783,15 @@ void onApiSystemStatus(AsyncWebServerRequest *request) { #define STRINGIFY(x) #x #define ENUM_TO_STRING(x) STRINGIFY(x) -void onApiSetWifiTxPower(AsyncWebServerRequest *request) { - if (request->hasParam("txPower")) { +void onApiSetWifiTxPower(AsyncWebServerRequest *request) +{ + if (request->hasParam("txPower")) + { AsyncWebParameter *txPowerParam = request->getParam("txPower"); int txPower = txPowerParam->value().toInt(); if (static_cast(wifi_power_t::WIFI_POWER_MINUS_1dBm) <= txPower && - txPower <= static_cast(wifi_power_t::WIFI_POWER_19_5dBm)) { + txPower <= static_cast(wifi_power_t::WIFI_POWER_19_5dBm)) + { // is valid value String txPowerName = std::to_string( @@ -703,7 +800,8 @@ void onApiSetWifiTxPower(AsyncWebServerRequest *request) { Serial.printf("Set WiFi Tx power to: %s\n", txPowerName); - if (WiFi.setTxPower(static_cast(txPower))) { + if (WiFi.setTxPower(static_cast(txPower))) + { preferences.putInt("txPower", txPower); request->send(200, "application/json", "{\"setTxPower\": \"ok\"}"); return; @@ -714,7 +812,8 @@ void onApiSetWifiTxPower(AsyncWebServerRequest *request) { return request->send(400); } -void onApiLightsStatus(AsyncWebServerRequest *request) { +void onApiLightsStatus(AsyncWebServerRequest *request) +{ AsyncResponseStream *response = request->beginResponseStream("application/json"); @@ -723,7 +822,8 @@ void onApiLightsStatus(AsyncWebServerRequest *request) { request->send(response); } -void onApiStopDataSources(AsyncWebServerRequest *request) { +void onApiStopDataSources(AsyncWebServerRequest *request) +{ AsyncResponseStream *response = request->beginResponseStream("application/json"); @@ -733,33 +833,40 @@ void onApiStopDataSources(AsyncWebServerRequest *request) { request->send(response); } -void onApiRestartDataSources(AsyncWebServerRequest *request) { +void onApiRestartDataSources(AsyncWebServerRequest *request) +{ AsyncResponseStream *response = request->beginResponseStream("application/json"); restartPriceNotify(); restartBlockNotify(); -// setupPriceNotify(); -// setupBlockNotify(); + // setupPriceNotify(); + // setupBlockNotify(); request->send(response); } -void onApiLightsOff(AsyncWebServerRequest *request) { +void onApiLightsOff(AsyncWebServerRequest *request) +{ setLights(0, 0, 0); request->send(200); } -void onApiLightsSetColor(AsyncWebServerRequest *request) { - if (request->hasParam("c")) { +void onApiLightsSetColor(AsyncWebServerRequest *request) +{ + if (request->hasParam("c")) + { AsyncResponseStream *response = request->beginResponseStream("application/json"); String rgbColor = request->getParam("c")->value(); - if (rgbColor.compareTo("off") == 0) { + if (rgbColor.compareTo("off") == 0) + { setLights(0, 0, 0); - } else { + } + else + { uint r, g, b; sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b); setLights(r, g, b); @@ -771,18 +878,23 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) { serializeJson(getLedStatusObject()["data"], *response); request->send(response); - } else { + } + else + { request->send(400); } } -void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { +void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) +{ JsonArray lights = json.as(); - if (lights.size() != pixels.numPixels()) { - if (!lights.size()) { + if (lights.size() != pixels.numPixels()) + { + if (!lights.size()) + { // if empty, assume off request - return onApiLightsOff(request); + return onApiLightsOff(request); } Serial.printf("Invalid values for LED set %d\n", lights.size()); @@ -790,22 +902,29 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { return; } - for (uint i = 0; i < pixels.numPixels(); i++) { + for (uint i = 0; i < pixels.numPixels(); i++) + { unsigned int red, green, blue; if (lights[i].containsKey("red") && lights[i].containsKey("green") && - lights[i].containsKey("blue")) { + lights[i].containsKey("blue")) + { red = lights[i]["red"].as(); green = lights[i]["green"].as(); blue = lights[i]["blue"].as(); - } else if (lights[i].containsKey("hex")) { + } + else if (lights[i].containsKey("hex")) + { if (!sscanf(lights[i]["hex"].as().c_str(), "#%02X%02X%02X", &red, - &green, &blue) == 3) { + &green, &blue) == 3) + { Serial.printf("Invalid hex for LED %d\n", i); request->send(400); return; } - } else { + } + else + { Serial.printf("No valid color for LED %d\n", i); request->send(400); return; @@ -821,11 +940,13 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { request->send(200); } -void onIndex(AsyncWebServerRequest *request) { +void onIndex(AsyncWebServerRequest *request) +{ request->send(LittleFS, "/index.html", String(), false); } -void onNotFound(AsyncWebServerRequest *request) { +void onNotFound(AsyncWebServerRequest *request) +{ // Serial.printf("NotFound, URL[%s]\n", request->url()); // Serial.printf("NotFound, METHOD[%s]\n", request->methodToString()); @@ -863,42 +984,50 @@ void onNotFound(AsyncWebServerRequest *request) { // Access-Control-Request-Method == POST might be better if (request->method() == HTTP_OPTIONS || - request->hasHeader("Sec-Fetch-Mode")) { + request->hasHeader("Sec-Fetch-Mode")) + { // Serial.printf("NotFound, Return[%d]\n", 200); request->send(200); - } else { + } + else + { // Serial.printf("NotFound, Return[%d]\n", 404); request->send(404); } }; -void eventSourceTask(void *pvParameters) { - for (;;) { +void eventSourceTask(void *pvParameters) +{ + for (;;) + { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); eventSourceUpdate(); } } #ifdef HAS_FRONTLIGHT -void onApiFrontlightOn(AsyncWebServerRequest *request) { +void onApiFrontlightOn(AsyncWebServerRequest *request) +{ frontlightFadeInAll(); request->send(200); } -void onApiFrontlightStatus(AsyncWebServerRequest *request) { +void onApiFrontlightStatus(AsyncWebServerRequest *request) +{ AsyncResponseStream *response = request->beginResponseStream("application/json"); JsonDocument root; JsonArray ledStates = root["data"].to(); - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { - uint16_t onTime, offTime; - flArray.getPWM(ledPin, &onTime, &offTime); + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) + { + uint16_t onTime, offTime; + flArray.getPWM(ledPin, &onTime, &offTime); - ledStates.add(onTime); + ledStates.add(onTime); } serializeJson(ledStates, *response); @@ -906,7 +1035,8 @@ void onApiFrontlightStatus(AsyncWebServerRequest *request) { request->send(response); } -void onApiFrontlightOff(AsyncWebServerRequest *request) { +void onApiFrontlightOff(AsyncWebServerRequest *request) +{ frontlightFadeOutAll(); request->send(200); diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 95fcda2..21b2ca0 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -27,6 +27,8 @@ void onApiSetWifiTxPower(AsyncWebServerRequest *request); void onApiShowScreen(AsyncWebServerRequest *request); void onApiShowText(AsyncWebServerRequest *request); +void onApiIdentify(AsyncWebServerRequest *request); + void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json); void onApiActionPause(AsyncWebServerRequest *request); From 88615ce248b0d196b36729cb7fbde56d9b0e36f1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 19 May 2024 17:49:39 +0200 Subject: [PATCH 031/188] Add version detection --- src/lib/config.cpp | 20 +++++++++++++++++--- src/lib/config.hpp | 3 ++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 95ca5ab..214297f 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -18,8 +18,9 @@ uint lastTimeSync; void setup() { - setupPreferences(); setupHardware(); + + setupPreferences(); setupDisplays(); if (preferences.getBool("ledTestOnPower", true)) { @@ -196,8 +197,8 @@ void tryImprovSetup() // esp_task_wdt_deinit(); // esp_task_wdt_reset(); } - setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); - setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); + setFgColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); + setBgColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); } // else // { @@ -370,6 +371,11 @@ void setupHardware() #endif } +#ifdef IS_HW_REV_B +pinMode(39, INPUT_PULLUP); + +#endif + #ifdef IS_BTCLOCK_S3 if (!mcp2.begin_I2C(0x21)) { @@ -724,4 +730,12 @@ String getHwRev() { #else return HW_REV; #endif +} + +bool isWhiteVersion() { + #ifdef IS_HW_REV_B + return digitalRead(39); + #else + return false; + #endif } \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index f7ca85b..8c03023 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -63,4 +63,5 @@ void improv_send_response(std::vector &response); void improv_set_error(improv::Error error); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); -String getHwRev(); \ No newline at end of file +String getHwRev(); +bool isWhiteVersion(); \ No newline at end of file From f7599cb0ffed3c053943aabf423dc9a5b001e5dd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 19 May 2024 17:51:43 +0200 Subject: [PATCH 032/188] Dependency update --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 6fd28bf..c51b239 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,7 +37,7 @@ lib_deps = mathieucarbou/ESP Async WebServer adafruit/Adafruit BusIO@^1.16.0 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 - adafruit/Adafruit NeoPixel@^1.12.0 + adafruit/Adafruit NeoPixel@^1.12.2 https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 From 82e80f66e2cc2dd58daa42bba6b3d38d481bd4d9 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 25 May 2024 00:46:08 +0200 Subject: [PATCH 033/188] Explicit CDC on boot --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index c51b239..c188fe5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -54,6 +54,7 @@ build_flags = -D NUM_SCREENS=7 -D I2C_SDA_PIN=35 -D I2C_SCK_PIN=36 + -DARDUINO_USB_CDC_ON_BOOT=1 -D IS_HW_REV_A build_unflags = ${btclock_base.build_unflags} From 32e40e2cb7a49059ddd78fa1f0458ca130ec4cda Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 7 Jun 2024 15:18:34 +0200 Subject: [PATCH 034/188] Fix frontlight --- dependencies.lock | 4 ++-- src/lib/config.cpp | 26 +++++++++++++------------- src/lib/led_handler.cpp | 8 +++++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index 27f0397..6400884 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -3,7 +3,7 @@ dependencies: component_hash: null source: type: idf - version: 4.4.6 -manifest_hash: c799a313787fb26f19e5716786e23d7a9f10f1cd0bbd7bc379c518bb5e67be3e + version: 4.4.7 +manifest_hash: 615d994fdba8799111cf7825a85adb23ca6a2ae02b2335b53fde1188ff862f23 target: esp32s3 version: 1.0.0 diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 214297f..fd2ae09 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -18,9 +18,9 @@ uint lastTimeSync; void setup() { + setupPreferences(); setupHardware(); - setupPreferences(); setupDisplays(); if (preferences.getBool("ledTestOnPower", true)) { @@ -701,26 +701,26 @@ uint getLastTimeSync() #ifdef HAS_FRONTLIGHT void setupFrontlight() { - if (!flArray.begin(PCA9685_MODE1_AUTOINCR, PCA9685_MODE2_INVERT)) + if (!flArray.begin(PCA9685_MODE1_AUTOINCR | PCA9685_MODE1_ALLCALL, PCA9685_MODE2_TOTEMPOLE)) { Serial.println(F("FL driver error")); return; } Serial.println(F("FL driver active")); - flArray.setFrequency(1000); - flArray.setOutputEnablePin(PCA_OE_PIN); - flArray.setOutputEnable(true); - delay(1000); - flArray.setOutputEnable(false); + if (!preferences.isKey("flMaxBrightness")) { - preferences.putUInt("flMaxBrightness", 4095); + preferences.putUInt("flMaxBrightness", 2048); + } + + if (preferences.getBool("flAlwaysOn", false)) { + Serial.println(F("FL Always on")); + + frontlightFadeInAll(); + } else { + Serial.println(F("FL all off")); + flArray.allOFF(); } - // Initialize all LEDs to off - // for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { - // flArray.setPWM(ledPin, 0, 0); // Turn off LED - // } - flArray.allOFF(); } #endif diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 2c31e4f..5d37924 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -338,11 +338,11 @@ void ledTheaterChaseRainbow(int wait) { Adafruit_NeoPixel getPixels() { return pixels; } #ifdef HAS_FRONTLIGHT -int flDelayTime = 10; +int flDelayTime = 5; void frontlightFadeInAll() { for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { flArray.setPWM(ledPin, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); @@ -351,11 +351,13 @@ void frontlightFadeInAll() { void frontlightFadeOutAll() { for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { flArray.setPWM(ledPin, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } + + flArray.allOFF(); } void frontlightFadeIn(uint num) { From 9ede0f4dc33cba20e1c3f4802c43b1f06605d058 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 8 Jun 2024 01:00:52 +0200 Subject: [PATCH 035/188] Web UI improvements, better frontlight functionality --- data | 2 +- src/lib/block_notify.cpp | 2 +- src/lib/config.cpp | 26 +- src/lib/led_handler.cpp | 610 ++++++++++++++++++++++++++------------- src/lib/led_handler.hpp | 11 + src/lib/webserver.cpp | 34 ++- src/lib/webserver.hpp | 3 + 7 files changed, 477 insertions(+), 211 deletions(-) diff --git a/data b/data index 6ecb482..e859ada 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 6ecb4826000e4a34f22d98541d28448f844340f7 +Subproject commit e859adac8631c6380a6a3f8b20b118a5531d4f8c diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index c2fb092..ab546b0 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -148,7 +148,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) filter["block"]["height"] = true; filter["mempool-blocks"][0]["medianFee"] = true; - DeserializationError error = deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter)); + deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter)); // if (error) { // Serial.print("deserializeJson() failed: "); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index fd2ae09..ac5e8cb 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -67,6 +67,14 @@ void setup() setupOTA(); waitUntilNoneBusy(); + + #ifdef HAS_FRONTLIGHT + if (!preferences.getBool("flAlwaysOn", false)) { + frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); + flArray.allOFF(); + } + #endif + forceFullRefresh(); } @@ -712,15 +720,17 @@ void setupFrontlight() { preferences.putUInt("flMaxBrightness", 2048); } - - if (preferences.getBool("flAlwaysOn", false)) { - Serial.println(F("FL Always on")); - - frontlightFadeInAll(); - } else { - Serial.println(F("FL all off")); - flArray.allOFF(); + if (!preferences.isKey("flEffectDelay")) + { + preferences.putUInt("flEffectDelay", 5); } + + if (!preferences.isKey("flFlashOnUpd")) + { + preferences.putBool("flFlashOnUpd", false); + } + + frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); } #endif diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 5d37924..1c7799a 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -5,138 +5,357 @@ QueueHandle_t ledTaskQueue = NULL; Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); uint ledTaskParams; -void ledTask(void *parameter) { - while (1) { - if (ledTaskQueue != NULL) { - if (xQueueReceive(ledTaskQueue, &ledTaskParams, portMAX_DELAY) == - pdPASS) { +#ifdef HAS_FRONTLIGHT +#define FL_FADE_STEP 50 - if (preferences.getBool("disableLeds", false)) { +bool frontlightOn = false; +bool flInTransition = false; + +void frontlightFlash(int flDelayTime) +{ + if (frontlightOn) + { + frontlightFadeOutAll(flDelayTime); + frontlightFadeInAll(flDelayTime); + } + else + { + frontlightFadeInAll(flDelayTime); + frontlightFadeOutAll(flDelayTime); + } +} + +void frontlightFadeInAll() +{ + frontlightFadeInAll(preferences.getUInt("flEffectDelay")); +} + +void frontlightFadeOutAll() +{ + frontlightFadeOutAll(preferences.getUInt("flEffectDelay")); +} + +void frontlightFadeIn(uint num) +{ + frontlightFadeIn(num, preferences.getUInt("flEffectDelay")); +} + +void frontlightFadeOut(uint num) +{ + frontlightFadeOut(num, preferences.getUInt("flEffectDelay")); +} + +void frontlightSetBrightness(uint brightness) +{ + if (brightness > 4096) + { + return; + } + + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) + { + flArray.setPWM(ledPin, 0, brightness); + } +} + +void frontlightFadeInAll(int flDelayTime) +{ + frontlightFadeInAll(flDelayTime, false); +} + +void frontlightFadeInAll(int flDelayTime, bool staggered) +{ + if (flInTransition) + return; + + flInTransition = true; + + if (staggered) + { + int maxBrightness = preferences.getUInt("flMaxBrightness"); + int step = FL_FADE_STEP; + int staggerDelay = flDelayTime / NUM_SCREENS; + + for (int dutyCycle = 0; dutyCycle <= maxBrightness + (NUM_SCREENS - 1) * maxBrightness / NUM_SCREENS; dutyCycle += step) + { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) + { + int ledBrightness = dutyCycle - ledPin * maxBrightness / NUM_SCREENS; + if (ledBrightness < 0) + ledBrightness = 0; + else if (ledBrightness > maxBrightness) + ledBrightness = maxBrightness; + + flArray.setPWM(ledPin + 1, 0, ledBrightness); + } + vTaskDelay(pdMS_TO_TICKS(staggerDelay)); + } + } + else + { + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += FL_FADE_STEP) + { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) + { + flArray.setPWM(ledPin, 0, dutyCycle); + } + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } + } + frontlightOn = true; + flInTransition = false; +} + +void frontlightFadeOutAll(int flDelayTime) +{ + frontlightFadeOutAll(flDelayTime, false); +} + +void frontlightFadeOutAll(int flDelayTime, bool staggered) +{ + if (flInTransition) + return; + flInTransition = true; + + if (staggered) + { + int maxBrightness = preferences.getUInt("flMaxBrightness"); + int step = FL_FADE_STEP; + int staggerDelay = flDelayTime / NUM_SCREENS; + + for (int dutyCycle = maxBrightness; dutyCycle >= 0; dutyCycle -= step) + { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) + { + int ledBrightness = dutyCycle - (NUM_SCREENS - 1 - ledPin) * maxBrightness / NUM_SCREENS; + if (ledBrightness < 0) + ledBrightness = 0; + else if (ledBrightness > maxBrightness) + ledBrightness = maxBrightness; + + flArray.setPWM(ledPin + 1, 0, ledBrightness); + } + vTaskDelay(pdMS_TO_TICKS(staggerDelay)); + } + } + else + { + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) + { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) + { + flArray.setPWM(ledPin, 0, dutyCycle); + } + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } + } + + flArray.allOFF(); + frontlightOn = false; + flInTransition = false; +} + +void frontlightFadeIn(uint num, int flDelayTime) +{ + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) + { + flArray.setPWM(num, 0, dutyCycle); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } +} + +void frontlightFadeOut(uint num, int flDelayTime) +{ + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) + { + flArray.setPWM(num, 0, dutyCycle); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } +} +#endif + +void ledTask(void *parameter) +{ + while (1) + { + if (ledTaskQueue != NULL) + { + if (xQueueReceive(ledTaskQueue, &ledTaskParams, portMAX_DELAY) == + pdPASS) + { + + if (preferences.getBool("disableLeds", false)) + { continue; } uint32_t oldLights[NEOPIXEL_COUNT]; // get current state - for (int i = 0; i < NEOPIXEL_COUNT; i++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) + { oldLights[i] = pixels.getPixelColor(i); } - switch (ledTaskParams) { - case LED_POWER_TEST: - ledRainbow(20); - pixels.clear(); - break; - case LED_EFFECT_WIFI_CONNECT_ERROR: - blinkDelayTwoColor(100, 3, pixels.Color(8, 161, 236), - pixels.Color(255, 0, 0)); - break; - case LED_FLASH_ERROR: - blinkDelayColor(250, 3, 255, 0, 0); - break; - case LED_EFFECT_HEARTBEAT: - blinkDelayColor(150, 2, 0, 0, 255); - break; - case LED_DATA_BLOCK_ERROR: - blinkDelayColor(150, 2, 128, 0, 128); - break; - case LED_DATA_PRICE_ERROR: - blinkDelayColor(150, 2, 177, 90, 31); - break; - case LED_FLASH_IDENTIFY: - blinkDelayTwoColor(100, 2, pixels.Color(255, 0, 0), - pixels.Color(0, 255, 255)); - blinkDelayTwoColor(100, 2, pixels.Color(0, 255, 0), - pixels.Color(0, 0, 255)); - break; - case LED_EFFECT_WIFI_CONNECT_SUCCESS: - case LED_FLASH_SUCCESS: - blinkDelayColor(150, 3, 0, 255, 0); - break; - case LED_PROGRESS_100: - pixels.setPixelColor(0, pixels.Color(0, 255, 0)); - case LED_PROGRESS_75: - pixels.setPixelColor(1, pixels.Color(0, 255, 0)); - case LED_PROGRESS_50: - pixels.setPixelColor(2, pixels.Color(0, 255, 0)); - case LED_PROGRESS_25: - pixels.setPixelColor(3, pixels.Color(0, 255, 0)); - pixels.show(); - break; - case LED_FLASH_UPDATE: - break; - case LED_FLASH_BLOCK_NOTIFY: - blinkDelayTwoColor(250, 3, pixels.Color(224, 67, 0), - pixels.Color(8, 2, 0)); - break; - case LED_EFFECT_WIFI_WAIT_FOR_CONFIG: - blinkDelayTwoColor(100, 1, pixels.Color(8, 161, 236), - pixels.Color(156, 225, 240)); - break; - case LED_EFFECT_WIFI_ERASE_SETTINGS: - blinkDelay(100, 3); - break; - case LED_EFFECT_WIFI_CONNECTING: - for (int i = NEOPIXEL_COUNT; i >= 0; i--) { - for (int j = NEOPIXEL_COUNT; j >= 0; j--) { - if (j == i) { - pixels.setPixelColor(i, pixels.Color(16, 197, 236)); - } else { - pixels.setPixelColor(j, pixels.Color(0, 0, 0)); - } - } - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(100)); + switch (ledTaskParams) + { + case LED_POWER_TEST: + ledRainbow(20); + pixels.clear(); + break; + case LED_EFFECT_WIFI_CONNECT_ERROR: + blinkDelayTwoColor(100, 3, pixels.Color(8, 161, 236), + pixels.Color(255, 0, 0)); + break; + case LED_FLASH_ERROR: + blinkDelayColor(250, 3, 255, 0, 0); + break; + case LED_EFFECT_HEARTBEAT: + blinkDelayColor(150, 2, 0, 0, 255); + break; + case LED_DATA_BLOCK_ERROR: + blinkDelayColor(150, 2, 128, 0, 128); + break; + case LED_DATA_PRICE_ERROR: + blinkDelayColor(150, 2, 177, 90, 31); + break; + case LED_FLASH_IDENTIFY: + blinkDelayTwoColor(100, 2, pixels.Color(255, 0, 0), + pixels.Color(0, 255, 255)); + blinkDelayTwoColor(100, 2, pixels.Color(0, 255, 0), + pixels.Color(0, 0, 255)); + break; + case LED_EFFECT_WIFI_CONNECT_SUCCESS: + case LED_FLASH_SUCCESS: + blinkDelayColor(150, 3, 0, 255, 0); + break; + case LED_PROGRESS_100: + pixels.setPixelColor(0, pixels.Color(0, 255, 0)); + case LED_PROGRESS_75: + pixels.setPixelColor(1, pixels.Color(0, 255, 0)); + case LED_PROGRESS_50: + pixels.setPixelColor(2, pixels.Color(0, 255, 0)); + case LED_PROGRESS_25: + pixels.setPixelColor(3, pixels.Color(0, 255, 0)); + pixels.show(); + break; + case LED_FLASH_UPDATE: + break; + case LED_FLASH_BLOCK_NOTIFY: + { +#ifdef HAS_FRONTLIGHT + bool frontlightWasOn = false; + + if (preferences.getBool("flFlashOnUpd", false)) + { + if (frontlightOn) + { + frontlightWasOn = true; + frontlightFadeOutAll(1); } - break; - case LED_EFFECT_PAUSE_TIMER: - for (int i = NEOPIXEL_COUNT; i >= 0; i--) { - for (int j = NEOPIXEL_COUNT; j >= 0; j--) { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) c = pixels.Color(0, 255, 0); - pixels.setPixelColor(j, c); - } - - pixels.show(); - - delay(100); + else + { + frontlightFadeInAll(1); } - pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - pixels.show(); - - delay(900); - - pixels.clear(); - pixels.show(); - break; - case LED_EFFECT_START_TIMER: - pixels.clear(); - pixels.setPixelColor((NEOPIXEL_COUNT - 1), pixels.Color(255, 0, 0)); - pixels.show(); - - delay(900); - - for (int i = NEOPIXEL_COUNT; i--; i > 0) { - for (int j = NEOPIXEL_COUNT; j--; j > 0) { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) c = pixels.Color(0, 255, 0); - - pixels.setPixelColor(j, c); + } +#endif + blinkDelayTwoColor(250, 3, pixels.Color(224, 67, 0), + pixels.Color(8, 2, 0)); +#ifdef HAS_FRONTLIGHT + if (preferences.getBool("flFlashOnUpd", false)) + { + if (frontlightWasOn) + { + frontlightFadeInAll(1); + } + else + { + frontlightFadeOutAll(1); + } + } +#endif + break; + } + case LED_EFFECT_WIFI_WAIT_FOR_CONFIG: + blinkDelayTwoColor(100, 1, pixels.Color(8, 161, 236), + pixels.Color(156, 225, 240)); + break; + case LED_EFFECT_WIFI_ERASE_SETTINGS: + blinkDelay(100, 3); + break; + case LED_EFFECT_WIFI_CONNECTING: + for (int i = NEOPIXEL_COUNT; i >= 0; i--) + { + for (int j = NEOPIXEL_COUNT; j >= 0; j--) + { + if (j == i) + { + pixels.setPixelColor(i, pixels.Color(16, 197, 236)); } - - pixels.show(); - - delay(100); + else + { + pixels.setPixelColor(j, pixels.Color(0, 0, 0)); + } + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + break; + case LED_EFFECT_PAUSE_TIMER: + for (int i = NEOPIXEL_COUNT; i >= 0; i--) + { + for (int j = NEOPIXEL_COUNT; j >= 0; j--) + { + uint32_t c = pixels.Color(0, 0, 0); + if (i == j) + c = pixels.Color(0, 255, 0); + pixels.setPixelColor(j, c); } - pixels.clear(); pixels.show(); - break; + + delay(100); + } + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.show(); + + delay(900); + + pixels.clear(); + pixels.show(); + break; + case LED_EFFECT_START_TIMER: + pixels.clear(); + pixels.setPixelColor((NEOPIXEL_COUNT - 1), pixels.Color(255, 0, 0)); + pixels.show(); + + delay(900); + + for (int i = NEOPIXEL_COUNT; i--; i > 0) + { + for (int j = NEOPIXEL_COUNT; j--; j > 0) + { + uint32_t c = pixels.Color(0, 0, 0); + if (i == j) + c = pixels.Color(0, 255, 0); + + pixels.setPixelColor(j, c); + } + + pixels.show(); + + delay(100); + } + + pixels.clear(); + pixels.show(); + break; } // revert to previous state unless power test - for (int i = 0; i < NEOPIXEL_COUNT; i++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) + { pixels.setPixelColor(i, oldLights[i]); } @@ -146,14 +365,17 @@ void ledTask(void *parameter) { } } -void setupLeds() { +void setupLeds() +{ pixels.begin(); pixels.setBrightness(preferences.getUInt("ledBrightness", 128)); pixels.clear(); pixels.show(); setupLedTask(); - if (preferences.getBool("ledTestOnPower", true)) { - while (!ledTaskQueue) { + if (preferences.getBool("ledTestOnPower", true)) + { + while (!ledTaskQueue) + { delay(1); // wait until queue is available } @@ -161,14 +383,17 @@ void setupLeds() { } } -void setupLedTask() { +void setupLedTask() +{ ledTaskQueue = xQueueCreate(5, sizeof(uint)); - xTaskCreate(ledTask, "LedTask", 2048, NULL, tskIDLE_PRIORITY, &ledTaskHandle); + xTaskCreate(ledTask, "LedTask", 2048, NULL, 10, &ledTaskHandle); } -void blinkDelay(int d, int times) { - for (int j = 0; j < times; j++) { +void blinkDelay(int d, int times) +{ + for (int j = 0; j < times; j++) + { pixels.setPixelColor(0, pixels.Color(255, 0, 0)); pixels.setPixelColor(1, pixels.Color(0, 255, 0)); pixels.setPixelColor(2, pixels.Color(255, 0, 0)); @@ -187,9 +412,12 @@ void blinkDelay(int d, int times) { pixels.show(); } -void blinkDelayColor(int d, int times, uint r, uint g, uint b) { - for (int j = 0; j < times; j++) { - for (int i = 0; i < NEOPIXEL_COUNT; i++) { +void blinkDelayColor(int d, int times, uint r, uint g, uint b) +{ + for (int j = 0; j < times; j++) + { + for (int i = 0; i < NEOPIXEL_COUNT; i++) + { pixels.setPixelColor(i, pixels.Color(r, g, b)); } @@ -204,15 +432,19 @@ void blinkDelayColor(int d, int times, uint r, uint g, uint b) { pixels.show(); } -void blinkDelayTwoColor(int d, int times, uint32_t c1, uint32_t c2) { - for (int j = 0; j < times; j++) { - for (int i = 0; i < NEOPIXEL_COUNT; i++) { +void blinkDelayTwoColor(int d, int times, uint32_t c1, uint32_t c2) +{ + for (int j = 0; j < times; j++) + { + for (int i = 0; i < NEOPIXEL_COUNT; i++) + { pixels.setPixelColor(i, c1); } pixels.show(); vTaskDelay(pdMS_TO_TICKS(d)); - for (int i = 0; i < NEOPIXEL_COUNT; i++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) + { pixels.setPixelColor(i, c2); } pixels.show(); @@ -222,7 +454,8 @@ void blinkDelayTwoColor(int d, int times, uint32_t c1, uint32_t c2) { pixels.show(); } -void clearLeds() { +void clearLeds() +{ preferences.putBool("ledStatus", false); pixels.clear(); pixels.show(); @@ -230,24 +463,31 @@ void clearLeds() { void setLights(int r, int g, int b) { setLights(pixels.Color(r, g, b)); } -void setLights(uint32_t color) { +void setLights(uint32_t color) +{ bool ledStatus = true; - for (int i = 0; i < NEOPIXEL_COUNT; i++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) + { pixels.setPixelColor(i, color); } pixels.show(); - if (color == pixels.Color(0, 0, 0)) { + if (color == pixels.Color(0, 0, 0)) + { ledStatus = false; - } else { + } + else + { saveLedState(); } preferences.putBool("ledStatus", ledStatus); } -void saveLedState() { - for (int i = 0; i < pixels.numPixels(); i++) { +void saveLedState() +{ + for (int i = 0; i < pixels.numPixels(); i++) + { int pixelColor = pixels.getPixelColor(i); char key[12]; snprintf(key, 12, "%s%d", "ledColor_", i); @@ -257,8 +497,10 @@ void saveLedState() { xTaskNotifyGive(eventSourceTaskHandle); } -void restoreLedState() { - for (int i = 0; i < pixels.numPixels(); i++) { +void restoreLedState() +{ + for (int i = 0; i < pixels.numPixels(); i++) + { char key[12]; snprintf(key, 12, "%s%d", "ledColor_", i); uint pixelColor = preferences.getUInt(key, pixels.Color(0, 0, 0)); @@ -270,8 +512,10 @@ void restoreLedState() { QueueHandle_t getLedTaskQueue() { return ledTaskQueue; } -bool queueLedEffect(uint effect) { - if (ledTaskQueue == NULL) { +bool queueLedEffect(uint effect) +{ + if (ledTaskQueue == NULL) + { return false; } @@ -279,13 +523,15 @@ bool queueLedEffect(uint effect) { xQueueSend(ledTaskQueue, &flashType, portMAX_DELAY); } -void ledRainbow(int wait) { +void ledRainbow(int wait) +{ // Hue of first pixel runs 5 complete loops through the color wheel. // Color wheel has a range of 65536 but it's OK if we roll over, so // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time // means we'll make 5*65536/256 = 1280 passes through this loop: for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; - firstPixelHue += 256) { + firstPixelHue += 256) + { // strip.rainbow() can take a single argument (first pixel hue) or // optionally a few extras: number of rainbow repetitions (default 1), // saturation and value (brightness) (both 0-255, similar to the @@ -294,83 +540,53 @@ void ledRainbow(int wait) { pixels.rainbow(firstPixelHue); // Above line is equivalent to: // strip.rainbow(firstPixelHue, 1, 255, 255, true); - pixels.show(); // Update strip with new contents + pixels.show(); // Update strip with new contents delayMicroseconds(wait); // vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment } } -void ledTheaterChase(uint32_t color, int wait) { - for (int a = 0; a < 10; a++) { // Repeat 10 times... - for (int b = 0; b < 3; b++) { // 'b' counts from 0 to 2... - pixels.clear(); // Set all pixels in RAM to 0 (off) +void ledTheaterChase(uint32_t color, int wait) +{ + for (int a = 0; a < 10; a++) + { // Repeat 10 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + pixels.clear(); // Set all pixels in RAM to 0 (off) // 'c' counts up from 'b' to end of strip in steps of 3... - for (int c = b; c < pixels.numPixels(); c += 3) { - pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' + for (int c = b; c < pixels.numPixels(); c += 3) + { + pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' } - pixels.show(); // Update strip with new contents - vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment + pixels.show(); // Update strip with new contents + vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment } } } -void ledTheaterChaseRainbow(int wait) { - int firstPixelHue = 0; // First pixel starts at red (hue 0) - for (int a = 0; a < 30; a++) { // Repeat 30 times... - for (int b = 0; b < 3; b++) { // 'b' counts from 0 to 2... - pixels.clear(); // Set all pixels in RAM to 0 (off) +void ledTheaterChaseRainbow(int wait) +{ + int firstPixelHue = 0; // First pixel starts at red (hue 0) + for (int a = 0; a < 30; a++) + { // Repeat 30 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + pixels.clear(); // Set all pixels in RAM to 0 (off) // 'c' counts up from 'b' to end of strip in increments of 3... - for (int c = b; c < pixels.numPixels(); c += 3) { + for (int c = b; c < pixels.numPixels(); c += 3) + { // hue of pixel 'c' is offset by an amount to make one full // revolution of the color wheel (range 65536) along the length // of the strip (strip.numPixels() steps): int hue = firstPixelHue + c * 65536L / pixels.numPixels(); - uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); // hue -> RGB - pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' + uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); // hue -> RGB + pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' } - pixels.show(); // Update strip with new contents - vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment - firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames + pixels.show(); // Update strip with new contents + vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment + firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames } } } Adafruit_NeoPixel getPixels() { return pixels; } - -#ifdef HAS_FRONTLIGHT -int flDelayTime = 5; - -void frontlightFadeInAll() { - for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); - } - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } -} - -void frontlightFadeOutAll() { - for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); - } - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } - - flArray.allOFF(); -} - -void frontlightFadeIn(uint num) { - for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { - flArray.setPWM(num, 0, dutyCycle); - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } -} - -void frontlightFadeOut(uint num) { - for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { - flArray.setPWM(num, 0, dutyCycle); - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } -} -#endif \ No newline at end of file diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 8fba3bd..2157ac2 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -59,8 +59,19 @@ void ledTheaterChase(uint32_t color, int wait); Adafruit_NeoPixel getPixels(); #ifdef HAS_FRONTLIGHT +void frontlightFlash(int flDelayTime); void frontlightFadeInAll(); void frontlightFadeOutAll(); void frontlightFadeIn(uint num); void frontlightFadeOut(uint num); + +void frontlightSetBrightness(uint brightness); + +void frontlightFadeInAll(int flDelayTime); +void frontlightFadeInAll(int flDelayTime, bool staggered); +void frontlightFadeOutAll(int flDelayTime); +void frontlightFadeOutAll(int flDelayTime, bool staggered); + +void frontlightFadeIn(uint num, int flDelayTime); +void frontlightFadeOut(uint num, int flDelayTime); #endif \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 3c9269a..73ef946 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -56,8 +56,12 @@ void setupWebserver() #ifdef HAS_FRONTLIGHT server.on("/api/frontlight/on", HTTP_GET, onApiFrontlightOn); - server.on("/api/frontlight/status", HTTP_GET, onApiFrontlightStatus); + server.on("/api/frontlight/flash", HTTP_GET, onApiFrontlightFlash); + server.on("/api/frontlight/brightness", HTTP_GET, onApiFrontlightSetBrightness); server.on("/api/frontlight/off", HTTP_GET, onApiFrontlightOff); + + server.addRewrite( + new OneParamRewrite("/api/frontlight/brightness/{b}", "/api/frontlight/brightness?b={b}")); #endif // server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, @@ -334,7 +338,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } - String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness"}; + String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay"}; for (String setting : uintSettings) { @@ -357,7 +361,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd"}; for (String setting : boolSettings) { @@ -478,8 +482,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; - root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", 4095); + root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", 2048); root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", false); + root["flEffectDelay"] = preferences.getUInt("flEffectDelay"); + root["flFlashOnUpd"] = preferences.getBool("flFlashOnUpd", false); #else root["hasFrontlight"] = false; @@ -1035,6 +1041,26 @@ void onApiFrontlightStatus(AsyncWebServerRequest *request) request->send(response); } +void onApiFrontlightFlash(AsyncWebServerRequest *request) +{ + frontlightFlash(preferences.getUInt("flEffectDelay")); + + request->send(200); +} + +void onApiFrontlightSetBrightness(AsyncWebServerRequest *request) +{ + if (request->hasParam("b")) + { + frontlightSetBrightness(request->getParam("b")->value().toInt()); + request->send(200); + } + else + { + request->send(400); + } +} + void onApiFrontlightOff(AsyncWebServerRequest *request) { frontlightFadeOutAll(); diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 21b2ca0..817869a 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -58,6 +58,9 @@ void onApiRestartDataSources(AsyncWebServerRequest *request); #ifdef HAS_FRONTLIGHT void onApiFrontlightOn(AsyncWebServerRequest *request); +void onApiFrontlightFlash(AsyncWebServerRequest *request); +void onApiFrontlightSetBrightness(AsyncWebServerRequest *request); + void onApiFrontlightStatus(AsyncWebServerRequest *request); void onApiFrontlightOff(AsyncWebServerRequest *request); #endif \ No newline at end of file From 474ddbb086ea76076572b60d350182233587221c Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 8 Jun 2024 01:17:20 +0200 Subject: [PATCH 036/188] Update github workflow actions --- .github/actions/install-build/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/install-build/action.yml b/.github/actions/install-build/action.yml index bf67cab..bb521cd 100644 --- a/.github/actions/install-build/action.yml +++ b/.github/actions/install-build/action.yml @@ -9,14 +9,14 @@ runs: node-version: lts/* cache: yarn cache-dependency-path: '**/yarn.lock' - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | ~/.cache/pip ~/.platformio/.cache ~/data/node_modules key: ${{ runner.os }}-pio - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' - name: Get current date From 491618dd783d3f8f0b838de4c3ffc751b5e8ec0e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 8 Jun 2024 11:35:23 +0200 Subject: [PATCH 037/188] Fix light control API functionality, changed light setting endpoints --- data | 2 +- src/lib/config.cpp | 2 +- src/lib/led_handler.cpp | 11 ++++++----- src/lib/webserver.cpp | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/data b/data index e859ada..4f15eee 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e859adac8631c6380a6a3f8b20b118a5531d4f8c +Subproject commit 4f15eee72bcd3873a0edb61bc77638a0ff5e3724 diff --git a/src/lib/config.cpp b/src/lib/config.cpp index ac5e8cb..8a2f463 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -722,7 +722,7 @@ void setupFrontlight() } if (!preferences.isKey("flEffectDelay")) { - preferences.putUInt("flEffectDelay", 5); + preferences.putUInt("flEffectDelay", 15); } if (!preferences.isKey("flFlashOnUpd")) diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 1c7799a..aa2fda6 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -6,7 +6,7 @@ Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); uint ledTaskParams; #ifdef HAS_FRONTLIGHT -#define FL_FADE_STEP 50 +#define FL_FADE_STEP 25 bool frontlightOn = false; bool flInTransition = false; @@ -15,13 +15,13 @@ void frontlightFlash(int flDelayTime) { if (frontlightOn) { - frontlightFadeOutAll(flDelayTime); - frontlightFadeInAll(flDelayTime); + frontlightFadeOutAll(flDelayTime, true); + frontlightFadeInAll(flDelayTime, true); } else { - frontlightFadeInAll(flDelayTime); - frontlightFadeOutAll(flDelayTime); + frontlightFadeInAll(flDelayTime, true); + frontlightFadeOutAll(flDelayTime, true); } } @@ -264,6 +264,7 @@ void ledTask(void *parameter) #ifdef HAS_FRONTLIGHT if (preferences.getBool("flFlashOnUpd", false)) { + vTaskDelay(pdMS_TO_TICKS(10)); if (frontlightWasOn) { frontlightFadeInAll(1); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 73ef946..7b73823 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -46,7 +46,7 @@ void setupWebserver() server.addHandler(handler); AsyncCallbackJsonWebHandler *lightsJsonHandler = - new AsyncCallbackJsonWebHandler("/api/lights", onApiLightsSetJson); + new AsyncCallbackJsonWebHandler("/api/lights/set", onApiLightsSetJson); server.addHandler(lightsJsonHandler); server.on("/api/lights/off", HTTP_GET, onApiLightsOff); @@ -68,7 +68,7 @@ void setupWebserver() // onApiLightsSetColor); server.on("/api/restart", HTTP_GET, onApiRestart); - server.addRewrite(new OneParamRewrite("/api/lights/{color}", + server.addRewrite(new OneParamRewrite("/api/lights/color/{color}", "/api/lights/color?c={color}")); server.addRewrite( new OneParamRewrite("/api/show/screen/{s}", "/api/show/screen?s={s}")); From 313efb7604e012d962aa192163dd103967d4d51e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 8 Jun 2024 15:07:15 +0200 Subject: [PATCH 038/188] Better handling of steal screen, add firmware web update functionality --- src/lib/block_notify.cpp | 1 + src/lib/webserver.cpp | 86 ++++++++++++++++++++++++++++++++++++++++ src/lib/webserver.hpp | 4 ++ 3 files changed, 91 insertions(+) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index ab546b0..83a3be3 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -192,6 +192,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } + vTaskDelay(pdMS_TO_TICKS(315*NUM_SCREENS)); // Extra delay because of screen switching } if (preferences.getBool("ledFlashOnUpd", false)) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 7b73823..d242eb7 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -67,6 +67,9 @@ void setupWebserver() // server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, // onApiLightsSetColor); + server.on("/firmware/update", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); + server.on("/firmware/update_webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); + server.on("/api/restart", HTTP_GET, onApiRestart); server.addRewrite(new OneParamRewrite("/api/lights/color/{color}", "/api/lights/color?c={color}")); @@ -109,6 +112,89 @@ void setupWebserver() void stopWebServer() { server.end(); } +void onFirmwareUpdate(AsyncWebServerRequest *request) +{ + bool shouldReboot = !Update.hasError(); + AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot ? "OK" : "FAIL"); + response->addHeader("Connection", "close"); + request->send(response); +} + + + +void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + asyncFileUpdateHandler(request, filename, index, data, len, final, U_SPIFFS); +} + +void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, int command) +{ + if (!index) + { + Serial.printf("Update Start: %s\n", filename.c_str()); + + // Update.runAsync(true); + if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000), command) + { + Update.printError(Serial); + } + } + if (!Update.hasError()) + { + if (Update.write(data, len) != len) + { + Update.printError(Serial); + } + } + if (final) + { + if (Update.end(true)) + { + Serial.printf("Update Success: %uB\n", index + len); + onApiRestart(request); + } + else + { + Update.printError(Serial); + } + } +} + +void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + asyncFileUpdateHandler(request, filename, index, data, len, final, U_FLASH); + + // if (!index) + // { + // Serial.printf("Update Start: %s\n", filename.c_str()); + + // // Update.runAsync(true); + // if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) + // { + // Update.printError(Serial); + // } + // } + // if (!Update.hasError()) + // { + // if (Update.write(data, len) != len) + // { + // Update.printError(Serial); + // } + // } + // if (final) + // { + // if (Update.end(true)) + // { + // Serial.printf("Update Success: %uB\n", index + len); + // onApiRestart(request); + // } + // else + // { + // Update.printError(Serial); + // } + // } +} + JsonDocument getStatusObject() { JsonDocument root; diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 817869a..3d16b35 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -44,6 +44,10 @@ void onApiLightsSetColor(AsyncWebServerRequest *request); void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json); void onApiRestart(AsyncWebServerRequest *request); +void onFirmwareUpdate(AsyncWebServerRequest *request); +void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, int command); +void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void onIndex(AsyncWebServerRequest *request); void onNotFound(AsyncWebServerRequest *request); From 2a8e39134276e0bb15d6f8b2e0339f9da7be3dfd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 9 Jun 2024 00:45:46 +0200 Subject: [PATCH 039/188] Add web UI flasher functionality --- data | 2 +- scripts/git_rev.py | 15 ++++++++++++++- src/lib/config.cpp | 44 +++++++++++++++++++++++++++++-------------- src/lib/config.hpp | 4 +++- src/lib/webserver.cpp | 38 ++++++++++++++++++++++++++++--------- 5 files changed, 77 insertions(+), 26 deletions(-) diff --git a/data b/data index 4f15eee..2a7ba58 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 4f15eee72bcd3873a0edb61bc77638a0ff5e3724 +Subproject commit 2a7ba588e2ab06d433e9ac3f6882bfd4a7f714fe diff --git a/scripts/git_rev.py b/scripts/git_rev.py index 9594475..33fd8cc 100644 --- a/scripts/git_rev.py +++ b/scripts/git_rev.py @@ -5,4 +5,17 @@ revision = ( .strip() .decode("utf-8") ) -print("'-DGIT_REV=\"%s\"'" % revision) \ No newline at end of file + +try: + tag = ( + subprocess.check_output(["git", "describe", "--tags", "--exact-match"]) + .strip() + .decode("utf-8") + ) + git_tag_define = '-DGIT_TAG="%s"' % tag +except subprocess.CalledProcessError: + git_tag_define = '' + +print("'-DGIT_REV=\"%s\"'" % revision) +if git_tag_define: + print(git_tag_define) \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 8a2f463..032dc19 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -68,12 +68,13 @@ void setup() waitUntilNoneBusy(); - #ifdef HAS_FRONTLIGHT - if (!preferences.getBool("flAlwaysOn", false)) { +#ifdef HAS_FRONTLIGHT + if (!preferences.getBool("flAlwaysOn", false)) + { frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); flArray.allOFF(); } - #endif +#endif forceFullRefresh(); } @@ -380,7 +381,7 @@ void setupHardware() } #ifdef IS_HW_REV_B -pinMode(39, INPUT_PULLUP); + pinMode(39, INPUT_PULLUP); #endif @@ -734,18 +735,33 @@ void setupFrontlight() } #endif -String getHwRev() { - #ifndef HW_REV - return "REV_0"; - #else - return HW_REV; - #endif +String getHwRev() +{ +#ifndef HW_REV + return "REV_0"; +#else + return HW_REV; +#endif } -bool isWhiteVersion() { - #ifdef IS_HW_REV_B +bool isWhiteVersion() +{ +#ifdef IS_HW_REV_B return digitalRead(39); - #else +#else return false; - #endif +#endif +} + +String getFsRev() +{ + File fsHash = LittleFS.open("/fs_hash.txt", "r"); + if (!fsHash) + { + Serial.println(F("Error loading WebUI")); + } + + String ret = fsHash.readString(); + fsHash.close(); + return ret; } \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 8c03023..b6fae71 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -64,4 +64,6 @@ void improv_set_error(improv::Error error); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); String getHwRev(); -bool isWhiteVersion(); \ No newline at end of file +bool isWhiteVersion(); + +String getFsRev(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index d242eb7..accc7c2 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -67,8 +67,10 @@ void setupWebserver() // server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, // onApiLightsSetColor); - server.on("/firmware/update", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); - server.on("/firmware/update_webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); + if (preferences.getBool("otaEnabled", true)) { + server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); + server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); + } server.on("/api/restart", HTTP_GET, onApiRestart); server.addRewrite(new OneParamRewrite("/api/lights/color/{color}", @@ -120,8 +122,6 @@ void onFirmwareUpdate(AsyncWebServerRequest *request) request->send(response); } - - void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { asyncFileUpdateHandler(request, filename, index, data, len, final, U_SPIFFS); @@ -129,14 +129,27 @@ void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, si void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, int command) { - if (!index) + if (!index) { Serial.printf("Update Start: %s\n", filename.c_str()); - // Update.runAsync(true); - if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000), command) + if (command == U_FLASH) { - Update.printError(Serial); + // Update.runAsync(true); + if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000), command) + { + Update.printError(Serial); + return; + } + } + else if (command == U_SPIFFS) + { + size_t fsSize = UPDATE_SIZE_UNKNOWN; // or specify the size of your filesystem partition + if (!Update.begin(fsSize, U_SPIFFS)) // or U_FS for LittleFS + { + Update.printError(Serial); + return; + } } } if (!Update.hasError()) @@ -162,7 +175,7 @@ void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, siz void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - asyncFileUpdateHandler(request, filename, index, data, len, final, U_FLASH); + asyncFileUpdateHandler(request, filename, index, data, len, final, U_FLASH); // if (!index) // { @@ -577,9 +590,16 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["hasFrontlight"] = false; #endif + root["hwRev"] = getHwRev(); + root["fsRev"] = getFsRev(); + #ifdef GIT_REV root["gitRev"] = String(GIT_REV); #endif +#ifdef GIT_TAG + root["gitTag"] = String(GIT_TAG); +#endif + #ifdef LAST_BUILD_TIME root["lastBuildTime"] = String(LAST_BUILD_TIME); #endif From 08929eb552d3b07997047ba5cfcff42f42894618 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 9 Jun 2024 01:07:04 +0200 Subject: [PATCH 040/188] Fix tagging script, show tag in UI --- data | 2 +- scripts/git_rev.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data b/data index 2a7ba58..5b3a071 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 2a7ba588e2ab06d433e9ac3f6882bfd4a7f714fe +Subproject commit 5b3a0712847d292d1aefcf8eb2b1062a24556fb2 diff --git a/scripts/git_rev.py b/scripts/git_rev.py index 33fd8cc..5ac166a 100644 --- a/scripts/git_rev.py +++ b/scripts/git_rev.py @@ -12,7 +12,7 @@ try: .strip() .decode("utf-8") ) - git_tag_define = '-DGIT_TAG="%s"' % tag + git_tag_define = '\'-DGIT_TAG=\"%s\"\'' % tag except subprocess.CalledProcessError: git_tag_define = '' From a8baa085c724476a02c889e70510af8cb7e6532c Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 9 Jun 2024 11:31:25 +0200 Subject: [PATCH 041/188] Fix setting defaults for stealing focus and block countdown --- data | 2 +- src/lib/block_notify.cpp | 2 +- src/lib/screen_handler.cpp | 2 +- src/lib/webserver.cpp | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data b/data index 5b3a071..2b2c8e7 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 5b3a0712847d292d1aefcf8eb2b1062a24556fb2 +Subproject commit 2b2c8e7fc72a4e0108caf63bccb2e8bf0a5f21a0 diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 83a3be3..e109aa7 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -177,7 +177,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) // xTaskNotifyGive(blockUpdateTaskHandle); if (getCurrentScreen() != SCREEN_BLOCK_HEIGHT && - preferences.getBool("stealFocus", true)) + preferences.getBool("stealFocus", false)) { uint64_t timerPeriod = 0; if (isTimerActive()) diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index d1215f6..d0fa761 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -57,7 +57,7 @@ void workerTask(void *pvParameters) { if (getCurrentScreen() != SCREEN_HALVING_COUNTDOWN) { taskEpdContent = parseBlockHeight(getBlockHeight()); } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", false)); + taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", true)); } if (getCurrentScreen() == SCREEN_HALVING_COUNTDOWN || diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index accc7c2..06fb77c 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -569,7 +569,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["otaEnabled"] = preferences.getBool("otaEnabled", true); root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", false); root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", false); - root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", false); + root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", true); root["suffixPrice"] = preferences.getBool("suffixPrice", false); root["disableLeds"] = preferences.getBool("disableLeds", false); @@ -749,10 +749,10 @@ void onApiSettingsPost(AsyncWebServerRequest *request) settingsChanged = true; } - if (request->hasParam("stealFocusOnBlock", true)) + if (request->hasParam("stealFocus", false)) { AsyncWebParameter *stealFocusOnBlock = - request->getParam("stealFocusOnBlock", true); + request->getParam("stealFocus", false); preferences.putBool("stealFocus", stealFocusOnBlock->value().toInt()); settingsChanged = true; From 262eae22dc9975cbe70ba0c89f6bb561472868c3 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 28 Jun 2024 15:18:40 +0200 Subject: [PATCH 042/188] Fix for EUR fetch in combination with own datasource, WebUI: detect timezone from browser --- data | 2 +- src/lib/block_notify.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data b/data index 2b2c8e7..fd76caa 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 2b2c8e7fc72a4e0108caf63bccb2e8bf0a5f21a0 +Subproject commit fd76caa6f4168ca345b6f847f85e5fc9bb6543b2 diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index e109aa7..29a7dcf 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -87,7 +87,7 @@ void setupBlockNotify() xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } - if (preferences.getBool("ownDataSource", true)) + if (!preferences.getBool("fetchEurPrice", false) && preferences.getBool("ownDataSource", true)) { return; } From 24c3b46365d0933607a6a1742254910cf5662507 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 28 Jun 2024 17:36:46 +0200 Subject: [PATCH 043/188] Allow custom mempool instance --- data | 2 +- src/lib/block_notify.cpp | 57 +++++++++++++++++++++++++++++----------- src/lib/config.cpp | 6 ++++- src/lib/config.hpp | 4 ++- src/lib/webserver.cpp | 5 ++-- 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/data b/data index fd76caa..52e90db 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit fd76caa6f4168ca345b6f847f85e5fc9bb6543b2 +Subproject commit 52e90dbdee7f53dcca5a9857dd411f98a569263a diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 29a7dcf..2ef4268 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -2,7 +2,7 @@ char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; -uint currentBlockHeight = 816000; +uint currentBlockHeight = 840000; uint blockMedianFee = 1; bool blockNotifyInit = false; unsigned long int lastBlockUpdate; @@ -59,7 +59,7 @@ void setupBlockNotify() String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - while (dnsErr != 1) + while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) { dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); @@ -73,8 +73,10 @@ void setupBlockNotify() } // Get current block height through regular API + int blockFetch = getBlockFetch(); - currentBlockHeight = getBlockFetch(); + if (blockFetch > currentBlockHeight) + currentBlockHeight = blockFetch; if (currentBlockHeight != -1) { @@ -95,13 +97,19 @@ void setupBlockNotify() // std::strcpy(wsServer, String("wss://" + mempoolInstance + // "/api/v1/ws").c_str()); + const String protocol = preferences.getBool("mempoolSecure", true) ? "wss" : "ws"; + + String mempoolUri = protocol + "://" + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE) + "/api/v1/ws"; + esp_websocket_client_config_t config = { - .uri = "wss://mempool.space/api/v1/ws", + // .uri = "wss://mempool.space/api/v1/ws", // .task_stack = (6*1024), // .cert_pem = mempoolWsCert, .user_agent = USER_AGENT, }; + config.uri = mempoolUri.c_str(); + blockNotifyClient = esp_websocket_client_init(&config); esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY, onWebsocketBlockEvent, blockNotifyClient); @@ -282,21 +290,40 @@ void restartBlockNotify() int getBlockFetch() { - String mempoolInstance = - preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + try { + WiFiClientSecure client; + client.setInsecure(); - // Get current block height through regular API - HTTPClient *http = new HTTPClient(); - http->begin("https://" + mempoolInstance + "/api/blocks/tip/height"); - int httpCode = http->GET(); + String mempoolInstance = + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - if (httpCode > 0 && httpCode == HTTP_CODE_OK) - { - String blockHeightStr = http->getString(); - return blockHeightStr.toInt(); + // Get current block height through regular API + HTTPClient http; + + const String protocol = preferences.getBool("mempoolSecure", true) ? "https" : "http"; + + if (preferences.getBool("mempoolSecure", true)) + http.begin(client, protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); + else + http.begin(protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); + + Serial.println("Fetching block height from " + protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); + int httpCode = http.GET(); + + if (httpCode > 0 && httpCode == HTTP_CODE_OK) + { + String blockHeightStr = http.getString(); + return blockHeightStr.toInt(); + } else { + Serial.println("HTTP code" + String(httpCode)); + return 0; + } + } + catch (...) { + Serial.println(F("An exception occured while trying to get the latest block")); } - return -1; + return 2203; // B-T-C } uint getLastBlockUpdate() diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 032dc19..2a8bc7d 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -46,6 +46,10 @@ void setup() { delay(1000); } + } else if (mcp1.digitalRead(1) == LOW) { + preferences.clear(); + queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); + ESP.restart(); } } @@ -60,7 +64,7 @@ void setup() setupTasks(); setupTimers(); - xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 4096, NULL, + xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, tskIDLE_PRIORITY, NULL); setupButtonTask(); diff --git a/src/lib/config.hpp b/src/lib/config.hpp index b6fae71..51c0086 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -27,8 +27,10 @@ #define NTP_SERVER "pool.ntp.org" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" #define TIME_OFFSET_SECONDS 3600 -#define USER_AGENT "BTClock/2.0" +#define USER_AGENT "BTClock/3.0" +#ifndef MCP_DEV_ADDR #define MCP_DEV_ADDR 0x20 +#endif #define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30 #define DEFAULT_MINUTES_FULL_REFRESH 60 diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 06fb77c..cd4e3fa 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -460,7 +460,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure"}; for (String setting : boolSettings) { @@ -557,9 +557,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH); root["wpTimeout"] = preferences.getUInt("wpTimeout", 600); root["tzOffset"] = preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS) / 60; - root["useBitcoinNode"] = preferences.getBool("useNode", false); +// root["useBitcoinNode"] = preferences.getBool("useNode", false); root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + root["mempoolSecure"] = preferences.getBool("mempoolSecure", true); root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", true); root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", false); root["ledBrightness"] = preferences.getUInt("ledBrightness", 128); From a9489c30f696eab03c8c12c07d4ea3e0adae2e26 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 28 Jun 2024 17:56:08 +0200 Subject: [PATCH 044/188] Upgrade and pin dependencies --- platformio.ini | 6 +- src/lib/webserver.cpp | 380 +++++++++++++++++++++--------------------- 2 files changed, 193 insertions(+), 193 deletions(-) diff --git a/platformio.ini b/platformio.ini index c188fe5..f061e74 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,10 +32,10 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.0.4 + bblanchon/ArduinoJson@^7.1.0 esphome/Improv@^1.2.3 - mathieucarbou/ESP Async WebServer - adafruit/Adafruit BusIO@^1.16.0 + mathieucarbou/ESP Async WebServer@2.10.8 + adafruit/Adafruit BusIO@^1.16.1 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.2 https://github.com/dsbaars/universal_pin diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index cd4e3fa..f0db255 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -348,7 +348,7 @@ void onApiShowScreen(AsyncWebServerRequest *request) { if (request->hasParam("s")) { - AsyncWebParameter *p = request->getParam("s"); + const AsyncWebParameter *p = request->getParam("s"); uint currentScreen = p->value().toInt(); setCurrentScreen(currentScreen); } @@ -359,7 +359,7 @@ void onApiShowText(AsyncWebServerRequest *request) { if (request->hasParam("t")) { - AsyncWebParameter *p = request->getParam("t"); + const AsyncWebParameter *p = request->getParam("t"); String t = p->value(); t.toUpperCase(); // This is needed as long as lowercase letters are glitchy @@ -629,7 +629,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) bool settingsChanged = false; if (request->hasParam("fgColor", true)) { - AsyncWebParameter *fgColor = request->getParam("fgColor", true); + const AsyncWebParameter *fgColor = request->getParam("fgColor", true); preferences.putUInt("fgColor", strtol(fgColor->value().c_str(), NULL, 16)); setFgColor(int(strtol(fgColor->value().c_str(), NULL, 16))); // Serial.print(F("Setting foreground color to ")); @@ -638,7 +638,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) } if (request->hasParam("bgColor", true)) { - AsyncWebParameter *bgColor = request->getParam("bgColor", true); + const AsyncWebParameter *bgColor = request->getParam("bgColor", true); preferences.putUInt("bgColor", strtol(bgColor->value().c_str(), NULL, 16)); setBgColor(int(strtol(bgColor->value().c_str(), NULL, 16))); @@ -652,226 +652,226 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) void onApiSettingsPost(AsyncWebServerRequest *request) { - bool settingsChanged = false; + // bool settingsChanged = false; - settingsChanged = processEpdColorSettings(request); + // settingsChanged = processEpdColorSettings(request); - int headers = request->headers(); - int i; - for (i = 0; i < headers; i++) - { - AsyncWebHeader *h = request->getHeader(i); - Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); - } + // int headers = request->headers(); + // int i; + // for (i = 0; i < headers; i++) + // { + // AsyncWebHeader *h = request->getHeader(i); + // Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); + // } - int params = request->params(); - for (int i = 0; i < params; i++) - { - AsyncWebParameter *p = request->getParam(i); - if (p->isFile()) - { // p->isPost() is also true - Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), - p->value().c_str(), p->size()); - } - else if (p->isPost()) - { - Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } - else - { - Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } - } + // int params = request->params(); + // for (int i = 0; i < params; i++) + // { + // const AsyncWebParameter *p = request->getParam(i); + // if (p->isFile()) + // { // p->isPost() is also true + // Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), + // p->value().c_str(), p->size()); + // } + // else if (p->isPost()) + // { + // Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + // } + // else + // { + // Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); + // } + // } - if (request->hasParam("fetchEurPrice", true)) - { - AsyncWebParameter *fetchEurPrice = request->getParam("fetchEurPrice", true); + // if (request->hasParam("fetchEurPrice", true)) + // { + // const AsyncWebParameter *fetchEurPrice = request->getParam("fetchEurPrice", true); - preferences.putBool("fetchEurPrice", fetchEurPrice->value().toInt()); - settingsChanged = true; - } - else - { - preferences.putBool("fetchEurPrice", 0); - settingsChanged = true; - } + // preferences.putBool("fetchEurPrice", fetchEurPrice->value().toInt()); + // settingsChanged = true; + // } + // else + // { + // preferences.putBool("fetchEurPrice", 0); + // settingsChanged = true; + // } - if (request->hasParam("ledTestOnPower", true)) - { - AsyncWebParameter *ledTestOnPower = - request->getParam("ledTestOnPower", true); + // if (request->hasParam("ledTestOnPower", true)) + // { + // const AsyncWebParameter *ledTestOnPower = + // request->getParam("ledTestOnPower", true); - preferences.putBool("ledTestOnPower", ledTestOnPower->value().toInt()); - settingsChanged = true; - } - else - { - preferences.putBool("ledTestOnPower", 0); - settingsChanged = true; - } + // preferences.putBool("ledTestOnPower", ledTestOnPower->value().toInt()); + // settingsChanged = true; + // } + // else + // { + // preferences.putBool("ledTestOnPower", 0); + // settingsChanged = true; + // } - if (request->hasParam("ledFlashOnUpd", true)) - { - AsyncWebParameter *ledFlashOnUpdate = - request->getParam("ledFlashOnUpd", true); + // if (request->hasParam("ledFlashOnUpd", true)) + // { + // const AsyncWebParameter *ledFlashOnUpdate = + // request->getParam("ledFlashOnUpd", true); - preferences.putBool("ledFlashOnUpd", ledFlashOnUpdate->value().toInt()); - settingsChanged = true; - } - else - { - preferences.putBool("ledFlashOnUpd", 0); - settingsChanged = true; - } + // preferences.putBool("ledFlashOnUpd", ledFlashOnUpdate->value().toInt()); + // settingsChanged = true; + // } + // else + // { + // preferences.putBool("ledFlashOnUpd", 0); + // settingsChanged = true; + // } - if (request->hasParam("mdnsEnabled", true)) - { - AsyncWebParameter *mdnsEnabled = request->getParam("mdnsEnabled", true); + // if (request->hasParam("mdnsEnabled", true)) + // { + // const AsyncWebParameter *mdnsEnabled = request->getParam("mdnsEnabled", true); - preferences.putBool("mdnsEnabled", mdnsEnabled->value().toInt()); - settingsChanged = true; - } - else - { - preferences.putBool("mdnsEnabled", 0); - settingsChanged = true; - } + // preferences.putBool("mdnsEnabled", mdnsEnabled->value().toInt()); + // settingsChanged = true; + // } + // else + // { + // preferences.putBool("mdnsEnabled", 0); + // settingsChanged = true; + // } - if (request->hasParam("otaEnabled", true)) - { - AsyncWebParameter *otaEnabled = request->getParam("otaEnabled", true); + // if (request->hasParam("otaEnabled", true)) + // { + // const AsyncWebParameter *otaEnabled = request->getParam("otaEnabled", true); - preferences.putBool("otaEnabled", otaEnabled->value().toInt()); - settingsChanged = true; - } - else - { - preferences.putBool("otaEnabled", 0); - settingsChanged = true; - } + // preferences.putBool("otaEnabled", otaEnabled->value().toInt()); + // settingsChanged = true; + // } + // else + // { + // preferences.putBool("otaEnabled", 0); + // settingsChanged = true; + // } - if (request->hasParam("stealFocus", false)) - { - AsyncWebParameter *stealFocusOnBlock = - request->getParam("stealFocus", false); + // if (request->hasParam("stealFocus", false)) + // { + // const AsyncWebParameter *stealFocusOnBlock = + // request->getParam("stealFocus", false); - preferences.putBool("stealFocus", stealFocusOnBlock->value().toInt()); - settingsChanged = true; - } - else - { - preferences.putBool("stealFocus", 0); - settingsChanged = true; - } + // preferences.putBool("stealFocus", stealFocusOnBlock->value().toInt()); + // settingsChanged = true; + // } + // else + // { + // preferences.putBool("stealFocus", 0); + // settingsChanged = true; + // } - if (request->hasParam("mcapBigChar", true)) - { - AsyncWebParameter *mcapBigChar = request->getParam("mcapBigChar", true); + // if (request->hasParam("mcapBigChar", true)) + // { + // const AsyncWebParameter *mcapBigChar = request->getParam("mcapBigChar", true); - preferences.putBool("mcapBigChar", mcapBigChar->value().toInt()); - settingsChanged = true; - } - else - { - preferences.putBool("mcapBigChar", 0); - settingsChanged = true; - } + // preferences.putBool("mcapBigChar", mcapBigChar->value().toInt()); + // settingsChanged = true; + // } + // else + // { + // preferences.putBool("mcapBigChar", 0); + // settingsChanged = true; + // } - if (request->hasParam("mempoolInstance", true)) - { - AsyncWebParameter *mempoolInstance = - request->getParam("mempoolInstance", true); + // if (request->hasParam("mempoolInstance", true)) + // { + // const AsyncWebParameter *mempoolInstance = + // request->getParam("mempoolInstance", true); - preferences.putString("mempoolInstance", mempoolInstance->value().c_str()); - settingsChanged = true; - } + // preferences.putString("mempoolInstance", mempoolInstance->value().c_str()); + // settingsChanged = true; + // } - if (request->hasParam("hostnamePrefix", true)) - { - AsyncWebParameter *hostnamePrefix = - request->getParam("hostnamePrefix", true); + // if (request->hasParam("hostnamePrefix", true)) + // { + // const AsyncWebParameter *hostnamePrefix = + // request->getParam("hostnamePrefix", true); - preferences.putString("hostnamePrefix", hostnamePrefix->value().c_str()); - settingsChanged = true; - } + // preferences.putString("hostnamePrefix", hostnamePrefix->value().c_str()); + // settingsChanged = true; + // } - if (request->hasParam("ledBrightness", true)) - { - AsyncWebParameter *ledBrightness = request->getParam("ledBrightness", true); + // if (request->hasParam("ledBrightness", true)) + // { + // const AsyncWebParameter *ledBrightness = request->getParam("ledBrightness", true); - preferences.putUInt("ledBrightness", ledBrightness->value().toInt()); - settingsChanged = true; - } + // preferences.putUInt("ledBrightness", ledBrightness->value().toInt()); + // settingsChanged = true; + // } - if (request->hasParam("fullRefreshMin", true)) - { - AsyncWebParameter *fullRefreshMin = - request->getParam("fullRefreshMin", true); + // if (request->hasParam("fullRefreshMin", true)) + // { + // const AsyncWebParameter *fullRefreshMin = + // request->getParam("fullRefreshMin", true); - preferences.putUInt("fullRefreshMin", fullRefreshMin->value().toInt()); - settingsChanged = true; - } + // preferences.putUInt("fullRefreshMin", fullRefreshMin->value().toInt()); + // settingsChanged = true; + // } - if (request->hasParam("wpTimeout", true)) - { - AsyncWebParameter *wpTimeout = request->getParam("wpTimeout", true); + // if (request->hasParam("wpTimeout", true)) + // { + // const AsyncWebParameter *wpTimeout = request->getParam("wpTimeout", true); - preferences.putUInt("wpTimeout", wpTimeout->value().toInt()); - settingsChanged = true; - } + // preferences.putUInt("wpTimeout", wpTimeout->value().toInt()); + // settingsChanged = true; + // } - std::vector screenNameMap = getScreenNameMap(); + // std::vector screenNameMap = getScreenNameMap(); - if (request->hasParam("screens")) - { - AsyncWebParameter *screenParam = request->getParam("screens", true); + // if (request->hasParam("screens")) + // { + // const AsyncWebParameter *screenParam = request->getParam("screens", true); - Serial.printf(screenParam->value().c_str()); - } + // Serial.printf(screenParam->value().c_str()); + // } - for (int i = 0; i < screenNameMap.size(); i++) - { - String key = "screen[" + String(i) + "]"; - String prefKey = "screen" + String(i) + "Visible"; - bool visible = false; - if (request->hasParam(key, true)) - { - AsyncWebParameter *screenParam = request->getParam(key, true); - visible = screenParam->value().toInt(); - } + // for (int i = 0; i < screenNameMap.size(); i++) + // { + // String key = "screen[" + String(i) + "]"; + // String prefKey = "screen" + String(i) + "Visible"; + // bool visible = false; + // if (request->hasParam(key, true)) + // { + // const AsyncWebParameter *screenParam = request->getParam(key, true); + // visible = screenParam->value().toInt(); + // } - preferences.putBool(prefKey.c_str(), visible); - } + // preferences.putBool(prefKey.c_str(), visible); + // } - if (request->hasParam("tzOffset", true)) - { - AsyncWebParameter *p = request->getParam("tzOffset", true); - int tzOffsetSeconds = p->value().toInt() * 60; - preferences.putInt("gmtOffset", tzOffsetSeconds); - settingsChanged = true; - } + // if (request->hasParam("tzOffset", true)) + // { + // const AsyncWebParameter *p = request->getParam("tzOffset", true); + // int tzOffsetSeconds = p->value().toInt() * 60; + // preferences.putInt("gmtOffset", tzOffsetSeconds); + // settingsChanged = true; + // } - if (request->hasParam("minSecPriceUpd", true)) - { - AsyncWebParameter *p = request->getParam("minSecPriceUpd", true); - int minSecPriceUpd = p->value().toInt(); - preferences.putUInt("minSecPriceUpd", minSecPriceUpd); - settingsChanged = true; - } + // if (request->hasParam("minSecPriceUpd", true)) + // { + // const AsyncWebParameter *p = request->getParam("minSecPriceUpd", true); + // int minSecPriceUpd = p->value().toInt(); + // preferences.putUInt("minSecPriceUpd", minSecPriceUpd); + // settingsChanged = true; + // } - if (request->hasParam("timePerScreen", true)) - { - AsyncWebParameter *p = request->getParam("timePerScreen", true); - uint timerSeconds = p->value().toInt() * 60; - preferences.putUInt("timerSeconds", timerSeconds); - settingsChanged = true; - } + // if (request->hasParam("timePerScreen", true)) + // { + // const AsyncWebParameter *p = request->getParam("timePerScreen", true); + // uint timerSeconds = p->value().toInt() * 60; + // preferences.putUInt("timerSeconds", timerSeconds); + // settingsChanged = true; + // } - request->send(200); - if (settingsChanged) - { - queueLedEffect(LED_FLASH_SUCCESS); - } + // request->send(200); + // if (settingsChanged) + // { + // queueLedEffect(LED_FLASH_SUCCESS); + // } } void onApiSystemStatus(AsyncWebServerRequest *request) @@ -900,7 +900,7 @@ void onApiSetWifiTxPower(AsyncWebServerRequest *request) { if (request->hasParam("txPower")) { - AsyncWebParameter *txPowerParam = request->getParam("txPower"); + const AsyncWebParameter *txPowerParam = request->getParam("txPower"); int txPower = txPowerParam->value().toInt(); if (static_cast(wifi_power_t::WIFI_POWER_MINUS_1dBm) <= txPower && txPower <= static_cast(wifi_power_t::WIFI_POWER_19_5dBm)) @@ -1076,7 +1076,7 @@ void onNotFound(AsyncWebServerRequest *request) // int params = request->params(); // for (int i = 0; i < params; i++) // { - // AsyncWebParameter *p = request->getParam(i); + // const AsyncWebParameter *p = request->getParam(i); // if (p->isFile()) // { // p->isPost() is also true // Serial.printf("NotFound FILE[%s]: %s, size: %u\n", From fb67893f8590be5c08f4f38053a08c0dd56735b1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 29 Jun 2024 02:19:25 +0200 Subject: [PATCH 045/188] Add BH1750 lux sensor support --- data | 2 +- platformio.ini | 1 + src/lib/config.cpp | 22 ++++- src/lib/config.hpp | 4 +- src/lib/led_handler.cpp | 23 +++++ src/lib/led_handler.hpp | 3 + src/lib/ota.cpp | 10 ++- src/lib/ota.hpp | 2 + src/lib/webserver.cpp | 42 ++++++--- src/main.cpp | 185 +++++++++++++++++++++++----------------- 10 files changed, 196 insertions(+), 98 deletions(-) diff --git a/data b/data index 52e90db..59e2750 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 52e90dbdee7f53dcca5a9857dd411f98a569263a +Subproject commit 59e2750cf3bc53958f9714d0daf8d26b9d1b441f diff --git a/platformio.ini b/platformio.ini index f061e74..6f9b109 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,6 +79,7 @@ build_flags = lib_deps = ${btclock_base.lib_deps} robtillaart/PCA9685@^0.7.1 + claws/BH1750@^1.3.0 build_unflags = ${btclock_base.build_unflags} diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 2a8bc7d..67d520c 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -10,6 +10,8 @@ Adafruit_MCP23X17 mcp2; #ifdef HAS_FRONTLIGHT PCA9685 flArray(PCA_I2C_ADDR); +BH1750 bh1750; +bool hasLuxSensor = false; #endif std::vector screenNameMap(SCREEN_COUNT); @@ -401,6 +403,15 @@ void setupHardware() #ifdef HAS_FRONTLIGHT setupFrontlight(); + + Wire.beginTransmission(0x5C); + byte error = Wire.endTransmission(); + + if (error == 0) { + Serial.println(F("Found BH1750")); + hasLuxSensor = true; + bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C); + } #endif } @@ -737,6 +748,10 @@ void setupFrontlight() frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); } + +float getLightLevel() { + return bh1750.readLightLevel(); +} #endif String getHwRev() @@ -768,4 +783,9 @@ String getFsRev() String ret = fsHash.readString(); fsHash.close(); return ret; -} \ No newline at end of file +} + +bool hasLightLevel() { + return hasLuxSensor; +} + diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 51c0086..bcfb0dc 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -22,6 +22,7 @@ #include "lib/webserver.hpp" #ifdef HAS_FRONTLIGHT #include "PCA9685.h" +#include "BH1750.h" #endif #define NTP_SERVER "pool.ntp.org" @@ -49,6 +50,8 @@ void finishSetup(); void setupMcp(); #ifdef HAS_FRONTLIGHT void setupFrontlight(); +float getLightLevel(); +bool hasLightLevel(); extern PCA9685 flArray; #endif @@ -67,5 +70,4 @@ void improv_set_error(improv::Error error); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); String getHwRev(); bool isWhiteVersion(); - String getFsRev(); \ No newline at end of file diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index aa2fda6..ca725ea 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -65,6 +65,8 @@ void frontlightFadeInAll(int flDelayTime) void frontlightFadeInAll(int flDelayTime, bool staggered) { + if (frontlightIsOn()) + return; if (flInTransition) return; @@ -113,6 +115,8 @@ void frontlightFadeOutAll(int flDelayTime) void frontlightFadeOutAll(int flDelayTime, bool staggered) { + if (!frontlightIsOn()) + return; if (flInTransition) return; flInTransition = true; @@ -155,6 +159,22 @@ void frontlightFadeOutAll(int flDelayTime, bool staggered) flInTransition = false; } +std::vector frontlightGetStatus() { + std::vector statuses; + for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) + { + uint16_t a = 0, b = 0; + flArray.getPWM(ledPin, &a, &b); + statuses.push_back(round(b-a/4096)); + } + + return statuses; +} + +bool frontlightIsOn() { + return frontlightOn; +} + void frontlightFadeIn(uint num, int flDelayTime) { for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) @@ -166,6 +186,9 @@ void frontlightFadeIn(uint num, int flDelayTime) void frontlightFadeOut(uint num, int flDelayTime) { + if (!frontlightIsOn()) + return; + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { flArray.setPWM(num, 0, dutyCycle); diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 2157ac2..0c3e731 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -65,7 +65,10 @@ void frontlightFadeOutAll(); void frontlightFadeIn(uint num); void frontlightFadeOut(uint num); +std::vector frontlightGetStatus(); + void frontlightSetBrightness(uint brightness); +bool frontlightIsOn(); void frontlightFadeInAll(int flDelayTime); void frontlightFadeInAll(int flDelayTime, bool staggered); diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index ea82348..373271b 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -1,6 +1,7 @@ #include "ota.hpp" TaskHandle_t taskOtaHandle = NULL; +bool isOtaUpdating = false; void setupOTA() { if (preferences.getBool("otaEnabled", true)) { @@ -47,7 +48,7 @@ void onOTAStart() { // Stop all timers esp_timer_stop(screenRotateTimer); esp_timer_stop(minuteTimer); - + isOtaUpdating = true; // Stop or suspend all tasks // vTaskSuspend(priceUpdateTaskHandle); // vTaskSuspend(blockUpdateTaskHandle); @@ -65,7 +66,7 @@ void onOTAStart() { void handleOTATask(void *parameter) { for (;;) { ArduinoOTA.handle(); // Allow OTA updates to occur - vTaskDelay(pdMS_TO_TICKS(2500)); + vTaskDelay(pdMS_TO_TICKS(2000)); } } @@ -130,6 +131,7 @@ void onOTAError(ota_error_t error) { Serial.println(F("\nOTA update error, restarting")); Wire.end(); SPI.end(); + isOtaUpdating = false; delay(1000); ESP.restart(); } @@ -141,3 +143,7 @@ void onOTAComplete() { delay(1000); ESP.restart(); } + +bool getIsOTAUpdating() { + return isOtaUpdating; +} \ No newline at end of file diff --git a/src/lib/ota.hpp b/src/lib/ota.hpp index 8b2ad75..42f28cc 100644 --- a/src/lib/ota.hpp +++ b/src/lib/ota.hpp @@ -11,3 +11,5 @@ void onOTAProgress(unsigned int progress, unsigned int total); void downloadUpdate(); void onOTAError(ota_error_t error); void onOTAComplete(); + +bool getIsOTAUpdating(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index f0db255..e7b923a 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -57,6 +57,8 @@ void setupWebserver() #ifdef HAS_FRONTLIGHT server.on("/api/frontlight/on", HTTP_GET, onApiFrontlightOn); server.on("/api/frontlight/flash", HTTP_GET, onApiFrontlightFlash); + server.on("/api/frontlight/status", HTTP_GET, onApiFrontlightStatus); + server.on("/api/frontlight/brightness", HTTP_GET, onApiFrontlightSetBrightness); server.on("/api/frontlight/off", HTTP_GET, onApiFrontlightOff); @@ -67,7 +69,8 @@ void setupWebserver() // server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, // onApiLightsSetColor); - if (preferences.getBool("otaEnabled", true)) { + if (preferences.getBool("otaEnabled", true)) + { server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); } @@ -229,6 +232,20 @@ JsonDocument getStatusObject() root["rssi"] = WiFi.RSSI(); +#ifdef HAS_FRONTLIGHT + std::vector statuses = frontlightGetStatus(); + uint16_t arr[NUM_SCREENS]; + std::copy(statuses.begin(), statuses.end(), arr); + + JsonArray data = root["flStatus"].to(); + copyArray(arr, data); + + if (hasLightLevel()) + { + root["lightLevel"] = getLightLevel(); + } +#endif + return root; } @@ -437,7 +454,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } - String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay"}; + String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle"}; for (String setting : uintSettings) { @@ -557,7 +574,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH); root["wpTimeout"] = preferences.getUInt("wpTimeout", 600); root["tzOffset"] = preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS) / 60; -// root["useBitcoinNode"] = preferences.getBool("useNode", false); + // root["useBitcoinNode"] = preferences.getBool("useNode", false); root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolSecure"] = preferences.getBool("mempoolSecure", true); @@ -586,9 +603,11 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", false); root["flEffectDelay"] = preferences.getUInt("flEffectDelay"); root["flFlashOnUpd"] = preferences.getBool("flFlashOnUpd", false); - + root["hasLightLevel"] = hasLightLevel(); + root["luxLightToggle"] = preferences.getUInt("luxLightToggle", 128); #else root["hasFrontlight"] = false; + root["hasLightLevel"] = false; #endif root["hwRev"] = getHwRev(); @@ -1133,17 +1152,14 @@ void onApiFrontlightStatus(AsyncWebServerRequest *request) request->beginResponseStream("application/json"); JsonDocument root; - JsonArray ledStates = root["data"].to(); - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) - { - uint16_t onTime, offTime; - flArray.getPWM(ledPin, &onTime, &offTime); + std::vector statuses = frontlightGetStatus(); + uint16_t arr[NUM_SCREENS]; + std::copy(statuses.begin(), statuses.end(), arr); - ledStates.add(onTime); - } - - serializeJson(ledStates, *response); + JsonArray data = root["flStatus"].to(); + copyArray(arr, data); + serializeJson(root, *response); request->send(response); } diff --git a/src/main.cpp b/src/main.cpp index 095a8a2..6c1a21f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,8 +22,7 @@ uint wifiLostConnection; uint priceNotifyLostConnection = 0; uint blockNotifyLostConnection = 0; -//char ptrTaskList[1500]; - +// char ptrTaskList[1500]; extern "C" void app_main() { @@ -46,103 +45,129 @@ extern "C" void app_main() int64_t currentUptime = esp_timer_get_time() / 1000000; ; - if (!WiFi.isConnected()) + if (!getIsOTAUpdating()) { - if (!wifiLostConnection) +#ifdef HAS_FRONTLIGHT + if (preferences.getUInt("luxLightToggle", 128) != 0) { - wifiLostConnection = currentUptime; - Serial.println(F("Lost WiFi connection, trying to reconnect...")); + if (hasLightLevel() && getLightLevel() == 0) + { + if (frontlightIsOn()) { + frontlightFadeOutAll(); + } + } + else if (hasLightLevel() && getLightLevel() < preferences.getUInt("luxLightToggle", 128) && !frontlightIsOn()) + { + frontlightFadeInAll(); + } + else if (frontlightIsOn() && getLightLevel() > preferences.getUInt("luxLightToggle", 128)) + { + frontlightFadeOutAll(); + } } - if ((currentUptime - wifiLostConnection) > 600) +#endif + + if (!WiFi.isConnected()) { - Serial.println(F("Still no connection after 10 minutes, restarting...")); - delay(2000); - ESP.restart(); + if (!wifiLostConnection) + { + wifiLostConnection = currentUptime; + Serial.println(F("Lost WiFi connection, trying to reconnect...")); + } + + if ((currentUptime - wifiLostConnection) > 600) + { + Serial.println(F("Still no connection after 10 minutes, restarting...")); + delay(2000); + ESP.restart(); + } + + WiFi.begin(); + } + else if (wifiLostConnection) + { + wifiLostConnection = 0; + Serial.println(F("Connection restored, reset timer.")); } - WiFi.begin(); - } - else if (wifiLostConnection) - { - wifiLostConnection = 0; - Serial.println(F("Connection restored, reset timer.")); - } - - if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) - { - priceNotifyLostConnection++; - Serial.println(F("Lost price data connection...")); - queueLedEffect(LED_DATA_PRICE_ERROR); - - // if price WS connection does not come back after 6*5 seconds, destroy and recreate - if (priceNotifyLostConnection > 6) + if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) { - Serial.println(F("Restarting price handler...")); + priceNotifyLostConnection++; + Serial.println(F("Lost price data connection...")); + queueLedEffect(LED_DATA_PRICE_ERROR); - restartPriceNotify(); - // setupPriceNotify(); + // if price WS connection does not come back after 6*5 seconds, destroy and recreate + if (priceNotifyLostConnection > 6) + { + Serial.println(F("Restarting price handler...")); + + restartPriceNotify(); + // setupPriceNotify(); + priceNotifyLostConnection = 0; + } + } + else if (priceNotifyLostConnection > 0 && isPriceNotifyConnected()) + { priceNotifyLostConnection = 0; } - } else if (priceNotifyLostConnection > 0 && isPriceNotifyConnected()) - { - priceNotifyLostConnection = 0; - } - if (getBlockNotifyInit() && !isBlockNotifyConnected()) - { - blockNotifyLostConnection++; - Serial.println(F("Lost block data connection...")); - queueLedEffect(LED_DATA_BLOCK_ERROR); - // if mempool WS connection does not come back after 6*5 seconds, destroy and recreate - if (blockNotifyLostConnection > 6) + if (getBlockNotifyInit() && !isBlockNotifyConnected()) { - Serial.println(F("Restarting block handler...")); + blockNotifyLostConnection++; + Serial.println(F("Lost block data connection...")); + queueLedEffect(LED_DATA_BLOCK_ERROR); + // if mempool WS connection does not come back after 6*5 seconds, destroy and recreate + if (blockNotifyLostConnection > 6) + { + Serial.println(F("Restarting block handler...")); - restartBlockNotify(); - //setupBlockNotify(); + restartBlockNotify(); + // setupBlockNotify(); + blockNotifyLostConnection = 0; + } + } + else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) + { blockNotifyLostConnection = 0; } - } - else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) - { - blockNotifyLostConnection = 0; - } - // if more than 5 price updates are missed, there is probably something wrong, reconnect - if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) - { - Serial.println(F("Detected 5 missed price updates... restarting price handler.")); - - restartPriceNotify(); - // setupPriceNotify(); - - priceNotifyLostConnection = 0; - } - - // If after 45 minutes no mempool blocks, check the rest API - if ((getLastBlockUpdate() - currentUptime) > 45 * 60) - { - Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); - int currentBlock = getBlockFetch(); - if (currentBlock != -1) + // if more than 5 price updates are missed, there is probably something wrong, reconnect + if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) { - if (currentBlock != getBlockHeight()) - { - Serial.println(F("Detected stuck block height... restarting block handler.")); - // Mempool source stuck, restart - restartBlockNotify(); - // setupBlockNotify(); - } - // set last block update so it doesn't fetch for 45 minutes - setLastBlockUpdate(currentUptime); - } - } + Serial.println(F("Detected 5 missed price updates... restarting price handler.")); - if (currentUptime - getLastTimeSync() > 24 * 60 * 60) { - Serial.println(F("Last time update is longer than 24 hours ago, sync again")); - syncTime(); - }; + restartPriceNotify(); + // setupPriceNotify(); + + priceNotifyLostConnection = 0; + } + + // If after 45 minutes no mempool blocks, check the rest API + if ((getLastBlockUpdate() - currentUptime) > 45 * 60) + { + Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); + int currentBlock = getBlockFetch(); + if (currentBlock != -1) + { + if (currentBlock != getBlockHeight()) + { + Serial.println(F("Detected stuck block height... restarting block handler.")); + // Mempool source stuck, restart + restartBlockNotify(); + // setupBlockNotify(); + } + // set last block update so it doesn't fetch for 45 minutes + setLastBlockUpdate(currentUptime); + } + } + + if (currentUptime - getLastTimeSync() > 24 * 60 * 60) + { + Serial.println(F("Last time update is longer than 24 hours ago, sync again")); + syncTime(); + }; + } vTaskDelay(pdMS_TO_TICKS(5000)); } From ac02e1470d24b53a53d914dd2faea85507ede9ed Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 29 Jun 2024 02:33:01 +0200 Subject: [PATCH 046/188] Bugfix for light sensor --- src/lib/config.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 67d520c..985d3a1 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -752,6 +752,10 @@ void setupFrontlight() float getLightLevel() { return bh1750.readLightLevel(); } + +bool hasLightLevel() { + return hasLuxSensor; +} #endif String getHwRev() @@ -785,7 +789,4 @@ String getFsRev() return ret; } -bool hasLightLevel() { - return hasLuxSensor; -} From 4f4e37ec3c0177930d6721e9fc712a58f7d6163b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 29 Jun 2024 16:40:21 +0200 Subject: [PATCH 047/188] Fix workflow --- .github/workflows/tagging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 8898ebc..109fdaa 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -112,6 +112,6 @@ jobs: target-directory: firmware_v3/ destination-github-username: 'btclock' destination-repository-name: 'web-flasher' - target-branch: btclock + target-branch: main user-name: ${{github.actor}} user-email: ${{github.actor}}@users.noreply.github.com \ No newline at end of file From 1d710ba7f772a2764dbe2adad9dc2303a4c2425f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 10 Jul 2024 01:08:42 +0200 Subject: [PATCH 048/188] Improve rev. B startup flow, webUI fixes --- data | 2 +- src/lib/config.cpp | 1 - src/lib/led_handler.cpp | 11 ++++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/data b/data index 59e2750..124c810 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 59e2750cf3bc53958f9714d0daf8d26b9d1b441f +Subproject commit 124c810e291a0c642da619f3c00109240c7a061a diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 985d3a1..a6fdf86 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -746,7 +746,6 @@ void setupFrontlight() preferences.putBool("flFlashOnUpd", false); } - frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); } float getLightLevel() { diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index ca725ea..43e5182 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -159,19 +159,21 @@ void frontlightFadeOutAll(int flDelayTime, bool staggered) flInTransition = false; } -std::vector frontlightGetStatus() { +std::vector frontlightGetStatus() +{ std::vector statuses; for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) { uint16_t a = 0, b = 0; flArray.getPWM(ledPin, &a, &b); - statuses.push_back(round(b-a/4096)); + statuses.push_back(round(b - a / 4096)); } return statuses; } -bool frontlightIsOn() { +bool frontlightIsOn() +{ return frontlightOn; } @@ -223,6 +225,9 @@ void ledTask(void *parameter) switch (ledTaskParams) { case LED_POWER_TEST: +#ifdef HAS_FRONTLIGHT + frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); +#endif ledRainbow(20); pixels.clear(); break; From 8e71f29d10e19bc2f26b2acd3cafae3fca56b445 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 11 Jul 2024 14:08:37 +0200 Subject: [PATCH 049/188] Use constants for setting default values, bring back wifi config portal timeout setting --- data | 2 +- src/lib/block_notify.cpp | 12 ++++----- src/lib/config.cpp | 37 +++++++++++++-------------- src/lib/config.hpp | 7 +----- src/lib/defaults.hpp | 43 ++++++++++++++++++++++++++++++++ src/lib/led_handler.cpp | 22 ++++++++-------- src/lib/ota.cpp | 2 +- src/lib/price_notify.cpp | 6 ++--- src/lib/screen_handler.cpp | 16 ++++++------ src/lib/shared.hpp | 2 ++ src/lib/webserver.cpp | 51 +++++++++++++++++++------------------- src/main.cpp | 8 +++--- 12 files changed, 122 insertions(+), 86 deletions(-) create mode 100644 src/lib/defaults.hpp diff --git a/data b/data index 124c810..2363d98 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 124c810e291a0c642da619f3c00109240c7a061a +Subproject commit 2363d98965bb1fdbfdf5d130b41732f5b864e2d0 diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 2ef4268..c775b7d 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -89,7 +89,7 @@ void setupBlockNotify() xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } - if (!preferences.getBool("fetchEurPrice", false) && preferences.getBool("ownDataSource", true)) + if (!preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) { return; } @@ -97,7 +97,7 @@ void setupBlockNotify() // std::strcpy(wsServer, String("wss://" + mempoolInstance + // "/api/v1/ws").c_str()); - const String protocol = preferences.getBool("mempoolSecure", true) ? "wss" : "ws"; + const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "wss" : "ws"; String mempoolUri = protocol + "://" + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE) + "/api/v1/ws"; @@ -185,7 +185,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) // xTaskNotifyGive(blockUpdateTaskHandle); if (getCurrentScreen() != SCREEN_BLOCK_HEIGHT && - preferences.getBool("stealFocus", false)) + preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) { uint64_t timerPeriod = 0; if (isTimerActive()) @@ -203,7 +203,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) vTaskDelay(pdMS_TO_TICKS(315*NUM_SCREENS)); // Extra delay because of screen switching } - if (preferences.getBool("ledFlashOnUpd", false)) + if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) { vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated queueLedEffect(LED_FLASH_BLOCK_NOTIFY); @@ -300,9 +300,9 @@ int getBlockFetch() // Get current block height through regular API HTTPClient http; - const String protocol = preferences.getBool("mempoolSecure", true) ? "https" : "http"; + const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http"; - if (preferences.getBool("mempoolSecure", true)) + if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) http.begin(client, protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); else http.begin(protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index a6fdf86..0b96689 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -24,7 +24,7 @@ void setup() setupHardware(); setupDisplays(); - if (preferences.getBool("ledTestOnPower", true)) + if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { queueLedEffect(LED_POWER_TEST); } @@ -75,7 +75,7 @@ void setup() waitUntilNoneBusy(); #ifdef HAS_FRONTLIGHT - if (!preferences.getBool("flAlwaysOn", false)) + if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON)) { frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); flArray.allOFF(); @@ -91,24 +91,21 @@ void tryImprovSetup() WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); WiFi.begin(); - if (preferences.getInt("txPower", 0)) + if (preferences.getInt("txPower", DEFAULT_TX_POWER)) { if (WiFi.setTxPower( - static_cast(preferences.getInt("txPower", 0)))) + static_cast(preferences.getInt("txPower", DEFAULT_TX_POWER)))) { Serial.printf("WiFi max tx power set to %d\n", - preferences.getInt("txPower", 0)); + preferences.getInt("txPower", DEFAULT_TX_POWER)); } } - // if (!preferences.getBool("wifiConfigured", false)) + // if (!preferences.getBool("wifiConfigured", DEFAULT_WIFI_CONFIGURED) { queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); - uint8_t x_buffer[16]; - uint8_t x_position = 0; - bool buttonPress = false; { std::lock_guard lockMcp(mcpMutex); @@ -128,7 +125,7 @@ void tryImprovSetup() String(mac[5], 16) + String(mac[1], 16)) .substring(2, 10); - // wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", 600)); + wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT)); wm.setWiFiAutoReconnect(false); wm.setDebugOutput(false); wm.setConfigPortalBlocking(true); @@ -228,13 +225,13 @@ void tryImprovSetup() void syncTime() { - configTime(preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS), 0, + configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, NTP_SERVER); struct tm timeinfo; while (!getLocalTime(&timeinfo)) { - configTime(preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS), 0, + configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, NTP_SERVER); delay(500); Serial.println(F("Retry set time")); @@ -249,8 +246,8 @@ void setupPreferences() setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); - setBlockHeight(preferences.getUInt("blockHeight", 816000)); - setPrice(preferences.getUInt("lastPrice", 30000)); + setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); + setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE)); screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height"; screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate"; @@ -265,7 +262,7 @@ void setupWebsocketClients(void *pvParameters) { setupBlockNotify(); - if (preferences.getBool("fetchEurPrice", false)) + if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) { setupPriceFetchTask(); } @@ -287,7 +284,7 @@ void setupTimers() void finishSetup() { - if (preferences.getBool("ledStatus", false)) + if (preferences.getBool("ledStatus", DEFAULT_LED_STATUS)) { restoreLedState(); } @@ -711,7 +708,7 @@ String getMyHostname() // WiFi.macAddress(mac); esp_efuse_mac_get_default(mac); char hostname[15]; - String hostnamePrefix = preferences.getString("hostnamePrefix", "btclock"); + String hostnamePrefix = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX); snprintf(hostname, sizeof(hostname), "%s-%02x%02x%02x", hostnamePrefix, mac[3], mac[4], mac[5]); return hostname; @@ -734,16 +731,16 @@ void setupFrontlight() if (!preferences.isKey("flMaxBrightness")) { - preferences.putUInt("flMaxBrightness", 2048); + preferences.putUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); } if (!preferences.isKey("flEffectDelay")) { - preferences.putUInt("flEffectDelay", 15); + preferences.putUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); } if (!preferences.isKey("flFlashOnUpd")) { - preferences.putBool("flFlashOnUpd", false); + preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); } } diff --git a/src/lib/config.hpp b/src/lib/config.hpp index bcfb0dc..c9c3199 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -26,17 +26,12 @@ #endif #define NTP_SERVER "pool.ntp.org" -#define DEFAULT_MEMPOOL_INSTANCE "mempool.space" -#define TIME_OFFSET_SECONDS 3600 +#define DEFAULT_TIME_OFFSET_SECONDS 3600 #define USER_AGENT "BTClock/3.0" #ifndef MCP_DEV_ADDR #define MCP_DEV_ADDR 0x20 #endif -#define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30 -#define DEFAULT_MINUTES_FULL_REFRESH 60 -#define DEFAULT_FG_COLOR GxEPD_WHITE -#define DEFAULT_BG_COLOR GxEPD_BLACK void setup(); void syncTime(); diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp new file mode 100644 index 0000000..510ab61 --- /dev/null +++ b/src/lib/defaults.hpp @@ -0,0 +1,43 @@ +#define INITIAL_BLOCK_HEIGHT 851500 +#define INITIAL_LAST_PRICE 50000 +#define DEFAULT_TX_POWER 0 + +#define DEFAULT_MEMPOOL_SECURE true +#define DEFAULT_LED_TEST_ON_POWER true +#define DEFAULT_LED_FLASH_ON_UPD false +#define DEFAULT_LED_BRIGHTNESS 128 +#define DEFAULT_STEAL_FOCUS false +#define DEFAULT_MCAP_BIG_CHAR true +#define DEFAULT_MDNS_ENABLED true +#define DEFAULT_OTA_ENABLED true +#define DEFAULT_FETCH_EUR_PRICE false +#define DEFAULT_USE_SATS_SYMBOL false +#define DEFAULT_USE_BLOCK_COUNTDOWN true +#define DEFAULT_SUFFIX_PRICE false +#define DEFAULT_DISABLE_LEDS false +#define DEFAULT_OWN_DATA_SOURCE true + +#define DEFAULT_TIME_OFFSET_SECONDS 3600 + +#define DEFAULT_HOSTNAME_PREFIX "btclock" +#define DEFAULT_MEMPOOL_INSTANCE "mempool.space" + +#define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30 +#define DEFAULT_MINUTES_FULL_REFRESH 60 + +#define DEFAULT_FG_COLOR GxEPD_WHITE +#define DEFAULT_BG_COLOR GxEPD_BLACK + +#define DEFAULT_WP_TIMEOUT 15*60 + +#define DEFAULT_FL_MAX_BRIGHTNESS 2048 +#define DEFAULT_FL_EFFECT_DELAY 15 + +#define DEFAULT_LUX_LIGHT_TOGGLE 128 +#define DEFAULT_FL_ALWAYS_ON false +#define DEFAULT_FL_FLASH_ON_UPDATE false + +#define DEFAULT_LED_STATUS false +#define DEFAULT_TIMER_ACTIVE true +#define DEFAULT_TIMER_SECONDS 1800 +#define DEFAULT_CURRENT_SCREEN 0 diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 43e5182..a1d06fa 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -209,7 +209,7 @@ void ledTask(void *parameter) pdPASS) { - if (preferences.getBool("disableLeds", false)) + if (preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS)) { continue; } @@ -221,7 +221,9 @@ void ledTask(void *parameter) { oldLights[i] = pixels.getPixelColor(i); } - + #ifdef HAS_FRONTLIGHT + uint flDelayTime = preferences.getUInt("flEffectDelay"); + #endif switch (ledTaskParams) { case LED_POWER_TEST: @@ -274,32 +276,32 @@ void ledTask(void *parameter) #ifdef HAS_FRONTLIGHT bool frontlightWasOn = false; - if (preferences.getBool("flFlashOnUpd", false)) + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) { if (frontlightOn) { frontlightWasOn = true; - frontlightFadeOutAll(1); + frontlightFadeOutAll(flDelayTime, true); } else { - frontlightFadeInAll(1); + frontlightFadeInAll(flDelayTime, true); } } #endif blinkDelayTwoColor(250, 3, pixels.Color(224, 67, 0), pixels.Color(8, 2, 0)); #ifdef HAS_FRONTLIGHT - if (preferences.getBool("flFlashOnUpd", false)) + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) { vTaskDelay(pdMS_TO_TICKS(10)); if (frontlightWasOn) { - frontlightFadeInAll(1); + frontlightFadeInAll(flDelayTime, true); } else { - frontlightFadeOutAll(1); + frontlightFadeOutAll(flDelayTime, true); } } #endif @@ -397,11 +399,11 @@ void ledTask(void *parameter) void setupLeds() { pixels.begin(); - pixels.setBrightness(preferences.getUInt("ledBrightness", 128)); + pixels.setBrightness(preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS)); pixels.clear(); pixels.show(); setupLedTask(); - if (preferences.getBool("ledTestOnPower", true)) + if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { while (!ledTaskQueue) { diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 373271b..c457a01 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -4,7 +4,7 @@ TaskHandle_t taskOtaHandle = NULL; bool isOtaUpdating = false; void setupOTA() { - if (preferences.getBool("otaEnabled", true)) { + if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED)) { ArduinoOTA.onStart(onOTAStart); ArduinoOTA.onProgress(onOTAProgress); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 0a07f39..18f36ad 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -43,9 +43,7 @@ bool priceNotifyInit = false; void setupPriceNotify() { - // currentPrice = preferences.get("lastPrice", 30000); - - if (preferences.getBool("ownDataSource", true)) + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) { config = {.uri = wsOwnServerPrice, .user_agent = USER_AGENT}; @@ -76,7 +74,7 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, break; case WEBSOCKET_EVENT_DATA: onWebsocketPriceMessage(data); - if (preferences.getBool("ownDataSource", true)) + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) { onWebsocketBlockMessage(data); } diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index d0fa761..85ecda5 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -30,17 +30,17 @@ void workerTask(void *pvParameters) { case TASK_PRICE_UPDATE: { uint price = getPrice(); char priceSymbol = '$'; - if (preferences.getBool("fetchEurPrice", false)) { + if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) { priceSymbol = '['; } if (getCurrentScreen() == SCREEN_BTC_TICKER) { - taskEpdContent = parsePriceData(price, priceSymbol, preferences.getBool("suffixPrice", false)); + taskEpdContent = parsePriceData(price, priceSymbol, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE)); } else if (getCurrentScreen() == SCREEN_MSCW_TIME) { - taskEpdContent = parseSatsPerCurrency(price, priceSymbol, preferences.getBool("useSatsSymbol", false)); + taskEpdContent = parseSatsPerCurrency(price, priceSymbol, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); } else { taskEpdContent = parseMarketCap(getBlockHeight(), price, priceSymbol, - preferences.getBool("mcapBigChar", true)); + preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } setEpdContent(taskEpdContent); @@ -57,7 +57,7 @@ void workerTask(void *pvParameters) { if (getCurrentScreen() != SCREEN_HALVING_COUNTDOWN) { taskEpdContent = parseBlockHeight(getBlockHeight()); } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", true)); + taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); } if (getCurrentScreen() == SCREEN_HALVING_COUNTDOWN || @@ -147,7 +147,7 @@ void setupTasks() { &taskScreenRotateTaskHandle); waitUntilNoneBusy(); - setCurrentScreen(preferences.getUInt("currentScreen", 0)); + setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } void setupTimeUpdateTimer(void *pvParameters) { @@ -180,7 +180,7 @@ void setupScreenRotateTimer(void *pvParameters) { esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer); - if (preferences.getBool("timerActive", true)) { + if (preferences.getBool("timerActive", DEFAULT_TIMER_ACTIVE)) { esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond); } @@ -188,7 +188,7 @@ void setupScreenRotateTimer(void *pvParameters) { vTaskDelete(NULL); } -uint getTimerSeconds() { return preferences.getUInt("timerSeconds", 1800); } +uint getTimerSeconds() { return preferences.getUInt("timerSeconds", DEFAULT_TIMER_SECONDS); } bool isTimerActive() { return esp_timer_is_active(screenRotateTimer); } diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 65e1009..60cfbd1 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -11,6 +11,8 @@ #include #include +#include "defaults.hpp" + extern Adafruit_MCP23X17 mcp1; #ifdef IS_BTCLOCK_S3 extern Adafruit_MCP23X17 mcp2; diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index e7b923a..432de65 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -69,7 +69,7 @@ void setupWebserver() // server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, // onApiLightsSetColor); - if (preferences.getBool("otaEnabled", true)) + if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED)) { server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); @@ -94,7 +94,7 @@ void setupWebserver() server.begin(); - if (preferences.getBool("mdnsEnabled", true)) + if (preferences.getBool("mdnsEnabled", DEFAULT_MDNS_ENABLED)) { if (!MDNS.begin(getMyHostname())) { @@ -454,7 +454,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } - String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle"}; + String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; for (String setting : uintSettings) { @@ -572,39 +572,38 @@ void onApiSettingsGet(AsyncWebServerRequest *request) "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); root["fullRefreshMin"] = preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH); - root["wpTimeout"] = preferences.getUInt("wpTimeout", 600); - root["tzOffset"] = preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS) / 60; - // root["useBitcoinNode"] = preferences.getBool("useNode", false); + root["wpTimeout"] = preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT); + root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - root["mempoolSecure"] = preferences.getBool("mempoolSecure", true); - root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", true); - root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", false); - root["ledBrightness"] = preferences.getUInt("ledBrightness", 128); - root["stealFocus"] = preferences.getBool("stealFocus", false); - root["mcapBigChar"] = preferences.getBool("mcapBigChar", true); - root["mdnsEnabled"] = preferences.getBool("mdnsEnabled", true); - root["otaEnabled"] = preferences.getBool("otaEnabled", true); - root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", false); - root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", false); - root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", true); - root["suffixPrice"] = preferences.getBool("suffixPrice", false); - root["disableLeds"] = preferences.getBool("disableLeds", false); + root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER); + root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD); + root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS); + root["stealFocus"] = preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS); + root["mcapBigChar"] = preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR); + root["mdnsEnabled"] = preferences.getBool("mdnsEnabled", DEFAULT_MDNS_ENABLED); + root["otaEnabled"] = preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED); + root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE); + root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL); + root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN); + root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE); + root["disableLeds"] = preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS); - root["hostnamePrefix"] = preferences.getString("hostnamePrefix", "btclock"); + root["hostnamePrefix"] = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX); root["hostname"] = getMyHostname(); root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); - root["ownDataSource"] = preferences.getBool("ownDataSource", true); + root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; - root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", 2048); - root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", false); - root["flEffectDelay"] = preferences.getUInt("flEffectDelay"); - root["flFlashOnUpd"] = preferences.getBool("flFlashOnUpd", false); + root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); + root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON); + root["flEffectDelay"] = preferences.getUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); + root["flFlashOnUpd"] = preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); root["hasLightLevel"] = hasLightLevel(); - root["luxLightToggle"] = preferences.getUInt("luxLightToggle", 128); + root["luxLightToggle"] = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); #else root["hasFrontlight"] = false; root["hasLightLevel"] = false; diff --git a/src/main.cpp b/src/main.cpp index 6c1a21f..0106eab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,7 +48,7 @@ extern "C" void app_main() if (!getIsOTAUpdating()) { #ifdef HAS_FRONTLIGHT - if (preferences.getUInt("luxLightToggle", 128) != 0) + if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { if (hasLightLevel() && getLightLevel() == 0) { @@ -56,11 +56,11 @@ extern "C" void app_main() frontlightFadeOutAll(); } } - else if (hasLightLevel() && getLightLevel() < preferences.getUInt("luxLightToggle", 128) && !frontlightIsOn()) + else if (hasLightLevel() && getLightLevel() < preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) && !frontlightIsOn()) { frontlightFadeInAll(); } - else if (frontlightIsOn() && getLightLevel() > preferences.getUInt("luxLightToggle", 128)) + else if (frontlightIsOn() && getLightLevel() > preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE)) { frontlightFadeOutAll(); } @@ -91,7 +91,7 @@ extern "C" void app_main() Serial.println(F("Connection restored, reset timer.")); } - if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", false) && !isPriceNotifyConnected()) + if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) { priceNotifyLostConnection++; Serial.println(F("Lost price data connection...")); From 87b22e5851b86a2d4b58dc9ceeba1860a95f271d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 11 Jul 2024 22:08:42 +0200 Subject: [PATCH 050/188] Nostr data source implementation --- data | 2 +- platformio.ini | 3 +- src/lib/block_notify.cpp | 36 +++++++----- src/lib/block_notify.hpp | 3 + src/lib/config.cpp | 34 +++++++---- src/lib/config.hpp | 2 + src/lib/defaults.hpp | 4 ++ src/lib/nostr_notify.cpp | 123 +++++++++++++++++++++++++++++++++++++++ src/lib/nostr_notify.hpp | 17 ++++++ src/lib/price_notify.cpp | 51 +++++++++------- src/lib/price_notify.hpp | 2 + src/lib/webserver.cpp | 8 ++- src/main.cpp | 2 +- 13 files changed, 236 insertions(+), 51 deletions(-) create mode 100644 src/lib/nostr_notify.cpp create mode 100644 src/lib/nostr_notify.hpp diff --git a/data b/data index 2363d98..ee4d6d8 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 2363d98965bb1fdbfdf5d130b41732f5b864e2d0 +Subproject commit ee4d6d88c76fa279e643faabf4216c88145e0b2c diff --git a/platformio.ini b/platformio.ini index 6f9b109..7392655 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,8 @@ lib_deps = https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 - + rblb/Nostrduino@^1.2.5 + [env:lolin_s3_mini] extends = btclock_base board = lolin_s3_mini diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index c775b7d..dad6ad1 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -172,7 +172,25 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) return; } - currentBlockHeight = block["height"].as(); + processNewBlock(block["height"].as()); + } + else if (doc.containsKey("mempool-blocks")) + { + JsonArray blockInfo = doc["mempool-blocks"].as(); + + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + + processNewBlockFee(medianFee); + } + + doc.clear(); +} + +void processNewBlock(uint newBlockHeight) { + if (newBlockHeight < currentBlockHeight) + return; + + currentBlockHeight = newBlockHeight; // Serial.printf("New block found: %d\r\n", block["height"].as()); preferences.putUInt("blockHeight", currentBlockHeight); @@ -209,30 +227,22 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) queueLedEffect(LED_FLASH_BLOCK_NOTIFY); } } - } - else if (doc.containsKey("mempool-blocks")) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); +} - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); - - if (blockMedianFee == medianFee) +void processNewBlockFee(uint newBlockFee) { + if (blockMedianFee == newBlockFee) { - doc.clear(); return; } // Serial.printf("New median fee: %d\r\n", medianFee); - blockMedianFee = medianFee; + blockMedianFee = newBlockFee; if (workQueue != nullptr) { WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } - } - - doc.clear(); } uint getBlockHeight() { return currentBlockHeight; } diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index c233b2e..0af7196 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -31,6 +31,9 @@ bool isBlockNotifyConnected(); void stopBlockNotify(); void restartBlockNotify(); +void processNewBlock(uint newBlockHeight); +void processNewBlockFee(uint newBlockFee); + bool getBlockNotifyInit(); uint getLastBlockUpdate(); int getBlockFetch(); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 0b96689..61840c9 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -48,7 +48,9 @@ void setup() { delay(1000); } - } else if (mcp1.digitalRead(1) == LOW) { + } + else if (mcp1.digitalRead(1) == LOW) + { preferences.clear(); queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); ESP.restart(); @@ -66,8 +68,16 @@ void setup() setupTasks(); setupTimers(); - xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, - tskIDLE_PRIORITY, NULL); + if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) + { + setupNostrNotify(); + setupNostrTask(); + } + else + { + xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, + tskIDLE_PRIORITY, NULL); + } setupButtonTask(); setupOTA(); @@ -401,14 +411,15 @@ void setupHardware() #ifdef HAS_FRONTLIGHT setupFrontlight(); - Wire.beginTransmission(0x5C); - byte error = Wire.endTransmission(); + Wire.beginTransmission(0x5C); + byte error = Wire.endTransmission(); - if (error == 0) { + if (error == 0) + { Serial.println(F("Found BH1750")); hasLuxSensor = true; bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C); - } + } #endif } @@ -742,14 +753,15 @@ void setupFrontlight() { preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); } - } -float getLightLevel() { +float getLightLevel() +{ return bh1750.readLightLevel(); } -bool hasLightLevel() { +bool hasLightLevel() +{ return hasLuxSensor; } #endif @@ -784,5 +796,3 @@ String getFsRev() fsHash.close(); return ret; } - - diff --git a/src/lib/config.hpp b/src/lib/config.hpp index c9c3199..c0d0ac5 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -16,6 +16,8 @@ #include "lib/improv.hpp" #include "lib/led_handler.hpp" #include "lib/ota.hpp" +#include "lib/nostr_notify.hpp" + #include "lib/price_notify.hpp" #include "lib/screen_handler.hpp" #include "lib/shared.hpp" diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 510ab61..0de2415 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -22,6 +22,10 @@ #define DEFAULT_HOSTNAME_PREFIX "btclock" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" +#define DEFAULT_USE_NOSTR false +#define DEFAULT_NOSTR_NPUB "642317135fd4c4205323b9dea8af3270657e62d51dc31a657c0ec8aab31c6288" +#define DEFAULT_NOSTR_RELAY "wss://nostr.dbtc.link" + #define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30 #define DEFAULT_MINUTES_FULL_REFRESH 60 diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp new file mode 100644 index 0000000..4f7650f --- /dev/null +++ b/src/lib/nostr_notify.cpp @@ -0,0 +1,123 @@ +#include "nostr_notify.hpp" + +std::vector pools; +nostr::Transport *transport; +TaskHandle_t nostrTaskHandle = NULL; + +void setupNostrNotify() +{ + nostr::esp32::ESP32Platform::initNostr(false); + + + + try + { + transport = nostr::esp32::ESP32Platform::getTransport(); + nostr::NostrPool *pool = new nostr::NostrPool(transport); + String relay = preferences.getString("nostrRelay"); + String pubKey = preferences.getString("nostrPubKey"); + pools.push_back(pool); + // Lets subscribe to the relay + String subId = pool->subscribeMany( + {relay}, + {{// we set the filters here (see + // https://github.com/nostr-protocol/nips/blob/master/01.md#from-client-to-relay-sending-events-and-creating-subscriptions) + {"kinds", {"1"}}, + {"authors", {pubKey}}}}, + [&](const String &subId, nostr::SignedNostrEvent *event) + { + // Received events callback, we can access the event content with + // event->getContent() Here you should handle the event, for this + // test we will just serialize it and print to console + JsonDocument doc; + JsonArray arr = doc["data"].to(); + event->toSendableEvent(arr); + // Access the second element which is the object + JsonObject obj = arr[1].as(); + + // Access the "tags" array + JsonArray tags = obj["tags"].as(); + + // Flag to check if the tag was found + bool tagFound = false; + uint medianFee = 0; + String typeValue; + + // Iterate over the tags array + for (JsonArray tag : tags) + { + // Check if the tag is an array with two elements + if (tag.size() == 2) + { + const char *key = tag[0]; + const char *value = tag[1]; + + // Check if the key is "type" and the value is "priceUsd" + if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0)) + { + typeValue = value; + tagFound = true; + } + else if (strcmp(key, "medianFee") == 0) + { + medianFee = tag[1].as(); + } + } + } + if (tagFound) + { + if (typeValue.equals("priceUsd")) + { + processNewPrice(obj["content"].as()); + } + else if (typeValue.equals("blockHeight")) + { + processNewBlock(obj["content"].as()); + } + + if (medianFee != 0) + { + processNewBlockFee(medianFee); + } + } + }, + [&](const String &subId, const String &reason) + { + // This is the callback that will be called when the subscription is + // closed + Serial.println("Subscription closed: " + reason); + }, + [&](const String &subId) + { + // This is the callback that will be called when the subscription is + // EOSE + Serial.println("Subscription EOSE: " + subId); + }); + } + catch (const std::exception &e) + { + Serial.println("Error: " + String(e.what())); + } +} + +void nostrTask(void *pvParameters) +{ + int blockFetch = getBlockFetch(); + processNewBlock(blockFetch); + + while (1) + { + for (nostr::NostrPool *pool : pools) + { + // Run internal loop: refresh relays, complete pending connections, send + // pending messages + pool->loop(); + } + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +void setupNostrTask() +{ + xTaskCreate(nostrTask, "nostrTask", 16384, NULL, 10, &nostrTaskHandle); +} \ No newline at end of file diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp new file mode 100644 index 0000000..e76c87b --- /dev/null +++ b/src/lib/nostr_notify.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "shared.hpp" + +#include +#include +#include +#include +#include +#include +#include "price_notify.hpp" +#include "block_notify.hpp" + +void setupNostrNotify(); + +void nostrTask(void *pvParameters); +void setupNostrTask(); \ No newline at end of file diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 18f36ad..66381fb 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -98,31 +98,40 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { if (currentPrice != doc["bitcoin"].as()) { - uint minSecPriceUpd = preferences.getUInt( - "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); - uint currentTime = esp_timer_get_time() / 1000000; - - if (lastPriceUpdate == 0 || - (currentTime - lastPriceUpdate) > minSecPriceUpd) - { - // const unsigned long oldPrice = currentPrice; - currentPrice = doc["bitcoin"].as(); - preferences.putUInt("lastPrice", currentPrice); - lastPriceUpdate = currentTime; - // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || - getCurrentScreen() == SCREEN_MSCW_TIME || - getCurrentScreen() == SCREEN_MARKET_CAP)) - { - WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; - xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); - } - //} - } + processNewPrice(doc["bitcoin"].as()); } } } +void processNewPrice(uint newPrice) +{ + uint minSecPriceUpd = preferences.getUInt( + "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); + uint currentTime = esp_timer_get_time() / 1000000; + + if (lastPriceUpdate == 0 || + (currentTime - lastPriceUpdate) > minSecPriceUpd) + { + // const unsigned long oldPrice = currentPrice; + currentPrice = newPrice; + if (lastPriceUpdate == 0 || + (currentTime - lastPriceUpdate) > 120) + { + preferences.putUInt("lastPrice", currentPrice); + } + lastPriceUpdate = currentTime; + // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { + if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || + getCurrentScreen() == SCREEN_MSCW_TIME || + getCurrentScreen() == SCREEN_MARKET_CAP)) + { + WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; + xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); + } + //} + } +} + uint getLastPriceUpdate() { return lastPriceUpdate; diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 44c343d..485b096 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -17,6 +17,8 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); uint getPrice(); void setPrice(uint newPrice); +void processNewPrice(uint newPrice); + bool isPriceNotifyConnected(); void stopPriceNotify(); void restartPriceNotify(); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 432de65..175d8f0 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -442,7 +442,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay"}; for (String setting : strSettings) { @@ -477,7 +477,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr"}; for (String setting : boolSettings) { @@ -577,6 +577,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + root["useNostr"] = preferences.getBool("useNostr", DEFAULT_USE_NOSTR); root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER); root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD); root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS); @@ -596,6 +597,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["txPower"] = WiFi.getTxPower(); root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); + root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); + root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); + #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); diff --git a/src/main.cpp b/src/main.cpp index 0106eab..a5d980e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,7 +44,7 @@ extern "C" void app_main() int64_t currentUptime = esp_timer_get_time() / 1000000; ; - + if (!getIsOTAUpdating()) { #ifdef HAS_FRONTLIGHT From 60593de7855588ecefb962b7e9e9605107687b6d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 11 Jul 2024 22:21:28 +0200 Subject: [PATCH 051/188] Nostr library update and subscription improvement --- platformio.ini | 2 +- src/lib/nostr_notify.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 7392655..f4cb6e5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 - rblb/Nostrduino@^1.2.5 + rblb/Nostrduino@^1.2.7 [env:lolin_s3_mini] extends = btclock_base diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 4f7650f..a2784d6 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -7,8 +7,12 @@ TaskHandle_t nostrTaskHandle = NULL; void setupNostrNotify() { nostr::esp32::ESP32Platform::initNostr(false); - - + time_t now; + time(&now); + struct tm* utcTimeInfo; + utcTimeInfo = gmtime(&now); + time_t utcNow = mktime(utcTimeInfo); + time_t timestamp60MinutesAgo = utcNow - 3600; try { @@ -23,6 +27,7 @@ void setupNostrNotify() {{// we set the filters here (see // https://github.com/nostr-protocol/nips/blob/master/01.md#from-client-to-relay-sending-events-and-creating-subscriptions) {"kinds", {"1"}}, + {"since", {String(timestamp60MinutesAgo)}}, {"authors", {pubKey}}}}, [&](const String &subId, nostr::SignedNostrEvent *event) { From 19559727c98c3bc0ce1eb5999456bed5aee75721 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 15 Jul 2024 16:36:51 +0200 Subject: [PATCH 052/188] Add Nostr Relay connection status --- data | 2 +- platformio.ini | 2 +- src/lib/nostr_notify.cpp | 27 ++++++++++++++++++++++++++- src/lib/nostr_notify.hpp | 4 +++- src/lib/webserver.cpp | 1 + 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/data b/data index ee4d6d8..e7b52b7 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit ee4d6d88c76fa279e643faabf4216c88145e0b2c +Subproject commit e7b52b7367f0585e87e16ceaf22d9a2339a1fd04 diff --git a/platformio.ini b/platformio.ini index f4cb6e5..14818ad 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 - rblb/Nostrduino@^1.2.7 + rblb/Nostrduino@^1.2.8 [env:lolin_s3_mini] extends = btclock_base diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index a2784d6..68e84a3 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -3,13 +3,14 @@ std::vector pools; nostr::Transport *transport; TaskHandle_t nostrTaskHandle = NULL; +boolean nostrIsConnected = false; void setupNostrNotify() { nostr::esp32::ESP32Platform::initNostr(false); time_t now; time(&now); - struct tm* utcTimeInfo; + struct tm *utcTimeInfo; utcTimeInfo = gmtime(&now); time_t utcNow = mktime(utcTimeInfo); time_t timestamp60MinutesAgo = utcNow - 3600; @@ -98,6 +99,25 @@ void setupNostrNotify() // EOSE Serial.println("Subscription EOSE: " + subId); }); + + std::vector *relays = pool->getConnectedRelays(); + for (nostr::NostrRelay *relay : *relays) + { + Serial.println("Registering to connection events of: " + relay->getUrl()); + relay->getConnection()->addConnectionStatusListener([&](const nostr::ConnectionStatus &status) + { + String sstatus="UNKNOWN"; + if(status==nostr::ConnectionStatus::CONNECTED){ + nostrIsConnected = true; + sstatus="CONNECTED"; + }else if(status==nostr::ConnectionStatus::DISCONNECTED){ + nostrIsConnected = false; + sstatus="DISCONNECTED"; + }else if(status==nostr::ConnectionStatus::ERROR){ + sstatus = "ERROR"; + } + Serial.println("Connection status changed: " + sstatus); }); + } } catch (const std::exception &e) { @@ -125,4 +145,9 @@ void nostrTask(void *pvParameters) void setupNostrTask() { xTaskCreate(nostrTask, "nostrTask", 16384, NULL, 10, &nostrTaskHandle); +} + +boolean nostrConnected() +{ + return nostrIsConnected; } \ No newline at end of file diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp index e76c87b..65e1f19 100644 --- a/src/lib/nostr_notify.hpp +++ b/src/lib/nostr_notify.hpp @@ -14,4 +14,6 @@ void setupNostrNotify(); void nostrTask(void *pvParameters); -void setupNostrTask(); \ No newline at end of file +void setupNostrTask(); + +boolean nostrConnected(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 175d8f0..c257a70 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -229,6 +229,7 @@ JsonDocument getStatusObject() JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected(); + conStatus["nostr"] = nostrConnected(); root["rssi"] = WiFi.RSSI(); From ca1c7178f1ea7dea514b7ac06552bc33dfa5aadd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 15 Jul 2024 16:49:21 +0200 Subject: [PATCH 053/188] Dependency upgrade --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 14818ad..b0bbd93 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,7 +37,7 @@ lib_deps = mathieucarbou/ESP Async WebServer@2.10.8 adafruit/Adafruit BusIO@^1.16.1 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 - adafruit/Adafruit NeoPixel@^1.12.2 + adafruit/Adafruit NeoPixel@^1.12.3 https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 From b13c7242a6494dbcfb50d6217f9cef69ed86a4ff Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 29 Jul 2024 20:49:46 +0200 Subject: [PATCH 054/188] Added BitAxe support --- data | 2 +- lib/btclock/bitaxe_handler.cpp | 51 ++++ lib/btclock/bitaxe_handler.hpp | 5 + src/fonts/fonts.hpp | 1 + src/icons/icons.cpp | 515 +++++++++++++++++++++++++++++++++ src/icons/icons.h | 10 + src/lib/bitaxe_fetch.cpp | 61 ++++ src/lib/bitaxe_fetch.hpp | 15 + src/lib/config.cpp | 53 +++- src/lib/config.hpp | 8 +- src/lib/defaults.hpp | 4 + src/lib/epd.cpp | 22 ++ src/lib/epd.hpp | 2 + src/lib/screen_handler.cpp | 81 +++++- src/lib/screen_handler.hpp | 4 +- src/lib/shared.hpp | 7 + src/lib/webserver.cpp | 15 +- src/main.cpp | 27 +- 18 files changed, 837 insertions(+), 46 deletions(-) create mode 100644 lib/btclock/bitaxe_handler.cpp create mode 100644 lib/btclock/bitaxe_handler.hpp create mode 100644 src/icons/icons.cpp create mode 100644 src/icons/icons.h create mode 100644 src/lib/bitaxe_fetch.cpp create mode 100644 src/lib/bitaxe_fetch.hpp diff --git a/data b/data index e7b52b7..be5647e 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e7b52b7367f0585e87e16ceaf22d9a2339a1fd04 +Subproject commit be5647e1a518036aedec2e51543c19e04bbfa191 diff --git a/lib/btclock/bitaxe_handler.cpp b/lib/btclock/bitaxe_handler.cpp new file mode 100644 index 0000000..c78ae47 --- /dev/null +++ b/lib/btclock/bitaxe_handler.cpp @@ -0,0 +1,51 @@ +#include "bitaxe_handler.hpp" + +std::array parseBitaxeHashRate(std::string text) +{ + std::array ret; + ret.fill(""); // Initialize all elements to empty strings + + std::size_t textLength = text.length(); + + // Calculate the position where the digits should start + // Account for the position of the "mdi:pickaxe" and the "GH/S" label + std::size_t startIndex = NUM_SCREENS - 1 - textLength; + + // Insert the "mdi:pickaxe" icon just before the digits + if (startIndex > 0) + { + ret[startIndex - 1] = "mdi:pickaxe"; + } + + // Place the digits + for (std::size_t i = 0; i < textLength; ++i) + { + ret[startIndex + i] = text.substr(i, 1); + } + + ret[NUM_SCREENS - 1] = "GH/S"; + ret[0] = "BIT/AXE"; + + return ret; +} + +std::array parseBitaxeBestDiff(std::string text) +{ + std::array ret; + std::uint32_t firstIndex = 0; + + if (text.length() < NUM_SCREENS) + { + text.insert(text.begin(), NUM_SCREENS - text.length(), ' '); + ret[0] = "BIT/AXE"; + ret[1] = "mdi:rocket"; + firstIndex = 2; + } + + for (std::uint8_t i = firstIndex; i < NUM_SCREENS; i++) + { + ret[i] = text[i]; + } + + return ret; +} diff --git a/lib/btclock/bitaxe_handler.hpp b/lib/btclock/bitaxe_handler.hpp new file mode 100644 index 0000000..c78c8f7 --- /dev/null +++ b/lib/btclock/bitaxe_handler.hpp @@ -0,0 +1,5 @@ +#include +#include + +std::array parseBitaxeHashRate(std::string text); +std::array parseBitaxeBestDiff(std::string text); diff --git a/src/fonts/fonts.hpp b/src/fonts/fonts.hpp index 1504d45..1c12d49 100644 --- a/src/fonts/fonts.hpp +++ b/src/fonts/fonts.hpp @@ -5,6 +5,7 @@ #include "antonio-semibold40.h" #include "antonio-semibold90.h" #include "sats-symbol.h" +//#include "icons.h" // #include "oswald-20.h" // #include "oswald-30.h" diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp new file mode 100644 index 0000000..68583da --- /dev/null +++ b/src/icons/icons.cpp @@ -0,0 +1,515 @@ +#include "icons.h" + +// 'pickaxe', 122x250px +const unsigned char epd_icons_pickaxe [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x71, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x20, 0xe1, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x40, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xf8, 0xc0, 0x00, 0x7f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0x00, 0x07, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; +// 'rocket-launch', 122x250px +const unsigned char epd_icons_rocket_launch [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf9, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe1, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x83, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x03, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe7, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc3, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x83, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x01, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x0f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x3f, 0x9f, 0xe0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0xfe, 0x07, 0xf8, 0x00, 0x00, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x01, 0xfc, 0x03, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x03, 0xf8, 0x01, 0xfe, 0x00, 0x00, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x07, 0xf0, 0x00, 0xff, 0x00, 0x03, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x0f, 0xe0, 0x00, 0x7f, 0x80, 0x0f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x1f, 0xc0, 0x00, 0x7f, 0xe0, 0x7f, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x7f, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0xfe, 0x00, 0x03, 0xfb, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc1, 0xfc, 0x00, 0x07, 0xf0, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc3, 0xf8, 0x00, 0x0f, 0xe0, 0x3f, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc7, 0xf0, 0x00, 0x1f, 0xc0, 0x7f, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xcf, 0xe0, 0x00, 0x3f, 0x80, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xdf, 0xc0, 0x00, 0x7f, 0x01, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0xfe, 0x03, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x01, 0xfc, 0x07, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x03, 0xf8, 0x0f, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x07, 0xf0, 0x1f, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x0f, 0xe0, 0x3f, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x1f, 0xc0, 0x7f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x3f, 0x80, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x00, 0x7f, 0x01, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x00, 0xfe, 0x03, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x01, 0xfc, 0x07, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x03, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x07, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x0f, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x1f, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; + +// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) +const int epd_icons_allArray_LEN = 2; +const unsigned char* epd_icons_allArray[2] = { + epd_icons_pickaxe, + epd_icons_rocket_launch +}; diff --git a/src/icons/icons.h b/src/icons/icons.h new file mode 100644 index 0000000..136b4cc --- /dev/null +++ b/src/icons/icons.h @@ -0,0 +1,10 @@ +#pragma once + +#ifndef ICONS_H +#define ICONS_H + +#include + +extern const unsigned char* epd_icons_allArray[]; + +#endif // ICONS_H diff --git a/src/lib/bitaxe_fetch.cpp b/src/lib/bitaxe_fetch.cpp new file mode 100644 index 0000000..cfb7635 --- /dev/null +++ b/src/lib/bitaxe_fetch.cpp @@ -0,0 +1,61 @@ +#include "bitaxe_fetch.hpp" + +TaskHandle_t bitaxeFetchTaskHandle; + +std::string bitaxeHashrate; +std::string bitaxeBestDiff; + +std::string getBitAxeHashRate() +{ + return bitaxeHashrate; +} + +std::string getBitaxeBestDiff() +{ + return bitaxeBestDiff; +} + +void taskBitaxeFetch(void *pvParameters) +{ + for (;;) + { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + HTTPClient http; + http.setUserAgent(USER_AGENT); + String bitaxeApiUrl = "http://" + preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME) + "/api/system/info"; + http.begin(bitaxeApiUrl.c_str()); + + int httpCode = http.GET(); + + if (httpCode == 200) + { + String payload = http.getString(); + JsonDocument doc; + deserializeJson(doc, payload); + bitaxeHashrate = std::to_string(static_cast(std::round(doc["hashRate"].as()))); + bitaxeBestDiff = doc["bestDiff"].as(); + + if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BITAXE_HASHRATE || getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) + { + WorkItem priceUpdate = {TASK_BITAXE_UPDATE, 0}; + xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); + } + } + else + { + Serial.print( + F("Error retrieving BitAxe data. HTTP status code: ")); + Serial.println(httpCode); + Serial.println(bitaxeApiUrl); + } + } +} + +void setupBitaxeFetchTask() +{ + xTaskCreate(taskBitaxeFetch, "bitaxeFetch", (6 * 1024), NULL, tskIDLE_PRIORITY, + &bitaxeFetchTaskHandle); + + xTaskNotifyGive(bitaxeFetchTaskHandle); +} \ No newline at end of file diff --git a/src/lib/bitaxe_fetch.hpp b/src/lib/bitaxe_fetch.hpp new file mode 100644 index 0000000..8e4f7f3 --- /dev/null +++ b/src/lib/bitaxe_fetch.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "lib/config.hpp" +#include "lib/shared.hpp" + +extern TaskHandle_t bitaxeFetchTaskHandle; + +void setupBitaxeFetchTask(); +void taskBitaxeFetch(void *pvParameters); + +std::string getBitAxeHashRate(); +std::string getBitaxeBestDiff(); \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 61840c9..c2a90d1 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -14,10 +14,14 @@ BH1750 bh1750; bool hasLuxSensor = false; #endif -std::vector screenNameMap(SCREEN_COUNT); +std::vector screenMappings; std::mutex mcpMutex; uint lastTimeSync; +void addScreenMapping(int value, const char* name) { + screenMappings.push_back({value, name}); +} + void setup() { setupPreferences(); @@ -250,6 +254,7 @@ void syncTime() lastTimeSync = esp_timer_get_time() / 1000000; } + void setupPreferences() { preferences.begin("btclock", false); @@ -259,13 +264,26 @@ void setupPreferences() setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE)); - screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height"; - screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate"; - screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar"; - screenNameMap[SCREEN_BTC_TICKER] = "Ticker"; - screenNameMap[SCREEN_TIME] = "Time"; - screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown"; - screenNameMap[SCREEN_MARKET_CAP] = "Market Cap"; + addScreenMapping(SCREEN_BLOCK_HEIGHT, "Block Height"); + addScreenMapping(SCREEN_MSCW_TIME, "Sats per dollar"); + addScreenMapping(SCREEN_BTC_TICKER, "Ticker"); + addScreenMapping(SCREEN_TIME, "Time"); + addScreenMapping(SCREEN_HALVING_COUNTDOWN, "Halving countdown"); + addScreenMapping(SCREEN_MARKET_CAP, "Market Cap"); + addScreenMapping(SCREEN_BLOCK_FEE_RATE, "Block Fee Rate"); + + // screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height"; + // screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate"; + // screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar"; + // screenNameMap[SCREEN_BTC_TICKER] = "Ticker"; + // screenNameMap[SCREEN_TIME] = "Time"; + // screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown"; + // screenNameMap[SCREEN_MARKET_CAP] = "Market Cap"; + + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { + addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate"); + addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty"); + } } void setupWebsocketClients(void *pvParameters) @@ -281,6 +299,11 @@ void setupWebsocketClients(void *pvParameters) setupPriceNotify(); } + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) + { + setupBitaxeFetchTask(); + } + vTaskDelete(NULL); } @@ -304,7 +327,7 @@ void finishSetup() } } -std::vector getScreenNameMap() { return screenNameMap; } +std::vector getScreenNameMap() { return screenMappings; } void setupMcp() { @@ -419,6 +442,9 @@ void setupHardware() Serial.println(F("Found BH1750")); hasLuxSensor = true; bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C); + } else { + Serial.println(F("BH1750 Not found")); + hasLuxSensor = false; } #endif } @@ -796,3 +822,12 @@ String getFsRev() fsHash.close(); return ret; } + +int findScreenIndexByValue(int value) { + for (int i = 0; i < screenMappings.size(); i++) { + if (screenMappings[i].value == value) { + return i; + } + } + return -1; // Return -1 if value is not found +} diff --git a/src/lib/config.hpp b/src/lib/config.hpp index c0d0ac5..d4c8078 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -17,6 +17,7 @@ #include "lib/led_handler.hpp" #include "lib/ota.hpp" #include "lib/nostr_notify.hpp" +#include "lib/bitaxe_fetch.hpp" #include "lib/price_notify.hpp" #include "lib/screen_handler.hpp" @@ -53,7 +54,7 @@ extern PCA9685 flArray; #endif String getMyHostname(); -std::vector getScreenNameMap(); +std::vector getScreenNameMap(); std::vector getLocalUrl(); bool improv_connectWifi(std::string ssid, std::string password); @@ -67,4 +68,7 @@ void improv_set_error(improv::Error error); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); String getHwRev(); bool isWhiteVersion(); -String getFsRev(); \ No newline at end of file +String getFsRev(); + +void addScreenMapping(int value, const char* name); +int findScreenIndexByValue(int value); \ No newline at end of file diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 0de2415..884f715 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -45,3 +45,7 @@ #define DEFAULT_TIMER_ACTIVE true #define DEFAULT_TIMER_SECONDS 1800 #define DEFAULT_CURRENT_SCREEN 0 + +#define DEFAULT_BITAXE_ENABLED false +#define DEFAULT_BITAXE_HOSTNAME "bitaxe1" + diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 9fef2eb..fe86c92 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -264,6 +264,10 @@ void prepareDisplayUpdateTask(void *pvParameters) { renderQr(epdIndex, epdContent[epdIndex], updatePartial); } + else if (epdContent[epdIndex].startsWith(F("mdi"))) + { + renderIcon(epdIndex, epdContent[epdIndex], updatePartial); + } else if (epdContent[epdIndex].length() > 5) { renderText(epdIndex, epdContent[epdIndex], updatePartial); @@ -513,6 +517,24 @@ void renderText(const uint dispNum, const String &text, bool partial) } } +void renderIcon(const uint dispNum, const String &text, bool partial) +{ + displays[dispNum].setRotation(2); + + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].setTextColor(getFgColor()); + + uint iconIndex = 0; + if (text.endsWith("rocket")) { + iconIndex = 1; + } + + displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor()); + +} + void renderQr(const uint dispNum, const String &text, bool partial) { #ifdef USE_QR diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 275aaae..749f940 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -12,6 +12,7 @@ #include "fonts/fonts.hpp" #include "lib/config.hpp" #include "lib/shared.hpp" +#include "icons/icons.h" #ifdef USE_QR #include "qrcodegen.h" @@ -42,6 +43,7 @@ int getFgColor(); void setBgColor(int color); void setFgColor(int color); +void renderIcon(const uint dispNum, const String &text, bool partial); void renderText(const uint dispNum, const String &text, bool partial); void renderQr(const uint dispNum, const String &text, bool partial); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 85ecda5..c91c096 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -27,6 +27,18 @@ void workerTask(void *pvParameters) { // Process the work item based on its type switch (receivedItem.type) { + case TASK_BITAXE_UPDATE: { + if (getCurrentScreen() == SCREEN_BITAXE_HASHRATE) { + taskEpdContent = + parseBitaxeHashRate(getBitAxeHashRate()); + } else if (getCurrentScreen() == SCREEN_BITAXE_BESTDIFF) { + taskEpdContent = + parseBitaxeBestDiff(getBitaxeBestDiff()); + } + setEpdContent(taskEpdContent); + + } + break; case TASK_PRICE_UPDATE: { uint price = getPrice(); char priceSymbol = '$'; @@ -104,15 +116,7 @@ void taskScreenRotate(void *pvParameters) { for (;;) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - int nextScreen = (currentScreen + 1) % SCREEN_COUNT; - String key = "screen" + String(nextScreen) + "Visible"; - - while (!preferences.getBool(key.c_str(), true)) { - nextScreen = (nextScreen + 1) % SCREEN_COUNT; - key = "screen" + String(nextScreen) + "Visible"; - } - - setCurrentScreen(nextScreen); + nextScreen(); } } @@ -124,6 +128,11 @@ void IRAM_ATTR minuteTimerISR(void *arg) { if (priceFetchTaskHandle != NULL) { vTaskNotifyGiveFromISR(priceFetchTaskHandle, &xHigherPriorityTaskWoken); } + + if (bitaxeFetchTaskHandle != NULL) { + vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); + } + if (xHigherPriorityTaskWoken == pdTRUE) { portYIELD_FROM_ISR(); } @@ -245,28 +254,72 @@ void setCurrentScreen(uint newScreen) { xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); break; } + case SCREEN_BITAXE_BESTDIFF: + case SCREEN_BITAXE_HASHRATE: { + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { + WorkItem bitaxeUpdate = {TASK_BITAXE_UPDATE, 0}; + xQueueSend(workQueue, &bitaxeUpdate, portMAX_DELAY); + } else { + setCurrentScreen(SCREEN_BLOCK_HEIGHT); + return; + } + break; + } } if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); } void nextScreen() { - int newCurrentScreen = (getCurrentScreen() + 1) % SCREEN_COUNT; - String key = "screen" + String(newCurrentScreen) + "Visible"; + int currentIndex = findScreenIndexByValue(getCurrentScreen()); + std::vector screenMappings = getScreenNameMap(); + + int newCurrentScreen; + + if (currentIndex < screenMappings.size() - 1) { + newCurrentScreen = (screenMappings[currentIndex + 1].value); + } else { + newCurrentScreen = screenMappings.front().value; + } + + String key = "screen" + String(screenMappings[currentIndex - 1].value) + "Visible"; while (!preferences.getBool(key.c_str(), true)) { - newCurrentScreen = (newCurrentScreen + 1) % SCREEN_COUNT; + currentIndex = findScreenIndexByValue(newCurrentScreen); + if (currentIndex < screenMappings.size() - 1) { + newCurrentScreen = (screenMappings[currentIndex + 1].value); + } else { + newCurrentScreen = screenMappings.front().value; + } + key = "screen" + String(newCurrentScreen) + "Visible"; } + setCurrentScreen(newCurrentScreen); } void previousScreen() { - int newCurrentScreen = modulo(getCurrentScreen() - 1, SCREEN_COUNT); + int currentIndex = findScreenIndexByValue(getCurrentScreen()); + std::vector screenMappings = getScreenNameMap(); + + int newCurrentScreen; + + if (currentIndex > 0) { + newCurrentScreen = screenMappings[currentIndex - 1].value; + } else { + newCurrentScreen = screenMappings.back().value; + } + String key = "screen" + String(newCurrentScreen) + "Visible"; while (!preferences.getBool(key.c_str(), true)) { - newCurrentScreen = modulo(newCurrentScreen - 1, SCREEN_COUNT); + int currentIndex = findScreenIndexByValue(newCurrentScreen); + if (currentIndex > 0) { + newCurrentScreen = screenMappings[currentIndex - 1].value; + } else { + newCurrentScreen = screenMappings.back().value; + } + key = "screen" + String(newCurrentScreen) + "Visible"; } setCurrentScreen(newCurrentScreen); diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index 6cbb2d0..a3ecaf8 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -5,6 +5,7 @@ #include #include +#include #include "lib/epd.hpp" #include "lib/price_fetch.hpp" @@ -25,7 +26,8 @@ typedef enum { TASK_PRICE_UPDATE, TASK_BLOCK_UPDATE, TASK_FEE_UPDATE, - TASK_TIME_UPDATE + TASK_TIME_UPDATE, + TASK_BITAXE_UPDATE } TaskType; typedef struct { diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 60cfbd1..f3a21cc 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -35,6 +35,8 @@ const PROGMEM int SCREEN_TIME = 3; const PROGMEM int SCREEN_HALVING_COUNTDOWN = 4; const PROGMEM int SCREEN_MARKET_CAP = 5; const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6; +const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; +const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_CUSTOM = 99; @@ -58,4 +60,9 @@ struct SpiRamAllocator : ArduinoJson::Allocator { void* reallocate(void* ptr, size_t new_size) override { return heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM); } +}; + +struct ScreenMapping { + int value; + const char* name; }; \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index c257a70..43ba652 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -443,7 +443,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname"}; for (String setting : strSettings) { @@ -478,7 +478,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled"}; for (String setting : boolSettings) { @@ -601,6 +601,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); + root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); + root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); + #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); @@ -629,14 +632,14 @@ void onApiSettingsGet(AsyncWebServerRequest *request) #endif JsonArray screens = root["screens"].to(); - std::vector screenNameMap = getScreenNameMap(); + std::vector screenNameMap = getScreenNameMap(); for (int i = 0; i < screenNameMap.size(); i++) { JsonObject o = screens.add(); - String key = "screen" + String(i) + "Visible"; - o["id"] = i; - o["name"] = screenNameMap[i]; + String key = "screen" + String(screenNameMap.at(i).value) + "Visible"; + o["id"] = screenNameMap.at(i).value; + o["name"] = screenNameMap.at(i).name; o["enabled"] = preferences.getBool(key.c_str(), true); } diff --git a/src/main.cpp b/src/main.cpp index a5d980e..ef8594e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,24 +48,25 @@ extern "C" void app_main() if (!getIsOTAUpdating()) { #ifdef HAS_FRONTLIGHT - if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) - { - if (hasLightLevel() && getLightLevel() == 0) + if (hasLightLevel()) { + if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { - if (frontlightIsOn()) { + if (hasLightLevel() && getLightLevel() == 0) + { + if (frontlightIsOn()) { + frontlightFadeOutAll(); + } + } + else if (hasLightLevel() && getLightLevel() < preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) && !frontlightIsOn()) + { + frontlightFadeInAll(); + } + else if (frontlightIsOn() && getLightLevel() > preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE)) + { frontlightFadeOutAll(); } } - else if (hasLightLevel() && getLightLevel() < preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) && !frontlightIsOn()) - { - frontlightFadeInAll(); - } - else if (frontlightIsOn() && getLightLevel() > preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE)) - { - frontlightFadeOutAll(); - } } - #endif if (!WiFi.isConnected()) From 0a08c5f9eaa2dab3f47cfe73879c7d7bebde844c Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 15 Aug 2024 00:37:50 +0200 Subject: [PATCH 055/188] Begin support for other currencies --- lib/btclock/data_handler.cpp | 145 ++++++++++++++++++++++++----------- lib/btclock/data_handler.hpp | 13 +++- src/lib/screen_handler.cpp | 2 +- 3 files changed, 115 insertions(+), 45 deletions(-) diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 7d8e9d0..6592807 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -4,27 +4,76 @@ #include #endif +char getCurrencySymbol(char input) +{ + switch (input) + { + case CURRENCY_EUR: + return '['; + break; + case CURRENCY_GBP: + return '\\'; + break; + case CURRENCY_JPY: + return ']'; + break; + case CURRENCY_AUD: + case CURRENCY_CAD: + case CURRENCY_USD: + return '$'; + break; + default: + return input; + } +} + +std::string getCurrencyCode(char input) +{ + switch (input) + { + case CURRENCY_EUR: + return "EUR"; + break; + case CURRENCY_GBP: + return "GBP"; + break; + case CURRENCY_JPY: + return "YEN"; + break; + case CURRENCY_AUD: + return "AUD"; + break; + case CURRENCY_CHF: + return "CHF"; + break; + case CURRENCY_CAD: + return "CAD"; + break; + default: + return "USD"; + } +} + std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat) { std::array ret; std::string priceString; - if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) { - priceString = currencySymbol + formatNumberWithSuffix(price, NUM_SCREENS-2); - } else { - priceString = currencySymbol + std::to_string(price); + if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) + { + priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, NUM_SCREENS - 2); + } + else + { + priceString = getCurrencySymbol(currencySymbol) + std::to_string(price); } std::uint32_t firstIndex = 0; if (priceString.length() < (NUM_SCREENS)) { priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); - if (currencySymbol == '[') - { - ret[0] = "BTC/EUR"; - } - else - { - ret[0] = "BTC/USD"; - } + + ret[0] = "BTC/" + getCurrencyCode(currencySymbol); + + firstIndex = 1; } @@ -36,7 +85,7 @@ std::array parsePriceData(std::uint32_t price, char cu return ret; } -std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol) +std::array parseSatsPerCurrency(std::uint32_t price,char currencySymbol, bool withSatsSymbol) { std::array ret; std::string priceString = std::to_string(int(round(1 / float(price) * 10e7))); @@ -47,15 +96,11 @@ std::array parseSatsPerCurrency(std::uint32_t price, c { priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); - - if (currencySymbol == '[') - { - ret[0] = "SATS/EUR"; - } - else - { + if (currencySymbol != CURRENCY_USD) + ret[0] = "SATS/" + getCurrencyCode(currencySymbol); + else ret[0] = "MSCW/TIME"; - } + firstIndex = 1; for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++) @@ -63,7 +108,8 @@ std::array parseSatsPerCurrency(std::uint32_t price, c ret[i] = priceString[i]; } - if (withSatsSymbol) { + if (withSatsSymbol) + { ret[insertSatSymbol] = "STS"; } } @@ -91,7 +137,8 @@ std::array parseBlockHeight(std::uint32_t blockHeight) return ret; } -std::array parseBlockFees(std::uint16_t blockFees) { +std::array parseBlockFees(std::uint16_t blockFees) +{ std::array ret; std::string blockFeesString = std::to_string(blockFees); std::uint32_t firstIndex = 0; @@ -103,12 +150,12 @@ std::array parseBlockFees(std::uint16_t blockFees) { firstIndex = 1; } - for (std::uint8_t i = firstIndex; i < NUM_SCREENS-1; i++) + for (std::uint8_t i = firstIndex; i < NUM_SCREENS - 1; i++) { ret[i] = blockFeesString[i]; } - ret[NUM_SCREENS-1] = "sat/vB"; + ret[NUM_SCREENS - 1] = "sat/vB"; return ret; } @@ -119,7 +166,8 @@ std::array parseHalvingCountdown(std::uint32_t blockHe const std::uint32_t nextHalvingBlock = 210000 - (blockHeight % 210000); const std::uint32_t minutesToHalving = nextHalvingBlock * 10; - if (asBlocks) { + if (asBlocks) + { std::string blockNrString = std::to_string(nextHalvingBlock); std::uint32_t firstIndex = 0; @@ -134,9 +182,9 @@ std::array parseHalvingCountdown(std::uint32_t blockHe { ret[i] = blockNrString[i]; } - - } else { - + } + else + { const int years = floor(minutesToHalving / 525600); const int days = floor((minutesToHalving - (years * 525600)) / (24 * 60)); @@ -174,7 +222,7 @@ std::array parseMarketCap(std::uint32_t blockHeight, s firstIndex = 1; // Serial.print("Market cap: "); // Serial.println(marketCap); - std::string priceString = currencySymbol + formatNumberWithSuffix(marketCap, (NUM_SCREENS-2)); + std::string priceString = currencySymbol + formatNumberWithSuffix(marketCap, (NUM_SCREENS - 2)); priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++) @@ -212,50 +260,61 @@ std::array parseMarketCap(std::uint32_t blockHeight, s } #ifdef __EMSCRIPTEN__ -emscripten::val arrayToStringArray(const std::array& arr) { +emscripten::val arrayToStringArray(const std::array &arr) +{ emscripten::val jsArray = emscripten::val::array(); - for (const auto& str : arr) { + for (const auto &str : arr) + { jsArray.call("push", str); } return jsArray; } -emscripten::val vectorToStringArray(const std::vector& vec) { +emscripten::val vectorToStringArray(const std::vector &vec) +{ emscripten::val jsArray = emscripten::val::array(); - for (size_t i = 0; i < vec.size(); ++i) { + for (size_t i = 0; i < vec.size(); ++i) + { jsArray.set(i, vec[i]); } return jsArray; } -emscripten::val parseBlockHeightArray(std::uint32_t blockHeight) { +emscripten::val parseBlockHeightArray(std::uint32_t blockHeight) +{ return arrayToStringArray(parseBlockHeight(blockHeight)); } -emscripten::val parsePriceDataArray(std::uint32_t price, const std::string& currencySymbol, bool useSuffixFormat = false) { +emscripten::val parsePriceDataArray(std::uint32_t price, const std::string ¤cySymbol, bool useSuffixFormat = false) +{ return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat)); } -emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks) { +emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks) +{ return arrayToStringArray(parseHalvingCountdown(blockHeight, asBlocks)); } -emscripten::val parseMarketCapArray(std::uint32_t blockHeight, std::uint32_t price, const std::string& currencySymbol, bool bigChars) { +emscripten::val parseMarketCapArray(std::uint32_t blockHeight, std::uint32_t price, const std::string ¤cySymbol, bool bigChars) +{ return arrayToStringArray(parseMarketCap(blockHeight, price, currencySymbol[0], bigChars)); } -emscripten::val parseBlockFeesArray(std::uint16_t blockFees) { +emscripten::val parseBlockFeesArray(std::uint16_t blockFees) +{ return arrayToStringArray(parseBlockFees(blockFees)); } -emscripten::val parseSatsPerCurrencyArray(std::uint32_t price, const std::string& currencySymbol, bool withSatsSymbol) { +emscripten::val parseSatsPerCurrencyArray(std::uint32_t price, const std::string ¤cySymbol, bool withSatsSymbol) +{ return arrayToStringArray(parseSatsPerCurrency(price, currencySymbol[0], withSatsSymbol)); } -EMSCRIPTEN_BINDINGS(my_module) { -// emscripten::register_vector("StringList"); +EMSCRIPTEN_BINDINGS(my_module) +{ + // emscripten::register_vector("StringList"); - emscripten::function("parseBlockHeight", &parseBlockHeightArray); + emscripten::function("parseBlockHeight", &parseBlockHeightArray); emscripten::function("parseHalvingCountdown", &parseHalvingCountdownArray); emscripten::function("parseMarketCap", &parseMarketCapArray); emscripten::function("parseBlockFees", &parseBlockFeesArray); diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index b31cfe7..226758f 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -5,9 +5,20 @@ #include "utils.hpp" -std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat = false); +const char CURRENCY_USD = '$'; +const char CURRENCY_EUR = '['; +const char CURRENCY_GBP = '\\'; +const char CURRENCY_JPY = ']'; +const char CURRENCY_AUD = '^'; +const char CURRENCY_CHF = '_'; +const char CURRENCY_CAD = '`'; + +std::array parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false); std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array parseBlockHeight(std::uint32_t blockHeight); std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks); std::array parseMarketCap(std::uint32_t blockHeight, std::uint32_t price, char currencySymbol, bool bigChars); std::array parseBlockFees(std::uint16_t blockFees); + +char getCurrencySymbol(char input); +std::string getCurrencyCode(char input); \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index c91c096..269d2a7 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -41,7 +41,7 @@ void workerTask(void *pvParameters) { break; case TASK_PRICE_UPDATE: { uint price = getPrice(); - char priceSymbol = '$'; + u_char priceSymbol = '$'; if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) { priceSymbol = '['; } From 99e622eeefc102b9e17e3e3fcc0d321e0f6484b1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 19 Aug 2024 01:53:16 +0200 Subject: [PATCH 056/188] Lock dependency --- platformio.ini | 2 +- src/lib/nostr_notify.hpp | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index b0bbd93..2321a84 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 - rblb/Nostrduino@^1.2.8 + rblb/Nostrduino@1.2.8 [env:lolin_s3_mini] extends = btclock_base diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp index 65e1f19..d604f96 100644 --- a/src/lib/nostr_notify.hpp +++ b/src/lib/nostr_notify.hpp @@ -3,11 +3,10 @@ #include "shared.hpp" #include -#include -#include -#include -#include -#include +#include "esp32/ESP32Platform.h" +#include "NostrEvent.h" +#include "NostrPool.h" + #include "price_notify.hpp" #include "block_notify.hpp" From e39a0ccc14c255ca7029deaec6ee5f1ba4706519 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 24 Aug 2024 16:27:55 +0300 Subject: [PATCH 057/188] Add zap notify functionality --- data | 2 +- lib/btclock/bitaxe_handler.cpp | 1 + lib/btclock/utils.cpp | 54 ++++++- lib/btclock/utils.hpp | 3 +- src/icons/icons.cpp | 265 ++++++++++++++++++++++++++++++++- src/lib/config.cpp | 5 +- src/lib/defaults.hpp | 4 +- src/lib/epd.cpp | 4 + src/lib/led_handler.cpp | 41 ++++- src/lib/led_handler.hpp | 4 + src/lib/nostr_notify.cpp | 191 ++++++++++++++++-------- src/lib/nostr_notify.hpp | 3 +- src/lib/webserver.cpp | 7 +- 13 files changed, 506 insertions(+), 78 deletions(-) diff --git a/data b/data index be5647e..876f3b0 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit be5647e1a518036aedec2e51543c19e04bbfa191 +Subproject commit 876f3b01d8b4a92b13c869b90fdb0abc978503d9 diff --git a/lib/btclock/bitaxe_handler.cpp b/lib/btclock/bitaxe_handler.cpp index c78ae47..cbc71ec 100644 --- a/lib/btclock/bitaxe_handler.cpp +++ b/lib/btclock/bitaxe_handler.cpp @@ -49,3 +49,4 @@ std::array parseBitaxeBestDiff(std::string text) return ret; } + diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index b801fc2..c419603 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -82,4 +82,56 @@ std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters) } return result; -} \ No newline at end of file +} + +/** + * Get sat amount from a bolt11 invoice + * + * Based on https://github.com/lnbits/nostr-zap-lamp/blob/main/nostrZapLamp/nostrZapLamp.ino + */ +int64_t getAmountInSatoshis(std::string bolt11) { + int64_t number = -1; + char multiplier = ' '; + + for (unsigned int i = 0; i < bolt11.length(); ++i) { + if (isdigit(bolt11[i])) { + number = 0; + while (isdigit(bolt11[i])) { + number = number * 10 + (bolt11[i] - '0'); + ++i; + } + for (unsigned int j = i; j < bolt11.length(); ++j) { + if (isalpha(bolt11[j])) { + multiplier = bolt11[j]; + break; + } + } + break; + } + } + + if (number == -1 || multiplier == ' ') { + return -1; + } + + int64_t satoshis = number; + + switch (multiplier) { + case 'm': + satoshis *= 100000; // 0.001 * 100,000,000 + break; + case 'u': + satoshis *= 100; // 0.000001 * 100,000,000 + break; + case 'n': + satoshis /= 10; // 0.000000001 * 100,000,000 + break; + case 'p': + satoshis /= 10000; // 0.000000000001 * 100,000,000 + break; + default: + return -1; + } + + return satoshis; +} diff --git a/lib/btclock/utils.hpp b/lib/btclock/utils.hpp index db338c0..b166cae 100644 --- a/lib/btclock/utils.hpp +++ b/lib/btclock/utils.hpp @@ -10,4 +10,5 @@ int modulo(int x,int N); double getSupplyAtBlock(std::uint32_t blockNr); -std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4); \ No newline at end of file +std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4); +int64_t getAmountInSatoshis(std::string bolt11); \ No newline at end of file diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index 68583da..6e76c4f 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -507,9 +507,264 @@ const unsigned char epd_icons_rocket_launch [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; -// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) -const int epd_icons_allArray_LEN = 2; -const unsigned char* epd_icons_allArray[2] = { - epd_icons_pickaxe, - epd_icons_rocket_launch +const unsigned char epd_icons_lightning [] PROGMEM = { + // 'lightning-bolt, 122x250px + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; + +// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) +const int epd_icons_allArray_LEN = 3; +const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { + epd_icons_pickaxe, + epd_icons_rocket_launch, + epd_icons_lightning }; diff --git a/src/lib/config.cpp b/src/lib/config.cpp index c2a90d1..79ffd4a 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -72,12 +72,13 @@ void setup() setupTasks(); setupTimers(); - if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) + if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR) || preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED)) { setupNostrNotify(); setupNostrTask(); } - else + + if (!preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) { xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, tskIDLE_PRIORITY, NULL); diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 884f715..f9e19b5 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -24,7 +24,7 @@ #define DEFAULT_USE_NOSTR false #define DEFAULT_NOSTR_NPUB "642317135fd4c4205323b9dea8af3270657e62d51dc31a657c0ec8aab31c6288" -#define DEFAULT_NOSTR_RELAY "wss://nostr.dbtc.link" +#define DEFAULT_NOSTR_RELAY "wss://relay.primal.net" #define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30 #define DEFAULT_MINUTES_FULL_REFRESH 60 @@ -49,3 +49,5 @@ #define DEFAULT_BITAXE_ENABLED false #define DEFAULT_BITAXE_HOSTNAME "bitaxe1" +#define DEFAULT_ZAP_NOTIFY_ENABLED false +#define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" \ No newline at end of file diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index fe86c92..3e39424 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -531,6 +531,10 @@ void renderIcon(const uint dispNum, const String &text, bool partial) iconIndex = 1; } + if (text.endsWith("lnbolt")) { + iconIndex = 2; + } + displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor()); } diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index a1d06fa..e124ea1 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -221,9 +221,9 @@ void ledTask(void *parameter) { oldLights[i] = pixels.getPixelColor(i); } - #ifdef HAS_FRONTLIGHT +#ifdef HAS_FRONTLIGHT uint flDelayTime = preferences.getUInt("flEffectDelay"); - #endif +#endif switch (ledTaskParams) { case LED_POWER_TEST: @@ -269,6 +269,43 @@ void ledTask(void *parameter) pixels.setPixelColor(3, pixels.Color(0, 255, 0)); pixels.show(); break; + case LED_EFFECT_NOSTR_ZAP: + { +#ifdef HAS_FRONTLIGHT + bool frontlightWasOn = false; + + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) + { + if (frontlightOn) + { + frontlightWasOn = true; + frontlightFadeOutAll(flDelayTime, true); + } + else + { + frontlightFadeInAll(flDelayTime, true); + } + } +#endif + blinkDelayColor(250, 3, 142, 48, 235); + // blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235), + // pixels.Color(169, 21, 255)); +#ifdef HAS_FRONTLIGHT + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) + { + vTaskDelay(pdMS_TO_TICKS(10)); + if (frontlightWasOn) + { + frontlightFadeInAll(flDelayTime, true); + } + else + { + frontlightFadeOutAll(flDelayTime, true); + } + } +#endif + break; + } case LED_FLASH_UPDATE: break; case LED_FLASH_BLOCK_NOTIFY: diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 0c3e731..898b121 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -28,6 +28,7 @@ const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103; const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104; + const int LED_PROGRESS_25 = 200; const int LED_PROGRESS_50 = 201; const int LED_PROGRESS_75 = 202; @@ -35,6 +36,9 @@ const int LED_PROGRESS_100 = 203; const int LED_DATA_PRICE_ERROR = 300; const int LED_DATA_BLOCK_ERROR = 301; + +const int LED_EFFECT_NOSTR_ZAP = 400; + const int LED_FLASH_IDENTIFY = 990; const int LED_POWER_TEST = 999; extern TaskHandle_t ledTaskHandle; diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 68e84a3..ea5743b 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -22,71 +22,33 @@ void setupNostrNotify() String relay = preferences.getString("nostrRelay"); String pubKey = preferences.getString("nostrPubKey"); pools.push_back(pool); + + // JsonArray filter = { + // {"kinds", {"1"}}, + // {"since", {String(timestamp60MinutesAgo)}}, + // {"authors", {pubKey}}, + // }; + + // { "kinds", {"9735"}, {"limit",{"1"}, + // {"#p", {"b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422"} }, + // Lets subscribe to the relay String subId = pool->subscribeMany( {relay}, - {{// we set the filters here (see - // https://github.com/nostr-protocol/nips/blob/master/01.md#from-client-to-relay-sending-events-and-creating-subscriptions) - {"kinds", {"1"}}, - {"since", {String(timestamp60MinutesAgo)}}, - {"authors", {pubKey}}}}, - [&](const String &subId, nostr::SignedNostrEvent *event) - { - // Received events callback, we can access the event content with - // event->getContent() Here you should handle the event, for this - // test we will just serialize it and print to console - JsonDocument doc; - JsonArray arr = doc["data"].to(); - event->toSendableEvent(arr); - // Access the second element which is the object - JsonObject obj = arr[1].as(); - - // Access the "tags" array - JsonArray tags = obj["tags"].as(); - - // Flag to check if the tag was found - bool tagFound = false; - uint medianFee = 0; - String typeValue; - - // Iterate over the tags array - for (JsonArray tag : tags) - { - // Check if the tag is an array with two elements - if (tag.size() == 2) - { - const char *key = tag[0]; - const char *value = tag[1]; - - // Check if the key is "type" and the value is "priceUsd" - if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0)) - { - typeValue = value; - tagFound = true; - } - else if (strcmp(key, "medianFee") == 0) - { - medianFee = tag[1].as(); - } - } - } - if (tagFound) - { - if (typeValue.equals("priceUsd")) - { - processNewPrice(obj["content"].as()); - } - else if (typeValue.equals("blockHeight")) - { - processNewBlock(obj["content"].as()); - } - - if (medianFee != 0) - { - processNewBlockFee(medianFee); - } - } - }, + {// First filter + { + {"kinds", {"9735"}}, + {"limit", {"1"}}, + {"since", {String(timestamp60MinutesAgo)}}, + {"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY)}}, + }, + // Second filter + { + {"kinds", {"1"}}, + {"since", {String(timestamp60MinutesAgo)}}, + {"authors", {pubKey}}, + }}, + handleNostrEventCallback, [&](const String &subId, const String &reason) { // This is the callback that will be called when the subscription is @@ -150,4 +112,109 @@ void setupNostrTask() boolean nostrConnected() { return nostrIsConnected; +} + +void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event) +{ + // Received events callback, we can access the event content with + // event->getContent() Here you should handle the event, for this + // test we will just serialize it and print to console + JsonDocument doc; + JsonArray arr = doc["data"].to(); + event->toSendableEvent(arr); + // Access the second element which is the object + JsonObject obj = arr[1].as(); + String json; + + // Serial.println(obj["kind"].as()); + + // Access the "tags" array + JsonArray tags = obj["tags"].as(); + + // Flag to check if the tag was found + bool tagFound = false; + uint medianFee = 0; + String typeValue; + + // Iterate over the tags array + for (JsonArray tag : tags) + { + // Check if the tag is an array with two elements + if (tag.size() == 2) + { + const char *key = tag[0]; + const char *value = tag[1]; + + // Check if the key is "type" and the value is "priceUsd" + if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0)) + { + typeValue = value; + tagFound = true; + } + else if (strcmp(key, "medianFee") == 0) + { + medianFee = tag[1].as(); + } + + if (strcmp(key, "bolt11") == 0) + { + + String text = String(getAmountInSatoshis(std::string(value))); + + std::size_t textLength = text.length(); + + // Calculate the position where the digits should start + // Account for the position of the "mdi:pickaxe" and the "GH/S" label + std::size_t startIndex = NUM_SCREENS - textLength; + + std::array textEpdContent = {"ZAP", "mdi-lnbolt", "", "", "", "", ""}; + + // Insert the "mdi:pickaxe" icon just before the digits + if (startIndex > 0 && preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)) + { + textEpdContent[startIndex - 1] = "STS"; + } + + // Place the digits + for (std::size_t i = 0; i < textLength; i++) + { + textEpdContent[startIndex + i] = text.substring(i, i + 1); + } + + uint64_t timerPeriod = 0; + if (isTimerActive()) + { + // store timer periode before making inactive to prevent artifacts + timerPeriod = getTimerSeconds(); + esp_timer_stop(screenRotateTimer); + } + setCurrentScreen(SCREEN_CUSTOM); + + setEpdContent(textEpdContent); + vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); + queueLedEffect(LED_EFFECT_NOSTR_ZAP); + if (timerPeriod > 0) + { + esp_timer_start_periodic(screenRotateTimer, + timerPeriod * usPerSecond); + } + } + } + } + if (tagFound) + { + if (typeValue.equals("priceUsd")) + { + processNewPrice(obj["content"].as()); + } + else if (typeValue.equals("blockHeight")) + { + processNewBlock(obj["content"].as()); + } + + if (medianFee != 0) + { + processNewBlockFee(medianFee); + } + } } \ No newline at end of file diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp index d604f96..7aa7afc 100644 --- a/src/lib/nostr_notify.hpp +++ b/src/lib/nostr_notify.hpp @@ -15,4 +15,5 @@ void setupNostrNotify(); void nostrTask(void *pvParameters); void setupNostrTask(); -boolean nostrConnected(); \ No newline at end of file +boolean nostrConnected(); +void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 43ba652..47565b7 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -443,7 +443,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey"}; for (String setting : strSettings) { @@ -478,7 +478,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify"}; for (String setting : boolSettings) { @@ -601,6 +601,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); + root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); + root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); + root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); From 27776373556cbf932ec8976e5877d469351423fb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 31 Aug 2024 15:45:19 +0200 Subject: [PATCH 058/188] Improve Nostr Zap functionality, BitAxe integration bugfix --- data | 2 +- lib/btclock/nostrdisplay_handler.cpp | 24 +++ lib/btclock/nostrdisplay_handler.hpp | 5 + src/icons/icons.cpp | 259 ++++++++++++++++++++++++++- src/lib/config.cpp | 50 +++--- src/lib/config.hpp | 16 +- src/lib/epd.cpp | 2 +- src/lib/nostr_notify.cpp | 177 +++++++++--------- src/lib/nostr_notify.hpp | 11 +- 9 files changed, 430 insertions(+), 116 deletions(-) create mode 100644 lib/btclock/nostrdisplay_handler.cpp create mode 100644 lib/btclock/nostrdisplay_handler.hpp diff --git a/data b/data index 876f3b0..21a7192 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 876f3b01d8b4a92b13c869b90fdb0abc978503d9 +Subproject commit 21a7192e6da4dbd38b49dba7d2840d09cf8a5073 diff --git a/lib/btclock/nostrdisplay_handler.cpp b/lib/btclock/nostrdisplay_handler.cpp new file mode 100644 index 0000000..8fabe1f --- /dev/null +++ b/lib/btclock/nostrdisplay_handler.cpp @@ -0,0 +1,24 @@ +#include "nostrdisplay_handler.hpp" + +std::array parseZapNotify(std::uint16_t amount, bool withSatsSymbol) +{ + std::string text = std::to_string(amount); + std::size_t textLength = text.length(); + std::size_t startIndex = NUM_SCREENS - textLength; + + std::array textEpdContent = {"ZAP", "mdi-lnbolt", "", "", "", "", ""}; + + // Insert the sats symbol just before the digits + if (startIndex > 0 && withSatsSymbol) + { + textEpdContent[startIndex - 1] = "STS"; + } + + // Place the digits + for (std::size_t i = 0; i < textLength; i++) + { + textEpdContent[startIndex + i] = text.substr(i, 1); + } + + return textEpdContent; +} \ No newline at end of file diff --git a/lib/btclock/nostrdisplay_handler.hpp b/lib/btclock/nostrdisplay_handler.hpp new file mode 100644 index 0000000..dd2b8ed --- /dev/null +++ b/lib/btclock/nostrdisplay_handler.hpp @@ -0,0 +1,5 @@ +#include +#include +#include "utils.hpp" + +std::array parseZapNotify(std::uint16_t amount, bool withSatsSymbol); diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index 6e76c4f..3254740 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -761,10 +761,265 @@ const unsigned char epd_icons_lightning [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; +// 'flash', 122x250px +const unsigned char epd_icons_flash [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; + // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) -const int epd_icons_allArray_LEN = 3; +const int epd_icons_allArray_LEN = 4; const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { epd_icons_pickaxe, epd_icons_rocket_launch, - epd_icons_lightning + epd_icons_lightning, + epd_icons_flash }; diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 79ffd4a..54c70e0 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -18,8 +18,9 @@ std::vector screenMappings; std::mutex mcpMutex; uint lastTimeSync; -void addScreenMapping(int value, const char* name) { - screenMappings.push_back({value, name}); +void addScreenMapping(int value, const char *name) +{ + screenMappings.push_back({value, name}); } void setup() @@ -74,16 +75,21 @@ void setup() if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR) || preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED)) { - setupNostrNotify(); + setupNostrNotify(preferences.getBool("useNostr", DEFAULT_USE_NOSTR), preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED)); setupNostrTask(); } - + if (!preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) { xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, tskIDLE_PRIORITY, NULL); } + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) + { + setupBitaxeFetchTask(); + } + setupButtonTask(); setupOTA(); @@ -255,7 +261,6 @@ void syncTime() lastTimeSync = esp_timer_get_time() / 1000000; } - void setupPreferences() { preferences.begin("btclock", false); @@ -281,9 +286,10 @@ void setupPreferences() // screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown"; // screenNameMap[SCREEN_MARKET_CAP] = "Market Cap"; - if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { - addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate"); - addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty"); + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) + { + addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate"); + addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty"); } } @@ -300,11 +306,6 @@ void setupWebsocketClients(void *pvParameters) setupPriceNotify(); } - if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) - { - setupBitaxeFetchTask(); - } - vTaskDelete(NULL); } @@ -443,13 +444,17 @@ void setupHardware() Serial.println(F("Found BH1750")); hasLuxSensor = true; bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C); - } else { + } + else + { Serial.println(F("BH1750 Not found")); hasLuxSensor = false; } #endif } + +#ifdef IMPROV_ENABLED void improvGetAvailableWifiNetworks() { int networkNum = WiFi.scanNetworks(); @@ -658,6 +663,8 @@ void improv_set_error(improv::Error error) Serial.write(data.data(), data.size()); } +#endif + void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { static bool first_connect = true; @@ -824,11 +831,14 @@ String getFsRev() return ret; } -int findScreenIndexByValue(int value) { - for (int i = 0; i < screenMappings.size(); i++) { - if (screenMappings[i].value == value) { - return i; - } +int findScreenIndexByValue(int value) +{ + for (int i = 0; i < screenMappings.size(); i++) + { + if (screenMappings[i].value == value) + { + return i; } - return -1; // Return -1 if value is not found + } + return -1; // Return -1 if value is not found } diff --git a/src/lib/config.hpp b/src/lib/config.hpp index d4c8078..6888154 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -13,7 +13,7 @@ #include "lib/block_notify.hpp" #include "lib/button_handler.hpp" #include "lib/epd.hpp" -#include "lib/improv.hpp" +// #include "lib/improv.hpp" #include "lib/led_handler.hpp" #include "lib/ota.hpp" #include "lib/nostr_notify.hpp" @@ -57,13 +57,13 @@ String getMyHostname(); std::vector getScreenNameMap(); std::vector getLocalUrl(); -bool improv_connectWifi(std::string ssid, std::string password); -void improvGetAvailableWifiNetworks(); -bool onImprovCommandCallback(improv::ImprovCommand cmd); -void onImprovErrorCallback(improv::Error err); -void improv_set_state(improv::State state); -void improv_send_response(std::vector &response); -void improv_set_error(improv::Error error); +// bool improv_connectWifi(std::string ssid, std::string password); +// void improvGetAvailableWifiNetworks(); +// bool onImprovCommandCallback(improv::ImprovCommand cmd); +// void onImprovErrorCallback(improv::Error err); +// void improv_set_state(improv::State state); +// void improv_send_response(std::vector &response); +// void improv_set_error(improv::Error error); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); String getHwRev(); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 3e39424..bc6a614 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -532,7 +532,7 @@ void renderIcon(const uint dispNum, const String &text, bool partial) } if (text.endsWith("lnbolt")) { - iconIndex = 2; + iconIndex = 3; } displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor()); diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index ea5743b..b0adfd3 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -5,7 +5,7 @@ nostr::Transport *transport; TaskHandle_t nostrTaskHandle = NULL; boolean nostrIsConnected = false; -void setupNostrNotify() +void setupNostrNotify(bool asDatasource, bool zapNotify) { nostr::esp32::ESP32Platform::initNostr(false); time_t now; @@ -23,49 +23,47 @@ void setupNostrNotify() String pubKey = preferences.getString("nostrPubKey"); pools.push_back(pool); - // JsonArray filter = { - // {"kinds", {"1"}}, - // {"since", {String(timestamp60MinutesAgo)}}, - // {"authors", {pubKey}}, - // }; + std::vector>> filters; - // { "kinds", {"9735"}, {"limit",{"1"}, - // {"#p", {"b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422"} }, + if (zapNotify) + { + String subIdZap = pool->subscribeMany( + {relay}, + { + { + {"kinds", {"9735"}}, + {"limit", {"1"}}, + {"since", {String(timestamp60MinutesAgo)}}, + {"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY)}}, + }, + }, + handleNostrZapCallback, + onNostrSubscriptionClosed, + onNostrSubscriptionEose); + Serial.println("[ Nostr ] Subscribing to Zap Notifications"); + } - // Lets subscribe to the relay - String subId = pool->subscribeMany( - {relay}, - {// First filter - { - {"kinds", {"9735"}}, - {"limit", {"1"}}, - {"since", {String(timestamp60MinutesAgo)}}, - {"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY)}}, - }, - // Second filter - { - {"kinds", {"1"}}, - {"since", {String(timestamp60MinutesAgo)}}, - {"authors", {pubKey}}, - }}, - handleNostrEventCallback, - [&](const String &subId, const String &reason) - { - // This is the callback that will be called when the subscription is - // closed - Serial.println("Subscription closed: " + reason); - }, - [&](const String &subId) - { - // This is the callback that will be called when the subscription is - // EOSE - Serial.println("Subscription EOSE: " + subId); - }); + if (asDatasource) + { + String subId = pool->subscribeMany( + {relay}, + {// First filter + { + {"kinds", {"1"}}, + {"since", {String(timestamp60MinutesAgo)}}, + {"authors", {pubKey}}, + }}, + handleNostrEventCallback, + onNostrSubscriptionClosed, + onNostrSubscriptionEose); + + Serial.println("[ Nostr ] Subscribing to Nostr Data Feed"); + } std::vector *relays = pool->getConnectedRelays(); for (nostr::NostrRelay *relay : *relays) { - Serial.println("Registering to connection events of: " + relay->getUrl()); + Serial.println("[ Nostr ] Registering to connection events of: " + relay->getUrl()); relay->getConnection()->addConnectionStatusListener([&](const nostr::ConnectionStatus &status) { String sstatus="UNKNOWN"; @@ -78,12 +76,12 @@ void setupNostrNotify() }else if(status==nostr::ConnectionStatus::ERROR){ sstatus = "ERROR"; } - Serial.println("Connection status changed: " + sstatus); }); + Serial.println("[ Nostr ] Connection status changed: " + sstatus); }); } } catch (const std::exception &e) { - Serial.println("Error: " + String(e.what())); + Serial.println("[ Nostr ] Error: " + String(e.what())); } } @@ -114,6 +112,20 @@ boolean nostrConnected() return nostrIsConnected; } +void onNostrSubscriptionClosed(const String &subId, const String &reason) +{ + // This is the callback that will be called when the subscription is + // closed + Serial.println("[ Nostr ] Subscription closed: " + reason); +} + +void onNostrSubscriptionEose(const String &subId) +{ + // This is the callback that will be called when the subscription is + // EOSE + Serial.println("[ Nostr ] Subscription EOSE: " + subId); +} + void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event) { // Received events callback, we can access the event content with @@ -124,11 +136,6 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even event->toSendableEvent(arr); // Access the second element which is the object JsonObject obj = arr[1].as(); - String json; - - // Serial.println(obj["kind"].as()); - - // Access the "tags" array JsonArray tags = obj["tags"].as(); // Flag to check if the tag was found @@ -155,31 +162,53 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even { medianFee = tag[1].as(); } + } + } + if (tagFound) + { + if (typeValue.equals("priceUsd")) + { + processNewPrice(obj["content"].as()); + } + else if (typeValue.equals("blockHeight")) + { + processNewBlock(obj["content"].as()); + } + + if (medianFee != 0) + { + processNewBlockFee(medianFee); + } + } +} + +void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) { + // Received events callback, we can access the event content with + // event->getContent() Here you should handle the event, for this + // test we will just serialize it and print to console + JsonDocument doc; + JsonArray arr = doc["data"].to(); + event->toSendableEvent(arr); + // Access the second element which is the object + JsonObject obj = arr[1].as(); + JsonArray tags = obj["tags"].as(); + + // Iterate over the tags array + for (JsonArray tag : tags) + { + // Check if the tag is an array with two elements + if (tag.size() == 2) + { + const char *key = tag[0]; + const char *value = tag[1]; if (strcmp(key, "bolt11") == 0) { + Serial.println(F("Got a zap")); + + int64_t satsAmount = getAmountInSatoshis(std::string(value)); - String text = String(getAmountInSatoshis(std::string(value))); - - std::size_t textLength = text.length(); - - // Calculate the position where the digits should start - // Account for the position of the "mdi:pickaxe" and the "GH/S" label - std::size_t startIndex = NUM_SCREENS - textLength; - - std::array textEpdContent = {"ZAP", "mdi-lnbolt", "", "", "", "", ""}; - - // Insert the "mdi:pickaxe" icon just before the digits - if (startIndex > 0 && preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)) - { - textEpdContent[startIndex - 1] = "STS"; - } - - // Place the digits - for (std::size_t i = 0; i < textLength; i++) - { - textEpdContent[startIndex + i] = text.substring(i, i + 1); - } + std::array textEpdContent = parseZapNotify(satsAmount, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); uint64_t timerPeriod = 0; if (isTimerActive()) @@ -201,20 +230,4 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even } } } - if (tagFound) - { - if (typeValue.equals("priceUsd")) - { - processNewPrice(obj["content"].as()); - } - else if (typeValue.equals("blockHeight")) - { - processNewBlock(obj["content"].as()); - } - - if (medianFee != 0) - { - processNewBlockFee(medianFee); - } - } } \ No newline at end of file diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp index 7aa7afc..d64e3e9 100644 --- a/src/lib/nostr_notify.hpp +++ b/src/lib/nostr_notify.hpp @@ -3,6 +3,9 @@ #include "shared.hpp" #include +#include +#include + #include "esp32/ESP32Platform.h" #include "NostrEvent.h" #include "NostrPool.h" @@ -10,10 +13,14 @@ #include "price_notify.hpp" #include "block_notify.hpp" -void setupNostrNotify(); +void setupNostrNotify(bool asDatasource, bool zapNotify); void nostrTask(void *pvParameters); void setupNostrTask(); boolean nostrConnected(); -void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event); \ No newline at end of file +void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event); +void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event); + +void onNostrSubscriptionClosed(const String &subId, const String &reason); +void onNostrSubscriptionEose(const String &subId); \ No newline at end of file From 65496fbb2938429912afe3ea6e0f13496b59f1fd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 31 Aug 2024 17:22:30 +0200 Subject: [PATCH 059/188] WebUI improvements --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 21a7192..4c5d961 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 21a7192e6da4dbd38b49dba7d2840d09cf8a5073 +Subproject commit 4c5d9616212b27e3f05c35370f0befcf2c5a04b2 From 9c67f769d395e21e6091ecc0f5c8639e4f40d26d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 31 Aug 2024 18:06:42 +0200 Subject: [PATCH 060/188] WebUI improvements for small screens --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 4c5d961..645c0f7 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 4c5d9616212b27e3f05c35370f0befcf2c5a04b2 +Subproject commit 645c0f7d494f2c5ef01c4f065aeac923cf9ff4ef From 478c951ffb95f5dc8153836031159d420cebba3d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 31 Aug 2024 23:11:48 +0200 Subject: [PATCH 061/188] Improved multi-currency support, improved WebUI checks --- data | 2 +- lib/btclock/data_handler.cpp | 7 +- lib/btclock/data_handler.hpp | 7 +- src/fonts/antonio-semibold90.h | 237 ++++++++++++++++++++++++++++++++- src/lib/defaults.hpp | 3 + src/lib/epd.cpp | 2 +- src/lib/price_notify.cpp | 2 + src/lib/shared.hpp | 14 -- src/lib/webserver.cpp | 6 +- 9 files changed, 249 insertions(+), 31 deletions(-) diff --git a/data b/data index 645c0f7..e21b989 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 645c0f7d494f2c5ef01c4f065aeac923cf9ff4ef +Subproject commit e21b9895a7c93f62565ffa2cd1d6c085b9b74035 diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 6592807..56cc6e4 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -12,10 +12,10 @@ char getCurrencySymbol(char input) return '['; break; case CURRENCY_GBP: - return '\\'; + return ']'; break; case CURRENCY_JPY: - return ']'; + return '^'; break; case CURRENCY_AUD: case CURRENCY_CAD: @@ -43,9 +43,6 @@ std::string getCurrencyCode(char input) case CURRENCY_AUD: return "AUD"; break; - case CURRENCY_CHF: - return "CHF"; - break; case CURRENCY_CAD: return "CAD"; break; diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index 226758f..6ccb49a 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -7,10 +7,9 @@ const char CURRENCY_USD = '$'; const char CURRENCY_EUR = '['; -const char CURRENCY_GBP = '\\'; -const char CURRENCY_JPY = ']'; -const char CURRENCY_AUD = '^'; -const char CURRENCY_CHF = '_'; +const char CURRENCY_GBP = ']'; +const char CURRENCY_JPY = '^'; +const char CURRENCY_AUD = '_'; const char CURRENCY_CAD = '`'; std::array parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false); diff --git a/src/fonts/antonio-semibold90.h b/src/fonts/antonio-semibold90.h index b55d5df..4ad6ab3 100644 --- a/src/fonts/antonio-semibold90.h +++ b/src/fonts/antonio-semibold90.h @@ -5029,7 +5029,231 @@ const uint8_t Antonio_SemiBold90pt7bBitmaps[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, - 0x00}; + 0x00, + + // Pound sign (start 60325) + 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, + 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, + 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x0F, + 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xE0, 0x00, + 0x0F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xE0, + 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0, + 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, + 0xF0, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, + 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, + 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00, 0x00, + 0x07, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, + 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFC, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, + 0xE0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x07, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, + 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x03, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x7F, 0xFF, 0xF0, 0x00, 0x00, + 0x0F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x01, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x00, + 0x00, 0x07, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, + 0x80, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFE, + 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, + 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, + 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, + + // yen-sign (start 61607) + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, + 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE3, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F, + 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x1F, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xC0, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xE0, 0x00, + 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, + 0xF8, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, + 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0x00, 0x00, + 0x03, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFE, + 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, + 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, + 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xC0, + 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, + 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, + 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xF8, 0x00, + 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x01, 0xFF, + 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, + 0x1F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF8, + 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, + 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, + 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xC0, + 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFE, + 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x07, + 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, + 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xC0, + 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, + 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x0F, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0xC0, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x81, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x07, 0xFF, 0xFF, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFE, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, + 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE0, 0x00, 0x00, + 0x00, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xF3, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFC, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0xDF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, + 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, + 0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, + 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, + 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 + }; const GFXglyph Antonio_SemiBold90pt7bGlyphs[] PROGMEM = { {0, 1, 1, 38, 0, 0}, // 0x20 ' ' @@ -5090,11 +5314,16 @@ const GFXglyph Antonio_SemiBold90pt7bGlyphs[] PROGMEM = { {53398, 108, 151, 116, 4, 106}, // 0x57 'W' {55437, 64, 151, 72, 4, 106}, // 0x58 'X' {56645, 71, 151, 73, 1, 106}, // 0x59 'Y' - {57986, 52, 151, 61, 6, 106}, - {58968, 70, 155, 84, 5, 104}}; // 0x5A 'Z' + {57986, 52, 151, 61, 6, 106}, // 0x5A 'Z' + {58968, 70, 155, 84, 5, 104}, // Euro-sign as [ + {0, 0, 0, 0, 0, 0}, // Skip backslash + {60325, 67, 153, 82, 7, 104 }, // Pound-sign as ] + {61607, 71, 151, 73, 1, 106 }, // Yen-sign as ^ + + }; const GFXfont Antonio_SemiBold90pt7b PROGMEM = { (uint8_t *)Antonio_SemiBold90pt7bBitmaps, - (GFXglyph *)Antonio_SemiBold90pt7bGlyphs, 0x20, 0x5B, 228}; + (GFXglyph *)Antonio_SemiBold90pt7bGlyphs, 0x20, 0x5E, 231}; // Approx. 60745 bytes diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index f9e19b5..2659df0 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -16,6 +16,9 @@ #define DEFAULT_SUFFIX_PRICE false #define DEFAULT_DISABLE_LEDS false #define DEFAULT_OWN_DATA_SOURCE true +#define DEFAULT_STAGING_SOURCE false +#define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD + #define DEFAULT_TIME_OFFSET_SECONDS 3600 diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index bc6a614..cc44c7e 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -252,7 +252,7 @@ void prepareDisplayUpdateTask(void *pvParameters) bool updatePartial = true; - if (strstr(epdContent[epdIndex].c_str(), "/") != NULL) + if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL) { String top = epdContent[epdIndex].substring( 0, epdContent[epdIndex].indexOf("/")); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 66381fb..062062a 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -1,6 +1,8 @@ #include "price_notify.hpp" const char *wsOwnServerPrice = "wss://ws.btclock.dev/ws?assets=bitcoin"; +const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws"; + const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; // const char* coinCapWsCert = R"(-----BEGIN CERTIFICATE----- diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index f3a21cc..4a2ca68 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -48,20 +48,6 @@ const PROGMEM int screens[SCREEN_COUNT] = { const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; -struct SpiRamAllocator : ArduinoJson::Allocator { - void* allocate(size_t size) override { - return heap_caps_malloc(size, MALLOC_CAP_SPIRAM); - } - - void deallocate(void* pointer) override { - heap_caps_free(pointer); - } - - void* reallocate(void* ptr, size_t new_size) override { - return heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM); - } -}; - struct ScreenMapping { int value; const char* name; diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 47565b7..0d516f1 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -455,7 +455,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } - String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; + String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout", "srcV2Currency"}; for (String setting : uintSettings) { @@ -478,7 +478,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource"}; for (String setting : boolSettings) { @@ -597,6 +597,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); + root["stagingSource"] = preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE); + root["srcV2Currency"] = preferences.getChar("srcV2Currency", DEFAULT_V2_SOURCE_CURRENCY); root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); From a31a42511f016fdf8420347e44483f1459606b65 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 2 Sep 2024 22:44:23 +0200 Subject: [PATCH 062/188] Fix disabled screen skipping --- data | 2 +- src/lib/defaults.hpp | 1 + src/lib/led_handler.cpp | 11 +++++++++++ src/lib/screen_handler.cpp | 9 +++++---- src/lib/webserver.cpp | 27 ++++++++++++++++++++++++++- src/lib/webserver.hpp | 4 ++++ 6 files changed, 48 insertions(+), 6 deletions(-) diff --git a/data b/data index e21b989..2c7f7f6 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e21b9895a7c93f62565ffa2cd1d6c085b9b74035 +Subproject commit 2c7f7f667ccb10271db072a4b4e6bf8fd4912f2b diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 2659df0..8e16eb8 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -15,6 +15,7 @@ #define DEFAULT_USE_BLOCK_COUNTDOWN true #define DEFAULT_SUFFIX_PRICE false #define DEFAULT_DISABLE_LEDS false +#define DEFAULT_DISABLE_FL false #define DEFAULT_OWN_DATA_SOURCE true #define DEFAULT_STAGING_SOURCE false #define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index e124ea1..0d780a6 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -13,6 +13,9 @@ bool flInTransition = false; void frontlightFlash(int flDelayTime) { + if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + return; + if (frontlightOn) { frontlightFadeOutAll(flDelayTime, true); @@ -65,6 +68,8 @@ void frontlightFadeInAll(int flDelayTime) void frontlightFadeInAll(int flDelayTime, bool staggered) { + if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + return; if (frontlightIsOn()) return; if (flInTransition) @@ -115,6 +120,8 @@ void frontlightFadeOutAll(int flDelayTime) void frontlightFadeOutAll(int flDelayTime, bool staggered) { + if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + return; if (!frontlightIsOn()) return; if (flInTransition) @@ -179,6 +186,8 @@ bool frontlightIsOn() void frontlightFadeIn(uint num, int flDelayTime) { + if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + return; for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { flArray.setPWM(num, 0, dutyCycle); @@ -188,6 +197,8 @@ void frontlightFadeIn(uint num, int flDelayTime) void frontlightFadeOut(uint num, int flDelayTime) { + if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + return; if (!frontlightIsOn()) return; diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 269d2a7..9335020 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -282,7 +282,7 @@ void nextScreen() { newCurrentScreen = screenMappings.front().value; } - String key = "screen" + String(screenMappings[currentIndex - 1].value) + "Visible"; + String key = "screen" + String(newCurrentScreen) + "Visible"; while (!preferences.getBool(key.c_str(), true)) { currentIndex = findScreenIndexByValue(newCurrentScreen); @@ -294,7 +294,7 @@ void nextScreen() { key = "screen" + String(newCurrentScreen) + "Visible"; } - + setCurrentScreen(newCurrentScreen); } @@ -326,8 +326,9 @@ void previousScreen() { } void showSystemStatusScreen() { - std::array sysStatusEpdContent = {"", "", "", "", - "", "", ""}; + std::array sysStatusEpdContent; + std::fill(sysStatusEpdContent.begin(), sysStatusEpdContent.end(), ""); + String ipAddr = WiFi.localIP().toString(); String subNet = WiFi.subnetMask().toString(); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 0d516f1..352983d 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -37,6 +37,9 @@ void setupWebserver() server.on("/api/show/screen", HTTP_GET, onApiShowScreen); server.on("/api/show/text", HTTP_GET, onApiShowText); + server.on("/api/screen/next", HTTP_GET, onApiScreenNext); + server.on("/api/screen/previous", HTTP_GET, onApiScreenPrevious); + AsyncCallbackJsonWebHandler *settingsPatchHandler = new AsyncCallbackJsonWebHandler("/api/json/settings", onApiSettingsPatch); server.addHandler(settingsPatchHandler); @@ -373,6 +376,27 @@ void onApiShowScreen(AsyncWebServerRequest *request) request->send(200); } +/** + * @Api + * @Path("/api/screen/next") + */ +void onApiScreenNext(AsyncWebServerRequest *request) +{ + nextScreen(); + request->send(200); +} + +/** + * @Api + * @Path("/api/screen/previous") + */ +void onApiScreenPrevious(AsyncWebServerRequest *request) +{ + previousScreen(); + + request->send(200); +} + void onApiShowText(AsyncWebServerRequest *request) { if (request->hasParam("t")) @@ -478,7 +502,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource"}; for (String setting : boolSettings) { @@ -611,6 +635,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; + root["flDisable"] = preferences.getBool("flDisable", DEFAULT_DISABLE_FL); root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON); root["flEffectDelay"] = preferences.getUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 3d16b35..b47d115 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -25,6 +25,10 @@ void onApiStatus(AsyncWebServerRequest *request); void onApiSystemStatus(AsyncWebServerRequest *request); void onApiSetWifiTxPower(AsyncWebServerRequest *request); + +void onApiScreenNext(AsyncWebServerRequest *request); +void onApiScreenPrevious(AsyncWebServerRequest *request); + void onApiShowScreen(AsyncWebServerRequest *request); void onApiShowText(AsyncWebServerRequest *request); void onApiIdentify(AsyncWebServerRequest *request); From 6cf464c3e36d67adb131df3e7add3aa37c93f055 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 2 Sep 2024 23:04:23 +0200 Subject: [PATCH 063/188] Dependency upgrade and cleanup --- platformio.ini | 3 +- src/lib/improv.cpp | 143 --------------------------------------------- src/lib/improv.hpp | 86 --------------------------- 3 files changed, 1 insertion(+), 231 deletions(-) delete mode 100644 src/lib/improv.cpp delete mode 100644 src/lib/improv.hpp diff --git a/platformio.ini b/platformio.ini index 2321a84..14799cf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,8 +33,7 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.1.0 - esphome/Improv@^1.2.3 - mathieucarbou/ESP Async WebServer@2.10.8 + mathieucarbou/ESPAsyncWebServer @ 3.2.0 adafruit/Adafruit BusIO@^1.16.1 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.3 diff --git a/src/lib/improv.cpp b/src/lib/improv.cpp deleted file mode 100644 index 3de3ef5..0000000 --- a/src/lib/improv.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "improv.h" - -namespace improv { - -ImprovCommand parse_improv_data(const std::vector &data, - bool check_checksum) { - return parse_improv_data(data.data(), data.size(), check_checksum); -} - -ImprovCommand parse_improv_data(const uint8_t *data, size_t length, - bool check_checksum) { - ImprovCommand improv_command; - Command command = (Command)data[0]; - uint8_t data_length = data[1]; - - if (data_length != length - 2 - check_checksum) { - improv_command.command = UNKNOWN; - return improv_command; - } - - if (check_checksum) { - uint8_t checksum = data[length - 1]; - - uint32_t calculated_checksum = 0; - for (uint8_t i = 0; i < length - 1; i++) { - calculated_checksum += data[i]; - } - - if ((uint8_t)calculated_checksum != checksum) { - improv_command.command = BAD_CHECKSUM; - return improv_command; - } - } - - if (command == WIFI_SETTINGS) { - uint8_t ssid_length = data[2]; - uint8_t ssid_start = 3; - size_t ssid_end = ssid_start + ssid_length; - - uint8_t pass_length = data[ssid_end]; - size_t pass_start = ssid_end + 1; - size_t pass_end = pass_start + pass_length; - - std::string ssid(data + ssid_start, data + ssid_end); - std::string password(data + pass_start, data + pass_end); - return {.command = command, .ssid = ssid, .password = password}; - } - - improv_command.command = command; - return improv_command; -} - -bool parse_improv_serial_byte(size_t position, uint8_t byte, - const uint8_t *buffer, - std::function &&callback, - std::function &&on_error) { - if (position == 0) return byte == 'I'; - if (position == 1) return byte == 'M'; - if (position == 2) return byte == 'P'; - if (position == 3) return byte == 'R'; - if (position == 4) return byte == 'O'; - if (position == 5) return byte == 'V'; - - if (position == 6) return byte == IMPROV_SERIAL_VERSION; - - if (position <= 8) return true; - - uint8_t type = buffer[7]; - uint8_t data_len = buffer[8]; - - if (position <= 8 + data_len) return true; - - if (position == 8 + data_len + 1) { - uint8_t checksum = 0x00; - for (size_t i = 0; i < position; i++) checksum += buffer[i]; - - if (checksum != byte) { - on_error(ERROR_INVALID_RPC); - return false; - } - - if (type == TYPE_RPC) { - auto command = parse_improv_data(&buffer[9], data_len, false); - return callback(command); - } - } - - return false; -} - -std::vector build_rpc_response(Command command, - const std::vector &datum, - bool add_checksum) { - std::vector out; - uint32_t length = 0; - out.push_back(command); - for (const auto &str : datum) { - uint8_t len = str.length(); - length += len + 1; - out.push_back(len); - out.insert(out.end(), str.begin(), str.end()); - } - out.insert(out.begin() + 1, length); - - if (add_checksum) { - uint32_t calculated_checksum = 0; - - for (uint8_t byte : out) { - calculated_checksum += byte; - } - out.push_back(calculated_checksum); - } - return out; -} - -#ifdef ARDUINO -std::vector build_rpc_response(Command command, - const std::vector &datum, - bool add_checksum) { - std::vector out; - uint32_t length = 0; - out.push_back(command); - for (const auto &str : datum) { - uint8_t len = str.length(); - length += len; - out.push_back(len); - out.insert(out.end(), str.begin(), str.end()); - } - out.insert(out.begin() + 1, length); - - if (add_checksum) { - uint32_t calculated_checksum = 0; - - for (uint8_t byte : out) { - calculated_checksum += byte; - } - out.push_back(calculated_checksum); - } - return out; -} -#endif // ARDUINO - -} // namespace improv \ No newline at end of file diff --git a/src/lib/improv.hpp b/src/lib/improv.hpp deleted file mode 100644 index 1c9453d..0000000 --- a/src/lib/improv.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#ifdef ARDUINO -#include -#endif // ARDUINO - -#include -#include -#include -#include - -namespace improv { - -static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000"; -static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001"; -static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002"; -static const char *const RPC_COMMAND_UUID = - "00467768-6228-2272-4663-277478268003"; -static const char *const RPC_RESULT_UUID = - "00467768-6228-2272-4663-277478268004"; -static const char *const CAPABILITIES_UUID = - "00467768-6228-2272-4663-277478268005"; - -enum Error : uint8_t { - ERROR_NONE = 0x00, - ERROR_INVALID_RPC = 0x01, - ERROR_UNKNOWN_RPC = 0x02, - ERROR_UNABLE_TO_CONNECT = 0x03, - ERROR_NOT_AUTHORIZED = 0x04, - ERROR_UNKNOWN = 0xFF, -}; - -enum State : uint8_t { - STATE_STOPPED = 0x00, - STATE_AWAITING_AUTHORIZATION = 0x01, - STATE_AUTHORIZED = 0x02, - STATE_PROVISIONING = 0x03, - STATE_PROVISIONED = 0x04, -}; - -enum Command : uint8_t { - UNKNOWN = 0x00, - WIFI_SETTINGS = 0x01, - IDENTIFY = 0x02, - GET_CURRENT_STATE = 0x02, - GET_DEVICE_INFO = 0x03, - GET_WIFI_NETWORKS = 0x04, - BAD_CHECKSUM = 0xFF, -}; - -static const uint8_t CAPABILITY_IDENTIFY = 0x01; -static const uint8_t IMPROV_SERIAL_VERSION = 1; - -enum ImprovSerialType : uint8_t { - TYPE_CURRENT_STATE = 0x01, - TYPE_ERROR_STATE = 0x02, - TYPE_RPC = 0x03, - TYPE_RPC_RESPONSE = 0x04 -}; - -struct ImprovCommand { - Command command; - std::string ssid; - std::string password; -}; - -ImprovCommand parse_improv_data(const std::vector &data, - bool check_checksum = true); -ImprovCommand parse_improv_data(const uint8_t *data, size_t length, - bool check_checksum = true); - -bool parse_improv_serial_byte(size_t position, uint8_t byte, - const uint8_t *buffer, - std::function &&callback, - std::function &&on_error); - -std::vector build_rpc_response(Command command, - const std::vector &datum, - bool add_checksum = true); -#ifdef ARDUINO -std::vector build_rpc_response(Command command, - const std::vector &datum, - bool add_checksum = true); -#endif // ARDUINO - -} // namespace improv \ No newline at end of file From 00ac808731d3c8b249cf6f13172f81697e173c72 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 2 Sep 2024 23:28:09 +0200 Subject: [PATCH 064/188] Fix GitHub action --- .github/workflows/tagging.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 109fdaa..d0fe113 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -22,6 +22,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: + include-hidden-files: true retention-days: 1 name: prepared-outputs path: .pio/**/*.bin From a4ff5a2f7506e18567c39a17d6395bdb2c9a6437 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 3 Sep 2024 01:36:44 +0200 Subject: [PATCH 065/188] Add WebUI authentication --- data | 2 +- src/lib/defaults.hpp | 6 +++++- src/lib/webserver.cpp | 48 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/data b/data index 2c7f7f6..34b09a2 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 2c7f7f667ccb10271db072a4b4e6bf8fd4912f2b +Subproject commit 34b09a2d1134d48d7733a3d11a9e6f3f15d080a9 diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 8e16eb8..ac95653 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -54,4 +54,8 @@ #define DEFAULT_BITAXE_HOSTNAME "bitaxe1" #define DEFAULT_ZAP_NOTIFY_ENABLED false -#define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" \ No newline at end of file +#define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" + +#define DEFAULT_HTTP_AUTH_ENABLED false +#define DEFAULT_HTTP_AUTH_USERNAME "btclock" +#define DEFAULT_HTTP_AUTH_PASSWORD "satoshi" \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 352983d..f735877 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -10,14 +10,23 @@ void setupWebserver() { client->send("welcome", NULL, millis(), 1000); }); server.addHandler(&events); + // server.ad. // server.serveStatic("/css", LittleFS, "/css/"); - server.serveStatic("/fonts", LittleFS, "/fonts/"); - server.serveStatic("/build", LittleFS, "/build"); - server.serveStatic("/swagger.json", LittleFS, "/swagger.json"); - server.serveStatic("/api.html", LittleFS, "/api.html"); - server.serveStatic("/fs_hash.txt", LittleFS, "/fs_hash.txt"); + // server.serveStatic("/fonts", LittleFS, "/fonts/"); + // server.serveStatic("/build", LittleFS, "/build"); + // server.serveStatic("/swagger.json", LittleFS, "/swagger.json"); + // server.serveStatic("/api.html", LittleFS, "/api.html"); + // server.serveStatic("/fs_hash.txt", LittleFS, "/fs_hash.txt"); - server.on("/", HTTP_GET, onIndex); + AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); + + if (preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED)) + { + staticHandler.setAuthentication( + preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME), + preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD)); + } + // server.on("/", HTTP_GET, onIndex); server.on("/api/status", HTTP_GET, onApiStatus); server.on("/api/system_status", HTTP_GET, onApiSystemStatus); @@ -437,6 +446,15 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) { + if ( + preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED) && + !request->authenticate( + preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME).c_str(), + preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD).c_str())) + { + return request->requestAuthentication(); + } + JsonObject settings = json.as(); bool settingsChanged = true; @@ -502,7 +520,10 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource"}; + "suffixPrice", "disableLeds", "ownDataSource", + "flAlwaysOn", "flDisable", "flFlashOnUpd", + "mempoolSecure", "useNostr", "bitaxeEnabled", + "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; for (String setting : boolSettings) { @@ -587,6 +608,15 @@ void onApiIdentify(AsyncWebServerRequest *request) */ void onApiSettingsGet(AsyncWebServerRequest *request) { + if ( + preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED) && + !request->authenticate( + preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME).c_str(), + preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD).c_str())) + { + return request->requestAuthentication(); + } + JsonDocument root; root["numScreens"] = NUM_SCREENS; root["fgColor"] = getFgColor(); @@ -633,6 +663,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); + root["httpAuthEnabled"] = preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED); + root["httpAuthUser"] = preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME); + root["httpAuthPass"] = preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD); + #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; root["flDisable"] = preferences.getBool("flDisable", DEFAULT_DISABLE_FL); From c276d32807acfe280f07f7cd7a56fecbed66ecdd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 5 Sep 2024 14:00:15 +0200 Subject: [PATCH 066/188] Implement multi-currency support with MsgPack --- data | 2 +- lib/btclock/data_handler.cpp | 38 ++-- lib/btclock/data_handler.hpp | 10 +- src/lib/config.cpp | 350 +++++++++++++---------------------- src/lib/config.hpp | 10 + src/lib/defaults.hpp | 4 +- src/lib/nostr_notify.cpp | 2 +- src/lib/price_fetch.cpp | 4 +- src/lib/price_notify.cpp | 77 +++----- src/lib/price_notify.hpp | 9 +- src/lib/screen_handler.cpp | 76 ++++++-- src/lib/screen_handler.hpp | 3 + src/lib/shared.hpp | 25 ++- src/lib/v2_notify.cpp | 142 ++++++++++++++ src/lib/v2_notify.hpp | 24 +++ src/lib/webserver.cpp | 288 ++++++---------------------- src/lib/webserver.hpp | 3 +- src/main.cpp | 2 +- 18 files changed, 528 insertions(+), 541 deletions(-) create mode 100644 src/lib/v2_notify.cpp create mode 100644 src/lib/v2_notify.hpp diff --git a/data b/data index 34b09a2..2fffb3e 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 34b09a2d1134d48d7733a3d11a9e6f3f15d080a9 +Subproject commit 2fffb3ef0284b4262ca97a81eb979259186604e5 diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 56cc6e4..0905e8a 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -32,25 +32,41 @@ std::string getCurrencyCode(char input) switch (input) { case CURRENCY_EUR: - return "EUR"; + return CURRENCY_CODE_EUR; break; case CURRENCY_GBP: - return "GBP"; + return CURRENCY_CODE_GBP; break; case CURRENCY_JPY: - return "YEN"; + return CURRENCY_CODE_JPY; break; case CURRENCY_AUD: - return "AUD"; + return CURRENCY_CODE_AUD; break; case CURRENCY_CAD: - return "CAD"; + return CURRENCY_CODE_CAD; break; default: - return "USD"; + return CURRENCY_CODE_USD; } } +char getCurrencyChar(const std::string& input) +{ + if (input == "EUR") + return CURRENCY_EUR; + else if (input == "GBP") + return CURRENCY_GBP; + else if (input == "JPY") + return CURRENCY_JPY; + else if (input == "AUD") + return CURRENCY_AUD; + else if (input == "CAD") + return CURRENCY_CAD; + else + return CURRENCY_USD; // Assuming USD is the default for unknown inputs +} + std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat) { std::array ret; @@ -205,14 +221,8 @@ std::array parseMarketCap(std::uint32_t blockHeight, s std::uint32_t firstIndex = 0; double supply = getSupplyAtBlock(blockHeight); int64_t marketCap = static_cast(supply * double(price)); - if (currencySymbol == '[') - { - ret[0] = "EUR/MCAP"; - } - else - { - ret[0] = "USD/MCAP"; - } + + ret[0] = getCurrencyCode(currencySymbol) + "/MCAP"; if (bigChars) { diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index 6ccb49a..2d563a4 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -12,6 +12,13 @@ const char CURRENCY_JPY = '^'; const char CURRENCY_AUD = '_'; const char CURRENCY_CAD = '`'; +const std::string CURRENCY_CODE_USD = "USD"; +const std::string CURRENCY_CODE_EUR = "EUR"; +const std::string CURRENCY_CODE_GBP = "GBP"; +const std::string CURRENCY_CODE_JPY = "JPY"; +const std::string CURRENCY_CODE_AUD = "AUD"; +const std::string CURRENCY_CODE_CAD = "CAD"; + std::array parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false); std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array parseBlockHeight(std::uint32_t blockHeight); @@ -20,4 +27,5 @@ std::array parseMarketCap(std::uint32_t blockHeight, s std::array parseBlockFees(std::uint16_t blockFees); char getCurrencySymbol(char input); -std::string getCurrencyCode(char input); \ No newline at end of file +std::string getCurrencyCode(char input); +char getCurrencyChar(const std::string& input); \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 54c70e0..7a19abe 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -66,7 +66,6 @@ void setup() setupWebserver(); - // setupWifi(); syncTime(); finishSetup(); @@ -268,24 +267,43 @@ void setupPreferences() setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); - setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE)); + setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); + + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) + setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD)); + else + setCurrentCurrency(CURRENCY_USD); + addScreenMapping(SCREEN_BLOCK_HEIGHT, "Block Height"); - addScreenMapping(SCREEN_MSCW_TIME, "Sats per dollar"); - addScreenMapping(SCREEN_BTC_TICKER, "Ticker"); + addScreenMapping(SCREEN_TIME, "Time"); addScreenMapping(SCREEN_HALVING_COUNTDOWN, "Halving countdown"); - addScreenMapping(SCREEN_MARKET_CAP, "Market Cap"); addScreenMapping(SCREEN_BLOCK_FEE_RATE, "Block Fee Rate"); + addScreenMapping(SCREEN_SATS_PER_CURRENCY, "Sats per dollar"); + addScreenMapping(SCREEN_BTC_TICKER, "Ticker"); + addScreenMapping(SCREEN_MARKET_CAP, "Market Cap"); + + + // addScreenMapping(SCREEN_SATS_PER_CURRENCY_USD, "Sats per USD"); + // addScreenMapping(SCREEN_BTC_TICKER_USD, "Ticker USD"); + // addScreenMapping(SCREEN_MARKET_CAP_USD, "Market Cap USD"); + + // addScreenMapping(SCREEN_SATS_PER_CURRENCY_EUR, "Sats per EUR"); + // addScreenMapping(SCREEN_BTC_TICKER_EUR, "Ticker EUR"); + // addScreenMapping(SCREEN_MARKET_CAP_EUR, "Market Cap EUR"); + // screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height"; // screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate"; - // screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar"; + // screenNameMap[SCREEN_SATS_PER_CURRENCY] = "Sats per dollar"; // screenNameMap[SCREEN_BTC_TICKER] = "Ticker"; // screenNameMap[SCREEN_TIME] = "Time"; // screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown"; // screenNameMap[SCREEN_MARKET_CAP] = "Market Cap"; + //addCurrencyMappings(getActiveCurrencies()); + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate"); @@ -293,16 +311,74 @@ void setupPreferences() } } +// void addCurrencyMappings(const std::vector& currencies) +// { +// for (const auto& currency : currencies) +// { +// int satsPerCurrencyScreen; +// int btcTickerScreen; +// int marketCapScreen; + +// // Determine the corresponding screen IDs based on the currency code +// if (currency == "USD") +// { +// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_USD; +// btcTickerScreen = SCREEN_BTC_TICKER_USD; +// marketCapScreen = SCREEN_MARKET_CAP_USD; +// } +// else if (currency == "EUR") +// { +// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_EUR; +// btcTickerScreen = SCREEN_BTC_TICKER_EUR; +// marketCapScreen = SCREEN_MARKET_CAP_EUR; +// } +// else if (currency == "GBP") +// { +// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_GBP; +// btcTickerScreen = SCREEN_BTC_TICKER_GBP; +// marketCapScreen = SCREEN_MARKET_CAP_GBP; +// } +// else if (currency == "JPY") +// { +// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_JPY; +// btcTickerScreen = SCREEN_BTC_TICKER_JPY; +// marketCapScreen = SCREEN_MARKET_CAP_JPY; +// } +// else if (currency == "AUD") +// { +// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_AUD; +// btcTickerScreen = SCREEN_BTC_TICKER_AUD; +// marketCapScreen = SCREEN_MARKET_CAP_AUD; +// } +// else if (currency == "CAD") +// { +// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_CAD; +// btcTickerScreen = SCREEN_BTC_TICKER_CAD; +// marketCapScreen = SCREEN_MARKET_CAP_CAD; +// } +// else +// { +// continue; // Unknown currency, skip it +// } + +// // Create the string locally to ensure it persists +// std::string satsPerCurrencyString = "Sats per " + currency; +// std::string btcTickerString = "Ticker " + currency; +// std::string marketCapString = "Market Cap " + currency; + +// // Pass the c_str() to the function +// addScreenMapping(satsPerCurrencyScreen, satsPerCurrencyString.c_str()); +// addScreenMapping(btcTickerScreen, btcTickerString.c_str()); +// addScreenMapping(marketCapScreen, marketCapString.c_str()); +// } +// } + void setupWebsocketClients(void *pvParameters) { - setupBlockNotify(); - - if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) - { - setupPriceFetchTask(); - } - else - { + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) { + setupV2Notify(); + } else { + setupBlockNotify(); setupPriceNotify(); } @@ -453,218 +529,6 @@ void setupHardware() #endif } - -#ifdef IMPROV_ENABLED -void improvGetAvailableWifiNetworks() -{ - int networkNum = WiFi.scanNetworks(); - - for (int id = 0; id < networkNum; ++id) - { - std::vector data = improv::build_rpc_response( - improv::GET_WIFI_NETWORKS, - {WiFi.SSID(id), String(WiFi.RSSI(id)), - (WiFi.encryptionType(id) == WIFI_AUTH_OPEN ? "NO" : "YES")}, - false); - improv_send_response(data); - } - // final response - std::vector data = improv::build_rpc_response( - improv::GET_WIFI_NETWORKS, std::vector{}, false); - improv_send_response(data); -} - -bool improv_connectWifi(std::string ssid, std::string password) -{ - uint8_t count = 0; - - WiFi.begin(ssid.c_str(), password.c_str()); - - while (WiFi.status() != WL_CONNECTED) - { - blinkDelay(500, 2); - - if (count > MAX_ATTEMPTS_WIFI_CONNECTION) - { - WiFi.disconnect(); - return false; - } - count++; - } - - return true; -} - -void onImprovErrorCallback(improv::Error err) -{ - blinkDelayColor(100, 1, 255, 0, 0); - // pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - // pixels.setPixelColor(1, pixels.Color(255, 0, 0)); - // pixels.setPixelColor(2, pixels.Color(255, 0, 0)); - // pixels.setPixelColor(3, pixels.Color(255, 0, 0)); - // pixels.show(); - // vTaskDelay(pdMS_TO_TICKS(100)); - - // pixels.clear(); - // pixels.show(); - // vTaskDelay(pdMS_TO_TICKS(100)); -} - -std::vector getLocalUrl() -{ - return {// URL where user can finish onboarding or use device - // Recommended to use website hosted by device - String("http://" + WiFi.localIP().toString()).c_str()}; -} - -bool onImprovCommandCallback(improv::ImprovCommand cmd) -{ - switch (cmd.command) - { - case improv::Command::GET_CURRENT_STATE: - { - if ((WiFi.status() == WL_CONNECTED)) - { - improv_set_state(improv::State::STATE_PROVISIONED); - std::vector data = improv::build_rpc_response( - improv::GET_CURRENT_STATE, getLocalUrl(), false); - improv_send_response(data); - } - else - { - improv_set_state(improv::State::STATE_AUTHORIZED); - } - - break; - } - - case improv::Command::WIFI_SETTINGS: - { - if (cmd.ssid.length() == 0) - { - improv_set_error(improv::Error::ERROR_INVALID_RPC); - break; - } - - improv_set_state(improv::STATE_PROVISIONING); - queueLedEffect(LED_EFFECT_WIFI_CONNECTING); - - if (improv_connectWifi(cmd.ssid, cmd.password)) - { - queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); - - // std::array epdContent = {"S", "U", "C", "C", - // "E", "S", "S"}; setEpdContent(epdContent); - - preferences.putBool("wifiConfigured", true); - - improv_set_state(improv::STATE_PROVISIONED); - std::vector data = improv::build_rpc_response( - improv::WIFI_SETTINGS, getLocalUrl(), false); - improv_send_response(data); - - delay(2500); - ESP.restart(); - setupWebserver(); - } - else - { - queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); - - improv_set_state(improv::STATE_STOPPED); - improv_set_error(improv::Error::ERROR_UNABLE_TO_CONNECT); - } - - break; - } - - case improv::Command::GET_DEVICE_INFO: - { - std::vector infos = {// Firmware name - "BTClock", - // Firmware version - "1.0.0", - // Hardware chip/variant - "ESP32S3", - // Device name - "BTClock"}; - std::vector data = - improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false); - improv_send_response(data); - break; - } - - case improv::Command::GET_WIFI_NETWORKS: - { - improvGetAvailableWifiNetworks(); - // std::array epdContent = {"W", "E", "B", "W", "I", - // "F", "I"}; setEpdContent(epdContent); - break; - } - - default: - { - improv_set_error(improv::ERROR_UNKNOWN_RPC); - return false; - } - } - - return true; -} - -void improv_set_state(improv::State state) -{ - std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; - data.resize(11); - data[6] = improv::IMPROV_SERIAL_VERSION; - data[7] = improv::TYPE_CURRENT_STATE; - data[8] = 1; - data[9] = state; - - uint8_t checksum = 0x00; - for (uint8_t d : data) - checksum += d; - data[10] = checksum; - - Serial.write(data.data(), data.size()); -} - -void improv_send_response(std::vector &response) -{ - std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; - data.resize(9); - data[6] = improv::IMPROV_SERIAL_VERSION; - data[7] = improv::TYPE_RPC_RESPONSE; - data[8] = response.size(); - data.insert(data.end(), response.begin(), response.end()); - - uint8_t checksum = 0x00; - for (uint8_t d : data) - checksum += d; - data.push_back(checksum); - - Serial.write(data.data(), data.size()); -} - -void improv_set_error(improv::Error error) -{ - std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'}; - data.resize(11); - data[6] = improv::IMPROV_SERIAL_VERSION; - data[7] = improv::TYPE_ERROR_STATE; - data[8] = 1; - data[9] = error; - - uint8_t checksum = 0x00; - for (uint8_t d : data) - checksum += d; - data[10] = checksum; - - Serial.write(data.data(), data.size()); -} - -#endif - void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { static bool first_connect = true; @@ -842,3 +706,37 @@ int findScreenIndexByValue(int value) } return -1; // Return -1 if value is not found } + +std::vector getAvailableCurrencies() +{ + return {CURRENCY_CODE_USD, CURRENCY_CODE_EUR, CURRENCY_CODE_GBP, CURRENCY_CODE_JPY, CURRENCY_CODE_AUD, CURRENCY_CODE_CAD}; +} + +std::vector getActiveCurrencies() +{ + std::vector result; + + // Convert Arduino String to std::string + std::string stdString = preferences.getString("actCurrencies", DEFAULT_ACTIVE_CURRENCIES).c_str(); + + // Use a stringstream to split the string + std::stringstream ss(stdString); + std::string item; + + // Split the string by comma and add each part to the vector + while (std::getline(ss, item, ',')) + { + result.push_back(item); + } + return result; +} + +bool isActiveCurrency(std::string ¤cy) +{ + std::vector ac = getActiveCurrencies(); + if (std::find(ac.begin(), ac.end(), currency) != ac.end()) + { + return true; + } + return false; +} \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 6888154..8b7434a 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -19,6 +19,8 @@ #include "lib/nostr_notify.hpp" #include "lib/bitaxe_fetch.hpp" +#include "lib/v2_notify.hpp" + #include "lib/price_notify.hpp" #include "lib/screen_handler.hpp" #include "lib/shared.hpp" @@ -64,6 +66,11 @@ std::vector getLocalUrl(); // void improv_set_state(improv::State state); // void improv_send_response(std::vector &response); // void improv_set_error(improv::Error error); +//void addCurrencyMappings(const std::vector& currencies); +std::vector getActiveCurrencies(); +std::vector getAvailableCurrencies(); + +bool isActiveCurrency(std::string ¤cy); void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); String getHwRev(); @@ -71,4 +78,7 @@ bool isWhiteVersion(); String getFsRev(); void addScreenMapping(int value, const char* name); +// void addScreenMapping(int value, const String& name); +// void addScreenMapping(int value, const std::string& name); + int findScreenIndexByValue(int value); \ No newline at end of file diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index ac95653..39e7406 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -58,4 +58,6 @@ #define DEFAULT_HTTP_AUTH_ENABLED false #define DEFAULT_HTTP_AUTH_USERNAME "btclock" -#define DEFAULT_HTTP_AUTH_PASSWORD "satoshi" \ No newline at end of file +#define DEFAULT_HTTP_AUTH_PASSWORD "satoshi" + +#define DEFAULT_ACTIVE_CURRENCIES "USD,EUR,JPY" \ No newline at end of file diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index b0adfd3..f9ac019 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -168,7 +168,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even { if (typeValue.equals("priceUsd")) { - processNewPrice(obj["content"].as()); + processNewPrice(obj["content"].as(), CURRENCY_USD); } else if (typeValue.equals("blockHeight")) { diff --git a/src/lib/price_fetch.cpp b/src/lib/price_fetch.cpp index d5e6e10..d2ddf67 100644 --- a/src/lib/price_fetch.cpp +++ b/src/lib/price_fetch.cpp @@ -29,9 +29,9 @@ void taskPriceFetch(void *pvParameters) { // usdPrice = doc["bitcoin"]["usd"]; eurPrice = doc["bitcoin"]["eur"].as(); - setPrice(eurPrice); + setPrice(eurPrice, CURRENCY_EUR); if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || - getCurrentScreen() == SCREEN_MSCW_TIME || + getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || getCurrentScreen() == SCREEN_MARKET_CAP)) { WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 062062a..f5ad972 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -5,36 +5,6 @@ const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws"; const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; -// const char* coinCapWsCert = R"(-----BEGIN CERTIFICATE----- -// MIIFMjCCBNmgAwIBAgIQBtgXvFyc28MsvQ1HjCnXJTAKBggqhkjOPQQDAjBKMQsw -// CQYDVQQGEwJVUzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEgMB4GA1UEAxMX -// Q2xvdWRmbGFyZSBJbmMgRUNDIENBLTMwHhcNMjMwNTEwMDAwMDAwWhcNMjQwNTA5 -// MjM1OTU5WjB1MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG -// A1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEe -// MBwGA1UEAxMVc25pLmNsb3VkZmxhcmVzc2wuY29tMFkwEwYHKoZIzj0CAQYIKoZI -// zj0DAQcDQgAEpvFIXzQKHuqTo+IE6c6sB4p0PMXK1KsseEGf2UN/CNRhG5hO7lr8 -// JtXrPZkawWBysZxOsEoetkPrDHMugCLfXKOCA3QwggNwMB8GA1UdIwQYMBaAFKXO -// N+rrsHUOlGeItEX62SQQh5YfMB0GA1UdDgQWBBShsZDJohaR1a5E0Qj7yblZjKDC -// gDA6BgNVHREEMzAxggwqLmNvaW5jYXAuaW+CCmNvaW5jYXAuaW+CFXNuaS5jbG91 -// ZGZsYXJlc3NsLmNvbTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUH -// AwEGCCsGAQUFBwMCMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2lj -// ZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwN6A1oDOGMWh0dHA6Ly9j -// cmw0LmRpZ2ljZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwPgYDVR0g -// BDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2Vy -// dC5jb20vQ1BTMHYGCCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29j -// c3AuZGlnaWNlcnQuY29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdp -// Y2VydC5jb20vQ2xvdWRmbGFyZUluY0VDQ0NBLTMuY3J0MAwGA1UdEwEB/wQCMAAw -// ggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB1AO7N0GTV2xrOxVy3nbTNE6Iyh0Z8 -// vOzew1FIWUZxH7WbAAABiAPnoRAAAAQDAEYwRAIgAP2W09OozuhmKeKKMsaVBcae -// o+nPHF1WUWk0i387YYYCIDIM1Wll7/4O3GNx2/Fx9bC6pi69Uya4pLxsCfW3fZMe -// AHYASLDja9qmRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGIA+eg+QAABAMA -// RzBFAiEAuNpSqrbx47gYBgBMz5M6q0CnV/WMJqWQOxYFKrwfwVACIH3nCs4bKToT -// e+MiBrqSDaekixk4kPFEQESO9qHCkWY5AHcA2ra/az+1tiKfm8K7XGvocJFxbLtR -// hIU0vaQ9MEjX+6sAAAGIA+eg1gAABAMASDBGAiEAolCFl2IfbOHUPAOxoi4BLclS -// v9FVXb7LwIvTuCfyrEQCIQDcvehwhV9XGopKGl17F2LYYKI7hvlO3RmpPZQJt1da -// MDAKBggqhkjOPQQDAgNHADBEAiAXRWZ/JVMsfpSFFTHQHUSqRnQ/7cCOWx+9svIy -// mYnFZQIgHMEG0Cm7O4cn5KUzKOsTwwK+2U15s/jPUQi2n2IDTEM= -// -----END CERTIFICATE-----)"; // WebsocketsClient client; esp_websocket_client_handle_t clientPrice = NULL; @@ -42,6 +12,8 @@ esp_websocket_client_config_t config; uint currentPrice = 50000; unsigned long int lastPriceUpdate; bool priceNotifyInit = false; +std::map currencyMap; +std::map lastUpdateMap; void setupPriceNotify() { @@ -100,48 +72,59 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { if (currentPrice != doc["bitcoin"].as()) { - processNewPrice(doc["bitcoin"].as()); + processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); } } } -void processNewPrice(uint newPrice) +void processNewPrice(uint newPrice, char currency) { uint minSecPriceUpd = preferences.getUInt( "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); uint currentTime = esp_timer_get_time() / 1000000; - if (lastPriceUpdate == 0 || - (currentTime - lastPriceUpdate) > minSecPriceUpd) + if (lastUpdateMap.find(currency) == lastUpdateMap.end()|| + (currentTime - lastUpdateMap[currency]) > minSecPriceUpd) { // const unsigned long oldPrice = currentPrice; - currentPrice = newPrice; - if (lastPriceUpdate == 0 || - (currentTime - lastPriceUpdate) > 120) - { - preferences.putUInt("lastPrice", currentPrice); - } - lastPriceUpdate = currentTime; + currencyMap[currency] = newPrice; + // if (lastUpdateMap[currency] == 0 || + // (currentTime - lastUpdateMap[currency]) > 120) + // { + // preferences.putUInt("lastPrice", currentPrice); + // } + lastUpdateMap[currency] = currentTime; // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || - getCurrentScreen() == SCREEN_MSCW_TIME || + getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || getCurrentScreen() == SCREEN_MARKET_CAP)) { - WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; + WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } //} } } -uint getLastPriceUpdate() +uint getLastPriceUpdate(char currency) { - return lastPriceUpdate; + if (lastUpdateMap.find(currency) == lastUpdateMap.end()) { + return 0; + } + + return lastUpdateMap[currency]; } -uint getPrice() { return currentPrice; } +uint getPrice(char currency) { + if (currencyMap.find(currency) == currencyMap.end()) { + return 0; + } + return currencyMap[currency]; +} -void setPrice(uint newPrice) { currentPrice = newPrice; } +void setPrice(uint newPrice, char currency) { + currencyMap[currency] = newPrice; +} bool isPriceNotifyConnected() { diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 485b096..541f899 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -14,14 +14,15 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); -uint getPrice(); -void setPrice(uint newPrice); +uint getPrice(char currency); +void setPrice(uint newPrice, char currency); -void processNewPrice(uint newPrice); +//void processNewPrice(uint newPrice); +void processNewPrice(uint newPrice, char currency); bool isPriceNotifyConnected(); void stopPriceNotify(); void restartPriceNotify(); bool getPriceNotifyInit(); -uint getLastPriceUpdate(); \ No newline at end of file +uint getLastPriceUpdate(char currency); \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 9335020..95a8682 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -16,6 +16,7 @@ std::string priceString; QueueHandle_t workQueue = NULL; uint currentScreen; +uint currentCurrency = CURRENCY_USD; void workerTask(void *pvParameters) { WorkItem receivedItem; @@ -40,18 +41,19 @@ void workerTask(void *pvParameters) { } break; case TASK_PRICE_UPDATE: { - uint price = getPrice(); - u_char priceSymbol = '$'; - if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) { - priceSymbol = '['; - } + uint currency = getCurrentCurrency(); + uint price = getPrice(currency); + // u_char priceSymbol = '$'; + // if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) { + // priceSymbol = '['; + // } if (getCurrentScreen() == SCREEN_BTC_TICKER) { - taskEpdContent = parsePriceData(price, priceSymbol, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE)); - } else if (getCurrentScreen() == SCREEN_MSCW_TIME) { - taskEpdContent = parseSatsPerCurrency(price, priceSymbol, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); + taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE)); + } else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) { + taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); } else { taskEpdContent = - parseMarketCap(getBlockHeight(), price, priceSymbol, + parseMarketCap(getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } @@ -152,7 +154,7 @@ void setupTasks() { xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY, &workerTaskHandle); - xTaskCreate(taskScreenRotate, "rotateScreen", 2048, NULL, tskIDLE_PRIORITY, + xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle); waitUntilNoneBusy(); @@ -242,7 +244,7 @@ void setCurrentScreen(uint newScreen) { break; } case SCREEN_MARKET_CAP: - case SCREEN_MSCW_TIME: + case SCREEN_SATS_PER_CURRENCY: case SCREEN_BTC_TICKER: { WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); @@ -270,10 +272,36 @@ void setCurrentScreen(uint newScreen) { if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); } +bool isCurrencySpecific(uint screen) { + switch (screen) { + case SCREEN_BTC_TICKER: + case SCREEN_SATS_PER_CURRENCY: + case SCREEN_MARKET_CAP: + return true; + default: + return false; + } +} + void nextScreen() { int currentIndex = findScreenIndexByValue(getCurrentScreen()); std::vector screenMappings = getScreenNameMap(); + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { + std::vector ac = getActiveCurrencies(); + std::string curCode = getCurrencyCode(getCurrentCurrency()); + if (getCurrencyCode(getCurrentCurrency()) != ac.back()) { + auto it = std::find(ac.begin(), ac.end(), curCode); + if (it != ac.end()) { + size_t index = std::distance(ac.begin(), it); + setCurrentCurrency(getCurrencyChar(ac.at(index+1))); + setCurrentScreen(getCurrentScreen()); + return; + } + } + setCurrentCurrency(getCurrencyChar(ac.front())); + } + int newCurrentScreen; if (currentIndex < screenMappings.size() - 1) { @@ -302,6 +330,23 @@ void previousScreen() { int currentIndex = findScreenIndexByValue(getCurrentScreen()); std::vector screenMappings = getScreenNameMap(); + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { + std::vector ac = getActiveCurrencies(); + std::string curCode = getCurrencyCode(getCurrentCurrency()); + if (getCurrencyCode(getCurrentCurrency()) != ac.front()) { + auto it = std::find(ac.begin(), ac.end(), curCode); + if (it != ac.end()) { + size_t index = std::distance(ac.begin(), it); + setCurrentCurrency(getCurrencyChar(ac.at(index-1))); + setCurrentScreen(getCurrentScreen()); + return; + } + } + setCurrentCurrency(getCurrencyChar(ac.back())); + + } + + int newCurrentScreen; if (currentIndex > 0) { @@ -352,4 +397,13 @@ void showSystemStatusScreen() { (int)round(ESP.getHeapSize() / 1024); setCurrentScreen(SCREEN_CUSTOM); setEpdContent(sysStatusEpdContent); +} + +void setCurrentCurrency(char currency) { + currentCurrency = currency; + preferences.putUChar("lastCurrency", currency); +} + +uint getCurrentCurrency() { + return currentCurrency; } \ No newline at end of file diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index a3ecaf8..a5c8f8e 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -60,3 +60,6 @@ void setTimerActive(bool status); void toggleTimerActive(); void setupTasks(); +void setCurrentCurrency(char currency); + +uint getCurrentCurrency(); \ No newline at end of file diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 4a2ca68..5c111df 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -29,12 +29,29 @@ extern std::mutex mcpMutex; #endif const PROGMEM int SCREEN_BLOCK_HEIGHT = 0; -const PROGMEM int SCREEN_MSCW_TIME = 1; -const PROGMEM int SCREEN_BTC_TICKER = 2; + const PROGMEM int SCREEN_TIME = 3; const PROGMEM int SCREEN_HALVING_COUNTDOWN = 4; -const PROGMEM int SCREEN_MARKET_CAP = 5; const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6; + +const PROGMEM int SCREEN_SATS_PER_CURRENCY = 10; + +const PROGMEM int SCREEN_BTC_TICKER = 20; +// const PROGMEM int SCREEN_BTC_TICKER_USD = 20; +// const PROGMEM int SCREEN_BTC_TICKER_EUR = 21; +// const PROGMEM int SCREEN_BTC_TICKER_GBP = 22; +// const PROGMEM int SCREEN_BTC_TICKER_JPY = 23; +// const PROGMEM int SCREEN_BTC_TICKER_AUD = 24; +// const PROGMEM int SCREEN_BTC_TICKER_CAD = 25; + +const PROGMEM int SCREEN_MARKET_CAP = 30; +// const PROGMEM int SCREEN_MARKET_CAP_USD = 30; +// const PROGMEM int SCREEN_MARKET_CAP_EUR = 31; +// const PROGMEM int SCREEN_MARKET_CAP_GBP = 32; +// const PROGMEM int SCREEN_MARKET_CAP_JPY = 33; +// const PROGMEM int SCREEN_MARKET_CAP_AUD = 34; +// const PROGMEM int SCREEN_MARKET_CAP_CAD = 35; + const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; @@ -42,7 +59,7 @@ const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_CUSTOM = 99; const int SCREEN_COUNT = 7; const PROGMEM int screens[SCREEN_COUNT] = { - SCREEN_BLOCK_HEIGHT, SCREEN_MSCW_TIME, SCREEN_BTC_TICKER, + SCREEN_BLOCK_HEIGHT, SCREEN_SATS_PER_CURRENCY, SCREEN_BTC_TICKER, SCREEN_TIME, SCREEN_HALVING_COUNTDOWN, SCREEN_MARKET_CAP, SCREEN_BLOCK_FEE_RATE}; const int usPerSecond = 1000000; diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp new file mode 100644 index 0000000..a2f9744 --- /dev/null +++ b/src/lib/v2_notify.cpp @@ -0,0 +1,142 @@ +#include "v2_notify.hpp" + +WebSocketsClient webSocket; +TaskHandle_t v2NotifyTaskHandle; + +void setupV2Notify() +{ + String hostname = "ws.btclock.dev"; + if ( preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) { + Serial.println(F("Connecting to V2 staging source")); + hostname = "ws-staging.btclock.dev"; + } + + webSocket.beginSSL(hostname, 443, "/api/v2/ws"); + webSocket.onEvent(onWebsocketV2Event); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); + + setupV2NotifyTask(); +} + +void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + Serial.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + Serial.printf("[WSc] Connected to url: %s\n", payload); + + JsonDocument response; + + response["type"] = "subscribe"; + response["eventType"] = "blockfee"; + size_t responseLength = measureMsgPack(response); + uint8_t* buffer = new uint8_t[responseLength]; + serializeMsgPack(response, buffer, responseLength); + webSocket.sendBIN(buffer, responseLength); + delete[] buffer; + + buffer = new uint8_t[responseLength]; + + response["type"] = "subscribe"; + response["eventType"] = "blockheight"; + responseLength = measureMsgPack(response); + buffer = new uint8_t[responseLength]; + serializeMsgPack(response, buffer, responseLength); + webSocket.sendBIN(buffer, responseLength); + + delete[] buffer; + + buffer = new uint8_t[responseLength]; + + response["type"] = "subscribe"; + response["eventType"] = "price"; + + JsonArray currenciesArray = response["currencies"].to(); + + for (const auto &str : getActiveCurrencies()) + { + currenciesArray.add(str); + } + +// response["currencies"] = currenciesArray; + responseLength = measureMsgPack(response); + buffer = new uint8_t[responseLength]; + serializeMsgPack(response, buffer, responseLength); + webSocket.sendBIN(buffer, responseLength); + break; + } + case WStype_TEXT: + Serial.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + { + JsonDocument doc; + DeserializationError error = deserializeMsgPack(doc, payload, length); + + handleV2Message(doc); + break; + } + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_PING: + case WStype_PONG: + case WStype_FRAGMENT_FIN: + break; + } +} + +void handleV2Message(JsonDocument doc) { + if (doc.containsKey("blockheight")) + { + uint newBlockHeight = doc["blockheight"].as(); + + if (newBlockHeight == getBlockHeight()) { + return; + } + + processNewBlock(newBlockHeight); + } + else if (doc.containsKey("blockfee")) + { + uint medianFee = doc["blockfee"].as(); + + processNewBlockFee(medianFee); + } else if (doc.containsKey("price")) + { + + // Iterate through the key-value pairs of the "price" object + for (JsonPair kv : doc["price"].as()) { + const char* currency = kv.key().c_str(); + uint newPrice = kv.value().as(); + + processNewPrice(newPrice, getCurrencyChar(currency)); + + } + } +} + +void taskV2Notify(void *pvParameters) { + for(;;) { + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void setupV2NotifyTask() { + xTaskCreate(taskV2Notify, "v2Notify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &v2NotifyTaskHandle); + +} + +bool isV2NotifyConnected() +{ + return webSocket.isConnected(); +} diff --git a/src/lib/v2_notify.hpp b/src/lib/v2_notify.hpp new file mode 100644 index 0000000..d3e4907 --- /dev/null +++ b/src/lib/v2_notify.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include "block_notify.hpp" +#include + +#include "lib/screen_handler.hpp" + +extern TaskHandle_t v2NotifyTaskHandle; + +void setupV2NotifyTask(); +void taskV2Notify(void *pvParameters); + +void setupV2Notify(); +void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length); +void handleV2Message(JsonDocument doc); + +bool isV2NotifyConnected(); +// void stopV2Notify(); +// void restartV2Notify(); +// bool getPriceNotifyInit(); +// uint getLastPriceUpdate(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index f735877..97441d4 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -41,9 +41,10 @@ void setupWebserver() server.on("/api/action/timer_restart", HTTP_GET, onApiActionTimerRestart); server.on("/api/settings", HTTP_GET, onApiSettingsGet); - server.on("/api/settings", HTTP_POST, onApiSettingsPost); server.on("/api/show/screen", HTTP_GET, onApiShowScreen); + server.on("/api/show/currency", HTTP_GET, onApiShowCurrency); + server.on("/api/show/text", HTTP_GET, onApiShowText); server.on("/api/screen/next", HTTP_GET, onApiScreenNext); @@ -88,6 +89,8 @@ void setupWebserver() } server.on("/api/restart", HTTP_GET, onApiRestart); + server.addRewrite( + new OneParamRewrite("/api/show/currency/{c}", "/api/show/currency?c={c}")); server.addRewrite(new OneParamRewrite("/api/lights/color/{color}", "/api/lights/color?c={color}")); server.addRewrite( @@ -241,10 +244,12 @@ JsonDocument getStatusObject() JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected(); + conStatus["V2"] = isV2NotifyConnected(); + conStatus["nostr"] = nostrConnected(); root["rssi"] = WiFi.RSSI(); - + root["currency"] = getCurrencyCode(getCurrentCurrency()); #ifdef HAS_FRONTLIGHT std::vector statuses = frontlightGetStatus(); uint16_t arr[NUM_SCREENS]; @@ -548,6 +553,23 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } + if (settings.containsKey("actCurrencies")) + { + String actCurrencies; + + for (JsonVariant cur : settings["actCurrencies"].as()) + { + if (!actCurrencies.isEmpty()) + { + actCurrencies += ","; + } + actCurrencies += cur.as(); + } + + preferences.putString("actCurrencies", actCurrencies.c_str()); + Serial.printf("Set actCurrencies: %s\n", actCurrencies); + } + if (settings.containsKey("txPower")) { int txPower = settings["txPower"].as(); @@ -696,6 +718,18 @@ void onApiSettingsGet(AsyncWebServerRequest *request) #endif JsonArray screens = root["screens"].to(); + JsonArray actCurrencies = root["actCurrencies"].to(); + for (const auto &str : getActiveCurrencies()) + { + actCurrencies.add(str); + } + + JsonArray availableCurrencies = root["availableCurrencies"].to(); + for (const auto &str : getAvailableCurrencies()) + { + availableCurrencies.add(str); + } + std::vector screenNameMap = getScreenNameMap(); for (int i = 0; i < screenNameMap.size(); i++) @@ -703,7 +737,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) JsonObject o = screens.add(); String key = "screen" + String(screenNameMap.at(i).value) + "Visible"; o["id"] = screenNameMap.at(i).value; - o["name"] = screenNameMap.at(i).name; + o["name"] = String(screenNameMap.at(i).name); o["enabled"] = preferences.getBool(key.c_str(), true); } @@ -740,230 +774,6 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) return settingsChanged; } -void onApiSettingsPost(AsyncWebServerRequest *request) -{ - // bool settingsChanged = false; - - // settingsChanged = processEpdColorSettings(request); - - // int headers = request->headers(); - // int i; - // for (i = 0; i < headers; i++) - // { - // AsyncWebHeader *h = request->getHeader(i); - // Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); - // } - - // int params = request->params(); - // for (int i = 0; i < params; i++) - // { - // const AsyncWebParameter *p = request->getParam(i); - // if (p->isFile()) - // { // p->isPost() is also true - // Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), - // p->value().c_str(), p->size()); - // } - // else if (p->isPost()) - // { - // Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - // } - // else - // { - // Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); - // } - // } - - // if (request->hasParam("fetchEurPrice", true)) - // { - // const AsyncWebParameter *fetchEurPrice = request->getParam("fetchEurPrice", true); - - // preferences.putBool("fetchEurPrice", fetchEurPrice->value().toInt()); - // settingsChanged = true; - // } - // else - // { - // preferences.putBool("fetchEurPrice", 0); - // settingsChanged = true; - // } - - // if (request->hasParam("ledTestOnPower", true)) - // { - // const AsyncWebParameter *ledTestOnPower = - // request->getParam("ledTestOnPower", true); - - // preferences.putBool("ledTestOnPower", ledTestOnPower->value().toInt()); - // settingsChanged = true; - // } - // else - // { - // preferences.putBool("ledTestOnPower", 0); - // settingsChanged = true; - // } - - // if (request->hasParam("ledFlashOnUpd", true)) - // { - // const AsyncWebParameter *ledFlashOnUpdate = - // request->getParam("ledFlashOnUpd", true); - - // preferences.putBool("ledFlashOnUpd", ledFlashOnUpdate->value().toInt()); - // settingsChanged = true; - // } - // else - // { - // preferences.putBool("ledFlashOnUpd", 0); - // settingsChanged = true; - // } - - // if (request->hasParam("mdnsEnabled", true)) - // { - // const AsyncWebParameter *mdnsEnabled = request->getParam("mdnsEnabled", true); - - // preferences.putBool("mdnsEnabled", mdnsEnabled->value().toInt()); - // settingsChanged = true; - // } - // else - // { - // preferences.putBool("mdnsEnabled", 0); - // settingsChanged = true; - // } - - // if (request->hasParam("otaEnabled", true)) - // { - // const AsyncWebParameter *otaEnabled = request->getParam("otaEnabled", true); - - // preferences.putBool("otaEnabled", otaEnabled->value().toInt()); - // settingsChanged = true; - // } - // else - // { - // preferences.putBool("otaEnabled", 0); - // settingsChanged = true; - // } - - // if (request->hasParam("stealFocus", false)) - // { - // const AsyncWebParameter *stealFocusOnBlock = - // request->getParam("stealFocus", false); - - // preferences.putBool("stealFocus", stealFocusOnBlock->value().toInt()); - // settingsChanged = true; - // } - // else - // { - // preferences.putBool("stealFocus", 0); - // settingsChanged = true; - // } - - // if (request->hasParam("mcapBigChar", true)) - // { - // const AsyncWebParameter *mcapBigChar = request->getParam("mcapBigChar", true); - - // preferences.putBool("mcapBigChar", mcapBigChar->value().toInt()); - // settingsChanged = true; - // } - // else - // { - // preferences.putBool("mcapBigChar", 0); - // settingsChanged = true; - // } - - // if (request->hasParam("mempoolInstance", true)) - // { - // const AsyncWebParameter *mempoolInstance = - // request->getParam("mempoolInstance", true); - - // preferences.putString("mempoolInstance", mempoolInstance->value().c_str()); - // settingsChanged = true; - // } - - // if (request->hasParam("hostnamePrefix", true)) - // { - // const AsyncWebParameter *hostnamePrefix = - // request->getParam("hostnamePrefix", true); - - // preferences.putString("hostnamePrefix", hostnamePrefix->value().c_str()); - // settingsChanged = true; - // } - - // if (request->hasParam("ledBrightness", true)) - // { - // const AsyncWebParameter *ledBrightness = request->getParam("ledBrightness", true); - - // preferences.putUInt("ledBrightness", ledBrightness->value().toInt()); - // settingsChanged = true; - // } - - // if (request->hasParam("fullRefreshMin", true)) - // { - // const AsyncWebParameter *fullRefreshMin = - // request->getParam("fullRefreshMin", true); - - // preferences.putUInt("fullRefreshMin", fullRefreshMin->value().toInt()); - // settingsChanged = true; - // } - - // if (request->hasParam("wpTimeout", true)) - // { - // const AsyncWebParameter *wpTimeout = request->getParam("wpTimeout", true); - - // preferences.putUInt("wpTimeout", wpTimeout->value().toInt()); - // settingsChanged = true; - // } - - // std::vector screenNameMap = getScreenNameMap(); - - // if (request->hasParam("screens")) - // { - // const AsyncWebParameter *screenParam = request->getParam("screens", true); - - // Serial.printf(screenParam->value().c_str()); - // } - - // for (int i = 0; i < screenNameMap.size(); i++) - // { - // String key = "screen[" + String(i) + "]"; - // String prefKey = "screen" + String(i) + "Visible"; - // bool visible = false; - // if (request->hasParam(key, true)) - // { - // const AsyncWebParameter *screenParam = request->getParam(key, true); - // visible = screenParam->value().toInt(); - // } - - // preferences.putBool(prefKey.c_str(), visible); - // } - - // if (request->hasParam("tzOffset", true)) - // { - // const AsyncWebParameter *p = request->getParam("tzOffset", true); - // int tzOffsetSeconds = p->value().toInt() * 60; - // preferences.putInt("gmtOffset", tzOffsetSeconds); - // settingsChanged = true; - // } - - // if (request->hasParam("minSecPriceUpd", true)) - // { - // const AsyncWebParameter *p = request->getParam("minSecPriceUpd", true); - // int minSecPriceUpd = p->value().toInt(); - // preferences.putUInt("minSecPriceUpd", minSecPriceUpd); - // settingsChanged = true; - // } - - // if (request->hasParam("timePerScreen", true)) - // { - // const AsyncWebParameter *p = request->getParam("timePerScreen", true); - // uint timerSeconds = p->value().toInt() * 60; - // preferences.putUInt("timerSeconds", timerSeconds); - // settingsChanged = true; - // } - - // request->send(200); - // if (settingsChanged) - // { - // queueLedEffect(LED_FLASH_SUCCESS); - // } -} - void onApiSystemStatus(AsyncWebServerRequest *request) { AsyncResponseStream *response = @@ -1209,6 +1019,30 @@ void eventSourceTask(void *pvParameters) } } +void onApiShowCurrency(AsyncWebServerRequest *request) +{ + if (request->hasParam("c")) + { + const AsyncWebParameter *p = request->getParam("c"); + std::string currency = p->value().c_str(); + + if (!isActiveCurrency(currency)) + { + request->send(404); + return; + } + + char curChar = getCurrencyChar(currency); + + setCurrentCurrency(curChar); + setCurrentScreen(getCurrentScreen()); + + request->send(200); + return; + } + request->send(404); +} + #ifdef HAS_FRONTLIGHT void onApiFrontlightOn(AsyncWebServerRequest *request) { diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index b47d115..3f71ac9 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -30,6 +30,8 @@ void onApiScreenNext(AsyncWebServerRequest *request); void onApiScreenPrevious(AsyncWebServerRequest *request); void onApiShowScreen(AsyncWebServerRequest *request); +void onApiShowCurrency(AsyncWebServerRequest *request); + void onApiShowText(AsyncWebServerRequest *request); void onApiIdentify(AsyncWebServerRequest *request); @@ -38,7 +40,6 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json); void onApiActionPause(AsyncWebServerRequest *request); void onApiActionTimerRestart(AsyncWebServerRequest *request); void onApiSettingsGet(AsyncWebServerRequest *request); -void onApiSettingsPost(AsyncWebServerRequest *request); void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json); void onApiFullRefresh(AsyncWebServerRequest *request); diff --git a/src/main.cpp b/src/main.cpp index ef8594e..0839e9d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -134,7 +134,7 @@ extern "C" void app_main() } // if more than 5 price updates are missed, there is probably something wrong, reconnect - if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) + if ((getLastPriceUpdate(CURRENCY_USD) - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) { Serial.println(F("Detected 5 missed price updates... restarting price handler.")); From 849e5ce4392de328a126eafe1e383bd6284f0804 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 9 Sep 2024 15:13:41 +0200 Subject: [PATCH 067/188] Finish V2 MsgPack WebSocket API implementation --- data | 2 +- dependencies.lock | 2 +- src/lib/block_notify.cpp | 2 +- src/lib/screen_handler.cpp | 9 +++++---- src/lib/v2_notify.cpp | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/data b/data index 2fffb3e..1fa62ca 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 2fffb3ef0284b4262ca97a81eb979259186604e5 +Subproject commit 1fa62ca88dc9cf85109712082e2b0d3916d03323 diff --git a/dependencies.lock b/dependencies.lock index 6400884..f47f33d 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: 615d994fdba8799111cf7825a85adb23ca6a2ae02b2335b53fde1188ff862f23 +manifest_hash: 3117ab97df715ceaa7b9cacfc3cc7071408d26f122740eea130b5286dc5cd988 target: esp32s3 version: 1.0.0 diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index dad6ad1..19f1893 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -2,7 +2,7 @@ char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; -uint currentBlockHeight = 840000; +uint currentBlockHeight = 860000; uint blockMedianFee = 1; bool blockNotifyInit = false; unsigned long int lastBlockUpdate; diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 95a8682..13e93b5 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -8,14 +8,13 @@ TaskHandle_t workerTaskHandle; esp_timer_handle_t screenRotateTimer; esp_timer_handle_t minuteTimer; -std::array taskEpdContent = {"", "", "", "", - "", "", ""}; +std::array taskEpdContent = {}; std::string priceString; #define WORK_QUEUE_SIZE 10 QueueHandle_t workQueue = NULL; -uint currentScreen; +uint currentScreen = SCREEN_BLOCK_HEIGHT; uint currentCurrency = CURRENCY_USD; void workerTask(void *pvParameters) { @@ -158,7 +157,9 @@ void setupTasks() { &taskScreenRotateTaskHandle); waitUntilNoneBusy(); - setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); + + if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) + setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } void setupTimeUpdateTimer(void *pvParameters) { diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index a2f9744..e23c0af 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -15,7 +15,7 @@ void setupV2Notify() webSocket.onEvent(onWebsocketV2Event); webSocket.setReconnectInterval(5000); webSocket.enableHeartbeat(15000, 3000, 2); - + setupV2NotifyTask(); } From f42cd250fee73c26d5ce52ad9dc1a9c24ebd724d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 9 Sep 2024 16:52:23 +0200 Subject: [PATCH 068/188] Add some tests for multi-currency --- test/test_datahandler/test_main.cpp | 137 +++++++++++++++++----------- 1 file changed, 86 insertions(+), 51 deletions(-) diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index 2c9ffb2..14f3a93 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -1,102 +1,135 @@ #include #include -void setUp(void) { +void setUp(void) +{ // set stuff up here } -void tearDown(void) { +void tearDown(void) +{ // clean stuff up here } -void test_CorrectSatsPerDollarConversion(void) { - std::array output = parseSatsPerCurrency(37253, '$', false); +void test_CorrectSatsPerDollarConversion(void) +{ + std::array output = parseSatsPerCurrency(37253, CURRENCY_USD, false); TEST_ASSERT_EQUAL_STRING("MSCW/TIME", output[0].c_str()); - TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-4].c_str()); - TEST_ASSERT_EQUAL_STRING("6", output[NUM_SCREENS-3].c_str()); - TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS-2].c_str()); - TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS-1].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("6", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS - 1].c_str()); } +void test_CorrectSatsPerPoundConversion(void) +{ + std::array output = parseSatsPerCurrency(37253, CURRENCY_GBP, false); + TEST_ASSERT_EQUAL_STRING("SATS/GBP", output[0].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("6", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS - 1].c_str()); +} -void test_SixCharacterBlockHeight(void) { +void test_SixCharacterBlockHeight(void) +{ std::array output = parseBlockHeight(999999); TEST_ASSERT_EQUAL_STRING("BLOCK/HEIGHT", output[0].c_str()); TEST_ASSERT_EQUAL_STRING("9", output[1].c_str()); } -void test_SevenCharacterBlockHeight(void) { +void test_SevenCharacterBlockHeight(void) +{ std::array output = parseBlockHeight(1000000); TEST_ASSERT_EQUAL_STRING("1", output[0].c_str()); TEST_ASSERT_EQUAL_STRING("0", output[1].c_str()); } -void test_FeeRateDisplay(void) { +void test_FeeRateDisplay(void) +{ uint testValue = 21; std::array output = parseBlockFees(static_cast(testValue)); TEST_ASSERT_EQUAL_STRING("FEE/RATE", output[0].c_str()); - TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-3].c_str()); - TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-2].c_str()); - TEST_ASSERT_EQUAL_STRING("sat/vB", output[NUM_SCREENS-1].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("sat/vB", output[NUM_SCREENS - 1].c_str()); } - -void test_PriceOf100kusd(void) { +void test_PriceOf100kusd(void) +{ std::array output = parsePriceData(100000, '$'); TEST_ASSERT_EQUAL_STRING("$", output[0].c_str()); TEST_ASSERT_EQUAL_STRING("1", output[1].c_str()); } -void test_PriceOf1MillionUsd(void) { +void test_PriceOf1MillionUsd(void) +{ std::array output = parsePriceData(1000000, '$'); TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); - - TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str()); - TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-2].c_str()); - TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS-1].c_str()); + + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS - 1].c_str()); } -void test_McapLowerUsd(void) { +void test_McapLowerUsd(void) +{ std::array output = parseMarketCap(810000, 26000, '$', true); TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str()); -// TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str()); - TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-5].c_str()); - TEST_ASSERT_EQUAL_STRING("5", output[NUM_SCREENS-4].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); - TEST_ASSERT_EQUAL_STRING("7", output[NUM_SCREENS-2].c_str()); - TEST_ASSERT_EQUAL_STRING("B", output[NUM_SCREENS-1].c_str()); + // TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str()); + TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING("5", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("7", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("B", output[NUM_SCREENS - 1].c_str()); } -void test_Mcap1TrillionUsd(void) { +void test_Mcap1TrillionUsd(void) +{ std::array output = parseMarketCap(831000, 52000, '$', true); TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str()); - TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str()); - TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str()); - TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); - TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-2].c_str()); - TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS-1].c_str()); + TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 6].c_str()); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str()); } -void test_Mcap1TrillionEur(void) { - std::array output = parseMarketCap(831000, 52000, '[', true); +void test_Mcap1TrillionEur(void) +{ + std::array output = parseMarketCap(831000, 52000, CURRENCY_EUR, true); TEST_ASSERT_EQUAL_STRING("EUR/MCAP", output[0].c_str()); - TEST_ASSERT_EQUAL_STRING("[", output[NUM_SCREENS-6].c_str()); - TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str()); - TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str()); - TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-2].c_str()); - TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS-1].c_str()); + TEST_ASSERT_TRUE(CURRENCY_EUR == output[NUM_SCREENS - 6].c_str()[0]); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str()); +} + +void test_Mcap1TrillionJpy(void) +{ + std::array output = parseMarketCap(831000, 52000, CURRENCY_JPY, true); + TEST_ASSERT_EQUAL_STRING("JPY/MCAP", output[0].c_str()); + TEST_ASSERT_TRUE(CURRENCY_JPY == output[NUM_SCREENS - 6].c_str()[0]); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str()); } // not needed when using generate_test_runner.rb -int runUnityTests(void) { +int runUnityTests(void) +{ UNITY_BEGIN(); RUN_TEST(test_CorrectSatsPerDollarConversion); + RUN_TEST(test_CorrectSatsPerPoundConversion); RUN_TEST(test_SixCharacterBlockHeight); RUN_TEST(test_SevenCharacterBlockHeight); RUN_TEST(test_FeeRateDisplay); @@ -104,15 +137,17 @@ int runUnityTests(void) { RUN_TEST(test_McapLowerUsd); RUN_TEST(test_Mcap1TrillionUsd); RUN_TEST(test_Mcap1TrillionEur); - //RUN_TEST(test_Mcap1MillionEur); + RUN_TEST(test_Mcap1TrillionJpy); return UNITY_END(); } -int main(void) { - return runUnityTests(); +int main(void) +{ + return runUnityTests(); } -extern "C" void app_main() { - runUnityTests(); +extern "C" void app_main() +{ + runUnityTests(); } From 7a1ce54248793fabde03a18d4259cfebd1206198 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 10 Sep 2024 10:21:02 +0200 Subject: [PATCH 069/188] Fix WebUI credentials not being saved --- src/lib/webserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 97441d4..bad307f 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -490,7 +490,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey", "httpAuthUser", "httpAuthPass"}; for (String setting : strSettings) { From 5425ea7fbfafa66a3c83e047f30b250e7abbc089 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 10 Sep 2024 11:12:14 +0200 Subject: [PATCH 070/188] Improve setup-WiFi password generation --- src/lib/config.cpp | 34 ++++++++++++++++++++++++---------- src/lib/config.hpp | 5 +++-- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 7a19abe..4acc5d3 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -62,7 +62,7 @@ void setup() } } - tryImprovSetup(); + setupWifi(); setupWebserver(); @@ -105,7 +105,7 @@ void setup() forceFullRefresh(); } -void tryImprovSetup() +void setupWifi() { WiFi.onEvent(WiFiEvent); WiFi.setAutoConnect(true); @@ -140,10 +140,10 @@ void tryImprovSetup() String softAP_SSID = String("BTClock" + String(mac[5], 16) + String(mac[1], 16)); WiFi.setHostname(softAP_SSID.c_str()); - String softAP_password = + String softAP_password = replaceAmbiguousChars( base64::encode(String(mac[2], 16) + String(mac[4], 16) + String(mac[5], 16) + String(mac[1], 16)) - .substring(2, 10); + .substring(2, 10)); wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT)); wm.setWiFiAutoReconnect(false); @@ -274,9 +274,8 @@ void setupPreferences() else setCurrentCurrency(CURRENCY_USD); - addScreenMapping(SCREEN_BLOCK_HEIGHT, "Block Height"); - + addScreenMapping(SCREEN_TIME, "Time"); addScreenMapping(SCREEN_HALVING_COUNTDOWN, "Halving countdown"); addScreenMapping(SCREEN_BLOCK_FEE_RATE, "Block Fee Rate"); @@ -285,7 +284,6 @@ void setupPreferences() addScreenMapping(SCREEN_BTC_TICKER, "Ticker"); addScreenMapping(SCREEN_MARKET_CAP, "Market Cap"); - // addScreenMapping(SCREEN_SATS_PER_CURRENCY_USD, "Sats per USD"); // addScreenMapping(SCREEN_BTC_TICKER_USD, "Ticker USD"); // addScreenMapping(SCREEN_MARKET_CAP_USD, "Market Cap USD"); @@ -302,7 +300,7 @@ void setupPreferences() // screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown"; // screenNameMap[SCREEN_MARKET_CAP] = "Market Cap"; - //addCurrencyMappings(getActiveCurrencies()); + // addCurrencyMappings(getActiveCurrencies()); if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { @@ -311,6 +309,19 @@ void setupPreferences() } } +String replaceAmbiguousChars(String input) +{ + const char *ambiguous = "1IlO0"; + const char *replacements = "LKQM8"; + + for (int i = 0; i < strlen(ambiguous); i++) + { + input.replace(ambiguous[i], replacements[i]); + } + + return input; +} + // void addCurrencyMappings(const std::vector& currencies) // { // for (const auto& currency : currencies) @@ -375,9 +386,12 @@ void setupPreferences() void setupWebsocketClients(void *pvParameters) { - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) { + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) + { setupV2Notify(); - } else { + } + else + { setupBlockNotify(); setupPriceNotify(); } diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 8b7434a..e615431 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -44,7 +44,7 @@ uint getLastTimeSync(); void setupPreferences(); void setupWebsocketClients(void *pvParameters); void setupHardware(); -void tryImprovSetup(); +void setupWifi(); void setupTimers(); void finishSetup(); void setupMcp(); @@ -81,4 +81,5 @@ void addScreenMapping(int value, const char* name); // void addScreenMapping(int value, const String& name); // void addScreenMapping(int value, const std::string& name); -int findScreenIndexByValue(int value); \ No newline at end of file +int findScreenIndexByValue(int value); +String replaceAmbiguousChars(String input); \ No newline at end of file From 1f2110fc5a4e458acf5935c72300c262e8718a8f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 11 Sep 2024 03:23:41 +0200 Subject: [PATCH 071/188] Make zap notify more lightning like, verify SSL certificates, remove price fetch code --- README.md | 7 +- data | 2 +- sdkconfig.defaults | 8 +- src/lib/block_notify.cpp | 95 ++++++++++----------- src/lib/config.cpp | 12 +++ src/lib/config.hpp | 3 +- src/lib/led_handler.cpp | 36 +++++++- src/lib/led_handler.hpp | 1 + src/lib/ota.cpp | 90 ++++++++++---------- src/lib/ota.hpp | 2 +- src/lib/price_fetch.cpp | 57 ------------- src/lib/price_fetch.hpp | 10 --- src/lib/price_notify.cpp | 89 ++++++++++++++++++-- src/lib/price_notify.hpp | 2 + src/lib/screen_handler.cpp | 3 - src/lib/screen_handler.hpp | 1 - src/lib/shared.cpp | 76 +++++++++++++++-- src/lib/shared.hpp | 12 ++- src/lib/v2_notify.cpp | 6 +- src/lib/webserver.cpp | 165 ++++++++++++++++++++++++++++++++++++- src/lib/webserver.hpp | 7 ++ 21 files changed, 494 insertions(+), 190 deletions(-) delete mode 100644 src/lib/price_fetch.cpp delete mode 100644 src/lib/price_fetch.hpp diff --git a/README.md b/README.md index 9e22ee3..5dfc0ad 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,16 @@ Biggest differences with v2 are: - Added market capitalization screen - LED flash on new block (and focus to block height screen on new block) +New features: +- BitAxe integration +- Zap notifier +- + "Steal focus on new block" means that when a new block is mined, the display will switch to the block height screen if it's not on it already. Most [information](https://github.com/btclock/btclock_v2/wiki) about BTClock v2 is still valid for this version. -**NOTE**: The software assumes that the hardware is run in a controlled private network. The Web UI and the OTA update mechanism are not password protected and accessible to anyone in the network. Also, since the device only fetches numbers through WebSockets it will skip server certificate verification to save resources. +**NOTE**: The software assumes that the hardware is run in a controlled private network. ~~The Web UI and the OTA update mechanism are not password protected and accessible to anyone in the network. Also, since the device only fetches numbers through WebSockets it will skip server certificate verification to save resources.~~ Since 3.2.0 the WebUI is password protectable and all certificates are verified. OTA update mechanism is not password-protected. ## Building diff --git a/data b/data index 1fa62ca..1c2d8dc 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 1fa62ca88dc9cf85109712082e2b0d3916d03323 +Subproject commit 1c2d8dcdd0efd846f39b0f24977c982a96f16392 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 09888de..ffb944e 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -7,8 +7,8 @@ CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y #CONFIG_FREERTOS_USE_TRACE_FACILITY=y #CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y #CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=n -CONFIG_ESP_TLS_INSECURE=y -CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y +#CONFIG_ESP_TLS_INSECURE=y +#CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y CONFIG_HEAP_CORRUPTION_DETECTION=CONFIG_HEAP_POISONING_LIGHT CONFIG_HEAP_POISONING_LIGHT=y @@ -50,4 +50,6 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_SPIRAM_CACHE_WORKAROUND=y CONFIG_COMPILER_OPTIMIZATION_PERF=y -CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y \ No newline at end of file +CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y +CONFIG_MBEDTLS_SSL_SERVER_VERIFY=n +CONFIG_MBEDTLS_SSL_VERIFY_CLIENT_CERTIFICATE=n \ No newline at end of file diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 19f1893..bf5d337 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -7,49 +7,42 @@ uint blockMedianFee = 1; bool blockNotifyInit = false; unsigned long int lastBlockUpdate; -// const char *mempoolWsCert = R"(-----BEGIN CERTIFICATE----- -// MIIHfTCCBmWgAwIBAgIRANFX3mhqRYDt1NFuENoSyaAwDQYJKoZIhvcNAQELBQAw -// gZUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -// BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE9MDsGA1UE -// AxM0U2VjdGlnbyBSU0EgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNl -// cnZlciBDQTAeFw0yMzA3MjQwMDAwMDBaFw0yNDA4MjIyMzU5NTlaMFcxCzAJBgNV -// BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEgMB4GA1UEChMXTUVNUE9PTCBTUEFDRSBD -// Ty4sIExURC4xFjAUBgNVBAMTDW1lbXBvb2wuc3BhY2UwggEiMA0GCSqGSIb3DQEB -// AQUAA4IBDwAwggEKAoIBAQCqmiPRWgo58d25R0biQjAksXMq5ciH7z7ZQo2w2AbB -// rHxpnlIry74b9S4wRY5UJeYmd6ZwA76NdSioDvxTJc29bLplY+Ftmfc4ET0zYb2k -// Fi86z7GOWb6Ezor/qez9uMM9cxd021Bvcs0/2OrL6Sgp66u9keDZv9NyvFPpXfuR -// tdV2r4HF57VJqZn105PN4k80kNWgDbae8aw+BuUNvQYKEe71yfB7Bh6zSh9pCSfM -// I6pIJdQzoada2uY1dQMoJeIq8qKNKqAPKGsH5McemUT5ZIKU/tjk3nfX0pz/sQa4 -// CN7tLH6UeUlctei92GFd6Xtn7RbKLhDUbc4Sq02Cc9iXAgMBAAGjggQDMIID/zAf -// BgNVHSMEGDAWgBQX2dYlJ2f5McJJQ9kwNkSMbKlP6zAdBgNVHQ4EFgQUXkxoddJ6 -// rKobsbmDdtuCK1ywXuIwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYD -// VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEoGA1UdIARDMEEwNQYMKwYBBAGy -// MQECAQMEMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgG -// BmeBDAECAjBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLnNlY3RpZ28uY29t -// L1NlY3RpZ29SU0FPcmdhbml6YXRpb25WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0Eu -// Y3JsMIGKBggrBgEFBQcBAQR+MHwwVQYIKwYBBQUHMAKGSWh0dHA6Ly9jcnQuc2Vj -// dGlnby5jb20vU2VjdGlnb1JTQU9yZ2FuaXphdGlvblZhbGlkYXRpb25TZWN1cmVT -// ZXJ2ZXJDQS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29t -// MIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdwB2/4g/Crb7lVHCYcz1h7o0tKTN -// uyncaEIKn+ZnTFo6dAAAAYmc9m/gAAAEAwBIMEYCIQD8XOozx411S/bnZambGjTB -// yTcr2fCmggUfQLSmqksD5gIhAIjiEMg0o1VSuQW31gWzfzL6idCkIZeSKN104cdp -// xa4SAHcA2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGJnPZwPwAA -// BAMASDBGAiEA2sPTZTzvxewzQ8vk36+BWAKuJS7AvJ5W3clvfwCa8OUCIQC74ekT -// Ged2fqQE4sVy74aS6HRA2ihC9VLtNrASJx1YjQB2AO7N0GTV2xrOxVy3nbTNE6Iy -// h0Z8vOzew1FIWUZxH7WbAAABiZz2cA8AAAQDAEcwRQIgEklH7wYCFuuJIFUHX5PY -// /vZ3bDoxOp+061PT3caa+rICIQC0abgfGlBKiHxp47JZxnW3wcVqWdiYX4ViLm9H -// xfx4ljCBxgYDVR0RBIG+MIG7gg1tZW1wb29sLnNwYWNlghMqLmZtdC5tZW1wb29s -// LnNwYWNlghMqLmZyYS5tZW1wb29sLnNwYWNlgg8qLm1lbXBvb2wuc3BhY2WCEyou -// dGs3Lm1lbXBvb2wuc3BhY2WCEyoudmExLm1lbXBvb2wuc3BhY2WCDGJpc3EubWFy -// a2V0c4IKYmlzcS5uaW5qYYIObGlxdWlkLm5ldHdvcmuCDGxpcXVpZC5wbGFjZYIN -// bWVtcG9vbC5uaW5qYTANBgkqhkiG9w0BAQsFAAOCAQEAFvOSRnlHDfq9C8acjZEG -// 5XIqjNYigyWyjOvx83of6Z3PBKkAZB5D/UHBPp+jBDJiEb/QXC7Z7Y7kpuvnoVib -// b4jDc0RjGEsxL+3F7cSw26m3wILJhhHooGZRmFY4GOAeCZtYCOTzJsiZvFpDoQjU -// hTBxtaps05z0Ly9/eYvkXnjnBNROZJVR+KYHlq4TIoGNc4q4KvpfHv2I/vhS2M1e -// bECNNPEyRxHGKdXXO3huocE7aVKpy+JDR6cWwDu6hpdc1j/SCDqdTDFQ7McHOrqA -// fpPh4FcfePMh7Mqxtg2pSs5pXPtiP0ZjLgxd7HbAXct8Y+/jGk+k3sx3SeYXVimr -// ew== -// -----END CERTIFICATE-----)"; +const char *mempoolWsCert = R"EOF( +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- +)EOF"; void setupBlockNotify() { @@ -103,13 +96,18 @@ void setupBlockNotify() esp_websocket_client_config_t config = { // .uri = "wss://mempool.space/api/v1/ws", - // .task_stack = (6*1024), - // .cert_pem = mempoolWsCert, - .user_agent = USER_AGENT, + .task_stack = (6*1024), + .user_agent = USER_AGENT }; + if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { + config.cert_pem = mempoolWsCert; + } + config.uri = mempoolUri.c_str(); + Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); + blockNotifyClient = esp_websocket_client_init(&config); esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY, onWebsocketBlockEvent, blockNotifyClient); @@ -302,7 +300,10 @@ int getBlockFetch() { try { WiFiClientSecure client; - client.setInsecure(); + + if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { + client.setCACert(mempoolWsCert); + } String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 4acc5d3..c295b93 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -753,4 +753,16 @@ bool isActiveCurrency(std::string ¤cy) return true; } return false; +} + +const char* getFirmwareFilename() { + if (HW_REV == "REV_B_EPD_2_13") { + return "btclock_rev_b_213epd_firmware.bin"; + } else if (HW_REV == "REV_A_EPD_2_13") { + return "lolin_s3_mini_213epd_firmware.bin"; + } else if (HW_REV == "REV_A_EPD_2_9") { + return "lolin_s3_mini_29epd_firmware.bin"; + } else { + return ""; + } } \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index e615431..e8fcffa 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -82,4 +82,5 @@ void addScreenMapping(int value, const char* name); // void addScreenMapping(int value, const std::string& name); int findScreenIndexByValue(int value); -String replaceAmbiguousChars(String input); \ No newline at end of file +String replaceAmbiguousChars(String input); +const char* getFirmwareFilename(); \ No newline at end of file diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 0d780a6..1b6756e 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -298,9 +298,14 @@ void ledTask(void *parameter) } } #endif - blinkDelayColor(250, 3, 142, 48, 235); - // blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235), - // pixels.Color(169, 21, 255)); + for (int flash = 0; flash < random(7, 10); flash++) + { + lightningStrike(); + delay(random(50, 150)); + } + // blinkDelayColor(250, 3, 142, 48, 235); + // blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235), + // pixels.Color(169, 21, 255)); #ifdef HAS_FRONTLIGHT if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) { @@ -668,4 +673,29 @@ void ledTheaterChaseRainbow(int wait) } } +void lightningStrike() +{ + uint32_t PURPLE = pixels.Color(128, 0, 128); + uint32_t YELLOW = pixels.Color(255, 226, 41); + + // Randomly choose which LEDs to light up + for (int i = 0; i < pixels.numPixels(); i++) + { + if (random(2) == 0) + { // 50% chance for each LED + pixels.setPixelColor(i, YELLOW); + } + else + { + pixels.setPixelColor(i, PURPLE); + } + } + pixels.show(); + + delay(random(10, 50)); // Flash duration + + // Return to purple background + // setAllPixels(PURPLE); +} + Adafruit_NeoPixel getPixels() { return pixels; } diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 898b121..b86475e 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -61,6 +61,7 @@ void ledRainbow(int wait); void ledTheaterChaseRainbow(int wait); void ledTheaterChase(uint32_t color, int wait); Adafruit_NeoPixel getPixels(); +void lightningStrike(); #ifdef HAS_FRONTLIGHT void frontlightFlash(int flDelayTime); diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index c457a01..709de61 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -70,62 +70,62 @@ void handleOTATask(void *parameter) { } } -void downloadUpdate() { - WiFiClientSecure client; - client.setInsecure(); - HTTPClient http; - http.setUserAgent(USER_AGENT); +// void downloadUpdate() { +// WiFiClientSecure client; +// client.setInsecure(); +// HTTPClient http; +// http.setUserAgent(USER_AGENT); - // Send HTTP request to CoinGecko API - http.useHTTP10(true); +// // Send HTTP request to CoinGecko API +// http.useHTTP10(true); - http.begin(client, - "https://api.github.com/repos/btclock/btclock_v3/releases/latest"); - int httpCode = http.GET(); +// http.begin(client, +// "https://api.github.com/repos/btclock/btclock_v3/releases/latest"); +// int httpCode = http.GET(); - if (httpCode == 200) { - // WiFiClient * stream = http->getStreamPtr(); +// if (httpCode == 200) { +// // WiFiClient * stream = http->getStreamPtr(); - JsonDocument filter; +// JsonDocument filter; - JsonObject filter_assets_0 = filter["assets"].add(); - filter_assets_0["name"] = true; - filter_assets_0["browser_download_url"] = true; +// JsonObject filter_assets_0 = filter["assets"].add(); +// filter_assets_0["name"] = true; +// filter_assets_0["browser_download_url"] = true; - JsonDocument doc; +// JsonDocument doc; - DeserializationError error = deserializeJson( - doc, http.getStream(), DeserializationOption::Filter(filter)); +// DeserializationError error = deserializeJson( +// doc, http.getStream(), DeserializationOption::Filter(filter)); - if (error) { - Serial.print("deserializeJson() failed: "); - Serial.println(error.c_str()); - return; - } +// if (error) { +// Serial.print("deserializeJson() failed: "); +// Serial.println(error.c_str()); +// return; +// } - String downloadUrl; - for (JsonObject asset : doc["assets"].as()) { - if (asset["name"].as().compareTo("firmware.bin") == 0) { - downloadUrl = asset["browser_download_url"].as(); - break; - } - } +// String downloadUrl; +// for (JsonObject asset : doc["assets"].as()) { +// if (asset["name"].as().compareTo("firmware.bin") == 0) { +// downloadUrl = asset["browser_download_url"].as(); +// break; +// } +// } - Serial.printf("Download update from %s", downloadUrl); +// Serial.printf("Download update from %s", downloadUrl); - // esp_http_client_config_t config = { - // .url = CONFIG_FIRMWARE_UPGRADE_URL, - // }; - // esp_https_ota_config_t ota_config = { - // .http_config = &config, - // }; - // esp_err_t ret = esp_https_ota(&ota_config); - // if (ret == ESP_OK) - // { - // esp_restart(); - // } - } -} +// // esp_http_client_config_t config = { +// // .url = CONFIG_FIRMWARE_UPGRADE_URL, +// // }; +// // esp_https_ota_config_t ota_config = { +// // .http_config = &config, +// // }; +// // esp_err_t ret = esp_https_ota(&ota_config); +// // if (ret == ESP_OK) +// // { +// // esp_restart(); +// // } +// } +// } void onOTAError(ota_error_t error) { Serial.println(F("\nOTA update error, restarting")); diff --git a/src/lib/ota.hpp b/src/lib/ota.hpp index 42f28cc..a3c4ef6 100644 --- a/src/lib/ota.hpp +++ b/src/lib/ota.hpp @@ -8,7 +8,7 @@ void setupOTA(); void onOTAStart(); void handleOTATask(void *parameter); void onOTAProgress(unsigned int progress, unsigned int total); -void downloadUpdate(); +// void downloadUpdate(); void onOTAError(ota_error_t error); void onOTAComplete(); diff --git a/src/lib/price_fetch.cpp b/src/lib/price_fetch.cpp deleted file mode 100644 index d2ddf67..0000000 --- a/src/lib/price_fetch.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "price_fetch.hpp" - -const PROGMEM char *cgApiUrl = - "https://api.coingecko.com/api/v3/simple/" - "price?ids=bitcoin&vs_currencies=usd%2Ceur"; - -TaskHandle_t priceFetchTaskHandle; - -void taskPriceFetch(void *pvParameters) { - WiFiClientSecure *client = new WiFiClientSecure; - client->setInsecure(); - for (;;) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - HTTPClient *http = new HTTPClient(); - http->setUserAgent(USER_AGENT); - - // Send HTTP request to CoinGecko API - http->begin(*client, cgApiUrl); - - int httpCode = http->GET(); - - // Parse JSON response and extract average price - uint usdPrice, eurPrice; - if (httpCode == 200) { - String payload = http->getString(); - JsonDocument doc; - deserializeJson(doc, payload); - // usdPrice = doc["bitcoin"]["usd"]; - eurPrice = doc["bitcoin"]["eur"].as(); - - setPrice(eurPrice, CURRENCY_EUR); - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || - getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || - getCurrentScreen() == SCREEN_MARKET_CAP)) { - WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; - xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); - } - - preferences.putUInt("lastPrice", eurPrice); - } else { - Serial.print( - F("Error retrieving BTC/USD price (CoinGecko). HTTP status code: ")); - Serial.println(httpCode); - if (httpCode == -1) { - WiFi.reconnect(); - } - } - } -} - -void setupPriceFetchTask() { - xTaskCreate(taskPriceFetch, "priceFetch", (6 * 1024), NULL, tskIDLE_PRIORITY, - &priceFetchTaskHandle); - - xTaskNotifyGive(priceFetchTaskHandle); -} \ No newline at end of file diff --git a/src/lib/price_fetch.hpp b/src/lib/price_fetch.hpp deleted file mode 100644 index 7016dda..0000000 --- a/src/lib/price_fetch.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -#include "lib/config.hpp" -#include "lib/shared.hpp" - -extern TaskHandle_t priceFetchTaskHandle; - -void setupPriceFetchTask(); -void taskPriceFetch(void *pvParameters); \ No newline at end of file diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index f5ad972..d6a1e71 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -5,6 +5,39 @@ const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws"; const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; +const char* coincapWsCert = R"EOF( +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +)EOF"; // WebsocketsClient client; esp_websocket_client_handle_t clientPrice = NULL; @@ -14,6 +47,7 @@ unsigned long int lastPriceUpdate; bool priceNotifyInit = false; std::map currencyMap; std::map lastUpdateMap; +WebSocketsClient priceNotifyWs; void setupPriceNotify() { @@ -26,14 +60,51 @@ void setupPriceNotify() { config = {.uri = wsServerPrice, .user_agent = USER_AGENT}; + config.cert_pem = coincapWsCert; + config.task_stack = (6*1024); } clientPrice = esp_websocket_client_init(&config); esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, onWebsocketPriceEvent, clientPrice); esp_websocket_client_start(clientPrice); + + // priceNotifyWs.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); + // priceNotifyWs.onEvent(onWebsocketPriceEvent); + // priceNotifyWs.setReconnectInterval(5000); + // priceNotifyWs.enableHeartbeat(15000, 3000, 2); } + +// void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { +// switch(type) { +// case WStype_DISCONNECTED: +// Serial.printf("[WSc] Disconnected!\n"); +// break; +// case WStype_CONNECTED: +// { +// Serial.printf("[WSc] Connected to url: %s\n", payload); + + +// break; +// } +// case WStype_TEXT: +// String message = String((char*)payload); +// onWebsocketPriceMessage(message); +// break; +// case WStype_BIN: +// break; +// case WStype_ERROR: +// case WStype_FRAGMENT_TEXT_START: +// case WStype_FRAGMENT_BIN_START: +// case WStype_FRAGMENT: +// case WStype_PING: +// case WStype_PONG: +// case WStype_FRAGMENT_FIN: +// break; +// } +// } + void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { @@ -83,7 +154,7 @@ void processNewPrice(uint newPrice, char currency) "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); uint currentTime = esp_timer_get_time() / 1000000; - if (lastUpdateMap.find(currency) == lastUpdateMap.end()|| + if (lastUpdateMap.find(currency) == lastUpdateMap.end() || (currentTime - lastUpdateMap[currency]) > minSecPriceUpd) { // const unsigned long oldPrice = currentPrice; @@ -108,22 +179,26 @@ void processNewPrice(uint newPrice, char currency) uint getLastPriceUpdate(char currency) { - if (lastUpdateMap.find(currency) == lastUpdateMap.end()) { + if (lastUpdateMap.find(currency) == lastUpdateMap.end()) + { return 0; } return lastUpdateMap[currency]; } -uint getPrice(char currency) { - if (currencyMap.find(currency) == currencyMap.end()) { +uint getPrice(char currency) +{ + if (currencyMap.find(currency) == currencyMap.end()) + { return 0; } - return currencyMap[currency]; + return currencyMap[currency]; } -void setPrice(uint newPrice, char currency) { - currencyMap[currency] = newPrice; +void setPrice(uint newPrice, char currency) +{ + currencyMap[currency] = newPrice; } bool isPriceNotifyConnected() diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 541f899..4aad356 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -12,6 +12,8 @@ void setupPriceNotify(); void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); +//void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); + void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); uint getPrice(char currency); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 13e93b5..091a6e0 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -126,9 +126,6 @@ void IRAM_ATTR minuteTimerISR(void *arg) { // vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken); WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken); - if (priceFetchTaskHandle != NULL) { - vTaskNotifyGiveFromISR(priceFetchTaskHandle, &xHigherPriorityTaskWoken); - } if (bitaxeFetchTaskHandle != NULL) { vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index a5c8f8e..91e824a 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -8,7 +8,6 @@ #include #include "lib/epd.hpp" -#include "lib/price_fetch.hpp" #include "lib/shared.hpp" // extern TaskHandle_t priceUpdateTaskHandle; diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index e6acfab..8a6b470 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -1,10 +1,74 @@ #include "shared.hpp" +const char *github_root_ca = + "-----BEGIN CERTIFICATE-----\n" + "MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n" + "MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n" + "eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n" + "JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n" + "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n" + "Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n" + "VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n" + "aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n" + "I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n" + "o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n" + "A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" + "VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n" + "zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n" + "RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n" + "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n" + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" + "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n" + "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n" + "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n" + "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n" + "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n" + "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n" + "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n" + "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n" + "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n" + "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n" + "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n" + "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n" + "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n" + "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n" + "MrY=\n" + "-----END CERTIFICATE-----\n"; + #ifdef TEST_SCREENS -uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24 -uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits -uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits -uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w +uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24 +uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits +uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits +uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w -uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display -#endif \ No newline at end of file +uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display +#endif + +// Function to calculate SHA-256 hash +String calculateSHA256(uint8_t *data, size_t len) +{ + byte shaResult[32]; + mbedtls_md_context_t ctx; + mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256; + + mbedtls_md_init(&ctx); + mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0); + mbedtls_md_starts(&ctx); + mbedtls_md_update(&ctx, data, len); + mbedtls_md_finish(&ctx, shaResult); + mbedtls_md_free(&ctx); + + char sha256_str[65]; + for (int i = 0; i < 32; i++) + { + sprintf(sha256_str + (i * 2), "%02x", shaResult[i]); + } + sha256_str[64] = 0; + + return String(sha256_str); +} diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 5c111df..26c4c95 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -65,7 +66,16 @@ const PROGMEM int screens[SCREEN_COUNT] = { const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; +extern const char *github_root_ca; + +const PROGMEM char UPDATE_FIRMWARE = 0; +const PROGMEM char UPDATE_WEBUI = 1; + + struct ScreenMapping { int value; const char* name; -}; \ No newline at end of file +}; + +String calculateSHA256(uint8_t* data, size_t len); + diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index e23c0af..13d302d 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -9,7 +9,9 @@ void setupV2Notify() if ( preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) { Serial.println(F("Connecting to V2 staging source")); hostname = "ws-staging.btclock.dev"; - } + } else { + Serial.println(F("Connecting to V2 source")); + } webSocket.beginSSL(hostname, 443, "/api/v2/ws"); webSocket.onEvent(onWebsocketV2Event); @@ -120,7 +122,7 @@ void handleV2Message(JsonDocument doc) { processNewPrice(newPrice, getCurrencyChar(currency)); } - } + } } void taskV2Notify(void *pvParameters) { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index bad307f..392e6c7 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -86,6 +86,8 @@ void setupWebserver() { server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); + // server.on("/update/webui", HTTP_GET, onUpdateWebUi); + // server.on("/update/firmware", HTTP_GET, onUpdateFirmware); } server.on("/api/restart", HTTP_GET, onApiRestart); @@ -662,7 +664,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["mcapBigChar"] = preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR); root["mdnsEnabled"] = preferences.getBool("mdnsEnabled", DEFAULT_MDNS_ENABLED); root["otaEnabled"] = preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED); - root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE); + // root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE); root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL); root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN); root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE); @@ -1043,6 +1045,167 @@ void onApiShowCurrency(AsyncWebServerRequest *request) request->send(404); } +String getLatestRelease(const String &fileToDownload) +{ + + // const char *fileToDownload = "littlefs.bin"; + + String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest"; + WiFiClientSecure client; + client.setCACert(github_root_ca); + HTTPClient http; + http.begin(client, releaseUrl); + http.setUserAgent(USER_AGENT); + + int httpCode = http.GET(); + + String downloadUrl = ""; + + if (httpCode > 0) + { + String payload = http.getString(); + + JsonDocument doc; + deserializeJson(doc, payload); + + JsonArray assets = doc["assets"]; + + for (JsonObject asset : assets) + { + if (asset["name"] == fileToDownload) + { + downloadUrl = asset["browser_download_url"].as(); + break; + } + } + Serial.printf("Latest release URL: %s\r\n", downloadUrl.c_str()); + } + return downloadUrl; +} + +void onUpdateWebUi(AsyncWebServerRequest *request) +{ + request->send(downloadUpdateHandler(UPDATE_WEBUI)); +} + +void onUpdateFirmware(AsyncWebServerRequest *request) +{ + request->send(downloadUpdateHandler(UPDATE_FIRMWARE)); +} + +int downloadUpdateHandler(char updateType) +{ + WiFiClientSecure client; + client.setCACert(github_root_ca); + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + + String latestRelease = ""; + + switch (updateType) + { + case UPDATE_FIRMWARE: + latestRelease = getLatestRelease(getFirmwareFilename()); + break; + case UPDATE_WEBUI: + latestRelease = getLatestRelease("littlefs.bin"); + break; + } + + if (latestRelease.equals("")) + { + return 503; + } + + http.begin(client, latestRelease); + http.setUserAgent(USER_AGENT); + + int httpCode = http.GET(); + if (httpCode == HTTP_CODE_OK) + { + int contentLength = http.getSize(); + if (contentLength > 0) + { + uint8_t *buffer = (uint8_t *)malloc(contentLength); + if (buffer) + { + WiFiClient *stream = http.getStreamPtr(); + size_t written = stream->readBytes(buffer, contentLength); + + if (written == contentLength) + { + String calculated_sha256 = calculateSHA256(buffer, contentLength); + Serial.print("Checksum is "); + Serial.println(calculated_sha256); + if (true) + { + Serial.println("Checksum verified. Proceeding with update."); + + Update.onProgress(onOTAProgress); + + int updateType = U_FLASH; + + switch (updateType) + { + case UPDATE_WEBUI: + updateType = U_SPIFFS; + break; + default: + { + updateType = U_FLASH; + } + } + + if (Update.begin(contentLength, updateType)) + { + Update.write(buffer, contentLength); + if (Update.end()) + { + Serial.println("Update complete. Rebooting."); + ESP.restart(); + } + else + { + Serial.println("Error in update process."); + } + } + else + { + Serial.println("Not enough space to begin OTA"); + } + } + else + { + Serial.println("Checksum mismatch. Aborting update."); + } + } + else + { + Serial.println("Error downloading firmware"); + } + free(buffer); + } + else + { + Serial.println("Not enough memory to allocate buffer"); + } + } + else + { + Serial.println("Invalid content length"); + } + } + else + { + Serial.print(httpCode); + Serial.println("Error on HTTP request"); + return 503; + } + http.end(); + + return 200; +} + #ifdef HAS_FRONTLIGHT void onApiFrontlightOn(AsyncWebServerRequest *request) { diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 3f71ac9..546930a 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -21,10 +21,17 @@ void stopWebServer(); void setupWebserver(); bool processEpdColorSettings(AsyncWebServerRequest *request); + + void onApiStatus(AsyncWebServerRequest *request); void onApiSystemStatus(AsyncWebServerRequest *request); void onApiSetWifiTxPower(AsyncWebServerRequest *request); +void onUpdateWebUi(AsyncWebServerRequest *request); +void onUpdateFirmware(AsyncWebServerRequest *request); +int downloadUpdateHandler(char updateType); + +String getLatestRelease(const String& fileToDownload); void onApiScreenNext(AsyncWebServerRequest *request); void onApiScreenPrevious(AsyncWebServerRequest *request); From 5d5b09f56c66809cd7501cb2e7ac34b996292adb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 11 Sep 2024 17:40:44 +0200 Subject: [PATCH 072/188] Prepare for automatic OTA updates --- .github/workflows/tagging.yml | 5 +- src/lib/ota.cpp | 384 ++++++++++++++++++++++++++++------ src/lib/ota.hpp | 18 +- src/lib/shared.cpp | 37 ++++ src/lib/shared.hpp | 3 +- src/lib/webserver.cpp | 187 +++-------------- src/lib/webserver.hpp | 2 - 7 files changed, 409 insertions(+), 227 deletions(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index d0fe113..1418356 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -56,7 +56,10 @@ jobs: run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin - name: Create checksum for merged binary - run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.sha256 + run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256 + + - name: Create checksum for littlefs partition + run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin.sha256 - name: Copy all artifacts to output folder run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 709de61..92838db 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -2,9 +2,12 @@ TaskHandle_t taskOtaHandle = NULL; bool isOtaUpdating = false; +QueueHandle_t otaQueue; -void setupOTA() { - if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED)) { +void setupOTA() +{ + if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED)) + { ArduinoOTA.onStart(onOTAStart); ArduinoOTA.onProgress(onOTAProgress); @@ -16,31 +19,38 @@ void setupOTA() { ArduinoOTA.setRebootOnSuccess(false); ArduinoOTA.begin(); // downloadUpdate(); + otaQueue = xQueueCreate(1, sizeof(UpdateMessage)); - xTaskCreate(handleOTATask, "handleOTA", 4096, NULL, tskIDLE_PRIORITY, + xTaskCreate(handleOTATask, "handleOTA", 8192, NULL, 20, &taskOtaHandle); } } -void onOTAProgress(unsigned int progress, unsigned int total) { +void onOTAProgress(unsigned int progress, unsigned int total) +{ uint percentage = progress / (total / 100); pixels.fill(pixels.Color(0, 255, 0)); - if (percentage < 100) { + if (percentage < 100) + { pixels.setPixelColor(0, pixels.Color(0, 0, 0)); } - if (percentage < 75) { + if (percentage < 75) + { pixels.setPixelColor(1, pixels.Color(0, 0, 0)); } - if (percentage < 50) { + if (percentage < 50) + { pixels.setPixelColor(2, pixels.Color(0, 0, 0)); } - if (percentage < 25) { + if (percentage < 25) + { pixels.setPixelColor(3, pixels.Color(0, 0, 0)); } pixels.show(); } -void onOTAStart() { +void onOTAStart() +{ forceFullRefresh(); std::array epdContent = {"U", "P", "D", "A", "T", "E", "!"}; @@ -58,76 +68,296 @@ void onOTAStart() { vTaskSuspend(ledTaskHandle); vTaskSuspend(buttonTaskHandle); - stopWebServer(); + // stopWebServer(); stopBlockNotify(); stopPriceNotify(); } -void handleOTATask(void *parameter) { - for (;;) { - ArduinoOTA.handle(); // Allow OTA updates to occur +void handleOTATask(void *parameter) +{ + UpdateMessage msg; + + for (;;) + { + if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE) + { + int result = downloadUpdateHandler(msg.updateType); + } + + ArduinoOTA.handle(); // Allow OTA updates to occur vTaskDelay(pdMS_TO_TICKS(2000)); } } -// void downloadUpdate() { -// WiFiClientSecure client; -// client.setInsecure(); -// HTTPClient http; -// http.setUserAgent(USER_AGENT); +String getLatestRelease(const String &fileToDownload) +{ + String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest"; + WiFiClientSecure client; + client.setCACert(github_root_ca); + HTTPClient http; + http.begin(client, releaseUrl); + http.setUserAgent(USER_AGENT); -// // Send HTTP request to CoinGecko API -// http.useHTTP10(true); + int httpCode = http.GET(); -// http.begin(client, -// "https://api.github.com/repos/btclock/btclock_v3/releases/latest"); -// int httpCode = http.GET(); + String downloadUrl = ""; -// if (httpCode == 200) { -// // WiFiClient * stream = http->getStreamPtr(); + if (httpCode > 0) + { + String payload = http.getString(); -// JsonDocument filter; + JsonDocument doc; + deserializeJson(doc, payload); -// JsonObject filter_assets_0 = filter["assets"].add(); -// filter_assets_0["name"] = true; -// filter_assets_0["browser_download_url"] = true; + JsonArray assets = doc["assets"]; -// JsonDocument doc; + for (JsonObject asset : assets) + { + if (asset["name"] == fileToDownload) + { + downloadUrl = asset["browser_download_url"].as(); + break; + } + } + Serial.printf("Latest release URL: %s\r\n", downloadUrl.c_str()); + } + return downloadUrl; +} -// DeserializationError error = deserializeJson( -// doc, http.getStream(), DeserializationOption::Filter(filter)); +int downloadUpdateHandler(char updateType) +{ + WiFiClientSecure client; + client.setCACert(github_root_ca); + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -// if (error) { -// Serial.print("deserializeJson() failed: "); -// Serial.println(error.c_str()); -// return; -// } + String latestRelease = ""; -// String downloadUrl; -// for (JsonObject asset : doc["assets"].as()) { -// if (asset["name"].as().compareTo("firmware.bin") == 0) { -// downloadUrl = asset["browser_download_url"].as(); -// break; -// } -// } + switch (updateType) + { + case UPDATE_FIRMWARE: + { + latestRelease = getLatestRelease(getFirmwareFilename()); + } + break; + case UPDATE_WEBUI: + { + latestRelease = getLatestRelease("littlefs.bin"); + updateWebUi(latestRelease, U_SPIFFS); + return 0; + } + break; + } -// Serial.printf("Download update from %s", downloadUrl); + if (latestRelease.isEmpty()) + { + return 503; + } + // First, download the expected SHA256 + String expectedSHA256 = downloadSHA256(getFirmwareFilename()); + if (expectedSHA256.isEmpty()) + { + Serial.println("Failed to get SHA256 checksum. Aborting update."); + return false; + } -// // esp_http_client_config_t config = { -// // .url = CONFIG_FIRMWARE_UPGRADE_URL, -// // }; -// // esp_https_ota_config_t ota_config = { -// // .http_config = &config, -// // }; -// // esp_err_t ret = esp_https_ota(&ota_config); -// // if (ret == ESP_OK) -// // { -// // esp_restart(); -// // } -// } -// } + http.begin(client, latestRelease); + http.setUserAgent(USER_AGENT); -void onOTAError(ota_error_t error) { + int httpCode = http.GET(); + if (httpCode == HTTP_CODE_OK) + { + int contentLength = http.getSize(); + if (contentLength > 0) + { + // Allocate memory to store the firmware + uint8_t *firmware = (uint8_t *)malloc(contentLength); + if (!firmware) + { + Serial.println(F("Not enough memory to store firmware")); + return false; + } + + WiFiClient *stream = http.getStreamPtr(); + size_t bytesRead = 0; + while (bytesRead < contentLength) + { + size_t available = stream->available(); + if (available) + { + size_t readBytes = stream->readBytes(firmware + bytesRead, available); + bytesRead += readBytes; + } + yield(); // Allow background tasks to run + } + + if (bytesRead != contentLength) + { + Serial.println("Failed to read entire firmware"); + free(firmware); + return false; + } + + // Calculate SHA256 + String calculated_sha256 = calculateSHA256(firmware, contentLength); + + Serial.print("Calculated checksum: "); + Serial.println(calculated_sha256); + Serial.print("Expected checksum: "); + Serial.println(expectedSHA256); + + if (calculated_sha256 != expectedSHA256) + { + Serial.println("Checksum mismatch. Aborting update."); + free(firmware); + return false; + } + + Update.onProgress(onOTAProgress); + + int updateType = (updateType == UPDATE_WEBUI) ? U_SPIFFS : U_FLASH; + + if (Update.begin(contentLength, updateType)) + { + size_t written = Update.writeStream(*stream); + + if (written == contentLength) + { + Serial.println("Written : " + String(written) + " successfully"); + } + else + { + Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?"); + } + + if (Update.end()) + { + Serial.println("OTA done!"); + if (Update.isFinished()) + { + Serial.println("Update successfully completed. Rebooting."); + ESP.restart(); + } + else + { + Serial.println("Update not finished? Something went wrong!"); + } + } + else + { + Serial.println("Error Occurred. Error #: " + String(Update.getError())); + } + } + else + { + Serial.println("Not enough space to begin OTA"); + } + } + else + { + Serial.println("Invalid content length"); + } + } + else + { + Serial.printf("HTTP error: %d\n", httpCode); + return 503; + } + http.end(); + + return 200; +} + +void updateWebUi(String latestRelease, int command) +{ + WiFiClientSecure client; + client.setCACert(github_root_ca); + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.begin(client, latestRelease); + http.setUserAgent(USER_AGENT); + + int httpCode = http.GET(); + if (httpCode == HTTP_CODE_OK) + { + int contentLength = http.getSize(); + if (contentLength > 0) + { + uint8_t *buffer = (uint8_t *)malloc(contentLength); + if (buffer) + { + WiFiClient *stream = http.getStreamPtr(); + size_t written = stream->readBytes(buffer, contentLength); + + if (written == contentLength) + { + String expectedSHA256 = ""; + if (command == U_FLASH) + { + expectedSHA256 = downloadSHA256(getFirmwareFilename()); + Serial.print("Expected checksum: "); + Serial.println(expectedSHA256); + } + + String calculated_sha256 = calculateSHA256(buffer, contentLength); + Serial.print("Checksum is "); + Serial.println(calculated_sha256); + if ((command == U_FLASH && expectedSHA256.equals(calculated_sha256)) || command == U_SPIFFS) + { + Serial.println("Checksum verified. Proceeding with update."); + + Update.onProgress(onOTAProgress); + + if (Update.begin(contentLength, command)) + { + onOTAStart(); + + Update.write(buffer, contentLength); + if (Update.end()) + { + Serial.println("Update complete. Rebooting."); + ESP.restart(); + } + else + { + Serial.println("Error in update process."); + } + } + else + { + Serial.println("Not enough space to begin OTA"); + } + } + else + { + Serial.println("Checksum mismatch. Aborting update."); + } + } + else + { + Serial.println("Error downloading firmware"); + } + free(buffer); + } + else + { + Serial.println("Not enough memory to allocate buffer"); + } + } + else + { + Serial.println("Invalid content length"); + } + } + else + { + Serial.print(httpCode); + Serial.println("Error on HTTP request"); + } +} + +void onOTAError(ota_error_t error) +{ Serial.println(F("\nOTA update error, restarting")); Wire.end(); SPI.end(); @@ -136,7 +366,8 @@ void onOTAError(ota_error_t error) { ESP.restart(); } -void onOTAComplete() { +void onOTAComplete() +{ Serial.println(F("\nOTA update finished")); Wire.end(); SPI.end(); @@ -144,6 +375,37 @@ void onOTAComplete() { ESP.restart(); } -bool getIsOTAUpdating() { +bool getIsOTAUpdating() +{ return isOtaUpdating; +} + +String downloadSHA256(const String &filename) +{ + String sha256Url = getLatestRelease(filename + ".sha256"); + if (sha256Url.isEmpty()) + { + Serial.println("Failed to get SHA256 file URL"); + return ""; + } + + WiFiClientSecure client; + client.setCACert(github_root_ca); + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.begin(client, sha256Url); + http.setUserAgent(USER_AGENT); + + int httpCode = http.GET(); + if (httpCode == HTTP_CODE_OK) + { + String sha256 = http.getString(); + sha256.trim(); // Remove any whitespace or newline characters + return sha256; + } + else + { + Serial.printf("Failed to download SHA256 file. HTTP error: %d\n", httpCode); + return ""; + } } \ No newline at end of file diff --git a/src/lib/ota.hpp b/src/lib/ota.hpp index a3c4ef6..fd29473 100644 --- a/src/lib/ota.hpp +++ b/src/lib/ota.hpp @@ -1,9 +1,20 @@ +#pragma once + #include #include #include "lib/config.hpp" #include "lib/shared.hpp" +#ifndef UPDATE_MESSAGE_HPP +#define UPDATE_MESSAGE_HPP +typedef struct { + char updateType; +} UpdateMessage; +#endif + +extern QueueHandle_t otaQueue; + void setupOTA(); void onOTAStart(); void handleOTATask(void *parameter); @@ -11,5 +22,10 @@ void onOTAProgress(unsigned int progress, unsigned int total); // void downloadUpdate(); void onOTAError(ota_error_t error); void onOTAComplete(); +int downloadUpdateHandler(char updateType); +String getLatestRelease(const String& fileToDownload); -bool getIsOTAUpdating(); \ No newline at end of file +bool getIsOTAUpdating(); + +void updateWebUi(String latestRelease, int command); +String downloadSHA256(const String& filename); \ No newline at end of file diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index 8a6b470..597d803 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -72,3 +72,40 @@ String calculateSHA256(uint8_t *data, size_t len) return String(sha256_str); } + +String calculateSHA256(WiFiClient *stream, size_t contentLength) { + mbedtls_md_context_t ctx; + mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256; + + mbedtls_md_init(&ctx); + mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0); + mbedtls_md_starts(&ctx); + + uint8_t buff[1024]; + size_t bytesRead = 0; + + while (bytesRead < contentLength) { + size_t toRead = min((size_t)(contentLength - bytesRead), sizeof(buff)); + size_t readBytes = stream->readBytes(buff, toRead); + + if (readBytes == 0) { + break; + } + + mbedtls_md_update(&ctx, buff, readBytes); + bytesRead += readBytes; + } + + byte shaResult[32]; + mbedtls_md_finish(&ctx, shaResult); + mbedtls_md_free(&ctx); + + String result = ""; + for (int i = 0; i < sizeof(shaResult); i++) { + char str[3]; + sprintf(str, "%02x", (int)shaResult[i]); + result += str; + } + + return result; +} \ No newline at end of file diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 26c4c95..dba240c 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -78,4 +79,4 @@ struct ScreenMapping { }; String calculateSHA256(uint8_t* data, size_t len); - +String calculateSHA256(WiFiClient *stream, size_t contentLength); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 392e6c7..a789e75 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -142,6 +142,32 @@ void onFirmwareUpdate(AsyncWebServerRequest *request) request->send(response); } +void onUpdateWebUi(AsyncWebServerRequest *request) +{ + UpdateMessage msg = {UPDATE_WEBUI}; + if (xQueueSend(otaQueue, &msg, 0) == pdTRUE) + { + request->send(200, "text/plain", "WebUI update triggered"); + } + else + { + request->send(503, "text/plain", "Update already in progress"); + } +} + +void onUpdateFirmware(AsyncWebServerRequest *request) +{ + UpdateMessage msg = {UPDATE_FIRMWARE}; + if (xQueueSend(otaQueue, &msg, 0) == pdTRUE) + { + request->send(200, "text/plain", "Firmware update triggered"); + } + else + { + request->send(503, "text/plain", "Update already in progress"); + } +} + void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { asyncFileUpdateHandler(request, filename, index, data, len, final, U_SPIFFS); @@ -1045,167 +1071,6 @@ void onApiShowCurrency(AsyncWebServerRequest *request) request->send(404); } -String getLatestRelease(const String &fileToDownload) -{ - - // const char *fileToDownload = "littlefs.bin"; - - String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest"; - WiFiClientSecure client; - client.setCACert(github_root_ca); - HTTPClient http; - http.begin(client, releaseUrl); - http.setUserAgent(USER_AGENT); - - int httpCode = http.GET(); - - String downloadUrl = ""; - - if (httpCode > 0) - { - String payload = http.getString(); - - JsonDocument doc; - deserializeJson(doc, payload); - - JsonArray assets = doc["assets"]; - - for (JsonObject asset : assets) - { - if (asset["name"] == fileToDownload) - { - downloadUrl = asset["browser_download_url"].as(); - break; - } - } - Serial.printf("Latest release URL: %s\r\n", downloadUrl.c_str()); - } - return downloadUrl; -} - -void onUpdateWebUi(AsyncWebServerRequest *request) -{ - request->send(downloadUpdateHandler(UPDATE_WEBUI)); -} - -void onUpdateFirmware(AsyncWebServerRequest *request) -{ - request->send(downloadUpdateHandler(UPDATE_FIRMWARE)); -} - -int downloadUpdateHandler(char updateType) -{ - WiFiClientSecure client; - client.setCACert(github_root_ca); - HTTPClient http; - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - - String latestRelease = ""; - - switch (updateType) - { - case UPDATE_FIRMWARE: - latestRelease = getLatestRelease(getFirmwareFilename()); - break; - case UPDATE_WEBUI: - latestRelease = getLatestRelease("littlefs.bin"); - break; - } - - if (latestRelease.equals("")) - { - return 503; - } - - http.begin(client, latestRelease); - http.setUserAgent(USER_AGENT); - - int httpCode = http.GET(); - if (httpCode == HTTP_CODE_OK) - { - int contentLength = http.getSize(); - if (contentLength > 0) - { - uint8_t *buffer = (uint8_t *)malloc(contentLength); - if (buffer) - { - WiFiClient *stream = http.getStreamPtr(); - size_t written = stream->readBytes(buffer, contentLength); - - if (written == contentLength) - { - String calculated_sha256 = calculateSHA256(buffer, contentLength); - Serial.print("Checksum is "); - Serial.println(calculated_sha256); - if (true) - { - Serial.println("Checksum verified. Proceeding with update."); - - Update.onProgress(onOTAProgress); - - int updateType = U_FLASH; - - switch (updateType) - { - case UPDATE_WEBUI: - updateType = U_SPIFFS; - break; - default: - { - updateType = U_FLASH; - } - } - - if (Update.begin(contentLength, updateType)) - { - Update.write(buffer, contentLength); - if (Update.end()) - { - Serial.println("Update complete. Rebooting."); - ESP.restart(); - } - else - { - Serial.println("Error in update process."); - } - } - else - { - Serial.println("Not enough space to begin OTA"); - } - } - else - { - Serial.println("Checksum mismatch. Aborting update."); - } - } - else - { - Serial.println("Error downloading firmware"); - } - free(buffer); - } - else - { - Serial.println("Not enough memory to allocate buffer"); - } - } - else - { - Serial.println("Invalid content length"); - } - } - else - { - Serial.print(httpCode); - Serial.println("Error on HTTP request"); - return 503; - } - http.end(); - - return 200; -} - #ifdef HAS_FRONTLIGHT void onApiFrontlightOn(AsyncWebServerRequest *request) { diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 546930a..9f47d02 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -29,9 +29,7 @@ void onApiSetWifiTxPower(AsyncWebServerRequest *request); void onUpdateWebUi(AsyncWebServerRequest *request); void onUpdateFirmware(AsyncWebServerRequest *request); -int downloadUpdateHandler(char updateType); -String getLatestRelease(const String& fileToDownload); void onApiScreenNext(AsyncWebServerRequest *request); void onApiScreenPrevious(AsyncWebServerRequest *request); From 283469dc4c0e8d66e2ea2759a9f791cc7ad74b24 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 11 Sep 2024 18:07:34 +0200 Subject: [PATCH 073/188] Fix github action --- .github/workflows/tagging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 1418356..2e232b6 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -59,7 +59,7 @@ jobs: run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256 - name: Create checksum for littlefs partition - run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin.sha256 + run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin.sha256 - name: Copy all artifacts to output folder run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} From d00c216126406c11d08c9c1e1a6422a2bea9bc52 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 11 Sep 2024 18:56:06 +0200 Subject: [PATCH 074/188] GitHub Workflow addition --- .github/workflows/tagging.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 2e232b6..825d100 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -55,6 +55,9 @@ jobs: - name: Create merged firmware binary run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin + - name: Create checksum for firmware + run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin.sha256 + - name: Create checksum for merged binary run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256 From 023ff29131436ba2a94503a3c2052c7c184a7e76 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 11 Sep 2024 20:27:40 +0200 Subject: [PATCH 075/188] Add single-click auto-update functionality --- data | 2 +- src/lib/ota.cpp | 77 +++++++++++++++++++++++++++++-------------- src/lib/ota.hpp | 10 ++++-- src/lib/shared.hpp | 7 ++-- src/lib/webserver.cpp | 24 +++----------- src/lib/webserver.hpp | 5 +-- 6 files changed, 72 insertions(+), 53 deletions(-) diff --git a/data b/data index 1c2d8dc..6c40b54 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 1c2d8dcdd0efd846f39b0f24977c982a96f16392 +Subproject commit 6c40b54273b7f7c7d6c2624d3c2a066435f27756 diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 92838db..3a391ca 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -4,6 +4,8 @@ TaskHandle_t taskOtaHandle = NULL; bool isOtaUpdating = false; QueueHandle_t otaQueue; + + void setupOTA() { if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED)) @@ -65,7 +67,7 @@ void onOTAStart() vTaskSuspend(workerTaskHandle); vTaskSuspend(taskScreenRotateTaskHandle); - vTaskSuspend(ledTaskHandle); +// vTaskSuspend(ledTaskHandle); vTaskSuspend(buttonTaskHandle); // stopWebServer(); @@ -81,7 +83,18 @@ void handleOTATask(void *parameter) { if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE) { - int result = downloadUpdateHandler(msg.updateType); + if (msg.updateType == UPDATE_ALL) { + int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI); + int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE); + + if (resultWebUi == 0 && resultFw == 0) { + ESP.restart(); + } else { + queueLedEffect(LED_FLASH_ERROR); + vTaskDelay(pdMS_TO_TICKS(3000)); + ESP.restart(); + } + } } ArduinoOTA.handle(); // Allow OTA updates to occur @@ -89,7 +102,7 @@ void handleOTATask(void *parameter) } } -String getLatestRelease(const String &fileToDownload) +ReleaseInfo getLatestRelease(const String &fileToDownload) { String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest"; WiFiClientSecure client; @@ -100,7 +113,7 @@ String getLatestRelease(const String &fileToDownload) int httpCode = http.GET(); - String downloadUrl = ""; + ReleaseInfo info = {"", ""}; if (httpCode > 0) { @@ -113,15 +126,26 @@ String getLatestRelease(const String &fileToDownload) for (JsonObject asset : assets) { - if (asset["name"] == fileToDownload) + String assetName = asset["name"].as(); + if (assetName == fileToDownload) + { + info.fileUrl = asset["browser_download_url"].as(); + } + else if (assetName == fileToDownload + ".sha256") + { + info.checksumUrl = asset["browser_download_url"].as(); + } + + if (!info.fileUrl.isEmpty() && !info.checksumUrl.isEmpty()) { - downloadUrl = asset["browser_download_url"].as(); break; } } - Serial.printf("Latest release URL: %s\r\n", downloadUrl.c_str()); + Serial.printf("Latest release URL: %s\r\n", info.fileUrl.c_str()); + Serial.printf("Checksum URL: %s\r\n", info.checksumUrl.c_str()); } - return downloadUrl; + http.end(); + return info; } int downloadUpdateHandler(char updateType) @@ -131,7 +155,7 @@ int downloadUpdateHandler(char updateType) HTTPClient http; http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - String latestRelease = ""; + ReleaseInfo latestRelease; switch (updateType) { @@ -143,25 +167,22 @@ int downloadUpdateHandler(char updateType) case UPDATE_WEBUI: { latestRelease = getLatestRelease("littlefs.bin"); - updateWebUi(latestRelease, U_SPIFFS); - return 0; + // updateWebUi(latestRelease.fileUrl, U_SPIFFS); + // return 0; } break; } - if (latestRelease.isEmpty()) - { - return 503; - } + // First, download the expected SHA256 - String expectedSHA256 = downloadSHA256(getFirmwareFilename()); + String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl); if (expectedSHA256.isEmpty()) { Serial.println("Failed to get SHA256 checksum. Aborting update."); return false; } - http.begin(client, latestRelease); + http.begin(client, latestRelease.fileUrl); http.setUserAgent(USER_AGENT); int httpCode = http.GET(); @@ -215,19 +236,21 @@ int downloadUpdateHandler(char updateType) Update.onProgress(onOTAProgress); - int updateType = (updateType == UPDATE_WEBUI) ? U_SPIFFS : U_FLASH; - if (Update.begin(contentLength, updateType)) { - size_t written = Update.writeStream(*stream); + onOTAStart(); + size_t written = Update.write(firmware, contentLength); if (written == contentLength) { Serial.println("Written : " + String(written) + " successfully"); + free(firmware); } else { Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?"); + free(firmware); + return 503; } if (Update.end()) @@ -236,26 +259,33 @@ int downloadUpdateHandler(char updateType) if (Update.isFinished()) { Serial.println("Update successfully completed. Rebooting."); - ESP.restart(); +// ESP.restart(); } else { Serial.println("Update not finished? Something went wrong!"); + free(firmware); + return 503; } } else { Serial.println("Error Occurred. Error #: " + String(Update.getError())); + free(firmware); + return 503; } } else { Serial.println("Not enough space to begin OTA"); + free(firmware); + return 503; } } else { Serial.println("Invalid content length"); + return 503; } } else @@ -265,7 +295,7 @@ int downloadUpdateHandler(char updateType) } http.end(); - return 200; + return 0; } void updateWebUi(String latestRelease, int command) @@ -380,9 +410,8 @@ bool getIsOTAUpdating() return isOtaUpdating; } -String downloadSHA256(const String &filename) +String downloadSHA256(const String &sha256Url) { - String sha256Url = getLatestRelease(filename + ".sha256"); if (sha256Url.isEmpty()) { Serial.println("Failed to get SHA256 file URL"); diff --git a/src/lib/ota.hpp b/src/lib/ota.hpp index fd29473..516f9a1 100644 --- a/src/lib/ota.hpp +++ b/src/lib/ota.hpp @@ -15,6 +15,11 @@ typedef struct { extern QueueHandle_t otaQueue; +struct ReleaseInfo { + String fileUrl; + String checksumUrl; +}; + void setupOTA(); void onOTAStart(); void handleOTATask(void *parameter); @@ -23,9 +28,10 @@ void onOTAProgress(unsigned int progress, unsigned int total); void onOTAError(ota_error_t error); void onOTAComplete(); int downloadUpdateHandler(char updateType); -String getLatestRelease(const String& fileToDownload); +ReleaseInfo getLatestRelease(const String& fileToDownload); bool getIsOTAUpdating(); void updateWebUi(String latestRelease, int command); -String downloadSHA256(const String& filename); \ No newline at end of file +String downloadSHA256(const String& filename); + diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index dba240c..68a64b6 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -69,9 +70,9 @@ const int usPerMinute = 60 * usPerSecond; extern const char *github_root_ca; -const PROGMEM char UPDATE_FIRMWARE = 0; -const PROGMEM char UPDATE_WEBUI = 1; - +const PROGMEM char UPDATE_FIRMWARE = U_FLASH; +const PROGMEM char UPDATE_WEBUI = U_SPIFFS; +const PROGMEM char UPDATE_ALL = 99; struct ScreenMapping { int value; diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index a789e75..3d8b6da 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -86,8 +86,7 @@ void setupWebserver() { server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); - // server.on("/update/webui", HTTP_GET, onUpdateWebUi); - // server.on("/update/firmware", HTTP_GET, onUpdateFirmware); + server.on("/api/firmware/auto_update", HTTP_GET, onAutoUpdateFirmware); } server.on("/api/restart", HTTP_GET, onApiRestart); @@ -142,29 +141,16 @@ void onFirmwareUpdate(AsyncWebServerRequest *request) request->send(response); } -void onUpdateWebUi(AsyncWebServerRequest *request) +void onAutoUpdateFirmware(AsyncWebServerRequest *request) { - UpdateMessage msg = {UPDATE_WEBUI}; + UpdateMessage msg = {UPDATE_ALL}; if (xQueueSend(otaQueue, &msg, 0) == pdTRUE) { - request->send(200, "text/plain", "WebUI update triggered"); + request->send(200, "application/json", "{\"msg\":\"Firmware update triggered\"}"); } else { - request->send(503, "text/plain", "Update already in progress"); - } -} - -void onUpdateFirmware(AsyncWebServerRequest *request) -{ - UpdateMessage msg = {UPDATE_FIRMWARE}; - if (xQueueSend(otaQueue, &msg, 0) == pdTRUE) - { - request->send(200, "text/plain", "Firmware update triggered"); - } - else - { - request->send(503, "text/plain", "Update already in progress"); + request->send(503,"application/json", "{\"msg\":\"Update already in progress\"}"); } } diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 9f47d02..8c49011 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -26,10 +26,6 @@ bool processEpdColorSettings(AsyncWebServerRequest *request); void onApiStatus(AsyncWebServerRequest *request); void onApiSystemStatus(AsyncWebServerRequest *request); void onApiSetWifiTxPower(AsyncWebServerRequest *request); -void onUpdateWebUi(AsyncWebServerRequest *request); -void onUpdateFirmware(AsyncWebServerRequest *request); - - void onApiScreenNext(AsyncWebServerRequest *request); void onApiScreenPrevious(AsyncWebServerRequest *request); @@ -58,6 +54,7 @@ void onFirmwareUpdate(AsyncWebServerRequest *request); void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, int command); void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void onAutoUpdateFirmware(AsyncWebServerRequest *request); void onIndex(AsyncWebServerRequest *request); void onNotFound(AsyncWebServerRequest *request); From 18bac7dcc7edbcb417a17815f28c342aef0efb14 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 12 Sep 2024 02:18:44 +0200 Subject: [PATCH 076/188] Remove old settings from sdkconfig and add optimizations --- dependencies.lock | 2 +- sdkconfig.defaults | 13 +++---------- src/lib/led_handler.cpp | 4 ++-- src/lib/ota.cpp | 2 ++ 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index f47f33d..2cb885d 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: 3117ab97df715ceaa7b9cacfc3cc7071408d26f122740eea130b5286dc5cd988 +manifest_hash: 841ba2a95f4b32d39636bf4fdf07c48a2052f71c9728a5b7f263083a3b430a4f target: esp32s3 version: 1.0.0 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index ffb944e..ab761ec 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -16,18 +16,14 @@ CONFIG_HEAP_POISONING_LIGHT=y CONFIG_ESP32S3_SPIRAM_SUPPORT=y CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y CONFIG_BOOTLOADER_LOG_LEVEL=0 -CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y CONFIG_ESP32S3_SPIRAM_SUPPORT=y CONFIG_LOG_DEFAULT_LEVEL_NONE=y CONFIG_LOG_DEFAULT_LEVEL=0 CONFIG_LOG_MAXIMUM_LEVEL=0 -CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y -CONFIG_LOG_BOOTLOADER_LEVEL=0 -CONFIG_CXX_EXCEPTIONS=y CONFIG_COMPILER_CXX_EXCEPTIONS=y #CONFIG_BOOTLOADER_WDT_ENABLE=n -#CONFIG_TASK_WDT=n +#CONFIG_ESP_TASK_WDT=n #Required for BTClock #CONFIG_SPIRAM_MODE_OCT=y @@ -42,14 +38,11 @@ CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=12 CONFIG_ESP32_WIFI_RX_BA_WIN=6 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y CONFIG_RTC_CLK_CAL_CYCLES=576 CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_SPIRAM_CACHE_WORKAROUND=y -CONFIG_COMPILER_OPTIMIZATION_PERF=y -CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y -CONFIG_MBEDTLS_SSL_SERVER_VERIFY=n -CONFIG_MBEDTLS_SSL_VERIFY_CLIENT_CERTIFICATE=n \ No newline at end of file +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_NEWLIB_NANO_FORMAT=y \ No newline at end of file diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 1b6756e..8dccf82 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -323,6 +323,8 @@ void ledTask(void *parameter) break; } case LED_FLASH_UPDATE: + blinkDelayTwoColor(250, 3, pixels.Color(0, 230, 0), + pixels.Color(230, 230, 0)); break; case LED_FLASH_BLOCK_NOTIFY: { @@ -574,7 +576,6 @@ void saveLedState() { int pixelColor = pixels.getPixelColor(i); char key[12]; - snprintf(key, 12, "%s%d", "ledColor_", i); preferences.putUInt(key, pixelColor); } @@ -586,7 +587,6 @@ void restoreLedState() for (int i = 0; i < pixels.numPixels(); i++) { char key[12]; - snprintf(key, 12, "%s%d", "ledColor_", i); uint pixelColor = preferences.getUInt(key, pixels.Color(0, 0, 0)); pixels.setPixelColor(i, pixelColor); } diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 3a391ca..d4b11a6 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -84,7 +84,9 @@ void handleOTATask(void *parameter) if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE) { if (msg.updateType == UPDATE_ALL) { + queueLedEffect(LED_FLASH_UPDATE); int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI); + queueLedEffect(LED_FLASH_UPDATE); int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE); if (resultWebUi == 0 && resultFw == 0) { From 630943ec5482d2c1137462640d51cf6c23c18f46 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 12 Sep 2024 02:22:29 +0200 Subject: [PATCH 077/188] Bring back snprintf for ledstate --- src/lib/led_handler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 8dccf82..a40097b 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -576,6 +576,7 @@ void saveLedState() { int pixelColor = pixels.getPixelColor(i); char key[12]; + snprintf(key, 12, "%s%d", "ledColor_", i); preferences.putUInt(key, pixelColor); } @@ -587,6 +588,7 @@ void restoreLedState() for (int i = 0; i < pixels.numPixels(); i++) { char key[12]; + snprintf(key, 12, "%s%d", "ledColor_", i); uint pixelColor = preferences.getUInt(key, pixels.Color(0, 0, 0)); pixels.setPixelColor(i, pixelColor); } From 5dd47c227529bc05b4b756e334d78db310d380eb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 16 Sep 2024 21:50:28 +0200 Subject: [PATCH 078/188] Fixes for Rev. B black PCB --- data | 2 +- sdkconfig.defaults | 2 +- src/fonts/fonts.hpp | 2 +- src/lib/config.cpp | 23 ++++++++++++++++++++++- src/lib/config.hpp | 2 +- src/lib/led_handler.cpp | 10 +++++----- src/lib/nostr_notify.cpp | 7 +++++-- src/lib/webserver.cpp | 2 +- 8 files changed, 37 insertions(+), 13 deletions(-) diff --git a/data b/data index 6c40b54..761c7f2 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 6c40b54273b7f7c7d6c2624d3c2a066435f27756 +Subproject commit 761c7f2991d347e97e77470ea3bf5511d7a7e507 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index ab761ec..59b2ec3 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -44,5 +44,5 @@ CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_SPIRAM_CACHE_WORKAROUND=y -CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_NEWLIB_NANO_FORMAT=y \ No newline at end of file diff --git a/src/fonts/fonts.hpp b/src/fonts/fonts.hpp index 1c12d49..099c4e0 100644 --- a/src/fonts/fonts.hpp +++ b/src/fonts/fonts.hpp @@ -1,7 +1,7 @@ #pragma once #include "antonio-semibold20.h" -#include "antonio-semibold30.h" +//#include "antonio-semibold30.h" #include "antonio-semibold40.h" #include "antonio-semibold90.h" #include "sats-symbol.h" diff --git a/src/lib/config.cpp b/src/lib/config.cpp index c295b93..cc50719 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -58,6 +58,9 @@ void setup() { preferences.clear(); queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); + nvs_flash_erase(); + delay(1000); + ESP.restart(); } } @@ -229,6 +232,9 @@ void setupWifi() // esp_task_wdt_deinit(); // esp_task_wdt_reset(); } + + + setFgColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); setBgColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); } @@ -274,6 +280,16 @@ void setupPreferences() else setCurrentCurrency(CURRENCY_USD); + if (!preferences.isKey("flDisable")) { + preferences.putBool("flDisable", isWhiteVersion() ? false : true); + } + + if (!preferences.isKey("fgColor")) { + preferences.putUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE); + preferences.putUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK); + } + + addScreenMapping(SCREEN_BLOCK_HEIGHT, "Block Height"); addScreenMapping(SCREEN_TIME, "Time"); @@ -455,6 +471,10 @@ void setupHardware() Serial.println(F("An Error has occurred while mounting LittleFS")); } + if (HW_REV == "REV_B_EPD_2_13" && !isWhiteVersion()) { + Serial.println(F("Black Rev B")); + } + if (!LittleFS.open("/index.html.gz", "r")) { Serial.println(F("Error loading WebUI")); @@ -509,7 +529,7 @@ void setupHardware() } #ifdef IS_HW_REV_B - pinMode(39, INPUT_PULLUP); + pinMode(39, INPUT_PULLDOWN); #endif @@ -690,6 +710,7 @@ String getHwRev() bool isWhiteVersion() { #ifdef IS_HW_REV_B + pinMode(39, INPUT_PULLDOWN); return digitalRead(39); #else return false; diff --git a/src/lib/config.hpp b/src/lib/config.hpp index e8fcffa..8c9da90 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -7,7 +7,7 @@ #include #include #include - +#include #include #include "lib/block_notify.hpp" diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index a40097b..4f882e0 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -13,7 +13,7 @@ bool flInTransition = false; void frontlightFlash(int flDelayTime) { - if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + if (preferences.getBool("flDisable")) return; if (frontlightOn) @@ -68,7 +68,7 @@ void frontlightFadeInAll(int flDelayTime) void frontlightFadeInAll(int flDelayTime, bool staggered) { - if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + if (preferences.getBool("flDisable")) return; if (frontlightIsOn()) return; @@ -120,7 +120,7 @@ void frontlightFadeOutAll(int flDelayTime) void frontlightFadeOutAll(int flDelayTime, bool staggered) { - if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + if (preferences.getBool("flDisable")) return; if (!frontlightIsOn()) return; @@ -186,7 +186,7 @@ bool frontlightIsOn() void frontlightFadeIn(uint num, int flDelayTime) { - if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + if (preferences.getBool("flDisable")) return; for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { @@ -197,7 +197,7 @@ void frontlightFadeIn(uint num, int flDelayTime) void frontlightFadeOut(uint num, int flDelayTime) { - if (preferences.getBool("flDisable", DEFAULT_DISABLE_FL)) + if (preferences.getBool("flDisable")) return; if (!frontlightIsOn()) return; diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index f9ac019..2e044bb 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -76,7 +76,8 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) }else if(status==nostr::ConnectionStatus::ERROR){ sstatus = "ERROR"; } - Serial.println("[ Nostr ] Connection status changed: " + sstatus); }); + //Serial.println("[ Nostr ] Connection status changed: " + sstatus); + }); } } catch (const std::exception &e) @@ -204,9 +205,11 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) if (strcmp(key, "bolt11") == 0) { - Serial.println(F("Got a zap")); + Serial.print(F("Got a zap of ")); int64_t satsAmount = getAmountInSatoshis(std::string(value)); + Serial.print(satsAmount); + Serial.println(F(" sats")); std::array textEpdContent = parseZapNotify(satsAmount, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 3d8b6da..801c286 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -705,7 +705,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; - root["flDisable"] = preferences.getBool("flDisable", DEFAULT_DISABLE_FL); + root["flDisable"] = preferences.getBool("flDisable"); root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON); root["flEffectDelay"] = preferences.getUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); From ff50acf913da8d8220ea79e110415dd6d18c521d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Sep 2024 02:00:10 +0200 Subject: [PATCH 079/188] Fix WebUI currency converter, reorganize some code --- data | 2 +- src/lib/block_notify.hpp | 1 + src/lib/button_handler.hpp | 1 + src/lib/config.cpp | 4 +- src/lib/nostr_notify.hpp | 1 + src/lib/ota.hpp | 1 + src/lib/screen_handler.cpp | 92 +------------------ src/lib/screen_handler.hpp | 12 +-- src/lib/timers.cpp | 86 ++++++++++++++++++ src/lib/timers.hpp | 22 +++++ src/lib/v2_notify.cpp | 180 ++++++++++++++++++++----------------- src/lib/v2_notify.hpp | 16 ++-- src/lib/webserver.cpp | 3 +- src/main.cpp | 2 +- 14 files changed, 228 insertions(+), 195 deletions(-) create mode 100644 src/lib/timers.cpp create mode 100644 src/lib/timers.hpp diff --git a/data b/data index 761c7f2..7d82b1e 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 761c7f2991d347e97e77470ea3bf5511d7a7e507 +Subproject commit 7d82b1e1a9014e80f725478333774f4d53e22134 diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index 0af7196..d5565eb 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -11,6 +11,7 @@ #include "lib/led_handler.hpp" #include "lib/screen_handler.hpp" +#include "lib/timers.hpp" #include "lib/shared.hpp" // using namespace websockets; diff --git a/src/lib/button_handler.hpp b/src/lib/button_handler.hpp index 03e1b36..9f28d33 100644 --- a/src/lib/button_handler.hpp +++ b/src/lib/button_handler.hpp @@ -4,6 +4,7 @@ #include "lib/screen_handler.hpp" #include "lib/shared.hpp" +#include "lib/timers.hpp" extern TaskHandle_t buttonTaskHandle; diff --git a/src/lib/config.cpp b/src/lib/config.cpp index cc50719..2898a31 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -404,7 +404,7 @@ void setupWebsocketClients(void *pvParameters) { if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) { - setupV2Notify(); + V2Notify::setupV2Notify(); } else { @@ -553,7 +553,7 @@ void setupHardware() { Serial.println(F("Found BH1750")); hasLuxSensor = true; - bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C); + bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C); } else { diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp index d64e3e9..045574d 100644 --- a/src/lib/nostr_notify.hpp +++ b/src/lib/nostr_notify.hpp @@ -12,6 +12,7 @@ #include "price_notify.hpp" #include "block_notify.hpp" +#include "lib/timers.hpp" void setupNostrNotify(bool asDatasource, bool zapNotify); diff --git a/src/lib/ota.hpp b/src/lib/ota.hpp index 516f9a1..44704f6 100644 --- a/src/lib/ota.hpp +++ b/src/lib/ota.hpp @@ -5,6 +5,7 @@ #include "lib/config.hpp" #include "lib/shared.hpp" +#include "lib/timers.hpp" #ifndef UPDATE_MESSAGE_HPP #define UPDATE_MESSAGE_HPP diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 091a6e0..d1c6165 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -5,8 +5,7 @@ // TaskHandle_t timeUpdateTaskHandle; TaskHandle_t taskScreenRotateTaskHandle; TaskHandle_t workerTaskHandle; -esp_timer_handle_t screenRotateTimer; -esp_timer_handle_t minuteTimer; + std::array taskEpdContent = {}; std::string priceString; @@ -23,8 +22,6 @@ void workerTask(void *pvParameters) { while (1) { // Wait for a work item to be available in the queue if (xQueueReceive(workQueue, &receivedItem, portMAX_DELAY)) { - uint firstIndex = 0; - // Process the work item based on its type switch (receivedItem.type) { case TASK_BITAXE_UPDATE: { @@ -42,10 +39,7 @@ void workerTask(void *pvParameters) { case TASK_PRICE_UPDATE: { uint currency = getCurrentCurrency(); uint price = getPrice(currency); - // u_char priceSymbol = '$'; - // if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) { - // priceSymbol = '['; - // } + if (getCurrentScreen() == SCREEN_BTC_TICKER) { taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE)); } else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) { @@ -121,29 +115,6 @@ void taskScreenRotate(void *pvParameters) { } } -void IRAM_ATTR minuteTimerISR(void *arg) { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - // vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken); - WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; - xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken); - - if (bitaxeFetchTaskHandle != NULL) { - vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); - } - - if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); - } -} - -void IRAM_ATTR screenRotateTimerISR(void *arg) { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - vTaskNotifyGiveFromISR(taskScreenRotateTaskHandle, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); - } -} - void setupTasks() { workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem)); @@ -159,65 +130,6 @@ void setupTasks() { setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } -void setupTimeUpdateTimer(void *pvParameters) { - const esp_timer_create_args_t minuteTimerConfig = { - .callback = &minuteTimerISR, .name = "minute_timer"}; - - esp_timer_create(&minuteTimerConfig, &minuteTimer); - - time_t currentTime; - struct tm timeinfo; - time(¤tTime); - localtime_r(¤tTime, &timeinfo); - uint32_t secondsUntilNextMinute = 60 - timeinfo.tm_sec; - - if (secondsUntilNextMinute > 0) - vTaskDelay(pdMS_TO_TICKS((secondsUntilNextMinute * 1000))); - - esp_timer_start_periodic(minuteTimer, usPerMinute); - - WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; - xQueueSend(workQueue, &timeUpdate, portMAX_DELAY); - // xTaskNotifyGive(timeUpdateTaskHandle); - - vTaskDelete(NULL); -} - -void setupScreenRotateTimer(void *pvParameters) { - const esp_timer_create_args_t screenRotateTimerConfig = { - .callback = &screenRotateTimerISR, .name = "screen_rotate_timer"}; - - esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer); - - if (preferences.getBool("timerActive", DEFAULT_TIMER_ACTIVE)) { - esp_timer_start_periodic(screenRotateTimer, - getTimerSeconds() * usPerSecond); - } - - vTaskDelete(NULL); -} - -uint getTimerSeconds() { return preferences.getUInt("timerSeconds", DEFAULT_TIMER_SECONDS); } - -bool isTimerActive() { return esp_timer_is_active(screenRotateTimer); } - -void setTimerActive(bool status) { - if (status) { - esp_timer_start_periodic(screenRotateTimer, - getTimerSeconds() * usPerSecond); - queueLedEffect(LED_EFFECT_START_TIMER); - preferences.putBool("timerActive", true); - } else { - esp_timer_stop(screenRotateTimer); - queueLedEffect(LED_EFFECT_PAUSE_TIMER); - preferences.putBool("timerActive", false); - } - - if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); -} - -void toggleTimerActive() { setTimerActive(!isTimerActive()); } - uint getCurrentScreen() { return currentScreen; } void setCurrentScreen(uint newScreen) { diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index 91e824a..6c99a7c 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -16,9 +16,6 @@ extern TaskHandle_t workerTaskHandle; extern TaskHandle_t taskScreenRotateTaskHandle; -extern esp_timer_handle_t screenRotateTimer; -extern esp_timer_handle_t minuteTimer; - extern QueueHandle_t workQueue; typedef enum { @@ -42,21 +39,14 @@ void previousScreen(); void showSystemStatusScreen(); -void setupTimeUpdateTimer(void *pvParameters); -void setupScreenRotateTimer(void *pvParameters); -void IRAM_ATTR minuteTimerISR(void *arg); -void IRAM_ATTR screenRotateTimerISR(void *arg); // void taskPriceUpdate(void *pvParameters); // void taskBlockUpdate(void *pvParameters); // void taskTimeUpdate(void *pvParameters); void taskScreenRotate(void *pvParameters); -uint getTimerSeconds(); -bool isTimerActive(); -void setTimerActive(bool status); -void toggleTimerActive(); + void setupTasks(); void setCurrentCurrency(char currency); diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp new file mode 100644 index 0000000..d84d358 --- /dev/null +++ b/src/lib/timers.cpp @@ -0,0 +1,86 @@ +#include "timers.hpp" + +esp_timer_handle_t screenRotateTimer; +esp_timer_handle_t minuteTimer; + +void setupTimeUpdateTimer(void *pvParameters) { + const esp_timer_create_args_t minuteTimerConfig = { + .callback = &minuteTimerISR, .name = "minute_timer"}; + + esp_timer_create(&minuteTimerConfig, &minuteTimer); + + time_t currentTime; + struct tm timeinfo; + time(¤tTime); + localtime_r(¤tTime, &timeinfo); + uint32_t secondsUntilNextMinute = 60 - timeinfo.tm_sec; + + if (secondsUntilNextMinute > 0) + vTaskDelay(pdMS_TO_TICKS((secondsUntilNextMinute * 1000))); + + esp_timer_start_periodic(minuteTimer, usPerMinute); + + WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; + xQueueSend(workQueue, &timeUpdate, portMAX_DELAY); + // xTaskNotifyGive(timeUpdateTaskHandle); + + vTaskDelete(NULL); +} + +void setupScreenRotateTimer(void *pvParameters) { + const esp_timer_create_args_t screenRotateTimerConfig = { + .callback = &screenRotateTimerISR, .name = "screen_rotate_timer"}; + + esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer); + + if (preferences.getBool("timerActive", DEFAULT_TIMER_ACTIVE)) { + esp_timer_start_periodic(screenRotateTimer, + getTimerSeconds() * usPerSecond); + } + + vTaskDelete(NULL); +} + +uint getTimerSeconds() { return preferences.getUInt("timerSeconds", DEFAULT_TIMER_SECONDS); } + +bool isTimerActive() { return esp_timer_is_active(screenRotateTimer); } + +void setTimerActive(bool status) { + if (status) { + esp_timer_start_periodic(screenRotateTimer, + getTimerSeconds() * usPerSecond); + queueLedEffect(LED_EFFECT_START_TIMER); + preferences.putBool("timerActive", true); + } else { + esp_timer_stop(screenRotateTimer); + queueLedEffect(LED_EFFECT_PAUSE_TIMER); + preferences.putBool("timerActive", false); + } + + if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); +} + +void toggleTimerActive() { setTimerActive(!isTimerActive()); } + +void IRAM_ATTR minuteTimerISR(void *arg) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken); + WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; + xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken); + + if (bitaxeFetchTaskHandle != NULL) { + vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); + } + + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +void IRAM_ATTR screenRotateTimerISR(void *arg) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + vTaskNotifyGiveFromISR(taskScreenRotateTaskHandle, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} \ No newline at end of file diff --git a/src/lib/timers.hpp b/src/lib/timers.hpp new file mode 100644 index 0000000..e543a9f --- /dev/null +++ b/src/lib/timers.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#include "lib/shared.hpp" +#include "lib/screen_handler.hpp" + +extern esp_timer_handle_t screenRotateTimer; +extern esp_timer_handle_t minuteTimer; + +void setupTimeUpdateTimer(void *pvParameters); +void setupScreenRotateTimer(void *pvParameters); + +void IRAM_ATTR minuteTimerISR(void *arg); +void IRAM_ATTR screenRotateTimerISR(void *arg); + +uint getTimerSeconds(); +bool isTimerActive(); +void setTimerActive(bool status); +void toggleTimerActive(); \ No newline at end of file diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 13d302d..33104a5 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -1,41 +1,51 @@ #include "v2_notify.hpp" -WebSocketsClient webSocket; -TaskHandle_t v2NotifyTaskHandle; +using namespace V2Notify; -void setupV2Notify() +namespace V2Notify { - String hostname = "ws.btclock.dev"; - if ( preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) { - Serial.println(F("Connecting to V2 staging source")); - hostname = "ws-staging.btclock.dev"; - } else { - Serial.println(F("Connecting to V2 source")); + WebSocketsClient webSocket; + + TaskHandle_t v2NotifyTaskHandle; + + void setupV2Notify() + { + String hostname = "ws.btclock.dev"; + if (preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) + { + Serial.println(F("Connecting to V2 staging source")); + hostname = "ws-staging.btclock.dev"; + } + else + { + Serial.println(F("Connecting to V2 source")); + } + + webSocket.beginSSL(hostname, 443, "/api/v2/ws"); + webSocket.onEvent(V2Notify::onWebsocketV2Event); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); + + V2Notify::setupV2NotifyTask(); } - webSocket.beginSSL(hostname, 443, "/api/v2/ws"); - webSocket.onEvent(onWebsocketV2Event); - webSocket.setReconnectInterval(5000); - webSocket.enableHeartbeat(15000, 3000, 2); - - setupV2NotifyTask(); -} - -void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) { - switch(type) { - case WStype_DISCONNECTED: - Serial.printf("[WSc] Disconnected!\n"); - break; - case WStype_CONNECTED: + void onWebsocketV2Event(WStype_t type, uint8_t *payload, size_t length) + { + switch (type) { - Serial.printf("[WSc] Connected to url: %s\n", payload); + case WStype_DISCONNECTED: + Serial.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + { + Serial.printf("[WSc] Connected to url: %s\n", payload); JsonDocument response; response["type"] = "subscribe"; response["eventType"] = "blockfee"; size_t responseLength = measureMsgPack(response); - uint8_t* buffer = new uint8_t[responseLength]; + uint8_t *buffer = new uint8_t[responseLength]; serializeMsgPack(response, buffer, responseLength); webSocket.sendBIN(buffer, responseLength); delete[] buffer; @@ -62,83 +72,89 @@ void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length) { { currenciesArray.add(str); } - -// response["currencies"] = currenciesArray; + + // response["currencies"] = currenciesArray; responseLength = measureMsgPack(response); buffer = new uint8_t[responseLength]; serializeMsgPack(response, buffer, responseLength); webSocket.sendBIN(buffer, responseLength); - break; + break; } - case WStype_TEXT: - Serial.printf("[WSc] get text: %s\n", payload); + case WStype_TEXT: + Serial.printf("[WSc] get text: %s\n", payload); - // send message to server - // webSocket.sendTXT("message here"); - break; - case WStype_BIN: + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: { JsonDocument doc; DeserializationError error = deserializeMsgPack(doc, payload, length); - handleV2Message(doc); - break; + V2Notify::handleV2Message(doc); + break; } - case WStype_ERROR: - case WStype_FRAGMENT_TEXT_START: - case WStype_FRAGMENT_BIN_START: - case WStype_FRAGMENT: + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: case WStype_PING: case WStype_PONG: - case WStype_FRAGMENT_FIN: - break; - } -} - -void handleV2Message(JsonDocument doc) { - if (doc.containsKey("blockheight")) - { - uint newBlockHeight = doc["blockheight"].as(); - - if (newBlockHeight == getBlockHeight()) { - return; + case WStype_FRAGMENT_FIN: + break; + } } - processNewBlock(newBlockHeight); - } - else if (doc.containsKey("blockfee")) - { - uint medianFee = doc["blockfee"].as(); + void handleV2Message(JsonDocument doc) + { + if (doc.containsKey("blockheight")) + { + uint newBlockHeight = doc["blockheight"].as(); - processNewBlockFee(medianFee); - } else if (doc.containsKey("price")) - { + if (newBlockHeight == getBlockHeight()) + { + return; + } - // Iterate through the key-value pairs of the "price" object - for (JsonPair kv : doc["price"].as()) { - const char* currency = kv.key().c_str(); - uint newPrice = kv.value().as(); - - processNewPrice(newPrice, getCurrencyChar(currency)); + processNewBlock(newBlockHeight); + } + else if (doc.containsKey("blockfee")) + { + uint medianFee = doc["blockfee"].as(); + processNewBlockFee(medianFee); + } + else if (doc.containsKey("price")) + { + + // Iterate through the key-value pairs of the "price" object + for (JsonPair kv : doc["price"].as()) + { + const char *currency = kv.key().c_str(); + uint newPrice = kv.value().as(); + + processNewPrice(newPrice, getCurrencyChar(currency)); + } + } } - } -} -void taskV2Notify(void *pvParameters) { - for(;;) { - webSocket.loop(); - vTaskDelay(10 / portTICK_PERIOD_MS); + void taskV2Notify(void *pvParameters) + { + for (;;) + { + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); + } } -} -void setupV2NotifyTask() { - xTaskCreate(taskV2Notify, "v2Notify", (6 * 1024), NULL, tskIDLE_PRIORITY, - &v2NotifyTaskHandle); + void setupV2NotifyTask() + { + xTaskCreate(V2Notify::taskV2Notify, "v2Notify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &V2Notify::v2NotifyTaskHandle); + } -} - -bool isV2NotifyConnected() -{ - return webSocket.isConnected(); -} + bool isV2NotifyConnected() + { + return webSocket.isConnected(); + } +} \ No newline at end of file diff --git a/src/lib/v2_notify.hpp b/src/lib/v2_notify.hpp index d3e4907..21a74ba 100644 --- a/src/lib/v2_notify.hpp +++ b/src/lib/v2_notify.hpp @@ -8,16 +8,18 @@ #include "lib/screen_handler.hpp" -extern TaskHandle_t v2NotifyTaskHandle; +namespace V2Notify { + extern TaskHandle_t v2NotifyTaskHandle; -void setupV2NotifyTask(); -void taskV2Notify(void *pvParameters); + void setupV2NotifyTask(); + void taskV2Notify(void *pvParameters); -void setupV2Notify(); -void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length); -void handleV2Message(JsonDocument doc); + void setupV2Notify(); + void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length); + void handleV2Message(JsonDocument doc); -bool isV2NotifyConnected(); + bool isV2NotifyConnected(); +} // void stopV2Notify(); // void restartV2Notify(); // bool getPriceNotifyInit(); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 801c286..88c40e4 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -256,9 +256,10 @@ JsonDocument getStatusObject() // root["espPsramSize"] = ESP.getPsramSize(); JsonObject conStatus = root["connectionStatus"].to(); + conStatus["price"] = isPriceNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected(); - conStatus["V2"] = isV2NotifyConnected(); + conStatus["V2"] = V2Notify::isV2NotifyConnected(); conStatus["nostr"] = nostrConnected(); diff --git a/src/main.cpp b/src/main.cpp index 0839e9d..908f155 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,7 +51,7 @@ extern "C" void app_main() if (hasLightLevel()) { if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { - if (hasLightLevel() && getLightLevel() == 0) + if (hasLightLevel() && getLightLevel() <= 2) { if (frontlightIsOn()) { frontlightFadeOutAll(); From 41bf2480ce8923a15883714bb4536991023a86ad Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Sep 2024 02:40:24 +0200 Subject: [PATCH 080/188] Dependency updates and fix URL rewrites --- platformio.ini | 4 ++-- src/lib/webserver.cpp | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 14799cf..13b8e5f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd [btclock_base] -platform = espressif32 @ ^6.6.0 +platform = espressif32 @ ^6.8.1 framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize @@ -33,7 +33,7 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.1.0 - mathieucarbou/ESPAsyncWebServer @ 3.2.0 + mathieucarbou/ESPAsyncWebServer @ 3.3.1 adafruit/Adafruit BusIO@^1.16.1 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.3 diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 88c40e4..738f4f5 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -20,6 +20,10 @@ void setupWebserver() AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); + + server.rewrite("/convert", "/"); + server.rewrite("/api", "/"); + if (preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED)) { staticHandler.setAuthentication( From ff0d8f5a0aa29224ee2f322178c5481301571479 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Sep 2024 15:58:07 +0200 Subject: [PATCH 081/188] Make BTClock v8 board working again --- .github/workflows/tagging.yml | 52 ++++++++++++++++++------ boards/{btclock.json => btclock_v8.json} | 6 +-- data | 2 +- partition_16mb.csv | 2 +- platformio.ini | 16 ++++++-- src/lib/button_handler.cpp | 20 +++++++-- src/lib/config.cpp | 14 +++---- src/lib/epd.cpp | 15 ++++--- src/lib/shared.hpp | 2 +- 9 files changed, 91 insertions(+), 38 deletions(-) rename boards/{btclock.json => btclock_v8.json} (92%) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 825d100..13e2a3c 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -1,9 +1,9 @@ name: BTClock CI -on: +on: push: tags: - - '*' + - "*" jobs: build: @@ -40,10 +40,14 @@ jobs: version: esp32s3 - name: btclock_rev_b version: esp32s3 + - name: btclock_v8 + version: esp32s3 epd_variant: [213epd, 29epd] exclude: - - chip: btclock_rev_b - epd_variant: 29epd + - chip: btclock_rev_b + epd_variant: 29epd + - chip: btclock_v8 + epd_variant: 29epd steps: - uses: actions/download-artifact@v4 with: @@ -51,13 +55,37 @@ jobs: path: .pio - name: Install esptools.py run: pip install --upgrade esptool - + # - name: Create merged firmware binary + # run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin - name: Create merged firmware binary - run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin + run: | + if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then + mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ + esptool.py --chip ${{ matrix.chip.version }} merge_bin \ + -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ + --flash_mode qio \ + 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \ + 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ + 0xe000 .pio/boot_app0.bin \ + 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ + 0x810000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin; + else + # Original command for other cases + mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ + esptool.py --chip ${{ matrix.chip.version }} merge_bin \ + -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ + --flash_mode dio \ + 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \ + 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ + 0xe000 .pio/boot_app0.bin \ + 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ + 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin + # Adjust the offset for littlefs or other files as needed for the original case + fi - name: Create checksum for firmware run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin.sha256 - + - name: Create checksum for merged binary run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256 @@ -91,10 +119,10 @@ jobs: merge-multiple: false - name: Write commit hash to file run: echo $GITHUB_SHA > commit.txt - + - name: Write build date to file run: echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" > date.txt - + - name: Create release uses: ncipollo/release-action@v1 with: @@ -117,8 +145,8 @@ jobs: with: source-directory: . target-directory: firmware_v3/ - destination-github-username: 'btclock' - destination-repository-name: 'web-flasher' + destination-github-username: "btclock" + destination-repository-name: "web-flasher" target-branch: main user-name: ${{github.actor}} - user-email: ${{github.actor}}@users.noreply.github.com \ No newline at end of file + user-email: ${{github.actor}}@users.noreply.github.com diff --git a/boards/btclock.json b/boards/btclock_v8.json similarity index 92% rename from boards/btclock.json rename to boards/btclock_v8.json index bee4064..b30bd19 100644 --- a/boards/btclock.json +++ b/boards/btclock_v8.json @@ -10,7 +10,7 @@ "-DBOARD_HAS_PSRAM", "-DARDUINO_BTCLOCK", "-DARDUINO_ESP32S3_DEV", - "-DIS_BTCLOCK_S3", + "-DIS_BTCLOCK_V8", "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1", @@ -20,8 +20,8 @@ "f_flash": "80000000L", "flash_mode": "qio", "psram_type": "opi", - "espidf": { - "sdkconfig_path": "boards" + "esp-idf": { + "sdkconfig_path": "boards/sdkconfig.btclock_v8" }, "hwids": [ [ diff --git a/data b/data index 7d82b1e..95aa9d6 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 7d82b1e1a9014e80f725478333774f4d53e22134 +Subproject commit 95aa9d67d17dde32dab3216810e7b4e4086feb03 diff --git a/partition_16mb.csv b/partition_16mb.csv index 80df02b..a32b7ca 100644 --- a/partition_16mb.csv +++ b/partition_16mb.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 36K, 20K, otadata, data, ota, 56K, 8K, app0, app, ota_0, 64K, 4096K, app1, app, ota_1, , 4096K, -spiffs, data, spiffs, , 3072K, +spiffs, data, spiffs, , 400K, coredump, data, coredump,, 64K, diff --git a/platformio.ini b/platformio.ini index 13b8e5f..30e03ec 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] data_dir = data/build_gz -default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd +default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd, btclock_v8_213epd [env] @@ -120,10 +120,11 @@ build_flags = -D VERSION_EPD_2_9 -D HW_REV=\"REV_B_EPD_2_9\" -[env:btclock_s3] +[env:btclock_v8] extends = btclock_base -board = btclock +board = btclock_v8 board_build.partitions = partition_16mb.csv +board_build.flash_mode = qio test_framework = unity build_flags = ${btclock_base.build_flags} @@ -145,6 +146,15 @@ build_flags = build_unflags = ${btclock_base.build_unflags} +[env:btclock_v8_213epd] +extends = env:btclock_v8 +test_framework = unity +build_flags = + ${env:btclock_v8.build_flags} + -D USE_QR + -D VERSION_EPD_2_13 + -D HW_REV=\"REV_V8_EPD_2_13\" + [env:native_test_only] platform = native test_framework = unity diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index 7b27a64..437090d 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -4,6 +4,18 @@ TaskHandle_t buttonTaskHandle = NULL; const TickType_t debounceDelay = pdMS_TO_TICKS(50); TickType_t lastDebounceTime = 0; +#ifdef IS_BTCLOCK_V8 +#define BTN_1 0 +#define BTN_2 1 +#define BTN_3 2 +#define BTN_4 3 +#else +#define BTN_1 3 +#define BTN_2 2 +#define BTN_3 1 +#define BTN_4 0 +#endif + void buttonTask(void *parameter) { while (1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); @@ -17,16 +29,16 @@ void buttonTask(void *parameter) { uint pin = mcp1.getLastInterruptPin(); switch (pin) { - case 3: + case BTN_1: toggleTimerActive(); break; - case 2: + case BTN_2: nextScreen(); break; - case 1: + case BTN_3: previousScreen(); break; - case 0: + case BTN_4: showSystemStatusScreen(); break; } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 2898a31..17805c0 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -4,7 +4,7 @@ Preferences preferences; Adafruit_MCP23X17 mcp1; -#ifdef IS_BTCLOCK_S3 +#ifdef IS_BTCLOCK_V8 Adafruit_MCP23X17 mcp2; #endif @@ -439,12 +439,12 @@ std::vector getScreenNameMap() { return screenMappings; } void setupMcp() { -#ifdef IS_BTCLOCK_S3 +#ifdef IS_BTCLOCK_V8 const int mcp1AddrPins[] = {MCP1_A0_PIN, MCP1_A1_PIN, MCP1_A2_PIN}; const int mcp1AddrValues[] = {LOW, LOW, LOW}; const int mcp2AddrPins[] = {MCP2_A0_PIN, MCP2_A1_PIN, MCP2_A2_PIN}; - const int mcp2AddrValues[] = {LOW, LOW, HIGH}; + const int mcp2AddrValues[] = {HIGH, LOW, LOW}; pinMode(MCP_RESET_PIN, OUTPUT); digitalWrite(MCP_RESET_PIN, HIGH); @@ -505,7 +505,7 @@ void setupHardware() if (!mcp1.begin_I2C(0x20)) { - Serial.println(F("Error MCP23017")); + Serial.println(F("Error MCP23017 1")); // while (1) // ; @@ -520,7 +520,7 @@ void setupHardware() mcp1.pinMode(i, INPUT_PULLUP); mcp1.setupInterruptPin(i, LOW); } -#ifndef IS_BTCLOCK_S3 +#ifndef IS_BTCLOCK_V8 for (int i = 8; i <= 14; i++) { mcp1.pinMode(i, OUTPUT); @@ -533,10 +533,10 @@ void setupHardware() #endif -#ifdef IS_BTCLOCK_S3 +#ifdef IS_BTCLOCK_V8 if (!mcp2.begin_I2C(0x21)) { - Serial.println(F("Error MCP23017")); + Serial.println(F("Error MCP23017 2")); // while (1) // ; diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index cc44c7e..7688fc8 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -30,7 +30,7 @@ MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { }; Native_Pin EPD_DC = Native_Pin(14); -#elif IS_BTCLOCK_S3 +#elif IS_BTCLOCK_V8 Native_Pin EPD_DC = Native_Pin(38); MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { @@ -104,8 +104,8 @@ GxEPD2_BW displays[NUM_SCREENS] = { EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]), EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]), EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]), -#ifdef IS_BTCLOCK_S3 - EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[7]), +#ifdef IS_BTCLOCK_V8 + EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[7], &EPD_BUSY[7]), #endif }; @@ -170,7 +170,7 @@ void setupDisplays() updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 2048, NULL, 11, NULL); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 4096, NULL, 11, NULL); for (uint i = 0; i < NUM_SCREENS; i++) { @@ -180,7 +180,7 @@ void setupDisplays() int *taskParam = new int; *taskParam = i; - xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 2048, taskParam, + xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 4096, taskParam, 11, &tasks[i]); // create task } @@ -194,8 +194,11 @@ void setupDisplays() } else { - + #ifdef IS_BTCLOCK_V8 + epdContent = {"B", "T", "C", "L", "O", "C", "K", "v8"}; + #else epdContent = {"B", "T", "C", "L", "O", "C", "K"}; + #endif } setEpdContent(epdContent); diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 68a64b6..a381c7f 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -17,7 +17,7 @@ #include "defaults.hpp" extern Adafruit_MCP23X17 mcp1; -#ifdef IS_BTCLOCK_S3 +#ifdef IS_BTCLOCK_V8 extern Adafruit_MCP23X17 mcp2; #endif extern Preferences preferences; From 8b72f2f6b300d09ab1bdf758269cd5b3fab19ddd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Sep 2024 18:25:13 +0200 Subject: [PATCH 082/188] Fix merged binary for v8 --- .github/workflows/tagging.yml | 8 +- boards/sdkconfig.btclock_v8 | 1598 +++++++++++++++++++++++++++++++++ 2 files changed, 1603 insertions(+), 3 deletions(-) create mode 100644 boards/sdkconfig.btclock_v8 diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 13e2a3c..2964234 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -63,10 +63,12 @@ jobs: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ esptool.py --chip ${{ matrix.chip.version }} merge_bin \ -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ - --flash_mode qio \ + --flash_mode dio \ + --flash_freq 80m \ + --flash_size 16MB \ 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \ 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ - 0xe000 .pio/boot_app0.bin \ + 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ 0x810000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin; else @@ -77,7 +79,7 @@ jobs: --flash_mode dio \ 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \ 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ - 0xe000 .pio/boot_app0.bin \ + 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin # Adjust the offset for littlefs or other files as needed for the original case diff --git a/boards/sdkconfig.btclock_v8 b/boards/sdkconfig.btclock_v8 new file mode 100644 index 0000000..0c3b34e --- /dev/null +++ b/boards/sdkconfig.btclock_v8 @@ -0,0 +1,1598 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) Project Configuration +# +CONFIG_IDF_CMAKE=y +CONFIG_IDF_TARGET_ARCH_XTENSA=y +CONFIG_IDF_TARGET="esp32s3" +CONFIG_IDF_TARGET_ESP32S3=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 + +# +# SDK tool configuration +# +CONFIG_SDK_TOOLPREFIX="xtensa-esp32s3-elf-" +# CONFIG_SDK_TOOLCHAIN_SUPPORTS_TIME_WIDE_64_BITS is not set +# end of SDK tool configuration + +# +# Build type +# +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +CONFIG_APP_BUILD_GENERATE_BINARIES=y +CONFIG_APP_BUILD_BOOTLOADER=y +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# end of Build type + +# +# Application manager +# +CONFIG_APP_COMPILE_TIME_DATE=y +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 +# end of Application manager + +# +# Bootloader config +# +CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0 +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_INFO is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +CONFIG_BOOTLOADER_LOG_LEVEL=0 + +# +# Serial Flash Configurations +# +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Serial Flash Configurations + +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# CONFIG_BOOTLOADER_APP_TEST is not set +CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y +CONFIG_BOOTLOADER_WDT_ENABLE=y +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +# end of Bootloader config + +# +# Security features +# +CONFIG_SECURE_BOOT_SUPPORTS_RSA=y +CONFIG_SECURE_TARGET_HAS_SECURE_ROM_DL_MODE=y +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# CONFIG_SECURE_BOOT is not set +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +# end of Security features + +# +# Boot ROM Behavior +# +CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y +# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set +# end of Boot ROM Behavior + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200 +# CONFIG_ESPTOOLPY_NO_STUB is not set +# CONFIG_ESPTOOLPY_OCT_FLASH is not set +# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set +# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_S3_STR=y +# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ="80m" +# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +# CONFIG_ESPTOOLPY_MONITOR_BAUD_CONSOLE is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_9600B is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_57600B is not set +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +# CONFIG_ESPTOOLPY_MONITOR_BAUD_230400B is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_2MB is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER is not set +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_SINGLE_APP=y +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Arduino Configuration +# +CONFIG_ARDUINO_VARIANT="esp32s3" +CONFIG_ENABLE_ARDUINO_DEPENDS=y +# CONFIG_AUTOSTART_ARDUINO is not set +# CONFIG_ARDUINO_RUN_CORE0 is not set +CONFIG_ARDUINO_RUN_CORE1=y +# CONFIG_ARDUINO_RUN_NO_AFFINITY is not set +CONFIG_ARDUINO_RUNNING_CORE=1 +CONFIG_ARDUINO_LOOP_STACK_SIZE=8192 +# CONFIG_ARDUINO_EVENT_RUN_CORE0 is not set +CONFIG_ARDUINO_EVENT_RUN_CORE1=y +# CONFIG_ARDUINO_EVENT_RUN_NO_AFFINITY is not set +CONFIG_ARDUINO_EVENT_RUNNING_CORE=1 +# CONFIG_ARDUINO_SERIAL_EVENT_RUN_CORE0 is not set +# CONFIG_ARDUINO_SERIAL_EVENT_RUN_CORE1 is not set +CONFIG_ARDUINO_SERIAL_EVENT_RUN_NO_AFFINITY=y +CONFIG_ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE=-1 +CONFIG_ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE=2048 +CONFIG_ARDUINO_SERIAL_EVENT_TASK_PRIORITY=24 +CONFIG_ARDUINO_UDP_RUN_CORE0=y +# CONFIG_ARDUINO_UDP_RUN_CORE1 is not set +# CONFIG_ARDUINO_UDP_RUN_NO_AFFINITY is not set +CONFIG_ARDUINO_UDP_RUNNING_CORE=0 +CONFIG_ARDUINO_UDP_TASK_PRIORITY=3 +# CONFIG_ARDUINO_ISR_IRAM is not set +# CONFIG_DISABLE_HAL_LOCKS is not set + +# +# Debug Log Configuration +# +# CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL_NONE is not set +CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL_ERROR=y +# CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL_WARN is not set +# CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL_INFO is not set +# CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_ARDUHAL_LOG_DEFAULT_LEVEL=1 +# CONFIG_ARDUHAL_LOG_COLORS is not set +# CONFIG_ARDUHAL_ESP_LOG is not set +# end of Debug Log Configuration + +CONFIG_ARDUHAL_PARTITION_SCHEME_DEFAULT=y +# CONFIG_ARDUHAL_PARTITION_SCHEME_MINIMAL is not set +# CONFIG_ARDUHAL_PARTITION_SCHEME_NO_OTA is not set +# CONFIG_ARDUHAL_PARTITION_SCHEME_HUGE_APP is not set +# CONFIG_ARDUHAL_PARTITION_SCHEME_MIN_SPIFFS is not set +CONFIG_ARDUHAL_PARTITION_SCHEME="default" +# CONFIG_ARDUINO_SELECTIVE_COMPILATION is not set +# end of Arduino Configuration + +# +# Compiler options +# +# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set +CONFIG_COMPILER_HIDE_PATHS_MACROS=y +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 +# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +# CONFIG_COMPILER_DISABLE_GCC8_WARNINGS is not set +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +# end of Compiler options + +# +# Component config +# + +# +# Application Level Tracing +# +# CONFIG_APPTRACE_DEST_JTAG is not set +CONFIG_APPTRACE_DEST_NONE=y +CONFIG_APPTRACE_LOCK_ENABLE=y +# end of Application Level Tracing + +# +# ESP-ASIO +# +# CONFIG_ASIO_SSL_SUPPORT is not set +# end of ESP-ASIO + +# +# Bluetooth +# +# CONFIG_BT_ENABLED is not set +# end of Bluetooth + +# +# CoAP Configuration +# +CONFIG_COAP_MBEDTLS_PSK=y +# CONFIG_COAP_MBEDTLS_PKI is not set +# CONFIG_COAP_MBEDTLS_DEBUG is not set +CONFIG_COAP_LOG_DEFAULT_LEVEL=0 +# end of CoAP Configuration + +# +# Driver configurations +# + +# +# ADC configuration +# +# CONFIG_ADC_FORCE_XPD_FSM is not set +CONFIG_ADC_DISABLE_DAC=y +# CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3 is not set +# end of ADC configuration + +# +# MCPWM configuration +# +# CONFIG_MCPWM_ISR_IN_IRAM is not set +# end of MCPWM configuration + +# +# SPI configuration +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of SPI configuration + +# +# TWAI configuration +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +# CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM is not set +# end of TWAI configuration + +# +# UART configuration +# +# CONFIG_UART_ISR_IN_IRAM is not set +# end of UART configuration + +# +# GDMA Configuration +# +# CONFIG_GDMA_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GDMA_ISR_IRAM_SAFE is not set +# end of GDMA Configuration +# end of Driver configurations + +# +# eFuse Bit Manager +# +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# CONFIG_EFUSE_VIRTUAL is not set +CONFIG_EFUSE_MAX_BLK_LEN=256 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +CONFIG_ESP_TLS_USING_MBEDTLS=y +CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +# end of ESP-TLS + +# +# ESP32S3-Specific +# +CONFIG_ESP32S3_REV_MIN_0=y +# CONFIG_ESP32S3_REV_MIN_1 is not set +# CONFIG_ESP32S3_REV_MIN_2 is not set +CONFIG_ESP32S3_REV_MIN_FULL=0 +CONFIG_ESP_REV_MIN_FULL=0 +CONFIG_ESP32S3_REV_MAX_FULL_STR_OPT=y +CONFIG_ESP32S3_REV_MAX_FULL=99 +CONFIG_ESP_REV_MAX_FULL=99 +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160 + +# +# Cache config +# +CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB=y +# CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x4000 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_4WAYS is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y +CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_16B is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_WRAP is not set +# CONFIG_ESP32S3_DATA_CACHE_16KB is not set +CONFIG_ESP32S3_DATA_CACHE_32KB=y +# CONFIG_ESP32S3_DATA_CACHE_64KB is not set +CONFIG_ESP32S3_DATA_CACHE_SIZE=0x8000 +# CONFIG_ESP32S3_DATA_CACHE_4WAYS is not set +CONFIG_ESP32S3_DATA_CACHE_8WAYS=y +CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_DATA_CACHE_LINE_16B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_32B=y +# CONFIG_ESP32S3_DATA_CACHE_LINE_64B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=32 +# CONFIG_ESP32S3_DATA_CACHE_WRAP is not set +# end of Cache config + +CONFIG_ESP32S3_SPIRAM_SUPPORT=y + +# +# SPI RAM config +# +# CONFIG_SPIRAM_MODE_QUAD is not set +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_TYPE_AUTO=y +# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set +CONFIG_SPIRAM_SIZE=-1 +CONFIG_SPIRAM_CLK_IO=30 +CONFIG_SPIRAM_CS_IO=26 +# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set +# CONFIG_SPIRAM_RODATA is not set +# CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY is not set +# CONFIG_SPIRAM_SPEED_80M is not set +CONFIG_SPIRAM_SPEED_40M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_INIT=y +# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# end of SPI RAM config + +# CONFIG_ESP32S3_TRAX is not set +CONFIG_ESP32S3_TRACEMEM_RESERVE_DRAM=0x0 +# CONFIG_ESP32S3_ULP_COPROC_ENABLED is not set +CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM=0 +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y +# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 +# CONFIG_ESP32S3_NO_BLOBS is not set +# CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM is not set +# CONFIG_ESP32S3_USE_FIXED_STATIC_RAM_SIZE is not set +# end of ESP32S3-Specific + +# +# ADC-Calibration +# +# end of ADC-Calibration + +# +# Common ESP-related +# +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +# end of Common ESP-related + +# +# Ethernet +# +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_SPI_ETHERNET=y +# CONFIG_ETH_SPI_ETHERNET_DM9051 is not set +# CONFIG_ETH_SPI_ETHERNET_W5500 is not set +# CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL is not set +# CONFIG_ETH_USE_OPENETH is not set +# end of Ethernet + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +# end of GDB Stub + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_URI_LEN=512 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +# CONFIG_HTTPD_WS_SUPPORT is not set +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# CONFIG_OTA_ALLOW_HTTP is not set +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +# end of ESP HTTPS server + +# +# Hardware Settings +# + +# +# MAC Config +# +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +# CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_TWO is not set +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4 +# end of MAC Config + +# +# Sleep Config +# +CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y +CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y +CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y +CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y +# end of Sleep Config + +# +# RTC Clock Config +# +CONFIG_RTC_CLOCK_BBPLL_POWER_ON_WITH_USB=y +# end of RTC Clock Config +# end of Hardware Settings + +# +# IPC (Inter-Processor Call) +# +CONFIG_ESP_IPC_TASK_STACK_SIZE=1536 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_IPC_ISR_ENABLE=y +# end of IPC (Inter-Processor Call) + +# +# LCD and Touch Panel +# + +# +# LCD Touch Drivers are maintained in the IDF Component Registry +# + +# +# LCD Peripheral Configuration +# +CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE=32 +# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set +# end of LCD Peripheral Configuration +# end of LCD and Touch Panel + +# +# ESP NETIF Adapter +# +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# CONFIG_ESP_NETIF_LOOPBACK is not set +CONFIG_ESP_NETIF_TCPIP_ADAPTER_COMPATIBLE_LAYER=y +# end of ESP NETIF Adapter + +# +# PHY +# +CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP_PHY_MAX_TX_POWER=20 +# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set +CONFIG_ESP_PHY_ENABLE_USB=y +CONFIG_ESP_PHY_RF_CAL_PARTIAL=y +# CONFIG_ESP_PHY_RF_CAL_NONE is not set +# CONFIG_ESP_PHY_RF_CAL_FULL is not set +CONFIG_ESP_PHY_CALIBRATION_MODE=0 +# end of PHY + +# +# Power Management +# +# CONFIG_PM_ENABLE is not set +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y +CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y +# end of Power Management + +# +# ESP Ringbuf +# +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set +# CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH is not set +# end of ESP Ringbuf + +# +# ESP System Settings +# +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# +# Memory protection +# +# end of Memory protection + +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 +CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y +# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set +# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# CONFIG_ESP_CONSOLE_USB_CDC is not set +# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_NONE is not set +# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set +CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y +CONFIG_ESP_CONSOLE_UART=y +CONFIG_ESP_CONSOLE_MULTIPLE_UART=y +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y +CONFIG_ESP_SYSTEM_BBPLL_RECALIB=y +# end of ESP System Settings + +# +# High resolution timer (esp_timer) +# +# CONFIG_ESP_TIMER_PROFILING is not set +CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y +CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set +CONFIG_ESP_TIMER_IMPL_SYSTIMER=y +# end of High resolution timer (esp_timer) + +# +# Wi-Fi +# +CONFIG_ESP32_WIFI_ENABLED=y +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=6 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=12 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y +# CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set +CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 +CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +# CONFIG_ESP32_WIFI_AMSDU_TX_ENABLED is not set +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set +# CONFIG_ESP_WIFI_FTM_ENABLE is not set +# CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE is not set +# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set +# CONFIG_ESP_WIFI_GMAC_SUPPORT is not set +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y +# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 +# end of Wi-Fi + +# +# Core dump +# +# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set +# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +# end of Core dump + +# +# FAT Filesystem support +# +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +CONFIG_FATFS_CODEPAGE_437=y +# CONFIG_FATFS_CODEPAGE_720 is not set +# CONFIG_FATFS_CODEPAGE_737 is not set +# CONFIG_FATFS_CODEPAGE_771 is not set +# CONFIG_FATFS_CODEPAGE_775 is not set +# CONFIG_FATFS_CODEPAGE_850 is not set +# CONFIG_FATFS_CODEPAGE_852 is not set +# CONFIG_FATFS_CODEPAGE_855 is not set +# CONFIG_FATFS_CODEPAGE_857 is not set +# CONFIG_FATFS_CODEPAGE_860 is not set +# CONFIG_FATFS_CODEPAGE_861 is not set +# CONFIG_FATFS_CODEPAGE_862 is not set +# CONFIG_FATFS_CODEPAGE_863 is not set +# CONFIG_FATFS_CODEPAGE_864 is not set +# CONFIG_FATFS_CODEPAGE_865 is not set +# CONFIG_FATFS_CODEPAGE_866 is not set +# CONFIG_FATFS_CODEPAGE_869 is not set +# CONFIG_FATFS_CODEPAGE_932 is not set +# CONFIG_FATFS_CODEPAGE_936 is not set +# CONFIG_FATFS_CODEPAGE_949 is not set +# CONFIG_FATFS_CODEPAGE_950 is not set +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_LFN_NONE=y +# CONFIG_FATFS_LFN_HEAP is not set +# CONFIG_FATFS_LFN_STACK is not set +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y +# CONFIG_FATFS_USE_FASTSEEK is not set +# end of FAT Filesystem support + +# +# Modbus configuration +# +CONFIG_FMB_COMM_MODE_TCP_EN=y +CONFIG_FMB_TCP_PORT_DEFAULT=502 +CONFIG_FMB_TCP_PORT_MAX_CONN=5 +CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20 +CONFIG_FMB_COMM_MODE_RTU_EN=y +CONFIG_FMB_COMM_MODE_ASCII_EN=y +CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150 +CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200 +CONFIG_FMB_QUEUE_LENGTH=20 +CONFIG_FMB_PORT_TASK_STACK_SIZE=4096 +CONFIG_FMB_SERIAL_BUF_SIZE=256 +CONFIG_FMB_SERIAL_ASCII_BITS_PER_SYMB=8 +CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS=1000 +CONFIG_FMB_PORT_TASK_PRIO=10 +# CONFIG_FMB_PORT_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_FMB_PORT_TASK_AFFINITY_CPU0=y +# CONFIG_FMB_PORT_TASK_AFFINITY_CPU1 is not set +CONFIG_FMB_PORT_TASK_AFFINITY=0x0 +CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT=y +CONFIG_FMB_CONTROLLER_SLAVE_ID=0x00112233 +CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT=20 +CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE=20 +CONFIG_FMB_CONTROLLER_STACK_SIZE=4096 +CONFIG_FMB_EVENT_QUEUE_TIMEOUT=20 +# CONFIG_FMB_TIMER_PORT_ENABLED is not set +# CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD is not set +# end of Modbus configuration + +# +# FreeRTOS +# +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y +CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y +# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set +CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y +# CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE is not set +# CONFIG_FREERTOS_ASSERT_DISABLE is not set +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +# CONFIG_FREERTOS_LEGACY_HOOKS is not set +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set +# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y +# CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH is not set +# end of FreeRTOS + +# +# Hardware Abstraction Layer (HAL) and Low Level (LL) +# +CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y +# CONFIG_HAL_ASSERTION_DISABLE is not set +# CONFIG_HAL_ASSERTION_SILIENT is not set +# CONFIG_HAL_ASSERTION_ENABLE is not set +CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 +# end of Hardware Abstraction Layer (HAL) and Low Level (LL) + +# +# Heap memory debugging +# +# CONFIG_HEAP_POISONING_DISABLED is not set +CONFIG_HEAP_POISONING_LIGHT=y +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +CONFIG_HEAP_TRACING_OFF=y +# CONFIG_HEAP_TRACING_STANDALONE is not set +# CONFIG_HEAP_TRACING_TOHOST is not set +# CONFIG_HEAP_TASK_TRACKING is not set +# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# end of Heap memory debugging + +# +# jsmn +# +# CONFIG_JSMN_PARENT_LINKS is not set +# CONFIG_JSMN_STRICT is not set +# end of jsmn + +# +# libsodium +# +# end of libsodium + +# +# Log output +# +CONFIG_LOG_DEFAULT_LEVEL_NONE=y +# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=0 +CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y +# CONFIG_LOG_MAXIMUM_LEVEL_ERROR is not set +# CONFIG_LOG_MAXIMUM_LEVEL_WARN is not set +# CONFIG_LOG_MAXIMUM_LEVEL_INFO is not set +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +CONFIG_LOG_MAXIMUM_LEVEL=0 +CONFIG_LOG_COLORS=y +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Log output + +# +# LWIP +# +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +# CONFIG_LWIP_NETIF_API is not set +CONFIG_LWIP_TCPIP_TASK_PRIO=18 +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +CONFIG_LWIP_SO_RCVBUF=y +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP_DEFAULT_TTL=64 +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +# CONFIG_LWIP_ETHARP_TRUST_IP_MAC is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_ESP_MLDV6_REPORT=y +CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 + +# +# DHCP server +# +CONFIG_LWIP_DHCPS=y +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV6=y +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# CONFIG_LWIP_IPV6_FORWARD is not set +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=12 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 +CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=0 +# CONFIG_LWIP_TCP_SACK_OUT is not set +# CONFIG_LWIP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +# CONFIG_LWIP_WND_SCALE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# CONFIG_LWIP_PPP_SUPPORT is not set +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +# CONFIG_LWIP_SLIP_SUPPORT is not set + +# +# ICMP +# +CONFIG_LWIP_ICMP=y +# CONFIG_LWIP_MULTICAST_PING is not set +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +CONFIG_LWIP_SNTP_MAX_SERVERS=1 +# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +# end of SNTP + +# +# DNS +# +CONFIG_LWIP_DNS_MAX_SERVERS=3 +# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set +# end of DNS + +CONFIG_LWIP_ESP_LWIP_ASSERT=y + +# +# Hooks +# +# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set +CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y +# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y +# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set +CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y +# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set +# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set +# end of Hooks + +# CONFIG_LWIP_DEBUG is not set +# end of LWIP + +# +# mbedTLS +# +CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +# CONFIG_MBEDTLS_DEBUG is not set + +# +# mbedTLS v2.28.x related +# +# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set +# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set +# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set +CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y +# end of mbedTLS v2.28.x related + +# +# Certificate Bundle +# +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 +# end of Certificate Bundle + +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +# CONFIG_MBEDTLS_CMAC_C is not set +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_AES_USE_INTERRUPT=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_ROM_MD5=y +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +CONFIG_MBEDTLS_HAVE_TIME=y +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA512_C=y +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# CONFIG_MBEDTLS_TLS_DISABLED is not set +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +CONFIG_MBEDTLS_PSK_MODES=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_PSK=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_PSK=y +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_PSK=y +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +# CONFIG_MBEDTLS_SSL_PROTO_SSL3 is not set +CONFIG_MBEDTLS_SSL_PROTO_TLS1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +CONFIG_MBEDTLS_X509_CHECK_KEY_USAGE=y +CONFIG_MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE=y +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# CONFIG_MBEDTLS_DES_C is not set +CONFIG_MBEDTLS_RC4_DISABLED=y +# CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT is not set +# CONFIG_MBEDTLS_RC4_ENABLED is not set +# CONFIG_MBEDTLS_BLOWFISH_C is not set +# CONFIG_MBEDTLS_XTEA_C is not set +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +# CONFIG_MBEDTLS_NIST_KW_C is not set +# end of Symmetric Ciphers + +# CONFIG_MBEDTLS_RIPEMD160_C is not set + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# end of Certificates + +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +# CONFIG_MBEDTLS_ECJPAKE_C is not set +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# CONFIG_MBEDTLS_POLY1305_C is not set +# CONFIG_MBEDTLS_CHACHA20_C is not set +# CONFIG_MBEDTLS_HKDF_C is not set +# CONFIG_MBEDTLS_THREADING_C is not set +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +# CONFIG_MBEDTLS_SECURITY_RISKS is not set +# end of mbedTLS + +# +# mDNS +# +CONFIG_MDNS_MAX_SERVICES=10 +CONFIG_MDNS_TASK_PRIORITY=1 +CONFIG_MDNS_TASK_STACK_SIZE=4096 +# CONFIG_MDNS_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_MDNS_TASK_AFFINITY_CPU0=y +# CONFIG_MDNS_TASK_AFFINITY_CPU1 is not set +CONFIG_MDNS_TASK_AFFINITY=0x0 +CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS=2000 +# CONFIG_MDNS_STRICT_MODE is not set +CONFIG_MDNS_TIMER_PERIOD_MS=100 +# CONFIG_MDNS_NETWORKING_SOCKET is not set +CONFIG_MDNS_MULTIPLE_INSTANCE=y +# end of mDNS + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set +# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set +# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# Newlib +# +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +CONFIG_NEWLIB_NANO_FORMAT=y +# end of Newlib + +# +# NVS +# +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# end of NVS + +# +# OpenSSL +# +# CONFIG_OPENSSL_DEBUG is not set +CONFIG_OPENSSL_ERROR_STACK=y +# CONFIG_OPENSSL_ASSERT_DO_NOTHING is not set +CONFIG_OPENSSL_ASSERT_EXIT=y +# end of OpenSSL + +# +# OpenThread +# +# CONFIG_OPENTHREAD_ENABLED is not set +# end of OpenThread + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# Main Flash configuration +# + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# CONFIG_SPI_FLASH_HPM_ENA is not set +CONFIG_SPI_FLASH_HPM_AUTO=y +# CONFIG_SPI_FLASH_HPM_DIS is not set +CONFIG_SPI_FLASH_HPM_ON=y +CONFIG_SPI_FLASH_HPM_DC_AUTO=y +# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +# CONFIG_SPI_FLASH_ROM_IMPL is not set +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# CONFIG_SPI_FLASH_USE_LEGACY_IMPL is not set +# CONFIG_SPI_FLASH_SHARE_SPI1_BUS is not set +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 +# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set +# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set +# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set + +# +# Auto-detect flash chips +# +CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_TH_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_OPI_CHIP=y +# end of Auto-detect flash chips + +CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y +# end of SPI Flash driver + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# CONFIG_SPIFFS_GC_STATS is not set +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# CONFIG_SPIFFS_DBG is not set +# CONFIG_SPIFFS_API_DBG is not set +# CONFIG_SPIFFS_GC_DBG is not set +# CONFIG_SPIFFS_CACHE_DBG is not set +# CONFIG_SPIFFS_CHECK_DBG is not set +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# end of Websocket +# end of TCP Transport + +# +# TinyUSB Stack +# +# CONFIG_TINYUSB is not set +# end of TinyUSB Stack + +# +# Unity unit testing library +# +CONFIG_UNITY_ENABLE_FLOAT=y +CONFIG_UNITY_ENABLE_DOUBLE=y +# CONFIG_UNITY_ENABLE_64BIT is not set +# CONFIG_UNITY_ENABLE_COLOR is not set +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# end of Unity unit testing library + +# +# USB-OTG +# +CONFIG_USB_OTG_SUPPORTED=y +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 +CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y +# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set +# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set + +# +# Root Hub configuration +# +CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250 +CONFIG_USB_HOST_RESET_HOLD_MS=30 +CONFIG_USB_HOST_RESET_RECOVERY_MS=30 +CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10 +# end of Root Hub configuration +# end of USB-OTG + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_VFS_SUPPORT_TERMIOS=y + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# end of Host File System I/O (Semihosting) +# end of Virtual file system + +# +# Wear Levelling +# +# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# Wi-Fi Provisioning Manager +# +CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 +CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +# CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set +# end of Wi-Fi Provisioning Manager + +# +# Supplicant +# +CONFIG_WPA_MBEDTLS_CRYPTO=y +# CONFIG_WPA_WAPI_PSK is not set +# CONFIG_WPA_SUITE_B_192 is not set +# CONFIG_WPA_DEBUG_PRINT is not set +# CONFIG_WPA_TESTING_OPTIONS is not set +# CONFIG_WPA_WPS_STRICT is not set +# CONFIG_WPA_11KV_SUPPORT is not set +# CONFIG_WPA_MBO_SUPPORT is not set +# CONFIG_WPA_DPP_SUPPORT is not set +# end of Supplicant + +# +# LittleFS +# +# CONFIG_LITTLEFS_SDMMC_SUPPORT is not set +CONFIG_LITTLEFS_MAX_PARTITIONS=3 +CONFIG_LITTLEFS_PAGE_SIZE=256 +CONFIG_LITTLEFS_OBJ_NAME_LEN=64 +CONFIG_LITTLEFS_READ_SIZE=128 +CONFIG_LITTLEFS_WRITE_SIZE=128 +CONFIG_LITTLEFS_LOOKAHEAD_SIZE=128 +CONFIG_LITTLEFS_CACHE_SIZE=512 +CONFIG_LITTLEFS_BLOCK_CYCLES=512 +CONFIG_LITTLEFS_USE_MTIME=y +# CONFIG_LITTLEFS_USE_ONLY_HASH is not set +# CONFIG_LITTLEFS_HUMAN_READABLE is not set +CONFIG_LITTLEFS_MTIME_USE_SECONDS=y +# CONFIG_LITTLEFS_MTIME_USE_NONCE is not set +# CONFIG_LITTLEFS_SPIFFS_COMPAT is not set +# CONFIG_LITTLEFS_FLUSH_FILE_EVERY_WRITE is not set +# CONFIG_LITTLEFS_FCNTL_GET_PATH is not set +# CONFIG_LITTLEFS_MULTIVERSION is not set +# CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE is not set +CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT=y +# CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL is not set +CONFIG_LITTLEFS_ASSERTS=y +# end of LittleFS +# end of Component config + +# +# Compatibility options +# +# CONFIG_LEGACY_INCLUDE_COMMON_HEADERS is not set +# end of Compatibility options + +# Deprecated options for backward compatibility +CONFIG_TOOLPREFIX="xtensa-esp32s3-elf-" +CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=0 +# CONFIG_APP_ROLLBACK_ENABLE is not set +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +# CONFIG_FLASHMODE_QIO is not set +# CONFIG_FLASHMODE_QOUT is not set +CONFIG_FLASHMODE_DIO=y +# CONFIG_FLASHMODE_DOUT is not set +# CONFIG_MONITOR_BAUD_9600B is not set +# CONFIG_MONITOR_BAUD_57600B is not set +CONFIG_MONITOR_BAUD_115200B=y +# CONFIG_MONITOR_BAUD_230400B is not set +# CONFIG_MONITOR_BAUD_921600B is not set +# CONFIG_MONITOR_BAUD_2MB is not set +# CONFIG_MONITOR_BAUD_OTHER is not set +CONFIG_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_MONITOR_BAUD=115200 +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set +CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +CONFIG_CXX_EXCEPTIONS=y +CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_DISABLE_GCC8_WARNINGS is not set +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +CONFIG_ADC2_DISABLE_DAC=y +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +CONFIG_ESP32C3_LIGHTSLEEP_GPIO_RESET_WORKAROUND=y +CONFIG_IPC_TASK_STACK_SIZE=1536 +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 +# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set +CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y +# CONFIG_ESP32S2_PANIC_PRINT_HALT is not set +CONFIG_ESP32S2_PANIC_PRINT_REBOOT=y +# CONFIG_ESP32S2_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP32S2_PANIC_GDBSTUB is not set +CONFIG_ESP32S2_ALLOW_RTC_FAST_MEM_AS_HEAP=y +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=3584 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_TIMER_TASK_STACK_SIZE=3584 +# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_MB_MASTER_TIMEOUT_MS_RESPOND=150 +CONFIG_MB_MASTER_DELAY_MS_CONVERT=200 +CONFIG_MB_QUEUE_LENGTH=20 +CONFIG_MB_SERIAL_TASK_STACK_SIZE=4096 +CONFIG_MB_SERIAL_BUF_SIZE=256 +CONFIG_MB_SERIAL_TASK_PRIO=10 +CONFIG_MB_CONTROLLER_SLAVE_ID_SUPPORT=y +CONFIG_MB_CONTROLLER_SLAVE_ID=0x00112233 +CONFIG_MB_CONTROLLER_NOTIFY_TIMEOUT=20 +CONFIG_MB_CONTROLLER_NOTIFY_QUEUE_SIZE=20 +CONFIG_MB_CONTROLLER_STACK_SIZE=4096 +CONFIG_MB_EVENT_QUEUE_TIMEOUT=20 +# CONFIG_MB_TIMER_PORT_ENABLED is not set +# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=3120 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_L2_TO_L3_COPY is not set +# CONFIG_USE_ONLY_LWIP_SELECT is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=12 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5760 +CONFIG_TCP_WND_DEFAULT=5760 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +# CONFIG_ESP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# CONFIG_PPP_SUPPORT is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +# CONFIG_USB_ENABLED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# End of deprecated options From e8a9e253f7afe73c21bd27b91a95ebb97a93e129 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Sep 2024 18:40:44 +0200 Subject: [PATCH 083/188] Fix matrix expansion --- .github/workflows/tagging.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 2964234..8f64608 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -44,9 +44,9 @@ jobs: version: esp32s3 epd_variant: [213epd, 29epd] exclude: - - chip: btclock_rev_b + - chip: {name: btclock_rev_b, version: esp32s3} epd_variant: 29epd - - chip: btclock_v8 + - chip: {name: btclock_v8, version: esp32s3} epd_variant: 29epd steps: - uses: actions/download-artifact@v4 From 85579e98cf89493e9f72b09644c2f9d1bff5096f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 28 Sep 2024 00:03:18 +0200 Subject: [PATCH 084/188] Bugfixes for stack size and market cap with small chars, write tests for the small char mcap --- lib/btclock/data_handler.cpp | 4 +- lib/btclock/utils.cpp | 3 +- sdkconfig.defaults | 2 +- src/fonts/antonio-semibold40.h | 88 ++++++++++++++++++++++++++--- src/lib/epd.cpp | 12 +++- src/lib/webserver.cpp | 3 +- test/test_datahandler/test_main.cpp | 65 +++++++++++++++++++++ 7 files changed, 162 insertions(+), 15 deletions(-) diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 0905e8a..7b63db6 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -220,7 +220,7 @@ std::array parseMarketCap(std::uint32_t blockHeight, s std::array ret; std::uint32_t firstIndex = 0; double supply = getSupplyAtBlock(blockHeight); - int64_t marketCap = static_cast(supply * double(price)); + uint64_t marketCap = static_cast(supply * double(price)); ret[0] = getCurrencyCode(currencySymbol) + "/MCAP"; @@ -256,7 +256,7 @@ std::array parseMarketCap(std::uint32_t blockHeight, s ret[i] = ""; } - ret[NUM_SCREENS - groups - 1] = " $ "; + ret[NUM_SCREENS - groups - 1] = std::string(" ") + currencySymbol + " "; for (std::uint32_t i = 0; i < groups; i++) { ret[(NUM_SCREENS - groups + i)] = stringValue.substr(i * 3, 3).c_str(); diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index c419603..53212e4 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -68,7 +68,8 @@ std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters) } else { - sprintf(result, "%llu", (unsigned long long)num); + snprintf(result, sizeof(result), "%llu", (unsigned long long)num); +// sprintf(result, "%llu", (unsigned long long)num); return result; } diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 59b2ec3..3294889 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -45,4 +45,4 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_SPIRAM_CACHE_WORKAROUND=y CONFIG_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_NEWLIB_NANO_FORMAT=y \ No newline at end of file +#CONFIG_NEWLIB_NANO_FORMAT=y \ No newline at end of file diff --git a/src/fonts/antonio-semibold40.h b/src/fonts/antonio-semibold40.h index 0f8ea5f..13725f9 100644 --- a/src/fonts/antonio-semibold40.h +++ b/src/fonts/antonio-semibold40.h @@ -1526,7 +1526,78 @@ const uint8_t Antonio_SemiBold40pt7bBitmaps[] PROGMEM = { 0x3F, 0xE0, 0x3F, 0xE0, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0x80, - 0x00}; + 0x00, + // euro + 0x00, 0x07, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x00, + 0x07, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFE, 0x07, 0xFE, 0x07, 0xFC, 0x0F, + 0xF8, 0x0F, 0xF8, 0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xE0, 0x1F, 0xF0, 0xFF, + 0x80, 0x3F, 0xE1, 0xFF, 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC, + 0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0, + 0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00, + 0x3F, 0xC3, 0xFE, 0x00, 0x7F, 0x87, 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01, + 0xFE, 0x1F, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0x80, + 0x0F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, + 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, + 0x3F, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x00, 0x1F, + 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x0F, + 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F, + 0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC3, 0xFE, + 0x00, 0x7F, 0x87, 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0, + 0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, 0xFF, 0x80, + 0x1F, 0xE1, 0xFF, 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC, 0x01, + 0xFF, 0x0F, 0xF8, 0x03, 0xFE, 0x0F, 0xF8, 0x07, 0xFC, 0x1F, 0xF0, 0x0F, + 0xF0, 0x3F, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, + 0x80, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF0, + 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF8, 0x00, + 0x00, 0x3F, 0x80, 0x00, + // pound + 0x00, 0x0F, 0xFC, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFC, 0x00, + 0x7F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xC0, 0x7F, + 0xFF, 0xFF, 0x83, 0xFF, 0x03, 0xFE, 0x0F, 0xF8, 0x07, 0xF8, 0x3F, 0xC0, + 0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC7, 0xFC, 0x00, 0xFF, 0x1F, 0xE0, 0x03, + 0xFC, 0x7F, 0x80, 0x0F, 0xF1, 0xFE, 0x00, 0x3F, 0xC7, 0xF8, 0x00, 0xFF, + 0x1F, 0xF0, 0x03, 0xFC, 0x7F, 0xC0, 0x0F, 0xF1, 0xFF, 0x00, 0x3F, 0xC3, + 0xFC, 0x00, 0xFF, 0x0F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0xFF, + 0x80, 0x00, 0x01, 0xFE, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x1F, 0xF0, + 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x03, 0xFC, 0x00, + 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, + 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFE, 0x3F, + 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFE, 0x00, 0x00, 0x0F, + 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x01, 0xFE, + 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x7F, 0xC0, + 0x00, 0x01, 0xFF, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x1F, 0xF0, 0x00, + 0x00, 0x7F, 0xC0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, + 0x1F, 0xF0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x0F, + 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x0F, 0xF8, + 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x1F, 0xF0, 0x00, + 0x01, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, + 0x7F, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF, + 0xFF, 0xFF, 0xFF, + // yen + 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x07, 0xFB, 0xFE, 0x00, 0x1F, 0xF3, + 0xFC, 0x00, 0x3F, 0xE7, 0xFC, 0x00, 0x7F, 0xCF, 0xF8, 0x00, 0xFF, 0x0F, + 0xF0, 0x03, 0xFE, 0x1F, 0xE0, 0x07, 0xFC, 0x3F, 0xE0, 0x0F, 0xF0, 0x7F, + 0xC0, 0x1F, 0xE0, 0x7F, 0x80, 0x7F, 0xC0, 0xFF, 0x80, 0xFF, 0x81, 0xFF, + 0x01, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFC, 0x07, 0xF8, 0x07, 0xFC, + 0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xC0, 0x0F, 0xF0, 0x7F, 0x80, 0x1F, 0xE0, + 0xFF, 0x00, 0x3F, 0xE3, 0xFC, 0x00, 0x3F, 0xC7, 0xF8, 0x00, 0x7F, 0x8F, + 0xF0, 0x00, 0xFF, 0x9F, 0xE0, 0x00, 0xFF, 0x7F, 0x80, 0x01, 0xFE, 0xFF, + 0x00, 0x03, 0xFD, 0xFE, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, + 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0x00, + 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x7F, 0xFC, 0x00, 0x00, 0xFF, 0xF8, 0x00, + 0x00, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0x80, 0x07, + 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0xF8, 0x3F, + 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0x00, + 0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x07, + 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, + 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, + 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, + 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, + 0x00, 0x00, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00, + 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, + 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, 0x00 + }; const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = { {0, 1, 1, 17, 0, 0}, // 0x20 ' ' @@ -1588,10 +1659,10 @@ const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = { {10963, 28, 67, 32, 2, -66}, // 0x58 'X' {11198, 31, 67, 32, 1, -66}, // 0x59 'Y' {11458, 23, 67, 27, 3, -66}, // 0x5A 'Z' - {11651, 17, 70, 26, 6, -66}, // 0x5B '[' - {11800, 24, 67, 30, 3, -66}, // 0x5C '\' - {12001, 16, 70, 26, 4, -66}, // 0x5D ']' - {12141, 29, 35, 37, 4, -66}, // 0x5E '^' + {18021, 31, 69, 37, 2, -67}, // 0x5B '[' --> euro { 18290, 31, 69, 37, 2, -67 } was {11651, 17, 70, 26, 6, -66} + {11800, 24, 67, 30, 3, -66}, // 0x5C '\' + {18557, 30, 68, 36, 3, -67 }, // 0x5D ']' --> pound { 0, 30, 68, 36, 3, -67 } was {12001, 16, 70, 26, 4, -66} + {18812, 31, 67, 32, 1, -66 }, // 0x5E '^' --> yen { 0, 31, 67, 32, 1, -66 } was {12141, 29, 35, 37, 4, -66 {12268, 25, 7, 29, 2, 2}, // 0x5F '_' {12290, 12, 15, 16, 2, -77}, // 0x60 '`' {12313, 27, 59, 36, 4, -57}, // 0x61 'a' @@ -1623,10 +1694,13 @@ const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = { {17509, 20, 75, 27, 4, -67}, // 0x7B '{' {17697, 9, 75, 21, 6, -70}, // 0x7C '|' {17782, 19, 75, 27, 4, -67}, // 0x7D '}' - {17961, 34, 14, 43, 4, -44}, {18021, 31, 69, 37, 2, -67}}; // 0x7E '~' + {17961, 34, 14, 43, 4, -44}}; // 0x7E '~' + + +//, {18021, 31, 69, 37, 2, -67} const GFXfont Antonio_SemiBold40pt7b PROGMEM = { (uint8_t *)Antonio_SemiBold40pt7bBitmaps, - (GFXglyph *)Antonio_SemiBold40pt7bGlyphs, 0x20, 0x7E, 101}; + (GFXglyph *)Antonio_SemiBold40pt7bGlyphs, 0x20, 0x7E, 100}; // Approx. 18961 bytes diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 7688fc8..a301f17 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -132,6 +132,12 @@ std::mutex epdMutex[NUM_SCREENS]; uint8_t qrcode[800]; +#ifdef IS_BTCLOCK_V8 +#define EPD_TASK_STACK_SIZE 4096 +#else +#define EPD_TASK_STACK_SIZE 2048 +#endif + void forceFullRefresh() { for (uint i = 0; i < NUM_SCREENS; i++) @@ -170,7 +176,7 @@ void setupDisplays() updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 4096, NULL, 11, NULL); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE, NULL, 11, NULL); for (uint i = 0; i < NUM_SCREENS; i++) { @@ -180,7 +186,7 @@ void setupDisplays() int *taskParam = new int; *taskParam = i; - xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 4096, taskParam, + xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam, 11, &tasks[i]); // create task } @@ -190,7 +196,7 @@ void setupDisplays() setFgColor(GxEPD_BLACK); setBgColor(GxEPD_WHITE); - epdContent = {" ", " ", " ", " ", " ", " ", " "}; + epdContent.fill(""); } else { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 738f4f5..d5dba86 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -300,7 +300,8 @@ JsonDocument getLedStatusObject() uint green = (pixColor >> 8) & 0xFF; uint blue = pixColor & 0xFF; char hexColor[8]; - sprintf(hexColor, "#%02X%02X%02X", red, green, blue); + snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue); + JsonObject object = colors.add(); object["red"] = red; diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index 14f3a93..29d337f 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -1,6 +1,18 @@ #include #include +template +std::string joinArrayWithBrackets(const std::array& arr, const std::string& separator = " ") { + std::ostringstream result; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { + result << separator; + } + result << '[' << arr[i] << ']'; + } + return result.str(); +} + void setUp(void) { // set stuff up here @@ -100,6 +112,24 @@ void test_Mcap1TrillionUsd(void) TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str()); } +void test_Mcap1TrillionUsdSmallChars(void) +{ + std::array output = parseMarketCap(831000, 52000, '$', false); + TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str()); + + std::string joined = joinArrayWithBrackets(output); + + + + TEST_ASSERT_EQUAL_STRING_MESSAGE(" $ ", output[NUM_SCREENS - 6].c_str(), joined.c_str()); + + TEST_ASSERT_EQUAL_STRING_MESSAGE(" 1", output[NUM_SCREENS - 5].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("020", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("825", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 1].c_str(), joined.c_str()); +} + void test_Mcap1TrillionEur(void) { std::array output = parseMarketCap(831000, 52000, CURRENCY_EUR, true); @@ -112,6 +142,23 @@ void test_Mcap1TrillionEur(void) TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str()); } +void test_Mcap1TrillionEurSmallChars(void) +{ + std::array output = parseMarketCap(831000, 52000, CURRENCY_EUR, false); + TEST_ASSERT_EQUAL_STRING("EUR/MCAP", output[0].c_str()); + + std::string joined = joinArrayWithBrackets(output); + + char result[4]; + snprintf(result, sizeof(result), " %c ", CURRENCY_EUR); + TEST_ASSERT_EQUAL_STRING(result, output[NUM_SCREENS - 6].c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE(" 1", output[NUM_SCREENS - 5].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("020", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("825", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 1].c_str(), joined.c_str()); +} + void test_Mcap1TrillionJpy(void) { std::array output = parseMarketCap(831000, 52000, CURRENCY_JPY, true); @@ -124,6 +171,21 @@ void test_Mcap1TrillionJpy(void) TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str()); } +void test_Mcap1TrillionJpySmallChars(void) +{ + std::array output = parseMarketCap(831000, 52000, CURRENCY_JPY, false); + TEST_ASSERT_EQUAL_STRING("JPY/MCAP", output[0].c_str()); + + char result[4]; + snprintf(result, sizeof(result), " %c ", CURRENCY_JPY); + TEST_ASSERT_EQUAL_STRING(result, output[NUM_SCREENS - 6].c_str()); + TEST_ASSERT_EQUAL_STRING(" 1", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING("020", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("825", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("000", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("000", output[NUM_SCREENS - 1].c_str()); +} + // not needed when using generate_test_runner.rb int runUnityTests(void) { @@ -136,8 +198,11 @@ int runUnityTests(void) RUN_TEST(test_PriceOf100kusd); RUN_TEST(test_McapLowerUsd); RUN_TEST(test_Mcap1TrillionUsd); + RUN_TEST(test_Mcap1TrillionUsdSmallChars); RUN_TEST(test_Mcap1TrillionEur); + RUN_TEST(test_Mcap1TrillionEurSmallChars); RUN_TEST(test_Mcap1TrillionJpy); + RUN_TEST(test_Mcap1TrillionJpySmallChars); return UNITY_END(); } From b0ec0685a1d26502193e67ef56224351c6f01272 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 3 Oct 2024 00:26:14 +0200 Subject: [PATCH 085/188] Bugfix for too short WiFi-setup passwords --- data | 2 +- platformio.ini | 6 +++--- src/lib/config.cpp | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data b/data index 95aa9d6..9867988 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 95aa9d67d17dde32dab3216810e7b4e4086feb03 +Subproject commit 9867988a095a09a693aa2dc567605d15cfe4ceb1 diff --git a/platformio.ini b/platformio.ini index 30e03ec..59f20fb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd, [btclock_base] -platform = espressif32 @ ^6.8.1 +platform = espressif32 @ ^6.9.0 framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize @@ -32,8 +32,8 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.1.0 - mathieucarbou/ESPAsyncWebServer @ 3.3.1 + bblanchon/ArduinoJson@^7.2.0 + mathieucarbou/ESPAsyncWebServer @ 3.3.7 adafruit/Adafruit BusIO@^1.16.1 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.3 diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 17805c0..d20a9d3 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -145,7 +145,7 @@ void setupWifi() WiFi.setHostname(softAP_SSID.c_str()); String softAP_password = replaceAmbiguousChars( base64::encode(String(mac[2], 16) + String(mac[4], 16) + - String(mac[5], 16) + String(mac[1], 16)) + String(mac[5], 16) + String(mac[1], 16) + String(mac[3], 16)) .substring(2, 10)); wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT)); @@ -155,10 +155,10 @@ void setupWifi() wm.setAPCallback([&](WiFiManager *wifiManager) { - // Serial.printf("Entered config mode:ip=%s, ssid='%s', pass='%s'\n", - // WiFi.softAPIP().toString().c_str(), - // wifiManager->getConfigPortalSSID().c_str(), - // softAP_password.c_str()); + Serial.printf("Entered config mode:ip=%s, ssid='%s', pass='%s'\n", + WiFi.softAPIP().toString().c_str(), + wifiManager->getConfigPortalSSID().c_str(), + softAP_password.c_str()); // delay(6000); setFgColor(GxEPD_BLACK); setBgColor(GxEPD_WHITE); From a614bd15dbe8f95a9fbb00b3cf99c8d6ecb5a47b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 23 Oct 2024 00:43:53 +0200 Subject: [PATCH 086/188] Restore Nostr Zap subscription after loss of connection --- data | 2 +- src/lib/nostr_notify.cpp | 76 +++++++++++++++++++++++++++------------- src/lib/nostr_notify.hpp | 5 ++- 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/data b/data index 9867988..384b431 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 9867988a095a09a693aa2dc567605d15cfe4ceb1 +Subproject commit 384b4317c4b58afd981f410a9b732700e733b00b diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 2e044bb..237eabe 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -4,16 +4,20 @@ std::vector pools; nostr::Transport *transport; TaskHandle_t nostrTaskHandle = NULL; boolean nostrIsConnected = false; +boolean nostrIsSubscribed = false; +boolean nostrIsSubscribing = true; + +String subIdZap; void setupNostrNotify(bool asDatasource, bool zapNotify) { nostr::esp32::ESP32Platform::initNostr(false); - time_t now; - time(&now); - struct tm *utcTimeInfo; - utcTimeInfo = gmtime(&now); - time_t utcNow = mktime(utcTimeInfo); - time_t timestamp60MinutesAgo = utcNow - 3600; + // time_t now; + // time(&now); + // struct tm *utcTimeInfo; + // utcTimeInfo = gmtime(&now); + // time_t utcNow = mktime(utcTimeInfo); + // time_t timestamp60MinutesAgo = utcNow - 3600; try { @@ -27,20 +31,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) if (zapNotify) { - String subIdZap = pool->subscribeMany( - {relay}, - { - { - {"kinds", {"9735"}}, - {"limit", {"1"}}, - {"since", {String(timestamp60MinutesAgo)}}, - {"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY)}}, - }, - }, - handleNostrZapCallback, - onNostrSubscriptionClosed, - onNostrSubscriptionEose); - Serial.println("[ Nostr ] Subscribing to Zap Notifications"); + subscribeZaps(pool, relay, 60); } if (asDatasource) @@ -50,7 +41,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) {// First filter { {"kinds", {"1"}}, - {"since", {String(timestamp60MinutesAgo)}}, + {"since", {String(getMinutesAgo(60))}}, {"authors", {pubKey}}, }}, handleNostrEventCallback, @@ -72,11 +63,12 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) sstatus="CONNECTED"; }else if(status==nostr::ConnectionStatus::DISCONNECTED){ nostrIsConnected = false; + nostrIsSubscribed = false; sstatus="DISCONNECTED"; }else if(status==nostr::ConnectionStatus::ERROR){ sstatus = "ERROR"; } - //Serial.println("[ Nostr ] Connection status changed: " + sstatus); + Serial.println("[ Nostr ] Connection status changed: " + sstatus); }); } } @@ -88,8 +80,10 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) void nostrTask(void *pvParameters) { - int blockFetch = getBlockFetch(); - processNewBlock(blockFetch); + if(preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) { + int blockFetch = getBlockFetch(); + processNewBlock(blockFetch); + } while (1) { @@ -98,6 +92,10 @@ void nostrTask(void *pvParameters) // Run internal loop: refresh relays, complete pending connections, send // pending messages pool->loop(); + if (!nostrIsSubscribed && !nostrIsSubscribing) { + Serial.println(F("Not subscribed")); + subscribeZaps(pool, preferences.getString("nostrRelay"), 1); + } } vTaskDelay(pdMS_TO_TICKS(100)); } @@ -125,6 +123,8 @@ void onNostrSubscriptionEose(const String &subId) // This is the callback that will be called when the subscription is // EOSE Serial.println("[ Nostr ] Subscription EOSE: " + subId); + nostrIsSubscribing = false; + nostrIsSubscribed = true; } void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event) @@ -183,6 +183,34 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even } } +time_t getMinutesAgo(int min) { + time_t now; + time(&now); + return now - (min * 60); +} + +void subscribeZaps(nostr::NostrPool *pool, const String &relay, int minutesAgo) { + if (subIdZap) { + pool->closeSubscription(subIdZap); + } + nostrIsSubscribing = true; + + subIdZap = pool->subscribeMany( + {relay}, + { + { + {"kinds", {"9735"}}, + {"limit", {"1"}}, + {"since", {String(getMinutesAgo(minutesAgo))}}, + {"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY)}}, + }, + }, + handleNostrZapCallback, + onNostrSubscriptionClosed, + onNostrSubscriptionEose); + Serial.println("[ Nostr ] Subscribing to Zap Notifications since " + String(getMinutesAgo(minutesAgo))); +} + void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) { // Received events callback, we can access the event content with // event->getContent() Here you should handle the event, for this diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp index 045574d..ebbe2cb 100644 --- a/src/lib/nostr_notify.hpp +++ b/src/lib/nostr_notify.hpp @@ -24,4 +24,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event); void onNostrSubscriptionClosed(const String &subId, const String &reason); -void onNostrSubscriptionEose(const String &subId); \ No newline at end of file +void onNostrSubscriptionEose(const String &subId); + +time_t getMinutesAgo(int min); +void subscribeZaps(nostr::NostrPool *pool, const String &relay, int minutesAgo); \ No newline at end of file From 82dd70a38ddf9a8337d4f9c33b079fe2754989eb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 25 Nov 2024 23:54:02 +0100 Subject: [PATCH 087/188] Use CA bundle instead of single certificates, make auto update URL configurable --- .gitignore | 3 +- .gitmodules | 2 +- ci/Dockerfile | 15 ++++++ partition.csv | 4 +- platformio.ini | 1 + src/lib/block_notify.cpp | 2 +- src/lib/config.cpp | 4 ++ src/lib/defaults.hpp | 4 +- src/lib/ota.cpp | 13 +++-- src/lib/price_notify.cpp | 47 +++------------- src/lib/shared.cpp | 113 +++++++++++++++++++++++++-------------- src/lib/shared.hpp | 7 ++- src/lib/webserver.cpp | 3 +- x509_crt_bundle | Bin 0 -> 63694 bytes 14 files changed, 126 insertions(+), 92 deletions(-) create mode 100644 ci/Dockerfile create mode 100644 x509_crt_bundle diff --git a/.gitignore b/.gitignore index e5f0444..b6395ed 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ data/.yarn data/node_modules node_modules .DS_Store -*.bin \ No newline at end of file +*.bin +ci/cache \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index f921d34..d8b183a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "data"] path = data - url = https://github.com/btclock/webui.git + url = https://git.btclock.dev/btclock/webui.git diff --git a/ci/Dockerfile b/ci/Dockerfile new file mode 100644 index 0000000..795d695 --- /dev/null +++ b/ci/Dockerfile @@ -0,0 +1,15 @@ +# Use the official Python 3.9 image as the base +FROM python:3.9-slim + +# Set the working directory +WORKDIR /workspace + +RUN apt-get update && apt-get install -y git + +# Install PlatformIO +RUN pip install platformio + +WORKDIR /usr/src + +CMD ["platformio", "run"] + diff --git a/partition.csv b/partition.csv index 4ca1357..318b4f0 100644 --- a/partition.csv +++ b/partition.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 36K, 20K, otadata, data, ota, 56K, 8K, -app0, app, ota_0, 64K, 1700K, -app1, app, ota_1, , 1700K, +app0, app, ota_0, 64K, 1740K, +app1, app, ota_1, , 1740K, spiffs, data, spiffs, , 400K, coredump, data, coredump,, 64K, diff --git a/platformio.ini b/platformio.ini index 59f20fb..25e7d9c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,6 +21,7 @@ monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize board_build.filesystem = littlefs extra_scripts = post:scripts/extra_script.py +board_build.embed_files = x509_crt_bundle build_flags = !python scripts/git_rev.py -DLAST_BUILD_TIME=$UNIX_TIME diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index bf5d337..b86d497 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -302,7 +302,7 @@ int getBlockFetch() WiFiClientSecure client; if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { - client.setCACert(mempoolWsCert); + client.setCACertBundle(rootca_crt_bundle_start); } String mempoolInstance = diff --git a/src/lib/config.cpp b/src/lib/config.cpp index d20a9d3..8f954b2 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -284,6 +284,10 @@ void setupPreferences() preferences.putBool("flDisable", isWhiteVersion() ? false : true); } + if (!preferences.isKey("gitReleaseUrl")) { + preferences.putString("gitReleaseUrl", DEFAULT_GIT_RELEASE_URL); + } + if (!preferences.isKey("fgColor")) { preferences.putUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE); preferences.putUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK); diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 39e7406..dd222a5 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -60,4 +60,6 @@ #define DEFAULT_HTTP_AUTH_USERNAME "btclock" #define DEFAULT_HTTP_AUTH_PASSWORD "satoshi" -#define DEFAULT_ACTIVE_CURRENCIES "USD,EUR,JPY" \ No newline at end of file +#define DEFAULT_ACTIVE_CURRENCIES "USD,EUR,JPY" + +#define DEFAULT_GIT_RELEASE_URL "https://git.btclock.dev/api/v1/repos/btclock/btclock_v3/releases/latest" diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index d4b11a6..1800f76 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -106,9 +106,12 @@ void handleOTATask(void *parameter) ReleaseInfo getLatestRelease(const String &fileToDownload) { - String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest"; + String releaseUrl = preferences.getString("gitReleaseUrl"); WiFiClientSecure client; - client.setCACert(github_root_ca); +// client.setCACert(isrg_root_x1cert); + client.setCACertBundle(rootca_crt_bundle_start); + + HTTPClient http; http.begin(client, releaseUrl); http.setUserAgent(USER_AGENT); @@ -153,7 +156,7 @@ ReleaseInfo getLatestRelease(const String &fileToDownload) int downloadUpdateHandler(char updateType) { WiFiClientSecure client; - client.setCACert(github_root_ca); + client.setCACertBundle(rootca_crt_bundle_start); HTTPClient http; http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); @@ -303,7 +306,7 @@ int downloadUpdateHandler(char updateType) void updateWebUi(String latestRelease, int command) { WiFiClientSecure client; - client.setCACert(github_root_ca); + client.setCACertBundle(rootca_crt_bundle_start); HTTPClient http; http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http.begin(client, latestRelease); @@ -421,7 +424,7 @@ String downloadSHA256(const String &sha256Url) } WiFiClientSecure client; - client.setCACert(github_root_ca); + client.setCACertBundle(rootca_crt_bundle_start); HTTPClient http; http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http.begin(client, sha256Url); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index d6a1e71..f995de8 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -5,40 +5,6 @@ const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws"; const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; -const char* coincapWsCert = R"EOF( ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)EOF"; - // WebsocketsClient client; esp_websocket_client_handle_t clientPrice = NULL; esp_websocket_client_config_t config; @@ -60,7 +26,8 @@ void setupPriceNotify() { config = {.uri = wsServerPrice, .user_agent = USER_AGENT}; - config.cert_pem = coincapWsCert; + config.cert_pem = isrg_root_x1cert; + config.task_stack = (6*1024); } @@ -159,11 +126,11 @@ void processNewPrice(uint newPrice, char currency) { // const unsigned long oldPrice = currentPrice; currencyMap[currency] = newPrice; - // if (lastUpdateMap[currency] == 0 || - // (currentTime - lastUpdateMap[currency]) > 120) - // { - // preferences.putUInt("lastPrice", currentPrice); - // } + if (currency == CURRENCY_USD && ( lastUpdateMap[currency] == 0 || + (currentTime - lastUpdateMap[currency]) > 120)) + { + preferences.putUInt("lastPrice", currentPrice); + } lastUpdateMap[currency] = currentTime; // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index 597d803..20bf6d4 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -1,44 +1,79 @@ #include "shared.hpp" -const char *github_root_ca = - "-----BEGIN CERTIFICATE-----\n" - "MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n" - "MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n" - "eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n" - "JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n" - "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n" - "Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n" - "VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n" - "aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n" - "I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n" - "o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n" - "A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" - "VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n" - "zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n" - "RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n" - "-----END CERTIFICATE-----\n" - "-----BEGIN CERTIFICATE-----\n" - "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n" - "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" - "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n" - "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n" - "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" - "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n" - "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n" - "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n" - "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n" - "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n" - "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n" - "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n" - "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n" - "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n" - "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n" - "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n" - "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n" - "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n" - "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n" - "MrY=\n" - "-----END CERTIFICATE-----\n"; +// const char *github_root_ca = +// "-----BEGIN CERTIFICATE-----\n" +// "MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n" +// "MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n" +// "eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n" +// "JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n" +// "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n" +// "Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n" +// "VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n" +// "aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n" +// "I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n" +// "o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n" +// "A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" +// "VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n" +// "zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n" +// "RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n" +// "-----END CERTIFICATE-----\n" +// "-----BEGIN CERTIFICATE-----\n" +// "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n" +// "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" +// "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n" +// "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n" +// "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" +// "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n" +// "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n" +// "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n" +// "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n" +// "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n" +// "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n" +// "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n" +// "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n" +// "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n" +// "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n" +// "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n" +// "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n" +// "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n" +// "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n" +// "MrY=\n" +// "-----END CERTIFICATE-----\n"; + +const char* isrg_root_x1cert = R"EOF( +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +)EOF"; + #ifdef TEST_SCREENS uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24 diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index a381c7f..19d1df0 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -9,6 +9,7 @@ #include #include #include +#include "esp_crt_bundle.h" #include #include @@ -68,7 +69,11 @@ const PROGMEM int screens[SCREEN_COUNT] = { const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; -extern const char *github_root_ca; +// extern const char *github_root_ca; +extern const char *isrg_root_x1cert; + +extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start"); + const PROGMEM char UPDATE_FIRMWARE = U_FLASH; const PROGMEM char UPDATE_WEBUI = U_SPIFFS; diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index d5dba86..7a6d28d 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -510,7 +510,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey", "httpAuthUser", "httpAuthPass"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl"}; for (String setting : strSettings) { @@ -701,6 +701,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); + root["gitReleaseUrl"] = preferences.getString("gitReleaseUrl", DEFAULT_GIT_RELEASE_URL); root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); diff --git a/x509_crt_bundle b/x509_crt_bundle new file mode 100644 index 0000000000000000000000000000000000000000..77d8ea3e85b3c2b257ffe256f6231d116a0cc7b8 GIT binary patch literal 63694 zcmb@uWmsM5vMr3e2KR-#ThQR{?!jGxTX1&??oM!bcMtBa!6mo^F5T($&N=t=m;If) zfABo4n(N1UYmTZKHLB(W3ISpRCSzb>gk^w+096KqKmid^V0^=X^v4GnIA|eLGb>jc z2Qzy{WCp}PY@l#Zf<|@@W+v8p48Fib4Db-pRDoiFxA4Hgz~DfjzQBk;uXY#^UtnOM zLDP$&xw#q^M>1dhUs)tSK#}Hh2jO6S^MOQs8;v!M{2W;OZBhpxKVi(<@(UN^r}}SM z6GicFJn~aUEe8EnLYa%c5}_%gpO0c3tG++Tfk}d1+kT&Bvm&+~0E?zk(41K+whdi< zd&RV-!_toHqAj5L1%W6^I(puN39|;4#=Il)9V&7N0KplxjwcQ(=#~_JwpthH*oKON z)S8MH(*T3?9j_1o{f2Civ>aTb_NxKSWjMN`r`97fHdL30ekM`lMR$bR$^)kw#5xEl zFc2^h2M8GhJ0mK?n;$NPRWh|SjMn!v!VX1mH`=L~a0ABkW0Lcp{3*MS zNLE+u4ZC|ET@SPvxEj2w@tq~CW1T?34$eLjowh7iXyphtV{t3rx>#VYR* zHmPFAfuMR`yD#&(iApqSmyC=sgj9X2Y{tUIgCce&yESc$pEr)(pu@&IQw!0C z3H;sJUk*t4I$V37xYhHSb@N2dy@(+KYZ^Mz)I__3I}uZ{g;Kw7SjVnwAYo>iHmjDv z4D|SLOYQ=W`@Hm0k@g(-`oxlh!`>TxP@$4|Y=d`Ym0L8cg*9|5$uz8ex5Msq>LLH+ z&B;>|W)Ydw3WX#R0C(C8-I?~p#`{*hMG?G3+jL-N&=LJzh(_2ghUkpc#Z-tZuQX56 zXT3E}{HZ$~(2cGk-0FId38us@F2C2-b=d@%NiXTJ5nYyiyc?f|xhO$k!E^m#4?knTPr zY+!+_uB-#`G+dRr7_7VY1hrb~XG@1jM(%DCUYRwuRP04&1!;K`ORqok&P?zl{M?h8BXDrK8oK0KbM%WSa0rC?&HL(T^BRx>kl>CQQBSJH z*#}b>R_PxQ<9vnK+h0OhSn(IUiE9YU$O!`!>G81ETJbgC6*wxSXZV}4?X7dWbKIp}9*xjKMrX-060mt#mKl4Xf}@+nn>awWjSk*V{hUT_QHx5gXF|O3ESF%#1Kq zs!h3}Nj=A0EPe6(W}c4S+<-p4W_i&pK=h~VXvs^Nj{(9;DU;x2{FQamc+Bm+po63z zyFM&)T|v_-k&BhmZ}yh*4vxf1DRQtAA5R0{Z$Jx4OB;4>!fON(wU*)JK9kjj93g9f5Hm|>4)L(O!k_{ep4%|UA%3FU1qm!D@48J z96YQS=7XKCl1*>b2zBor{DJ99L1+V~vjp8_s`Dn;nt|IRmc<4kG1#Q8PJ@XDc#qhx z>#**`O)J;to1EU_>1E_S`?BTlN~r7zZ{B~eMSpNuN=s$spns?Y$U#`TO?X!q+*cfn zVnxZlg={NU(Ctl)+;7znuIU$p336GyOJ_%Mvna z(IDxo4)v5?FM$lzEPI&A>{2XR%Y0iZpa%M$y8chOFZ znzGwc`w(o>qba34aM9GUGZ0w1rR+kk0Y1KE8tfbJqv-`w3QmWD=?!3$ayQBwvr`4) z0SD{{!=B|@@zP=`@5%uhR>AR$ER71-g3*;7mf%hGE)9nhJDl8a6!l*uzPY!vf)~AE z*}gP%(5K-664#)3W)f-%S~VgEvE^ElxFuK`yCqbPmPI@+_ub62SQMUaIe#B?!cD^w zxb$xA*iQM#HMeYe^EDQYiXfrFq9;cCY3hjO`xVutUYbsP0{+OQyPy%bHpR#JCHxG9##AWG9bHnsX-%MNmW`Bxq{$Z$Z zq1cMR-iM1T$%5k}bAF#pV5B#OslKqPhmaax4g0PM)RFNT5Eqck|NfV;FgU%^*1vxl zIJFNL5D}<85ZF{nD7$Q~BK&h#sJrB6cM(a0O8pyJoMk9m!YQDAgSHg%$8!#A?L(gY zA)n%0O+b>cHv01SeI%>xw2key_x}2q9}JnZMo8#CGzVu>WjWsYl8Kh;OC{^#%;5$x0_U; zwbvkuRcF5kE>L0=4IFU`QVL%Yc0*9`U8;)t9Y_b#ejo!CR95~Nh2k2#Xh8YAttZ46SE5AgJ?b1eNIg# zUDCNE>fyNFp`x4aC+M{-?!z?)2A80p>g)QP+#wj3|BI>}~+D9?p>V2cwefK?E z0iF0uV?uX0WKC+Y+S3sZ?i%fft;AS{?2zqN<a?F(SBDdsTX)9> z(kGA4bPyz>A;=8Mnig4UA)B3yXi8P1Jgy+)=6ygCv~wZFXWb@L?{U5=iZ~Wmx(bY# z+Cm-ZwsDw!xvJ<<;TtGZ$bsIm2^0+_IHDle_5)Oa4OyzBgo#}Lh+Cf53j8a~6lDK~ z848raYg48uE-Ldk%utYing9y2vP!T0#BXbG19Ow-AgM%T-Eu7jPO)nA{v^?~Ve|B- z{h880Jw9_?RZ}H@=~i z18d<$y-)6~Dq9XFJc@?$^}b}t72J#VEmx)xKhc~oLVAnv93`chDssqwT?ysTGZD;O z|8d@7eZ_xL5W$fno0N~@&}9UHhK?gWJj@y$m!8ZQX=~dk4hF1K=T=+u4ryzm*-i3s zjU5OGTa#jYD$SmkDJUs>NS=Ovt(3V&_J^B!U){{jh|hrggDIo&JJ{F*lmBbg@k|W=lB4wL>nqDIA1ZEM9EU zrq-18Z_lH=qWf+M-6WB$S4D77zTd67?DWao4lRzphK?%nM$3(R&%{6O*ZIz%_SF|D zld3LP$=y`y8mhaQje|DWz_$$i)?=nW!2BY;xfngdoq(BO5@%%8S> zyXyMV4WTV_)srB}2XGRo^&D(11ut${Ly$Hyj+L|7iE@I#z-dH1YH^$NDUB(H8#X^( z=f#00H+=}G+Y>vCkb7EO8?}K%ScSDq_hvwcjDJUg@V(DbR&mZDrIv&)5+r8asV@~@ z*&)q^us-@Rr{o?jd%DDyX*;h>bQ&Hi*bBMt-BkNfaO z`L!b!wY1UKvjm7T{mCqUn>YLRsBJ!;k%5!v`j9Nyi~!#!rxY=D69wt<$8e4GnBqY6 zNfl9dnP3p%QJ>sXhJ4C#X^XYU%7xGI9R5^OFd5D|>v)fGGcMe>>vrt?DJzP&9042D zt9@qTgwDN0gjn;1W>3j2u9FJwd?=#Qjf-S+-gJwR8-iJ+UJ1D%pPopGN(~+uec9-A|QtHl?2LlkI2A*kdr&=Zlwf-jxLq%~rIHqgeEH?oKuL(;1< zdArh9cLny^u;+_ltCelj)Ec>F=>9@~`KG$ZI0Y?hW4sFXMYWn_!yER&k2NQf-kT8* zOvLpz4Px<7RYEcW;hZ2!0Q&U~Raq|1Xu|m}GFBzP@xVLL<1>v8Nw=;tE(|#>{RKox zi52Px>sI^NvEu=`eg1^`B2TM@1H7ovx+b;QW{Fs8IF;|-M^NWFl-4-7a1~LXLdurg zax(~rL$Hq5>w2({(4Sq{q~@$AcrkFJF)YZMAKmCXkZ~~)Xzf9xvUXXsG`6H1@oYCa z-YUBAjrx6>hsj9Sj0)Y^-4NRms}>!Tp>m{h|3-8()D9h!pz0PJTzdMU^nQhM8xEK2 zjf~J-p-d&&;N>EAVvOnGS%e70l!~xg3O5|0Un4ka563uH9!p4r>l=E}zOO}zW={pq zCUxuX7j8yqJ9;Wce*x{4Rs0IH*Usgqz=Z<+hnS^jW^L)ph|Pfcqj^F8+xDY>5oY`+ zX0c{-=Y_j|axWSbSe4fqdgq(yeHb+;p+n^nY+4Acj~YYbrj?P4;Sm~0$bu<{gS0lm zE=e|krR#mb(x!{W#y3Y91kD}ecGHSNys9n(rZYYWf{Urx#kjBpI1q~1aRU19G4lWS zrz`wkL7HHjAvjzu*_o{0dnD>yMS(YWCqltZ4Us*e!=OZhUhFkJEN1>y+14q48?fs`LU;rQ2t)0Cg$s6gEgKRN zB>0DkE5_(h`My9h*J|WooxXDzr7`h>4v@4dp~91GmopI^E89ue54a-lMLx9z*F425m&+U@T7cC_c^19?sONDX%XK*kWh(j9jRM& z#R!R6(+hEk{k+W-&@K~@C#gO{-RBZ%t26<^o*cd_ez>7zmxl~N4;$X4*t&@2_JBF~z|w?MgEuHeK)%fOc!Iw7D^+Yk(#7sREQ zFqVrRb!l9TQ8l2u(Ip5DcbclHYXhMbq)C3yhn$hd+|{tlstWE!{sjXNj{9`+xWq^) zesT@#UntvkBhyWh_HepU0r|*`>_#XHONdm@$5}$e36>U?>K|yAGF6!0Tk9B8L{0o7 zMg(7zG5;^g_?27yR4h?o1%(xq{@@k>MSezH2JAng1QrG5ztfA?to+-A+}Y=d72_e~ z;1KkTz?HDF0E|9*Y7p}k*5(Trj2ZUF?(GWS$iR7>DAl8BLiO()>;eq!a)ohPe=#?3h)W_Hpl6 zYKVLt8+bVDJ(g@U&nx{tN+Ew1TT59|!kQwr(+ky4HhO24xs!HIIjwcyk4~CF+ym(1 zL45a?ukzVf=xwnEcBqAc+S^*R>3U={cYLMOaBQMdyTo_I>ZwCS{#}r=qh4F8!*4#c z6c}BaIU2OZKjI{g?R>7dnUwjw7-~y0#>Uf{(0gH7RK7b7Z%qMTiBLBz@9u)y1EBYM zL{;8IPX0a%lbr#wt^fXrRg$LBZl9M&wv?uYE^sT?DQfo<)p&l%pjLz=#LF9zQkM}_ zNY;&_^{|5qG+Fcz&9<9}e)aO;0EDiPBLb5?;m8*&b~b7kW4{=AYMvK)XD9vaV-sFp zAHNNiA~^yAx8xFo4u1^i3wW;e+cdoz)}&C*>SJzf_II99vEz$xPz{%mK+yPKWP6Wr zbB7+1Z$%C^F&C>EnP^8);p7(ljQmcUw8lrkHKZP}5-sMmQKq}KlvHfw-aqrNu3l$`Zr_w{a=WCoE}VP0WieB& zL;y6)X>GoKT4eiRZaN+*pBtcQ0+xkDVxvvr+m)!gAV!H&zu6umy&eFkh?#;TCD3K&> zOhnjTNQLph!3*^Pcj?kpP0yxjTF9LnkjmPXa=hhZv*D_K*gC8;(~scpmSnmHiFouAI= zVxz$$Xz88Z&jdSITT&M8?F(tXO{kty$dJ8{iZ+F&Z@gQ9IG{`6l`otm>V+wMD`r`9 z|Kd>+sJh)e7vSRDEO;p9b+gb;Y=z%3$!4jhb#a=9rG$MAI_0#t8Q#WSGc6{$HF)P0 z``Jk(O~8mZ55Ef%R6~TeG4Zltwpy9+ta(KWDDe?Bo}9U3Ur;s{^IX6V2?85HTXYtO zeW!M%EgjE?#h>q1ZmL~F>rH-uP3SeV3H&Vhpm5Oqf`Tf4bl;4ONDK&nrZPB4IZ5$9 z%)gTQKR*6zCMK--+tij?RMMd*kyt^WXq@8kequ4<`}o$5@fiVuD08<^?t01^L7mbn z+D9{1WSf>jxJYwM{Dr7R^j0J>Hm!4mnrICz688Drxe;jFrVC&@|4NT#Y45S8X`7+D zrFsP4DdB+6IHsR@_OFC2+p@byK-dq9|g}H^zATp$rROKh-phS%p z07M1~rFgH6(=RZ_>63i4)eo2_>ahNyJlBu(aBd(m=u=^TXee`{ zE;|x$BCj$QG_kVzz;2u8nL`NU@Tk5Qi72 zDDRHW1f$E-V@cDt^xz)G8orDD@te0TX*-i%GrMZ3A0K8e5mFsvcA6FFuskQP$272> znjvl;6+TNQgRS1(*M`aE%IJrj2a!DZ33LTL%+gX)Y1q0Mn$MH#8o)1q5x=Z0(djv*02czNkX}3udgAcK)zUg{Rc4ecclneQTuD^t! zbuyrn(iW!kFRm&4iZ;Ptfb-ha{FTIDP*DDCX#`Eptn~nbHgq&n4u*7$=nSYoSSP&G zA35)*V*XpWwJCqpn{LDd(+AHgw`}-O%YP^ds)O_{S_3zXtI{~kQmg=%sCkwz_LRAr zSbZq6Rc$FOV*=|Ny?TGRfkty?E^K8@idHjn}46&gKm>~|lOQ_Fx9yvD(rp4lGKI?>DZpA)16J(W@0v1_KRx+|QF<1MHvy{1N^D4U9qPO*P5mhU!rJo9;*G1(Pyo)-?v~$3fGKC z^0W^-ofxK_7FUltYJvL>uKfu<7<$wp>TlC1AW-3(g2Y zP0ZD)N%cQ1Dq~5ifI60L0^C`eu=5NJv!i!y|DokOi{k( z1xIb`s8O$7h-CK9jaDeG0g2bWhm$hBKb^&Zzq3`XBi*4qHPWt_JmtoK)e!8<4?HBv z*aC-KVG(!}GsodjF2(P={H;K2vL^MCx%jf6vrp#wD&5Io52T++vMc_boFR?UZv8no z<+$pP2+j2Jh`XxONwAI%t``O2cK#IIH5~vy7{fNOFsI$hw)U&!`U{=4z?Va3k zIGYfO!4%s8T)>T>)2vjsuARvi1#|ugVt&<8PII+fG@)UQoJ9?L) z=k`KWs0X({u5G`B?6O)@>|4ST%NLP?ABo+qRt-N`Ldl;sU|I?dlWp4fhd}u3hL%=t zQT8{H)K~~1U*!vSO9}3NU<iCDwGQ1>I7iNJ#m?HbSM+f+ZR}rOG0|A%3VF`2MC3FzH`Jxy%hrMA> zEKh_c?(nmdzGxVdBBYqAHdWio;}*P z#JsveH`hghVrnm~=PzWkxyC-M6++EQ__z#AM(KOppLFD8+_pJqtVgjQ@2A+yNvKLx zL5V2f#T~sGD3%;MBDYr%Tp9l({+2OvaJI3tFtaxKt-oviN0~9`EaEIJ^c1p=a>rfc zq;})HlSh;dg^Z$147VV*wb;dP=u`du@oB&5rZdQl`Uh;$jN@lKvz0JAvkEP+CSdch z?Yki?%!+24n}sbtwQ!cZYRq2E*|wxa6I-M-1Um%5;lFRnpnWJ&q+V%p;{7BA1r?5; zLyS9PUPjcM3`}QbG}2CDtV|-wCsc(qzu6toxrX$P=cMuJ;(}OlrNb!EX7QfSHo~JV ze&$PimJT`b+V_G9G6W#XBW{s%9^H<2a#Jg0{PG#7>;ho9A5=f%1f9@6xSxi@Y!?}a zV2$wKuL`m0jc+-N40_yL#rm*)UuLvT7;cwB`d@B4)J{93_VBm87!B=3c!QPETI$% zxTNBtQv!^VI1#Kq*)O4G%l;&kw1X7JEcLLNCO+1Q)P@w~RM!EAtmrTu=u zdj`{AgLJUcErM~U{XK8#B|}^zZI5}6B;od5vnDmJ8*BHR$#aRZT$mMwF7?^GZqLA{ z)(EzkORu|SWR%`CXQ!_61`u-}w<2M`E%3c2m99AUC^1_DqGJpr$oNpn^{9RgnBB_l z_c|8HC2;ovM?wd8VD{e*p0$#~uk9-=YCXqhv_Pn(I5w-}<7owg=4Wf71sg0;F4k6W zdhbHsO*&r3_QcGF22VsEuUM^xbQ{?wgu#P;PwUV`FO6M3e0Kd@-;s2@@>K6fNtG3i3k(+#fS zo&5ZiyM$LcSw*b0F63l&!oSc}O;oRlyUvQYlI={zxI=rrySqHN@v`eZ4{MP1!kmrR zOoN7Ym{>EFQ`i12JOLj&3ePf6kG(0-o_RhoA%ob%H&4oED|@oprC-%_B)*X7ddk%#`~BhxF|y{&>qe(z&q;*6CTdFXAjNbA)Uujy_GO#=V>7( zR%m^&SahU?406#&F2e1*BRtLjFyNSf%NR#h1<;k!_L8QjrB9zv1~LUXB#dhI(Suo* z;niat#$y}7_{u;e8B1CtVoVnR2-v_|*zPhI+FPLw?~<{{j`T{pjS2`F8rmA7p1yyG zg3U5%2hLF+x7|aebb6^$aTTT}$Z%8I&!uEF%>$N2%%fvJ!|B+LLi%i1qNe&)DR6M; zQgexBNjuV-JC}Fq(RX+Cn^rYAvrQ=5mkAV^mktRy7O8?B!ZtZD<)f;W^haTQL$ozz z;BXM9x{>U60%ooBZKi_gDj`(tdNJP%%4Ir7P>tf)C1WAZz$OOMf|p_4eJZMWsCtb! zUFuI)ysM_BlhZg)Ei^1@A{u7Dq;vLE9CMI`P8eZjFK(y!gtp=lymCdtRUc*fqDY2prNi(nD~_b4u*1J zMK%TjrFAVnZ#^uzy)>0M_^i&S0;iuyaqwo~G!eD3@1u*t;Q1G8XI=k-11cxLi5f5l z!>0r6unl{IMf2D=ahcjOiGbc*B8DgE6H_Alf?zmhLbbk4&P=M+;mzocMf(y<@5X&t z!%tah;aD#@dIh!A%Xkd27tW4#PmfnGTg)!}+XjweJ=i4G0tgSVaoon_G zp=g{H6A!cXES?y#n;_V0Ke$pyNM|1 z|1yi(SQ`GZnMnKZh`_xME|qh8wm0V+ByCT8&hF`FK@H#^l0C zsTypa&T=L?2|!3BP!ghuQT8a*fO16#APW77sax6-sJ)a&!@5)n5cG`@S9w%I?U%|u zt(tvsJq5x6tc!xu{ty1k3N)7p2 zYiq#otfhD50!L~qPK5aShBd;Mrl>!DK5Z}9g(*hJUk6ucP#z%CW-?DqG(cG%T6Se~ z58#yOQwX^y`I_!r|4i5GsY&idX)4^(Z@NKSiHPh$<#?n~B7vuDq?>aASiM*7K zUp));)hb9?^7#jNTH)n}K|v9rGzjADUGzID>`=`R@^?WITq4OR1kNF0^#MpG93l@z zprc@K-Hguwes$pzDv>cjPmx-_Yr$DPTc*|?5og@1MpxRU@@FL8<;5zAHuq&7OM35; zoVoL3>T$!#mW* zPVQK@VKm$^{mV2mEl8eSrH9sC2 zW9>w1ulKXx2Sq{o<#Z(JfL_QqX0yhPN zG_Vv>nF#j*1sXVV;ZXFjz1o|4?1=(?xwJJekp1KX6p-WLCs=nng*0MQVhF+GSb9bO zluExlw4Zsz_eduwA*|lNQPGs0x#T58C;!* zXRtcmMFXuG!jY|fV8Y@IXT770r$9P|VxhB%SJdDU!t@?KBa_C>K$nl8pt9a2Dp@H3 zO09ecqE#Op1iho;Gcjm=9Ac}rW%tNzK?~f$O&7=`;hOO(Mu~&K*|`Hf>D8fyvS##h z`npaK4BulJmc`@kux{|$QdiVm`K;Frx8LbJx|E+VvjzD?-~=wM#bt@dML^Lt#Z=kZ zrN1O@&=X27r(KQ)GW8s}E%}KLt(CD)Y)~zZn%n3*mqNcek7 z14eaQxCNW;dM1y@J#EDTPqe2N$B){_cQ+Wg#{(8vBbV;y)R6g$maa-pmg%?!)(8i# zaG#0IbJuYg7&EJs+@H-yjEh%{CUP<)QXmN}e4>_n)gRV~I>FMmi#6Fk?eny`Wv9bB zRlyFVe;-f!4AFf@m7DFC)j(>O53>Cu@Wo#dC;Cg&{BxxtWFYvVfnolu(kPl)IvLsh z+qI_mBFJmnh4J?7&|OcmVC%qbEBjX&+h)nAe0w}C>!fl})$+IOg7g?X1cP5pk-A*U zu|TPj9%x+sZuXl@o0PwS2^DhOKY@keU*t%W-|M+IH|*x1)F0{bPFPOvJwE81;MHbi zv1(?=j+~3fJ_o>n^X@IeZppT+#Irxe95AsGO%ia*kO!n!qQx;S>A{Oi>k5>|ithVq z$f+HKhBawtudF6Z5U`oFi0$~*vmc3ZRv?;0VLqtmfRH%*t2r^1V)$UYsFU7%VxoZwMMx4x+0ft>#$71Qq9 zwZDVJkV&snxtQU4_@s3u-X>%e6n=7=?Sa%n{;lX60*BYRQQ&2F4ly6B{jy}iS9^%? zuB?MD^-__CJ^H(G+cXiYre3s&x2~YVVrRYD^H8Tj97KWD@Q7-Hx3)!Mbl(r>2>tfKJ=}h3me5${!;tbZ7W@dD zIz?=v(TO)<5YRGJc@ecxDlGC+Y@%E1>}gKGjjiAY#skS60PjCR-~@APj=FBQ&GJ!I zMg^GH^`e-P%ktqa%1U~Wb-ny(h2+##xGtQ-)_KNrA96~+E}moi^1=!c!N@<0>)Iy>#ZW_XdV!=p}ab5u5XN+&M|uTCDoq(64kQ0 zJ`$+}l@F{aq#(qHps9yX$dqnk(noOvAEYZxX638A0l>huZcqi$UNReJko-Xv=J`5d zkK=OHDM}SRAKkP@3nCeZQ^rhx%W_7%kmsh(Sgk0UJ1@-z1adDO+dsbKBcm7TmC(wd72DNNot?76ie>O?Yd%ETa}QSz5w*VgO?`K*Jr-(oeMqGOkY0~G!y`o@!Y+&C8JEFE|7r4 zUuC^t4=K2iMc1jY9M|86kU}yF`{|6%U!Kn0cQeA$QJo1$QhFQ&7N1QA7)v}qKHqfKng)tJvDKNwh?U6kSF6xUr~*}#83 zI|%W1Zw7Abl1s`;F1*)|ybHx&ohQT5p?(2Ghcnu(Q-ICx*j#$$tQTk<1yl7snakiD zjH`3UZAF7qQ$DOehBnfn&1OkF@_Q@pTrFuo&2KeWq26JzLH~2AdZoog41gbf7?!Aw zjfthvkEKI^qLH1GnSs%(>?9@lb50J50xPPd_`~V{#*R-tGDsNycD8dW*GfkmZ$o?` zA9wb5-+K=x2w&Tqn{cnk{pD73y2QlXaLIe>o2vUS{&-dQeTi#lP2X}4THl1|6c#I# z+!2$HWbKS4*YryEj|5<_O!PVvy` zZj4Vs2epieolAFjy9I}~G>@xe%~eEILRMxlUifx(uG1A*Sz;m7EqIb)3xo1hy5_}E zlp%VN?S6)OPT1!*jZ-}J;|!)oOMtH4a0{n%oJ%_@R56T7kTosmN$mEI>TKofgw<-h z394bWEX9=b^52jei=74>Ft;isS1{F4){s)wirw374jbO?`65N^r+0@2KfE;#eKNQQ zzIXZVuI2pT6!dVsUWMQquUgwib~Xn*d4vwr5(!(^*B7c>GF^HYBS|@mmB&OXLv>*! zGy(FZ_=|Y$Vdh7_Nf3>iwcjxw)m zj6&mg=`y ztgE+O>B_~^V_bQk?9N~wtkQ4J1H3h}#S}6*`Wj9tO3y}|Oi`QG%YZGD33`d}GN0|7 zH)u2~cECDh?Xs|0lJ+@FkK;+*owE1LngX!eLc*WRPS_6ZTVMRPMm)NvzDU$WfM ze_HR-oUqn8r(>Y=eR^oE#efmp4NmP#wOFeht_3|`x{7WzGQ8E2D%j5hbS+}{VsfI^Mk`ew`ul$7u-SimRE~1w-dyvxy)#6FoSeTn=GtYiho^F?(Cy| zm#W?>g^D9ec~In6IC*F!$6%?}hJP0S&C;st2*+`%dU~j3(3xo}sw|aVkU|=8~Skgw@1M z>pLkzR_dDC*Faggsr@dA5z#NHgT=KK$nqRIi;pH{vzo~1Oer{Rv(1|MLnEYSipu0# z8^dfP!FrGQ4HF}$=#SF{QfsmKGh6G|5K3!w>5{dd7>a$qtYWXo+r!I#IM}U6C&Y5C z-V1?sG#s8SX@%~>%zl|s$`0WEj!FVr9WGs%#nT_V^uFw^iL_bF>S3~$^R3$7hQu&v9C4ope9Yl<;wtKI6kVYD8XIG7 ze^6z|kx7}ElQjKaM=j6@G27n=Vsa&HXuy6j+?;;5w`3A86iDK=8x#Ab8~gvBZ2a7Q zM?w3)CL4?Z(cf#$S?zL2kq6%_`D($^0-6|yS7>*Ti@>;Xl)JvFG2oHZ_M8Q*h0|&i}dikaV9`k@ED*#WiW|S_SD-IcR&y<1yFOf`heFhP! z$iv;fvsZI!xIu`9uR9+feYo$J+U*HsfQi8HWIgIzZ$U`wj0oBeej8x&z&lW~fszJ} znKb_Cx+=EziFkT^wwGLdBZE5?DmP4()Gnx(3CzTiCK|#l$uK?&d|3Cppp)Qug4is9ytx^}=x?q*G8`OC95^R!hOC~Jv z%4XLui`-uatm`_uje)24?6Xw08i>V2PP9YW zNO(KbLd4O*nf+9I^VcagYWVaFAy7Cww%tmZ)H5c0UtB>hziW+yK11sv zSE=aS=EAAFWeAe`F)(!wJF@JB0L^PQ9{oc9KP%sC@4rV$7;w-v@AJ zerBS98->Q3&WO}*l}bA8!!~t4RxCI`WzAh5^Uj15d&IrE=#Xf9m<_j;7$7Kn=1-~d z)BfhKa+a?u_=U18yhOR9_fZpgm6$MF4Jfi_!W`uUW%~`a_ln70eDxSUku}vF|ID{x zRLF=>gVl?#L+CWtwgkBqdbkde>1WQVP; zSbA-DUVt=iw91Kf1MDw$kg7<_XJcDXy5KfAmTEav>q=OQ?FhO2Sidr^yAfJ9GC_IZ zF~y)s=-Z8NJ7>4fBI7s#n?P;w!}c7ZS>NShE#{O|Y>_LdP=kchppx8o2!M2u4faQE zSikj9q}@G9s);u+^}1)hS`Y5iWsU9pXojm7k3Mm<23x@kh9qpHCik{N?3;Q$`ZsR) zXpsI7biifYX!HMJ%rXBvn5!8nhsIlQBMvZyF^2#4BJ*{y{ zcRL8fDY0N&et=b!s_4tdcI6J0dMRBeU6Wb#Wr7u28(t1C&9z4N<0>$eWD0piM}O;i zB)B#0AknQ4V-K?`UZXly2@{ZR%e|I6(#doQ%mEO@VVzX0sXFcZWai6@UPmF4y>=J^ zeInQhE)p7etuES*V*^Zx)bzb*ieeav19}*RZ9J{4jL*T98sr_z%LBm7WBOF@y1C{n z!vs5Vpq-vDrI@;@jdKB=PdlWvO~sd9VU!ru4-ioVnRh{ERS0K2Ay`L;xOz`+mZ2+i zps*Qa&2Ls`lsHPra+wbueU@m)5M_5jC(qClaV(RWtZ%?RcFf%VQ&XJQs{$B#Ej$VaqmC;W7%;2>Tb&wwM=ugIlD zUsQS4h8V1nKm*1uUc%Aqx~h4DEp@%3W#eyeXv=^(Ai*9Sy*)a_eTCPe$^E;B&XN?+_}iwBB=^=ej7R#XBADEzB+ z1~4%&G5izF=^q)On0)lVPB{}m1#AjhSUZ+IQG@!=IDI0WSLfn4vcTVCp=xosdGlr8 z>TAgkx6C^ssIW0~+mVjgiU2b<~2B4UKFUOhxr&xVSTh6or(La~mP~ zKCSnp%bYV4_f9cg<5sRSV`%rCSB2i6H8r3PQ%ZCr7I`@uiBmC{Tp8ZZVLLDNbSLTw zxQmwoNxVuKzut>ivi6r`0fT~}Xk_4MXY}KK2-?_vu(A77?_Hy;Bm9?d{Qil_ zl02W?__6C7)W~kx;h8O$c(pYCP55L!5)6(QX5)kQO}`3#t&qtl5>36D4P<0~Ts-UZ z#+JLaLZ9XWvHncFw)V&(e%{8o6H4rv$SF`f&@(G|6I|7Kh_d0q>t}=tV^T4&1X%6d zX7nB?*-4Q*RIP7=I>5 zzUH<y~v>%Na360nBqrKShfHJ<07a;bQaYv<6H0TlMhM*pEl?y(A0yPTYlhQ z>Xlnc{K73?eg3Zpti%nCtbc6tlQWS1@G}12w%0w_pBrKS0?=>gONDY+YwwbIW|&1T zY~=NPtBYi#OhUB%7Zq3GC>H0>U&`YJ?UL{7v_FP+32ZXTIZXP%e;i9CED_u)Ez=3K zzJv}X&|qrIIf}dA*Md2INt&v#t*KSalnFYEZEZW2Wnu`H#L!XgRqVb2NsL(3r|uvj z(2rQ}X{3}Ccj5k|8%*?+yu>!xoijtD1Z2W$ZQlW+cAmGoTbV=VHoU=>kA;5yz2U6< z9pAN;2H5v6EW;!#4#YUx%qVZPdx1%B7*;{FGdE^M+iH(+vRlN(7cwZBBnA}b$N$zOr`yRmZNGcgo^`?u>(CAW^qN?n zLxH*}dwYq15t_x+A*SB({D^xlel&?c(Mggf?&Ikf!Y5 zQq{a^{Q<05HmljR$Nh!z+@6Ogj;aclA0g{mI@rjN-y;q>HiINzmN2Py`khs zr2l-P^xt?ox`LH%q0~~@48nF7#gwE*c43rI^zK6UZAb{{LEE8@h$8fg*0sj|tuPhH z5u@HbC9~9*fsZiko`}s%Nt<|y6;T5#LvIrbpPqP_(EYNIf*Af5F3t_`9U%t3<3?8(fh0z z)1upbe(B3SGhp_*&77Im2kfy@YbGssOABwiNWf9Yea88WL#cTP1{3E^vN(OP2 zxncn)x_$U6KLUWWv$in%pjtMXj!E}Gb)*R_*3@+OEt#%kb*A%64C+}qK>VqU4^3Phs##UDX30&QVur47Be-3C$swSevZe9ZZHa5PQrr^rj6j5B zdp53i(evK;62Vnm`{C*@bS-wQpYsoiUN}AM=%Mqv%r3?x1wdQQ zfJi%bpdLN4-}|3yqh^Cu!t6gCM{QceRKKXvGlnv+g5WSpRPQ)tejHpPe}|0}Z2K0v zFx3e!gChtWC}Y`o?$zxIcm|oelry{Lm)pGT1%HIe=XE-y+rV)~v8y2~L)6#qYO>Rk zFT{Hj46zO1-MFu6B8Jq;-U|r3S;Gfoy-}(%s&9`-TS3`}vuUJRF|i&DN2&`z4Fkfm z^>>R&ikZPCYiGr0fLuT|;3Aj$DHbCOusiABVmrSQJqaDrw>S=sf^9@?@FRX35n9<; zn>pIp1E|J7knZ1(R#Zw>fM4nd+Wii2|3n`tr%@@x!9Dq!5S8VELp5y>6(B8~gOI5y zT%ztF&vbhj9_HG+YEjYJvmPgm-yXvTQx1>5%kOZr+&tMRzcK93>A+^~5{95|Kss7L zIi_^qxYgk)I;QK%TkUY(fmI(C8_WzBO`fxj24=wXvEtZMdn7+xuA1$d-(YC$*(}Nw zK7r-)NK*vVkmM?TbmG7W!J|qd({d#4m)C89GzP21MG4c76bUG&1IclV)aatoNd7q- z#5v@uIR{T25#(9}VZ?l8 zf~$y$>531^+crQp`1UFVd_m=lYrTtw<^}aGaxBn8#XsbJpb7)&alJGR>)du{sj|UI z#BmXSH!}!fA2qJ{X$2#YP+}#SY&wRNN)+D=vSKY~dwa$LrbgRy=B2MFg&C*yUg;y> z!NQT99o6}s8r}ha5^*oV4EqFGRPZOv%Z(xtxGP5vKbi^a*D<;DZ#YH1cz_|QX#3(_ z9OP^#?7KMRe+3#@lGTvgqKXoFNRUE9O3Vpy*T%5Wj zdm%GBe~M3v&IhKW z={=I$M^(g!zsM3WCw38w&m=3#RaQZwVtp0HLI96a9+C9Xvt)i8h?1u*UG zJgOqr={UF3RwBan{(SKNcH{p6%~R5m|3LFZf1`OhCw)sZ13<^p0N@J#8-Tu^IR4>- zSjC>G1@t|c^Y%G!7^l0$YkJCvca=rm^cICK&H<$LrEdk`UK}W^HM`%m7{K+wX8vOETbK&!HLFiP@o(Xf708 znuLTO1BO`$->Orm+E|H+=?zJ(D^6A@vR@HNs={7 z$jW25Fv9L~wl0Qcizo6s{K9giWVtBQ_mJvqM-37*TL<|DY3@@f`}A8 z4Bukp_F2Lb+Sa%4UMKE5SD9JWNw7b%(zzA`)qL!w%=+p;5`c&Uy!(%a_cJkvMu8VH zGco(7%ki2}+}ePK{uhS_1^3q*gi2C?hPWa>6CFFt57QH6{$nOtGjTcShLryJVElG> zWOlpd@GXxyb0kf}?pnnyr1Usl`WGmelvYdCB}|D%*HRAl!hEq(8vDU-#vt4l`$v1>I{4dpU5^Bo5L4`lJbqPEjpL5j*q9ikMBmrlg88g&X0qKMjfFj(CP@*9OT>&>sy%@8ls=BY?|A1P_=uI660`Os$|SnWm0O6T%E zGk;kZ*XEB3x;=Aolf9b2#s@fZWHOH7&{^?LNX>3Z2Y1k5h` zE~gWfkKn!WEG(4B-1)?$n6nCp;BY-O68pyp6ibM@jPakGcsYQ_llcwf$jH*;(cyf@ zIIyS!PHwi}0tvq&H9H$4Jqsfv1M9E+2NV|L*V}^Mqs(n zmXcMx?(;aT=kb|Dvjd&c#VoCVz4xcxtBexe+v&6PSjVt!+{98%-~GBv`rusohkoyD zn@B~MnWRo-##kOqTBn(^Ao1aZ3MB~31qhUPn$Mf0Sn3@wKfbkdix!eIeHpLQSc7y% z!LOIgKTN_h+j+(yhbZH785DOIFdf+<+5QGt29kRPH7p+b!xx8p82LRENX$Fsl-U6Mb!$jU45}`b9I0S&j;!BZ=OOZFe%y&rb8j!>@MNEufbXq~l}?vqn8= z&eeg<8{irt-+G6$;jryMtEO-t6Yb?*!J@B*A2LP0r^E$9!+2-vKV_SV`C=5wbav1} zVYEjiYNhUCLDd0&Kb{?|eR_>;tyb~S3^&KSCu$qVw(LCT_Wl~((AX?fTs~(D4bucd z!e`d9PP&G1L+?PVZ9f@-VIEJCi$()jAqMJv{mZ)r2tI>ir0u6})(lhy-pj3#bG^99 zM-z*Ah4OaO5=^+^0)6Ni`8-Y^m6j(-IidJS~8n&cp4%wg$9=u* zinJeTDvxI0f58~MouviyWht-8#8Z&06eOBSoj@p)Ix34Gc(Ly>xqY6hnpn zB39f-Kf0p;OuWnTH_x^xKnGPIiGVMkQkU!a8SLta)} zp5jzS6a_~!(f7tQ$QR*-f>&bDZimx%Cp+=oTV9le&|uYgHSq%3WyS_dE{S^puX{a0 zm`whvcDMjl_=`a&%%CGkxmJ$Awvz zb2Z*x;i!xA=J3wLLXGe+rF`(gw8p7v8R~EV9D|+M6g}=Iy1=;uB?`ntU7~GHnx5Y=3vVwL+^rz{*STq2qncw#%C=ktdfs z>@2n~N2&H}RITx<93Kzec6Gc24B6dDS*~B6&eDENNJcU)OJbgL0`}dgxD3#XfWz1E z3{KSMuQ&+)a-(%d69Ud7MclkZK)I?QV#t0WFt(ThTT|(hWca@0GM9!R0kRTfoo*Qj zGf^u0t>h}0m0~XU17zt;FRS0dcESrRa=$47j@$*sL$^>}G2H?CSh*F@D1Y8+0acA< z^L?bG)j;wVI;oORT~)$Dq68aZ=#Yt1L)baU;Kh{7BB=+$|> z)3OfnjCLT|=DBl>NwP!+_?l6Ro!Y{7E(mY6bcj;R{73Gz=iZU%;BfO-5IPxaNPNw_ zFTuRF>!`=0Kjb7l7~X2UDvzzc-VBgD&pD4vCC->ewP8_eCD2w!YjP;;^tAEN-$%C; z)iAn%M@kb}e6(7bd*4y(l~=jSt74OV`*j;x>=D8TS+%~1L{<~Dd&7>Bq~W@@moUVWN+r^MyO!q zV5DbnU`i-zZ{uY9t4j!q0xPblAo|1p)c$OGWqU6jE>T%8(lV%2$#H|*RvOmXq9d%i zc2u)mubsMbp03QTzXHjUFRz&fSqjC~R~bB_B3rZ*OnsqGBl4bPh zMwBYq85U$`CSh!B8RgAo+g?UN3~{mDvEy65B^lL_;vEl~IW3yxnIcA_xW`s53vJ_1 zGbyeb;pH1j_WR}e09m7&LNDb#2phAN#q`UI=~)9(A5o z(yNz)NCg_XQhWp=*(r4e9P-vdoEcvB1<2s8>PI~Cu4pizQST;b!K-;ZByc4iz>y#N zzHG>$cnZcj)(koBMiKzOn4p`{v}37-{MboH_eQM2k25*nOE)Yhm%?x4W2-+HPL4k) zfA$>s8BSOIX`yEjr%0g~Wyp$is&hMmc^)@b)B)Rt;o1Nd95ng!_3{>)zzp#OnbxM; z6XvdMNSVyeVmL?NDw(|XbBo~>qu@{MAP?~1zk9v^4W`HNPtE7iwAoBwf9OLfi3@ZI zo;TwzrE4bOaCngQE9mzc;i$`7cDg+ug%glMBfmhbUsIyverW3LW!M1S!1u#s3!fKW zfHt?@0dlzqk@q5AfVNK6bXh;W<3D6yaT+p(geCX_qyVV)$o=NR|GC=ptL^|x^v~5E zKs)+BD)^+F1u)ruq-~(fifr`+8*kHZNqO{3nsSFWNo0fiiYSvn?2z(q35|Le`~k~> zxFIqbsD+EWG~4oi&h3?C+3ASkQV0)4BLdVk(<bg)D8QAv4cng#y8|2)llp?SZv_oA}mZKLQ4Oi`PN5=I25q)+{!7>pw!h*8WKbYt=09Qu;v)BfMg8!FO`_{vCvNkjLdv->U z|IcFpH8oE@(^gd{=$@*1uvX4tw36+H_fp4Urxz5SSI@_k8~ghoH4TYVQ*B;zkebPZ zr;x79;+os_rF!9~uX=9>6+OfHnCdHc7 zgnEA=GKEM+O$?sWV`;G>Q^`<0?&wf?WElUj7}-TK|E}<2;Z{4GB2wj2~+J<9TT{7$i6lbc*gGps(T=xprfDn!4@d#v zb^fz|nEAi)5B~#l6cvU~y!X;--)EmBy2*s8pY_0xBC1-|1UJ!%)wV(9nqRTkXlr7+~ZE-v-1)01}0I zw)Qs0Mh<{q0gA+e;v$6nEDS7+Omr-4e@wxK5-lRX+}xk#FmL!sEp2ow%zM=%4uELa z2vYXQnpY3uYLTb9B4L}Ut5(zz3T%fX?5u0%gT~TcA#Ll=GuW{$g2CFAaLaomK&~5r zzu9k(x9Ozt>!@9luk7S%E@PE=Gjt}&0{^n4y*t3=u~6=^YPXMeidy}hJG4xqnF2^L zOY-5v9jZSpE5TqatIzyf>d!q*8$3);CYLdDhS)MoPC@>A7TgTG4kws)beH_ zMCLS^+cX7}+VES3(g-Bk-`14AMryPJ%q)D9_1E zyyL2n{c!PAK{*Yc?)D;kSe3r^{XlfJ$t5Ow9LIy`!lY{=J;6sRSRJjDGJ{Z!mxRs> z>yITe`NYNP+B;-nUZxbeyJ3w>NX;13Dd{LLMXr0d7BVA!`lB4JG^+RPuS$VaHAiGUT7-42HG7L+EQ2Lv3f5p2^^^k4nTdNQ_%dfN-TZ z&0vM#^bHl(V<+jy5z0mTjC&JH_mejBe~s_|8sGna#s>ggzk5D8DSC1`((geZmO#>8 z&)f!(GK;(G>j7erJD{lpSQlmX?WON)q<+E!P*?;-4So@E0EO|#h5^7>&>z%@ z6-tis)s5-xCJxnRFToO5-qm?H1Gz-NjC&OZjgZQ36LJT_&Z&Lcy`;oUUz#i@Jw8xe zxs0Aek${cn>b_i>iiC^hSgERdW?Sq2NUh`B1kx&zj)PF!-PLIVy1Ih%p1Y=U4G6!G zNwhij;0Xq`l_g*ajznAWL&H|hN?&!e}rjWq2^2%P!d>KV5+VO|z; zt+Z{^*t|VwXk3AB04|xhy?EU-XF^8j$N=!+9()*d;%-fT@aMT(a4NVYK3lGjrA`{} zXQk6Ypr<6$j3q@NF^tMGEkl4-h(k9v4QEVR=-u?VnuqvD-d zJ;zkQkg+p06Cmq=H9r-`@WUITy0lab?92)r2N8tlJn78j~}sEPPbwNqwa?05Xr=)PF6e50&UZVz7%Q*K3E0WIP9|u7oD>7 zkAyhkH(NqHr&;h|9O@O@dG_JuT#N6c)p5;b>s=vTgWKReBDGD>L3UA8LdlS>e99?| zF(>GX5e|9tIl0j;4-IS3(+O$B)=`!^4KGIMQH4C~sX54e;O-5K+Df0K^FRslq60$S zr3OeSuWSaf-*JwkA|QbxdoP?aQ@g=`qNG7br z{YIi<3h#Wjwnz#IB4am#n(jI%leM`SNRbQ5r5$2c-Ql1p%%Jq#rb1+)z2z0)cU@H=;)FtrxwsG>K)z>u8ud zj2&o$YJW`uZUbVGanjOG*`nDY7sYRD7hEmf09F)`%TQ5%Y=IG8UG-$S866Q)m?Snf zAU%8mF|K0{El`Dz??M}ywG#^u@c>RdMY>5Z@5Qi!@B?xx0WgokZ@)uBpM1dCg z#)x&W)N|JR^&)^tBxL4bWL_}_*(^K4ar^HQ-Yw6h&H*+Bg#FfW_A8en$wGH&O@K;*HnG&sB zQEtPuebdyw5ggu+ZPnpSAT-I!SPv04#X3t;W742RxeHTFaVm+!Qm{biTsqjJ1O1ly zWz+NpEqk9gjpwHBL2LPq1`*5Q!43L+&CTc@r;ejf;KW_Q(=hED?73?|$x3cOqH35^9r4V-m>hof6eRPKMg`VY zIs+IvpQ-@Z$l6mMFCbw3ONT{W9%X`mw;~d(10GDZNc5xt#~7b=LfLa)e3^ApK%|#V zMwl<0xRt|}M=XB#Md!r<4YAzvU1zmfU7g^4zw3UjxsLkyFitJm+0qDBe8jPR`esgHEu%%}Rje za32kAB$xx|c>@8g%;;4K*LZnVo+qH{$$}K&q1^+T9^sb$!}` z+#&m+jSEmL)wBL468Sgi{;tXZz~iO>KP2Fl53Cz&%6XLyWs5NFl$z59Btg57SAAQFXi?z z?t_xAl){Wc361!A^k}0_8}C~7kTl`UtGxgU#fGX7g6F%X=uGG$YJT3TGfawOboJLv z;@E`{^MxT(p!V(zTVG72tzRb^c?_xFoo}rk)4jeQ5h?94II!_yp<;4Cj`vv~A}T0LF_~naPSSMH#}cCs#)Y_dOePd}I0c`= zsmjW2z_FbUF>BJ8W4qK}c@^&3ezWJ@_+~IcR;PfviO^!)V)Y6RptgNkZA^nm{xwO% zBN06c7>nA{+J{{Y!q6Ys-@jWrc9o$o_yg-c`dBfwX$ACKBM#fnv@&Tcr6Edc$v~VM z-yK_FI16p&Qyc(ffW4nC;|EWWZ%70SDe6YSyNs-2t z!Gzn-$8q9*vmRWx$#yvs<`zZG#!eSe0kNr*AL_)Oz=YoQIT8HbPYVPfpWiJIP&fOz z783>q+Q{m=k`tgC@N3i_8U@Bk+R4z=9sq}|4StKjKR^02Gy|Fjf`2Z0j2{#=ouz<} zERYjPTsSbv2dE%kWy!uq{&a_uN;WZm>xS*9gXhNnpm}2(xe=>yz5VJ|$$t@VdO#zP zSG&I+@|dS%EVZIkMSzn1mF--6od-)<=~su9(RsWu|GayuXf##}TvE{$P70Mc^O{a` zj&$y;&LlQ9@NitDPusPPT7h`}Pp&#J!lsyFc_I!>oKFY?&dvRp7vPgY7lB8+O^Xa= z${}O<^)Z#QrQT>HiC=k737OE>p!<5jgx!1!9^0mh@+#irUWFg)>DJY zEfeE$+|_X$F{Q-6uw3!`~B3&!zH*x!O`vp)dMc5wa@Mb<$n4keHSrv;y>E*w0f z$tApMnSd~+1WM65dd!2>vB_36hFIPJ-L1T;oS*c?02bAxNU_+QA?3V3)AMV}PD?C7 z3CO_RY0s}jrYB#{6*7I|qQ`$+0u8_ssQq>XfSMl_9mRJyj6y7-XRBxZZ_f9Z3dK+U z&9BW2C@4R8^xtpIw<7?+)<0i^N0;I}+-PvlVW~96a1B)954+Lmlk2KeM2H_S9@k6f z2nx2BJ+BYf8$p-i)gHC%JtKH^fK43H5v^;1`rB0emjGISiJr7QNzHZ(9zK`5HhjRGqErN*8Kv0 zV`TXYkijB;uTTNja{tHaz$hlW5w?2O7ejRTi1?QYp8nIl?4&EOC+Mp0<`+tH1$_L5 zjz%??;Tx!rgkNA(FV+mtVJO&>FGtE`gw?~`W(1^4JFgK_yqT(*g5aTJFLKO{Rl&`+ zk%A_)$|P~G?OS@ijrV$`hI|6$TuKH+JX?t`W9Kt?7InbDbF$^cK^S(x3;YICl4Fut z+c7J7GsNR;_pV7|`Yyk~sdHs#InP(_g8^?~N>>GBsCNBwUWN`wS5F+1d^DD7*8gy; zsA1^gnS7TkmVuB^UVwRNenxU$1;vL^t)AV#rH1Lpt={;*B}ze7<$puUBB6W+(vh@5us) zao}syjzZOD4!tt4h`3K>52wojZG{za_43lx6c##u4`)W=;M}g*AnRM(-tTsYaW_u~UxbG>oUQCG?w&Ix1c$zf0lE)1#uA76uU zB(=`aw#{3n?^^l|~_(fL_)LBvxCAsvLW5-fY#Pa%9Hir7xOLa&3 zC7(w+tHqT~^UVjHpH>L)1ixFMxbSbesDPfOqnVYBz0q$WQqWD`$o|Jf*H6V7z&PVy zA0+(y?eB*8M}M3P)=XTI%&oddYM)x?Hbb$sxtHKuRryIXl8PeLa6Mb=s1gd?YMGp7 zOg~5esWxN@`GDa#MwJ z-Q-`cN?rwrr6%GQnKbgk&R*XtpwM>V%-Y(XMs*+$K@6vEE)FreyUg7jHl3LY35cOg zb##3w*VcWi&j){~U4>K!nNi0~Hn_?&*ocPp(yE4VzX<$tFauhy9l)dWjCN$!a?8$Q z?=LFTu-XqBoE!6s>(6hr8B692QBA28^wynDnQv@0{Bo03+U% zK?Ty$D`eR0KTiQrR{#AJ{(GS0Z&9Y7pyXeB2ZH~oo$;8bSMEWIh_~NBOjizZ`L?D? zz%GHkWm@4=MaAuT5_2qz2z+qDyC!yx{pR(FrvXTQu4ZK$|6KEX{mQqYAhup&y%L}H ziIs?@n#x~x&ihp zS~(C3$^sT+C<)R0YBYiVq0#hbEs9Tr_TtxcYfzp=d~emJYdKaw>LJ9*S(|Y&cLCN@ zM&p=B5gqC~q0<8rw$yo4(bT-$(>1H(uRXc!1a-xW*2B#Y5{l^9Bx>rl?GUGz!MEM!XLxk3z%^`uB_Z^7(&F8md zq9>D~?1DY(Yn%M-N)Cic)Rs#ZGmyDEO$#>V4-r^2(dZkd8vV6vE!z0K zRiT`Oko1V4^}32e9#LATLup6#HWJ!~>O3|*m`q>@>S6mm@v#(#3aTW~2vtyR zxvBhKBc!9@iTa8@yQaX?SJ?1G9R-=;dtip3R(C!4G19!3q`tT1v@53aASJuN?pnFW zXw3dHoK;&s9q;WvQPO?LMvX)h(mummhRi8Y8U|v1xH<7#wWE!;fl>#o4u`Ef+ zrHfx8-lQts3ncJI)yc*F9~Vpq;O7394DnAL&p+2bC(zgxsuXHI_Nx2!XepF)2paiH z_Ma!VEVCrd`Appse>7s}+?+sYSNkO!a)Oi+_%+M6u)hbY(cB8G^yHd1Yzb!%S{sK- zzJ`snnL?3!)4+jws+Ph8HM9jYIuv5Sg!vE$1Q4s}wzsM*Z}@>pV@Ppto1$`cOpjMn zWgr%WE6uVV_FdqWL`?QB`fOwl`+9t%Ede=i_yeD|fj^f&C3cF%_?o&o=YtLUJi(&` z;(?4GI!7rPtm0YO?8{0EP`OTOF7#C)CNgZN`yU6P3-Acqzj=iJl&tl$?+*h@Wb`*z z>sM3mU+V*ZECg)6LO6c69Kz^iGFnF9#UW#rK23{yVc<9nOk~dbB|r{Rl2j8)(2^*_ zd|(#zO4lrE-Dl@rYf>4n zfr7^Hq~qBt3J+c7ZNuIm`pXktBXKhYX@3?QO?vCcEQNJw^wpvlJo{J_Wv+&ZEFKYh z@g?WSHX#%dI^HC%h532&V@|eDR5MBNO*p%d;y$dDw=S;ZN=KR^>AAk}F@l|QnmVW- zGxOiQ6y;NIw`Z$nv1ENtnbd+;hb}R@?x+S6c2@!_Z&UnQx4U`6l}kTW^hBg!*qd*1 z>I4Z9kNQ@*k8B$_3Zv89ZoWhs8;P+>;9B1jb8Z~Ul*h;ND}I!IS)Rc%Q~?38 z06)G>N*K@lEia$DTV)<_>{L4U3$#2$VCotM=fVWI8u!$DNJLj8n@vFMMtFtoNckBz znLlNnD0nXc!W_EBT-4d33r$pSL?B z8%T_|(xaW2l8acV!cya#(ho<%s%~C|zR{?sjUcdcds(1h+8w~WW&F+n{+0ABR`re> zYXNb(($D1@dH|Q8^RJfps|pVb^>P~ptkm88blNPHYD&M@aP`(2~StVO5ZmPoBdQy!=9Ptb(85NtOf-u0y)&N zU^sG}9*sOWgDPO*v%&Q-q)J&Tf)&L-1H} z-$ZRVze<(6$x?b2igqTMV(9BGfoOlYZ@(z>iq!|vq?l&TjY}0Y)YYoKV|eBStB~e1 zzwQ5YLh@)a{23vf4el8F;Rzu*la7AXQo-mp{B0ZYCaxOj#~7SY$RAF@004jVemjc) z947qCT0v2W|ACM43krSXr2GTT;6t8`E+}YsEB)m(#(Sy&r_#@l+^W{|2JJgwp|O!~ z-+$T{wH}H_9zfYBNWV66B(OxR-rL?ku?l2J5vtOD9C1cm&}_}=yPXr*R&`#9?qV*I z4BRyyV%MNb-j6C-N_BSM+)^cg*X?BnljgYS+_ z7(iTgv)*lNya56q=ekj)n=7Q}Oq=YYtxW@@RKl%rX5g@gs7%5*MT*kme+F|~#aKwOasF$GxbqI@#E~1#R z@w#vNQ$V?%t`iEpOUPKS}&ObU;5(|@#JWd z%p3NtujF_wNPhI2lD{!-hY;CzqaZd>Vm zx#U+XB)5%f{4eaAL%;;q zx1Fyv021|I128BUfBnV3HzWVx__jDVk?hwBKTPMU7R%*C!MH>Z=HwkCk0ojXLlKSu z#}&xXH7H&S_Lu>wrf(2Ovj#v)hF9_7sU1l;IDy; zg+hpG3`I_uvy(Y(qFZFg0@|~SC8WG~#$|OZowc(hSz|I*h3mj1SS& zX_A1 zO@BaDXK_sj6+A{nv9w<+4=KpDs1qDBSs{4_#m(K~0Wt(cA^qQiETHuUV9EZ7LKLFP z{4%11Lb8A#-_|{gi--&ID+v?wD=Ue~Du^qo|DBlsz1mGw2+(}{UJ3g4F+v6A|EvwS zn2UZo;OOX*cESk93E{2$P^TN^9E2?3rDDtz;&)_k0G|=Q%JdpUUcMXaQC%ru@XVq{ zs&ao%M2oF2r+@}Y^`jqJ&ig2<$`lFc^Mg=Z0~xoT20#GTsygQfp(-ol$USs}KGfm( z^myXSRmPNtT@pg-p_1blL~y_y>hGgdP9eixZ+%$ac}kX6{^|1Yq}=B^h}+)%An#sX zrr;nUf|%WHlln!}NJ~s3|21kSNl|xMs<2Q^_5AD6K8h_l#PT=8evufMEM7@FA*q$p zOi-7vn>36mD>2eC@SW2ojOqE;dGC86PH^hIu|-&}4GlOyv15|#nmD1(I;qC8wziq$ zkjkC2kU~WF+5#I|IU8@rEL%f%==suA)6hoP7EE$-)x>OD*Y|MRo?FHyL5+%fVRl?J zXCTalmqXTTAdJDliWYq4X>9fCPh-x~OXA>n!Wf}SbfJtT!e2~qCNwb}hihG*BY-kb z-5PPQ`#|f3?AxG7C!#H>l!9O~m^W}v*y(qejMNmTV!2)KG&1UEGTO+53SQ9+5^G zQWS8jl{lx4mV4j?!1d4(T;P0QLqAaIOR~Z%sdhUbLYmsvnFHriKu{p)%RtNXZp$oE zMORn0uP}|X?W+Fhlw$6{5I>11gh;dQcL5o&@px$XsoK5siYPOOVuD-K)0xJJmjJEsccYI z`!hgpad;_CdnGc;cd0%mnJ?O{+qJ*Gs;ABzlMNK{-NFp(2O4m%kSjkP#2DKWWj}s~ z`DHK%O6wS4Pa>(AmU0|WCoNawMGktQR7Sq`7xNn8AG&smvP@P#khNsXvezN0VO+m_ zBNAnJ-Dwu$ob1f4t|j0c9GYb2E_S++gNy@MbUd^siN6bPP`)#B5*y2r!My^sE&M99 zK2->d%DW@Jc1q9cfN`;RT@nmv#0xi}JL;OKx*DmW+2?#V!uB=4ZgG_XrceU5v%dFN z-^%+v!+gfcP{_^Jh8@8Vn2q@^z?%s1uta#(N))d`EkDC6$cs9*k(So;JFU5w>b&C} zY?ypr>2Ttf^yzo$=5Z~-2CXBspf#te>J9PkX~wVYTo5L+DP~o>4kn#np#EBf z(Li9&B9o87Ym^+jOlhr^^AxfHv*z>p9opu>Fvsi`at;dhLS%&wGI!Ps@E7%O@{HKP zgj4Wyb#)zk`Sa}carAo$Y;>>%+Cb!u3d>0_%^zGri==&gyYEUg9gw5EP_OKz!xG|54OTg_7I6Y_xO(I?mW$PcNgHjd=Y=Gc4lTxS*?72xSGb9uj`a5O%128IsM;qG~^nFtme9@qL zYloayzCs!wQhYgfMaWLBrVD%HyHtdhz@W!fS4AEW7PgWJ4eO8L9u-|0w(F+&hCYDu zD?%~@i}T3ZDpz67iTORzxq{w$CgD86R@LGYrr{K_rp~PzCUcAEXg15Oh0jbS8&4-r zcLd|&0xjkfIYLc=|6I4VP=_9KBKnj1`*3Hg`Li5JII3qD`=t!siO6W?K3C;n!tC+S zR?q|Dp7hC#mc;wP6AT(TA5r{}j@U8MQ#kCickVtc&SKCzl?Qqem-7OP1broU8>m{^ zR>kP?#)Yq}jm=l7X{}B=DlRx}=q=<0NUc6+Cmy`e!E}-RaXbD`9ms!xR2O&2X%%=` z?8QUkm@aP2@#&R(Gs~3t>qOTR3uh{Pshgn3ogPE;yk6dYB8H`ENx*<%Y{!!3hlmDh zg_TBpf~?!cwI)`kH%e-q#+bs_zgYfaDHp*T0Gl}8FbujeWR;C7g%i4Z1XQWuSrKDd z#fCDukNlzos1lb7Y01TwC9QnSEqKMxb_dGQR@tl~*EF+zwcf4fJl&Q;`Lg6WLGNOa z&jxR@Ev|Vwgt9rk-Cje*j+1xhViNj^{3Q2yF{J`@I!d?UJT|+kH8hlfZnQg}MxU`9 z9mGXQWA`mISAh_XTO%)6%bTp=O`Hn($kD^scQ;Q~ryQQe;oBw&^glT)CV+a$|A`y& z&uA5Ex206b=xDT&SMB~?m1>a?H8|S&3CuI1V`iPcf{w8_!RHkmVKL@RHLs@V6Xio?eBB(nf&SkwkrCwt>x1?YP`X7A7 zpQ_~0C}`4V2KF`%HpY&CY2LqOJXz?NzPV#i6zad<|5g6^jm}_1_!pB5FeV15#QXyw zpK0L+5pd#1AsO`r3j&d+H7|Ut4`Vd)btoO*M#5M=2Xrw#=B^&SD#Cirb9C7)pLVQy z%#QYRUJ3c30hn>h&E0t%BKYQ8a)D2Mrk6W70lE^Q6-RCeoMUHSg|2{IJAh08fc)Q1 z@qgwk0z^-K`m?`kkpJZ?{z-FdNV6@TF@ho?1|2$~RaCz-^dg02T_k!i@5t5MMl{Cs zhRj5c5idLce&e%XZUBfXu-1DqK0igomiB&5)989X>d%)(6C+kwBMG-2r4sRF@N1HS zc)^gdDAB(DhRp0>51;ofYw8K);@OPk-e8C{)d$w$)?w_;38ybqlU5G(*h-Y^epszp zJbd7sRkd&79T==$leD-Yu>|b^Z%ybQb>KAiHT;BKN^>g;ehyKe=5Xc8sM()=Pez$t zn;H`lGtJ@0NFtDLwFPA}xiSSa$W~T9_0(|ENvqyOJ$Q zd_NOf_Hj%!NmSV*N_fD8JRkwrl>nKy$=d>P40xUHVABr!;G*fR8e!Ot&{`QRk&V~@{os=GT?^<{j^E&);q$n|BB=n$(OvljMX#E6t!bQ5Mc0-j1&z(Z|5mw04xn&S^f4uuqzz zrQTFG?3yT5g<0dmxpiSQ^*K&OGEW^MJC2EB6`jdR=KO7Kf1*qo7Mg;@6PK-%?Pvv6 z30f*6S8+{v@u=YA)fe06qj4n8_%Rs<^l1`4N|%}nF5uUeM=7tv7iG+ntFNQ@D;Zq% z=HPU??{fHJ@!nwU*zz#%<}`HaZ?JBp&5j%yU@|hW%50~fN>m&Acfen9t$MxLNt{`18(@$nFN2QU#L6;ue6^Z1ciZym0etWa0kueww+a#cb$mBz^DE z@EwLPqqVk#R>~lLaBVtv$E-C9Gl}+knhy8L+_iJp7oKKUI9ve}3$TToxh0$MbD=EY zl5gnL0^qgHemfCK1^S-?8PKqJGW;sS3WNg6isCZDii(7CQv6CHvI^4Q!xYW0atkOd z#gEVZOUd*v=lyNK@UIB<$NaT^<}B__9bYJP30$O*W`0ICG@Bp|)CaB~1i@?bmv4TvbW?_Ao^l-~NT-E%J4+#165W(5@Y zBx4~9$nSx~3iAo|5K@xE+>w`7|`m{i)j7Y&(z0a z1r``7N^QXspNh1>qz(|Rl;%#(_RjUuhkY%MxR0&~=<>=X2T_i~fPn`a<<&ZjIjW2$ zduHiD^!FVDV3(yusDB`ID*zz0_{}K)7d`hM2u1%43rX@|8>x}6$S`l8_GRMvxBa2I-Je zq(rGZQCN$$_x8B`^!RA znUR}9< zf(e|WCk%Ua9;+T}lM41c4s%K$#yWgvwX}u!UFAxkQiO#ZNRpNvW4owNF49K)`=_54 zFM0{ZBtB3!!hmMwKPEU8qGy!7qAFX7C?ly+2gHP^#wLhRFL8;xk%5L;>p#r^6J9{V`ZLpp^`3TR*B*SJU@m1#wXbnX4tyaj3hODjS$hDqrpQJOD zZa+3fs;7sjiVj#zOt6WifP4L7omOKL+Zr0VK>rr-iqK+y%bTjhc zWohTCuup5VWPYAMTj2S#esKOIlpkbxB>~pf!1hUFVj*Kg6Mzly7eiZMfbyOTM#|R7 z*}~c7Hc$kte-pZA0{}qss1lIacG8rxG1+#r=5rn#b{BD+TO8<0c?pFFc-zDef)jSQPE$oHAjAOy;j2E79dhl0!va@b_E4vBXo@t9AH_&6a}l>%-` z$7?J;Fzln`ihZ)O;*otD6`e%1><<;4Z92%)hVa>2ZrWr9kiMgE=lCyspkFFCwTXsJ@KQ z+_rTDl$|oYAcZ~-z?5N6NmprR9N5sB>y0M3Oi8_<%L}kNq#=rd*LTp-DHryn4)-4f z&rKZ&%Y+w%6=Gf3dg^gio@(E&g*=TG;=t%?aT)hvf!=oPDD4#2D$@=l%HvRm>JnV`?>vB5S`#sU^FK!yxCI%vFJu8uZUD~f*# zD9vC#zaU)$2a&YTg{5%EP%${>)G6cK^#?IVZ)DZ`0Kf1`3>ude_FS%}04(3)Z7PJED~h>+`0r_(uTw-1dFm*gpwkJq;TjNlI2sZV8PkUq59f%*!CYFk zRV&hHT@^}Vi%-W1K97S7(lcj1-#b8@An5T<#a2_N1TaL4b(8H6uiu?OpzrcFHVWmpANfSlLxy6+4=eS0tp#J+b&=ns>pOGf#R*jy&M2^lKGV&QHfu zHV*MV#&sq6`ki?}=L$wjZRg$l3!Kl_p*CUS5xS^^)Gh|K!ZCatKC-`+bA;t<NEzq?qrn6TXBatue*0rHijZ=acdt z7YZ}a+-Z~z0!=5|VM5DGZ9&gul_Qi%9byxuhT0GHi#u0X4j76TrD=Uwz1z_MTp?!( zoXNYlrn-qi9ogiBp}M$=@WiM?Ld~}DN}LTcgFoeB2b7D=17rk_0l(|$fkT7VkWvB2 z0Nn5A;=rTfC`hRQ(~jbHF198=`DDawP3#<<04DeC>ELKYKkvDF?_ZjrC4~QuU}mxE z@ukUVrjsiN)(9`WIX|bIR&k}sQ#PRJdXAI72n)cVu*73-F4TVfn06LfK1=IMrPhY~ zGz%&1s>)ERn2x7HS?OeQ!_1_m82LGcm4&A|&gHPqIDmG$wTwZ<@?z zNmGgX1lE^s?6jX%TG=-An|xgz5E4hCb{K6Wz3lhbm;Y+F{5Gh}$)venIL6{^(v`p` zqo6?}Ldnc=X0os$QB}|h{A~AR@0v>ZnLq|9UR52RA3C%wgTmJ-I84M=QxOgyQW5t~ z!H`o~>iL+LWr(jKLc<7y*LPH=()uF7jotQEaHMUU8Le1No+gx8yp6^C93XjD5o-rj zS^s;25xEu2F9L0{JBsZ+$d1@8vtOF3!`IxUb1@67RLK^UZgx*#$vWXlvIZ?F1N;%k z+bftioC9nJ$mgP5GQKQ~zRZz%?1Ncg95>E2SsCi4kDY|9h{w)>Y}vCohbEU76UVXD zs?8h^;sDfHb`SO-aK7Ky)-<@yC(X-u8q3t?*?~y>=Dp@m)A0>t!bK2(v7)L^9r7+awFRFyci2-mhVZv# z^rKmsh*?$U@|&ml(PDOpLZ!+S=@kdC+d!Y@pxGxihli>A>72LH@vh2hgf&kzCVn35 zJ)38Crpn(^4L=oP9@wP7eIKx5KhHzm3@mR=ZBjcm88bkt4wq_(V1yhG5~K2pHCIG+ zyM(V*8GM(5AC#Nfn1PgRfIJ>}K8B zEqW#Ir5nWd`pgLt5z)m_*T+qaB7{>~YIz@~>dVCUVmq7Z{X{lO&YH;&|$PeQbNeXSzZ@T9wm4f)Ck6<1-n+up<)3#l-{X!52IJZmzJ_ZWDj zXz=!MrX?{nHo5yc*%I_cu9tQ&0Sy=TsA~2YmqW(Y<8QT|hifZfv9?KMxetQ$D_02L zaKx_W5_<3{mKZ~AW7G=NiE+H(sPs9K?DXw+xlWE2i^VO#b)>=-yz7N@0un$uJlvXU z%qUFAKm8dPVHXoadjN3i_WdqIK>Qim1{nWju)L$}G(!EBDjjOCy$MB31A@jx!()?s>Ue%)) zI*K7lSQuB|{WZLFQN@&CYg@M2 zns69zyx%Y3{yLQ1fD|A#6GEMp{o-idz%9k!4NC54|0R-!L10$R78WCJwJw-;I4SQt z6iAwaFf{CA#h{s1Hag01wVPqt?d5zTs;)!T&PK0#m9o8eARfRKyFFMjkVWcFhHwZl z;_`B;^vWV~_iidY0wnMs2RPkxtQjI817#KzXH{>CJmLofs*AX(#nrFlE(whG7kYtqk5se z5|x@q8WL$BUNHw}q)uy`HY~P;HNi7_UUsj7+h@zQZ&2(Q?Jdqm) zmxFc~sj!3`&k@mrQ?>&Imyd}A@dARX$AeY>56k(!%m@whPR@TR?D!iD+hg=G&Gk=q zZ8+kyknMhw%fxdZlIwLA^i7ZE9bDrR(roruLBmFeVgCf?9!7Vdu_fKz7i~ zihYS+TILz`1B)8rMBXjXaYM=L=nK{%pO~oHCLcjjO)ggCd!j*61-2bXr35i>5u;Ge_iGs-rQt>HULO8dv%O+2ChD7IYGIWT^4vK zjY{$iAJS}xmq3}4SWzsQHs>#p5X6@FR34UhDoo0b)pK4NdDg8WOGoC6Ix zk~Q&{0?6@fWZr7)QHdNAxQDQ`bmRTeHHaW3X4F)(pr<$xbp&m$*N!?}!2{!dLd%ea zxl=)L3h>7(0%%_%XXz?K7n;LHNC_Y*p*bpZ)0lpQ+(@HI}CD$U^ur;2EMWm z>W|bq8_X8gTJ@yRQwL^K)Gx~JoN?;$00I8!MOSU;7`J?{Yc0EE?N9U58)$xdKG>xa z!pwK=<1h%Q5{>{vAVn*&9MH5k2jVQi@!mFtMu1i^v^KSKG-1YN!v0|aAfNz=4(&|r zfJpO4hT{&{z!9*8goGYI763^3>gWnAmv#DWnxjoHds>VZgw&vpSD!YTSWbG}E^1EL!%1=~ZqQZ50 zRIwVY&4;TUlRQ0MS{(Tt7?Kn9LLtliC?!rjh_cu9?fMWqU8Ekg2_T?d@=amyTTlB1S;T_+k)kPOpyL1#(qn+_$wF{vn^8W$|rr2 zsZSM+HJC(VBCzNYLoftWo#Q~(6eHCLfhUplw1hsf?;sO~y^nG)JWA#1o>9d0p*FG= zv$C-ePx$-&{kmwJ2{!1(<{l{DcVdbAgT95NE3iBM-;X6GKzx8O`u}d_z+lU7GYsF5 zKjhMS5027Y@0$hZZ!_REJzHHrt;pN|F0C8ir6FO3{mjbs=qh1@5BVKtCcJ5<`io4g zz~Eu^&qtV7AV#Mou&N&cSn@)RD+=Pt#dv)UwnlmumHG=bU_O`D1II$M2csV^1q>w* z-hiVMJVKJeS!tHv4*}dfIo2}+gU<+2GraQPJh)!N0jamkRy1;P=6&_OkNkO!tp`c( zIbrsC)rWZI5?TU?o7d7196P~zth85a(`sy9IC;|My;X%1^#j=nVS63PEm7YH=179b zoO>boEQ_cUhV~k?SFiQSi`ky`b&#rN;H98+a;E$6g;>8v77XzMJJA7Cpc*W9$U^4s zW4%TpXWzYS>Nen;^TM90Dw=R{19v1D;O->)Ot;BU^gWZVPD%W#0(^Scz3KN^1_n-~0jEB*dLAenk^eY;^&a?cxvD zy67k|;M4j74a9Ab+A-&znT@P6=!7~IcJ@n-jm|S&%(ll)OV8oXUm07rBt5s}2TsCK zOItcc45xs&B*)Dsw-|h9GvKwRsh?cB_nD!uWd=`^mv-m~r!T>Z(imsta);u4z#+{y z;hN1XNIrG%vf2%n)xOCz-}L~`X%F``ViyS-@e6f0c{*$P%Bzud*rpuTgV3KR)DH-x zJ`YYPP~G3Pf5V~SsM%V00G#dql7{?A75BI8x<7l#|3Tl&1Dv1PIa>W4$A107JJ+%4 z-C;j^=&2Q`{Hm=WbdwFGuJ2cE`f9CSCH;~-mVCp|I$PwBaVD_j^BD_`Y-}!FOB44V zP|Z?aW@%2Zafopls*l9_JkU8qa!$b7cZFW{DXoH}a3B<$Pm+dcQjWkP24``Jjpt<^p++g4du`DB zo|fwGVP;j*k2MX&3qO-3?~miOTR0rLh#^eYNd z%jS4mBRIkg+PS}kLXV|O)Sx(~hf`p&1kFk{pqTirKy^?xL{Y5ho77K9`2(@k_kpDT zn^?-m#PZV`rM<_}Kc@ZuYZLvyg41GDiMSNJi_e{c(1FDAUzh@NpvI1#hZr6KDM|)F zx_Wm-KvY8(uZWgG8ib5=7VR-vmWWa(hq|HrV2BJcp_TZ&b)Z$&**T+@-)GG+AWGNa zFlgCpK^?RmX1^I2g{1}w1j4Dm--FfvPw&$|>k5ClPk}Dh?_$=i-`vi(ru-00GD7f@ zOw0sGxCvcAy+2>*m__vu=~xxTZ#4>U63e8`r+GG}>dvM4$;<=^(o#-*Y+`EWhs7!J$pIn>5gjkhHN3uw6P0j-`ExX)k*>6 z7)&XH8K`LNZ^aakN@ylV$IPPf#M>m8rWhQ@x{c?zjon3kWQ@teLm1cusAZKSX$soc z;;F(L+qHqXxlLJi^Aaq%qet<18W>$`Q6rls+}j}Oo8{~3ti8F+l@!wbe5+@eGz&vz zJotH`i{egtI7a4T8XIu|!p*|dhlVR1xqPO%x+mD5!uq4J#HdqQT)>z=QB(9;fo;_q ziTQG1Xpx$SPx3FxQ@%MAL}-!28kEbaCLD#*`f!#oqT!Ny`aJ~K%&q!u(ly&+>$bA` zX7#3v=ZsK1H9EYxbqEN;?~WAtS*xXGW-r)P%H4QL$W zZuUKo#0^OD=xk9$wU|ZH5|8BGqxeqRU-!npO&BEaeEOP+p?6&TY%|8?>S(BOG*o*C z>XAWnAbY)@*^ex&D5DrIH3crr#!BOio4X+#fxwgcm(8lG%!~{y55CQ2 zqCpASS{XVT-aEW-Xph9`Rb9-0JXB)B62w9bDh&5iir~@E?=Ssul7NQj?c{;`vIKB6 zg8R#VDL4QUrvWLZU4Pk-5|cvxw)YVsY+hR|DmXc=UCx^G|$gE z^9W>(rn07Ai7(=}xOS{djB2CJZ@;cKmE3;(Qd%Jd&f9udj>QkI$^$mdLMFw3jbR_m zK8fh+RD5_~G2oxi>i_ccZ!=B6<^S%i=9nIa*E@QCg6%gRpAh+=*O?#0Dg7ndzZtz% z;-eteF<+;fm}5Re+|o<#Nn(eAmMj99WVl}DCjOag@*1?wG=WWkQ+ojddOe z|H)~^-s-X+)Ci5xwkhnYO!D@#*2gx~b!MIPJ_pn8fUyzoy0J<(0o&ym-jt)4950sO~gnRU$sL&GWbx z4sJtQbJ3q&Ml=x>&g^yejW!7Mc>B;dfy*q`Barhb>(C4+`Y)f?x1hdvo1$r7Dne&E zgjCJjIWvtUsC--`nO-6$GK|h;$aQ0hvtBG^sfFA)jUI~4MC7bbP7Pb&kQ3&ygr>!OPU;P_2CU zST1|EiOUN=GnXvmC=fHH7}HB64MV%WoGt!((ue{Z7%tfB`1U1EW3SW$F>=9?$Qti%#nT^m+3s)=4oDtZU%0fO{j`TIgwTT>x$eso@k+4-#{@3GV zT$U0gAd}hEMda6r0tEz36G|QMdGn>5pVbt}AOYeDYzp+zWj&LRamHf?APpU$og_9R za8O$z9Z}1w4=zaG=CJ!5IRHD*b)zQS25d?Hw(g{`A;?&ab@- zKfxnl{(19X!1b;Q4;%qsmHu|LkP0v+`x9P&@&Wvu2lrcX0qINe^glYqZ_ewSeEGN-8t34RPAQ;Q{{YO((~BN&x~b9c z9d-a_(09`}Fg>cI{u}Qbr3B6tY&j&UIFY;Cw$1IYxRT|@dsE^Si@QXtNxiuwc9e~# zQ4{p)nUjPeFI)=bT zyd@^>&mnS_iP|Efg^w`sH$gSJQ@(6LQN>D2Y_Tqa;3EZAW&UYmfHwKX$aw zw7bTQnbT+Qsj_49YLir0)8d+$&2Y=i3gHLW!-*8lnYznG{eL1Zpf31LPPf%1;n0Z0 zj;wse!?o1Ab&hPRq5ksyq43I{TbR0D+DlaeIfRI_i4pq~<=WWO>c92zh-LNy{~#%c?r}-4XXI2?l;xyp^x%W~Zg!8?xi&5V5c;9AB4EBBDw> zI43ggM!*W4YM;S`DVD(MkSeQFJQ#Prucs?l4pCAaXL)U3f-^y8W6h>P2BZ4e%ATIK zEUG<%wJ323%Ipcw*?77#$M*NScg#G}ljx~sy||~k`rn}&yby_|0|~2tak?b`t*pg8 z)*%r7k_B3oW~v;m(7RF7OV(g zjKnYHQ=F&1HnuNID<5DL^tMT|%|;Uaw2GGTLRk4_t4pvmT_F-dLLGK}6kXxSg^okx z{QmVvpV$pXf~rE7(AoOzO`efdY4OoO9PjpQRQyC6>66*s*K({3!CZnaKImS4s40rQ zLS_xG{0vp|t>uYUGLWEm`kPp{uiMqA=)n96a85H9)l|TfJ)tFUQZG0n$htI#6dxJK z6c^#X3BnEerr%ttXqz_%H#FtoLsWq0b*f@ZqCZHxHR+{WKK-sob`uhG?m*+RgVg=< zdmmX~;&H8|66ol&=^fPUZ@7UM0eFpXl22TrSVg2>-N4s=zl(te-}*!k!SKK75#DiE zfFm&cU61fbP0#PWqSz+@!PRNG_L-U;@p-FVR*Cnr-@LusTIu-?7u;4uUe7hbYM38r z@V^>`5enR<5+Wm7cf=S4#l6t8C#@RH;#wc+mhX9PL(${eTs=>j0T;64&jERT=-{y5 ziN9laZDd;fQV$4t&?T<6haAHv#46=X;OdYVYw>jQD-%7*B%G_VCFo;^lPBGm3e|rMLTR zL>ELe)|8GPIwu#B0>vVEH@F@-30|g8a8R2yu{HSTAjoAzrwU3edAaUKzZ3NG>16wS5tnKv6* zaNzry;`BJ&tjBb|ZuKS&vmf(M+^W?EgT`69+{$xf4L=OSsFdx8U1Ds!MBK{4eKsr` z+klS2XOteo2?b(`x}oW+rP2Z<$ zA2Ly@i7GNIp^6QEHc!pA9)ENxRgH)Agkv91rP^R=E&jdXo}8BzHP6y{o*_MDlsm9AebHPGjuOGiwx zaeYMfbOZ3lS31Q#Yo(Lf$B(u)s$Gx=C1i9kC*+Ai-V{@S&7AEhGAaaXvc7_$*5Sst z_>ye`o|cazCsy=qUTczu_%X+ZqCEm>k(=KxV)Jn1EEF2zeSzY=*OwnvA2K+= z$}`8u5qaUkA40e7Gy|tmYLbStcY+K76Hy9|g=2D}MNSgUMpt!aPJn7N5kZJP-xZs` zMjAZjf!J!fva`RB)hvu>R!n{%7WnwGqu*=Fi2wUhH~qxbB;cpGLT*j1zz3!lP+WHv zJ}_v|LXH+5zzI3`eI!7Nc@<+jXJ;o9Ak)nssTDL73A-QNwYReTYi}+1gWlSIAX<9Z zl|M5G}u(SOkn-U4rkd!$F4}*zGKYc)=s?#e#SYVoK5%ZDI2y3I)46v?_VejVzSNAq{aw{iEoLM3P9COS%1DB2jna7wza0)@5cg!Ep_L3G=&< z(H&yh4>l4ZX9H8G1dXrZv8NHRt^AlC2I`$P`E5bP#vtP@-S*Rx27U3%;VwLIUf4Y; zW*rOh>J^Ef@(R7x20{15E|6%j5>HrJI62svnI6axn8Lo((r{>mLh``jM!y_}bWfBG zj|P8xBQQIrY-r(e9~=g9gWkRhw2pzTw?DFCz^*8Q;yv-m55DY~YqDSo!}H=y0agW^P`U_ByRkLB816*&P@Y3KE3x&$R9+qKo{}3nT`*XvF-A2iRA8gM5S2fvG##eYk$VWx4A+ z4Qi~6?<4irEPKa#L*ITGWUKB~I@|%DYbf0f+UHOa(zNx;omu4c4tq35~;d@qe69=&=_~<1*o9nih zvxOVa$>v3@j2hIgb~{?E>mrYlqF-*LSzDx@yGb&MO_i-8@`@Q;vLq={wwQGeFw|M? ziud*szp5kxEJ?yN<;fQv1&VV7?R^~bmlQFRcce+vX_dvJiqIkPtSL#E`?R7I*a<5x zZ^NJZJrQf(i&aC2H(KcczD}ot&KP%Dqfek^N4G1jLM(MHSBtEssYsn~#IE>8>S^0y z;mt`y*$&4=Q^N^7_qUHC!i3?oL4#;t=9*W-4H}}T)JxF`(&4rp_C}CC@D(LxOSbZU zt*AvLk!?ru-&f~3o5)$yuCIb1QBL$ru=p4w3-}TbPH*oFwi{`NK@FVm);zPUXsIUt z_Ej#Gp|AaDXHGHQ<6?Z3HG_&EviXB@tq{n44njc-hkrmoJ zAmG!Z%19r2Tdg!-Ah8*w^7GV2-tO422d5Tj7Ti@u!63j$SzBA!+F3lz>H~*=7#o_| z{ZTB2fc+!h^32c`KrCrzZDL_-=JXG?0Rq*ZxBSkQ1N&!N4(!&J0|VM}V4xt4x=K_s zyd2NR0)|@&THk+YC>ep{U)9}QEQW2=0OQ{>He5bJ4lpjy{aWfY=Cxf7UjYLGtLgim znkFnE6bsw$AgrpU?hz6Fw7yynn(kQ79+Y}`*mWsO#+6PdWUuX~knpB3@C?VtA#vrg z;I9|6!l(vwpGzU{k0P(xh${G5R!wfdf>Qx)ib;-lwQaeavU;*57&h(6s1Ny(&IaXp zyQ=NEsO)%h-u1YnzX){t4hsKiB(A16k@?dP5Fm6($0F4}DLBq^=}vRlZ9=_l?sF$b z??f7_Dc_I`e&bW_Ws<1w=WnoF+y3ZpuO}R-K=JyT`yH`u^EqbBdxWAIJZslSt54m< z`BjJg3N>IksPR(KViEeL>F_TgKf)yP0mK(Sy|-ACiw&icXM^V-L7!{>P^b8^?AavF z^gM1@pFAF=%WM_Qm-0ZSMiiv?p@%`y7>-`qpNEdZ44Y0Ynh~PlT{IvXl1l zS@MmZd@@}lX@C?oenfK2O5p4mL3heU{c2(x&o79+n{QpN@(S0k2>{LWbYEDE;$n85 z+_CEnq>{wFb=^zTNID(Tj&FEtTEaNBhs@3qwXf7St_D8d{$u?X<|7BJ;Uy>qNg;1` z>D=(6ei=_86dSX8m=xJf6r6Hlzr~9<$jNiXfwqmZ-ghr%Ff+EWVk@59k<&)rs+xb; zw0FZnV9?;?0ndn~0l;E(_j{WM8V&yE3&bL~b7}tU-oK@cQ3Dq1sVb|fs1o0$u$dW{ z=zjViRxw>_rF>{NQRW@Soxrs~GFoE*wOq)#Il zUM;)|2XBBx-`5!aq${xYxO({OWQ6-lZlDi+@~C0gm()e_c;gaZrra9)wcc#j^y9Y- zIx3a=)!r<=5p3)3?jZl_?EZ(phX0Pj@k0e*JwfVRfTOo z2TN}PyEq)4HN;o6e>T#w_+_GU+LMC{g;rvotEYtDA?2Gwr1B^5Sl@qFOq^^p7o1>O)!e7H60-j zdmq$LDUip7JO`G!hbo)sb!-J$q=y+e)L^#u5iMg#J77Y#`at_;Y165cl!qSVExVdC ztkkU3Q(WKYPqvhvBOOzCS^zVyMjJ_4p7c3GLJxSfx2+dR^(oikrzj8KatW*TR zZao!d5W$tLIV6|R{0>2wFi9aUt>|C1)aF)yX@;@duMtFuY&^YV)C5FThFzT=Di{cI zt_CF9q9fU4d}skg<p=(jOLu6pla;{p0u6k_kACuHmBdQO5^WL{EvM_takF4mU zoLd!3>5Qem7|U5V)`oShRX19daVAKbq?m+(&>T%cLfTro?A#}c`|{IRcmb?}_mBUp z?Cy;Pw0|}h(4ha(SOAiA-Jk~NP zO9jJCfy+IT96;n(PYD}47Q@s9mxfgO6r$wo6Ym$Kd1-JFktKUqT;!%-^sb}IqlMfv zU=4<;iY6j#j;F;Yk5Ym-0k1&PZjW!uU$?;j?D6Gc;`+G}SpFo$_#-^{-#y1u2;5yX z-{jX$+i;TgW5QG_i3A2mvWU@asJZ))y?MV{xpErMJuxUu)6)U*t76pjJq!ItE)ueB zfv5a3Zq;FZmL5^w?yOI0uI?CDy_~kr8GUc$f?^%Fi6Du=lIQ7*a0&Gr@>dD=DuMGs z&gh+HnGojO%Q1pl1k&;&BJn)i>eC?hgUX*gz00|`TbRFzoxL*{LDK}I^&4W7SZ-X3 z1K5C#(8cHHZ!ZO7OL>{f=W^Q=)qY}QXhH;UU|p8jtF($HpGzC|9}^R2NwN=J0z;A# z39hqtYBt$56e{hXR9y~b>?Nt97|POAHP5xhzzPOa$43Ps1$BZ{fMs|sY-nu$T}JgRX+2Vc#w6f z{qn~`!$k8}-l*E3fyj+-^ix-lPy_wsp!k{8yIB;HNEbywJWNW7O zPOBS%?c1Dyy9@&WoZ%Lo@O9n`R^m_A1x3j!j+n<3YL>sFln=;Z|A2l%bW`nT(!+roAl#k)=U9W!pXiR|&br{OMpEv2ND5tv z;Xsv9rpkBNOMIx!@>fCKtc;S>Wfs9&kLvL-PLiQ!k`IzM!qMX$PL6x+3%ZxFavmA} zfjoupd7aPaxi3me-t-iG7Mg}DrIrmePf#E{6kAz>+87rdr)CH+B{n2Xi3LuQx+`Kp zJzgVwn4BVFht8FUO??mQ;BL2|qCY}3S1MkA{j4W14k?=>l9^PZ0~Th__N!akOu%Ij zajFzf9jbWcg_s&3^N;yeB9`KmZ#1vUbK+#4@JN4EH1e@CXd5hPItePMu}WuASMo?p79t zCY{w*#8iJZ{qNz38n``Ke{63Ay8Gt|IU3r!1K;qYus{On5K|M2cgH*b<*~m_h<1_b zIkm~ypm%1UtS86Wjx&i7@_sU#Gg-ln@Eu?p2wW%bSU=&Oc%hH$rET+;Ng;^xEQ?60 zr6(0G?g;tRS0sqTSSixuwf&{X)AuoH2c6>9LH4@y$Dnbfm-0+SKC6_*+Xp7RuNwMU zjju7|Zr(Ip%@nq`zYu}1o6^kBR39ZHvUjgo;8H~dxXfV)y#TGy+dqr`a#LnfiHM!0fz z1?R*nB@?S?t1SWh#6w6?iF`*+Mb|_yt(bDE%y0Dash{a2?PSTRKaM$QFB5dQaMa9v z_w*1>w}eyI^0I(0_Jbv}_URA@slMI(ZytP6z#z_jFCrQuuz2Ry=(-Eaa4~V*?pSy< zrrQ)9umbV+8iOssnNdVhg&4@fVqt51N7e`A;52e4b_AFL9B*eI{K)hFcq=BAF*OjJ znVt~;_1S+W?(SayFZ(%udkX3j>qLaV_t?OVQ>0xm?K%i7wQ8&`H6${4yx3t3eHy=1syoL2PKe_Y0TS6 z5{)nxB<4;>X>S5#`pfv2WEbN66a`=-fz9db=q7%y`vK4W+LC{IL zmMrKdmC(_Im>zYV|z8wV1#pS@fKX9z>4uSP{CR{0+O zAk}02I{ew~90-zc6Z5loo%yx5%DU=-a37RkBHUM>-l5|8Ym4qI z3mag?ztt`8o27*{z|n&EHdJTfVFPgf@s|)ocP7Jq6)HT!A62M8l)KHh0*RK~fmBy= z07ol;hs7WL{Rl+&)vABR#=lRAwRS3y0I5V6Y^18^0e^}O9a!#D;ROtVSiLqD;cArm z+Sy|g&i8^r)lZp=3tC3zLol>!=+IlnMA;$l0=^arL9`QJzf1)|@@f)Ucgvn(x*W* zFHqkgBweK!nP>=g46kLsaoInyOB!H65LN3Q_^u0u<5E<~~vf9OHWj9k-BlZ#}}JbNmai(q~~}0cD>-7sV&1O5dJVX@@3AQ_0S^NZ{Z80zXU_|wp5 z8EWr0I(=p=O-h2%LlU)iQ=bnB2obWi2qUM)&Mg@YkFWMsErfzpK9z*7yTjE zpLz~B=o#1+N^A&Z3j+=#_W-Ig7hv(IovpR|AM}1`)PF}y0Uz`?Y8IM;>Kqp`5vH^ba^x6+{523~)Y8un zM2zrF@Ak{Ls2Y^`x#o;;p{!O`}QYpPkcBXg!gxWFnK#wCU~`lY&f z3YIQZy*1hnKia-?ui}PRTT@KIWI@wmbW`tm_oDhpYlHG-7THYC{%9I`!1~a;F0t;Y zWDh7S8kDReI9o{`yM;*<9O19A&~Hyw^warIu;{idO66H(jh^k+f81WF^_O8L{WZ{l1oFRu^~@{kpE`uwi8cDpCX-~FfvS4o&-zZY5hE3U4kd$zTW_NJ;lEJxZQ- z?fCq<_6d42`>Y!YyYgp|)&H9D#pPi>Aa2C~noGsGjSEzh>hpxfxo;An`ns`xd! zCLTXF1}MklRi?(c`)ut+gUo3a?>c?V3jmWyy4H?SHO$>(lwo=kN$TS0^_aKs?2Gdx zzGp6^+NWkY9+tM3&rCB*E#gR^i43`u+BRP*3_|_i`2+TrgrIKx(Bun!$vLLvrfy%`f5Hm zHvKi0!gO+usHPN< zLqv?7?z;Eh-TEeP|I?_(%<{Ll`afx~|I+pbqMDeK_M5QXzDZL|w5^LE@g3-m#=B*2O!8Q95iw#3J8H0AtQs=QSz;!-wGlhYovQD zgPVoZWh5IGJZeyj^F^Ykc{{{csbhu{DJ literal 0 HcmV?d00001 From 1ccd5f18fbd5f427c6edd6ca641ed132211e223b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 26 Nov 2024 00:03:07 +0100 Subject: [PATCH 088/188] Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 384b431..5066032 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 384b4317c4b58afd981f410a9b732700e733b00b +Subproject commit 5066032a55b5436ec5c888cf0eb91bdc9e71bea0 From 9f3351f85bdd8c8e0d892db3d2030f737b107065 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 26 Nov 2024 18:07:47 +0100 Subject: [PATCH 089/188] Add Forgejo CI action Remove report action Fix release step Add platformio and node_modules cache Try with less targets Disable more Restore all variants Remove verbose and feature branch run --- .forgejo/workflows/push.yaml | 171 +++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 .forgejo/workflows/push.yaml diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml new file mode 100644 index 0000000..81fdc06 --- /dev/null +++ b/.forgejo/workflows/push.yaml @@ -0,0 +1,171 @@ +on: + push: + tags: + - "*" + workflow_dispatch: + +jobs: + build: + runs-on: docker + container: + image: ghcr.io/catthehacker/ubuntu:js-22.04 + permissions: + contents: write + checks: write + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: yarn + cache-dependency-path: '**/yarn.lock' + - uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + ~/data/node_modules + .pio + data/node_modules + key: ${{ runner.os }}-pio + - uses: actions/setup-python@v5 + with: + python-version: '3.9' + cache: 'pip' + - name: Get current date + id: dateAndTime + shell: bash + run: echo "dateAndTime=$(date +'%Y-%m-%d-%H:%M')" >> $GITHUB_OUTPUT + - name: Install PlatformIO Core + shell: bash + run: pip install --upgrade platformio + - name: Run unit tests + shell: bash + run: mkdir -p junit-reports && pio test -e native_test_only --junit-output-path junit-reports/ + - name: Build BTClock firmware + shell: bash + run: pio run + - name: Build BTClock filesystem + shell: bash + run: pio run --target buildfs + - name: Copy bootloader to output folder + run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin .pio + - name: Upload artifacts + uses: https://code.forgejo.org/forgejo/upload-artifact@v4 + with: + include-hidden-files: true + retention-days: 1 + name: prepared-outputs + path: .pio/**/*.bin + merge: + runs-on: docker + container: + image: ghcr.io/catthehacker/ubuntu:js-22.04 + permissions: + contents: write + checks: write + needs: build + continue-on-error: true + strategy: + matrix: + chip: + - name: lolin_s3_mini + version: esp32s3 + - name: btclock_rev_b + version: esp32s3 + - name: btclock_v8 + version: esp32s3 + epd_variant: [213epd, 29epd] + exclude: + - chip: {name: btclock_rev_b, version: esp32s3} + epd_variant: 29epd + - chip: {name: btclock_v8, version: esp32s3} + epd_variant: 29epd + steps: + - uses: https://code.forgejo.org/forgejo/download-artifact@v4 + with: + name: prepared-outputs + path: .pio + - name: Install esptools.py + run: pip install --upgrade esptool + - name: Create merged firmware binary + run: | + if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then + mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ + esptool.py --chip ${{ matrix.chip.version }} merge_bin \ + -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ + --flash_mode dio \ + --flash_freq 80m \ + --flash_size 16MB \ + 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \ + 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ + 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ + 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ + 0x810000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin; + else + # Original command for other cases + mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ + esptool.py --chip ${{ matrix.chip.version }} merge_bin \ + -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ + --flash_mode dio \ + 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \ + 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ + 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ + 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ + 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin + # Adjust the offset for littlefs or other files as needed for the original case + fi + + - name: Create checksum for firmware + run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin.sha256 + + - name: Create checksum for merged binary + run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256 + + - name: Create checksum for littlefs partition + run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin.sha256 + + - name: Copy all artifacts to output folder + run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} + + - name: Create OTA binary file + run: mv ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin + - name: Upload artifacts + uses: https://code.forgejo.org/forgejo/upload-artifact@v4 + with: + name: build-${{ matrix.chip.name }}-${{ matrix.epd_variant }} + path: | + ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin + ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.sha256 + release: + runs-on: docker + permissions: + contents: write + checks: write + needs: merge + steps: + - name: Download matrix outputs + uses: https://code.forgejo.org/forgejo/download-artifact@v4 + with: + pattern: build-* + merge-multiple: false + path: temp + - name: Copy files + run: | + mkdir -p release + find temp -type f \( -name "*.bin" -o -name "*.sha256" \) -exec cp -f {} release/ \; + - name: Create release + uses: https://code.forgejo.org/actions/forgejo-release@v2.4.0 + with: + url: 'https://git.btclock.dev' + repo: '${{ github.repository }}' + direction: upload + tag: '${{ github.ref_name }}' + sha: '${{ github.sha }}' + release-dir: release + token: ${{ secrets.TOKEN }} + override: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }} + prerelease: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }} + release-notes-assistant: false \ No newline at end of file From 3b47c81cfe06028fdc12e583e4d36b23b710a2b9 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 26 Nov 2024 20:50:32 +0100 Subject: [PATCH 090/188] Webserver dependency update and testing new CI flow --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 25e7d9c..021a03d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,7 +34,7 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.2.0 - mathieucarbou/ESPAsyncWebServer @ 3.3.7 + mathieucarbou/ESPAsyncWebServer @ 3.3.23 adafruit/Adafruit BusIO@^1.16.1 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.3 From 239297c26d586aad885c1598947baca4eebd4950 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 26 Nov 2024 21:48:28 +0100 Subject: [PATCH 091/188] Add own badges to README --- .forgejo/workflows/push.yaml | 2 ++ README.md | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index 81fdc06..b48a34e 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -1,3 +1,5 @@ +name: 'BTClock CI' + on: push: tags: diff --git a/README.md b/README.md index 5dfc0ad..81890b4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # BTClock v3 -[![BTClock CI](https://github.com/btclock/btclock_v3/actions/workflows/tagging.yml/badge.svg)](https://github.com/btclock/btclock_v3/actions/workflows/tagging.yml) +[![Latest release](https://git.btclock.dev/btclock/btclock_v3/badges/release.svg)](https://git.btclock.dev/btclock/btclock_v3/releases/latest) + +[![BTClock CI](https://git.btclock.dev/btclock/btclock_v3/badges/workflows/push.yaml/badge.svg)](https://git.btclock.dev/btclock/btclock_v3/actions?workflow=push.yaml&actor=0&status=0) Software for the BTClock project. From d37307cccfea6b35dfb149b6e3d5248d9a8460b3 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 27 Nov 2024 11:33:12 +0100 Subject: [PATCH 092/188] Add Mow mode notation and setting --- data | 2 +- lib/btclock/data_handler.cpp | 4 +- lib/btclock/data_handler.hpp | 2 +- lib/btclock/utils.cpp | 25 ++++++++--- lib/btclock/utils.hpp | 1 + src/lib/defaults.hpp | 2 + src/lib/epd.cpp | 70 +++++++++++++++++++++++++++-- src/lib/screen_handler.cpp | 2 +- src/lib/webserver.cpp | 2 + test/test_datahandler/test_main.cpp | 29 ++++++++++++ 10 files changed, 124 insertions(+), 15 deletions(-) diff --git a/data b/data index 5066032..d74e9da 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 5066032a55b5436ec5c888cf0eb91bdc9e71bea0 +Subproject commit d74e9dab60772ef11f9b033cfe982d216a9c95ee diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 7b63db6..3209811 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -67,13 +67,13 @@ char getCurrencyChar(const std::string& input) return CURRENCY_USD; // Assuming USD is the default for unknown inputs } -std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat) +std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat, bool mowMode) { std::array ret; std::string priceString; if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) { - priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, NUM_SCREENS - 2); + priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, NUM_SCREENS - 2, mowMode); } else { diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index 2d563a4..f8e237c 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -19,7 +19,7 @@ const std::string CURRENCY_CODE_JPY = "JPY"; const std::string CURRENCY_CODE_AUD = "AUD"; const std::string CURRENCY_CODE_CAD = "CAD"; -std::array parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false); +std::array parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false, bool mowMode = false); std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array parseBlockHeight(std::uint32_t blockHeight); std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks); diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index 53212e4..9056d14 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -28,7 +28,12 @@ double getSupplyAtBlock(std::uint32_t blockNr) return totalBitcoinInCirculation; } -std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters) +std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters) +{ + return formatNumberWithSuffix(num, numCharacters, false); +} + +std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mowMode) { static char result[20]; // Adjust size as needed const long long quadrillion = 1000000000000000LL; @@ -56,30 +61,36 @@ std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters) numDouble /= billion; suffix = 'B'; } - else if (num >= million || numDigits > 6) + else if (num >= million || numDigits > 6 || (mowMode && num >= thousand)) { numDouble /= million; suffix = 'M'; } - else if (num >= thousand || numDigits > 3) + else if (!mowMode && (num >= thousand || numDigits > 3)) { numDouble /= thousand; suffix = 'K'; } - else + else if (!mowMode) { snprintf(result, sizeof(result), "%llu", (unsigned long long)num); -// sprintf(result, "%llu", (unsigned long long)num); return result; } + else // mowMode is true and num < 1000 + { + numDouble /= million; + suffix = 'M'; + } // Add suffix int len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix); - // If there's room, add decimal places + // If there's room, add more decimal places if (len < numCharacters) { - snprintf(result, sizeof(result), "%.*f%c", numCharacters - len - 1, numDouble, suffix); + int restLen = mowMode ? numCharacters - len : numCharacters - len - 1; + + snprintf(result, sizeof(result), "%.*f%c", restLen, numDouble, suffix); } return result; diff --git a/lib/btclock/utils.hpp b/lib/btclock/utils.hpp index b166cae..cf4a107 100644 --- a/lib/btclock/utils.hpp +++ b/lib/btclock/utils.hpp @@ -11,4 +11,5 @@ int modulo(int x,int N); double getSupplyAtBlock(std::uint32_t blockNr); std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4); +std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mowMode); int64_t getAmountInSatoshis(std::string bolt11); \ No newline at end of file diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index dd222a5..c665b3b 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -18,6 +18,8 @@ #define DEFAULT_DISABLE_FL false #define DEFAULT_OWN_DATA_SOURCE true #define DEFAULT_STAGING_SOURCE false +#define DEFAULT_MOW_MODE false + #define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index a301f17..eac002c 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -283,7 +283,10 @@ void prepareDisplayUpdateTask(void *pvParameters) } else { - if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) + if (epdContent[epdIndex].length() == 2) { + showChars(epdIndex, epdContent[epdIndex], updatePartial, &FONT_BIG); + } + else if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) { if (epdContent[epdIndex].equals("STS")) { @@ -407,6 +410,32 @@ void splitText(const uint dispNum, const String &top, const String &bottom, displays[dispNum].print(bottom); } +// void showChars(const uint dispNum, const String &chars, bool partial, +// const GFXfont *font) +// { +// displays[dispNum].setRotation(2); +// displays[dispNum].setFont(font); +// displays[dispNum].setTextColor(getFgColor()); +// int16_t tbx, tby; +// uint16_t tbw, tbh; + +// displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); + +// // center the bounding box by transposition of the origin: +// uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; +// uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; + +// displays[dispNum].fillScreen(getBgColor()); + +// displays[dispNum].setCursor(x, y); +// displays[dispNum].print(chars); + +// // displays[dispNum].setCursor(10, 3); +// // displays[dispNum].setFont(&FONT_SMALL); +// // displays[dispNum].setTextColor(getFgColor()); +// // displays[dispNum].println("Y = " + y); +// } + void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) { @@ -466,6 +495,18 @@ void showDigit(const uint dispNum, char chr, bool partial, // displays[dispNum].println("Y = " + y); } +int16_t calculateDescent(const GFXfont *font) { + int16_t maxDescent = 0; + for (uint16_t i = font->first; i <= font->last; i++) { + GFXglyph *glyph = &font->glyph[i - font->first]; + int16_t descent = glyph->yOffset; + if (descent > maxDescent) { + maxDescent = descent; + } + } + return maxDescent; +} + void showChars(const uint dispNum, const String &chars, bool partial, const GFXfont *font) { @@ -475,12 +516,35 @@ void showChars(const uint dispNum, const String &chars, bool partial, int16_t tbx, tby; uint16_t tbw, tbh; displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); + + int16_t descent = calculateDescent(font); + // center the bounding box by transposition of the origin: uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; displays[dispNum].fillScreen(getBgColor()); - displays[dispNum].setCursor(x, y); - displays[dispNum].print(chars); + // displays[dispNum].setCursor(x, y); + // displays[dispNum].print(chars); + + for (int i = 0; i < chars.length(); i++) { + char c = chars[i]; + if (c == '.' || c == ',') { + // For the dot, calculate its specific descent + GFXglyph *dotGlyph = &font->glyph[c -font->first]; + int16_t dotDescent = dotGlyph->yOffset; + + // Draw the dot with adjusted y-position + displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height); + displays[dispNum].print(c); + } else { + // For other characters, use the original y-position + displays[dispNum].setCursor(x, y); + displays[dispNum].print(c); + } + + // Move x-position for the next character + x += font->glyph[c - font->first].xAdvance; + } } int getBgColor() { return bgColor; } diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index d1c6165..47ac609 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -41,7 +41,7 @@ void workerTask(void *pvParameters) { uint price = getPrice(currency); if (getCurrentScreen() == SCREEN_BTC_TICKER) { - taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE)); + taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE), preferences.getBool("mowMode", DEFAULT_MOW_MODE)); } else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) { taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); } else { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 7a6d28d..c4ad256 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -546,6 +546,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", "suffixPrice", "disableLeds", "ownDataSource", + "mowMode", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; @@ -687,6 +688,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN); root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE); root["disableLeds"] = preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS); + root["mowMode"] = preferences.getBool("mowMode", DEFAULT_MOW_MODE); root["hostnamePrefix"] = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX); root["hostname"] = getMyHostname(); diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index 29d337f..218408c 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -86,6 +86,33 @@ void test_PriceOf1MillionUsd(void) TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS - 1].c_str()); } +void test_PriceSuffixMode(void) +{ + std::array output = parsePriceData(93000, '$', true, false); + TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); + + TEST_ASSERT_EQUAL_STRING("9", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("K", output[NUM_SCREENS - 1].c_str()); +} + +void test_PriceSuffixModeMow(void) +{ + std::array output = parsePriceData(93000, '$', true, true); + + std::string joined = joinArrayWithBrackets(output); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[0].c_str(), joined.c_str()); + + TEST_ASSERT_EQUAL_STRING_MESSAGE(".", output[NUM_SCREENS - 5].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("9", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("3", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("M", output[NUM_SCREENS - 1].c_str(), joined.c_str()); +} + void test_McapLowerUsd(void) { std::array output = parseMarketCap(810000, 26000, '$', true); @@ -203,6 +230,8 @@ int runUnityTests(void) RUN_TEST(test_Mcap1TrillionEurSmallChars); RUN_TEST(test_Mcap1TrillionJpy); RUN_TEST(test_Mcap1TrillionJpySmallChars); + RUN_TEST(test_PriceSuffixMode); + RUN_TEST(test_PriceSuffixModeMow); return UNITY_END(); } From 2951055f68473dfca060d46c56347628edf81da7 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 27 Nov 2024 11:41:58 +0100 Subject: [PATCH 093/188] Add requirements.txt for CI --- requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a91b030 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +platformio \ No newline at end of file From 4cda081d05e76351cce434b9d6a14298bedb8dcd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 28 Nov 2024 18:22:07 +0100 Subject: [PATCH 094/188] Add suffix compact mode, added extra zap notify settings, WebUI cleanup --- .forgejo/workflows/push.yaml | 2 +- data | 2 +- dependencies.lock | 2 +- lib/btclock/data_handler.cpp | 40 +++++++++++++++++++++++++---- lib/btclock/data_handler.hpp | 3 ++- partition.csv | 6 ++--- platformio.ini | 4 +-- src/lib/defaults.hpp | 3 +++ src/lib/epd.cpp | 2 +- src/lib/led_handler.cpp | 4 +-- src/lib/nostr_notify.cpp | 5 +++- src/lib/screen_handler.cpp | 5 +++- src/lib/webserver.cpp | 7 ++++- test/test_datahandler/test_main.cpp | 16 ++++++++++++ 14 files changed, 81 insertions(+), 20 deletions(-) diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index b48a34e..5121d24 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -116,7 +116,7 @@ jobs: 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ - 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin + 0x370000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin # Adjust the offset for littlefs or other files as needed for the original case fi diff --git a/data b/data index d74e9da..de99a22 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit d74e9dab60772ef11f9b033cfe982d216a9c95ee +Subproject commit de99a221d688949da0d95e2f9df2da5ba77f5c0d diff --git a/dependencies.lock b/dependencies.lock index 2cb885d..c338e6c 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: 841ba2a95f4b32d39636bf4fdf07c48a2052f71c9728a5b7f263083a3b430a4f +manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff target: esp32s3 version: 1.0.0 diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 3209811..c1f1d1f 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -67,7 +67,7 @@ char getCurrencyChar(const std::string& input) return CURRENCY_USD; // Assuming USD is the default for unknown inputs } -std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat, bool mowMode) +std::array parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat, bool mowMode, bool shareDot) { std::array ret; std::string priceString; @@ -80,7 +80,7 @@ std::array parsePriceData(std::uint32_t price, char cu priceString = getCurrencySymbol(currencySymbol) + std::to_string(price); } std::uint32_t firstIndex = 0; - if (priceString.length() < (NUM_SCREENS)) + if ((shareDot && priceString.length() <= (NUM_SCREENS)) || priceString.length() < (NUM_SCREENS)) { priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); @@ -89,11 +89,41 @@ std::array parsePriceData(std::uint32_t price, char cu firstIndex = 1; } - - for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++) + + if (shareDot) { - ret[i] = priceString[i]; + std::vector tempArray; + size_t dotPosition = priceString.find('.'); + if (dotPosition != std::string::npos && dotPosition > 0) + { + for (size_t i = 0; i < priceString.length(); ++i) + { + if (i == dotPosition - 1) + { + tempArray.push_back(std::string(1, priceString[i]) + "."); + ++i; // Skip the dot in the next iteration + } + else + { + tempArray.push_back(std::string(1, priceString[i])); + } + } + + // Copy from tempArray to ret + for (std::uint32_t i = firstIndex; i < NUM_SCREENS && i - firstIndex < tempArray.size(); ++i) + { + ret[i] = tempArray[i - firstIndex]; + } + } } + else + { + for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++) + { + ret[i] = std::string(1, priceString[i]); + } + } + return ret; } diff --git a/lib/btclock/data_handler.hpp b/lib/btclock/data_handler.hpp index f8e237c..4dda86b 100644 --- a/lib/btclock/data_handler.hpp +++ b/lib/btclock/data_handler.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "utils.hpp" @@ -19,7 +20,7 @@ const std::string CURRENCY_CODE_JPY = "JPY"; const std::string CURRENCY_CODE_AUD = "AUD"; const std::string CURRENCY_CODE_CAD = "CAD"; -std::array parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false, bool mowMode = false); +std::array parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = false); std::array parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol); std::array parseBlockHeight(std::uint32_t blockHeight); std::array parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks); diff --git a/partition.csv b/partition.csv index 318b4f0..778b9f2 100644 --- a/partition.csv +++ b/partition.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 36K, 20K, otadata, data, ota, 56K, 8K, -app0, app, ota_0, 64K, 1740K, -app1, app, ota_1, , 1740K, -spiffs, data, spiffs, , 400K, +app0, app, ota_0, 64K, 1760K, +app1, app, ota_1, , 1760K, +spiffs, data, spiffs, , 410K, coredump, data, coredump,, 64K, diff --git a/platformio.ini b/platformio.ini index 021a03d..7b0d229 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,9 +33,9 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.2.0 + bblanchon/ArduinoJson@^7.2.1 mathieucarbou/ESPAsyncWebServer @ 3.3.23 - adafruit/Adafruit BusIO@^1.16.1 + adafruit/Adafruit BusIO@^1.16.2 adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 adafruit/Adafruit NeoPixel@^1.12.3 https://github.com/dsbaars/universal_pin diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index c665b3b..8af3b5b 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -19,6 +19,7 @@ #define DEFAULT_OWN_DATA_SOURCE true #define DEFAULT_STAGING_SOURCE false #define DEFAULT_MOW_MODE false +#define DEFAULT_SUFFIX_SHARE_DOT false #define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD @@ -57,6 +58,8 @@ #define DEFAULT_ZAP_NOTIFY_ENABLED false #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" +#define DEFAULT_LED_FLASH_ON_ZAP true +#define DEFAULT_FL_FLASH_ON_ZAP true #define DEFAULT_HTTP_AUTH_ENABLED false #define DEFAULT_HTTP_AUTH_USERNAME "btclock" diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index eac002c..1c04725 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -534,7 +534,7 @@ void showChars(const uint dispNum, const String &chars, bool partial, int16_t dotDescent = dotGlyph->yOffset; // Draw the dot with adjusted y-position - displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height); + displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8); displays[dispNum].print(c); } else { // For other characters, use the original y-position diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 4f882e0..338c3c3 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -285,7 +285,7 @@ void ledTask(void *parameter) #ifdef HAS_FRONTLIGHT bool frontlightWasOn = false; - if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) + if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) { if (frontlightOn) { @@ -307,7 +307,7 @@ void ledTask(void *parameter) // blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235), // pixels.Color(169, 21, 255)); #ifdef HAS_FRONTLIGHT - if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) + if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) { vTaskDelay(pdMS_TO_TICKS(10)); if (frontlightWasOn) diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 237eabe..bad593d 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -252,7 +252,10 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) setEpdContent(textEpdContent); vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); - queueLedEffect(LED_EFFECT_NOSTR_ZAP); + if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) + { + queueLedEffect(LED_EFFECT_NOSTR_ZAP); + } if (timerPeriod > 0) { esp_timer_start_periodic(screenRotateTimer, diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 47ac609..1f2b4a4 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -41,7 +41,10 @@ void workerTask(void *pvParameters) { uint price = getPrice(currency); if (getCurrentScreen() == SCREEN_BTC_TICKER) { - taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE), preferences.getBool("mowMode", DEFAULT_MOW_MODE)); + taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE), + preferences.getBool("mowMode", DEFAULT_MOW_MODE), + preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT) + ); } else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) { taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); } else { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index c4ad256..777fe0b 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -546,7 +546,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", "suffixPrice", "disableLeds", "ownDataSource", - "mowMode", + "mowMode", "suffixShareDot", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; @@ -689,6 +689,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE); root["disableLeds"] = preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS); root["mowMode"] = preferences.getBool("mowMode", DEFAULT_MOW_MODE); + root["suffixShareDot"] = preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT); root["hostnamePrefix"] = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX); root["hostname"] = getMyHostname(); @@ -703,6 +704,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); + root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP); + root["gitReleaseUrl"] = preferences.getString("gitReleaseUrl", DEFAULT_GIT_RELEASE_URL); root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); @@ -719,6 +722,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["flAlwaysOn"] = preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON); root["flEffectDelay"] = preferences.getUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); root["flFlashOnUpd"] = preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); + root["flFlashOnZap"] = preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP); + root["hasLightLevel"] = hasLightLevel(); root["luxLightToggle"] = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); #else diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index 218408c..f2ad64c 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -113,6 +113,21 @@ void test_PriceSuffixModeMow(void) TEST_ASSERT_EQUAL_STRING_MESSAGE("M", output[NUM_SCREENS - 1].c_str(), joined.c_str()); } +void test_PriceSuffixModeMowCompact(void) +{ + std::array output = parsePriceData(93000, '$', true, true, true); + + std::string joined = joinArrayWithBrackets(output); + + TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0.", output[NUM_SCREENS - 5].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("9", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("3", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("M", output[NUM_SCREENS - 1].c_str(), joined.c_str()); +} + void test_McapLowerUsd(void) { std::array output = parseMarketCap(810000, 26000, '$', true); @@ -232,6 +247,7 @@ int runUnityTests(void) RUN_TEST(test_Mcap1TrillionJpySmallChars); RUN_TEST(test_PriceSuffixMode); RUN_TEST(test_PriceSuffixModeMow); + RUN_TEST(test_PriceSuffixModeMowCompact); return UNITY_END(); } From 031b506fedc536e130fb8a5e874b59e3cb051864 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 28 Nov 2024 19:21:15 +0100 Subject: [PATCH 095/188] Fix partition tables for bigger flash sizes --- partition_16mb.csv | 2 +- partition_8mb.csv | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/partition_16mb.csv b/partition_16mb.csv index a32b7ca..e26e145 100644 --- a/partition_16mb.csv +++ b/partition_16mb.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 36K, 20K, otadata, data, ota, 56K, 8K, app0, app, ota_0, 64K, 4096K, app1, app, ota_1, , 4096K, -spiffs, data, spiffs, , 400K, +spiffs, data, spiffs, , 410K, coredump, data, coredump,, 64K, diff --git a/partition_8mb.csv b/partition_8mb.csv index 4ca1357..778b9f2 100644 --- a/partition_8mb.csv +++ b/partition_8mb.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 36K, 20K, otadata, data, ota, 56K, 8K, -app0, app, ota_0, 64K, 1700K, -app1, app, ota_1, , 1700K, -spiffs, data, spiffs, , 400K, +app0, app, ota_0, 64K, 1760K, +app1, app, ota_1, , 1760K, +spiffs, data, spiffs, , 410K, coredump, data, coredump,, 64K, From 981895d3157df20b16a1c7eddba9d61e27277803 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 29 Nov 2024 01:22:07 +0100 Subject: [PATCH 096/188] Convert partition table to hex, update WebUI --- data | 2 +- partition.csv | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data b/data index de99a22..f0fa58b 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit de99a221d688949da0d95e2f9df2da5ba77f5c0d +Subproject commit f0fa58b5ea60f695aeaae9ddd7138cbb3686e96a diff --git a/partition.csv b/partition.csv index 778b9f2..80714ea 100644 --- a/partition.csv +++ b/partition.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 36K, 20K, -otadata, data, ota, 56K, 8K, -app0, app, ota_0, 64K, 1760K, -app1, app, ota_1, , 1760K, -spiffs, data, spiffs, , 410K, -coredump, data, coredump,, 64K, +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x1b8000, +app1, app, ota_1, , 0x1b8000, +spiffs, data, spiffs, , 0x66800, +coredump, data, coredump,, 0x10000, \ No newline at end of file From 41b5fcf1c1938c4bac7e9cea53395c07e39ffaec Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 5 Dec 2024 04:32:32 +0100 Subject: [PATCH 097/188] Bugfix for suffix compact mode --- lib/btclock/data_handler.cpp | 9 +++++---- maintainers.yaml | 20 ++++++++++++++++++++ test/test_datahandler/test_main.cpp | 26 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 maintainers.yaml diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index c1f1d1f..80e2328 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -90,10 +90,11 @@ std::array parsePriceData(std::uint32_t price, char cu firstIndex = 1; } - if (shareDot) + size_t dotPosition = priceString.find('.'); + + if (shareDot && dotPosition != std::string::npos && dotPosition > 0) { std::vector tempArray; - size_t dotPosition = priceString.find('.'); if (dotPosition != std::string::npos && dotPosition > 0) { for (size_t i = 0; i < priceString.length(); ++i) @@ -322,9 +323,9 @@ emscripten::val parseBlockHeightArray(std::uint32_t blockHeight) return arrayToStringArray(parseBlockHeight(blockHeight)); } -emscripten::val parsePriceDataArray(std::uint32_t price, const std::string ¤cySymbol, bool useSuffixFormat = false) +emscripten::val parsePriceDataArray(std::uint32_t price, const std::string ¤cySymbol, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = false) { - return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat)); + return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat, mowMode, shareDot)); } emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks) diff --git a/maintainers.yaml b/maintainers.yaml new file mode 100644 index 0000000..8176c63 --- /dev/null +++ b/maintainers.yaml @@ -0,0 +1,20 @@ +identifier: BTClock +maintainers: +- npub1k5f85zx0xdskyayqpfpc0zq6n7vwqjuuxugkayk72fgynp34cs3qfcvqg2 +relays: +- wss://relay.noderunners.network/ +- wss://nostr.sathoarder.com/ +- wss://offchain.pub/ +- wss://nostr3.daedaluslabs.io/ +- wss://nostr4.daedaluslabs.io/ +- wss://nostr.dbtc.link/ +- wss://purplepag.es/ +- wss://nos.lol/ +- wss://nostr1.daedaluslabs.io/ +- wss://nostr.noderunners.network/ +- wss://nostr.lnbitcoin.cz/ +- wss://relay.primal.net/ +- wss://relay.damus.io +- wss://nostr-relay.derekross.me/ +- wss://nostr2.azzamo.net/ +- wss://nostr2.daedaluslabs.io/ diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index f2ad64c..6314f34 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -98,6 +98,30 @@ void test_PriceSuffixMode(void) TEST_ASSERT_EQUAL_STRING("K", output[NUM_SCREENS - 1].c_str()); } +void test_PriceSuffixModeCompact1(void) +{ + std::array output = parsePriceData(100000, '$', true, false, true); + TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); + + TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("K", output[NUM_SCREENS - 1].c_str()); +} + +void test_PriceSuffixModeCompact2(void) +{ + std::array output = parsePriceData(1000000, '$', true, false, true); + TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); + + TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING("1.", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS - 1].c_str()); +} + void test_PriceSuffixModeMow(void) { std::array output = parsePriceData(93000, '$', true, true); @@ -246,6 +270,8 @@ int runUnityTests(void) RUN_TEST(test_Mcap1TrillionJpy); RUN_TEST(test_Mcap1TrillionJpySmallChars); RUN_TEST(test_PriceSuffixMode); + RUN_TEST(test_PriceSuffixModeCompact1); + RUN_TEST(test_PriceSuffixModeCompact2); RUN_TEST(test_PriceSuffixModeMow); RUN_TEST(test_PriceSuffixModeMowCompact); From f0f591a16f7c5a9409be5cf83d9fa5d0476c9da6 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 5 Dec 2024 05:21:14 +0100 Subject: [PATCH 098/188] Make better use of the screens in compact suffix mode --- lib/btclock/data_handler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 80e2328..60b0ef1 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -73,7 +73,8 @@ std::array parsePriceData(std::uint32_t price, char cu std::string priceString; if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) { - priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, NUM_SCREENS - 2, mowMode); + int numScreens = shareDot && !mowMode ? NUM_SCREENS - 1 : NUM_SCREENS - 2; + priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, numScreens, mowMode); } else { From 33c06c86a1394a3a8e608b5e65f4755edf63e315 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 5 Dec 2024 18:22:59 +0100 Subject: [PATCH 099/188] Prepare sats per dollar for 1B amounts --- lib/btclock/data_handler.cpp | 21 +++++++++++-- test/test_datahandler/test_main.cpp | 47 +++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index 60b0ef1..b489bb8 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -139,9 +139,26 @@ std::array parseSatsPerCurrency(std::uint32_t price,ch if (priceString.length() < (NUM_SCREENS)) { - priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); + // Check if price is greater than 1 billion + if (price >= 100000000) + { + double satsPerCurrency = (1.0 / static_cast(price)) * 1e8; // Calculate satoshis + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << satsPerCurrency; // Format with 3 decimal places + priceString = oss.str(); + } + else + { + priceString = std::to_string(static_cast(round(1.0 / static_cast(price) * 1e8))); // Default formatting + } - if (currencySymbol != CURRENCY_USD) + // Pad the string with spaces if necessary + if (priceString.length() < NUM_SCREENS) + { + priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); + } + + if (currencySymbol != CURRENCY_USD || price >= 100000000) // no time anymore when earlier than 1 ret[0] = "SATS/" + getCurrencyCode(currencySymbol); else ret[0] = "MSCW/TIME"; diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index 6314f34..b09c9b1 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -33,6 +33,17 @@ void test_CorrectSatsPerDollarConversion(void) TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS - 1].c_str()); } +void test_SatsPerDollarAfter1B(void) +{ + std::array output = parseSatsPerCurrency(120000000, CURRENCY_USD, false); + TEST_ASSERT_EQUAL_STRING("SATS/USD", output[0].c_str()); + TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 5].c_str()); + TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str()); + TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS - 3].c_str()); + TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 2].c_str()); + TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 1].c_str()); +} + void test_CorrectSatsPerPoundConversion(void) { std::array output = parseSatsPerCurrency(37253, CURRENCY_GBP, false); @@ -101,25 +112,33 @@ void test_PriceSuffixMode(void) void test_PriceSuffixModeCompact1(void) { std::array output = parsePriceData(100000, '$', true, false, true); - TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); - TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 5].c_str()); - TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 4].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str()); - TEST_ASSERT_EQUAL_STRING("K", output[NUM_SCREENS - 1].c_str()); + std::string joined = joinArrayWithBrackets(output); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("BTC/USD", output[0].c_str(), joined.c_str()); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("1", output[NUM_SCREENS - 5].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0.", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("K", output[NUM_SCREENS - 1].c_str(), joined.c_str()); } void test_PriceSuffixModeCompact2(void) { std::array output = parsePriceData(1000000, '$', true, false, true); - TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); - TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 5].c_str()); - TEST_ASSERT_EQUAL_STRING("1.", output[NUM_SCREENS - 4].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str()); - TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str()); - TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS - 1].c_str()); + std::string joined = joinArrayWithBrackets(output); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("BTC/USD", output[0].c_str(), joined.c_str()); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("1.", output[NUM_SCREENS - 5].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("M", output[NUM_SCREENS - 1].c_str(), joined.c_str()); } void test_PriceSuffixModeMow(void) @@ -143,7 +162,8 @@ void test_PriceSuffixModeMowCompact(void) std::string joined = joinArrayWithBrackets(output); - TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("BTC/USD", output[0].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str()); TEST_ASSERT_EQUAL_STRING_MESSAGE("0.", output[NUM_SCREENS - 5].c_str(), joined.c_str()); TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str()); @@ -258,6 +278,7 @@ int runUnityTests(void) UNITY_BEGIN(); RUN_TEST(test_CorrectSatsPerDollarConversion); RUN_TEST(test_CorrectSatsPerPoundConversion); + RUN_TEST(test_SatsPerDollarAfter1B); RUN_TEST(test_SixCharacterBlockHeight); RUN_TEST(test_SevenCharacterBlockHeight); RUN_TEST(test_FeeRateDisplay); From d6604d28d66a4e8458a8f412fff5a13c92c2599a Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 5 Dec 2024 18:28:46 +0100 Subject: [PATCH 100/188] Update some initial values --- src/lib/block_notify.cpp | 2 +- src/lib/price_notify.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index b86d497..c4db06e 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -2,7 +2,7 @@ char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; -uint currentBlockHeight = 860000; +uint currentBlockHeight = 873400; uint blockMedianFee = 1; bool blockNotifyInit = false; unsigned long int lastBlockUpdate; diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index f995de8..ab5192b 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -8,7 +8,7 @@ const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; // WebsocketsClient client; esp_websocket_client_handle_t clientPrice = NULL; esp_websocket_client_config_t config; -uint currentPrice = 50000; +uint currentPrice = 90000; unsigned long int lastPriceUpdate; bool priceNotifyInit = false; std::map currencyMap; From 132aa835cd8492900b9af9bc89f6cd1e25ad4635 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 9 Dec 2024 15:58:39 -0600 Subject: [PATCH 101/188] Mow Units no rounding! --- lib/btclock/data_handler.cpp | 11 +++++++++-- lib/btclock/utils.cpp | 24 +++++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/btclock/data_handler.cpp b/lib/btclock/data_handler.cpp index b489bb8..eaf4538 100644 --- a/lib/btclock/data_handler.cpp +++ b/lib/btclock/data_handler.cpp @@ -73,7 +73,7 @@ std::array parsePriceData(std::uint32_t price, char cu std::string priceString; if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) { - int numScreens = shareDot && !mowMode ? NUM_SCREENS - 1 : NUM_SCREENS - 2; + int numScreens = shareDot || mowMode ? NUM_SCREENS - 1 : NUM_SCREENS - 2; priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, numScreens, mowMode); } else @@ -85,7 +85,14 @@ std::array parsePriceData(std::uint32_t price, char cu { priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); - ret[0] = "BTC/" + getCurrencyCode(currencySymbol); + if (mowMode) + { + ret[0] = "MOW/UNITS"; + } + else + { + ret[0] = "BTC/" + getCurrencyCode(currencySymbol); + } firstIndex = 1; diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index 9056d14..45dfdd2 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -1,4 +1,5 @@ #include "utils.hpp" +#include int modulo(int x, int N) { @@ -83,14 +84,31 @@ std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mo } // Add suffix - int len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix); + int len; + + // Mow Mode always uses string truncation to avoid rounding + std::string mowAsString = std::to_string(numDouble); + if (mowMode) { + // Default to one decimal place + len = snprintf(result, sizeof(result), "%s%c", mowAsString.substr(0, mowAsString.find(".") + 2).c_str(), suffix); + } + else + { + len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix); + } // If there's room, add more decimal places if (len < numCharacters) { int restLen = mowMode ? numCharacters - len : numCharacters - len - 1; - - snprintf(result, sizeof(result), "%.*f%c", restLen, numDouble, suffix); + + if (mowMode) { + snprintf(result, sizeof(result), "%s%c", mowAsString.substr(0, mowAsString.find(".") + 2 + restLen).c_str(), suffix); + } + else + { + snprintf(result, sizeof(result), "%.*f%c", restLen, numDouble, suffix); + } } return result; From 9ada991ab15f7aa90060c8692da8554852617d7b Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 9 Dec 2024 16:05:55 -0600 Subject: [PATCH 102/188] Update utils.cpp --- lib/btclock/utils.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index 45dfdd2..0e19962 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -1,5 +1,4 @@ #include "utils.hpp" -#include int modulo(int x, int N) { From 2a116d97ed88333296949c6b84529bbb303827f3 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 10 Dec 2024 15:13:17 +0100 Subject: [PATCH 103/188] Add frontlight off when dark setting --- data | 2 +- src/lib/defaults.hpp | 2 ++ src/lib/webserver.cpp | 4 +++- src/main.cpp | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/data b/data index f0fa58b..25e91b2 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit f0fa58b5ea60f695aeaae9ddd7138cbb3686e96a +Subproject commit 25e91b2086936e7c6ef09e917c6efd371293fd7e diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 8af3b5b..ada5531 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -45,6 +45,8 @@ #define DEFAULT_FL_EFFECT_DELAY 15 #define DEFAULT_LUX_LIGHT_TOGGLE 128 +#define DEFAULT_FL_OFF_WHEN_DARK true + #define DEFAULT_FL_ALWAYS_ON false #define DEFAULT_FL_FLASH_ON_UPDATE false diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 777fe0b..b901379 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -546,7 +546,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", "suffixPrice", "disableLeds", "ownDataSource", - "mowMode", "suffixShareDot", + "mowMode", "suffixShareDot", "flOffWhenDark", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; @@ -726,6 +726,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["hasLightLevel"] = hasLightLevel(); root["luxLightToggle"] = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); + root["flOffWhenDark"] = preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK); + #else root["hasFrontlight"] = false; root["hasLightLevel"] = false; diff --git a/src/main.cpp b/src/main.cpp index 908f155..b9d2ae7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,7 +51,7 @@ extern "C" void app_main() if (hasLightLevel()) { if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { - if (hasLightLevel() && getLightLevel() <= 2) + if (hasLightLevel() && getLightLevel() <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) { if (frontlightIsOn()) { frontlightFadeOutAll(); From dbf2c53083c05187d5e6f152d5e2ba32a3fd16a1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 10 Dec 2024 15:18:04 +0100 Subject: [PATCH 104/188] Adapted tests for Mow Units --- test/test_datahandler/test_main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_datahandler/test_main.cpp b/test/test_datahandler/test_main.cpp index b09c9b1..24118e4 100644 --- a/test/test_datahandler/test_main.cpp +++ b/test/test_datahandler/test_main.cpp @@ -143,7 +143,7 @@ void test_PriceSuffixModeCompact2(void) void test_PriceSuffixModeMow(void) { - std::array output = parsePriceData(93000, '$', true, true); + std::array output = parsePriceData(93600, '$', true, true); std::string joined = joinArrayWithBrackets(output); @@ -158,11 +158,11 @@ void test_PriceSuffixModeMow(void) void test_PriceSuffixModeMowCompact(void) { - std::array output = parsePriceData(93000, '$', true, true, true); + std::array output = parsePriceData(93600, '$', true, true, true); std::string joined = joinArrayWithBrackets(output); - TEST_ASSERT_EQUAL_STRING_MESSAGE("BTC/USD", output[0].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("MOW/UNITS", output[0].c_str(), joined.c_str()); TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str()); TEST_ASSERT_EQUAL_STRING_MESSAGE("0.", output[NUM_SCREENS - 5].c_str(), joined.c_str()); From 34b77ea1056e2d7fa8c3df0795b666c6b2d07d84 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 12 Dec 2024 23:12:59 +0100 Subject: [PATCH 105/188] Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 25e91b2..653a39d 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 25e91b2086936e7c6ef09e917c6efd371293fd7e +Subproject commit 653a39d0a339bcf3ac3365eacd2e5e8136ecf4c6 From db0ec01c869bb2485308943a679b98a036272b05 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 17 Dec 2024 08:53:00 -0600 Subject: [PATCH 106/188] Add bitaxe logo --- lib/btclock/bitaxe_handler.cpp | 4 +- src/icons/icons.cpp | 260 ++++++++++++++++++++++++++++++++- src/lib/epd.cpp | 6 +- 3 files changed, 264 insertions(+), 6 deletions(-) diff --git a/lib/btclock/bitaxe_handler.cpp b/lib/btclock/bitaxe_handler.cpp index cbc71ec..c18b7de 100644 --- a/lib/btclock/bitaxe_handler.cpp +++ b/lib/btclock/bitaxe_handler.cpp @@ -24,7 +24,7 @@ std::array parseBitaxeHashRate(std::string text) } ret[NUM_SCREENS - 1] = "GH/S"; - ret[0] = "BIT/AXE"; + ret[0] = "mdi:bitaxe"; return ret; } @@ -37,7 +37,7 @@ std::array parseBitaxeBestDiff(std::string text) if (text.length() < NUM_SCREENS) { text.insert(text.begin(), NUM_SCREENS - text.length(), ' '); - ret[0] = "BIT/AXE"; + ret[0] = "mdi:bitaxe"; ret[1] = "mdi:rocket"; firstIndex = 2; } diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index 3254740..ca69f63 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -1015,11 +1015,267 @@ const unsigned char epd_icons_flash [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; +// 'bitaxe_logo', 122x250px +const unsigned char epd_icons_bitaxe_logo [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xcf, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0x83, 0xff, 0xff, 0xf7, 0xf0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xf1, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xcf, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xf9, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xf8, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0x8f, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x8f, 0xf7, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xef, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x11, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0x9f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xfb, 0xf0, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xf7, 0xf8, 0x3f, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xf1, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0x83, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xef, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xff, 0xff, 0x83, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x1f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x0f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x01, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xbf, 0xff, 0x01, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc1, 0x00, 0x00, 0x7f, 0xc1, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x81, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xfd, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xfb, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xf3, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xe7, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0x8f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x82, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; + + // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) -const int epd_icons_allArray_LEN = 4; +const int epd_icons_allArray_LEN = 5; const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { epd_icons_pickaxe, epd_icons_rocket_launch, epd_icons_lightning, - epd_icons_flash + epd_icons_flash, + epd_icons_bitaxe_logo }; diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 1c04725..3ef06e6 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -603,10 +603,12 @@ void renderIcon(const uint dispNum, const String &text, bool partial) if (text.endsWith("rocket")) { iconIndex = 1; } - - if (text.endsWith("lnbolt")) { + else if (text.endsWith("lnbolt")) { iconIndex = 3; } + else if (text.endsWith("bitaxe")) { + iconIndex = 4; + } displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor()); From 4a52fc0bf22c050a440eca7aa885cf18bcd42f10 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Dec 2024 00:50:20 +0100 Subject: [PATCH 107/188] Add vertical screen description option --- data | 2 +- src/lib/defaults.hpp | 1 + src/lib/epd.cpp | 6 +++++- src/lib/webserver.cpp | 3 +++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/data b/data index 653a39d..266a99b 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 653a39d0a339bcf3ac3365eacd2e5e8136ecf4c6 +Subproject commit 266a99be96189bea92e0ef593f930bb92d3b5b20 diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index ada5531..11df69a 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -70,3 +70,4 @@ #define DEFAULT_ACTIVE_CURRENCIES "USD,EUR,JPY" #define DEFAULT_GIT_RELEASE_URL "https://git.btclock.dev/api/v1/repos/btclock/btclock_v3/releases/latest" +#define DEFAULT_VERTICAL_DESC true diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 1c04725..986b9cc 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -373,7 +373,11 @@ extern "C" void updateDisplay(void *pvParameters) noexcept void splitText(const uint dispNum, const String &top, const String &bottom, bool partial) { - displays[dispNum].setRotation(2); + if(preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { + displays[dispNum].setRotation(1); + } else { + displays[dispNum].setRotation(2); + } displays[dispNum].setFont(&FONT_SMALL); displays[dispNum].setTextColor(getFgColor()); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index b901379..11b9024 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -549,6 +549,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mowMode", "suffixShareDot", "flOffWhenDark", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", + "verticalDesc", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; for (String setting : boolSettings) @@ -689,6 +690,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE); root["disableLeds"] = preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS); root["mowMode"] = preferences.getBool("mowMode", DEFAULT_MOW_MODE); + root["verticalDesc"] = preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC); + root["suffixShareDot"] = preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT); root["hostnamePrefix"] = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX); From 83d293c58e46f7698fd484bda35c7130a568014c Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Dec 2024 01:32:11 +0100 Subject: [PATCH 108/188] Add bitaxe logo to WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 266a99b..85b9b17 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 266a99be96189bea92e0ef593f930bb92d3b5b20 +Subproject commit 85b9b17506f89696b89ab6f6e6ed231b7a8f6e91 From af4c46665912b444bdaa056bbab6c060a767220e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Dec 2024 19:47:03 +0100 Subject: [PATCH 109/188] Switch to leaner MCP23017 library, create new aligned partition tables --- .forgejo/workflows/push.yaml | 26 +++++++++++++------ partition.csv | 4 +-- partition_16mb.csv | 12 ++++----- partition_8mb.csv | 12 ++++----- platformio.ini | 14 +++++------ scripts/pre_script.py | 7 ++++++ src/lib/button_handler.cpp | 23 ++++++++--------- src/lib/config.cpp | 48 ++++++++++++++++++++++++++---------- src/lib/config.hpp | 6 +++-- src/lib/epd.cpp | 11 ++++++++- src/lib/epd.hpp | 1 + src/lib/shared.cpp | 10 +++++++- src/lib/shared.hpp | 12 ++++++--- 13 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 scripts/pre_script.py diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index 5121d24..6ccb926 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -95,7 +95,6 @@ jobs: - name: Create merged firmware binary run: | if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then - mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ esptool.py --chip ${{ matrix.chip.version }} merge_bin \ -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ --flash_mode dio \ @@ -105,7 +104,19 @@ jobs: 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ - 0x810000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin; + 0xDF0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_16MB.bin + elif [ "${{ matrix.chip.name }}" == "btclock_rev_b" ]; then + mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ + esptool.py --chip ${{ matrix.chip.version }} merge_bin \ + -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ + --flash_mode dio \ + --flash_freq 80m \ + --flash_size 8MB \ + 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \ + 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ + 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ + 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ + 0x6F0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_8MB.bin; else # Original command for other cases mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ @@ -116,7 +127,7 @@ jobs: 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \ 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \ 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ - 0x370000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin + 0x380000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_4MB.bin # Adjust the offset for littlefs or other files as needed for the original case fi @@ -127,10 +138,11 @@ jobs: run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256 - name: Create checksum for littlefs partition - run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin.sha256 - - - name: Copy all artifacts to output folder - run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} + run: | + fs_file=$(find .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }} -name "littlefs*.bin") + shasum -a 256 "$fs_file" | awk '{print $1}' > "${fs_file}.sha256" + - name: Copy all artifacts to output folder + run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} - name: Create OTA binary file run: mv ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin diff --git a/partition.csv b/partition.csv index 80714ea..eedbf5b 100644 --- a/partition.csv +++ b/partition.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x1b8000, app1, app, ota_1, , 0x1b8000, -spiffs, data, spiffs, , 0x66800, -coredump, data, coredump,, 0x10000, \ No newline at end of file +spiffs, data, spiffs, , 0x66C00, +coredump, data, coredump,, 0x10000, diff --git a/partition_16mb.csv b/partition_16mb.csv index e26e145..7e58611 100644 --- a/partition_16mb.csv +++ b/partition_16mb.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 36K, 20K, -otadata, data, ota, 56K, 8K, -app0, app, ota_0, 64K, 4096K, -app1, app, ota_1, , 4096K, -spiffs, data, spiffs, , 410K, -coredump, data, coredump,, 64K, +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x6F0000, +app1, app, ota_1, , 0x6F0000, +spiffs, data, spiffs, , 0x200000, +coredump, data, coredump,, 0x10000, \ No newline at end of file diff --git a/partition_8mb.csv b/partition_8mb.csv index 778b9f2..025f649 100644 --- a/partition_8mb.csv +++ b/partition_8mb.csv @@ -1,7 +1,7 @@ # Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 36K, 20K, -otadata, data, ota, 56K, 8K, -app0, app, ota_0, 64K, 1760K, -app1, app, ota_1, , 1760K, -spiffs, data, spiffs, , 410K, -coredump, data, coredump,, 64K, +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x370000, +app1, app, ota_1, , 0x370000, +spiffs, data, spiffs, , 0xCD000, +coredump, data, coredump,, 0x10000, \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 7b0d229..d2abb92 100644 --- a/platformio.ini +++ b/platformio.ini @@ -20,8 +20,9 @@ framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize board_build.filesystem = littlefs -extra_scripts = post:scripts/extra_script.py -board_build.embed_files = x509_crt_bundle +extra_scripts = pre:scripts/pre_script.py, post:scripts/extra_script.py +board_build.embed_files = + x509_crt_bundle build_flags = !python scripts/git_rev.py -DLAST_BUILD_TIME=$UNIX_TIME @@ -35,14 +36,13 @@ lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.2.1 mathieucarbou/ESPAsyncWebServer @ 3.3.23 - adafruit/Adafruit BusIO@^1.16.2 - adafruit/Adafruit MCP23017 Arduino Library@^2.3.2 + robtillaart/MCP23017@^0.8.0 adafruit/Adafruit NeoPixel@^1.12.3 - https://github.com/dsbaars/universal_pin + https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 rblb/Nostrduino@1.2.8 - + [env:lolin_s3_mini] extends = btclock_base board = lolin_s3_mini @@ -64,7 +64,7 @@ build_unflags = [env:btclock_rev_b] extends = btclock_base board = btclock_rev_b -board_build.partitions = partition.csv +board_build.partitions = partition_8mb.csv build_flags = ${btclock_base.build_flags} -D MCP_INT_PIN=8 diff --git a/scripts/pre_script.py b/scripts/pre_script.py new file mode 100644 index 0000000..45d6bea --- /dev/null +++ b/scripts/pre_script.py @@ -0,0 +1,7 @@ +Import("env") + +flash_size = env.BoardConfig().get("upload.flash_size", "4MB") +fs_image_name = f"littlefs_{flash_size}" +env.Replace(ESP32_FS_IMAGE_NAME=fs_image_name) +env.Replace(ESP8266_FS_IMAGE_NAME=fs_image_name) + diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index 437090d..72f1eaa 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -5,15 +5,15 @@ const TickType_t debounceDelay = pdMS_TO_TICKS(50); TickType_t lastDebounceTime = 0; #ifdef IS_BTCLOCK_V8 -#define BTN_1 0 -#define BTN_2 1 -#define BTN_3 2 -#define BTN_4 3 +#define BTN_1 256 +#define BTN_2 512 +#define BTN_3 1024 +#define BTN_4 2048 #else -#define BTN_1 3 -#define BTN_2 2 -#define BTN_3 1 -#define BTN_4 0 +#define BTN_1 2048 +#define BTN_2 1024 +#define BTN_3 512 +#define BTN_4 256 #endif void buttonTask(void *parameter) { @@ -22,11 +22,12 @@ void buttonTask(void *parameter) { std::lock_guard lock(mcpMutex); TickType_t currentTime = xTaskGetTickCount(); + if ((currentTime - lastDebounceTime) >= debounceDelay) { lastDebounceTime = currentTime; if (!digitalRead(MCP_INT_PIN)) { - uint pin = mcp1.getLastInterruptPin(); + uint pin = mcp1.getInterruptFlagRegister(); switch (pin) { case BTN_1: @@ -43,12 +44,12 @@ void buttonTask(void *parameter) { break; } } - mcp1.clearInterrupts(); + mcp1.getInterruptCaptureRegister(); } else { } // Very ugly, but for some reason this is necessary while (!digitalRead(MCP_INT_PIN)) { - mcp1.clearInterrupts(); + mcp1.getInterruptCaptureRegister(); } } } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 8f954b2..e9912a9 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -2,10 +2,12 @@ #define MAX_ATTEMPTS_WIFI_CONNECTION 20 +// zlib_turbo zt; + Preferences preferences; -Adafruit_MCP23X17 mcp1; +MCP23017 mcp1(0x20); #ifdef IS_BTCLOCK_V8 -Adafruit_MCP23X17 mcp2; +MCP23017 mcp2(0x21); #endif #ifdef HAS_FRONTLIGHT @@ -35,7 +37,7 @@ void setup() } { std::lock_guard lockMcp(mcpMutex); - if (mcp1.digitalRead(3) == LOW) + if (mcp1.read1(3) == LOW) { preferences.putBool("wifiConfigured", false); preferences.remove("txPower"); @@ -46,7 +48,7 @@ void setup() } { - if (mcp1.digitalRead(0) == LOW) + if (mcp1.read1(0) == LOW) { // Then loop forever to prevent anything else from writing to the screen while (true) @@ -54,7 +56,7 @@ void setup() delay(1000); } } - else if (mcp1.digitalRead(1) == LOW) + else if (mcp1.read1(1) == LOW) { preferences.clear(); queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); @@ -66,6 +68,7 @@ void setup() } setupWifi(); + // loadIcons(); setupWebserver(); @@ -106,6 +109,7 @@ void setup() #endif forceFullRefresh(); + } void setupWifi() @@ -132,7 +136,7 @@ void setupWifi() bool buttonPress = false; { std::lock_guard lockMcp(mcpMutex); - buttonPress = (mcp1.digitalRead(2) == LOW); + buttonPress = (mcp1.read1(2) == LOW); } { @@ -507,7 +511,7 @@ void setupHardware() Wire.begin(I2C_SDA_PIN, I2C_SCK_PIN, 400000); - if (!mcp1.begin_I2C(0x20)) + if (!mcp1.begin()) { Serial.println(F("Error MCP23017 1")); @@ -517,17 +521,20 @@ void setupHardware() else { pinMode(MCP_INT_PIN, INPUT_PULLUP); - mcp1.setupInterrupts(false, false, LOW); +// mcp1.setupInterrupts(false, false, LOW); + mcp1.enableControlRegister(MCP23x17_IOCR_ODR); + + mcp1.mirrorInterrupts(true); for (int i = 0; i < 4; i++) { - mcp1.pinMode(i, INPUT_PULLUP); - mcp1.setupInterruptPin(i, LOW); + mcp1.pinMode1(i, INPUT_PULLUP); + mcp1.enableInterrupt(i, LOW); } #ifndef IS_BTCLOCK_V8 for (int i = 8; i <= 14; i++) { - mcp1.pinMode(i, OUTPUT); + mcp1.pinMode1(i, OUTPUT); } #endif } @@ -538,7 +545,7 @@ void setupHardware() #endif #ifdef IS_BTCLOCK_V8 - if (!mcp2.begin_I2C(0x21)) + if (!mcp2.begin()) { Serial.println(F("Error MCP23017 2")); @@ -790,4 +797,19 @@ const char* getFirmwareFilename() { } else { return ""; } -} \ No newline at end of file +} + +// void loadIcons() { +// size_t ocean_logo_size = 886; + +// int iUncompSize = zt.gzip_info((uint8_t *)epd_compress_bitaxe, ocean_logo_size); +// Serial.printf("uncompressed size = %d\n", iUncompSize); + +// uint8_t *pUncompressed; +// pUncompressed = (uint8_t *)malloc(iUncompSize+4); +// int rc = zt.gunzip((uint8_t *)epd_compress_bitaxe, ocean_logo_size, pUncompressed); + +// if (rc == ZT_SUCCESS) { +// Serial.println("Decode success"); +// } +// } \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 8c9da90..53abe17 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -83,4 +83,6 @@ void addScreenMapping(int value, const char* name); int findScreenIndexByValue(int value); String replaceAmbiguousChars(String input); -const char* getFirmwareFilename(); \ No newline at end of file +const char* getFirmwareFilename(); + +// void loadIcons(); \ No newline at end of file diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 1e27eae..2b30f26 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -191,7 +191,7 @@ void setupDisplays() } // Hold lower button to enable "storage mode" (prevents burn-in of ePaper displays) - if (mcp1.digitalRead(0) == LOW) + if (mcp1.read1(0) == LOW) { setFgColor(GxEPD_BLACK); setBgColor(GxEPD_WHITE); @@ -614,10 +614,19 @@ void renderIcon(const uint dispNum, const String &text, bool partial) iconIndex = 4; } + + + displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor()); + +// displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); + + } + + void renderQr(const uint dispNum, const String &text, bool partial) { #ifdef USE_QR diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 749f940..1194cf1 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -4,6 +4,7 @@ #include #include + #include #include #include diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index 20bf6d4..3422ff8 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -143,4 +143,12 @@ String calculateSHA256(WiFiClient *stream, size_t contentLength) { } return result; -} \ No newline at end of file +} + +// uint8_t* getOceanIcon() { +// zlib_turbo zt; +// int iUncompSize = zt.gzip_info((uint8_t *)ocean_logo_comp, ocean_logo_size); +// uint8_t *pUncompressed; +// pUncompressed = (uint8_t *)malloc(iUncompSize+4); +// zt.gunzip((uint8_t *)ocean_logo_comp, ocean_logo_size, pUncompressed); +// } \ No newline at end of file diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 19d1df0..4753764 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include "MCP23017.h" +// #include #include #include #include @@ -17,9 +18,9 @@ #include "defaults.hpp" -extern Adafruit_MCP23X17 mcp1; +extern MCP23017 mcp1; #ifdef IS_BTCLOCK_V8 -extern Adafruit_MCP23X17 mcp2; +extern MCP23017 mcp2; #endif extern Preferences preferences; extern std::mutex mcpMutex; @@ -73,7 +74,12 @@ const int usPerMinute = 60 * usPerSecond; extern const char *isrg_root_x1cert; extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start"); +// extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start"); +// extern const uint8_t ocean_logo_comp_end[] asm("_binary_ocean_gz_end"); +// uint8_t* getOceanIcon(); + +// const size_t ocean_logo_size = ocean_logo_comp_end - ocean_logo_comp; const PROGMEM char UPDATE_FIRMWARE = U_FLASH; const PROGMEM char UPDATE_WEBUI = U_SPIFFS; From de8fe2e26eed61918f8133c614860a26d6291f9a Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Dec 2024 20:41:42 +0100 Subject: [PATCH 110/188] Fix preaction script --- scripts/extra_script.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/extra_script.py b/scripts/extra_script.py index d7e4b31..8451978 100644 --- a/scripts/extra_script.py +++ b/scripts/extra_script.py @@ -5,6 +5,9 @@ from shutil import copyfileobj, rmtree from pathlib import Path import subprocess + + + revision = ( subprocess.check_output(["git", "rev-parse", "HEAD"]) .strip() @@ -43,5 +46,15 @@ def before_buildfs(source, target, env): output_directory = 'data/build_gz' process_directory(input_directory, output_directory) +flash_size = env.BoardConfig().get("upload.flash_size", "4MB") +fs_image_name = f"littlefs_{flash_size}" +env.Replace(ESP32_FS_IMAGE_NAME=fs_image_name) +env.Replace(ESP8266_FS_IMAGE_NAME=fs_image_name) + os.environ["PUBLIC_BASE_URL"] = "" -env.AddPreAction("$BUILD_DIR/littlefs.bin", before_buildfs) +fs_name = env.get("ESP32_FS_IMAGE_NAME", "littlefs.bin") +# Or alternatively: +# fs_name = env.get("FSTOOLNAME", "littlefs.bin") + +# Use the variable in the pre-action +env.AddPreAction(f"$BUILD_DIR/{fs_name}.bin", before_buildfs) From 0dcde59fb4e2d3ac939886689b23e9b2c77b1ab1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Dec 2024 21:40:18 +0100 Subject: [PATCH 111/188] Fix workflow --- .forgejo/workflows/push.yaml | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index 6ccb926..02458ae 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -1,4 +1,4 @@ -name: 'BTClock CI' +name: "BTClock CI" on: push: @@ -22,7 +22,7 @@ jobs: with: node-version: lts/* cache: yarn - cache-dependency-path: '**/yarn.lock' + cache-dependency-path: "**/yarn.lock" - uses: actions/cache@v4 with: path: | @@ -34,8 +34,8 @@ jobs: key: ${{ runner.os }}-pio - uses: actions/setup-python@v5 with: - python-version: '3.9' - cache: 'pip' + python-version: "3.9" + cache: "pip" - name: Get current date id: dateAndTime shell: bash @@ -48,7 +48,7 @@ jobs: run: mkdir -p junit-reports && pio test -e native_test_only --junit-output-path junit-reports/ - name: Build BTClock firmware shell: bash - run: pio run + run: pio run - name: Build BTClock filesystem shell: bash run: pio run --target buildfs @@ -81,9 +81,9 @@ jobs: version: esp32s3 epd_variant: [213epd, 29epd] exclude: - - chip: {name: btclock_rev_b, version: esp32s3} + - chip: { name: btclock_rev_b, version: esp32s3 } epd_variant: 29epd - - chip: {name: btclock_v8, version: esp32s3} + - chip: { name: btclock_v8, version: esp32s3 } epd_variant: 29epd steps: - uses: https://code.forgejo.org/forgejo/download-artifact@v4 @@ -93,7 +93,9 @@ jobs: - name: Install esptools.py run: pip install --upgrade esptool - name: Create merged firmware binary + shell: bash run: | + mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then esptool.py --chip ${{ matrix.chip.version }} merge_bin \ -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ @@ -106,7 +108,6 @@ jobs: 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ 0xDF0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_16MB.bin elif [ "${{ matrix.chip.name }}" == "btclock_rev_b" ]; then - mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ esptool.py --chip ${{ matrix.chip.version }} merge_bin \ -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ --flash_mode dio \ @@ -118,8 +119,6 @@ jobs: 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ 0x6F0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_8MB.bin; else - # Original command for other cases - mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \ esptool.py --chip ${{ matrix.chip.version }} merge_bin \ -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ --flash_mode dio \ @@ -132,17 +131,20 @@ jobs: fi - name: Create checksum for firmware + shell: bash run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin.sha256 - name: Create checksum for merged binary + shell: bash run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256 - name: Create checksum for littlefs partition + shell: bash run: | fs_file=$(find .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }} -name "littlefs*.bin") shasum -a 256 "$fs_file" | awk '{print $1}' > "${fs_file}.sha256" - - name: Copy all artifacts to output folder - run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} + - name: Copy all artifacts to output folder + run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} - name: Create OTA binary file run: mv ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin @@ -173,13 +175,13 @@ jobs: - name: Create release uses: https://code.forgejo.org/actions/forgejo-release@v2.4.0 with: - url: 'https://git.btclock.dev' - repo: '${{ github.repository }}' + url: "https://git.btclock.dev" + repo: "${{ github.repository }}" direction: upload - tag: '${{ github.ref_name }}' - sha: '${{ github.sha }}' + tag: "${{ github.ref_name }}" + sha: "${{ github.sha }}" release-dir: release token: ${{ secrets.TOKEN }} override: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }} prerelease: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }} - release-notes-assistant: false \ No newline at end of file + release-notes-assistant: false From c989169ff46581785e10132db04d980264713571 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Dec 2024 22:20:40 +0100 Subject: [PATCH 112/188] Fix workflow and auto updater webui filenames --- .forgejo/workflows/push.yaml | 2 +- src/lib/config.cpp | 13 +++++++++++++ src/lib/config.hpp | 2 +- src/lib/ota.cpp | 3 +-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index 02458ae..b95439c 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -142,7 +142,7 @@ jobs: shell: bash run: | fs_file=$(find .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }} -name "littlefs*.bin") - shasum -a 256 "$fs_file" | awk '{print $1}' > "${fs_file}.sha256" + shasum -a 256 "$fs_file" | awk '{print $1}' > "${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${fs_file}.sha256" - name: Copy all artifacts to output folder run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} diff --git a/src/lib/config.cpp b/src/lib/config.cpp index e9912a9..1dac009 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -799,6 +799,19 @@ const char* getFirmwareFilename() { } } +const char* getWebUiFilename() { + if (HW_REV == "REV_B_EPD_2_13") { + return "littlefs_8MB.bin"; + } else if (HW_REV == "REV_A_EPD_2_13") { + return "littlefs_4MB.bin"; + } else if (HW_REV == "REV_A_EPD_2_9") { + return "littlefs_4MB.bin"; + } else { + return "littlefs_4MB.bin"; + } +} + + // void loadIcons() { // size_t ocean_logo_size = 886; diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 53abe17..51aef87 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -84,5 +84,5 @@ void addScreenMapping(int value, const char* name); int findScreenIndexByValue(int value); String replaceAmbiguousChars(String input); const char* getFirmwareFilename(); - +const char* getWebUiFilename(); // void loadIcons(); \ No newline at end of file diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 1800f76..05c45e1 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -171,14 +171,13 @@ int downloadUpdateHandler(char updateType) break; case UPDATE_WEBUI: { - latestRelease = getLatestRelease("littlefs.bin"); + latestRelease = getLatestRelease(getWebUiFilename()); // updateWebUi(latestRelease.fileUrl, U_SPIFFS); // return 0; } break; } - // First, download the expected SHA256 String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl); if (expectedSHA256.isEmpty()) From ae2e6656dfaef3cf762c86057b3716871b0fb850 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 18 Dec 2024 22:52:50 +0100 Subject: [PATCH 113/188] Fix LittleFS sha256 generation --- .forgejo/workflows/push.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index b95439c..219205f 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -142,7 +142,10 @@ jobs: shell: bash run: | fs_file=$(find .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }} -name "littlefs*.bin") - shasum -a 256 "$fs_file" | awk '{print $1}' > "${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${fs_file}.sha256" + echo $fs_file + fs_name=$(basename "$fs_file") + shasum -a 256 "$fs_file" | awk '{print $1}' > "${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${fs_name}.sha256" + cat "${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${fs_name}.sha256" - name: Copy all artifacts to output folder run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }} From 1bd465b33acdb241389cdde1dcdcdcdb8c55789d Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 17 Dec 2024 20:17:21 -0600 Subject: [PATCH 114/188] rebuilt with larger partitions --- lib/btclock/mining_pool_stats_handler.cpp | 66 +++ lib/btclock/mining_pool_stats_handler.hpp | 4 + partition_8mb.csv | 2 +- platformio.ini | 1 + src/icons/icons.cpp | 516 +++++++++++++++++++++- src/lib/config.cpp | 10 + src/lib/config.hpp | 1 + src/lib/defaults.hpp | 7 + src/lib/epd.cpp | 6 + src/lib/mining_pool_stats_fetch.cpp | 120 +++++ src/lib/mining_pool_stats_fetch.hpp | 15 + src/lib/screen_handler.cpp | 24 +- src/lib/screen_handler.hpp | 4 +- src/lib/shared.hpp | 2 + src/lib/timers.cpp | 4 + src/lib/webserver.cpp | 8 +- 16 files changed, 782 insertions(+), 8 deletions(-) create mode 100644 lib/btclock/mining_pool_stats_handler.cpp create mode 100644 lib/btclock/mining_pool_stats_handler.hpp create mode 100644 src/lib/mining_pool_stats_fetch.cpp create mode 100644 src/lib/mining_pool_stats_fetch.hpp diff --git a/lib/btclock/mining_pool_stats_handler.cpp b/lib/btclock/mining_pool_stats_handler.cpp new file mode 100644 index 0000000..17eb4c8 --- /dev/null +++ b/lib/btclock/mining_pool_stats_handler.cpp @@ -0,0 +1,66 @@ +#include "mining_pool_stats_handler.hpp" +#include + +std::array parseMiningPoolStatsHashRate(std::string miningPoolName, std::string text) +{ + std::array ret; + ret.fill(""); // Initialize all elements to empty strings + std::string hashrate; + std::string label; + + if (text.length() > 21) { + // We are massively future-proof!! + label = "ZH/S"; + hashrate = text.substr(0, text.length() - 21); + } else if (text.length() > 18) { + label = "EH/S"; + hashrate = text.substr(0, text.length() - 18); + } else if (text.length() > 15) { + label = "PH/S"; + hashrate = text.substr(0, text.length() - 15); + } else if (text.length() > 12) { + label = "TH/S"; + hashrate = text.substr(0, text.length() - 12); + } else if (text.length() > 9) { + label = "GH/S"; + hashrate = text.substr(0, text.length() - 9); + } else if (text.length() > 6) { + label = "MH/S"; + hashrate = text.substr(0, text.length() - 6); + } else if (text.length() > 3) { + label = "KH/S"; + hashrate = text.substr(0, text.length() - 3); + } else { + label = "H/S"; + hashrate = text; + } + + std::size_t textLength = hashrate.length(); + + // Calculate the position where the digits should start + // Account for the position of the mining pool logo and the hashrate label + std::size_t startIndex = NUM_SCREENS - 1 - textLength; + + std::cout << "miningPoolName: " << miningPoolName << std::endl; + + // Insert the mining pool logo icon just before the digits + if (startIndex > 0) + { + if (miningPoolName == "ocean") { + ret[startIndex - 1] = "mdi:braiins_logo"; + } else if (miningPoolName == "braiins") { + ret[startIndex - 1] = "mdi:braiins_logo"; + } + } + + // Place the digits + for (std::size_t i = 0; i < textLength; ++i) + { + ret[startIndex + i] = hashrate.substr(i, 1); + } + + ret[NUM_SCREENS - 1] = label; + ret[0] = "POOL/STATS"; + + return ret; +} \ No newline at end of file diff --git a/lib/btclock/mining_pool_stats_handler.hpp b/lib/btclock/mining_pool_stats_handler.hpp new file mode 100644 index 0000000..b79d8f9 --- /dev/null +++ b/lib/btclock/mining_pool_stats_handler.hpp @@ -0,0 +1,4 @@ +#include +#include + +std::array parseMiningPoolStatsHashRate(std::string miningPoolName, std::string text); \ No newline at end of file diff --git a/partition_8mb.csv b/partition_8mb.csv index 025f649..e0584ae 100644 --- a/partition_8mb.csv +++ b/partition_8mb.csv @@ -4,4 +4,4 @@ otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x370000, app1, app, ota_1, , 0x370000, spiffs, data, spiffs, , 0xCD000, -coredump, data, coredump,, 0x10000, \ No newline at end of file +coredump, data, coredump,, 0x10000, diff --git a/platformio.ini b/platformio.ini index d2abb92..125ad96 100644 --- a/platformio.ini +++ b/platformio.ini @@ -96,6 +96,7 @@ build_flags = [env:btclock_rev_b_213epd] extends = env:btclock_rev_b +board_build.partitions = partition_8mb.csv test_framework = unity build_flags = ${env:btclock_rev_b.build_flags} diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index ca69f63..d80fde1 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -1270,12 +1270,524 @@ const unsigned char epd_icons_bitaxe_logo [] PROGMEM = { }; +// 'ocean_logo', 122x250px +const unsigned char epd_icons_ocean_logo [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x78, 0x00, 0x1f, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xfc, 0x00, 0x3f, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x00, 0x7f, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xc0, + 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xc0, + 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xc0, + 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xc0, + 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xc0, + 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xc0, + 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xc0, + 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x3f, 0xc0, + 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x1f, 0xc0, + 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x03, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x1f, 0xc0, + 0xfe, 0x00, 0x78, 0x00, 0x7f, 0xff, 0xf1, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x1f, 0xc0, + 0xfe, 0x00, 0xfc, 0x00, 0x3f, 0xff, 0xf1, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xc0, 0x1f, 0xc0, + 0xff, 0x00, 0xff, 0x00, 0x1f, 0xff, 0xf1, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xc0, + 0xff, 0x01, 0xff, 0x80, 0x1f, 0xff, 0xf3, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xc0, + 0xff, 0x01, 0xff, 0xc0, 0x0f, 0xff, 0xe3, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xc0, + 0xff, 0x01, 0xff, 0xe0, 0x07, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xc0, + 0xff, 0x81, 0xff, 0xf8, 0x03, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xc0, + 0xff, 0x81, 0xff, 0xfc, 0x01, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xc0, + 0xff, 0x83, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xc0, + 0xff, 0xc3, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xc0, + 0xff, 0xc3, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xc0, + 0xff, 0xe3, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xc0, + 0xff, 0xe3, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xc0, + 0xff, 0xf3, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xf9, 0xff, 0xc0, + 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xf9, 0xff, 0xc0, + 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xf0, 0xff, 0xc0, + 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0xf0, 0xff, 0xc0, + 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xf0, 0x7f, 0xc0, + 0xff, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xf0, 0x7f, 0xc0, + 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xf0, 0x07, 0xff, 0xf0, 0x7f, 0xc0, + 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf8, 0x03, 0xff, 0xf0, 0x3f, 0xc0, + 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf8, 0x01, 0xff, 0xe0, 0x3f, 0xc0, + 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xf1, 0xff, 0xfc, 0x00, 0xff, 0xe0, 0x3f, 0xc0, + 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xf1, 0xff, 0xfe, 0x00, 0x3f, 0xe0, 0x1f, 0xc0, + 0xff, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xf1, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x1f, 0xc0, + 0xfe, 0x00, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xf1, 0xff, 0xff, 0x80, 0x0f, 0xc0, 0x1f, 0xc0, + 0xfe, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x07, 0xff, 0xe1, 0xff, 0xff, 0x80, 0x07, 0x80, 0x1f, 0xc0, + 0xfe, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xe3, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xc0, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xc0, + 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x1f, 0xc0, + 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xc0, + 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xc0, + 0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xc0, + 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xc0, + 0xff, 0x80, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xc0, + 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xc0, + 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xc0, + 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xc0, 0x0f, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0x80, 0x0f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0x80, 0x0f, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x07, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; + + +// 'braiins_pool', 122x250px +const unsigned char epd_icons_braiins_logo [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xc0, 0x38, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0x7c, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x00, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x01, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x01, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x1f, 0x01, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x0c, 0x01, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x01, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x03, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x03, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x07, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x0f, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x1f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x3f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0x00, 0x00, 0x01, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x03, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0x00, 0x03, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x07, 0xff, 0x81, 0xff, 0xe0, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x07, 0xfe, 0x00, 0x7f, 0xe0, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x1f, 0xf0, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x00, 0x0f, 0xf0, 0x00, 0x0f, 0xf0, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x0f, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xc0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x06, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0x00, 0x0f, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x00, 0x03, 0xff, 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0x00, 0x07, 0xff, 0x00, 0xff, 0xe0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x03, 0xff, 0x00, 0x0f, 0xfc, 0x00, 0x3f, 0xe0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x1f, 0xf0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xf0, 0x00, 0x0f, 0xf0, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xc0, 0x00, 0x03, 0xf0, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0x80, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x03, 0xff, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xf3, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x0f, 0xff, 0xc3, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x07, 0xff, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x03, 0xfc, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x01, 0xf0, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x00, 0xc0, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x1f, 0x80, 0x00, 0x07, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xc0, 0x00, 0x1f, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x00, 0x7f, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x01, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x0f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x3f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0xc0, 0x3f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0xfc, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x38, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x10, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x7c, 0x03, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x1e, 0x00, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; + + // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) -const int epd_icons_allArray_LEN = 5; +const int epd_icons_allArray_LEN = 7; const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { epd_icons_pickaxe, epd_icons_rocket_launch, epd_icons_lightning, epd_icons_flash, - epd_icons_bitaxe_logo + epd_icons_bitaxe_logo, + epd_icons_ocean_logo, + epd_icons_braiins_logo }; diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 1dac009..f764b54 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -95,6 +95,11 @@ void setup() setupBitaxeFetchTask(); } + if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) + { + setupMiningPoolStatsFetchTask(); + } + setupButtonTask(); setupOTA(); @@ -331,6 +336,11 @@ void setupPreferences() addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate"); addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty"); } + + if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) + { + addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate"); + } } String replaceAmbiguousChars(String input) diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 51aef87..87dd4d6 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -18,6 +18,7 @@ #include "lib/ota.hpp" #include "lib/nostr_notify.hpp" #include "lib/bitaxe_fetch.hpp" +#include "lib/mining_pool_stats_fetch.hpp" #include "lib/v2_notify.hpp" diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 11df69a..8380f46 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -58,6 +58,13 @@ #define DEFAULT_BITAXE_ENABLED false #define DEFAULT_BITAXE_HOSTNAME "bitaxe1" +#define MINING_POOL_NAME_OCEAN "ocean" +#define MINING_POOL_NAME_BRAIINS "braiins" + +#define DEFAULT_MINING_POOL_STATS_ENABLED true +#define DEFAULT_MINING_POOL_NAME MINING_POOL_NAME_OCEAN +#define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher + #define DEFAULT_ZAP_NOTIFY_ENABLED false #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" #define DEFAULT_LED_FLASH_ON_ZAP true diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 2b30f26..5281087 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -613,6 +613,12 @@ void renderIcon(const uint dispNum, const String &text, bool partial) else if (text.endsWith("bitaxe")) { iconIndex = 4; } + else if (text.endsWith("ocean_logo")) { + iconIndex = 5; + } + else if (text.endsWith("braiins_logo")) { + iconIndex = 6; + } diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp new file mode 100644 index 0000000..6fd3914 --- /dev/null +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -0,0 +1,120 @@ +#include "mining_pool_stats_fetch.hpp" + +TaskHandle_t miningPoolStatsFetchTaskHandle; + +std::string miningPoolStatsHashrate; +std::string miningPoolStatsBestDiff; + +std::string getMiningPoolStatsHashRate() +{ + return miningPoolStatsHashrate; +} + + +void taskMiningPoolStatsFetch(void *pvParameters) +{ + for (;;) + { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + HTTPClient http; + http.setUserAgent(USER_AGENT); + String miningPoolStatsApiUrl; + + // TODO: Remove when webui gets proper update + Serial.println("FORCING miningPoolUser"); + preferences.putString("miningPoolName", MINING_POOL_NAME_OCEAN); + + Serial.println("miningPoolName: \"" + preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) + "\""); + Serial.println("miningPoolUser: \"" + preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER) + "\""); + + std::string httpHeaderKey = ""; + std::string httpHeaderValue; + if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_OCEAN) { + miningPoolStatsApiUrl = "https://api.ocean.xyz/v1/statsnap/" + preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); + Serial.println(miningPoolStatsApiUrl); + + } + else if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_BRAIINS) { + miningPoolStatsApiUrl = "https://pool.braiins.com/accounts/profile/json/btc/"; + httpHeaderKey = "Pool-Auth-Token"; + httpHeaderValue = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); + Serial.println(miningPoolStatsApiUrl); + } + else + { + Serial.println("Unknown mining pool: \"" + preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) + "\""); + continue; + } + + http.begin(miningPoolStatsApiUrl.c_str()); + + if (httpHeaderKey.length() > 0) { + http.addHeader(httpHeaderKey.c_str(), httpHeaderValue.c_str()); + } + + int httpCode = http.GET(); + + if (httpCode == 200) + { + String payload = http.getString(); + JsonDocument doc; + deserializeJson(doc, payload); + + Serial.println(doc.as()); + + if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_OCEAN) { + Serial.println(doc["result"].as()); + Serial.println(doc["result"]["hashrate_300s"].as()); + miningPoolStatsHashrate = doc["result"]["hashrate_300s"].as(); + } + else if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_BRAIINS) { + // Reports hashrate in specific hashrate units (e.g. Gh/s); we want raw total hashes per second. + std::string hashrateUnit = doc["btc"]["hash_rate_unit"].as(); + Serial.println(doc["btc"]["hash_rate_unit"].as()); + int multiplier = 0; + if (hashrateUnit == "Zh/s") { + multiplier = 21; + } else if (hashrateUnit == "Eh/s") { + multiplier = 18; + } else if (hashrateUnit == "Ph/s") { + multiplier = 15; + } else if (hashrateUnit == "Th/s") { + multiplier = 12; + } else if (hashrateUnit == "Gh/s") { + multiplier = 9; + } else if (hashrateUnit == "Mh/s") { + multiplier = 6; + } else if (hashrateUnit == "Kh/s") { + multiplier = 3; + } + + // Add zeroes to pad to a full h/s output + miningPoolStatsHashrate = std::to_string(static_cast(std::round(doc["btc"]["hash_rate_5m"].as()))) + std::string(multiplier, '0'); + Serial.println(miningPoolStatsHashrate.c_str()); + } + + // if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || getCurrentScreen() == SCREEN_MINING_POOL_STATS_BESTDIFF)) + if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE)) + { + WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; + xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); + } + } + else + { + Serial.print( + F("Error retrieving mining pool data. HTTP status code: ")); + Serial.println(httpCode); + Serial.println(miningPoolStatsApiUrl); + } + } +} + +void setupMiningPoolStatsFetchTask() +{ + xTaskCreate(taskMiningPoolStatsFetch, "miningPoolStatsFetch", (6 * 1024), NULL, tskIDLE_PRIORITY, + &miningPoolStatsFetchTaskHandle); + + xTaskNotifyGive(miningPoolStatsFetchTaskHandle); +} \ No newline at end of file diff --git a/src/lib/mining_pool_stats_fetch.hpp b/src/lib/mining_pool_stats_fetch.hpp new file mode 100644 index 0000000..f06207b --- /dev/null +++ b/src/lib/mining_pool_stats_fetch.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "lib/config.hpp" +#include "lib/shared.hpp" + +extern TaskHandle_t miningPoolStatsFetchTaskHandle; + +void setupMiningPoolStatsFetchTask(); +void taskMiningPoolStatsFetch(void *pvParameters); + +std::string getMiningPoolStatsHashRate(); +// std::string getMiningPoolStatsBestDiff(); \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 1f2b4a4..5d8c41c 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -33,9 +33,19 @@ void workerTask(void *pvParameters) { parseBitaxeBestDiff(getBitaxeBestDiff()); } setEpdContent(taskEpdContent); - + break; + } + case TASK_MINING_POOL_STATS_UPDATE: { + if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { + taskEpdContent = + parseMiningPoolStatsHashRate(preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(), getMiningPoolStatsHashRate()); + // } else if (getCurrentScreen() == SCREEN_BITAXE_BESTDIFF) { + // taskEpdContent = + // parseBitaxeBestDiff(getBitaxeBestDiff()); + } + setEpdContent(taskEpdContent); + break; } - break; case TASK_PRICE_UPDATE: { uint currency = getCurrentCurrency(); uint price = getPrice(currency); @@ -179,6 +189,16 @@ void setCurrentScreen(uint newScreen) { return; } break; + } + case SCREEN_MINING_POOL_STATS_HASHRATE: { + if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) { + WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; + xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY); + } else { + setCurrentScreen(SCREEN_BLOCK_HEIGHT); + return; + } + break; } } diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index 6c99a7c..ee658c9 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -6,6 +6,7 @@ #include #include +#include #include "lib/epd.hpp" #include "lib/shared.hpp" @@ -23,7 +24,8 @@ typedef enum { TASK_BLOCK_UPDATE, TASK_FEE_UPDATE, TASK_TIME_UPDATE, - TASK_BITAXE_UPDATE + TASK_BITAXE_UPDATE, + TASK_MINING_POOL_STATS_UPDATE } TaskType; typedef struct { diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 4753764..5fc2c2b 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -60,6 +60,8 @@ const PROGMEM int SCREEN_MARKET_CAP = 30; const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; +const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 85; + const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_CUSTOM = 99; const int SCREEN_COUNT = 7; diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp index d84d358..2fdb71c 100644 --- a/src/lib/timers.cpp +++ b/src/lib/timers.cpp @@ -72,6 +72,10 @@ void IRAM_ATTR minuteTimerISR(void *arg) { vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); } + if (miningPoolStatsFetchTaskHandle != NULL) { + vTaskNotifyGiveFromISR(miningPoolStatsFetchTaskHandle, &xHigherPriorityTaskWoken); + } + if (xHigherPriorityTaskWoken == pdTRUE) { portYIELD_FROM_ISR(); } diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 11b9024..0a55ff8 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -510,7 +510,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl"}; for (String setting : strSettings) { @@ -549,7 +549,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mowMode", "suffixShareDot", "flOffWhenDark", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", - "verticalDesc", + "miningPoolStatsEnabled", "verticalDesc", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; for (String setting : boolSettings) @@ -714,6 +714,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); + root["miningPoolStatsEnabled"] = preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED); + root["miningPoolName"] = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME); + root["miningPoolUser"] = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); + root["httpAuthEnabled"] = preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED); root["httpAuthUser"] = preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME); root["httpAuthPass"] = preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD); From 2bc5984f6ffb28f8ff1830ad44479bdea71838d2 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 15:58:12 -0600 Subject: [PATCH 115/188] Add mining pool daily/expected earnings --- lib/btclock/mining_pool_stats_handler.cpp | 84 ++++++++++++++++++++--- lib/btclock/mining_pool_stats_handler.hpp | 3 +- src/lib/config.cpp | 1 + src/lib/defaults.hpp | 2 +- src/lib/mining_pool_stats_fetch.cpp | 26 +++---- src/lib/mining_pool_stats_fetch.hpp | 2 +- src/lib/screen_handler.cpp | 9 +-- src/lib/shared.hpp | 1 + 8 files changed, 95 insertions(+), 33 deletions(-) diff --git a/lib/btclock/mining_pool_stats_handler.cpp b/lib/btclock/mining_pool_stats_handler.cpp index 17eb4c8..6802e42 100644 --- a/lib/btclock/mining_pool_stats_handler.cpp +++ b/lib/btclock/mining_pool_stats_handler.cpp @@ -41,16 +41,10 @@ std::array parseMiningPoolStatsHashRate(std::string mi // Account for the position of the mining pool logo and the hashrate label std::size_t startIndex = NUM_SCREENS - 1 - textLength; - std::cout << "miningPoolName: " << miningPoolName << std::endl; - - // Insert the mining pool logo icon just before the digits + // Insert the pickaxe icon just before the digits if (startIndex > 0) { - if (miningPoolName == "ocean") { - ret[startIndex - 1] = "mdi:braiins_logo"; - } else if (miningPoolName == "braiins") { - ret[startIndex - 1] = "mdi:braiins_logo"; - } + ret[startIndex - 1] = "mdi:pickaxe"; } // Place the digits @@ -60,7 +54,79 @@ std::array parseMiningPoolStatsHashRate(std::string mi } ret[NUM_SCREENS - 1] = label; - ret[0] = "POOL/STATS"; + + // Set the mining pool logo + if (miningPoolName == "ocean") { + ret[0] = "mdi:ocean_logo"; + } else if (miningPoolName == "braiins") { + ret[0] = "mdi:braiins_logo"; + } + + return ret; +} + + +std::array parseMiningPoolStatsDailyEarnings(std::string miningPoolName, int sats) +{ + std::array ret; + ret.fill(""); // Initialize all elements to empty strings + std::string satsDisplay = std::to_string(sats); + std::string label; + + if (miningPoolName == "braiins") { + // fpps guarantees payout; report current daily earnings + label = "sats/earned"; + } else { + // TIDES can only estimate earnings on the next block Ocean finds + label = "sats/block"; + } + + if (sats >= 100000000) { + // A whale mining 1+ BTC per day! No decimal points; whales scoff at such things. + label = "BTC" + label.substr(4); + satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 8); + } else if (sats >= 10000000) { + // 10.0M to 99.9M you get one decimal point + satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 6) + "." + satsDisplay[2] + "M"; + } else if (sats >= 1000000) { + // 1.00M to 9.99M you get two decimal points + satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 6) + "." + satsDisplay.substr(2, 2) + "M"; + } else if (sats >= 100000) { + // 100K to 999K you get no extra precision + satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 3) + "K"; + } else if (sats >= 10000) { + // 10.0K to 99.9K you get one decimal point + satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 3) + "." + satsDisplay[2] + "K"; + } else { + // Pleb miner! 4 digit or fewer sats will fit as-is. no-op. + } + + std::size_t textLength = satsDisplay.length(); + + // Calculate the position where the digits should start + // Account for the position of the mining pool logo + std::size_t startIndex = NUM_SCREENS - 1 - textLength; + + // Insert the pickaxe icon just before the digits if there's room + if (startIndex > 0) + { + ret[startIndex - 1] = "mdi:pickaxe"; + } + + // Place the digits + for (std::size_t i = 0; i < textLength; ++i) + { + ret[startIndex + i] = satsDisplay.substr(i, 1); + } + + ret[NUM_SCREENS - 1] = label; + + // Set the mining pool logo + if (miningPoolName == "ocean") { + ret[0] = "mdi:ocean_logo"; + } else if (miningPoolName == "braiins") { + ret[0] = "mdi:braiins_logo"; + } return ret; } \ No newline at end of file diff --git a/lib/btclock/mining_pool_stats_handler.hpp b/lib/btclock/mining_pool_stats_handler.hpp index b79d8f9..495c7db 100644 --- a/lib/btclock/mining_pool_stats_handler.hpp +++ b/lib/btclock/mining_pool_stats_handler.hpp @@ -1,4 +1,5 @@ #include #include -std::array parseMiningPoolStatsHashRate(std::string miningPoolName, std::string text); \ No newline at end of file +std::array parseMiningPoolStatsHashRate(std::string miningPoolName, std::string text); +std::array parseMiningPoolStatsDailyEarnings(std::string miningPoolName, int sats); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index f764b54..d8e71ef 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -340,6 +340,7 @@ void setupPreferences() if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) { addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate"); + addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings"); } } diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 8380f46..3d28829 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -61,7 +61,7 @@ #define MINING_POOL_NAME_OCEAN "ocean" #define MINING_POOL_NAME_BRAIINS "braiins" -#define DEFAULT_MINING_POOL_STATS_ENABLED true +#define DEFAULT_MINING_POOL_STATS_ENABLED false #define DEFAULT_MINING_POOL_NAME MINING_POOL_NAME_OCEAN #define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index 6fd3914..9de1cd7 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -3,13 +3,17 @@ TaskHandle_t miningPoolStatsFetchTaskHandle; std::string miningPoolStatsHashrate; -std::string miningPoolStatsBestDiff; +int miningPoolStatsDailyEarnings; std::string getMiningPoolStatsHashRate() { return miningPoolStatsHashrate; } +int getMiningPoolStatsDailyEarnings() +{ + return miningPoolStatsDailyEarnings; +} void taskMiningPoolStatsFetch(void *pvParameters) { @@ -21,25 +25,15 @@ void taskMiningPoolStatsFetch(void *pvParameters) http.setUserAgent(USER_AGENT); String miningPoolStatsApiUrl; - // TODO: Remove when webui gets proper update - Serial.println("FORCING miningPoolUser"); - preferences.putString("miningPoolName", MINING_POOL_NAME_OCEAN); - - Serial.println("miningPoolName: \"" + preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) + "\""); - Serial.println("miningPoolUser: \"" + preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER) + "\""); - std::string httpHeaderKey = ""; std::string httpHeaderValue; if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_OCEAN) { miningPoolStatsApiUrl = "https://api.ocean.xyz/v1/statsnap/" + preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); - Serial.println(miningPoolStatsApiUrl); - } else if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_BRAIINS) { miningPoolStatsApiUrl = "https://pool.braiins.com/accounts/profile/json/btc/"; httpHeaderKey = "Pool-Auth-Token"; httpHeaderValue = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); - Serial.println(miningPoolStatsApiUrl); } else { @@ -64,14 +58,12 @@ void taskMiningPoolStatsFetch(void *pvParameters) Serial.println(doc.as()); if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_OCEAN) { - Serial.println(doc["result"].as()); - Serial.println(doc["result"]["hashrate_300s"].as()); miningPoolStatsHashrate = doc["result"]["hashrate_300s"].as(); + miningPoolStatsDailyEarnings = int(doc["result"]["estimated_earn_next_block"].as() * 100000000); } else if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_BRAIINS) { // Reports hashrate in specific hashrate units (e.g. Gh/s); we want raw total hashes per second. std::string hashrateUnit = doc["btc"]["hash_rate_unit"].as(); - Serial.println(doc["btc"]["hash_rate_unit"].as()); int multiplier = 0; if (hashrateUnit == "Zh/s") { multiplier = 21; @@ -91,11 +83,11 @@ void taskMiningPoolStatsFetch(void *pvParameters) // Add zeroes to pad to a full h/s output miningPoolStatsHashrate = std::to_string(static_cast(std::round(doc["btc"]["hash_rate_5m"].as()))) + std::string(multiplier, '0'); - Serial.println(miningPoolStatsHashrate.c_str()); + + miningPoolStatsDailyEarnings = int(doc["btc"]["today_reward"].as() * 100000000); } - // if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || getCurrentScreen() == SCREEN_MINING_POOL_STATS_BESTDIFF)) - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE)) + if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) { WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); diff --git a/src/lib/mining_pool_stats_fetch.hpp b/src/lib/mining_pool_stats_fetch.hpp index f06207b..bae5267 100644 --- a/src/lib/mining_pool_stats_fetch.hpp +++ b/src/lib/mining_pool_stats_fetch.hpp @@ -12,4 +12,4 @@ void setupMiningPoolStatsFetchTask(); void taskMiningPoolStatsFetch(void *pvParameters); std::string getMiningPoolStatsHashRate(); -// std::string getMiningPoolStatsBestDiff(); \ No newline at end of file +int getMiningPoolStatsDailyEarnings(); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 5d8c41c..84f91d6 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -39,9 +39,9 @@ void workerTask(void *pvParameters) { if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { taskEpdContent = parseMiningPoolStatsHashRate(preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(), getMiningPoolStatsHashRate()); - // } else if (getCurrentScreen() == SCREEN_BITAXE_BESTDIFF) { - // taskEpdContent = - // parseBitaxeBestDiff(getBitaxeBestDiff()); + } else if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS) { + taskEpdContent = + parseMiningPoolStatsDailyEarnings(preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(), getMiningPoolStatsDailyEarnings()); } setEpdContent(taskEpdContent); break; @@ -190,7 +190,8 @@ void setCurrentScreen(uint newScreen) { } break; } - case SCREEN_MINING_POOL_STATS_HASHRATE: { + case SCREEN_MINING_POOL_STATS_HASHRATE: + case SCREEN_MINING_POOL_STATS_EARNINGS: { if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) { WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY); diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 5fc2c2b..22e40b2 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -61,6 +61,7 @@ const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 85; +const PROGMEM int SCREEN_MINING_POOL_STATS_EARNINGS = 86; const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_CUSTOM = 99; From e175b5f2f5389c57eef6a5fd22d7082459a5c70d Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 16:00:47 -0600 Subject: [PATCH 116/188] syncing to my forked webui submodule --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index d8b183a..e44bd8c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "data"] path = data - url = https://git.btclock.dev/btclock/webui.git + url = https://git.btclock.dev/kdmukai/webui.git From fabc6c1d28ffb8db5f5a64c97a23e006cdc88690 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 17:07:44 -0600 Subject: [PATCH 117/188] bugfix for long preferences key --- src/lib/config.cpp | 4 ++-- src/lib/screen_handler.cpp | 2 +- src/lib/webserver.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index d8e71ef..8c387ba 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -95,7 +95,7 @@ void setup() setupBitaxeFetchTask(); } - if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) + if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { setupMiningPoolStatsFetchTask(); } @@ -337,7 +337,7 @@ void setupPreferences() addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty"); } - if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) + if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate"); addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings"); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 84f91d6..6c4357c 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -192,7 +192,7 @@ void setCurrentScreen(uint newScreen) { } case SCREEN_MINING_POOL_STATS_HASHRATE: case SCREEN_MINING_POOL_STATS_EARNINGS: { - if (preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED)) { + if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY); } else { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 0a55ff8..4dc0c1e 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -549,7 +549,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mowMode", "suffixShareDot", "flOffWhenDark", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", - "miningPoolStatsEnabled", "verticalDesc", + "miningPoolStats", "verticalDesc", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; for (String setting : boolSettings) @@ -714,7 +714,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); - root["miningPoolStatsEnabled"] = preferences.getBool("miningPoolStatsEnabled", DEFAULT_MINING_POOL_STATS_ENABLED); + root["miningPoolStats"] = preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED); root["miningPoolName"] = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME); root["miningPoolUser"] = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); From c3af0b4d368fdd58bffa94eb5b4fb9a93405589a Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 17:08:32 -0600 Subject: [PATCH 118/188] Docs update; info on mining pool stats config --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81890b4..367a38d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Biggest differences with v2 are: New features: - BitAxe integration - Zap notifier -- +- Braiins Pool and Ocean mining stats integration "Steal focus on new block" means that when a new block is mined, the display will switch to the block height screen if it's not on it already. @@ -27,4 +27,27 @@ Most [information](https://github.com/btclock/btclock_v2/wiki) about BTClock v2 ## Building -Use PlatformIO to build it yourself. Make sure you fetch the [WebUI](https://github.com/btclock/webui) submodule. \ No newline at end of file +Use PlatformIO to build it yourself. Make sure you fetch the [WebUI](https://github.com/btclock/webui) submodule. + + +## Braiins Pool and Ocean integration +Enable mining pool stats by accessing your btclock's web UI (point a web browser at the device's IP address). + +Under Settings -> Extra Features: toggle Enable Mining Pool Stats. + +New options will appear. Select your mining pool and enter your pool username (Ocean) or api key (Braiins). + + +### Braiins Pool integration +Create an API key based on the steps [here](https://academy.braiins.com/en/braiins-pool/monitoring/#api-configuration). + +The key's permissions should be: +* Web Access: no +* API Access: yes +* Access Permissions: Read-only + +Copy the token that is created for the new key. Enter this as your "Mining Pool username or api key" in the btclock web UI. + + +### Ocean integration +Your "Mining Pool username" is just the onchain withdrawal address that you specify when pointing your miners at Ocean. From 72e5ee6580928bb4aa166f84af22336d4a756145 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 21:05:39 -0600 Subject: [PATCH 119/188] README update --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 367a38d..71d3319 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ Under Settings -> Extra Features: toggle Enable Mining Pool Stats. New options will appear. Select your mining pool and enter your pool username (Ocean) or api key (Braiins). +The Mining Pool Earnings screen displays: +* Braiins: Today's mining reward thus far +* Ocean: Your estimated earnings if the pool were to find a block right now + ### Braiins Pool integration Create an API key based on the steps [here](https://academy.braiins.com/en/braiins-pool/monitoring/#api-configuration). From be224d1f91d7e961fdef38e8c448003d8351fa0d Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 21:06:45 -0600 Subject: [PATCH 120/188] Restoring .gitmodule link to main webui repo --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index e44bd8c..d8b183a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "data"] path = data - url = https://git.btclock.dev/kdmukai/webui.git + url = https://git.btclock.dev/btclock/webui.git From e758659a4acd58e64e1a80149f22cf0e50b222e3 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 21:31:05 -0600 Subject: [PATCH 121/188] Remove platformio edit --- platformio.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 125ad96..d2abb92 100644 --- a/platformio.ini +++ b/platformio.ini @@ -96,7 +96,6 @@ build_flags = [env:btclock_rev_b_213epd] extends = env:btclock_rev_b -board_build.partitions = partition_8mb.csv test_framework = unity build_flags = ${env:btclock_rev_b.build_flags} From 8f9307d1e4de2b92fbc38f16e6599f495a50754b Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 18 Dec 2024 21:34:52 -0600 Subject: [PATCH 122/188] Remove whitespace --- partition_8mb.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partition_8mb.csv b/partition_8mb.csv index e0584ae..025f649 100644 --- a/partition_8mb.csv +++ b/partition_8mb.csv @@ -4,4 +4,4 @@ otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x370000, app1, app, ota_1, , 0x370000, spiffs, data, spiffs, , 0xCD000, -coredump, data, coredump,, 0x10000, +coredump, data, coredump,, 0x10000, \ No newline at end of file From 01ef6daf9f41b17ffcde80aca0f4729e73690976 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 19 Dec 2024 13:53:16 -0600 Subject: [PATCH 123/188] Update screen id consts --- src/lib/shared.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 22e40b2..8027519 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -60,8 +60,8 @@ const PROGMEM int SCREEN_MARKET_CAP = 30; const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; -const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 85; -const PROGMEM int SCREEN_MINING_POOL_STATS_EARNINGS = 86; +const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 70; +const PROGMEM int SCREEN_MINING_POOL_STATS_EARNINGS = 71; const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_CUSTOM = 99; From f9aa593f0b39b6889c041288c982b39932730cb6 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 19 Dec 2024 13:54:28 -0600 Subject: [PATCH 124/188] Removed pool name consts; reduce Preferences hits --- src/lib/defaults.hpp | 5 +---- src/lib/mining_pool_stats_fetch.cpp | 15 ++++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 3d28829..7ca89fb 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -58,11 +58,8 @@ #define DEFAULT_BITAXE_ENABLED false #define DEFAULT_BITAXE_HOSTNAME "bitaxe1" -#define MINING_POOL_NAME_OCEAN "ocean" -#define MINING_POOL_NAME_BRAIINS "braiins" - #define DEFAULT_MINING_POOL_STATS_ENABLED false -#define DEFAULT_MINING_POOL_NAME MINING_POOL_NAME_OCEAN +#define DEFAULT_MINING_POOL_NAME "ocean" #define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher #define DEFAULT_ZAP_NOTIFY_ENABLED false diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index 9de1cd7..3d440af 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -2,6 +2,7 @@ TaskHandle_t miningPoolStatsFetchTaskHandle; +std::string miningPoolName; std::string miningPoolStatsHashrate; int miningPoolStatsDailyEarnings; @@ -25,19 +26,21 @@ void taskMiningPoolStatsFetch(void *pvParameters) http.setUserAgent(USER_AGENT); String miningPoolStatsApiUrl; + miningPoolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + std::string httpHeaderKey = ""; std::string httpHeaderValue; - if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_OCEAN) { + if (miningPoolName == "ocean") { miningPoolStatsApiUrl = "https://api.ocean.xyz/v1/statsnap/" + preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); } - else if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_BRAIINS) { + else if (miningPoolName == "braiins") { miningPoolStatsApiUrl = "https://pool.braiins.com/accounts/profile/json/btc/"; httpHeaderKey = "Pool-Auth-Token"; httpHeaderValue = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); } else { - Serial.println("Unknown mining pool: \"" + preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) + "\""); + // Unknown mining pool / missing implementation continue; } @@ -55,13 +58,11 @@ void taskMiningPoolStatsFetch(void *pvParameters) JsonDocument doc; deserializeJson(doc, payload); - Serial.println(doc.as()); - - if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_OCEAN) { + if (miningPoolName == "ocean") { miningPoolStatsHashrate = doc["result"]["hashrate_300s"].as(); miningPoolStatsDailyEarnings = int(doc["result"]["estimated_earn_next_block"].as() * 100000000); } - else if (preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME) == MINING_POOL_NAME_BRAIINS) { + else if (miningPoolName == "braiins") { // Reports hashrate in specific hashrate units (e.g. Gh/s); we want raw total hashes per second. std::string hashrateUnit = doc["btc"]["hash_rate_unit"].as(); int multiplier = 0; From 814cd234a9b4824ca4f0cf9a57f74cc37b865054 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 20 Dec 2024 01:08:03 +0100 Subject: [PATCH 125/188] Big refactor of mining pool support, optimization of existing icons --- lib/btclock/mining_pool_stats_handler.cpp | 29 +- lib/btclock/mining_pool_stats_handler.hpp | 4 +- src/icons/icons.cpp | 1508 +++++------------ src/lib/config.cpp | 4 +- src/lib/epd.cpp | 29 +- src/lib/epd.hpp | 1 + src/lib/mining_pool/braiins/brains_pool.cpp | 32 + src/lib/mining_pool/braiins/brains_pool.hpp | 18 + src/lib/mining_pool/logo_data.hpp | 10 + src/lib/mining_pool/mining_pool_interface.hpp | 21 + .../noderunners/noderunners_pool.cpp | 39 + .../noderunners/noderunners_pool.hpp | 20 + src/lib/mining_pool/ocean/ocean_pool.cpp | 26 + src/lib/mining_pool/ocean/ocean_pool.hpp | 15 + src/lib/mining_pool/pool_factory.cpp | 20 + src/lib/mining_pool/pool_factory.hpp | 35 + src/lib/mining_pool/pool_stats.hpp | 10 + src/lib/mining_pool_stats_fetch.cpp | 86 +- src/lib/mining_pool_stats_fetch.hpp | 4 + src/lib/screen_handler.cpp | 4 +- src/lib/shared.hpp | 30 +- src/lib/webserver.cpp | 16 +- src/lib/webserver.hpp | 1 + 23 files changed, 772 insertions(+), 1190 deletions(-) create mode 100644 src/lib/mining_pool/braiins/brains_pool.cpp create mode 100644 src/lib/mining_pool/braiins/brains_pool.hpp create mode 100644 src/lib/mining_pool/logo_data.hpp create mode 100644 src/lib/mining_pool/mining_pool_interface.hpp create mode 100644 src/lib/mining_pool/noderunners/noderunners_pool.cpp create mode 100644 src/lib/mining_pool/noderunners/noderunners_pool.hpp create mode 100644 src/lib/mining_pool/ocean/ocean_pool.cpp create mode 100644 src/lib/mining_pool/ocean/ocean_pool.hpp create mode 100644 src/lib/mining_pool/pool_factory.cpp create mode 100644 src/lib/mining_pool/pool_factory.hpp create mode 100644 src/lib/mining_pool/pool_stats.hpp diff --git a/lib/btclock/mining_pool_stats_handler.cpp b/lib/btclock/mining_pool_stats_handler.cpp index 6802e42..445b912 100644 --- a/lib/btclock/mining_pool_stats_handler.cpp +++ b/lib/btclock/mining_pool_stats_handler.cpp @@ -1,7 +1,7 @@ #include "mining_pool_stats_handler.hpp" #include -std::array parseMiningPoolStatsHashRate(std::string miningPoolName, std::string text) +std::array parseMiningPoolStatsHashRate(std::string text) { std::array ret; ret.fill(""); // Initialize all elements to empty strings @@ -55,31 +55,19 @@ std::array parseMiningPoolStatsHashRate(std::string mi ret[NUM_SCREENS - 1] = label; - // Set the mining pool logo - if (miningPoolName == "ocean") { - ret[0] = "mdi:ocean_logo"; - } else if (miningPoolName == "braiins") { - ret[0] = "mdi:braiins_logo"; - } + + ret[0] = "mdi:miningpool"; + return ret; } -std::array parseMiningPoolStatsDailyEarnings(std::string miningPoolName, int sats) +std::array parseMiningPoolStatsDailyEarnings(int sats, std::string label) { std::array ret; ret.fill(""); // Initialize all elements to empty strings std::string satsDisplay = std::to_string(sats); - std::string label; - - if (miningPoolName == "braiins") { - // fpps guarantees payout; report current daily earnings - label = "sats/earned"; - } else { - // TIDES can only estimate earnings on the next block Ocean finds - label = "sats/block"; - } if (sats >= 100000000) { // A whale mining 1+ BTC per day! No decimal points; whales scoff at such things. @@ -121,12 +109,7 @@ std::array parseMiningPoolStatsDailyEarnings(std::stri ret[NUM_SCREENS - 1] = label; - // Set the mining pool logo - if (miningPoolName == "ocean") { - ret[0] = "mdi:ocean_logo"; - } else if (miningPoolName == "braiins") { - ret[0] = "mdi:braiins_logo"; - } + ret[0] = "mdi:miningpool"; return ret; } \ No newline at end of file diff --git a/lib/btclock/mining_pool_stats_handler.hpp b/lib/btclock/mining_pool_stats_handler.hpp index 495c7db..d9aaadd 100644 --- a/lib/btclock/mining_pool_stats_handler.hpp +++ b/lib/btclock/mining_pool_stats_handler.hpp @@ -1,5 +1,5 @@ #include #include -std::array parseMiningPoolStatsHashRate(std::string miningPoolName, std::string text); -std::array parseMiningPoolStatsDailyEarnings(std::string miningPoolName, int sats); +std::array parseMiningPoolStatsHashRate(std::string text); +std::array parseMiningPoolStatsDailyEarnings(int sats, std::string label); diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index d80fde1..5fd6c1a 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -1,578 +1,7 @@ #include "icons.h" -// 'pickaxe', 122x250px -const unsigned char epd_icons_pickaxe [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x71, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x20, 0xe1, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x40, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xf8, 0xc0, 0x00, 0x7f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0x00, 0x07, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 -}; -// 'rocket-launch', 122x250px -const unsigned char epd_icons_rocket_launch [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf9, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe1, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x83, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x03, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe7, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc3, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x83, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x01, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x0f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x3f, 0x9f, 0xe0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0xfe, 0x07, 0xf8, 0x00, 0x00, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x01, 0xfc, 0x03, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x03, 0xf8, 0x01, 0xfe, 0x00, 0x00, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x07, 0xf0, 0x00, 0xff, 0x00, 0x03, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x0f, 0xe0, 0x00, 0x7f, 0x80, 0x0f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x1f, 0xc0, 0x00, 0x7f, 0xe0, 0x7f, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x7f, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0xfe, 0x00, 0x03, 0xfb, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc1, 0xfc, 0x00, 0x07, 0xf0, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc3, 0xf8, 0x00, 0x0f, 0xe0, 0x3f, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc7, 0xf0, 0x00, 0x1f, 0xc0, 0x7f, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xcf, 0xe0, 0x00, 0x3f, 0x80, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xdf, 0xc0, 0x00, 0x7f, 0x01, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0xfe, 0x03, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x01, 0xfc, 0x07, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x03, 0xf8, 0x0f, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x07, 0xf0, 0x1f, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x0f, 0xe0, 0x3f, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x1f, 0xc0, 0x7f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x3f, 0x80, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x00, 0x7f, 0x01, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x00, 0xfe, 0x03, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x01, 0xfc, 0x07, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x03, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x07, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x0f, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xc0, 0x1f, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 -}; - -const unsigned char epd_icons_lightning [] PROGMEM = { - // 'lightning-bolt, 122x250px - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +// 'lightning-bolt', 122x122px +const unsigned char epd_icons_lightning_bolt [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, @@ -694,6 +123,10 @@ const unsigned char epd_icons_lightning [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; +// 'rocket-launch', 122x122px +const unsigned char epd_icons_rocket_launch [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, @@ -702,32 +135,89 @@ const unsigned char epd_icons_lightning [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf1, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc1, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x03, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x03, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x80, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x80, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x7e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x81, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x07, 0xe7, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x0f, 0xc3, 0xf0, 0x00, 0x00, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x1f, 0x81, 0xf8, 0x00, 0x03, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x3f, 0x00, 0xfc, 0x00, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x7e, 0x00, 0x7e, 0x00, 0x3f, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0xfc, 0x00, 0x3f, 0x80, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x81, 0xf8, 0x00, 0x7f, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x03, 0xf0, 0x00, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x07, 0xe0, 0x01, 0xf3, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x0f, 0xc0, 0x03, 0xe0, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x1f, 0x80, 0x07, 0xc0, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x3f, 0x00, 0x0f, 0x81, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x7e, 0x00, 0x1f, 0x03, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x3e, 0x07, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x7c, 0x0f, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x00, 0xf8, 0x1f, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x01, 0xf0, 0x3f, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x03, 0xe0, 0x7f, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x80, 0x07, 0xc0, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x00, 0x0f, 0x81, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x00, 0x1f, 0x03, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x00, 0x3e, 0x07, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x00, 0x7c, 0x0f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x00, 0xf8, 0x1f, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x01, 0xf0, 0x3f, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, @@ -760,227 +250,98 @@ const unsigned char epd_icons_lightning [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; - -// 'flash', 122x250px -const unsigned char epd_icons_flash [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +// 'pickaxe', 122x122px +const unsigned char epd_icons_pickaxe [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x31, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xfd, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, @@ -1270,262 +631,135 @@ const unsigned char epd_icons_bitaxe_logo [] PROGMEM = { }; -// 'ocean_logo', 122x250px +// // 'ocean_logo', 122x250px const unsigned char epd_icons_ocean_logo [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x78, 0x00, 0x1f, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xfc, 0x00, 0x3f, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x00, 0x7f, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xc0, - 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xc0, - 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xc0, - 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xc0, - 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xc0, - 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xc0, - 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xc0, - 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x3f, 0xc0, - 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x1f, 0xc0, - 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x03, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x1f, 0xc0, - 0xfe, 0x00, 0x78, 0x00, 0x7f, 0xff, 0xf1, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x1f, 0xc0, - 0xfe, 0x00, 0xfc, 0x00, 0x3f, 0xff, 0xf1, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xc0, 0x1f, 0xc0, - 0xff, 0x00, 0xff, 0x00, 0x1f, 0xff, 0xf1, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xc0, - 0xff, 0x01, 0xff, 0x80, 0x1f, 0xff, 0xf3, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xc0, - 0xff, 0x01, 0xff, 0xc0, 0x0f, 0xff, 0xe3, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xc0, - 0xff, 0x01, 0xff, 0xe0, 0x07, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xc0, - 0xff, 0x81, 0xff, 0xf8, 0x03, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xc0, - 0xff, 0x81, 0xff, 0xfc, 0x01, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xc0, - 0xff, 0x83, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xc0, - 0xff, 0xc3, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xc0, - 0xff, 0xc3, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xc0, - 0xff, 0xe3, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xc0, - 0xff, 0xe3, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xc0, - 0xff, 0xf3, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xf9, 0xff, 0xc0, - 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xf9, 0xff, 0xc0, - 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xf0, 0xff, 0xc0, - 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0xf0, 0xff, 0xc0, - 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xf0, 0x7f, 0xc0, - 0xff, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xf0, 0x7f, 0xc0, - 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xf0, 0x07, 0xff, 0xf0, 0x7f, 0xc0, - 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf8, 0x03, 0xff, 0xf0, 0x3f, 0xc0, - 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf8, 0x01, 0xff, 0xe0, 0x3f, 0xc0, - 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xf1, 0xff, 0xfc, 0x00, 0xff, 0xe0, 0x3f, 0xc0, - 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xf1, 0xff, 0xfe, 0x00, 0x3f, 0xe0, 0x1f, 0xc0, - 0xff, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xf1, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x1f, 0xc0, - 0xfe, 0x00, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xf1, 0xff, 0xff, 0x80, 0x0f, 0xc0, 0x1f, 0xc0, - 0xfe, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x07, 0xff, 0xe1, 0xff, 0xff, 0x80, 0x07, 0x80, 0x1f, 0xc0, - 0xfe, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xe3, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xc0, - 0xfe, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xc0, - 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x1f, 0xc0, - 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xc0, - 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xc0, - 0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xc0, - 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xc0, - 0xff, 0x80, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xc0, - 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xc0, - 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xc0, - 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xc0, 0x0f, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0x80, 0x0f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0x80, 0x0f, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x07, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +// 'ocean_logo', 122x122px +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x20, 0x00, 0x0f, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xfc, 0x00, 0x3f, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xfc, 0x00, 0x7f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xfe, 0x00, 0x7f, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xc0, +0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xc0, +0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, +0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, +0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, +0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, +0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, +0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, +0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xc0, +0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xc0, +0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xc0, +0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xc0, +0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xc0, +0xfc, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xc0, +0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xc0, +0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xc0, +0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x03, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xe0, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x03, 0xff, 0xff, 0xe1, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xf0, 0x07, 0xc0, +0xf8, 0x03, 0xe0, 0x01, 0xff, 0xff, 0xe3, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xff, 0xf0, 0x07, 0xc0, +0xf8, 0x07, 0xf8, 0x00, 0xff, 0xff, 0xe3, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x07, 0xc0, +0xf8, 0x07, 0xfc, 0x00, 0x7f, 0xff, 0xe3, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xc0, +0xf8, 0x07, 0xfe, 0x00, 0x3f, 0xff, 0xe7, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xc0, +0xf8, 0x07, 0xff, 0x80, 0x1f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xc0, +0xfc, 0x0f, 0xff, 0xc0, 0x0f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xc0, +0xfc, 0x0f, 0xff, 0xe0, 0x0f, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xc0, +0xfc, 0x0f, 0xff, 0xf8, 0x07, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xc0, +0xfe, 0x0f, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xc0, +0xfe, 0x1f, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xc0, +0xff, 0x1f, 0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xc0, +0xff, 0x1f, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xc0, +0xff, 0x9f, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xc0, +0xff, 0xdf, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xdf, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xfe, 0xff, 0xc0, +0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xfe, 0x7f, 0xc0, +0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xff, 0xff, 0xfe, 0x7f, 0xc0, +0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xfe, 0x3f, 0xc0, +0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xfc, 0x1f, 0xc0, +0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xfc, 0x1f, 0xc0, +0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xfc, 0x1f, 0xc0, +0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfc, 0x01, 0xff, 0xfc, 0x0f, 0xc0, +0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xfc, 0x00, 0xff, 0xfc, 0x0f, 0xc0, +0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xfe, 0x00, 0x7f, 0xf8, 0x07, 0xc0, +0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xf9, 0xff, 0xff, 0x00, 0x1f, 0xf8, 0x07, 0xc0, +0xf8, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xf1, 0xff, 0xff, 0x80, 0x0f, 0xf8, 0x07, 0xc0, +0xf8, 0x07, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xf1, 0xff, 0xff, 0xc0, 0x07, 0xf0, 0x07, 0xc0, +0xf8, 0x03, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0xf1, 0xff, 0xff, 0xe0, 0x01, 0xf0, 0x07, 0xc0, +0xf8, 0x03, 0xff, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x01, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x30, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x00, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x80, 0x00, 0x07, 0xc0, +0xf8, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xc0, +0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x0f, 0xc0, +0xfc, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xc0, +0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xc0, +0xfe, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xc0, +0xfe, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xc0, +0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xc0, +0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xc0, +0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xc0, +0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, +0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, +0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, +0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, +0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, +0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, +0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xc0, +0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0x80, 0x1f, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0x80, 0x0f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0x00, 0x0f, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3c, 0x00, 0x03, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, +0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; -// 'braiins_pool', 122x250px +// // 'braiins_pool', 122x250px const unsigned char epd_icons_braiins_logo [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, @@ -1779,15 +1013,139 @@ const unsigned char epd_icons_braiins_logo [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; +const unsigned char epd_icons_noderunners_logo [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x01, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x01, 0xff, 0x00, 0x01, 0xfb, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x01, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0x80, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xdf, 0xf0, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xdf, 0xf0, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xcf, 0xf8, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xcf, 0xfc, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xc7, 0xfe, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe7, 0xfe, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe3, 0xff, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe1, 0xff, 0x81, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe1, 0xff, 0x81, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0xff, 0xc1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x7f, 0xe1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x3f, 0xe1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x3f, 0xf1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x1f, 0xf9, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x0f, 0xf9, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x0f, 0xfd, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x07, 0xfd, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x03, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x01, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe1, 0xc0, 0x00, 0x3f, 0xe0, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe7, 0xf0, 0x00, 0x3f, 0xe0, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) const int epd_icons_allArray_LEN = 7; const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { epd_icons_pickaxe, epd_icons_rocket_launch, - epd_icons_lightning, - epd_icons_flash, + epd_icons_lightning_bolt, epd_icons_bitaxe_logo, epd_icons_ocean_logo, - epd_icons_braiins_logo + epd_icons_braiins_logo, + epd_icons_noderunners_logo }; diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 8c387ba..d7f403f 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -340,7 +340,9 @@ void setupPreferences() if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate"); - addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings"); + if (getMiningPool()->supportsDailyEarnings()) { + addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings"); + } } } diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 5281087..062fc74 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -604,26 +604,36 @@ void renderIcon(const uint dispNum, const String &text, bool partial) displays[dispNum].setTextColor(getFgColor()); uint iconIndex = 0; + uint width = 122; + uint height = 122; if (text.endsWith("rocket")) { iconIndex = 1; } else if (text.endsWith("lnbolt")) { - iconIndex = 3; + iconIndex = 2; } else if (text.endsWith("bitaxe")) { - iconIndex = 4; + width = 122; + height = 250; + iconIndex = 3; } - else if (text.endsWith("ocean_logo")) { - iconIndex = 5; - } - else if (text.endsWith("braiins_logo")) { - iconIndex = 6; + else if (text.endsWith("miningpool")) { + LogoData logo = getMiningPoolLogo(); + + int x_offset = (displays[dispNum].width() - logo.width) / 2; + int y_offset = (displays[dispNum].height() - logo.height) / 2; + // Close the file + + displays[dispNum].drawInvertedBitmap(x_offset,y_offset, logo.data, logo.width, logo.height, getFgColor()); + return; } - + + int x_offset = (displays[dispNum].width() - width) / 2; + int y_offset = (displays[dispNum].height() - height) / 2; - displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor()); + displays[dispNum].drawInvertedBitmap(x_offset,y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); // displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); @@ -633,6 +643,7 @@ void renderIcon(const uint dispNum, const String &text, bool partial) + void renderQr(const uint dispNum, const String &text, bool partial) { #ifdef USE_QR diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 1194cf1..79be979 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -14,6 +14,7 @@ #include "lib/config.hpp" #include "lib/shared.hpp" #include "icons/icons.h" +#include "mining_pool_stats_fetch.hpp" #ifdef USE_QR #include "qrcodegen.h" diff --git a/src/lib/mining_pool/braiins/brains_pool.cpp b/src/lib/mining_pool/braiins/brains_pool.cpp new file mode 100644 index 0000000..5418829 --- /dev/null +++ b/src/lib/mining_pool/braiins/brains_pool.cpp @@ -0,0 +1,32 @@ +#include "brains_pool.hpp" + +void BraiinsPool::prepareRequest(HTTPClient& http) const { + http.addHeader("Pool-Auth-Token", poolUser.c_str()); +} + +std::string BraiinsPool::getApiUrl() const { + return "https://pool.braiins.com/accounts/profile/json/btc/"; +} + +PoolStats BraiinsPool::parseResponse(const JsonDocument &doc) const +{ + std::string unit = doc["btc"]["hash_rate_unit"].as(); + + static const std::unordered_map multipliers = { + {"Zh/s", 21}, {"Eh/s", 18}, {"Ph/s", 15}, {"Th/s", 12}, {"Gh/s", 9}, {"Mh/s", 6}, {"Kh/s", 3}}; + + int multiplier = multipliers.at(unit); + float hashValue = doc["btc"]["hash_rate_5m"].as(); + + return PoolStats{ + .hashrate = std::to_string(static_cast(std::round(hashValue))) + std::string(multiplier, '0'), + .dailyEarnings = static_cast(doc["btc"]["today_reward"].as() * 100000000)}; +} + +LogoData BraiinsPool::getLogo() const { + return LogoData{ + .data = epd_icons_allArray[5], + .width = 122, + .height = 250 + }; +} \ No newline at end of file diff --git a/src/lib/mining_pool/braiins/brains_pool.hpp b/src/lib/mining_pool/braiins/brains_pool.hpp new file mode 100644 index 0000000..ea2c0e0 --- /dev/null +++ b/src/lib/mining_pool/braiins/brains_pool.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "lib/mining_pool/mining_pool_interface.hpp" +#include + +class BraiinsPool : public MiningPoolInterface +{ +public: + void setPoolUser(const std::string &user) override { poolUser = user; } + void prepareRequest(HTTPClient &http) const override; + std::string getApiUrl() const override; + PoolStats parseResponse(const JsonDocument &doc) const override; + LogoData getLogo() const override; + bool supportsDailyEarnings() const override { return true; } + std::string getDailyEarningsLabel() const override { return "sats/earned"; } +private: + static int getHashrateMultiplier(const std::string &unit); +}; \ No newline at end of file diff --git a/src/lib/mining_pool/logo_data.hpp b/src/lib/mining_pool/logo_data.hpp new file mode 100644 index 0000000..7cd0819 --- /dev/null +++ b/src/lib/mining_pool/logo_data.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +struct LogoData { + const uint8_t* data; + size_t width; + size_t height; +}; diff --git a/src/lib/mining_pool/mining_pool_interface.hpp b/src/lib/mining_pool/mining_pool_interface.hpp new file mode 100644 index 0000000..1df84b9 --- /dev/null +++ b/src/lib/mining_pool/mining_pool_interface.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include "pool_stats.hpp" +#include "logo_data.hpp" + +class MiningPoolInterface { +public: + virtual ~MiningPoolInterface() = default; + virtual void setPoolUser(const std::string& user) = 0; + virtual void prepareRequest(HTTPClient& http) const = 0; + virtual std::string getApiUrl() const = 0; + virtual PoolStats parseResponse(const JsonDocument& doc) const = 0; + virtual LogoData getLogo() const = 0; + virtual bool supportsDailyEarnings() const = 0; + virtual std::string getDailyEarningsLabel() const = 0; + +protected: + std::string poolUser; +}; \ No newline at end of file diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.cpp b/src/lib/mining_pool/noderunners/noderunners_pool.cpp new file mode 100644 index 0000000..22ee112 --- /dev/null +++ b/src/lib/mining_pool/noderunners/noderunners_pool.cpp @@ -0,0 +1,39 @@ +// src/noderunners/noderunners_pool.cpp +#include "noderunners_pool.hpp" + +void NodeRunnersPool::prepareRequest(HTTPClient& http) const { + // Empty as NodeRunners doesn't need special headers +} + +std::string NodeRunnersPool::getApiUrl() const { + return "https://pool.noderunners.network/api/v1/users/" + poolUser; +} + +PoolStats NodeRunnersPool::parseResponse(const JsonDocument& doc) const { + std::string hashrateStr = doc["hashrate1m"].as(); + char unit = hashrateStr.back(); + std::string value = hashrateStr.substr(0, hashrateStr.size() - 1); + + int multiplier = getHashrateMultiplier(unit); + + return PoolStats{ + .hashrate = value + std::string(multiplier, '0'), + .dailyEarnings = std::nullopt + }; +} + +LogoData NodeRunnersPool::getLogo() const { + return LogoData { + .data = epd_icons_allArray[6], + .width = 122, + .height = 122 + }; +} + +int NodeRunnersPool::getHashrateMultiplier(char unit) { + static const std::unordered_map multipliers = { + {'Z', 21}, {'E', 18}, {'P', 15}, {'T', 12}, + {'G', 9}, {'M', 6}, {'K', 3} + }; + return multipliers.at(unit); +} diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.hpp b/src/lib/mining_pool/noderunners/noderunners_pool.hpp new file mode 100644 index 0000000..ea1b0a2 --- /dev/null +++ b/src/lib/mining_pool/noderunners/noderunners_pool.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#include "lib/mining_pool/mining_pool_interface.hpp" +#include + +class NodeRunnersPool : public MiningPoolInterface { +public: + void setPoolUser(const std::string& user) override { poolUser = user; } + + void prepareRequest(HTTPClient& http) const override; + std::string getApiUrl() const override; + PoolStats parseResponse(const JsonDocument& doc) const override; + LogoData getLogo() const override; + bool supportsDailyEarnings() const override { return false; } + std::string getDailyEarningsLabel() const override { return ""; } + +private: + static int getHashrateMultiplier(char unit); +}; \ No newline at end of file diff --git a/src/lib/mining_pool/ocean/ocean_pool.cpp b/src/lib/mining_pool/ocean/ocean_pool.cpp new file mode 100644 index 0000000..f66ad5c --- /dev/null +++ b/src/lib/mining_pool/ocean/ocean_pool.cpp @@ -0,0 +1,26 @@ +#include "ocean_pool.hpp" + +void OceanPool::prepareRequest(HTTPClient& http) const { + // Empty as Ocean doesn't need special headers +} + +std::string OceanPool::getApiUrl() const { + return "https://api.ocean.xyz/v1/statsnap/" + poolUser; +} + +PoolStats OceanPool::parseResponse(const JsonDocument& doc) const { + return PoolStats{ + .hashrate = doc["result"]["hashrate_300s"].as(), + .dailyEarnings = static_cast( + doc["result"]["estimated_earn_next_block"].as() * 100000000 + ) + }; +} + +LogoData OceanPool::getLogo() const { + return LogoData{ + .data = epd_icons_allArray[4], + .width = 122, + .height = 122 + }; +} \ No newline at end of file diff --git a/src/lib/mining_pool/ocean/ocean_pool.hpp b/src/lib/mining_pool/ocean/ocean_pool.hpp new file mode 100644 index 0000000..3756579 --- /dev/null +++ b/src/lib/mining_pool/ocean/ocean_pool.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "lib/mining_pool/mining_pool_interface.hpp" +#include + +class OceanPool : public MiningPoolInterface { +public: + void setPoolUser(const std::string& user) override { poolUser = user; } + void prepareRequest(HTTPClient& http) const override; + std::string getApiUrl() const override; + PoolStats parseResponse(const JsonDocument& doc) const override; + LogoData getLogo() const override; + bool supportsDailyEarnings() const override { return true; } + std::string getDailyEarningsLabel() const override { return "sats/block"; } +}; \ No newline at end of file diff --git a/src/lib/mining_pool/pool_factory.cpp b/src/lib/mining_pool/pool_factory.cpp new file mode 100644 index 0000000..0a786a9 --- /dev/null +++ b/src/lib/mining_pool/pool_factory.cpp @@ -0,0 +1,20 @@ +#include "pool_factory.hpp" + + +const char* PoolFactory::MINING_POOL_NAME_OCEAN = "ocean"; +const char* PoolFactory::MINING_POOL_NAME_NODERUNNERS = "noderunners"; +const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins"; + +std::unique_ptr PoolFactory::createPool(const std::string& poolName) { + static const std::unordered_map()>> poolFactories = { + {MINING_POOL_NAME_OCEAN, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_NODERUNNERS, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique(); }} + }; + + auto it = poolFactories.find(poolName); + if (it == poolFactories.end()) { + return nullptr; + } + return it->second(); +} \ No newline at end of file diff --git a/src/lib/mining_pool/pool_factory.hpp b/src/lib/mining_pool/pool_factory.hpp new file mode 100644 index 0000000..92cba72 --- /dev/null +++ b/src/lib/mining_pool/pool_factory.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "mining_pool_interface.hpp" +#include +#include +#include "noderunners/noderunners_pool.hpp" +#include "braiins/brains_pool.hpp" +#include "ocean/ocean_pool.hpp" + +class PoolFactory { + public: + static std::unique_ptr createPool(const std::string& poolName); + static std::vector getAvailablePools() { + return { + MINING_POOL_NAME_OCEAN, + MINING_POOL_NAME_NODERUNNERS, + MINING_POOL_NAME_BRAIINS + }; + } + + static std::string getAvailablePoolsAsString() { + const auto pools = getAvailablePools(); + std::string result; + for (size_t i = 0; i < pools.size(); ++i) { + result += pools[i]; + if (i < pools.size() - 1) { + result += ", "; + } + } + return result; + } + private: + static const char* MINING_POOL_NAME_OCEAN; + static const char* MINING_POOL_NAME_NODERUNNERS; + static const char* MINING_POOL_NAME_BRAIINS; +}; \ No newline at end of file diff --git a/src/lib/mining_pool/pool_stats.hpp b/src/lib/mining_pool/pool_stats.hpp new file mode 100644 index 0000000..842310a --- /dev/null +++ b/src/lib/mining_pool/pool_stats.hpp @@ -0,0 +1,10 @@ + +#pragma once + +#include +#include + +struct PoolStats { + std::string hashrate; + std::optional dailyEarnings; +}; diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index 3d440af..b263edb 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -24,68 +24,38 @@ void taskMiningPoolStatsFetch(void *pvParameters) HTTPClient http; http.setUserAgent(USER_AGENT); - String miningPoolStatsApiUrl; - miningPoolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); - std::string httpHeaderKey = ""; - std::string httpHeaderValue; - if (miningPoolName == "ocean") { - miningPoolStatsApiUrl = "https://api.ocean.xyz/v1/statsnap/" + preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); - } - else if (miningPoolName == "braiins") { - miningPoolStatsApiUrl = "https://pool.braiins.com/accounts/profile/json/btc/"; - httpHeaderKey = "Pool-Auth-Token"; - httpHeaderValue = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); - } - else + auto poolInterface = PoolFactory::createPool(poolName); + if (!poolInterface) { - // Unknown mining pool / missing implementation + Serial.println("Unknown mining pool: \"" + String(poolName.c_str()) + "\""); continue; } - http.begin(miningPoolStatsApiUrl.c_str()); - - if (httpHeaderKey.length() > 0) { - http.addHeader(httpHeaderKey.c_str(), httpHeaderValue.c_str()); - } - + poolInterface->setPoolUser(poolUser); + std::string apiUrl = poolInterface->getApiUrl(); + http.begin(apiUrl.c_str()); + poolInterface->prepareRequest(http); int httpCode = http.GET(); - if (httpCode == 200) { String payload = http.getString(); JsonDocument doc; deserializeJson(doc, payload); - if (miningPoolName == "ocean") { - miningPoolStatsHashrate = doc["result"]["hashrate_300s"].as(); - miningPoolStatsDailyEarnings = int(doc["result"]["estimated_earn_next_block"].as() * 100000000); + PoolStats stats = poolInterface->parseResponse(doc); + miningPoolStatsHashrate = stats.hashrate; + + if (stats.dailyEarnings) + { + miningPoolStatsDailyEarnings = *stats.dailyEarnings; } - else if (miningPoolName == "braiins") { - // Reports hashrate in specific hashrate units (e.g. Gh/s); we want raw total hashes per second. - std::string hashrateUnit = doc["btc"]["hash_rate_unit"].as(); - int multiplier = 0; - if (hashrateUnit == "Zh/s") { - multiplier = 21; - } else if (hashrateUnit == "Eh/s") { - multiplier = 18; - } else if (hashrateUnit == "Ph/s") { - multiplier = 15; - } else if (hashrateUnit == "Th/s") { - multiplier = 12; - } else if (hashrateUnit == "Gh/s") { - multiplier = 9; - } else if (hashrateUnit == "Mh/s") { - multiplier = 6; - } else if (hashrateUnit == "Kh/s") { - multiplier = 3; - } - - // Add zeroes to pad to a full h/s output - miningPoolStatsHashrate = std::to_string(static_cast(std::round(doc["btc"]["hash_rate_5m"].as()))) + std::string(multiplier, '0'); - - miningPoolStatsDailyEarnings = int(doc["btc"]["today_reward"].as() * 100000000); + else + { + miningPoolStatsDailyEarnings = 0; // or any other default value } if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) @@ -99,7 +69,6 @@ void taskMiningPoolStatsFetch(void *pvParameters) Serial.print( F("Error retrieving mining pool data. HTTP status code: ")); Serial.println(httpCode); - Serial.println(miningPoolStatsApiUrl); } } } @@ -110,4 +79,21 @@ void setupMiningPoolStatsFetchTask() &miningPoolStatsFetchTaskHandle); xTaskNotifyGive(miningPoolStatsFetchTaskHandle); -} \ No newline at end of file +} + +std::unique_ptr& getMiningPool() +{ + static std::unique_ptr currentMiningPool; + + if (!currentMiningPool) { + std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + currentMiningPool = PoolFactory::createPool(poolName); + } + + return currentMiningPool; +} + +LogoData getMiningPoolLogo() +{ + return getMiningPool()->getLogo(); +} diff --git a/src/lib/mining_pool_stats_fetch.hpp b/src/lib/mining_pool_stats_fetch.hpp index bae5267..7a84454 100644 --- a/src/lib/mining_pool_stats_fetch.hpp +++ b/src/lib/mining_pool_stats_fetch.hpp @@ -2,6 +2,7 @@ #include #include +#include "mining_pool/pool_factory.hpp" #include "lib/config.hpp" #include "lib/shared.hpp" @@ -13,3 +14,6 @@ void taskMiningPoolStatsFetch(void *pvParameters); std::string getMiningPoolStatsHashRate(); int getMiningPoolStatsDailyEarnings(); + +std::unique_ptr& getMiningPool(); +LogoData getMiningPoolLogo(); \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 6c4357c..d4043fa 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -38,10 +38,10 @@ void workerTask(void *pvParameters) { case TASK_MINING_POOL_STATS_UPDATE: { if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { taskEpdContent = - parseMiningPoolStatsHashRate(preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(), getMiningPoolStatsHashRate()); + parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate()); } else if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS) { taskEpdContent = - parseMiningPoolStatsDailyEarnings(preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(), getMiningPoolStatsDailyEarnings()); + parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), getMiningPool()->getDailyEarningsLabel()); } setEpdContent(taskEpdContent); break; diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 8027519..9fd7455 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -42,26 +42,15 @@ const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6; const PROGMEM int SCREEN_SATS_PER_CURRENCY = 10; const PROGMEM int SCREEN_BTC_TICKER = 20; -// const PROGMEM int SCREEN_BTC_TICKER_USD = 20; -// const PROGMEM int SCREEN_BTC_TICKER_EUR = 21; -// const PROGMEM int SCREEN_BTC_TICKER_GBP = 22; -// const PROGMEM int SCREEN_BTC_TICKER_JPY = 23; -// const PROGMEM int SCREEN_BTC_TICKER_AUD = 24; -// const PROGMEM int SCREEN_BTC_TICKER_CAD = 25; const PROGMEM int SCREEN_MARKET_CAP = 30; -// const PROGMEM int SCREEN_MARKET_CAP_USD = 30; -// const PROGMEM int SCREEN_MARKET_CAP_EUR = 31; -// const PROGMEM int SCREEN_MARKET_CAP_GBP = 32; -// const PROGMEM int SCREEN_MARKET_CAP_JPY = 33; -// const PROGMEM int SCREEN_MARKET_CAP_AUD = 34; -// const PROGMEM int SCREEN_MARKET_CAP_CAD = 35; + +const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 70; +const PROGMEM int SCREEN_MINING_POOL_STATS_EARNINGS = 71; const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; -const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 70; -const PROGMEM int SCREEN_MINING_POOL_STATS_EARNINGS = 71; const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_CUSTOM = 99; @@ -94,4 +83,15 @@ struct ScreenMapping { }; String calculateSHA256(uint8_t* data, size_t len); -String calculateSHA256(WiFiClient *stream, size_t contentLength); \ No newline at end of file +String calculateSHA256(WiFiClient *stream, size_t contentLength); + +namespace ArduinoJson { + template + struct Converter> { + static void toJson(const std::vector& src, JsonVariant dst) { + JsonArray array = dst.to(); + for (T item : src) + array.add(item); + } + }; +} \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 4dc0c1e..8b031d6 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -717,11 +717,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["miningPoolStats"] = preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED); root["miningPoolName"] = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME); root["miningPoolUser"] = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); - + root["availablePools"] = PoolFactory::getAvailablePools(); root["httpAuthEnabled"] = preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED); root["httpAuthUser"] = preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME); root["httpAuthPass"] = preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD); - #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; root["flDisable"] = preferences.getBool("flDisable"); @@ -755,17 +754,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) #endif JsonArray screens = root["screens"].to(); - JsonArray actCurrencies = root["actCurrencies"].to(); - for (const auto &str : getActiveCurrencies()) - { - actCurrencies.add(str); - } - - JsonArray availableCurrencies = root["availableCurrencies"].to(); - for (const auto &str : getAvailableCurrencies()) - { - availableCurrencies.add(str); - } + root["actCurrencies"] = getActiveCurrencies(); + root["availableCurrencies"] = getAvailableCurrencies(); std::vector screenNameMap = getScreenNameMap(); diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 8c49011..6817baf 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -14,6 +14,7 @@ #include "lib/price_notify.hpp" #include "lib/screen_handler.hpp" #include "webserver/OneParamRewrite.hpp" +#include "lib/mining_pool/pool_factory.hpp" extern TaskHandle_t eventSourceTaskHandle; From f613c7e9a1f4671807f1abce2475e0a7fd9d183d Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 20 Dec 2024 01:33:10 +0100 Subject: [PATCH 126/188] Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 85b9b17..dfe703d 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 85b9b17506f89696b89ab6f6e6ed231b7a8f6e91 +Subproject commit dfe703d67683f92313bea5080df4d3f6fa4ef26d From aeee5238b3e53b743ae00c6148dd4ca440a85bac Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 20 Dec 2024 04:00:09 +0100 Subject: [PATCH 127/188] Improve mining pool interface, added GoBrrr pool, Public Pool and Satoshi Radio pool --- lib/btclock/mining_pool_stats_handler.hpp | 5 - src/icons/icons.cpp | 131 +++++++++++++++++- src/lib/mining_pool/braiins/brains_pool.hpp | 2 + .../mining_pool/gobrrr_pool/gobrrr_pool.cpp | 14 ++ .../mining_pool/gobrrr_pool/gobrrr_pool.hpp | 15 ++ src/lib/mining_pool/mining_pool_interface.hpp | 2 + .../mining_pool_stats_handler.cpp | 17 ++- .../mining_pool/mining_pool_stats_handler.hpp | 8 ++ .../noderunners/noderunners_pool.cpp | 13 +- .../noderunners/noderunners_pool.hpp | 6 +- src/lib/mining_pool/ocean/ocean_pool.hpp | 3 + src/lib/mining_pool/pool_factory.cpp | 11 +- src/lib/mining_pool/pool_factory.hpp | 11 +- .../mining_pool/public_pool/public_pool.cpp | 21 +++ .../mining_pool/public_pool/public_pool.hpp | 15 ++ .../satoshi_radio/satoshi_radio_pool.cpp | 6 + .../satoshi_radio/satoshi_radio_pool.hpp | 14 ++ src/lib/screen_handler.cpp | 4 +- src/lib/screen_handler.hpp | 2 +- 19 files changed, 274 insertions(+), 26 deletions(-) delete mode 100644 lib/btclock/mining_pool_stats_handler.hpp create mode 100644 src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp create mode 100644 src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp rename {lib/btclock => src/lib/mining_pool}/mining_pool_stats_handler.cpp (90%) create mode 100644 src/lib/mining_pool/mining_pool_stats_handler.hpp create mode 100644 src/lib/mining_pool/public_pool/public_pool.cpp create mode 100644 src/lib/mining_pool/public_pool/public_pool.hpp create mode 100644 src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp create mode 100644 src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.hpp diff --git a/lib/btclock/mining_pool_stats_handler.hpp b/lib/btclock/mining_pool_stats_handler.hpp deleted file mode 100644 index d9aaadd..0000000 --- a/lib/btclock/mining_pool_stats_handler.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#include -#include - -std::array parseMiningPoolStatsHashRate(std::string text); -std::array parseMiningPoolStatsDailyEarnings(int sats, std::string label); diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index 5fd6c1a..91aee98 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -1138,8 +1138,134 @@ const unsigned char epd_icons_noderunners_logo [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; +// 'gobrrr', 122x122px +const unsigned char epd_icons_gobrrr_logo [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb3, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x10, 0x48, 0x84, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x24, 0x84, 0x20, 0x21, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x82, 0x25, 0x19, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x28, 0x18, 0x20, 0x41, 0x4c, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x83, 0x21, 0x88, 0x84, 0x21, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x8c, 0x48, 0x24, 0x09, 0x24, 0x82, 0x60, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x21, 0x08, 0x80, 0x61, 0x08, 0x18, 0x0a, 0x3f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfc, 0x02, 0x22, 0x19, 0x04, 0x42, 0x41, 0x88, 0x0f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x98, 0x82, 0x42, 0x1b, 0x11, 0x04, 0x21, 0x23, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xe2, 0x20, 0x50, 0x42, 0xff, 0xe4, 0x64, 0x24, 0x83, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xc8, 0x46, 0x14, 0x93, 0xff, 0xf8, 0x01, 0x84, 0x28, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x03, 0x10, 0x81, 0x0f, 0xff, 0xfe, 0x98, 0x11, 0x08, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x24, 0x01, 0x24, 0x5f, 0xff, 0xff, 0x02, 0x42, 0x42, 0x3f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfc, 0x88, 0xa9, 0x04, 0x3f, 0xff, 0xff, 0x90, 0x48, 0x21, 0x0f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x42, 0x22, 0x51, 0xbf, 0xf3, 0xff, 0xc5, 0x09, 0x0c, 0x47, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf3, 0x10, 0x40, 0x00, 0x7f, 0xc0, 0x7f, 0xc4, 0x21, 0x10, 0x93, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe0, 0x25, 0x14, 0xaa, 0xff, 0x0c, 0x1f, 0xc8, 0x84, 0x42, 0x09, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe4, 0x80, 0x04, 0x82, 0xfe, 0x21, 0x0f, 0x82, 0x50, 0x88, 0x63, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x84, 0x4a, 0xc8, 0x11, 0xfc, 0x81, 0x27, 0x21, 0x06, 0x23, 0x00, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x91, 0x10, 0x02, 0x45, 0xfc, 0x18, 0x42, 0x18, 0x3f, 0xf0, 0x18, 0x7f, 0xff, 0xc0, + 0xff, 0xff, 0x10, 0x91, 0x31, 0x13, 0xf9, 0x42, 0x10, 0x82, 0x7f, 0xfc, 0x82, 0x3f, 0xff, 0xc0, + 0xff, 0xfe, 0x46, 0x04, 0x04, 0x43, 0xf8, 0x04, 0x8c, 0x21, 0xff, 0xfe, 0x62, 0x1f, 0xff, 0xc0, + 0xff, 0xfc, 0x00, 0x62, 0x42, 0x13, 0xf8, 0xb0, 0x41, 0x0c, 0xff, 0xff, 0x08, 0xcf, 0xff, 0xc0, + 0xff, 0xfc, 0xa9, 0x08, 0x98, 0x8b, 0xf2, 0x03, 0x10, 0xc3, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xc0, + 0xff, 0xf8, 0x81, 0x04, 0x80, 0x8b, 0xf2, 0x48, 0x26, 0x13, 0xf8, 0x3f, 0xd5, 0x27, 0xff, 0xc0, + 0xff, 0xf2, 0x14, 0x61, 0x24, 0x23, 0xf0, 0x88, 0x80, 0x17, 0xf0, 0x0f, 0xc4, 0x23, 0xff, 0xc0, + 0xff, 0xf0, 0x44, 0x08, 0x05, 0x27, 0xf4, 0x22, 0x17, 0xe7, 0xe5, 0x27, 0xe0, 0x8b, 0xff, 0xc0, + 0xff, 0xe4, 0x41, 0x86, 0x50, 0x43, 0xf1, 0x04, 0x47, 0xf7, 0xc1, 0x0f, 0xe9, 0x01, 0xff, 0xc0, + 0xff, 0xc3, 0x18, 0x30, 0x42, 0x13, 0xf9, 0x31, 0x27, 0xf7, 0xd0, 0x43, 0xe1, 0x31, 0xff, 0xc0, + 0xff, 0xd0, 0x02, 0x01, 0x08, 0x8b, 0xf8, 0x00, 0x8f, 0xef, 0xc6, 0x33, 0xe4, 0x04, 0xff, 0xc0, + 0xff, 0xc4, 0xa0, 0xc9, 0x22, 0x23, 0xfa, 0x4c, 0x17, 0xef, 0xc0, 0x87, 0xf0, 0xc4, 0xff, 0xc0, + 0xff, 0x88, 0x8c, 0x08, 0x21, 0x05, 0xfc, 0x41, 0x47, 0xf7, 0xc8, 0x03, 0xe2, 0x10, 0x7f, 0xc0, + 0xff, 0x82, 0x11, 0x22, 0x0c, 0x51, 0xfc, 0x10, 0x27, 0xe7, 0xc9, 0x5b, 0xe8, 0x12, 0x7f, 0xc0, + 0xff, 0x24, 0x41, 0x22, 0xc0, 0x83, 0xfe, 0x86, 0x0f, 0xf7, 0xc2, 0x07, 0xe3, 0x42, 0x3f, 0xc0, + 0xff, 0x09, 0x08, 0x78, 0x3a, 0x28, 0xff, 0x20, 0xc3, 0xf7, 0xe2, 0x27, 0xe0, 0x08, 0xbf, 0xc0, + 0xfe, 0x40, 0x92, 0x79, 0x38, 0x40, 0xff, 0x88, 0x1f, 0xe7, 0xf0, 0x8f, 0xcc, 0xa1, 0x1f, 0xc0, + 0xfe, 0x14, 0x27, 0xff, 0xfd, 0x14, 0x7f, 0xe3, 0x3f, 0xf3, 0xf4, 0x9f, 0xc0, 0x24, 0x5f, 0xc0, + 0xfe, 0x21, 0x0f, 0xff, 0xff, 0xc1, 0x3f, 0xff, 0xff, 0xf3, 0xfe, 0x7f, 0x93, 0x04, 0x1f, 0xc0, + 0xfc, 0x88, 0xcf, 0xff, 0xff, 0xf2, 0x1f, 0xff, 0xff, 0xf1, 0xff, 0xff, 0x84, 0x49, 0x2f, 0xc0, + 0xfc, 0x46, 0x0f, 0xff, 0xff, 0xf8, 0x8f, 0xff, 0xff, 0xf0, 0xff, 0xfe, 0x20, 0x40, 0x8f, 0xc0, + 0xfc, 0x10, 0x27, 0xff, 0xff, 0xfc, 0xa7, 0xff, 0xfe, 0xf2, 0x7f, 0xfe, 0x29, 0x12, 0x1f, 0xc0, + 0xf9, 0x81, 0x27, 0xf0, 0x2f, 0xfe, 0x01, 0xff, 0xf8, 0x12, 0x3f, 0xf8, 0x82, 0x12, 0x47, 0xc0, + 0xf8, 0x29, 0x0f, 0xe2, 0x01, 0xff, 0x18, 0x7f, 0xc1, 0x00, 0x87, 0x80, 0x12, 0x40, 0x47, 0xc0, + 0xfa, 0x22, 0x4f, 0xe8, 0xc4, 0xff, 0x22, 0x00, 0x04, 0x4c, 0x10, 0x25, 0x40, 0x4c, 0x17, 0xc0, + 0xf8, 0x84, 0x17, 0xe1, 0x10, 0xff, 0x00, 0x80, 0x24, 0x41, 0x40, 0x04, 0x19, 0x01, 0x87, 0xc0, + 0xf1, 0x11, 0x27, 0xe4, 0x12, 0x7f, 0x4c, 0x24, 0x91, 0x11, 0x0c, 0xd0, 0x81, 0x30, 0x27, 0xc0, + 0xf4, 0x40, 0x8f, 0xe4, 0x82, 0x7f, 0x11, 0x12, 0x01, 0x12, 0x21, 0x02, 0x44, 0x06, 0x23, 0xc0, + 0xf0, 0x8c, 0x07, 0xe0, 0xa4, 0xff, 0x13, 0x12, 0x64, 0xc2, 0x42, 0x32, 0x30, 0xc0, 0x8b, 0xc0, + 0xf2, 0x21, 0x67, 0xea, 0x08, 0xff, 0x7f, 0x4f, 0xcf, 0xe5, 0xfb, 0xf8, 0xfe, 0x18, 0x43, 0xc0, + 0xf2, 0x10, 0x0f, 0xe0, 0x42, 0xfe, 0x3f, 0xbf, 0xcf, 0xef, 0xfb, 0xf9, 0xfe, 0x03, 0x13, 0xc0, + 0xf0, 0xc6, 0x27, 0xe5, 0x17, 0xfe, 0xbf, 0xff, 0xef, 0xff, 0xf3, 0xff, 0xfc, 0xa0, 0x27, 0xc0, + 0xf1, 0x00, 0x87, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xfe, 0x0c, 0x83, 0xc0, + 0xf2, 0x28, 0x97, 0xff, 0xff, 0xf9, 0x3f, 0xff, 0xcf, 0xff, 0xf9, 0xff, 0xfc, 0x40, 0x4b, 0xc0, + 0xf0, 0x82, 0x17, 0xff, 0xff, 0xf2, 0x1f, 0xff, 0xc7, 0xff, 0xf1, 0xff, 0xfd, 0x23, 0x13, 0xc0, + 0xe4, 0x12, 0x47, 0xff, 0xff, 0xf8, 0x9f, 0xf3, 0x97, 0xfc, 0xf3, 0xff, 0x5c, 0x24, 0x13, 0xc0, + 0xf1, 0x48, 0x4f, 0xff, 0xff, 0xfc, 0x7f, 0xc0, 0x07, 0xf0, 0x01, 0xfc, 0x00, 0x84, 0x83, 0xc0, + 0xf1, 0x01, 0x07, 0xff, 0xff, 0xff, 0x1f, 0xc8, 0x67, 0xf3, 0x0d, 0xfc, 0x82, 0x10, 0x63, 0xc0, + 0xe4, 0x31, 0x27, 0xe0, 0x03, 0xff, 0x1f, 0xc6, 0x0f, 0xf0, 0x41, 0xfc, 0x52, 0x43, 0x09, 0xc0, + 0xf0, 0x84, 0x27, 0xe0, 0x08, 0xff, 0x3f, 0x90, 0x87, 0xe4, 0x21, 0xfc, 0x44, 0x48, 0x0b, 0xc0, + 0xf2, 0x08, 0x8f, 0xec, 0xc0, 0x7f, 0x9f, 0x80, 0x97, 0xf1, 0x8d, 0xf9, 0x09, 0x08, 0xa3, 0xc0, + 0xf1, 0x48, 0x87, 0xe1, 0x14, 0x7f, 0x9f, 0xcc, 0x27, 0xe4, 0x11, 0xfc, 0x20, 0x22, 0x07, 0xc0, + 0xf0, 0x42, 0x37, 0xe1, 0x05, 0x3f, 0x9f, 0x91, 0x27, 0xe0, 0x41, 0xfc, 0x84, 0x84, 0x53, 0xc0, + 0xf2, 0x12, 0x07, 0xe8, 0x60, 0x7f, 0x9f, 0x81, 0x07, 0xf3, 0x09, 0xf8, 0x92, 0x51, 0x03, 0xc0, + 0xf2, 0x20, 0xcf, 0xe2, 0x09, 0x7f, 0x9f, 0xc8, 0x4f, 0xf0, 0x25, 0xfc, 0x10, 0x02, 0x2b, 0xc0, + 0xf0, 0x89, 0x07, 0xe4, 0x84, 0x7f, 0x9f, 0x92, 0x27, 0xe4, 0x91, 0xfc, 0x45, 0x88, 0x83, 0xc0, + 0xf9, 0x08, 0x37, 0xe0, 0x30, 0xff, 0x9f, 0x84, 0x87, 0xe4, 0x83, 0xf9, 0x20, 0x24, 0x4f, 0xc0, + 0xf8, 0x62, 0x0f, 0xea, 0x43, 0xff, 0x3f, 0xc0, 0x4f, 0xf0, 0x28, 0x99, 0x08, 0x91, 0x07, 0xc0, + 0xf8, 0x82, 0x4f, 0xf2, 0x4f, 0xff, 0x1f, 0x99, 0x17, 0xf2, 0x4f, 0xef, 0x8a, 0x02, 0x37, 0xc0, + 0xf9, 0x10, 0x87, 0xff, 0xff, 0xfe, 0x5f, 0xc1, 0x07, 0xe2, 0x1f, 0xff, 0xa0, 0xa4, 0x87, 0xc0, + 0xf8, 0x44, 0x9f, 0xff, 0xff, 0xfe, 0x1f, 0xc4, 0x67, 0xf0, 0x8f, 0xff, 0x84, 0x10, 0x47, 0xc0, + 0xfc, 0x12, 0x0f, 0xff, 0xff, 0xfc, 0x9f, 0x90, 0x8f, 0xf4, 0x4f, 0xff, 0x91, 0x42, 0x1f, 0xc0, + 0xfd, 0x20, 0x67, 0xff, 0xff, 0xf8, 0x5f, 0xc6, 0x07, 0xf1, 0x1f, 0xff, 0x91, 0x09, 0x8f, 0xc0, + 0xfc, 0x49, 0x0f, 0xff, 0xff, 0xe1, 0x1f, 0xc0, 0x57, 0xf0, 0x8f, 0xff, 0x84, 0x24, 0x2f, 0xc0, + 0xfc, 0x42, 0x0f, 0xfb, 0x7e, 0x04, 0x3f, 0x19, 0x07, 0xc6, 0x2f, 0xff, 0xa4, 0x90, 0x0f, 0xc0, + 0xfe, 0x10, 0xa0, 0x78, 0x78, 0x90, 0x80, 0x01, 0x20, 0x00, 0x0f, 0xff, 0x88, 0x42, 0xdf, 0xc0, + 0xfe, 0x84, 0x80, 0x38, 0x38, 0x86, 0x00, 0xc4, 0x20, 0x29, 0x40, 0x00, 0x42, 0x08, 0x1f, 0xc0, + 0xfe, 0x24, 0x12, 0x02, 0x02, 0x20, 0x64, 0x10, 0x8c, 0x81, 0x13, 0xfc, 0x11, 0x89, 0x3f, 0xc0, + 0xff, 0x11, 0x24, 0xc2, 0x90, 0x22, 0x09, 0x26, 0x10, 0x94, 0x24, 0xf9, 0x88, 0x21, 0x3f, 0xc0, + 0xff, 0x41, 0x21, 0x10, 0x85, 0x89, 0x88, 0x20, 0x42, 0x10, 0x80, 0xf2, 0x22, 0x24, 0x3f, 0xc0, + 0xff, 0x8c, 0x08, 0x11, 0x24, 0x08, 0x22, 0x89, 0x22, 0x42, 0x4a, 0x64, 0x10, 0x84, 0xff, 0xc0, + 0xff, 0x80, 0xc4, 0x88, 0x10, 0x42, 0x02, 0x08, 0x88, 0x49, 0x10, 0x09, 0x44, 0x90, 0x7f, 0xc0, + 0xff, 0xd2, 0x17, 0xef, 0xdf, 0xff, 0xfd, 0xf6, 0xff, 0xff, 0xff, 0xc0, 0x08, 0x12, 0xff, 0xc0, + 0xff, 0xc2, 0x1b, 0x76, 0xed, 0xb5, 0xb7, 0x77, 0x66, 0xb6, 0x6d, 0xe4, 0xa3, 0x02, 0xff, 0xc0, + 0xff, 0xe8, 0xdb, 0x5b, 0x36, 0x6d, 0xb6, 0xd9, 0xbb, 0xad, 0xb6, 0x64, 0x20, 0x61, 0xff, 0xc0, + 0xff, 0xe0, 0x1d, 0xdb, 0xdb, 0xce, 0x6d, 0xbe, 0xdb, 0x6d, 0xb7, 0xb1, 0x0c, 0x0d, 0xff, 0xc0, + 0xff, 0xf5, 0x06, 0xdc, 0xdb, 0x73, 0xdb, 0x66, 0xdc, 0xdb, 0x6d, 0x81, 0x11, 0x83, 0xff, 0xc0, + 0xff, 0xf1, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x80, 0x00, 0x48, 0x40, 0x33, 0xff, 0xc0, + 0xff, 0xf8, 0x25, 0x26, 0xac, 0x89, 0x2c, 0xb3, 0x26, 0x26, 0x9a, 0x4c, 0xca, 0x07, 0xff, 0xc0, + 0xff, 0xfc, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xf7, 0xbc, 0x8f, 0xff, 0xc0, + 0xff, 0xfc, 0x9a, 0xd9, 0x53, 0x66, 0xd3, 0x4c, 0xd9, 0x99, 0x6d, 0xb6, 0xe6, 0x3f, 0xff, 0xc0, + 0xff, 0xfe, 0x1b, 0x6f, 0x7d, 0xbb, 0x7d, 0xf7, 0x6e, 0xef, 0x6d, 0xbb, 0x7a, 0x1f, 0xff, 0xc0, + 0xff, 0xff, 0x4d, 0xb3, 0xa6, 0xdb, 0x4d, 0x37, 0x6e, 0xed, 0xb6, 0xcd, 0x98, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0x26, 0xdc, 0xfb, 0x6d, 0xf7, 0xd9, 0xb3, 0x35, 0xb6, 0xf6, 0xec, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe4, 0x22, 0x04, 0x92, 0x08, 0x24, 0x48, 0x88, 0x49, 0x09, 0x11, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xe2, 0x48, 0x90, 0x00, 0x82, 0x04, 0x84, 0x92, 0x00, 0x40, 0x45, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf8, 0x84, 0x93, 0x29, 0x31, 0x90, 0x91, 0x11, 0x96, 0x14, 0x87, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xf9, 0x21, 0x00, 0x44, 0x04, 0x22, 0x10, 0x44, 0x10, 0x91, 0x2f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x08, 0x4c, 0x44, 0x90, 0x22, 0x46, 0x00, 0x40, 0x82, 0x1f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xfe, 0x92, 0x21, 0x10, 0x84, 0x88, 0x40, 0xaa, 0x4a, 0x24, 0x9f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0x81, 0x01, 0x02, 0x22, 0x09, 0x18, 0x21, 0x08, 0x20, 0x7f, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xcc, 0x54, 0x62, 0x28, 0xc1, 0x03, 0x04, 0x21, 0x8a, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf0, 0x84, 0x08, 0x81, 0x14, 0x60, 0x48, 0x84, 0x0b, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xf8, 0x89, 0x08, 0x44, 0x10, 0x0c, 0x22, 0x50, 0x67, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xfe, 0x20, 0xc3, 0x12, 0x43, 0x01, 0x84, 0x06, 0x1f, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x06, 0x10, 0x20, 0x88, 0x60, 0x11, 0x20, 0xbf, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xd0, 0x11, 0x24, 0x20, 0x8c, 0x48, 0x91, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xf1, 0x44, 0x43, 0x0c, 0x01, 0x02, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x08, 0x90, 0x41, 0x31, 0x30, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x22, 0x04, 0x31, 0x04, 0x05, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x21, 0x84, 0x42, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x28, 0x04, 0x48, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x51, 0x11, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +}; + // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) -const int epd_icons_allArray_LEN = 7; +const int epd_icons_allArray_LEN = 8; const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { epd_icons_pickaxe, epd_icons_rocket_launch, @@ -1147,5 +1273,6 @@ const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { epd_icons_bitaxe_logo, epd_icons_ocean_logo, epd_icons_braiins_logo, - epd_icons_noderunners_logo + epd_icons_noderunners_logo, + epd_icons_gobrrr_logo }; diff --git a/src/lib/mining_pool/braiins/brains_pool.hpp b/src/lib/mining_pool/braiins/brains_pool.hpp index ea2c0e0..a002c58 100644 --- a/src/lib/mining_pool/braiins/brains_pool.hpp +++ b/src/lib/mining_pool/braiins/brains_pool.hpp @@ -12,6 +12,8 @@ public: PoolStats parseResponse(const JsonDocument &doc) const override; LogoData getLogo() const override; bool supportsDailyEarnings() const override { return true; } + bool hasLogo() const override { return true; } + std::string getDisplayLabel() const override { return "BRAIINS/POOL"; } // Fallback if needed std::string getDailyEarningsLabel() const override { return "sats/earned"; } private: static int getHashrateMultiplier(const std::string &unit); diff --git a/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp new file mode 100644 index 0000000..90cd420 --- /dev/null +++ b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp @@ -0,0 +1,14 @@ +// src/noderunners/noderunners_pool.cpp +#include "gobrrr_pool.hpp" + +std::string GoBrrrPool::getApiUrl() const { + return "https://pool.gobrrr.me/api/client/" + poolUser; +} + +LogoData GoBrrrPool::getLogo() const { + return LogoData { + .data = epd_icons_allArray[7], + .width = 122, + .height = 122 + }; +} diff --git a/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp new file mode 100644 index 0000000..c50fe83 --- /dev/null +++ b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include "lib/mining_pool/mining_pool_interface.hpp" +#include "lib/mining_pool/public_pool/public_pool.hpp" + +#include + +class GoBrrrPool : public PublicPool { +public: + std::string getApiUrl() const override; + bool hasLogo() const override { return true; } + std::string getDisplayLabel() const override { return "GOBRRR/POOL"; } + LogoData getLogo() const override; +}; \ No newline at end of file diff --git a/src/lib/mining_pool/mining_pool_interface.hpp b/src/lib/mining_pool/mining_pool_interface.hpp index 1df84b9..63bb53f 100644 --- a/src/lib/mining_pool/mining_pool_interface.hpp +++ b/src/lib/mining_pool/mining_pool_interface.hpp @@ -12,7 +12,9 @@ public: virtual void prepareRequest(HTTPClient& http) const = 0; virtual std::string getApiUrl() const = 0; virtual PoolStats parseResponse(const JsonDocument& doc) const = 0; + virtual bool hasLogo() const = 0; virtual LogoData getLogo() const = 0; + virtual std::string getDisplayLabel() const = 0; virtual bool supportsDailyEarnings() const = 0; virtual std::string getDailyEarningsLabel() const = 0; diff --git a/lib/btclock/mining_pool_stats_handler.cpp b/src/lib/mining_pool/mining_pool_stats_handler.cpp similarity index 90% rename from lib/btclock/mining_pool_stats_handler.cpp rename to src/lib/mining_pool/mining_pool_stats_handler.cpp index 445b912..4eb8007 100644 --- a/lib/btclock/mining_pool_stats_handler.cpp +++ b/src/lib/mining_pool/mining_pool_stats_handler.cpp @@ -1,7 +1,7 @@ #include "mining_pool_stats_handler.hpp" #include -std::array parseMiningPoolStatsHashRate(std::string text) +std::array parseMiningPoolStatsHashRate(std::string text, const MiningPoolInterface& pool) { std::array ret; ret.fill(""); // Initialize all elements to empty strings @@ -55,15 +55,18 @@ std::array parseMiningPoolStatsHashRate(std::string te ret[NUM_SCREENS - 1] = label; - - ret[0] = "mdi:miningpool"; + if (pool.hasLogo()) { + ret[0] = "mdi:miningpool"; + } else { + ret[0] = pool.getDisplayLabel(); + } return ret; } -std::array parseMiningPoolStatsDailyEarnings(int sats, std::string label) +std::array parseMiningPoolStatsDailyEarnings(int sats, std::string label, const MiningPoolInterface& pool) { std::array ret; ret.fill(""); // Initialize all elements to empty strings @@ -109,7 +112,11 @@ std::array parseMiningPoolStatsDailyEarnings(int sats, ret[NUM_SCREENS - 1] = label; - ret[0] = "mdi:miningpool"; + if (pool.hasLogo()) { + ret[0] = "mdi:miningpool"; + } else { + ret[0] = pool.getDisplayLabel(); + } return ret; } \ No newline at end of file diff --git a/src/lib/mining_pool/mining_pool_stats_handler.hpp b/src/lib/mining_pool/mining_pool_stats_handler.hpp new file mode 100644 index 0000000..a284bc7 --- /dev/null +++ b/src/lib/mining_pool/mining_pool_stats_handler.hpp @@ -0,0 +1,8 @@ +#include +#include +#ifndef UNITY_TEST +#include "lib/mining_pool/mining_pool_interface.hpp" +#endif + +std::array parseMiningPoolStatsHashRate(std::string text, const MiningPoolInterface& pool); +std::array parseMiningPoolStatsDailyEarnings(int sats, std::string label, const MiningPoolInterface& pool); diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.cpp b/src/lib/mining_pool/noderunners/noderunners_pool.cpp index 22ee112..495ffd9 100644 --- a/src/lib/mining_pool/noderunners/noderunners_pool.cpp +++ b/src/lib/mining_pool/noderunners/noderunners_pool.cpp @@ -1,15 +1,15 @@ // src/noderunners/noderunners_pool.cpp #include "noderunners_pool.hpp" -void NodeRunnersPool::prepareRequest(HTTPClient& http) const { +void NoderunnersPool::prepareRequest(HTTPClient& http) const { // Empty as NodeRunners doesn't need special headers } -std::string NodeRunnersPool::getApiUrl() const { +std::string NoderunnersPool::getApiUrl() const { return "https://pool.noderunners.network/api/v1/users/" + poolUser; } -PoolStats NodeRunnersPool::parseResponse(const JsonDocument& doc) const { +PoolStats NoderunnersPool::parseResponse(const JsonDocument& doc) const { std::string hashrateStr = doc["hashrate1m"].as(); char unit = hashrateStr.back(); std::string value = hashrateStr.substr(0, hashrateStr.size() - 1); @@ -22,7 +22,7 @@ PoolStats NodeRunnersPool::parseResponse(const JsonDocument& doc) const { }; } -LogoData NodeRunnersPool::getLogo() const { +LogoData NoderunnersPool::getLogo() const { return LogoData { .data = epd_icons_allArray[6], .width = 122, @@ -30,7 +30,10 @@ LogoData NodeRunnersPool::getLogo() const { }; } -int NodeRunnersPool::getHashrateMultiplier(char unit) { +int NoderunnersPool::getHashrateMultiplier(char unit) { + if (unit == '0') + return 0; + static const std::unordered_map multipliers = { {'Z', 21}, {'E', 18}, {'P', 15}, {'T', 12}, {'G', 9}, {'M', 6}, {'K', 3} diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.hpp b/src/lib/mining_pool/noderunners/noderunners_pool.hpp index ea1b0a2..c6cb25c 100644 --- a/src/lib/mining_pool/noderunners/noderunners_pool.hpp +++ b/src/lib/mining_pool/noderunners/noderunners_pool.hpp @@ -4,7 +4,7 @@ #include "lib/mining_pool/mining_pool_interface.hpp" #include -class NodeRunnersPool : public MiningPoolInterface { +class NoderunnersPool : public MiningPoolInterface { public: void setPoolUser(const std::string& user) override { poolUser = user; } @@ -14,7 +14,9 @@ public: LogoData getLogo() const override; bool supportsDailyEarnings() const override { return false; } std::string getDailyEarningsLabel() const override { return ""; } + bool hasLogo() const override { return true; } + std::string getDisplayLabel() const override { return "NODE/RUNNERS"; } // Fallback if needed -private: +protected: static int getHashrateMultiplier(char unit); }; \ No newline at end of file diff --git a/src/lib/mining_pool/ocean/ocean_pool.hpp b/src/lib/mining_pool/ocean/ocean_pool.hpp index 3756579..2773915 100644 --- a/src/lib/mining_pool/ocean/ocean_pool.hpp +++ b/src/lib/mining_pool/ocean/ocean_pool.hpp @@ -10,6 +10,9 @@ public: std::string getApiUrl() const override; PoolStats parseResponse(const JsonDocument& doc) const override; LogoData getLogo() const override; + bool hasLogo() const override { return true; } + std::string getDisplayLabel() const override { return "OCEAN/POOL"; } // Fallback if needed bool supportsDailyEarnings() const override { return true; } std::string getDailyEarningsLabel() const override { return "sats/block"; } + }; \ No newline at end of file diff --git a/src/lib/mining_pool/pool_factory.cpp b/src/lib/mining_pool/pool_factory.cpp index 0a786a9..d3073e1 100644 --- a/src/lib/mining_pool/pool_factory.cpp +++ b/src/lib/mining_pool/pool_factory.cpp @@ -1,15 +1,20 @@ #include "pool_factory.hpp" - const char* PoolFactory::MINING_POOL_NAME_OCEAN = "ocean"; const char* PoolFactory::MINING_POOL_NAME_NODERUNNERS = "noderunners"; const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins"; +const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio"; +const char* PoolFactory::MINING_POOL_NAME_PUBLIC_POOL = "public_pool"; +const char* PoolFactory::MINING_POOL_NAME_GOBRRR_POOL = "gobrrr_pool"; std::unique_ptr PoolFactory::createPool(const std::string& poolName) { static const std::unordered_map()>> poolFactories = { {MINING_POOL_NAME_OCEAN, []() { return std::make_unique(); }}, - {MINING_POOL_NAME_NODERUNNERS, []() { return std::make_unique(); }}, - {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique(); }} + {MINING_POOL_NAME_NODERUNNERS, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique(); }} }; auto it = poolFactories.find(poolName); diff --git a/src/lib/mining_pool/pool_factory.hpp b/src/lib/mining_pool/pool_factory.hpp index 92cba72..a8e4861 100644 --- a/src/lib/mining_pool/pool_factory.hpp +++ b/src/lib/mining_pool/pool_factory.hpp @@ -5,6 +5,9 @@ #include "noderunners/noderunners_pool.hpp" #include "braiins/brains_pool.hpp" #include "ocean/ocean_pool.hpp" +#include "satoshi_radio/satoshi_radio_pool.hpp" +#include "public_pool/public_pool.hpp" +#include "gobrrr_pool/gobrrr_pool.hpp" class PoolFactory { public: @@ -13,7 +16,10 @@ class PoolFactory { return { MINING_POOL_NAME_OCEAN, MINING_POOL_NAME_NODERUNNERS, - MINING_POOL_NAME_BRAIINS + MINING_POOL_NAME_SATOSHI_RADIO, + MINING_POOL_NAME_BRAIINS, + MINING_POOL_NAME_PUBLIC_POOL, + MINING_POOL_NAME_GOBRRR_POOL }; } @@ -32,4 +38,7 @@ class PoolFactory { static const char* MINING_POOL_NAME_OCEAN; static const char* MINING_POOL_NAME_NODERUNNERS; static const char* MINING_POOL_NAME_BRAIINS; + static const char* MINING_POOL_NAME_SATOSHI_RADIO; + static const char* MINING_POOL_NAME_PUBLIC_POOL; + static const char* MINING_POOL_NAME_GOBRRR_POOL; }; \ No newline at end of file diff --git a/src/lib/mining_pool/public_pool/public_pool.cpp b/src/lib/mining_pool/public_pool/public_pool.cpp new file mode 100644 index 0000000..9a5d56f --- /dev/null +++ b/src/lib/mining_pool/public_pool/public_pool.cpp @@ -0,0 +1,21 @@ +// src/noderunners/noderunners_pool.cpp +#include "public_pool.hpp" + +std::string PublicPool::getApiUrl() const { + return "https://public-pool.io:40557/api/client/" + poolUser; +} + +PoolStats PublicPool::parseResponse(const JsonDocument& doc) const { + uint64_t totalHashrate = 0; + + for (JsonVariantConst worker : doc["workers"].as()) { + totalHashrate += static_cast(std::llround(worker["hashRate"].as())); + } + + + + return PoolStats{ + .hashrate = std::to_string(totalHashrate), + .dailyEarnings = std::nullopt // Public Pool doesn't support daily earnings + }; +} \ No newline at end of file diff --git a/src/lib/mining_pool/public_pool/public_pool.hpp b/src/lib/mining_pool/public_pool/public_pool.hpp new file mode 100644 index 0000000..e2fdaed --- /dev/null +++ b/src/lib/mining_pool/public_pool/public_pool.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include "lib/mining_pool/mining_pool_interface.hpp" +#include "lib/mining_pool/noderunners/noderunners_pool.hpp" + +#include + +class PublicPool : public NoderunnersPool { +public: + std::string getApiUrl() const override; + bool hasLogo() const override { return false; } + std::string getDisplayLabel() const override { return "PUBLIC/POOL"; } + PoolStats parseResponse(const JsonDocument& doc) const override; +}; \ No newline at end of file diff --git a/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp b/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp new file mode 100644 index 0000000..5b8f4a3 --- /dev/null +++ b/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp @@ -0,0 +1,6 @@ +// src/noderunners/noderunners_pool.cpp +#include "satoshi_radio_pool.hpp" + +std::string SatoshiRadioPool::getApiUrl() const { + return "https://pool.satoshiradio.nl/api/v1/users/" + poolUser; +} diff --git a/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.hpp b/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.hpp new file mode 100644 index 0000000..d809fb1 --- /dev/null +++ b/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.hpp @@ -0,0 +1,14 @@ + +#pragma once + +#include "lib/mining_pool/mining_pool_interface.hpp" +#include "lib/mining_pool/noderunners/noderunners_pool.hpp" + +#include + +class SatoshiRadioPool : public NoderunnersPool { +public: + std::string getApiUrl() const override; + bool hasLogo() const override { return false; } + std::string getDisplayLabel() const override { return "SATOSHI/RADIO"; } // Fallback if needed +}; \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index d4043fa..ea55e02 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -38,10 +38,10 @@ void workerTask(void *pvParameters) { case TASK_MINING_POOL_STATS_UPDATE: { if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { taskEpdContent = - parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate()); + parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()); } else if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS) { taskEpdContent = - parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), getMiningPool()->getDailyEarningsLabel()); + parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), getMiningPool()->getDailyEarningsLabel(), *getMiningPool()); } setEpdContent(taskEpdContent); break; diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index ee658c9..fcdf2ba 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include "lib/mining_pool/mining_pool_stats_handler.hpp" #include "lib/epd.hpp" #include "lib/shared.hpp" From e8a7b221cbd579fe8745da282e914b580caf2d53 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 20 Dec 2024 04:07:46 +0100 Subject: [PATCH 128/188] Update WebUI for new mining pools --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index dfe703d..fd328d4 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit dfe703d67683f92313bea5080df4d3f6fa4ef26d +Subproject commit fd328d4f05345eaa73cf27d05bb542eaa6915cdb From 7bcb24bab0a7c8ba8d5bcdba5a91a07834ad9a38 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 20 Dec 2024 14:11:26 +0100 Subject: [PATCH 129/188] Mining pool bugfixes, improvements and icon optimizations --- lib/btclock/utils.cpp | 79 ++ lib/btclock/utils.hpp | 6 +- platformio.ini | 4 +- src/icons/icons.cpp | 727 ++++++------------ src/lib/config.cpp | 78 -- src/lib/epd.cpp | 4 +- src/lib/mining_pool/braiins/brains_pool.cpp | 4 +- src/lib/mining_pool/braiins/brains_pool.hpp | 3 +- .../mining_pool/mining_pool_stats_handler.cpp | 39 +- .../mining_pool/mining_pool_stats_handler.hpp | 5 +- .../noderunners/noderunners_pool.cpp | 19 +- .../noderunners/noderunners_pool.hpp | 4 +- src/lib/mining_pool_stats_fetch.cpp | 1 + test/test_mining_pool/test_main.cpp | 75 ++ 14 files changed, 411 insertions(+), 637 deletions(-) create mode 100644 test/test_mining_pool/test_main.cpp diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index 0e19962..d5c8027 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -164,3 +164,82 @@ int64_t getAmountInSatoshis(std::string bolt11) { return satoshis; } + +void parseHashrateString(const std::string& hashrate, std::string& label, std::string& output, unsigned int maxCharacters) { + // Handle empty string or "0" cases + if (hashrate.empty() || hashrate == "0") { + label = "H/S"; + output = "0"; + return; + } + + size_t suffixLength = 0; + if (hashrate.length() > 21) { + label = "ZH/S"; + suffixLength = 21; + } else if (hashrate.length() > 18) { + label = "EH/S"; + suffixLength = 18; + } else if (hashrate.length() > 15) { + label = "PH/S"; + suffixLength = 15; + } else if (hashrate.length() > 12) { + label = "TH/S"; + suffixLength = 12; + } else if (hashrate.length() > 9) { + label = "GH/S"; + suffixLength = 9; + } else if (hashrate.length() > 6) { + label = "MH/S"; + suffixLength = 6; + } else if (hashrate.length() > 3) { + label = "KH/S"; + suffixLength = 3; + } else { + label = "H/S"; + suffixLength = 0; + } + + double value = std::stod(hashrate) / std::pow(10, suffixLength); + + // Calculate integer part length + int integerPartLength = std::to_string(static_cast(value)).length(); + + // Calculate remaining space for decimals + int remainingSpace = maxCharacters - integerPartLength; + + char buffer[32]; + if (remainingSpace <= 0) + { + // No space for decimals, just round to integer + snprintf(buffer, sizeof(buffer), "%.0f", value); + } + else + { + // Space for decimal point and some decimals + snprintf(buffer, sizeof(buffer), "%.*f", remainingSpace - 1, value); + } + + // Remove trailing zeros and decimal point if necessary + output = buffer; + if (output.find('.') != std::string::npos) + { + output = output.substr(0, output.find_last_not_of('0') + 1); + if (output.back() == '.') + { + output.pop_back(); + } + } + +} + +int getHashrateMultiplier(char unit) { + if (unit == '0') + return 0; + + static const std::unordered_map multipliers = { + {'Z', 21}, {'E', 18}, {'P', 15}, {'T', 12}, + {'G', 9}, {'M', 6}, {'K', 3} + }; + return multipliers.at(unit); +} diff --git a/lib/btclock/utils.hpp b/lib/btclock/utils.hpp index cf4a107..44df032 100644 --- a/lib/btclock/utils.hpp +++ b/lib/btclock/utils.hpp @@ -5,6 +5,8 @@ #include #include #include +#include + int modulo(int x,int N); @@ -12,4 +14,6 @@ double getSupplyAtBlock(std::uint32_t blockNr); std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4); std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mowMode); -int64_t getAmountInSatoshis(std::string bolt11); \ No newline at end of file +int64_t getAmountInSatoshis(std::string bolt11); +void parseHashrateString(const std::string& hashrate, std::string& label, std::string& output, unsigned int maxCharacters); +int getHashrateMultiplier(char unit); \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index d2abb92..81d4a49 100644 --- a/platformio.ini +++ b/platformio.ini @@ -164,4 +164,6 @@ build_flags = -D MCP_INT_PIN=8 -D NEOPIXEL_PIN=34 -D NEOPIXEL_COUNT=4 - -D NUM_SCREENS=7 \ No newline at end of file + -D NUM_SCREENS=7 + -D UNITY_TEST + -std=gnu++17 diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index 91aee98..55854f8 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -376,258 +376,160 @@ const unsigned char epd_icons_pickaxe [] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 }; -// 'bitaxe_logo', 122x250px const unsigned char epd_icons_bitaxe_logo [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xcf, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0x83, 0xff, 0xff, 0xf7, 0xf0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xf1, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xcf, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xf9, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xf8, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0x8f, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x8f, 0xf7, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xef, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x11, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0x9f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xfb, 0xf0, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xf7, 0xf8, 0x3f, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xf1, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0x83, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xef, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xff, 0xff, 0x83, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x1f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x0f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x01, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xbf, 0xff, 0x01, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc1, 0x00, 0x00, 0x7f, 0xc1, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x81, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xfd, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xfb, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xf3, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xe7, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0x8f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x82, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 + // 'bitaxe_dark copy', 88x220px + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3e, 0xff, 0xff, 0xfd, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x00, 0xff, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0x00, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, + 0xf0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, + 0xff, 0xf0, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, + 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, + 0xff, 0xff, 0xe0, 0x07, 0xf8, 0x7f, 0xff, 0xfd, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xfe, + 0x3f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0x1f, 0xff, 0xff, 0xf8, 0x07, + 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xcf, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0x80, 0x0f, + 0xff, 0xe7, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xf3, 0xff, 0xff, 0xf0, + 0x01, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xf3, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xf0, + 0x1f, 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xfd, 0xff, 0xff, + 0xe0, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xfd, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, + 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, + 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfe, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfe, 0x7f, + 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xfc, 0x7f, 0xff, 0xff, + 0xff, 0xfc, 0x00, 0x00, 0x7f, 0x3f, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7e, + 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x0f, 0xff, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, + 0x1f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xf0, 0x03, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + 0x0f, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xfc, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, + 0x00, 0x3f, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, + 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, + 0xe0, 0x0f, 0xff, 0xf1, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xf9, 0xfc, + 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x3f, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xfe, 0x3f, + 0xff, 0xf8, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xfc, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, + 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xf7, 0xff, 0xff, 0xfc, 0x3f, 0xff, + 0xff, 0xff, 0xe0, 0x07, 0xff, 0xf3, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, + 0xf3, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xf3, 0xff, 0xff, 0xf8, 0x01, + 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, + 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xfc, 0xff, 0xff, 0xf0, + 0x03, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xfe, 0x3f, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xf8, + 0x3f, 0xff, 0x7e, 0x1f, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xfc, 0xff, 0x03, 0xff, + 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xe0, + 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, + 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, + 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, + 0xff, 0x83, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xf8, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf8, 0x7f, + 0xff, 0xf9, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0xff, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xe0, 0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0xff, 0xc0, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x1f, 0xf0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0xff, 0xc0, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xe0, 0x3f, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1f, 0xff, 0xf9, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, + 0xff, 0xf0, 0x1f, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xfd, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, + 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, + 0xef, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x00, + 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, + 0x00, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8, + 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x60, 0x00, + 0x00, 0x00, 0x03, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, + 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, + 0xff, 0xe0, 0x07, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xe0, 0x07, 0xff, 0xff, + 0x80, 0x1f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xc0, 0x07, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xf8, + 0x0f, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x07, 0xff, 0x80, 0x0f, 0xff, + 0xff, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0x80, 0x1f, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, + 0xf0, 0x01, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xf0, 0x1f, + 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xf8, 0x7f, 0xff, + 0xff, 0xe0, 0x03, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xfe, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, + 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xfe, 0x00, 0x7f, 0xff, 0x80, 0x03, 0xff, + 0xff, 0xff, 0xf8, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfe, + 0x00, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xe0, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x01, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xfc, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x03, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, + 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, + 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, + 0xbf, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xe0, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0x9f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xe7, 0xff, 0xff, 0xe7, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x73, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff }; @@ -761,256 +663,79 @@ const unsigned char epd_icons_ocean_logo [] PROGMEM = { // // 'braiins_pool', 122x250px const unsigned char epd_icons_braiins_logo [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xc0, 0x38, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0x7c, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x00, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x01, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x01, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x1f, 0x01, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x0c, 0x01, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x01, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x03, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x03, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x07, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x0f, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x1f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x3f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0x00, 0x00, 0x01, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x03, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0x00, 0x03, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x07, 0xff, 0x81, 0xff, 0xe0, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x07, 0xfe, 0x00, 0x7f, 0xe0, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x1f, 0xf0, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x00, 0x0f, 0xf0, 0x00, 0x0f, 0xf0, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x0f, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xc0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x06, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0x00, 0x0f, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x00, 0x03, 0xff, 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0x00, 0x07, 0xff, 0x00, 0xff, 0xe0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x03, 0xff, 0x00, 0x0f, 0xfc, 0x00, 0x3f, 0xe0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x1f, 0xf0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xf0, 0x00, 0x0f, 0xf0, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xfc, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xc0, 0x00, 0x03, 0xf0, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x07, 0x80, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x03, 0xff, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xf3, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0x0f, 0xff, 0xc3, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xfe, 0x00, 0x07, 0xff, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x03, 0xfc, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x01, 0xf0, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x00, 0xc0, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x1f, 0x80, 0x00, 0x07, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xc0, 0x00, 0x1f, 0xff, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x00, 0x7f, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x01, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x0f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x3f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xe0, 0x7f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0xc0, 0x3f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x00, 0xfc, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x38, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf8, 0x00, 0x10, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x7c, 0x03, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x1e, 0x00, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x3f, 0x80, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 +// 'braiins-vector-logo', 37x230px + 0xff, 0xff, 0xf8, 0x0f, 0xf8, 0xff, 0xff, 0xe0, 0x01, 0xf8, 0x01, 0xff, 0xc0, 0x00, 0xf8, 0x01, + 0xff, 0x80, 0x00, 0x78, 0x01, 0xff, 0x00, 0x00, 0x38, 0x01, 0xff, 0x00, 0x00, 0x18, 0x01, 0xfe, + 0x00, 0x00, 0x18, 0x01, 0xfe, 0x00, 0x00, 0x18, 0x01, 0xfe, 0x01, 0xf0, 0x08, 0x01, 0xfc, 0x03, + 0xf8, 0x08, 0x01, 0xfc, 0x03, 0xf8, 0x08, 0x01, 0xfc, 0x07, 0xf8, 0x08, 0x01, 0xfc, 0x07, 0xfc, + 0x08, 0x01, 0xfc, 0x07, 0xfc, 0x08, 0x01, 0xfc, 0x07, 0xfc, 0x08, 0x01, 0xf8, 0x07, 0xf8, 0x08, + 0x00, 0xf8, 0x07, 0xf8, 0x08, 0x80, 0xf0, 0x07, 0xf8, 0x08, 0x80, 0x00, 0x0f, 0xf0, 0x08, 0x80, + 0x00, 0x0f, 0xf0, 0x08, 0xc0, 0x00, 0x0f, 0xe0, 0x18, 0xc0, 0x00, 0x1f, 0xc0, 0x18, 0xe0, 0x00, + 0x3f, 0xc0, 0x38, 0xf0, 0x00, 0x3f, 0xe0, 0x38, 0xf8, 0x00, 0xff, 0xf0, 0x78, 0xfe, 0x03, 0xff, + 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xfd, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0x00, + 0x08, 0xff, 0xff, 0xfc, 0x00, 0x08, 0xff, 0xff, 0xf0, 0x00, 0x38, 0xff, 0xff, 0xc0, 0x00, 0xf8, + 0xff, 0xff, 0x00, 0x03, 0xf8, 0xff, 0xfc, 0x00, 0x07, 0xf8, 0xff, 0xf0, 0x00, 0x1f, 0xf8, 0xff, + 0xc0, 0x00, 0x7f, 0xf8, 0xff, 0x80, 0x01, 0xff, 0xf8, 0xfe, 0x00, 0x03, 0xff, 0xf8, 0xf8, 0x00, + 0x0f, 0xff, 0xf8, 0xf0, 0x00, 0x3f, 0xff, 0xf8, 0xc0, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x03, 0xff, + 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf8, + 0x08, 0xff, 0xff, 0xff, 0x80, 0x08, 0xff, 0xff, 0xfc, 0x00, 0x08, 0xff, 0xff, 0xc0, 0x00, 0x08, + 0xff, 0xfe, 0x00, 0x00, 0x08, 0xff, 0xf0, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x08, 0xf8, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, + 0x03, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, + 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0x08, 0xff, + 0xff, 0xff, 0x80, 0x08, 0xff, 0xff, 0xf8, 0x00, 0x08, 0xff, 0xff, 0xc0, 0x00, 0x08, 0xff, 0xfe, + 0x00, 0x00, 0x08, 0xff, 0xf0, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x03, 0xff, + 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xf8, + 0x00, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0x08, 0xff, 0xff, 0xff, + 0x00, 0x08, 0xff, 0xff, 0xf8, 0x00, 0x08, 0xff, 0xff, 0xc0, 0x00, 0x08, 0xff, 0xfe, 0x00, 0x00, + 0x08, 0xff, 0xe0, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x08, 0xf0, 0x00, 0x00, 0x00, 0x08, + 0x80, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x07, 0xff, 0xf8, 0x00, + 0x00, 0x3f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0xff, + 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, + 0xff, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, + 0xf8, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0xe0, 0x00, 0x00, 0x03, 0xf8, 0xfc, 0x00, 0x00, 0x00, 0x08, + 0xff, 0xc0, 0x00, 0x00, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x08, 0xff, 0xff, 0x00, 0x00, 0x08, 0xff, + 0xff, 0xf0, 0x00, 0x08, 0xff, 0xff, 0xfe, 0x00, 0x08, 0xff, 0xff, 0xff, 0xc0, 0x08, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xe8, + 0xfe, 0x00, 0xff, 0xff, 0x88, 0xf0, 0x00, 0x3f, 0xfe, 0x08, 0xe0, 0x00, 0x1f, 0xf8, 0x08, 0xc0, + 0x00, 0x0f, 0xe0, 0x08, 0xc0, 0x00, 0x07, 0x80, 0x08, 0x80, 0x00, 0x06, 0x00, 0x08, 0x80, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0xfe, 0x00, 0x00, 0x38, 0x01, 0xff, 0x00, + 0x00, 0xf8, 0x01, 0xff, 0x00, 0x03, 0xf8, 0x01, 0xff, 0x00, 0x0f, 0xf8, 0x01, 0xff, 0x00, 0x3f, + 0xf8, 0x01, 0xff, 0x00, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, + 0x01, 0xff, 0x01, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xbf, 0xf8, 0xff, 0xff, + 0xf8, 0x03, 0xf8, 0xf8, 0x07, 0xf0, 0x00, 0xf8, 0xf0, 0x01, 0xe0, 0x00, 0x78, 0xc0, 0x00, 0xc0, + 0x00, 0x38, 0xc0, 0x00, 0x80, 0x00, 0x18, 0x80, 0x00, 0x00, 0x00, 0x18, 0x80, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0xf0, 0x08, 0x00, 0xfc, 0x07, 0xf8, 0x08, + 0x01, 0xfc, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, + 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, + 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, + 0xf8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x03, + 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xf8 }; const unsigned char epd_icons_noderunners_logo [] PROGMEM = { diff --git a/src/lib/config.cpp b/src/lib/config.cpp index d7f403f..04e9a60 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -359,68 +359,6 @@ String replaceAmbiguousChars(String input) return input; } -// void addCurrencyMappings(const std::vector& currencies) -// { -// for (const auto& currency : currencies) -// { -// int satsPerCurrencyScreen; -// int btcTickerScreen; -// int marketCapScreen; - -// // Determine the corresponding screen IDs based on the currency code -// if (currency == "USD") -// { -// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_USD; -// btcTickerScreen = SCREEN_BTC_TICKER_USD; -// marketCapScreen = SCREEN_MARKET_CAP_USD; -// } -// else if (currency == "EUR") -// { -// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_EUR; -// btcTickerScreen = SCREEN_BTC_TICKER_EUR; -// marketCapScreen = SCREEN_MARKET_CAP_EUR; -// } -// else if (currency == "GBP") -// { -// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_GBP; -// btcTickerScreen = SCREEN_BTC_TICKER_GBP; -// marketCapScreen = SCREEN_MARKET_CAP_GBP; -// } -// else if (currency == "JPY") -// { -// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_JPY; -// btcTickerScreen = SCREEN_BTC_TICKER_JPY; -// marketCapScreen = SCREEN_MARKET_CAP_JPY; -// } -// else if (currency == "AUD") -// { -// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_AUD; -// btcTickerScreen = SCREEN_BTC_TICKER_AUD; -// marketCapScreen = SCREEN_MARKET_CAP_AUD; -// } -// else if (currency == "CAD") -// { -// satsPerCurrencyScreen = SCREEN_SATS_PER_CURRENCY_CAD; -// btcTickerScreen = SCREEN_BTC_TICKER_CAD; -// marketCapScreen = SCREEN_MARKET_CAP_CAD; -// } -// else -// { -// continue; // Unknown currency, skip it -// } - -// // Create the string locally to ensure it persists -// std::string satsPerCurrencyString = "Sats per " + currency; -// std::string btcTickerString = "Ticker " + currency; -// std::string marketCapString = "Market Cap " + currency; - -// // Pass the c_str() to the function -// addScreenMapping(satsPerCurrencyScreen, satsPerCurrencyString.c_str()); -// addScreenMapping(btcTickerScreen, btcTickerString.c_str()); -// addScreenMapping(marketCapScreen, marketCapString.c_str()); -// } -// } - void setupWebsocketClients(void *pvParameters) { if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) @@ -823,19 +761,3 @@ const char* getWebUiFilename() { return "littlefs_4MB.bin"; } } - - -// void loadIcons() { -// size_t ocean_logo_size = 886; - -// int iUncompSize = zt.gzip_info((uint8_t *)epd_compress_bitaxe, ocean_logo_size); -// Serial.printf("uncompressed size = %d\n", iUncompSize); - -// uint8_t *pUncompressed; -// pUncompressed = (uint8_t *)malloc(iUncompSize+4); -// int rc = zt.gunzip((uint8_t *)epd_compress_bitaxe, ocean_logo_size, pUncompressed); - -// if (rc == ZT_SUCCESS) { -// Serial.println("Decode success"); -// } -// } \ No newline at end of file diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 062fc74..b91e75c 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -613,8 +613,8 @@ void renderIcon(const uint dispNum, const String &text, bool partial) iconIndex = 2; } else if (text.endsWith("bitaxe")) { - width = 122; - height = 250; + width = 88; + height = 220; iconIndex = 3; } else if (text.endsWith("miningpool")) { diff --git a/src/lib/mining_pool/braiins/brains_pool.cpp b/src/lib/mining_pool/braiins/brains_pool.cpp index 5418829..93f9d37 100644 --- a/src/lib/mining_pool/braiins/brains_pool.cpp +++ b/src/lib/mining_pool/braiins/brains_pool.cpp @@ -26,7 +26,7 @@ PoolStats BraiinsPool::parseResponse(const JsonDocument &doc) const LogoData BraiinsPool::getLogo() const { return LogoData{ .data = epd_icons_allArray[5], - .width = 122, - .height = 250 + .width = 37, + .height = 230 }; } \ No newline at end of file diff --git a/src/lib/mining_pool/braiins/brains_pool.hpp b/src/lib/mining_pool/braiins/brains_pool.hpp index a002c58..e9354e5 100644 --- a/src/lib/mining_pool/braiins/brains_pool.hpp +++ b/src/lib/mining_pool/braiins/brains_pool.hpp @@ -2,6 +2,7 @@ #include "lib/mining_pool/mining_pool_interface.hpp" #include +#include class BraiinsPool : public MiningPoolInterface { @@ -15,6 +16,4 @@ public: bool hasLogo() const override { return true; } std::string getDisplayLabel() const override { return "BRAIINS/POOL"; } // Fallback if needed std::string getDailyEarningsLabel() const override { return "sats/earned"; } -private: - static int getHashrateMultiplier(const std::string &unit); }; \ No newline at end of file diff --git a/src/lib/mining_pool/mining_pool_stats_handler.cpp b/src/lib/mining_pool/mining_pool_stats_handler.cpp index 4eb8007..ffd9885 100644 --- a/src/lib/mining_pool/mining_pool_stats_handler.cpp +++ b/src/lib/mining_pool/mining_pool_stats_handler.cpp @@ -1,46 +1,19 @@ #include "mining_pool_stats_handler.hpp" -#include -std::array parseMiningPoolStatsHashRate(std::string text, const MiningPoolInterface& pool) +std::array parseMiningPoolStatsHashRate(const std::string& hashrate, const MiningPoolInterface& pool) { std::array ret; ret.fill(""); // Initialize all elements to empty strings - std::string hashrate; std::string label; + std::string output; - if (text.length() > 21) { - // We are massively future-proof!! - label = "ZH/S"; - hashrate = text.substr(0, text.length() - 21); - } else if (text.length() > 18) { - label = "EH/S"; - hashrate = text.substr(0, text.length() - 18); - } else if (text.length() > 15) { - label = "PH/S"; - hashrate = text.substr(0, text.length() - 15); - } else if (text.length() > 12) { - label = "TH/S"; - hashrate = text.substr(0, text.length() - 12); - } else if (text.length() > 9) { - label = "GH/S"; - hashrate = text.substr(0, text.length() - 9); - } else if (text.length() > 6) { - label = "MH/S"; - hashrate = text.substr(0, text.length() - 6); - } else if (text.length() > 3) { - label = "KH/S"; - hashrate = text.substr(0, text.length() - 3); - } else { - label = "H/S"; - hashrate = text; - } - - std::size_t textLength = hashrate.length(); + parseHashrateString(hashrate, label, output, 4); + std::size_t textLength = output.length(); // Calculate the position where the digits should start // Account for the position of the mining pool logo and the hashrate label std::size_t startIndex = NUM_SCREENS - 1 - textLength; - + // Insert the pickaxe icon just before the digits if (startIndex > 0) { @@ -50,7 +23,7 @@ std::array parseMiningPoolStatsHashRate(std::string te // Place the digits for (std::size_t i = 0; i < textLength; ++i) { - ret[startIndex + i] = hashrate.substr(i, 1); + ret[startIndex + i] = output.substr(i, 1); } ret[NUM_SCREENS - 1] = label; diff --git a/src/lib/mining_pool/mining_pool_stats_handler.hpp b/src/lib/mining_pool/mining_pool_stats_handler.hpp index a284bc7..dcbc1db 100644 --- a/src/lib/mining_pool/mining_pool_stats_handler.hpp +++ b/src/lib/mining_pool/mining_pool_stats_handler.hpp @@ -1,8 +1,11 @@ #include #include +#include +#include + #ifndef UNITY_TEST #include "lib/mining_pool/mining_pool_interface.hpp" #endif -std::array parseMiningPoolStatsHashRate(std::string text, const MiningPoolInterface& pool); +std::array parseMiningPoolStatsHashRate(const std::string& hashrate, const MiningPoolInterface& pool); std::array parseMiningPoolStatsDailyEarnings(int sats, std::string label, const MiningPoolInterface& pool); diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.cpp b/src/lib/mining_pool/noderunners/noderunners_pool.cpp index 495ffd9..0f55180 100644 --- a/src/lib/mining_pool/noderunners/noderunners_pool.cpp +++ b/src/lib/mining_pool/noderunners/noderunners_pool.cpp @@ -15,9 +15,13 @@ PoolStats NoderunnersPool::parseResponse(const JsonDocument& doc) const { std::string value = hashrateStr.substr(0, hashrateStr.size() - 1); int multiplier = getHashrateMultiplier(unit); + double hashrate = std::stod(value) * std::pow(10, multiplier); + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.0f", hashrate); return PoolStats{ - .hashrate = value + std::string(multiplier, '0'), + .hashrate = buffer, .dailyEarnings = std::nullopt }; } @@ -28,15 +32,4 @@ LogoData NoderunnersPool::getLogo() const { .width = 122, .height = 122 }; -} - -int NoderunnersPool::getHashrateMultiplier(char unit) { - if (unit == '0') - return 0; - - static const std::unordered_map multipliers = { - {'Z', 21}, {'E', 18}, {'P', 15}, {'T', 12}, - {'G', 9}, {'M', 6}, {'K', 3} - }; - return multipliers.at(unit); -} +} \ No newline at end of file diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.hpp b/src/lib/mining_pool/noderunners/noderunners_pool.hpp index c6cb25c..5ecce53 100644 --- a/src/lib/mining_pool/noderunners/noderunners_pool.hpp +++ b/src/lib/mining_pool/noderunners/noderunners_pool.hpp @@ -3,6 +3,7 @@ #include "lib/mining_pool/mining_pool_interface.hpp" #include +#include class NoderunnersPool : public MiningPoolInterface { public: @@ -16,7 +17,4 @@ public: std::string getDailyEarningsLabel() const override { return ""; } bool hasLogo() const override { return true; } std::string getDisplayLabel() const override { return "NODE/RUNNERS"; } // Fallback if needed - -protected: - static int getHashrateMultiplier(char unit); }; \ No newline at end of file diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index b263edb..2f9c10b 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -47,6 +47,7 @@ void taskMiningPoolStatsFetch(void *pvParameters) deserializeJson(doc, payload); PoolStats stats = poolInterface->parseResponse(doc); + miningPoolStatsHashrate = stats.hashrate; if (stats.dailyEarnings) diff --git a/test/test_mining_pool/test_main.cpp b/test/test_mining_pool/test_main.cpp new file mode 100644 index 0000000..f028aba --- /dev/null +++ b/test/test_mining_pool/test_main.cpp @@ -0,0 +1,75 @@ +#include +#include + +void test_parseMiningPoolStatsHashRate1dot34TH(void) +{ + std::string hashrate; + std::string label; + std::string output; + + parseHashrateString("1340000000000", label, output, 4); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("TH/S", label.c_str(), label.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("1.34", output.c_str(), output.c_str()); +} + +void test_parseMiningPoolStatsHashRate645GH(void) +{ + std::string hashrate = "645000000000"; + std::string label; + std::string output; + + parseHashrateString(hashrate, label, output, 4); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("GH/S", label.c_str(), label.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("645", output.c_str(), output.c_str()); +} + +void test_parseMiningPoolStatsHashRateEmpty(void) +{ + std::string hashrate = ""; + std::string label; + std::string output; + + parseHashrateString(hashrate, label, output, 4); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("H/S", label.c_str(), label.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output.c_str(), output.c_str()); +} + +void test_parseMiningPoolStatsHashRateZero(void) +{ + std::string hashrate = "0"; + std::string label; + std::string output; + + parseHashrateString(hashrate, label, output, 4); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("H/S", label.c_str(), label.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output.c_str(), output.c_str()); +} + + + + +// not needed when using generate_test_runner.rb +int runUnityTests(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_parseMiningPoolStatsHashRate1dot34TH); + RUN_TEST(test_parseMiningPoolStatsHashRate645GH); + RUN_TEST(test_parseMiningPoolStatsHashRateZero); + RUN_TEST(test_parseMiningPoolStatsHashRateEmpty); + + return UNITY_END(); +} + +int main(void) +{ + return runUnityTests(); +} + +extern "C" void app_main() +{ + runUnityTests(); +} From fb70d435a9e97e66aefc5d8320fe4d05d57c3f6f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 20 Dec 2024 23:02:54 +0100 Subject: [PATCH 130/188] Get mining pool logos by download --- platformio.ini | 227 +++++---- scripts/extra_script.py | 69 ++- src/icons/icons.cpp | 465 +----------------- src/lib/block_notify.cpp | 56 +-- src/lib/config.cpp | 8 +- src/lib/config.hpp | 1 - src/lib/defaults.hpp | 2 + src/lib/epd.cpp | 18 +- src/lib/epd.hpp | 2 +- src/lib/mining_pool/braiins/brains_pool.cpp | 14 +- src/lib/mining_pool/braiins/brains_pool.hpp | 16 +- .../mining_pool/gobrrr_pool/gobrrr_pool.cpp | 10 +- .../mining_pool/gobrrr_pool/gobrrr_pool.hpp | 17 +- src/lib/mining_pool/logo_data.hpp | 3 +- src/lib/mining_pool/mining_pool_interface.cpp | 18 + src/lib/mining_pool/mining_pool_interface.hpp | 14 +- .../noderunners/noderunners_pool.cpp | 8 - .../noderunners/noderunners_pool.hpp | 17 +- src/lib/mining_pool/ocean/ocean_pool.cpp | 8 - src/lib/mining_pool/ocean/ocean_pool.hpp | 17 +- src/lib/mining_pool/pool_factory.cpp | 109 ++++ src/lib/mining_pool/pool_factory.hpp | 12 + src/lib/mining_pool_stats_fetch.cpp | 51 +- src/lib/shared.cpp | 30 +- src/lib/shared.hpp | 16 +- src/lib/webserver.cpp | 7 +- 26 files changed, 547 insertions(+), 668 deletions(-) create mode 100644 src/lib/mining_pool/mining_pool_interface.cpp diff --git a/platformio.ini b/platformio.ini index 81d4a49..be29cbe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -7,119 +7,137 @@ ; ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html + [platformio] data_dir = data/build_gz default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd, btclock_v8_213epd [env] - [btclock_base] platform = espressif32 @ ^6.9.0 -framework = arduino, espidf +framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize board_build.filesystem = littlefs extra_scripts = pre:scripts/pre_script.py, post:scripts/extra_script.py +platform_packages = + earlephilhower/tool-mklittlefs-rp2040-earlephilhower board_build.embed_files = - x509_crt_bundle -build_flags = - !python scripts/git_rev.py - -DLAST_BUILD_TIME=$UNIX_TIME - -DARDUINO_USB_CDC_ON_BOOT - -DCORE_DEBUG_LEVEL=0 - -fexceptions + x509_crt_bundle +build_flags = + !python scripts/git_rev.py + -DLAST_BUILD_TIME=$UNIX_TIME + -DARDUINO_USB_CDC_ON_BOOT + -DCORE_DEBUG_LEVEL=0 + -fexceptions build_unflags = - -Werror=all - -fno-exceptions + -Werror=all + -fno-exceptions lib_deps = - https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.2.1 - mathieucarbou/ESPAsyncWebServer @ 3.3.23 - robtillaart/MCP23017@^0.8.0 - adafruit/Adafruit NeoPixel@^1.12.3 - https://github.com/dsbaars/universal_pin#feature/mcp23017_rt - https://github.com/dsbaars/GxEPD2#universal_pin - https://github.com/tzapu/WiFiManager.git#v2.0.17 - rblb/Nostrduino@1.2.8 + https://github.com/joltwallet/esp_littlefs.git + bblanchon/ArduinoJson@^7.2.1 + mathieucarbou/ESPAsyncWebServer @ 3.3.23 + robtillaart/MCP23017@^0.8.0 + adafruit/Adafruit NeoPixel@^1.12.3 + https://github.com/dsbaars/universal_pin#feature/mcp23017_rt + https://github.com/dsbaars/GxEPD2#universal_pin + https://github.com/tzapu/WiFiManager.git#v2.0.17 + rblb/Nostrduino@1.2.8 [env:lolin_s3_mini] extends = btclock_base board = lolin_s3_mini board_build.partitions = partition.csv -build_flags = - ${btclock_base.build_flags} - -D MCP_INT_PIN=8 - -D NEOPIXEL_PIN=34 - -D NEOPIXEL_COUNT=4 - -D NUM_SCREENS=7 - -D I2C_SDA_PIN=35 - -D I2C_SCK_PIN=36 - -DARDUINO_USB_CDC_ON_BOOT=1 - -D IS_HW_REV_A +build_flags = + ${btclock_base.build_flags} + -D MCP_INT_PIN=8 + -D NEOPIXEL_PIN=34 + -D NEOPIXEL_COUNT=4 + -D NUM_SCREENS=7 + -D I2C_SDA_PIN=35 + -D I2C_SCK_PIN=36 + -DARDUINO_USB_CDC_ON_BOOT=1 + -D IS_HW_REV_A build_unflags = - ${btclock_base.build_unflags} - + ${btclock_base.build_unflags} +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:btclock_rev_b] extends = btclock_base board = btclock_rev_b board_build.partitions = partition_8mb.csv -build_flags = - ${btclock_base.build_flags} - -D MCP_INT_PIN=8 - -D NEOPIXEL_PIN=15 - -D NEOPIXEL_COUNT=4 - -D NUM_SCREENS=7 - -D I2C_SDA_PIN=35 - -D I2C_SCK_PIN=36 - -D HAS_FRONTLIGHT - -D PCA_OE_PIN=45 - -D PCA_I2C_ADDR=0x42 - -D IS_HW_REV_B +build_flags = + ${btclock_base.build_flags} + -D MCP_INT_PIN=8 + -D NEOPIXEL_PIN=15 + -D NEOPIXEL_COUNT=4 + -D NUM_SCREENS=7 + -D I2C_SDA_PIN=35 + -D I2C_SCK_PIN=36 + -D HAS_FRONTLIGHT + -D PCA_OE_PIN=45 + -D PCA_I2C_ADDR=0x42 + -D IS_HW_REV_B lib_deps = - ${btclock_base.lib_deps} - robtillaart/PCA9685@^0.7.1 - claws/BH1750@^1.3.0 + ${btclock_base.lib_deps} + robtillaart/PCA9685@^0.7.1 + claws/BH1750@^1.3.0 build_unflags = - ${btclock_base.build_unflags} + ${btclock_base.build_unflags} +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:lolin_s3_mini_213epd] extends = env:lolin_s3_mini test_framework = unity -build_flags = - ${env:lolin_s3_mini.build_flags} - -D USE_QR - -D VERSION_EPD_2_13 - -D HW_REV=\"REV_A_EPD_2_13\" - +build_flags = + ${env:lolin_s3_mini.build_flags} + -D USE_QR + -D VERSION_EPD_2_13 + -D HW_REV=\"REV_A_EPD_2_13\" +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:btclock_rev_b_213epd] extends = env:btclock_rev_b test_framework = unity -build_flags = - ${env:btclock_rev_b.build_flags} - -D USE_QR - -D VERSION_EPD_2_13 - -D HW_REV=\"REV_B_EPD_2_13\" +build_flags = + ${env:btclock_rev_b.build_flags} + -D USE_QR + -D VERSION_EPD_2_13 + -D HW_REV=\"REV_B_EPD_2_13\" +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:lolin_s3_mini_29epd] extends = env:lolin_s3_mini test_framework = unity -build_flags = - ${env:lolin_s3_mini.build_flags} - -D USE_QR - -D VERSION_EPD_2_9 - -D HW_REV=\"REV_A_EPD_2_9\" +build_flags = + ${env:lolin_s3_mini.build_flags} + -D USE_QR + -D VERSION_EPD_2_9 + -D HW_REV=\"REV_A_EPD_2_9\" +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:btclock_rev_b_29epd] extends = env:btclock_rev_b test_framework = unity -build_flags = - ${env:btclock_rev_b.build_flags} - -D USE_QR - -D VERSION_EPD_2_9 - -D HW_REV=\"REV_B_EPD_2_9\" +build_flags = + ${env:btclock_rev_b.build_flags} + -D USE_QR + -D VERSION_EPD_2_9 + -D HW_REV=\"REV_B_EPD_2_9\" +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:btclock_v8] extends = btclock_base @@ -127,43 +145,52 @@ board = btclock_v8 board_build.partitions = partition_16mb.csv board_build.flash_mode = qio test_framework = unity -build_flags = - ${btclock_base.build_flags} - -D MCP_INT_PIN=4 - -D NEOPIXEL_PIN=5 - -D NEOPIXEL_COUNT=4 - -D NUM_SCREENS=8 - -D SPI_SDA_PIN=11 - -D SPI_SCK_PIN=12 - -D I2C_SDA_PIN=1 - -D I2C_SCK_PIN=2 - -D MCP_RESET_PIN=21 - -D MCP1_A0_PIN=6 - -D MCP1_A1_PIN=7 - -D MCP1_A2_PIN=8 - -D MCP2_A0_PIN=9 - -D MCP2_A1_PIN=10 - -D MCP2_A2_PIN=14 +build_flags = + ${btclock_base.build_flags} + -D MCP_INT_PIN=4 + -D NEOPIXEL_PIN=5 + -D NEOPIXEL_COUNT=4 + -D NUM_SCREENS=8 + -D SPI_SDA_PIN=11 + -D SPI_SCK_PIN=12 + -D I2C_SDA_PIN=1 + -D I2C_SCK_PIN=2 + -D MCP_RESET_PIN=21 + -D MCP1_A0_PIN=6 + -D MCP1_A1_PIN=7 + -D MCP1_A2_PIN=8 + -D MCP2_A0_PIN=9 + -D MCP2_A1_PIN=10 + -D MCP2_A2_PIN=14 build_unflags = - ${btclock_base.build_unflags} + ${btclock_base.build_unflags} +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:btclock_v8_213epd] extends = env:btclock_v8 test_framework = unity -build_flags = - ${env:btclock_v8.build_flags} - -D USE_QR - -D VERSION_EPD_2_13 - -D HW_REV=\"REV_V8_EPD_2_13\" +build_flags = + ${env:btclock_v8.build_flags} + -D USE_QR + -D VERSION_EPD_2_13 + -D HW_REV=\"REV_V8_EPD_2_13\" +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 [env:native_test_only] platform = native test_framework = unity -build_flags = - ${btclock_base.build_flags} - -D MCP_INT_PIN=8 - -D NEOPIXEL_PIN=34 - -D NEOPIXEL_COUNT=4 - -D NUM_SCREENS=7 - -D UNITY_TEST - -std=gnu++17 +build_flags = + ${btclock_base.build_flags} + -D MCP_INT_PIN=8 + -D NEOPIXEL_PIN=34 + -D NEOPIXEL_COUNT=4 + -D NUM_SCREENS=7 + -D UNITY_TEST + -std=gnu++17 +platform_packages = + platformio/tool-mklittlefs@^1.203.210628 + earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 diff --git a/scripts/extra_script.py b/scripts/extra_script.py index 8451978..ebc94e9 100644 --- a/scripts/extra_script.py +++ b/scripts/extra_script.py @@ -1,7 +1,7 @@ Import("env") import os import gzip -from shutil import copyfileobj, rmtree +from shutil import copyfileobj, rmtree, copyfile, copytree from pathlib import Path import subprocess @@ -29,7 +29,7 @@ def process_directory(input_dir, output_dir): Path(output_root).mkdir(parents=True, exist_ok=True) for file in files: - # if file.endswith(('.html', '.css', '.js')): +# if not file.endswith(('.bin')): 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) @@ -41,11 +41,72 @@ def process_directory(input_dir, output_dir): # 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' +# copytree("assets", "data/dist/assets") + process_directory(input_directory, output_directory) +def get_fs_partition_size(env): + import csv + + # Get partition table path - first try custom, then default + board_config = env.BoardConfig() + partition_table = board_config.get("build.partitions", "default.csv") + + # Handle default partition table path + if partition_table == "default.csv" or partition_table == "huge_app.csv": + partition_table = os.path.join(env.PioPlatform().get_package_dir("framework-arduinoespressif32"), + "tools", "partitions", partition_table) + + # Parse CSV to find spiffs/littlefs partition + with open(partition_table, 'r') as f: + for row in csv.reader(f): + if len(row) < 5: + continue + # Remove comments and whitespace + row = [cell.strip().split('#')[0] for cell in row] + # Check if this is a spiffs or littlefs partition + if row[0].startswith(('spiffs', 'littlefs')): + # Size is in hex format + return int(row[4], 16) + return 0 + +def get_littlefs_used_size(binary_path): + mklittlefs_path = os.path.join(env.PioPlatform().get_package_dir("tool-mklittlefs-rp2040-earlephilhower"), "mklittlefs") + + try: + result = subprocess.run([mklittlefs_path, '-l', binary_path], capture_output=True, text=True) + + if result.returncode == 0: + # Parse the output to sum up file sizes + total_size = 0 + for line in result.stdout.splitlines(): + if line.strip() and not line.startswith('') and not line.startswith('Creation'): + # Each line format: size filename + size = line.split()[0] + total_size += int(size) + return total_size + except Exception as e: + print(f"Error getting filesystem size: {e}") + return 0 + + +def after_littlefs(source, target, env): + binary_path = str(target[0]) + partition_size = get_fs_partition_size(env) + used_size = get_littlefs_used_size(binary_path) + + percentage = (used_size / partition_size) * 100 + bar_width = 50 + filled = int(bar_width * percentage / 100) + bar = '=' * filled + '-' * (bar_width - filled) + + print(f"\nLittleFS Actual Usage: [{bar}] {percentage:.1f}% ({used_size}/{partition_size} bytes)") + + flash_size = env.BoardConfig().get("upload.flash_size", "4MB") fs_image_name = f"littlefs_{flash_size}" env.Replace(ESP32_FS_IMAGE_NAME=fs_image_name) @@ -58,3 +119,7 @@ fs_name = env.get("ESP32_FS_IMAGE_NAME", "littlefs.bin") # Use the variable in the pre-action env.AddPreAction(f"$BUILD_DIR/{fs_name}.bin", before_buildfs) +env.AddPostAction(f"$BUILD_DIR/{fs_name}.bin", after_littlefs) +# LittleFS Actual Usage: [==============================--------------------] 60.4% (254165/420864 bytes) +# LittleFS Actual Usage: [==============================--------------------] 60.2% (253476/420864 bytes) +# 372736 used \ No newline at end of file diff --git a/src/icons/icons.cpp b/src/icons/icons.cpp index 55854f8..d9ae65c 100644 --- a/src/icons/icons.cpp +++ b/src/icons/icons.cpp @@ -532,472 +532,11 @@ const unsigned char epd_icons_bitaxe_logo [] PROGMEM = { 0xff, 0xff, 0xff, 0xff }; - -// // 'ocean_logo', 122x250px -const unsigned char epd_icons_ocean_logo [] PROGMEM = { -// 'ocean_logo', 122x122px -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x20, 0x00, 0x0f, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xfc, 0x00, 0x3f, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xfc, 0x00, 0x7f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xfe, 0x00, 0x7f, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xc0, -0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xc0, -0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, -0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, -0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, -0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, -0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, -0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, -0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xc0, -0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xc0, -0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xc0, -0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xc0, -0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xc0, -0xfc, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xc0, -0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xc0, -0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xc0, -0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x03, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xe0, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x03, 0xff, 0xff, 0xe1, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xf0, 0x07, 0xc0, -0xf8, 0x03, 0xe0, 0x01, 0xff, 0xff, 0xe3, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xff, 0xf0, 0x07, 0xc0, -0xf8, 0x07, 0xf8, 0x00, 0xff, 0xff, 0xe3, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x07, 0xc0, -0xf8, 0x07, 0xfc, 0x00, 0x7f, 0xff, 0xe3, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xc0, -0xf8, 0x07, 0xfe, 0x00, 0x3f, 0xff, 0xe7, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xc0, -0xf8, 0x07, 0xff, 0x80, 0x1f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xc0, -0xfc, 0x0f, 0xff, 0xc0, 0x0f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xc0, -0xfc, 0x0f, 0xff, 0xe0, 0x0f, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xc0, -0xfc, 0x0f, 0xff, 0xf8, 0x07, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xc0, -0xfe, 0x0f, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xc0, -0xfe, 0x1f, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xc0, -0xff, 0x1f, 0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xc0, -0xff, 0x1f, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xc0, -0xff, 0x9f, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xc0, -0xff, 0xdf, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xdf, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xfe, 0xff, 0xc0, -0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xfe, 0x7f, 0xc0, -0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xff, 0xff, 0xfe, 0x7f, 0xc0, -0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xfe, 0x3f, 0xc0, -0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xfc, 0x1f, 0xc0, -0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xfc, 0x1f, 0xc0, -0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xfc, 0x1f, 0xc0, -0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfc, 0x01, 0xff, 0xfc, 0x0f, 0xc0, -0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xfc, 0x00, 0xff, 0xfc, 0x0f, 0xc0, -0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xfe, 0x00, 0x7f, 0xf8, 0x07, 0xc0, -0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xf9, 0xff, 0xff, 0x00, 0x1f, 0xf8, 0x07, 0xc0, -0xf8, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xf1, 0xff, 0xff, 0x80, 0x0f, 0xf8, 0x07, 0xc0, -0xf8, 0x07, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xf1, 0xff, 0xff, 0xc0, 0x07, 0xf0, 0x07, 0xc0, -0xf8, 0x03, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0xf1, 0xff, 0xff, 0xe0, 0x01, 0xf0, 0x07, 0xc0, -0xf8, 0x03, 0xff, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x01, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x30, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x00, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0x80, 0x00, 0x07, 0xc0, -0xf8, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xc0, -0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x0f, 0xc0, -0xfc, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xc0, -0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xc0, -0xfe, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xc0, -0xfe, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xc0, -0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xc0, -0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xc0, -0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xc0, -0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0, -0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0, -0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0, -0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0, -0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0, -0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xc0, -0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xc0, -0xff, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0x80, 0x1f, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0x80, 0x0f, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xff, 0x00, 0x0f, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3c, 0x00, 0x03, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 -}; - - -// // 'braiins_pool', 122x250px -const unsigned char epd_icons_braiins_logo [] PROGMEM = { -// 'braiins-vector-logo', 37x230px - 0xff, 0xff, 0xf8, 0x0f, 0xf8, 0xff, 0xff, 0xe0, 0x01, 0xf8, 0x01, 0xff, 0xc0, 0x00, 0xf8, 0x01, - 0xff, 0x80, 0x00, 0x78, 0x01, 0xff, 0x00, 0x00, 0x38, 0x01, 0xff, 0x00, 0x00, 0x18, 0x01, 0xfe, - 0x00, 0x00, 0x18, 0x01, 0xfe, 0x00, 0x00, 0x18, 0x01, 0xfe, 0x01, 0xf0, 0x08, 0x01, 0xfc, 0x03, - 0xf8, 0x08, 0x01, 0xfc, 0x03, 0xf8, 0x08, 0x01, 0xfc, 0x07, 0xf8, 0x08, 0x01, 0xfc, 0x07, 0xfc, - 0x08, 0x01, 0xfc, 0x07, 0xfc, 0x08, 0x01, 0xfc, 0x07, 0xfc, 0x08, 0x01, 0xf8, 0x07, 0xf8, 0x08, - 0x00, 0xf8, 0x07, 0xf8, 0x08, 0x80, 0xf0, 0x07, 0xf8, 0x08, 0x80, 0x00, 0x0f, 0xf0, 0x08, 0x80, - 0x00, 0x0f, 0xf0, 0x08, 0xc0, 0x00, 0x0f, 0xe0, 0x18, 0xc0, 0x00, 0x1f, 0xc0, 0x18, 0xe0, 0x00, - 0x3f, 0xc0, 0x38, 0xf0, 0x00, 0x3f, 0xe0, 0x38, 0xf8, 0x00, 0xff, 0xf0, 0x78, 0xfe, 0x03, 0xff, - 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xfd, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, - 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, - 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, - 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0x00, - 0x08, 0xff, 0xff, 0xfc, 0x00, 0x08, 0xff, 0xff, 0xf0, 0x00, 0x38, 0xff, 0xff, 0xc0, 0x00, 0xf8, - 0xff, 0xff, 0x00, 0x03, 0xf8, 0xff, 0xfc, 0x00, 0x07, 0xf8, 0xff, 0xf0, 0x00, 0x1f, 0xf8, 0xff, - 0xc0, 0x00, 0x7f, 0xf8, 0xff, 0x80, 0x01, 0xff, 0xf8, 0xfe, 0x00, 0x03, 0xff, 0xf8, 0xf8, 0x00, - 0x0f, 0xff, 0xf8, 0xf0, 0x00, 0x3f, 0xff, 0xf8, 0xc0, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x03, 0xff, - 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, - 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, - 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, - 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf8, - 0x08, 0xff, 0xff, 0xff, 0x80, 0x08, 0xff, 0xff, 0xfc, 0x00, 0x08, 0xff, 0xff, 0xc0, 0x00, 0x08, - 0xff, 0xfe, 0x00, 0x00, 0x08, 0xff, 0xf0, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x08, 0xf8, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, - 0x03, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, - 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, - 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0x08, 0xff, - 0xff, 0xff, 0x80, 0x08, 0xff, 0xff, 0xf8, 0x00, 0x08, 0xff, 0xff, 0xc0, 0x00, 0x08, 0xff, 0xfe, - 0x00, 0x00, 0x08, 0xff, 0xf0, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x08, 0x00, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x03, 0xff, - 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xf8, - 0x00, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, - 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, - 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0x08, 0xff, 0xff, 0xff, - 0x00, 0x08, 0xff, 0xff, 0xf8, 0x00, 0x08, 0xff, 0xff, 0xc0, 0x00, 0x08, 0xff, 0xfe, 0x00, 0x00, - 0x08, 0xff, 0xe0, 0x00, 0x00, 0x08, 0xff, 0x00, 0x00, 0x00, 0x08, 0xf0, 0x00, 0x00, 0x00, 0x08, - 0x80, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x07, 0xff, 0xf8, 0x00, - 0x00, 0x3f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0xff, - 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, - 0xff, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, - 0xf8, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0xe0, 0x00, 0x00, 0x03, 0xf8, 0xfc, 0x00, 0x00, 0x00, 0x08, - 0xff, 0xc0, 0x00, 0x00, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x08, 0xff, 0xff, 0x00, 0x00, 0x08, 0xff, - 0xff, 0xf0, 0x00, 0x08, 0xff, 0xff, 0xfe, 0x00, 0x08, 0xff, 0xff, 0xff, 0xc0, 0x08, 0xff, 0xff, - 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, - 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, - 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xe8, - 0xfe, 0x00, 0xff, 0xff, 0x88, 0xf0, 0x00, 0x3f, 0xfe, 0x08, 0xe0, 0x00, 0x1f, 0xf8, 0x08, 0xc0, - 0x00, 0x0f, 0xe0, 0x08, 0xc0, 0x00, 0x07, 0x80, 0x08, 0x80, 0x00, 0x06, 0x00, 0x08, 0x80, 0x00, - 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0xfe, 0x00, 0x00, 0x38, 0x01, 0xff, 0x00, - 0x00, 0xf8, 0x01, 0xff, 0x00, 0x03, 0xf8, 0x01, 0xff, 0x00, 0x0f, 0xf8, 0x01, 0xff, 0x00, 0x3f, - 0xf8, 0x01, 0xff, 0x00, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, - 0x01, 0xff, 0x01, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, 0x01, 0xff, 0x01, 0xff, 0xf8, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, - 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, - 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, - 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xbf, 0xf8, 0xff, 0xff, - 0xf8, 0x03, 0xf8, 0xf8, 0x07, 0xf0, 0x00, 0xf8, 0xf0, 0x01, 0xe0, 0x00, 0x78, 0xc0, 0x00, 0xc0, - 0x00, 0x38, 0xc0, 0x00, 0x80, 0x00, 0x18, 0x80, 0x00, 0x00, 0x00, 0x18, 0x80, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0xf0, 0x08, 0x00, 0xfc, 0x07, 0xf8, 0x08, - 0x01, 0xfc, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, - 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, - 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, 0xf8, 0x08, 0x01, 0xfe, 0x07, - 0xf8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x03, - 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xf8 -}; - -const unsigned char epd_icons_noderunners_logo [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x01, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x01, 0xff, 0x00, 0x01, 0xfb, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x01, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0x80, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x01, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xdf, 0xf0, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xdf, 0xf0, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xcf, 0xf8, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xcf, 0xfc, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xc7, 0xfe, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe7, 0xfe, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe3, 0xff, 0x01, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe1, 0xff, 0x81, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe1, 0xff, 0x81, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0xff, 0xc1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x7f, 0xe1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x3f, 0xe1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x3f, 0xf1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x1f, 0xf9, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x0f, 0xf9, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xe0, 0x0f, 0xfd, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x07, 0xfd, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x03, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x01, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe1, 0xc0, 0x00, 0x3f, 0xe0, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe7, 0xf0, 0x00, 0x3f, 0xe0, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 -}; - -// 'gobrrr', 122x122px -const unsigned char epd_icons_gobrrr_logo [] PROGMEM = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb3, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x10, 0x48, 0x84, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x24, 0x84, 0x20, 0x21, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x82, 0x25, 0x19, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x28, 0x18, 0x20, 0x41, 0x4c, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x83, 0x21, 0x88, 0x84, 0x21, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x8c, 0x48, 0x24, 0x09, 0x24, 0x82, 0x60, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x21, 0x08, 0x80, 0x61, 0x08, 0x18, 0x0a, 0x3f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfc, 0x02, 0x22, 0x19, 0x04, 0x42, 0x41, 0x88, 0x0f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x98, 0x82, 0x42, 0x1b, 0x11, 0x04, 0x21, 0x23, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xe2, 0x20, 0x50, 0x42, 0xff, 0xe4, 0x64, 0x24, 0x83, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xc8, 0x46, 0x14, 0x93, 0xff, 0xf8, 0x01, 0x84, 0x28, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x03, 0x10, 0x81, 0x0f, 0xff, 0xfe, 0x98, 0x11, 0x08, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x24, 0x01, 0x24, 0x5f, 0xff, 0xff, 0x02, 0x42, 0x42, 0x3f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfc, 0x88, 0xa9, 0x04, 0x3f, 0xff, 0xff, 0x90, 0x48, 0x21, 0x0f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x42, 0x22, 0x51, 0xbf, 0xf3, 0xff, 0xc5, 0x09, 0x0c, 0x47, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf3, 0x10, 0x40, 0x00, 0x7f, 0xc0, 0x7f, 0xc4, 0x21, 0x10, 0x93, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe0, 0x25, 0x14, 0xaa, 0xff, 0x0c, 0x1f, 0xc8, 0x84, 0x42, 0x09, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe4, 0x80, 0x04, 0x82, 0xfe, 0x21, 0x0f, 0x82, 0x50, 0x88, 0x63, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x84, 0x4a, 0xc8, 0x11, 0xfc, 0x81, 0x27, 0x21, 0x06, 0x23, 0x00, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x91, 0x10, 0x02, 0x45, 0xfc, 0x18, 0x42, 0x18, 0x3f, 0xf0, 0x18, 0x7f, 0xff, 0xc0, - 0xff, 0xff, 0x10, 0x91, 0x31, 0x13, 0xf9, 0x42, 0x10, 0x82, 0x7f, 0xfc, 0x82, 0x3f, 0xff, 0xc0, - 0xff, 0xfe, 0x46, 0x04, 0x04, 0x43, 0xf8, 0x04, 0x8c, 0x21, 0xff, 0xfe, 0x62, 0x1f, 0xff, 0xc0, - 0xff, 0xfc, 0x00, 0x62, 0x42, 0x13, 0xf8, 0xb0, 0x41, 0x0c, 0xff, 0xff, 0x08, 0xcf, 0xff, 0xc0, - 0xff, 0xfc, 0xa9, 0x08, 0x98, 0x8b, 0xf2, 0x03, 0x10, 0xc3, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xc0, - 0xff, 0xf8, 0x81, 0x04, 0x80, 0x8b, 0xf2, 0x48, 0x26, 0x13, 0xf8, 0x3f, 0xd5, 0x27, 0xff, 0xc0, - 0xff, 0xf2, 0x14, 0x61, 0x24, 0x23, 0xf0, 0x88, 0x80, 0x17, 0xf0, 0x0f, 0xc4, 0x23, 0xff, 0xc0, - 0xff, 0xf0, 0x44, 0x08, 0x05, 0x27, 0xf4, 0x22, 0x17, 0xe7, 0xe5, 0x27, 0xe0, 0x8b, 0xff, 0xc0, - 0xff, 0xe4, 0x41, 0x86, 0x50, 0x43, 0xf1, 0x04, 0x47, 0xf7, 0xc1, 0x0f, 0xe9, 0x01, 0xff, 0xc0, - 0xff, 0xc3, 0x18, 0x30, 0x42, 0x13, 0xf9, 0x31, 0x27, 0xf7, 0xd0, 0x43, 0xe1, 0x31, 0xff, 0xc0, - 0xff, 0xd0, 0x02, 0x01, 0x08, 0x8b, 0xf8, 0x00, 0x8f, 0xef, 0xc6, 0x33, 0xe4, 0x04, 0xff, 0xc0, - 0xff, 0xc4, 0xa0, 0xc9, 0x22, 0x23, 0xfa, 0x4c, 0x17, 0xef, 0xc0, 0x87, 0xf0, 0xc4, 0xff, 0xc0, - 0xff, 0x88, 0x8c, 0x08, 0x21, 0x05, 0xfc, 0x41, 0x47, 0xf7, 0xc8, 0x03, 0xe2, 0x10, 0x7f, 0xc0, - 0xff, 0x82, 0x11, 0x22, 0x0c, 0x51, 0xfc, 0x10, 0x27, 0xe7, 0xc9, 0x5b, 0xe8, 0x12, 0x7f, 0xc0, - 0xff, 0x24, 0x41, 0x22, 0xc0, 0x83, 0xfe, 0x86, 0x0f, 0xf7, 0xc2, 0x07, 0xe3, 0x42, 0x3f, 0xc0, - 0xff, 0x09, 0x08, 0x78, 0x3a, 0x28, 0xff, 0x20, 0xc3, 0xf7, 0xe2, 0x27, 0xe0, 0x08, 0xbf, 0xc0, - 0xfe, 0x40, 0x92, 0x79, 0x38, 0x40, 0xff, 0x88, 0x1f, 0xe7, 0xf0, 0x8f, 0xcc, 0xa1, 0x1f, 0xc0, - 0xfe, 0x14, 0x27, 0xff, 0xfd, 0x14, 0x7f, 0xe3, 0x3f, 0xf3, 0xf4, 0x9f, 0xc0, 0x24, 0x5f, 0xc0, - 0xfe, 0x21, 0x0f, 0xff, 0xff, 0xc1, 0x3f, 0xff, 0xff, 0xf3, 0xfe, 0x7f, 0x93, 0x04, 0x1f, 0xc0, - 0xfc, 0x88, 0xcf, 0xff, 0xff, 0xf2, 0x1f, 0xff, 0xff, 0xf1, 0xff, 0xff, 0x84, 0x49, 0x2f, 0xc0, - 0xfc, 0x46, 0x0f, 0xff, 0xff, 0xf8, 0x8f, 0xff, 0xff, 0xf0, 0xff, 0xfe, 0x20, 0x40, 0x8f, 0xc0, - 0xfc, 0x10, 0x27, 0xff, 0xff, 0xfc, 0xa7, 0xff, 0xfe, 0xf2, 0x7f, 0xfe, 0x29, 0x12, 0x1f, 0xc0, - 0xf9, 0x81, 0x27, 0xf0, 0x2f, 0xfe, 0x01, 0xff, 0xf8, 0x12, 0x3f, 0xf8, 0x82, 0x12, 0x47, 0xc0, - 0xf8, 0x29, 0x0f, 0xe2, 0x01, 0xff, 0x18, 0x7f, 0xc1, 0x00, 0x87, 0x80, 0x12, 0x40, 0x47, 0xc0, - 0xfa, 0x22, 0x4f, 0xe8, 0xc4, 0xff, 0x22, 0x00, 0x04, 0x4c, 0x10, 0x25, 0x40, 0x4c, 0x17, 0xc0, - 0xf8, 0x84, 0x17, 0xe1, 0x10, 0xff, 0x00, 0x80, 0x24, 0x41, 0x40, 0x04, 0x19, 0x01, 0x87, 0xc0, - 0xf1, 0x11, 0x27, 0xe4, 0x12, 0x7f, 0x4c, 0x24, 0x91, 0x11, 0x0c, 0xd0, 0x81, 0x30, 0x27, 0xc0, - 0xf4, 0x40, 0x8f, 0xe4, 0x82, 0x7f, 0x11, 0x12, 0x01, 0x12, 0x21, 0x02, 0x44, 0x06, 0x23, 0xc0, - 0xf0, 0x8c, 0x07, 0xe0, 0xa4, 0xff, 0x13, 0x12, 0x64, 0xc2, 0x42, 0x32, 0x30, 0xc0, 0x8b, 0xc0, - 0xf2, 0x21, 0x67, 0xea, 0x08, 0xff, 0x7f, 0x4f, 0xcf, 0xe5, 0xfb, 0xf8, 0xfe, 0x18, 0x43, 0xc0, - 0xf2, 0x10, 0x0f, 0xe0, 0x42, 0xfe, 0x3f, 0xbf, 0xcf, 0xef, 0xfb, 0xf9, 0xfe, 0x03, 0x13, 0xc0, - 0xf0, 0xc6, 0x27, 0xe5, 0x17, 0xfe, 0xbf, 0xff, 0xef, 0xff, 0xf3, 0xff, 0xfc, 0xa0, 0x27, 0xc0, - 0xf1, 0x00, 0x87, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xfe, 0x0c, 0x83, 0xc0, - 0xf2, 0x28, 0x97, 0xff, 0xff, 0xf9, 0x3f, 0xff, 0xcf, 0xff, 0xf9, 0xff, 0xfc, 0x40, 0x4b, 0xc0, - 0xf0, 0x82, 0x17, 0xff, 0xff, 0xf2, 0x1f, 0xff, 0xc7, 0xff, 0xf1, 0xff, 0xfd, 0x23, 0x13, 0xc0, - 0xe4, 0x12, 0x47, 0xff, 0xff, 0xf8, 0x9f, 0xf3, 0x97, 0xfc, 0xf3, 0xff, 0x5c, 0x24, 0x13, 0xc0, - 0xf1, 0x48, 0x4f, 0xff, 0xff, 0xfc, 0x7f, 0xc0, 0x07, 0xf0, 0x01, 0xfc, 0x00, 0x84, 0x83, 0xc0, - 0xf1, 0x01, 0x07, 0xff, 0xff, 0xff, 0x1f, 0xc8, 0x67, 0xf3, 0x0d, 0xfc, 0x82, 0x10, 0x63, 0xc0, - 0xe4, 0x31, 0x27, 0xe0, 0x03, 0xff, 0x1f, 0xc6, 0x0f, 0xf0, 0x41, 0xfc, 0x52, 0x43, 0x09, 0xc0, - 0xf0, 0x84, 0x27, 0xe0, 0x08, 0xff, 0x3f, 0x90, 0x87, 0xe4, 0x21, 0xfc, 0x44, 0x48, 0x0b, 0xc0, - 0xf2, 0x08, 0x8f, 0xec, 0xc0, 0x7f, 0x9f, 0x80, 0x97, 0xf1, 0x8d, 0xf9, 0x09, 0x08, 0xa3, 0xc0, - 0xf1, 0x48, 0x87, 0xe1, 0x14, 0x7f, 0x9f, 0xcc, 0x27, 0xe4, 0x11, 0xfc, 0x20, 0x22, 0x07, 0xc0, - 0xf0, 0x42, 0x37, 0xe1, 0x05, 0x3f, 0x9f, 0x91, 0x27, 0xe0, 0x41, 0xfc, 0x84, 0x84, 0x53, 0xc0, - 0xf2, 0x12, 0x07, 0xe8, 0x60, 0x7f, 0x9f, 0x81, 0x07, 0xf3, 0x09, 0xf8, 0x92, 0x51, 0x03, 0xc0, - 0xf2, 0x20, 0xcf, 0xe2, 0x09, 0x7f, 0x9f, 0xc8, 0x4f, 0xf0, 0x25, 0xfc, 0x10, 0x02, 0x2b, 0xc0, - 0xf0, 0x89, 0x07, 0xe4, 0x84, 0x7f, 0x9f, 0x92, 0x27, 0xe4, 0x91, 0xfc, 0x45, 0x88, 0x83, 0xc0, - 0xf9, 0x08, 0x37, 0xe0, 0x30, 0xff, 0x9f, 0x84, 0x87, 0xe4, 0x83, 0xf9, 0x20, 0x24, 0x4f, 0xc0, - 0xf8, 0x62, 0x0f, 0xea, 0x43, 0xff, 0x3f, 0xc0, 0x4f, 0xf0, 0x28, 0x99, 0x08, 0x91, 0x07, 0xc0, - 0xf8, 0x82, 0x4f, 0xf2, 0x4f, 0xff, 0x1f, 0x99, 0x17, 0xf2, 0x4f, 0xef, 0x8a, 0x02, 0x37, 0xc0, - 0xf9, 0x10, 0x87, 0xff, 0xff, 0xfe, 0x5f, 0xc1, 0x07, 0xe2, 0x1f, 0xff, 0xa0, 0xa4, 0x87, 0xc0, - 0xf8, 0x44, 0x9f, 0xff, 0xff, 0xfe, 0x1f, 0xc4, 0x67, 0xf0, 0x8f, 0xff, 0x84, 0x10, 0x47, 0xc0, - 0xfc, 0x12, 0x0f, 0xff, 0xff, 0xfc, 0x9f, 0x90, 0x8f, 0xf4, 0x4f, 0xff, 0x91, 0x42, 0x1f, 0xc0, - 0xfd, 0x20, 0x67, 0xff, 0xff, 0xf8, 0x5f, 0xc6, 0x07, 0xf1, 0x1f, 0xff, 0x91, 0x09, 0x8f, 0xc0, - 0xfc, 0x49, 0x0f, 0xff, 0xff, 0xe1, 0x1f, 0xc0, 0x57, 0xf0, 0x8f, 0xff, 0x84, 0x24, 0x2f, 0xc0, - 0xfc, 0x42, 0x0f, 0xfb, 0x7e, 0x04, 0x3f, 0x19, 0x07, 0xc6, 0x2f, 0xff, 0xa4, 0x90, 0x0f, 0xc0, - 0xfe, 0x10, 0xa0, 0x78, 0x78, 0x90, 0x80, 0x01, 0x20, 0x00, 0x0f, 0xff, 0x88, 0x42, 0xdf, 0xc0, - 0xfe, 0x84, 0x80, 0x38, 0x38, 0x86, 0x00, 0xc4, 0x20, 0x29, 0x40, 0x00, 0x42, 0x08, 0x1f, 0xc0, - 0xfe, 0x24, 0x12, 0x02, 0x02, 0x20, 0x64, 0x10, 0x8c, 0x81, 0x13, 0xfc, 0x11, 0x89, 0x3f, 0xc0, - 0xff, 0x11, 0x24, 0xc2, 0x90, 0x22, 0x09, 0x26, 0x10, 0x94, 0x24, 0xf9, 0x88, 0x21, 0x3f, 0xc0, - 0xff, 0x41, 0x21, 0x10, 0x85, 0x89, 0x88, 0x20, 0x42, 0x10, 0x80, 0xf2, 0x22, 0x24, 0x3f, 0xc0, - 0xff, 0x8c, 0x08, 0x11, 0x24, 0x08, 0x22, 0x89, 0x22, 0x42, 0x4a, 0x64, 0x10, 0x84, 0xff, 0xc0, - 0xff, 0x80, 0xc4, 0x88, 0x10, 0x42, 0x02, 0x08, 0x88, 0x49, 0x10, 0x09, 0x44, 0x90, 0x7f, 0xc0, - 0xff, 0xd2, 0x17, 0xef, 0xdf, 0xff, 0xfd, 0xf6, 0xff, 0xff, 0xff, 0xc0, 0x08, 0x12, 0xff, 0xc0, - 0xff, 0xc2, 0x1b, 0x76, 0xed, 0xb5, 0xb7, 0x77, 0x66, 0xb6, 0x6d, 0xe4, 0xa3, 0x02, 0xff, 0xc0, - 0xff, 0xe8, 0xdb, 0x5b, 0x36, 0x6d, 0xb6, 0xd9, 0xbb, 0xad, 0xb6, 0x64, 0x20, 0x61, 0xff, 0xc0, - 0xff, 0xe0, 0x1d, 0xdb, 0xdb, 0xce, 0x6d, 0xbe, 0xdb, 0x6d, 0xb7, 0xb1, 0x0c, 0x0d, 0xff, 0xc0, - 0xff, 0xf5, 0x06, 0xdc, 0xdb, 0x73, 0xdb, 0x66, 0xdc, 0xdb, 0x6d, 0x81, 0x11, 0x83, 0xff, 0xc0, - 0xff, 0xf1, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x80, 0x00, 0x48, 0x40, 0x33, 0xff, 0xc0, - 0xff, 0xf8, 0x25, 0x26, 0xac, 0x89, 0x2c, 0xb3, 0x26, 0x26, 0x9a, 0x4c, 0xca, 0x07, 0xff, 0xc0, - 0xff, 0xfc, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xf7, 0xbc, 0x8f, 0xff, 0xc0, - 0xff, 0xfc, 0x9a, 0xd9, 0x53, 0x66, 0xd3, 0x4c, 0xd9, 0x99, 0x6d, 0xb6, 0xe6, 0x3f, 0xff, 0xc0, - 0xff, 0xfe, 0x1b, 0x6f, 0x7d, 0xbb, 0x7d, 0xf7, 0x6e, 0xef, 0x6d, 0xbb, 0x7a, 0x1f, 0xff, 0xc0, - 0xff, 0xff, 0x4d, 0xb3, 0xa6, 0xdb, 0x4d, 0x37, 0x6e, 0xed, 0xb6, 0xcd, 0x98, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0x26, 0xdc, 0xfb, 0x6d, 0xf7, 0xd9, 0xb3, 0x35, 0xb6, 0xf6, 0xec, 0x3f, 0xff, 0xc0, - 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe4, 0x22, 0x04, 0x92, 0x08, 0x24, 0x48, 0x88, 0x49, 0x09, 0x11, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xe2, 0x48, 0x90, 0x00, 0x82, 0x04, 0x84, 0x92, 0x00, 0x40, 0x45, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf8, 0x84, 0x93, 0x29, 0x31, 0x90, 0x91, 0x11, 0x96, 0x14, 0x87, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xf9, 0x21, 0x00, 0x44, 0x04, 0x22, 0x10, 0x44, 0x10, 0x91, 0x2f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x08, 0x4c, 0x44, 0x90, 0x22, 0x46, 0x00, 0x40, 0x82, 0x1f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xfe, 0x92, 0x21, 0x10, 0x84, 0x88, 0x40, 0xaa, 0x4a, 0x24, 0x9f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0x81, 0x01, 0x02, 0x22, 0x09, 0x18, 0x21, 0x08, 0x20, 0x7f, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xcc, 0x54, 0x62, 0x28, 0xc1, 0x03, 0x04, 0x21, 0x8a, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf0, 0x84, 0x08, 0x81, 0x14, 0x60, 0x48, 0x84, 0x0b, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xf8, 0x89, 0x08, 0x44, 0x10, 0x0c, 0x22, 0x50, 0x67, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xfe, 0x20, 0xc3, 0x12, 0x43, 0x01, 0x84, 0x06, 0x1f, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0x06, 0x10, 0x20, 0x88, 0x60, 0x11, 0x20, 0xbf, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xd0, 0x11, 0x24, 0x20, 0x8c, 0x48, 0x91, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xf1, 0x44, 0x43, 0x0c, 0x01, 0x02, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xfc, 0x08, 0x90, 0x41, 0x31, 0x30, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x22, 0x04, 0x31, 0x04, 0x05, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x21, 0x84, 0x42, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x28, 0x04, 0x48, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x51, 0x11, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0 -}; - // Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032) -const int epd_icons_allArray_LEN = 8; +const int epd_icons_allArray_LEN = 4; const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = { epd_icons_pickaxe, epd_icons_rocket_launch, epd_icons_lightning_bolt, - epd_icons_bitaxe_logo, - epd_icons_ocean_logo, - epd_icons_braiins_logo, - epd_icons_noderunners_logo, - epd_icons_gobrrr_logo + epd_icons_bitaxe_logo }; diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index c4db06e..48989bd 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -296,45 +296,27 @@ void restartBlockNotify() } -int getBlockFetch() -{ - try { - WiFiClientSecure client; +int getBlockFetch() { + try { + String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http"; + String url = protocol + "://" + mempoolInstance + "/api/blocks/tip/height"; - if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { - client.setCACertBundle(rootca_crt_bundle_start); + HTTPClient* http = HttpHelper::begin(url); + Serial.println("Fetching block height from " + url); + int httpCode = http->GET(); + + if (httpCode > 0 && httpCode == HTTP_CODE_OK) { + String blockHeightStr = http->getString(); + HttpHelper::end(http); + return blockHeightStr.toInt(); + } + HttpHelper::end(http); + Serial.println("HTTP code" + String(httpCode)); + } catch (...) { + Serial.println(F("An exception occurred while trying to get the latest block")); } - - String mempoolInstance = - preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - - // Get current block height through regular API - HTTPClient http; - - const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http"; - - if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) - http.begin(client, protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); - else - http.begin(protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); - - Serial.println("Fetching block height from " + protocol + "://" + mempoolInstance + "/api/blocks/tip/height"); - int httpCode = http.GET(); - - if (httpCode > 0 && httpCode == HTTP_CODE_OK) - { - String blockHeightStr = http.getString(); - return blockHeightStr.toInt(); - } else { - Serial.println("HTTP code" + String(httpCode)); - return 0; - } - } - catch (...) { - Serial.println(F("An exception occured while trying to get the latest block")); - } - - return 2203; // B-T-C + return 2203; // B-T-C } uint getLastBlockUpdate() diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 04e9a60..9feb9a9 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -439,15 +439,19 @@ void setupHardware() Serial.println(F("Error loading WebUI")); } - // if (!LittleFS.exists("/qr.txt")) // { // File f = LittleFS.open("/qr.txt", "w"); // if(f) { - + // if (f.print("Hello")) { + // Serial.println(F("Written QR to FS")); + // Serial.printf("\nLittleFS free: %zu\n", LittleFS.totalBytes() - LittleFS.usedBytes()); + // } // } else { // Serial.println(F("Can't write QR to FS")); // } + + // f.close(); // } setupLeds(); diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 87dd4d6..2c001cd 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -33,7 +33,6 @@ #define NTP_SERVER "pool.ntp.org" #define DEFAULT_TIME_OFFSET_SECONDS 3600 -#define USER_AGENT "BTClock/3.0" #ifndef MCP_DEV_ADDR #define MCP_DEV_ADDR 0x20 #endif diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 7ca89fb..d880f2d 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -75,3 +75,5 @@ #define DEFAULT_GIT_RELEASE_URL "https://git.btclock.dev/api/v1/repos/btclock/btclock_v3/releases/latest" #define DEFAULT_VERTICAL_DESC true + +#define DEFAULT_MINING_POOL_LOGOS_URL "https://git.btclock.dev/btclock/mining-pool-logos/raw/branch/main" diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index b91e75c..260f772 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -176,7 +176,7 @@ void setupDisplays() updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE, NULL, 11, NULL); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE*2, NULL, 11, NULL); for (uint i = 0; i < NUM_SCREENS; i++) { @@ -275,7 +275,10 @@ void prepareDisplayUpdateTask(void *pvParameters) } else if (epdContent[epdIndex].startsWith(F("mdi"))) { - renderIcon(epdIndex, epdContent[epdIndex], updatePartial); + bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial); + if (!updated) { + continue; + } } else if (epdContent[epdIndex].length() > 5) { @@ -594,7 +597,7 @@ void renderText(const uint dispNum, const String &text, bool partial) } } -void renderIcon(const uint dispNum, const String &text, bool partial) +bool renderIcon(const uint dispNum, const String &text, bool partial) { displays[dispNum].setRotation(2); @@ -620,12 +623,17 @@ void renderIcon(const uint dispNum, const String &text, bool partial) else if (text.endsWith("miningpool")) { LogoData logo = getMiningPoolLogo(); + if (logo.size == 0) { + Serial.println("No logo found"); + return false; + } + int x_offset = (displays[dispNum].width() - logo.width) / 2; int y_offset = (displays[dispNum].height() - logo.height) / 2; // Close the file displays[dispNum].drawInvertedBitmap(x_offset,y_offset, logo.data, logo.width, logo.height, getFgColor()); - return; + return true; } @@ -635,7 +643,7 @@ void renderIcon(const uint dispNum, const String &text, bool partial) displays[dispNum].drawInvertedBitmap(x_offset,y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); - + return true; // displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 79be979..c267047 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -45,7 +45,7 @@ int getFgColor(); void setBgColor(int color); void setFgColor(int color); -void renderIcon(const uint dispNum, const String &text, bool partial); +bool renderIcon(const uint dispNum, const String &text, bool partial); void renderText(const uint dispNum, const String &text, bool partial); void renderQr(const uint dispNum, const String &text, bool partial); diff --git a/src/lib/mining_pool/braiins/brains_pool.cpp b/src/lib/mining_pool/braiins/brains_pool.cpp index 93f9d37..47b6e61 100644 --- a/src/lib/mining_pool/braiins/brains_pool.cpp +++ b/src/lib/mining_pool/braiins/brains_pool.cpp @@ -10,6 +10,13 @@ std::string BraiinsPool::getApiUrl() const { PoolStats BraiinsPool::parseResponse(const JsonDocument &doc) const { + if (doc["btc"].isNull()) { + return PoolStats{ + .hashrate = "0", + .dailyEarnings = 0 + }; + } + std::string unit = doc["btc"]["hash_rate_unit"].as(); static const std::unordered_map multipliers = { @@ -23,10 +30,3 @@ PoolStats BraiinsPool::parseResponse(const JsonDocument &doc) const .dailyEarnings = static_cast(doc["btc"]["today_reward"].as() * 100000000)}; } -LogoData BraiinsPool::getLogo() const { - return LogoData{ - .data = epd_icons_allArray[5], - .width = 37, - .height = 230 - }; -} \ No newline at end of file diff --git a/src/lib/mining_pool/braiins/brains_pool.hpp b/src/lib/mining_pool/braiins/brains_pool.hpp index e9354e5..d79f66b 100644 --- a/src/lib/mining_pool/braiins/brains_pool.hpp +++ b/src/lib/mining_pool/braiins/brains_pool.hpp @@ -11,9 +11,23 @@ public: void prepareRequest(HTTPClient &http) const override; std::string getApiUrl() const override; PoolStats parseResponse(const JsonDocument &doc) const override; - LogoData getLogo() const override; bool supportsDailyEarnings() const override { return true; } bool hasLogo() const override { return true; } std::string getDisplayLabel() const override { return "BRAIINS/POOL"; } // Fallback if needed std::string getDailyEarningsLabel() const override { return "sats/earned"; } + std::string getLogoFilename() const override { + return "braiins.bin"; + } + + std::string getPoolName() const override { + return "braiins"; + } + + int getLogoWidth() const override { + return 37; + } + + int getLogoHeight() const override { + return 230; + } }; \ No newline at end of file diff --git a/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp index 90cd420..0186fec 100644 --- a/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp +++ b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.cpp @@ -3,12 +3,4 @@ std::string GoBrrrPool::getApiUrl() const { return "https://pool.gobrrr.me/api/client/" + poolUser; -} - -LogoData GoBrrrPool::getLogo() const { - return LogoData { - .data = epd_icons_allArray[7], - .width = 122, - .height = 122 - }; -} +} \ No newline at end of file diff --git a/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp index c50fe83..8c9ffb8 100644 --- a/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp +++ b/src/lib/mining_pool/gobrrr_pool/gobrrr_pool.hpp @@ -11,5 +11,20 @@ public: std::string getApiUrl() const override; bool hasLogo() const override { return true; } std::string getDisplayLabel() const override { return "GOBRRR/POOL"; } - LogoData getLogo() const override; + + std::string getLogoFilename() const override { + return "gobrrr.bin"; + } + + std::string getPoolName() const override { + return "gobrrr_pool"; + } + + int getLogoWidth() const override { + return 122; + } + + int getLogoHeight() const override { + return 122; + } }; \ No newline at end of file diff --git a/src/lib/mining_pool/logo_data.hpp b/src/lib/mining_pool/logo_data.hpp index 7cd0819..75dcb35 100644 --- a/src/lib/mining_pool/logo_data.hpp +++ b/src/lib/mining_pool/logo_data.hpp @@ -6,5 +6,6 @@ struct LogoData { const uint8_t* data; size_t width; - size_t height; + size_t height; + size_t size; }; diff --git a/src/lib/mining_pool/mining_pool_interface.cpp b/src/lib/mining_pool/mining_pool_interface.cpp new file mode 100644 index 0000000..ced6cb9 --- /dev/null +++ b/src/lib/mining_pool/mining_pool_interface.cpp @@ -0,0 +1,18 @@ +#include "mining_pool_interface.hpp" +#include "pool_factory.hpp" + +LogoData MiningPoolInterface::getLogo() const { + if (!hasLogo()) { + return LogoData{nullptr, 0, 0, 0}; + } + + // Check if logo exists + String logoPath = String(PoolFactory::getLogosDir()) + "/" + String(getPoolName().c_str()) + "_logo.bin"; + + if (!LittleFS.exists(logoPath)) { + return LogoData{nullptr, 0, 0, 0}; + } + + // Now load the logo (whether it was just downloaded or already existed) + return PoolFactory::loadLogoFromFS(getPoolName(), this); +} \ No newline at end of file diff --git a/src/lib/mining_pool/mining_pool_interface.hpp b/src/lib/mining_pool/mining_pool_interface.hpp index 63bb53f..28383a3 100644 --- a/src/lib/mining_pool/mining_pool_interface.hpp +++ b/src/lib/mining_pool/mining_pool_interface.hpp @@ -4,6 +4,7 @@ #include #include "pool_stats.hpp" #include "logo_data.hpp" +#include "lib/shared.hpp" class MiningPoolInterface { public: @@ -13,10 +14,21 @@ public: virtual std::string getApiUrl() const = 0; virtual PoolStats parseResponse(const JsonDocument& doc) const = 0; virtual bool hasLogo() const = 0; - virtual LogoData getLogo() const = 0; + virtual LogoData getLogo() const; virtual std::string getDisplayLabel() const = 0; virtual bool supportsDailyEarnings() const = 0; virtual std::string getDailyEarningsLabel() const = 0; + virtual std::string getLogoFilename() const { return ""; } + virtual std::string getPoolName() const = 0; + virtual int getLogoWidth() const { return 0; } + virtual int getLogoHeight() const { return 0; } + std::string getLogoUrl() const { + if (!hasLogo() || getLogoFilename().empty()) { + return ""; + } + std::string baseUrl = preferences.getString("poolLogosUrl", DEFAULT_MINING_POOL_LOGOS_URL).c_str(); + return baseUrl + "/" + getLogoFilename().c_str(); + } protected: std::string poolUser; diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.cpp b/src/lib/mining_pool/noderunners/noderunners_pool.cpp index 0f55180..da34e38 100644 --- a/src/lib/mining_pool/noderunners/noderunners_pool.cpp +++ b/src/lib/mining_pool/noderunners/noderunners_pool.cpp @@ -24,12 +24,4 @@ PoolStats NoderunnersPool::parseResponse(const JsonDocument& doc) const { .hashrate = buffer, .dailyEarnings = std::nullopt }; -} - -LogoData NoderunnersPool::getLogo() const { - return LogoData { - .data = epd_icons_allArray[6], - .width = 122, - .height = 122 - }; } \ No newline at end of file diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.hpp b/src/lib/mining_pool/noderunners/noderunners_pool.hpp index 5ecce53..562ff50 100644 --- a/src/lib/mining_pool/noderunners/noderunners_pool.hpp +++ b/src/lib/mining_pool/noderunners/noderunners_pool.hpp @@ -1,4 +1,3 @@ - #pragma once #include "lib/mining_pool/mining_pool_interface.hpp" @@ -12,9 +11,23 @@ public: void prepareRequest(HTTPClient& http) const override; std::string getApiUrl() const override; PoolStats parseResponse(const JsonDocument& doc) const override; - LogoData getLogo() const override; bool supportsDailyEarnings() const override { return false; } std::string getDailyEarningsLabel() const override { return ""; } bool hasLogo() const override { return true; } std::string getDisplayLabel() const override { return "NODE/RUNNERS"; } // Fallback if needed + std::string getLogoFilename() const override { + return "noderunners.bin"; + } + + std::string getPoolName() const override { + return "noderunners"; + } + + int getLogoWidth() const override { + return 122; + } + + int getLogoHeight() const override { + return 122; + } }; \ No newline at end of file diff --git a/src/lib/mining_pool/ocean/ocean_pool.cpp b/src/lib/mining_pool/ocean/ocean_pool.cpp index f66ad5c..f6050af 100644 --- a/src/lib/mining_pool/ocean/ocean_pool.cpp +++ b/src/lib/mining_pool/ocean/ocean_pool.cpp @@ -16,11 +16,3 @@ PoolStats OceanPool::parseResponse(const JsonDocument& doc) const { ) }; } - -LogoData OceanPool::getLogo() const { - return LogoData{ - .data = epd_icons_allArray[4], - .width = 122, - .height = 122 - }; -} \ No newline at end of file diff --git a/src/lib/mining_pool/ocean/ocean_pool.hpp b/src/lib/mining_pool/ocean/ocean_pool.hpp index 2773915..3ede176 100644 --- a/src/lib/mining_pool/ocean/ocean_pool.hpp +++ b/src/lib/mining_pool/ocean/ocean_pool.hpp @@ -9,10 +9,23 @@ public: void prepareRequest(HTTPClient& http) const override; std::string getApiUrl() const override; PoolStats parseResponse(const JsonDocument& doc) const override; - LogoData getLogo() const override; bool hasLogo() const override { return true; } std::string getDisplayLabel() const override { return "OCEAN/POOL"; } // Fallback if needed bool supportsDailyEarnings() const override { return true; } std::string getDailyEarningsLabel() const override { return "sats/block"; } - + std::string getLogoFilename() const override { + return "ocean.bin"; + } + + std::string getPoolName() const override { + return "ocean"; + } + + int getLogoWidth() const override { + return 122; + } + + int getLogoHeight() const override { + return 122; + } }; \ No newline at end of file diff --git a/src/lib/mining_pool/pool_factory.cpp b/src/lib/mining_pool/pool_factory.cpp index d3073e1..150793a 100644 --- a/src/lib/mining_pool/pool_factory.cpp +++ b/src/lib/mining_pool/pool_factory.cpp @@ -6,6 +6,7 @@ const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins"; const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio"; const char* PoolFactory::MINING_POOL_NAME_PUBLIC_POOL = "public_pool"; const char* PoolFactory::MINING_POOL_NAME_GOBRRR_POOL = "gobrrr_pool"; +const char* PoolFactory::LOGOS_DIR = "/logos"; std::unique_ptr PoolFactory::createPool(const std::string& poolName) { static const std::unordered_map()>> poolFactories = { @@ -22,4 +23,112 @@ std::unique_ptr PoolFactory::createPool(const std::string& return nullptr; } return it->second(); +} + +void PoolFactory::downloadPoolLogo(const std::string& poolName, const MiningPoolInterface* poolInterface) +{ + const int MAX_RETRIES = 5; + const int RETRY_DELAY_MS = 1000; // 1 second between retries + + if (!poolInterface || !poolInterface->hasLogo()) { + Serial.println(F("No pool interface or logo")); + return; + } + + // Ensure logos directory exists + if (!LittleFS.exists(LOGOS_DIR)) { + LittleFS.mkdir(LOGOS_DIR); + } + + String logoPath = String(LOGOS_DIR) + "/" + String(poolName.c_str()) + "_logo.bin"; + + // Only download if the logo doesn't exist + if (!LittleFS.exists(logoPath)) { + // Clean up logos directory first + File root = LittleFS.open(LOGOS_DIR, "r"); + if (root) { + File file = root.openNextFile(); + while (file) { + String path = file.path(); + file.close(); + LittleFS.remove(path); + file = root.openNextFile(); + } + root.close(); + } + + // Download new logo with retries + std::string logoUrl = poolInterface->getLogoUrl(); + if (!logoUrl.empty()) { + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { + Serial.printf("Downloading pool logo (attempt %d of %d)...\n", attempt, MAX_RETRIES); + + HTTPClient http; + http.setUserAgent(USER_AGENT); + http.begin(logoUrl.c_str()); + int httpCode = http.GET(); + + if (httpCode == 200) { + File file = LittleFS.open(logoPath, "w"); + if (file) { + http.writeToStream(&file); + file.close(); + Serial.println(F("Logo downloaded successfully")); + http.end(); + return; // Success! + } + } + + http.end(); + + if (attempt < MAX_RETRIES) { + Serial.printf("Failed to download logo, HTTP code: %d. Retrying...\n", httpCode); + vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS)); + } else { + Serial.printf("Failed to download logo after %d attempts\n", MAX_RETRIES); + } + } + } + } else { + Serial.println(F("Logo already exists")); + } +} + +LogoData PoolFactory::loadLogoFromFS(const std::string& poolName, const MiningPoolInterface* poolInterface) +{ + // Initialize with dimensions from the pool interface + LogoData logo = {nullptr, + 0, + 0, + 0}; + + String logoPath = String(LOGOS_DIR) + "/" + String(poolName.c_str()) + "_logo.bin"; + if (!LittleFS.exists(logoPath)) { + return logo; + } + + // Only set dimensions if file exists + logo.width = static_cast(poolInterface->getLogoWidth()); + logo.height = static_cast(poolInterface->getLogoHeight()); + + File file = LittleFS.open(logoPath, "r"); + if (!file) { + return logo; + } + + size_t size = file.size(); + uint8_t* buffer = new uint8_t[size]; + + + if (file.read(buffer, size) == size) { + logo.data = buffer; + logo.size = size; + } else { + delete[] buffer; + logo.data = nullptr; + logo.size = 0; + } + + file.close(); + return logo; } \ No newline at end of file diff --git a/src/lib/mining_pool/pool_factory.hpp b/src/lib/mining_pool/pool_factory.hpp index a8e4861..96231c9 100644 --- a/src/lib/mining_pool/pool_factory.hpp +++ b/src/lib/mining_pool/pool_factory.hpp @@ -2,15 +2,22 @@ #include "mining_pool_interface.hpp" #include #include +#include "lib/shared.hpp" +#include "lib/config.hpp" + #include "noderunners/noderunners_pool.hpp" #include "braiins/brains_pool.hpp" #include "ocean/ocean_pool.hpp" #include "satoshi_radio/satoshi_radio_pool.hpp" #include "public_pool/public_pool.hpp" #include "gobrrr_pool/gobrrr_pool.hpp" +#include +#include + class PoolFactory { public: + static const char* getLogosDir() { return LOGOS_DIR; } static std::unique_ptr createPool(const std::string& poolName); static std::vector getAvailablePools() { return { @@ -34,6 +41,10 @@ class PoolFactory { } return result; } + + static void downloadPoolLogo(const std::string& poolName, const MiningPoolInterface* poolInterface); + static LogoData loadLogoFromFS(const std::string& poolName, const MiningPoolInterface* poolInterface); + private: static const char* MINING_POOL_NAME_OCEAN; static const char* MINING_POOL_NAME_NODERUNNERS; @@ -41,4 +52,5 @@ class PoolFactory { static const char* MINING_POOL_NAME_SATOSHI_RADIO; static const char* MINING_POOL_NAME_PUBLIC_POOL; static const char* MINING_POOL_NAME_GOBRRR_POOL; + static const char* LOGOS_DIR; }; \ No newline at end of file diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index 2f9c10b..3f870bb 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -18,6 +18,12 @@ int getMiningPoolStatsDailyEarnings() void taskMiningPoolStatsFetch(void *pvParameters) { + std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + auto poolInterface = PoolFactory::createPool(poolName); + + std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); + + // Main stats fetching loop for (;;) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); @@ -25,16 +31,8 @@ void taskMiningPoolStatsFetch(void *pvParameters) HTTPClient http; http.setUserAgent(USER_AGENT); - std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); - std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); - - auto poolInterface = PoolFactory::createPool(poolName); - if (!poolInterface) - { - Serial.println("Unknown mining pool: \"" + String(poolName.c_str()) + "\""); - continue; - } + poolInterface->setPoolUser(poolUser); std::string apiUrl = poolInterface->getApiUrl(); http.begin(apiUrl.c_str()); @@ -74,12 +72,36 @@ void taskMiningPoolStatsFetch(void *pvParameters) } } -void setupMiningPoolStatsFetchTask() -{ - xTaskCreate(taskMiningPoolStatsFetch, "miningPoolStatsFetch", (6 * 1024), NULL, tskIDLE_PRIORITY, - &miningPoolStatsFetchTaskHandle); +void downloadMiningPoolLogoTask(void *pvParameters) { + std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + auto poolInterface = PoolFactory::createPool(poolName); + PoolFactory::downloadPoolLogo(poolName, poolInterface.get()); + + // If we're on the mining pool stats screen, trigger a display update + if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { + WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; + xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); + } xTaskNotifyGive(miningPoolStatsFetchTaskHandle); + vTaskDelete(NULL); +} + +void setupMiningPoolStatsFetchTask() +{ + xTaskCreate(downloadMiningPoolLogoTask, + "logoDownload", + (6 * 1024), + NULL, + 12, + NULL); + + xTaskCreate(taskMiningPoolStatsFetch, + "miningPoolStatsFetch", + (6 * 1024), + NULL, + tskIDLE_PRIORITY, + &miningPoolStatsFetchTaskHandle); } std::unique_ptr& getMiningPool() @@ -96,5 +118,6 @@ std::unique_ptr& getMiningPool() LogoData getMiningPoolLogo() { - return getMiningPool()->getLogo(); + LogoData logo = getMiningPool()->getLogo(); + return logo; } diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index 3422ff8..a776750 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -151,4 +151,32 @@ String calculateSHA256(WiFiClient *stream, size_t contentLength) { // uint8_t *pUncompressed; // pUncompressed = (uint8_t *)malloc(iUncompSize+4); // zt.gunzip((uint8_t *)ocean_logo_comp, ocean_logo_size, pUncompressed); -// } \ No newline at end of file +// } + +WiFiClientSecure HttpHelper::secureClient; +WiFiClient HttpHelper::insecureClient; +bool HttpHelper::certBundleSet = false; + +HTTPClient* HttpHelper::begin(const String& url) { + HTTPClient* http = new HTTPClient(); + + if (url.startsWith("https://")) { + if (!certBundleSet) { + secureClient.setCACertBundle(rootca_crt_bundle_start); + certBundleSet = true; + } + http->begin(secureClient, url); + } else { + http->begin(insecureClient, url); + } + + http->setUserAgent(USER_AGENT); + return http; +} + +void HttpHelper::end(HTTPClient* http) { + if (http) { + http->end(); + delete http; + } +} \ No newline at end of file diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 9fd7455..adbbe5e 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -12,12 +12,15 @@ #include #include "esp_crt_bundle.h" #include +#include #include #include #include "defaults.hpp" +#define USER_AGENT "BTClock/3.0" + extern MCP23017 mcp1; #ifdef IS_BTCLOCK_V8 extern MCP23017 mcp2; @@ -94,4 +97,15 @@ namespace ArduinoJson { array.add(item); } }; -} \ No newline at end of file +} + +class HttpHelper { +public: + static HTTPClient* begin(const String& url); + static void end(HTTPClient* http); + +private: + static WiFiClientSecure secureClient; + static bool certBundleSet; + static WiFiClient insecureClient; +}; \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 8b031d6..eddc2f9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -510,7 +510,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl"}; for (String setting : strSettings) { @@ -768,6 +768,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request) o["enabled"] = preferences.getBool(key.c_str(), true); } + root["poolLogosUrl"] = preferences.getString("poolLogosUrl", DEFAULT_MINING_POOL_LOGOS_URL); + AsyncResponseStream *response = request->beginResponseStream("application/json"); serializeJson(root, *response); @@ -812,6 +814,9 @@ void onApiSystemStatus(AsyncWebServerRequest *request) root["espHeapSize"] = ESP.getHeapSize(); root["espFreePsram"] = ESP.getFreePsram(); root["espPsramSize"] = ESP.getPsramSize(); + root["fsUsedBytes"] = LittleFS.usedBytes(); + root["fsTotalBytes"] = LittleFS.totalBytes(); + root["rssi"] = WiFi.RSSI(); root["txPower"] = WiFi.getTxPower(); From 46c0f3a22bdb37cdf1f80e3d16416bb03ab9c50f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 00:01:27 +0100 Subject: [PATCH 131/188] Improve webserver functionality --- src/lib/webserver.cpp | 279 ++++++++++++++++-------------------------- src/lib/webserver.hpp | 3 +- 2 files changed, 107 insertions(+), 175 deletions(-) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index eddc2f9..ee9a701 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -1,26 +1,37 @@ #include "webserver.hpp" +static const char* JSON_CONTENT = "application/json"; + +static const char *const PROGMEM strSettings[] = { + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl"}; + +static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout", "srcV2Currency"}; + +static const char *const PROGMEM boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", + "mdnsEnabled", "otaEnabled", "stealFocus", + "mcapBigChar", "useSatsSymbol", "useBlkCountdown", + "suffixPrice", "disableLeds", "ownDataSource", + "mowMode", "suffixShareDot", "flOffWhenDark", + "flAlwaysOn", "flDisable", "flFlashOnUpd", + "mempoolSecure", "useNostr", "bitaxeEnabled", + "miningPoolStats", "verticalDesc", + "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; + AsyncWebServer server(80); AsyncEventSource events("/events"); TaskHandle_t eventSourceTaskHandle; +#define HTTP_OK 200 +#define HTTP_BAD_REQUEST 400 + void setupWebserver() { events.onConnect([](AsyncEventSourceClient *client) { client->send("welcome", NULL, millis(), 1000); }); server.addHandler(&events); - // server.ad. - // server.serveStatic("/css", LittleFS, "/css/"); - // server.serveStatic("/fonts", LittleFS, "/fonts/"); - // server.serveStatic("/build", LittleFS, "/build"); - // server.serveStatic("/swagger.json", LittleFS, "/swagger.json"); - // server.serveStatic("/api.html", LittleFS, "/api.html"); - // server.serveStatic("/fs_hash.txt", LittleFS, "/fs_hash.txt"); - AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); - server.rewrite("/convert", "/"); server.rewrite("/api", "/"); @@ -31,7 +42,6 @@ void setupWebserver() preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD)); } // server.on("/", HTTP_GET, onIndex); - server.on("/api/status", HTTP_GET, onApiStatus); server.on("/api/system_status", HTTP_GET, onApiSystemStatus); server.on("/api/wifi_set_tx_power", HTTP_GET, onApiSetWifiTxPower); @@ -51,8 +61,8 @@ void setupWebserver() server.on("/api/show/text", HTTP_GET, onApiShowText); - server.on("/api/screen/next", HTTP_GET, onApiScreenNext); - server.on("/api/screen/previous", HTTP_GET, onApiScreenPrevious); + server.on("/api/screen/next", HTTP_GET, onApiScreenControl); + server.on("/api/screen/previous", HTTP_GET, onApiScreenControl); AsyncCallbackJsonWebHandler *settingsPatchHandler = new AsyncCallbackJsonWebHandler("/api/json/settings", onApiSettingsPatch); @@ -212,36 +222,6 @@ void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, siz void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { asyncFileUpdateHandler(request, filename, index, data, len, final, U_FLASH); - - // if (!index) - // { - // Serial.printf("Update Start: %s\n", filename.c_str()); - - // // Update.runAsync(true); - // if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) - // { - // Update.printError(Serial); - // } - // } - // if (!Update.hasError()) - // { - // if (Update.write(data, len) != len) - // { - // Update.printError(Serial); - // } - // } - // if (final) - // { - // if (Update.end(true)) - // { - // Serial.printf("Update Success: %uB\n", index + len); - // onApiRestart(request); - // } - // else - // { - // Update.printError(Serial); - // } - // } } JsonDocument getStatusObject() @@ -260,7 +240,7 @@ JsonDocument getStatusObject() // root["espPsramSize"] = ESP.getPsramSize(); JsonObject conStatus = root["connectionStatus"].to(); - + conStatus["price"] = isPriceNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected(); @@ -313,25 +293,26 @@ JsonDocument getLedStatusObject() return root; } -void eventSourceUpdate() -{ - if (!events.count()) - return; - JsonDocument root = getStatusObject(); - JsonArray data = root["data"].to(); +void eventSourceUpdate() { + if (!events.count()) return; + + JsonDocument doc = getStatusObject(); + doc["leds"] = getLedStatusObject()["data"]; - root["leds"] = getLedStatusObject()["data"]; + // Get current EPD content directly as array + std::array epdContent = getCurrentEpdContent(); + + // Add EPD content arrays + JsonArray data = doc["data"].to(); + + // Copy array elements directly + for(const auto& content : epdContent) { + data.add(content); + } - String epdContent[NUM_SCREENS]; - std::array retEpdContent = getCurrentEpdContent(); - std::copy(std::begin(retEpdContent), std::end(retEpdContent), epdContent); - - copyArray(epdContent, data); - - String bufString; - serializeJson(root, bufString); - - events.send(bufString.c_str(), "status"); + String buffer; + serializeJson(doc, buffer); + events.send(buffer.c_str(), "status"); } /** @@ -341,21 +322,22 @@ void eventSourceUpdate() void onApiStatus(AsyncWebServerRequest *request) { AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); JsonDocument root = getStatusObject(); + + // Get current EPD content directly as array + std::array epdContent = getCurrentEpdContent(); + + // Add EPD content arrays JsonArray data = root["data"].to(); - JsonArray rendered = root["rendered"].to(); - String epdContent[NUM_SCREENS]; + + // Copy array elements directly + for(const auto& content : epdContent) { + data.add(content); + } root["leds"] = getLedStatusObject()["data"]; - - std::array retEpdContent = getCurrentEpdContent(); - - std::copy(std::begin(retEpdContent), std::end(retEpdContent), epdContent); - - copyArray(epdContent, data); - copyArray(epdContent, rendered); serializeJson(root, *response); request->send(response); @@ -368,7 +350,7 @@ void onApiStatus(AsyncWebServerRequest *request) void onApiActionPause(AsyncWebServerRequest *request) { setTimerActive(false); - request->send(200); + request->send(HTTP_OK); }; /** @@ -378,7 +360,7 @@ void onApiActionPause(AsyncWebServerRequest *request) void onApiActionTimerRestart(AsyncWebServerRequest *request) { setTimerActive(true); - request->send(200); + request->send(HTTP_OK); } /** @@ -392,7 +374,7 @@ void onApiFullRefresh(AsyncWebServerRequest *request) setEpdContent(newEpdContent, true); - request->send(200); + request->send(HTTP_OK); } /** @@ -407,28 +389,21 @@ void onApiShowScreen(AsyncWebServerRequest *request) uint currentScreen = p->value().toInt(); setCurrentScreen(currentScreen); } - request->send(200); + request->send(HTTP_OK); } /** * @Api * @Path("/api/screen/next") */ -void onApiScreenNext(AsyncWebServerRequest *request) -{ - nextScreen(); - request->send(200); -} - -/** - * @Api - * @Path("/api/screen/previous") - */ -void onApiScreenPrevious(AsyncWebServerRequest *request) -{ - previousScreen(); - - request->send(200); +void onApiScreenControl(AsyncWebServerRequest *request) { + const String& action = request->url(); + if (action.endsWith("/next")) { + nextScreen(); + } else if (action.endsWith("/previous")) { + previousScreen(); + } + request->send(HTTP_OK); } void onApiShowText(AsyncWebServerRequest *request) @@ -448,7 +423,7 @@ void onApiShowText(AsyncWebServerRequest *request) setEpdContent(textEpdContent); } setCurrentScreen(SCREEN_CUSTOM); - request->send(200); + request->send(HTTP_OK); } void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) @@ -466,7 +441,7 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) setEpdContent(epdContent); setCurrentScreen(SCREEN_CUSTOM); - request->send(200); + request->send(HTTP_OK); } void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) @@ -487,18 +462,20 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) if (settings.containsKey("fgColor")) { String fgColor = settings["fgColor"].as(); - preferences.putUInt("fgColor", strtol(fgColor.c_str(), NULL, 16)); - setFgColor(int(strtol(fgColor.c_str(), NULL, 16))); + uint32_t color = strtol(fgColor.c_str(), NULL, 16); + preferences.putUInt("fgColor", color); + setFgColor(color); Serial.print(F("Setting foreground color to ")); - Serial.println(strtol(fgColor.c_str(), NULL, 16)); + Serial.println(color); settingsChanged = true; } if (settings.containsKey("bgColor")) { String bgColor = settings["bgColor"].as(); - preferences.putUInt("bgColor", strtol(bgColor.c_str(), NULL, 16)); - setBgColor(int(strtol(bgColor.c_str(), NULL, 16))); + uint32_t color = strtol(bgColor.c_str(), NULL, 16); + preferences.putUInt("bgColor", color); + setBgColor(color); Serial.print(F("Setting background color to ")); Serial.println(bgColor.c_str()); settingsChanged = true; @@ -510,8 +487,6 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl"}; - for (String setting : strSettings) { if (settings.containsKey(setting)) @@ -522,7 +497,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } - String uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout", "srcV2Currency"}; + for (String setting : uintSettings) { @@ -542,16 +517,6 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) gmtOffset, settings["tzOffset"].as(), written); } - String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", - "mdnsEnabled", "otaEnabled", "stealFocus", - "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", - "mowMode", "suffixShareDot", "flOffWhenDark", - "flAlwaysOn", "flDisable", "flFlashOnUpd", - "mempoolSecure", "useNostr", "bitaxeEnabled", - "miningPoolStats", "verticalDesc", - "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; - for (String setting : boolSettings) { if (settings.containsKey(setting)) @@ -619,7 +584,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } - request->send(200); + request->send(HTTP_OK); if (settingsChanged) { queueLedEffect(LED_FLASH_SUCCESS); @@ -628,7 +593,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) void onApiRestart(AsyncWebServerRequest *request) { - request->send(200); + request->send(HTTP_OK); if (events.count()) events.send("closing"); @@ -642,7 +607,7 @@ void onApiIdentify(AsyncWebServerRequest *request) { queueLedEffect(LED_FLASH_IDENTIFY); - request->send(200); + request->send(HTTP_OK); } /** @@ -771,7 +736,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["poolLogosUrl"] = preferences.getString("poolLogosUrl", DEFAULT_MINING_POOL_LOGOS_URL); AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); serializeJson(root, *response); request->send(response); @@ -783,8 +748,9 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) if (request->hasParam("fgColor", true)) { const AsyncWebParameter *fgColor = request->getParam("fgColor", true); - preferences.putUInt("fgColor", strtol(fgColor->value().c_str(), NULL, 16)); - setFgColor(int(strtol(fgColor->value().c_str(), NULL, 16))); + uint32_t color = strtol(fgColor->value().c_str(), NULL, 16); + preferences.putUInt("fgColor", color); + setFgColor(color); // Serial.print(F("Setting foreground color to ")); // Serial.println(fgColor->value().c_str()); settingsChanged = true; @@ -793,8 +759,9 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) { const AsyncWebParameter *bgColor = request->getParam("bgColor", true); - preferences.putUInt("bgColor", strtol(bgColor->value().c_str(), NULL, 16)); - setBgColor(int(strtol(bgColor->value().c_str(), NULL, 16))); + uint32_t color = strtol(bgColor->value().c_str(), NULL, 16); + preferences.putUInt("bgColor", color); + setBgColor(color); // Serial.print(F("Setting background color to ")); // Serial.println(bgColor->value().c_str()); settingsChanged = true; @@ -806,7 +773,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) void onApiSystemStatus(AsyncWebServerRequest *request) { AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); JsonDocument root; @@ -848,19 +815,19 @@ void onApiSetWifiTxPower(AsyncWebServerRequest *request) if (WiFi.setTxPower(static_cast(txPower))) { preferences.putInt("txPower", txPower); - request->send(200, "application/json", "{\"setTxPower\": \"ok\"}"); + request->send(HTTP_OK, "application/json", "{\"setTxPower\": \"ok\"}"); return; } } } - return request->send(400); + return request->send(HTTP_BAD_REQUEST); } void onApiLightsStatus(AsyncWebServerRequest *request) { AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); serializeJson(getLedStatusObject()["data"], *response); @@ -870,7 +837,7 @@ void onApiLightsStatus(AsyncWebServerRequest *request) void onApiStopDataSources(AsyncWebServerRequest *request) { AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); stopPriceNotify(); stopBlockNotify(); @@ -881,7 +848,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request) void onApiRestartDataSources(AsyncWebServerRequest *request) { AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); restartPriceNotify(); restartBlockNotify(); @@ -894,7 +861,7 @@ void onApiRestartDataSources(AsyncWebServerRequest *request) void onApiLightsOff(AsyncWebServerRequest *request) { setLights(0, 0, 0); - request->send(200); + request->send(HTTP_OK); } void onApiLightsSetColor(AsyncWebServerRequest *request) @@ -902,7 +869,7 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) if (request->hasParam("c")) { AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); String rgbColor = request->getParam("c")->value(); @@ -926,7 +893,7 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) } else { - request->send(400); + request->send(HTTP_BAD_REQUEST); } } @@ -943,7 +910,7 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) } Serial.printf("Invalid values for LED set %d\n", lights.size()); - request->send(400); + request->send(HTTP_BAD_REQUEST); return; } @@ -964,14 +931,14 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) &green, &blue) == 3) { Serial.printf("Invalid hex for LED %d\n", i); - request->send(400); + request->send(HTTP_BAD_REQUEST); return; } } else { Serial.printf("No valid color for LED %d\n", i); - request->send(400); + request->send(HTTP_BAD_REQUEST); return; } @@ -982,7 +949,7 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) pixels.show(); saveLedState(); - request->send(200); + request->send(HTTP_OK); } void onIndex(AsyncWebServerRequest *request) @@ -992,48 +959,14 @@ void onIndex(AsyncWebServerRequest *request) void onNotFound(AsyncWebServerRequest *request) { - // Serial.printf("NotFound, URL[%s]\n", request->url()); - - // Serial.printf("NotFound, METHOD[%s]\n", request->methodToString()); - - // int headers = request->headers(); - // int i; - // for (i = 0; i < headers; i++) - // { - // AsyncWebHeader *h = request->getHeader(i); - // Serial.printf("NotFound HEADER[%s]: %s\n", h->name().c_str(), - // h->value().c_str()); - // } - - // int params = request->params(); - // for (int i = 0; i < params; i++) - // { - // const AsyncWebParameter *p = request->getParam(i); - // if (p->isFile()) - // { // p->isPost() is also true - // Serial.printf("NotFound FILE[%s]: %s, size: %u\n", - // p->name().c_str(), p->value().c_str(), p->size()); - // } - // else if (p->isPost()) - // { - // Serial.printf("NotFound POST[%s]: %s\n", p->name().c_str(), - // p->value().c_str()); - // } - // else - // { - // Serial.printf("NotFound GET[%s]: %s\n", p->name().c_str(), - // p->value().c_str()); - // } - // } - - // Access-Control-Request-Method == POST might be better + // Access-Control-Request-Method == POST might be better if (request->method() == HTTP_OPTIONS || request->hasHeader("Sec-Fetch-Mode")) { // Serial.printf("NotFound, Return[%d]\n", 200); - request->send(200); + request->send(HTTP_OK); } else { @@ -1069,7 +1002,7 @@ void onApiShowCurrency(AsyncWebServerRequest *request) setCurrentCurrency(curChar); setCurrentScreen(getCurrentScreen()); - request->send(200); + request->send(HTTP_OK); return; } request->send(404); @@ -1080,13 +1013,13 @@ void onApiFrontlightOn(AsyncWebServerRequest *request) { frontlightFadeInAll(); - request->send(200); + request->send(HTTP_OK); } void onApiFrontlightStatus(AsyncWebServerRequest *request) { AsyncResponseStream *response = - request->beginResponseStream("application/json"); + request->beginResponseStream(JSON_CONTENT); JsonDocument root; @@ -1105,7 +1038,7 @@ void onApiFrontlightFlash(AsyncWebServerRequest *request) { frontlightFlash(preferences.getUInt("flEffectDelay")); - request->send(200); + request->send(HTTP_OK); } void onApiFrontlightSetBrightness(AsyncWebServerRequest *request) @@ -1113,11 +1046,11 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request) if (request->hasParam("b")) { frontlightSetBrightness(request->getParam("b")->value().toInt()); - request->send(200); + request->send(HTTP_OK); } else { - request->send(400); + request->send(HTTP_BAD_REQUEST); } } @@ -1125,6 +1058,6 @@ void onApiFrontlightOff(AsyncWebServerRequest *request) { frontlightFadeOutAll(); - request->send(200); + request->send(HTTP_OK); } #endif \ No newline at end of file diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 6817baf..45fa854 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -28,8 +28,7 @@ void onApiStatus(AsyncWebServerRequest *request); void onApiSystemStatus(AsyncWebServerRequest *request); void onApiSetWifiTxPower(AsyncWebServerRequest *request); -void onApiScreenNext(AsyncWebServerRequest *request); -void onApiScreenPrevious(AsyncWebServerRequest *request); +void onApiScreenControl(AsyncWebServerRequest *request); void onApiShowScreen(AsyncWebServerRequest *request); void onApiShowCurrency(AsyncWebServerRequest *request); From 753838b122fbf7d00faa0ddbea6d3ebd2a0e8c62 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 00:15:08 +0100 Subject: [PATCH 132/188] Switch to new ArduinoJson methods --- src/lib/webserver.cpp | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index ee9a701..f1810cc 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -275,7 +275,6 @@ JsonDocument getLedStatusObject() for (uint i = 0; i < pixels.numPixels(); i++) { uint32_t pixColor = pixels.getPixelColor(pixels.numPixels() - i - 1); - uint alpha = (pixColor >> 24) & 0xFF; uint red = (pixColor >> 16) & 0xFF; uint green = (pixColor >> 8) & 0xFF; uint blue = pixColor & 0xFF; @@ -459,7 +458,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) bool settingsChanged = true; - if (settings.containsKey("fgColor")) + if (settings["fgColor"].is()) { String fgColor = settings["fgColor"].as(); uint32_t color = strtol(fgColor.c_str(), NULL, 16); @@ -469,7 +468,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) Serial.println(color); settingsChanged = true; } - if (settings.containsKey("bgColor")) + if (settings["bgColor"].is()) { String bgColor = settings["bgColor"].as(); @@ -481,7 +480,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settingsChanged = true; } - if (settings.containsKey("timePerScreen")) + if (settings["timePerScreen"].is()) { preferences.putUInt("timerSeconds", settings["timePerScreen"].as() * 60); @@ -489,11 +488,11 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) for (String setting : strSettings) { - if (settings.containsKey(setting)) + if (settings[setting].is()) { preferences.putString(setting.c_str(), settings[setting].as()); Serial.printf("Setting %s to %s\r\n", setting.c_str(), - settings[setting].as()); + settings[setting].as().c_str()); } } @@ -501,7 +500,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) for (String setting : uintSettings) { - if (settings.containsKey(setting)) + if (settings[setting].is()) { preferences.putUInt(setting.c_str(), settings[setting].as()); Serial.printf("Setting %s to %d\r\n", setting.c_str(), @@ -509,7 +508,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } - if (settings.containsKey("tzOffset")) + if (settings["tzOffset"].is()) { int gmtOffset = settings["tzOffset"].as() * 60; size_t written = preferences.putInt("gmtOffset", gmtOffset); @@ -519,15 +518,15 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) for (String setting : boolSettings) { - if (settings.containsKey(setting)) + if (settings[setting].is()) { - preferences.putBool(setting.c_str(), settings[setting].as()); + preferences.putBool(setting.c_str(), settings[setting].as()); Serial.printf("Setting %s to %d\r\n", setting.c_str(), - settings[setting].as()); + settings[setting].as()); } } - if (settings.containsKey("screens")) + if (settings["screens"].is()) { for (JsonVariant screen : settings["screens"].as()) { @@ -535,12 +534,12 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) uint id = s["id"].as(); String key = "screen[" + String(id) + "]"; String prefKey = "screen" + String(id) + "Visible"; - bool visible = s["enabled"].as(); + bool visible = s["enabled"].as(); preferences.putBool(prefKey.c_str(), visible); } } - if (settings.containsKey("actCurrencies")) + if (settings["actCurrencies"].is()) { String actCurrencies; @@ -554,10 +553,10 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } preferences.putString("actCurrencies", actCurrencies.c_str()); - Serial.printf("Set actCurrencies: %s\n", actCurrencies); + Serial.printf("Set actCurrencies: %s\n", actCurrencies.c_str()); } - if (settings.containsKey("txPower")) + if (settings["txPower"].is()) { int txPower = settings["txPower"].as(); @@ -918,14 +917,14 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { unsigned int red, green, blue; - if (lights[i].containsKey("red") && lights[i].containsKey("green") && - lights[i].containsKey("blue")) + if (lights[i]["red"].is() && lights[i]["green"].is() && + lights[i]["blue"].is()) { red = lights[i]["red"].as(); green = lights[i]["green"].as(); blue = lights[i]["blue"].as(); } - else if (lights[i].containsKey("hex")) + else if (lights[i]["hex"].is()) { if (!sscanf(lights[i]["hex"].as().c_str(), "#%02X%02X%02X", &red, &green, &blue) == 3) From 03dbb8add68d8b361b434462d7deb9b84731aa76 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 00:50:57 +0100 Subject: [PATCH 133/188] Small improvements for epd --- src/lib/epd.cpp | 141 ++++++++++++++---------------------------------- 1 file changed, 40 insertions(+), 101 deletions(-) diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 260f772..9b5bfd1 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -138,6 +138,9 @@ uint8_t qrcode[800]; #define EPD_TASK_STACK_SIZE 2048 #endif +#define BUSY_TIMEOUT_COUNT 200 +#define BUSY_RETRY_DELAY pdMS_TO_TICKS(10) + void forceFullRefresh() { for (uint i = 0; i < NUM_SCREENS; i++) @@ -417,89 +420,36 @@ void splitText(const uint dispNum, const String &top, const String &bottom, displays[dispNum].print(bottom); } -// void showChars(const uint dispNum, const String &chars, bool partial, -// const GFXfont *font) -// { -// displays[dispNum].setRotation(2); -// displays[dispNum].setFont(font); -// displays[dispNum].setTextColor(getFgColor()); -// int16_t tbx, tby; -// uint16_t tbw, tbh; +// Consolidate common display setup code into a helper function +void setupDisplay(const uint dispNum, const GFXfont *font) { + displays[dispNum].setRotation(2); + displays[dispNum].setFont(font); + displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].fillScreen(getBgColor()); +} -// displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); +void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) { + String str(chr); + if (chr == '.') { + str = "!"; + } + + setupDisplay(dispNum, font); + + int16_t tbx, tby; + uint16_t tbw, tbh; + displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); -// // center the bounding box by transposition of the origin: -// uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; -// uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; + uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; + uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; -// displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].setCursor(x, y); + displays[dispNum].print(str); -// displays[dispNum].setCursor(x, y); -// displays[dispNum].print(chars); - -// // displays[dispNum].setCursor(10, 3); -// // displays[dispNum].setFont(&FONT_SMALL); -// // displays[dispNum].setTextColor(getFgColor()); -// // displays[dispNum].println("Y = " + y); -// } - -void showDigit(const uint dispNum, char chr, bool partial, - const GFXfont *font) -{ - String str(chr); - - if (chr == '.') - { - str = "!"; - } - displays[dispNum].setRotation(2); - displays[dispNum].setFont(font); - displays[dispNum].setTextColor(getFgColor()); - int16_t tbx, tby; - uint16_t tbw, tbh; - - displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); - - // center the bounding box by transposition of the origin: - uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; - uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; - - // if (str.equals(".")) - // { - // // int16_t yAdvance = font->yAdvance; - // // uint8_t charIndex = 46 - font->first; - // // GFXglyph *glyph = (&font->glyph)[charIndex]; - // int16_t tbx2, tby2; - // uint16_t tbw2, tbh2; - // displays[dispNum].getTextBounds(".!", 0, 0, &tbx2, &tby2, &tbw2, &tbh2); - - // y = ((displays[dispNum].height() - tbh2) / 2) - tby2; - // // Serial.print("yAdvance"); - // // Serial.println(yAdvance); - // // if (glyph != nullptr) { - // // Serial.print("height"); - // // Serial.println(glyph->height); - // // Serial.print("yOffset"); - // // Serial.println(glyph->yOffset); - // // } - - // // y = 250-99+18+19; - // } - - displays[dispNum].fillScreen(getBgColor()); - - displays[dispNum].setCursor(x, y); - displays[dispNum].print(str); - - if (chr == '.') - { - displays[dispNum].fillRect(x, y, displays[dispNum].width(), round(displays[dispNum].height() * 0.9), getBgColor()); - } - - // displays[dispNum].setCursor(10, 3); - // displays[dispNum].setFont(&FONT_SMALL); - // displays[dispNum].setTextColor(getFgColor()); - // displays[dispNum].println("Y = " + y); + if (chr == '.') { + displays[dispNum].fillRect(x, y, displays[dispNum].width(), + round(displays[dispNum].height() * 0.9), getBgColor()); + } } int16_t calculateDescent(const GFXfont *font) { @@ -517,21 +467,16 @@ int16_t calculateDescent(const GFXfont *font) { void showChars(const uint dispNum, const String &chars, bool partial, const GFXfont *font) { - displays[dispNum].setRotation(2); - displays[dispNum].setFont(font); - displays[dispNum].setTextColor(getFgColor()); + setupDisplay(dispNum, font); + + int16_t tbx, tby; uint16_t tbw, tbh; displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); - int16_t descent = calculateDescent(font); - // center the bounding box by transposition of the origin: uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; - displays[dispNum].fillScreen(getBgColor()); - // displays[dispNum].setCursor(x, y); - // displays[dispNum].print(chars); for (int i = 0; i < chars.length(); i++) { char c = chars[i]; @@ -603,8 +548,8 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), displays[dispNum].height()); - displays[dispNum].fillScreen(getBgColor()); - displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].setTextColor(getFgColor()); uint iconIndex = 0; uint width = 122; @@ -649,9 +594,6 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) } - - - void renderQr(const uint dispNum, const String &text, bool partial) { #ifdef USE_QR @@ -695,15 +637,12 @@ void waitUntilNoneBusy() while (EPD_BUSY[i].digitalRead()) { count++; - vTaskDelay(10); - if (count == 200) - { - // displays[i].init(0, false); - vTaskDelay(100); - } - else if (count > 205) - { - Serial.printf("Busy timeout %d", i); + vTaskDelay(BUSY_RETRY_DELAY); + + if (count == BUSY_TIMEOUT_COUNT) { + vTaskDelay(pdMS_TO_TICKS(100)); + } else if (count > BUSY_TIMEOUT_COUNT + 5) { + log_e("Display %d busy timeout", i); break; } } From 9889e983eca45b9808eac76aedc73683bcce2967 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 00:51:26 +0100 Subject: [PATCH 134/188] Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index fd328d4..924be8f 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit fd328d4f05345eaa73cf27d05bb542eaa6915cdb +Subproject commit 924be8fc2eb02fe384a20b53da1a9fa3d8db8a05 From 8a818c66a063e9c2e8fe4eab86841e902fb76cfa Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 01:15:59 +0100 Subject: [PATCH 135/188] Update readme --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 71d3319..1962434 100644 --- a/README.md +++ b/README.md @@ -16,21 +16,21 @@ Biggest differences with v2 are: New features: - BitAxe integration -- Zap notifier -- Braiins Pool and Ocean mining stats integration +- Nostr Zap notifier +- Multiple mining pool stats integrations "Steal focus on new block" means that when a new block is mined, the display will switch to the block height screen if it's not on it already. -Most [information](https://github.com/btclock/btclock_v2/wiki) about BTClock v2 is still valid for this version. +See the [docs](https://git.btclock.dev/btclock/docs) repo for more information and building instructions. **NOTE**: The software assumes that the hardware is run in a controlled private network. ~~The Web UI and the OTA update mechanism are not password protected and accessible to anyone in the network. Also, since the device only fetches numbers through WebSockets it will skip server certificate verification to save resources.~~ Since 3.2.0 the WebUI is password protectable and all certificates are verified. OTA update mechanism is not password-protected. ## Building -Use PlatformIO to build it yourself. Make sure you fetch the [WebUI](https://github.com/btclock/webui) submodule. +Use PlatformIO to build it yourself. Make sure you fetch the [WebUI](https://git.btclock.dev/btclock/webui) submodule. -## Braiins Pool and Ocean integration +## Mining pool stats Enable mining pool stats by accessing your btclock's web UI (point a web browser at the device's IP address). Under Settings -> Extra Features: toggle Enable Mining Pool Stats. @@ -41,6 +41,8 @@ The Mining Pool Earnings screen displays: * Braiins: Today's mining reward thus far * Ocean: Your estimated earnings if the pool were to find a block right now +For solo mining pools, there are no earning estimations. Your username is the onchain withdrawal address, without the worker name. + ### Braiins Pool integration Create an API key based on the steps [here](https://academy.braiins.com/en/braiins-pool/monitoring/#api-configuration). From 4fdd6b6b4fe7387e7e6a99d855c31c3d2bb98c77 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 17:36:07 +0100 Subject: [PATCH 136/188] Fix button handling --- src/lib/button_handler.cpp | 58 +++++++++++++++++++++++--------------- src/lib/button_handler.hpp | 7 +++++ src/lib/config.cpp | 46 +++++++++++++++--------------- 3 files changed, 66 insertions(+), 45 deletions(-) diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index 72f1eaa..c2f2a21 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -19,41 +19,52 @@ TickType_t lastDebounceTime = 0; void buttonTask(void *parameter) { while (1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - std::lock_guard lock(mcpMutex); - + TickType_t currentTime = xTaskGetTickCount(); - + if ((currentTime - lastDebounceTime) >= debounceDelay) { lastDebounceTime = currentTime; - + + std::lock_guard lock(mcpMutex); + if (!digitalRead(MCP_INT_PIN)) { - uint pin = mcp1.getInterruptFlagRegister(); - - switch (pin) { - case BTN_1: - toggleTimerActive(); - break; - case BTN_2: - nextScreen(); - break; - case BTN_3: - previousScreen(); - break; - case BTN_4: - showSystemStatusScreen(); - break; - } + uint16_t intFlags = mcp1.getInterruptFlagRegister(); + uint16_t intCap = mcp1.getInterruptCaptureRegister(); + + // Check each button individually + if (intFlags & BTN_1) handleButton1(); + if (intFlags & BTN_2) handleButton2(); + if (intFlags & BTN_3) handleButton3(); + if (intFlags & BTN_4) handleButton4(); } - mcp1.getInterruptCaptureRegister(); - } else { } - // Very ugly, but for some reason this is necessary + + // Clear interrupt state while (!digitalRead(MCP_INT_PIN)) { + std::lock_guard lock(mcpMutex); mcp1.getInterruptCaptureRegister(); + delay(1); // Small delay to prevent tight loop } } } +// Helper functions to handle each button +void handleButton1() { + toggleTimerActive(); +} + +void handleButton2() { + nextScreen(); +} + +void handleButton3() { + previousScreen(); +} + +void handleButton4() { + showSystemStatusScreen(); +} + void IRAM_ATTR handleButtonInterrupt() { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xTaskNotifyFromISR(buttonTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); @@ -67,4 +78,5 @@ void setupButtonTask() { &buttonTaskHandle); // Create the FreeRTOS task // Use interrupt instead of task attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, CHANGE); + Serial.printf("Button task created\n"); } diff --git a/src/lib/button_handler.hpp b/src/lib/button_handler.hpp index 9f28d33..ed8ae38 100644 --- a/src/lib/button_handler.hpp +++ b/src/lib/button_handler.hpp @@ -8,6 +8,13 @@ extern TaskHandle_t buttonTaskHandle; +// Task and setup functions void buttonTask(void *pvParameters); void IRAM_ATTR handleButtonInterrupt(); void setupButtonTask(); + +// Individual button handlers +void handleButton1(); +void handleButton2(); +void handleButton3(); +void handleButton4(); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 9feb9a9..08f645e 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -466,32 +466,34 @@ void setupHardware() Wire.begin(I2C_SDA_PIN, I2C_SCK_PIN, 400000); - if (!mcp1.begin()) - { + if (!mcp1.begin()) { Serial.println(F("Error MCP23017 1")); - - // while (1) - // ; - } - else - { + } else { pinMode(MCP_INT_PIN, INPUT_PULLUP); -// mcp1.setupInterrupts(false, false, LOW); - mcp1.enableControlRegister(MCP23x17_IOCR_ODR); - - mcp1.mirrorInterrupts(true); - - for (int i = 0; i < 4; i++) - { - mcp1.pinMode1(i, INPUT_PULLUP); - mcp1.enableInterrupt(i, LOW); + + // Enable mirrored interrupts (both INTA and INTB pins signal any interrupt) + if (!mcp1.mirrorInterrupts(true)) { + Serial.println(F("Error setting up mirrored interrupts")); } -#ifndef IS_BTCLOCK_V8 - for (int i = 8; i <= 14; i++) - { - mcp1.pinMode1(i, OUTPUT); + + // Configure all 4 button pins as inputs with pullups and interrupts + for (int i = 0; i < 4; i++) { + if (!mcp1.pinMode1(i, INPUT_PULLUP)) { + Serial.printf("Error setting pin %d to input pull up\n", i); + } + // Enable interrupt on CHANGE for each pin + if (!mcp1.enableInterrupt(i, CHANGE)) { + Serial.printf("Error enabling interrupt for pin %d\n", i); + } } -#endif + + // Set interrupt pins as open drain with active-low polarity + if (!mcp1.setInterruptPolarity(2)) { // 2 = Open drain + Serial.println(F("Error setting interrupt polarity")); + } + + // Clear any pending interrupts + mcp1.getInterruptCaptureRegister(); } #ifdef IS_HW_REV_B From c44626cb42b8341aedd4d4cc3d4259acb53e6131 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 17:46:10 +0100 Subject: [PATCH 137/188] Unclutter main.cpp --- src/main.cpp | 252 ++++++++++++++++++++++++--------------------------- 1 file changed, 116 insertions(+), 136 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b9d2ae7..845de01 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,152 +22,132 @@ uint wifiLostConnection; uint priceNotifyLostConnection = 0; uint blockNotifyLostConnection = 0; -// char ptrTaskList[1500]; -extern "C" void app_main() -{ +int64_t getUptime() { + return esp_timer_get_time() / 1000000; +} + +void handlePriceNotifyDisconnection() { + if (priceNotifyLostConnection == 0) { + priceNotifyLostConnection = getUptime(); + Serial.println(F("Lost price notification connection, trying to reconnect...")); + } + + if ((getUptime() - priceNotifyLostConnection) > 300) { // 5 minutes timeout + Serial.println(F("Price notification connection lost for 5 minutes, restarting handler...")); + restartPriceNotify(); + priceNotifyLostConnection = 0; + } +} + +void handleBlockNotifyDisconnection() { + if (blockNotifyLostConnection == 0) { + blockNotifyLostConnection = getUptime(); + Serial.println(F("Lost block notification connection, trying to reconnect...")); + } + + if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout + Serial.println(F("Block notification connection lost for 5 minutes, restarting handler...")); + restartBlockNotify(); + blockNotifyLostConnection = 0; + } +} + +void handleFrontlight() { +#ifdef HAS_FRONTLIGHT + if (hasLightLevel() && preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { + uint lightLevel = getLightLevel(); + uint luxThreshold = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); + + if (lightLevel <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) { + if (frontlightIsOn()) frontlightFadeOutAll(); + } else if (lightLevel < luxThreshold && !frontlightIsOn()) { + frontlightFadeInAll(); + } else if (frontlightIsOn() && lightLevel > luxThreshold) { + frontlightFadeOutAll(); + } + } +#endif +} + +void checkWiFiConnection() { + if (!WiFi.isConnected()) { + if (!wifiLostConnection) { + wifiLostConnection = getUptime(); + Serial.println(F("Lost WiFi connection, trying to reconnect...")); + } + if ((getUptime() - wifiLostConnection) > 600) { + Serial.println(F("Still no connection after 10 minutes, restarting...")); + delay(2000); + ESP.restart(); + } + WiFi.begin(); + } else if (wifiLostConnection) { + wifiLostConnection = 0; + Serial.println(F("Connection restored, reset timer.")); + } +} + +void checkMissedBlocks() { + Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); + int currentBlock = getBlockFetch(); + if (currentBlock != -1) { + if (currentBlock != getBlockHeight()) { + Serial.println(F("Detected stuck block height... restarting block handler.")); + restartBlockNotify(); + } + setLastBlockUpdate(getUptime()); + } +} + + +void monitorDataConnections() { + // Price notification monitoring + if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) { + handlePriceNotifyDisconnection(); + } else if (priceNotifyLostConnection > 0 && isPriceNotifyConnected()) { + priceNotifyLostConnection = 0; + } + + // Block notification monitoring + if (getBlockNotifyInit() && !isBlockNotifyConnected()) { + handleBlockNotifyDisconnection(); + } else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) { + blockNotifyLostConnection = 0; + } + + // Check for missed price updates + if ((getLastPriceUpdate(CURRENCY_USD) - getUptime()) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) { + Serial.println(F("Detected 5 missed price updates... restarting price handler.")); + restartPriceNotify(); + priceNotifyLostConnection = 0; + } + + // Check for missed blocks + if ((getLastBlockUpdate() - getUptime()) > 45 * 60) { + checkMissedBlocks(); + } +} + +extern "C" void app_main() { initArduino(); - Serial.begin(115200); setup(); - while (true) - { - // vTaskList(ptrTaskList); - // Serial.println(F("**********************************")); - // Serial.println(F("Task State Prio Stack Num")); - // Serial.println(F("**********************************")); - // Serial.print(ptrTaskList); - // Serial.println(F("**********************************")); - if (eventSourceTaskHandle != NULL) + while (true) { + if (eventSourceTaskHandle != NULL) { xTaskNotifyGive(eventSourceTaskHandle); + } - int64_t currentUptime = esp_timer_get_time() / 1000000; - ; - - if (!getIsOTAUpdating()) - { -#ifdef HAS_FRONTLIGHT - if (hasLightLevel()) { - if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) - { - if (hasLightLevel() && getLightLevel() <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) - { - if (frontlightIsOn()) { - frontlightFadeOutAll(); - } - } - else if (hasLightLevel() && getLightLevel() < preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) && !frontlightIsOn()) - { - frontlightFadeInAll(); - } - else if (frontlightIsOn() && getLightLevel() > preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE)) - { - frontlightFadeOutAll(); - } - } - } -#endif + if (!getIsOTAUpdating()) { + handleFrontlight(); + checkWiFiConnection(); + monitorDataConnections(); - if (!WiFi.isConnected()) - { - if (!wifiLostConnection) - { - wifiLostConnection = currentUptime; - Serial.println(F("Lost WiFi connection, trying to reconnect...")); - } - - if ((currentUptime - wifiLostConnection) > 600) - { - Serial.println(F("Still no connection after 10 minutes, restarting...")); - delay(2000); - ESP.restart(); - } - - WiFi.begin(); - } - else if (wifiLostConnection) - { - wifiLostConnection = 0; - Serial.println(F("Connection restored, reset timer.")); - } - - if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) - { - priceNotifyLostConnection++; - Serial.println(F("Lost price data connection...")); - queueLedEffect(LED_DATA_PRICE_ERROR); - - // if price WS connection does not come back after 6*5 seconds, destroy and recreate - if (priceNotifyLostConnection > 6) - { - Serial.println(F("Restarting price handler...")); - - restartPriceNotify(); - // setupPriceNotify(); - priceNotifyLostConnection = 0; - } - } - else if (priceNotifyLostConnection > 0 && isPriceNotifyConnected()) - { - priceNotifyLostConnection = 0; - } - - if (getBlockNotifyInit() && !isBlockNotifyConnected()) - { - blockNotifyLostConnection++; - Serial.println(F("Lost block data connection...")); - queueLedEffect(LED_DATA_BLOCK_ERROR); - // if mempool WS connection does not come back after 6*5 seconds, destroy and recreate - if (blockNotifyLostConnection > 6) - { - Serial.println(F("Restarting block handler...")); - - restartBlockNotify(); - // setupBlockNotify(); - blockNotifyLostConnection = 0; - } - } - else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) - { - blockNotifyLostConnection = 0; - } - - // if more than 5 price updates are missed, there is probably something wrong, reconnect - if ((getLastPriceUpdate(CURRENCY_USD) - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5)) - { - Serial.println(F("Detected 5 missed price updates... restarting price handler.")); - - restartPriceNotify(); - // setupPriceNotify(); - - priceNotifyLostConnection = 0; - } - - // If after 45 minutes no mempool blocks, check the rest API - if ((getLastBlockUpdate() - currentUptime) > 45 * 60) - { - Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); - int currentBlock = getBlockFetch(); - if (currentBlock != -1) - { - if (currentBlock != getBlockHeight()) - { - Serial.println(F("Detected stuck block height... restarting block handler.")); - // Mempool source stuck, restart - restartBlockNotify(); - // setupBlockNotify(); - } - // set last block update so it doesn't fetch for 45 minutes - setLastBlockUpdate(currentUptime); - } - } - - if (currentUptime - getLastTimeSync() > 24 * 60 * 60) - { + if (getUptime() - getLastTimeSync() > 24 * 60 * 60) { Serial.println(F("Last time update is longer than 24 hours ago, sync again")); syncTime(); - }; + } } vTaskDelay(pdMS_TO_TICKS(5000)); From 957a947bc5e81565b9fb7fa422c347897ec7b50b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 21 Dec 2024 17:53:35 +0100 Subject: [PATCH 138/188] More clean-up and bugfixes --- src/lib/bitaxe_fetch.cpp | 2 +- src/lib/button_handler.cpp | 1 - src/lib/epd.cpp | 19 ------------------- src/lib/epd.hpp | 1 - src/lib/mining_pool_stats_fetch.cpp | 2 +- 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/lib/bitaxe_fetch.cpp b/src/lib/bitaxe_fetch.cpp index cfb7635..71ab90e 100644 --- a/src/lib/bitaxe_fetch.cpp +++ b/src/lib/bitaxe_fetch.cpp @@ -54,7 +54,7 @@ void taskBitaxeFetch(void *pvParameters) void setupBitaxeFetchTask() { - xTaskCreate(taskBitaxeFetch, "bitaxeFetch", (6 * 1024), NULL, tskIDLE_PRIORITY, + xTaskCreate(taskBitaxeFetch, "bitaxeFetch", (3 * 1024), NULL, tskIDLE_PRIORITY, &bitaxeFetchTaskHandle); xTaskNotifyGive(bitaxeFetchTaskHandle); diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index c2f2a21..61e6c6d 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -78,5 +78,4 @@ void setupButtonTask() { &buttonTaskHandle); // Create the FreeRTOS task // Use interrupt instead of task attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, CHANGE); - Serial.printf("Button task created\n"); } diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 9b5bfd1..528db1d 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -149,25 +149,6 @@ void forceFullRefresh() } } -void refreshFromMemory() -{ - for (uint i = 0; i < NUM_SCREENS; i++) - { - int *taskParam = new int; - *taskParam = i; - - xTaskCreate( - [](void *pvParameters) - { - const int epdIndex = *(int *)pvParameters; - delete (int *)pvParameters; - displays[epdIndex].refresh(false); - vTaskDelete(NULL); - }, - "PrepareUpd", 4096, taskParam, tskIDLE_PRIORITY, NULL); - } -} - void setupDisplays() { std::lock_guard lockMcp(mcpMutex); diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index c267047..35fcdd3 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -26,7 +26,6 @@ typedef struct { } UpdateDisplayTaskItem; void forceFullRefresh(); -void refreshFromMemory(); void setupDisplays(); void splitText(const uint dispNum, const String &top, const String &bottom, diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index 3f870bb..b8fed5c 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -93,7 +93,7 @@ void setupMiningPoolStatsFetchTask() "logoDownload", (6 * 1024), NULL, - 12, + tskIDLE_PRIORITY, NULL); xTaskCreate(taskMiningPoolStatsFetch, From 66c662e1fdf5ccfa072aac951a9fe8fd6abdffbd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 26 Dec 2024 21:43:50 +0100 Subject: [PATCH 139/188] Use own nostrduino fork, improve nostr code, WebUI update --- data | 2 +- platformio.ini | 8 +- src/lib/nostr_notify.cpp | 223 ++++++++++++++++++++++----------------- 3 files changed, 132 insertions(+), 101 deletions(-) diff --git a/data b/data index 924be8f..65b6df5 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 924be8fc2eb02fe384a20b53da1a9fa3d8db8a05 +Subproject commit 65b6df5d92f3f936e823a5c1413681aee85ab0c5 diff --git a/platformio.ini b/platformio.ini index be29cbe..90f1cc0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -43,7 +43,7 @@ lib_deps = https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 - rblb/Nostrduino@1.2.8 + https://github.com/dsbaars/nostrduino#feature/fix-btclock [env:lolin_s3_mini] extends = btclock_base @@ -71,7 +71,11 @@ board = btclock_rev_b board_build.partitions = partition_8mb.csv build_flags = ${btclock_base.build_flags} - -D MCP_INT_PIN=8 + -D MCP_INT_PIN=8docker run --rm -e RENOVATE_TOKEN -e LOG_LEVEL=debug \ + -e "RENOVATE_REPOSITORIES=[\"your-repo-name\"]" \ + renovate/renovate \ + --platform local \ + --dry-run -D NEOPIXEL_PIN=15 -D NEOPIXEL_COUNT=4 -D NUM_SCREENS=7 diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index bad593d..d19002c 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -27,8 +27,8 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) String pubKey = preferences.getString("nostrPubKey"); pools.push_back(pool); - std::vector>> filters; - + std::vector *relays = pool->getConnectedRelays(); + if (zapNotify) { subscribeZaps(pool, relay, 60); @@ -46,31 +46,29 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) }}, handleNostrEventCallback, onNostrSubscriptionClosed, - onNostrSubscriptionEose); + onNostrSubscriptionEose + ); - Serial.println("[ Nostr ] Subscribing to Nostr Data Feed"); + Serial.println(F("[ Nostr ] Subscribing to Nostr Data Feed")); } - std::vector *relays = pool->getConnectedRelays(); for (nostr::NostrRelay *relay : *relays) { Serial.println("[ Nostr ] Registering to connection events of: " + relay->getUrl()); - relay->getConnection()->addConnectionStatusListener([&](const nostr::ConnectionStatus &status) - { - String sstatus="UNKNOWN"; - if(status==nostr::ConnectionStatus::CONNECTED){ - nostrIsConnected = true; - sstatus="CONNECTED"; - }else if(status==nostr::ConnectionStatus::DISCONNECTED){ - nostrIsConnected = false; + relay->getConnection()->addConnectionStatusListener([](const nostr::ConnectionStatus &status) + { + static const char* STATUS_STRINGS[] = {"UNKNOWN", "CONNECTED", "DISCONNECTED", "ERROR"}; + int statusIndex = static_cast(status); + + nostrIsConnected = (status == nostr::ConnectionStatus::CONNECTED); + if (!nostrIsConnected) { nostrIsSubscribed = false; - sstatus="DISCONNECTED"; - }else if(status==nostr::ConnectionStatus::ERROR){ - sstatus = "ERROR"; } - Serial.println("[ Nostr ] Connection status changed: " + sstatus); - }); + + Serial.println("[ Nostr ] Connection status changed: " + String(STATUS_STRINGS[statusIndex])); + }); } + } catch (const std::exception &e) { @@ -103,7 +101,7 @@ void nostrTask(void *pvParameters) void setupNostrTask() { - xTaskCreate(nostrTask, "nostrTask", 16384, NULL, 10, &nostrTaskHandle); + xTaskCreate(nostrTask, "nostrTask", 8192, NULL, 10, &nostrTaskHandle); } boolean nostrConnected() @@ -129,55 +127,57 @@ void onNostrSubscriptionEose(const String &subId) void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event) { - // Received events callback, we can access the event content with - // event->getContent() Here you should handle the event, for this - // test we will just serialize it and print to console JsonDocument doc; JsonArray arr = doc["data"].to(); event->toSendableEvent(arr); - // Access the second element which is the object + + // Early return if array is invalid + if (arr.size() < 2 || !arr[1].is()) { + return; + } + JsonObject obj = arr[1].as(); JsonArray tags = obj["tags"].as(); + if (!tags) { + return; + } - // Flag to check if the tag was found - bool tagFound = false; - uint medianFee = 0; + // Use direct value access instead of multiple comparisons String typeValue; - - // Iterate over the tags array - for (JsonArray tag : tags) - { - // Check if the tag is an array with two elements - if (tag.size() == 2) - { - const char *key = tag[0]; - const char *value = tag[1]; - - // Check if the key is "type" and the value is "priceUsd" - if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0)) - { - typeValue = value; - tagFound = true; - } - else if (strcmp(key, "medianFee") == 0) - { - medianFee = tag[1].as(); - } + uint medianFee = 0; + + for (JsonArray tag : tags) { + if (tag.size() != 2) continue; + + const char *key = tag[0]; + if (!key) continue; + + // Use switch for better performance on string comparisons + switch (key[0]) { + case 't': // type + if (strcmp(key, "type") == 0) { + const char *value = tag[1]; + if (value) typeValue = value; + } + break; + case 'm': // medianFee + if (strcmp(key, "medianFee") == 0) { + medianFee = tag[1].as(); + } + break; } } - if (tagFound) - { - if (typeValue.equals("priceUsd")) - { + + // Process the data + if (!typeValue.isEmpty()) { + if (typeValue == "priceUsd") { processNewPrice(obj["content"].as(), CURRENCY_USD); } - else if (typeValue.equals("blockHeight")) - { + else if (typeValue == "blockHeight") { processNewBlock(obj["content"].as()); } - if (medianFee != 0) - { + if (medianFee != 0) { processNewBlockFee(medianFee); } } @@ -202,7 +202,27 @@ void subscribeZaps(nostr::NostrPool *pool, const String &relay, int minutesAgo) {"kinds", {"9735"}}, {"limit", {"1"}}, {"since", {String(getMinutesAgo(minutesAgo))}}, - {"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY)}}, + {"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY) }}, + // {"#p", [&]() { + // std::initializer_list pubkeys; + // String pubkeysStr = preferences.getString("nostrZapPubkeys", ""); + // if (pubkeysStr.length() > 0) { + // // Assuming pubkeys are comma-separated + // char* str = strdup(pubkeysStr.c_str()); + // char* token = strtok(str, ","); + // std::vector keys; + // while (token != NULL) { + // keys.push_back(String(token)); + // token = strtok(NULL, ","); + // } + // free(str); + // return std::initializer_list(keys.begin(), keys.end()); + // } + // // Return default if no pubkeys found + // return std::initializer_list{ + // preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY) + // }; + // }()}, }, }, handleNostrZapCallback, @@ -212,56 +232,63 @@ void subscribeZaps(nostr::NostrPool *pool, const String &relay, int minutesAgo) } void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) { - // Received events callback, we can access the event content with - // event->getContent() Here you should handle the event, for this - // test we will just serialize it and print to console JsonDocument doc; JsonArray arr = doc["data"].to(); event->toSendableEvent(arr); - // Access the second element which is the object + + // Early return if invalid + if (arr.size() < 2 || !arr[1].is()) { + return; + } + JsonObject obj = arr[1].as(); JsonArray tags = obj["tags"].as(); + if (!tags) { + return; + } - // Iterate over the tags array - for (JsonArray tag : tags) - { - // Check if the tag is an array with two elements - if (tag.size() == 2) - { - const char *key = tag[0]; - const char *value = tag[1]; + uint64_t zapAmount = 0; + String zapPubkey; + + for (JsonArray tag : tags) { + if (tag.size() != 2) continue; + + const char *key = tag[0]; + const char *value = tag[1]; + if (!key || !value) continue; - if (strcmp(key, "bolt11") == 0) - { - Serial.print(F("Got a zap of ")); - - int64_t satsAmount = getAmountInSatoshis(std::string(value)); - Serial.print(satsAmount); - Serial.println(F(" sats")); - - std::array textEpdContent = parseZapNotify(satsAmount, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); - - uint64_t timerPeriod = 0; - if (isTimerActive()) - { - // store timer periode before making inactive to prevent artifacts - timerPeriod = getTimerSeconds(); - esp_timer_stop(screenRotateTimer); - } - setCurrentScreen(SCREEN_CUSTOM); - - setEpdContent(textEpdContent); - vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); - if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) - { - queueLedEffect(LED_EFFECT_NOSTR_ZAP); - } - if (timerPeriod > 0) - { - esp_timer_start_periodic(screenRotateTimer, - timerPeriod * usPerSecond); - } - } + if (key[0] == 'b' && strcmp(key, "bolt11") == 0) { + zapAmount = getAmountInSatoshis(std::string(value)); + } + else if (key[0] == 'p' && strcmp(key, "p") == 0) { + zapPubkey = value; } } + + if (zapAmount == 0) return; + + std::array textEpdContent = parseZapNotify(zapAmount, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); + + Serial.printf("Got a zap of %llu sats for %s\n", zapAmount, zapPubkey.c_str()); + + uint64_t timerPeriod = 0; + if (isTimerActive()) + { + // store timer periode before making inactive to prevent artifacts + timerPeriod = getTimerSeconds(); + esp_timer_stop(screenRotateTimer); + } + setCurrentScreen(SCREEN_CUSTOM); + + setEpdContent(textEpdContent); + vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); + if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) + { + queueLedEffect(LED_EFFECT_NOSTR_ZAP); + } + if (timerPeriod > 0) + { + esp_timer_start_periodic(screenRotateTimer, + timerPeriod * usPerSecond); + } } \ No newline at end of file From 698c3a3a437d9e97e9855b60175975639813d3c9 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 26 Dec 2024 22:20:30 +0100 Subject: [PATCH 140/188] More code optimizations, remove unnecessary checks --- src/lib/led_handler.cpp | 21 +++++++++------------ src/lib/led_handler.hpp | 2 +- src/lib/nostr_notify.cpp | 4 +--- src/lib/v2_notify.cpp | 8 +++++--- src/main.cpp | 9 ++++++++- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 338c3c3..c2c56f4 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -6,7 +6,7 @@ Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); uint ledTaskParams; #ifdef HAS_FRONTLIGHT -#define FL_FADE_STEP 25 +constexpr uint16_t FL_FADE_STEP = 25; bool frontlightOn = false; bool flInTransition = false; @@ -68,18 +68,15 @@ void frontlightFadeInAll(int flDelayTime) void frontlightFadeInAll(int flDelayTime, bool staggered) { - if (preferences.getBool("flDisable")) - return; - if (frontlightIsOn()) - return; - if (flInTransition) + if (preferences.getBool("flDisable") || frontlightIsOn() || flInTransition) return; flInTransition = true; + const int maxBrightness = preferences.getUInt("flMaxBrightness"); + if (staggered) { - int maxBrightness = preferences.getUInt("flMaxBrightness"); int step = FL_FADE_STEP; int staggerDelay = flDelayTime / NUM_SCREENS; @@ -100,7 +97,7 @@ void frontlightFadeInAll(int flDelayTime, bool staggered) } else { - for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += FL_FADE_STEP) + for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { @@ -179,7 +176,7 @@ std::vector frontlightGetStatus() return statuses; } -bool frontlightIsOn() +inline bool frontlightIsOn() { return frontlightOn; } @@ -225,7 +222,7 @@ void ledTask(void *parameter) continue; } - uint32_t oldLights[NEOPIXEL_COUNT]; + std::array oldLights; // get current state for (int i = 0; i < NEOPIXEL_COUNT; i++) @@ -498,7 +495,7 @@ void blinkDelay(int d, int times) pixels.show(); } -void blinkDelayColor(int d, int times, uint r, uint g, uint b) +void blinkDelayColor(int d, int times, uint r, uint g, uint b) { for (int j = 0; j < times; j++) { @@ -518,7 +515,7 @@ void blinkDelayColor(int d, int times, uint r, uint g, uint b) pixels.show(); } -void blinkDelayTwoColor(int d, int times, uint32_t c1, uint32_t c2) +void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2) { for (int j = 0; j < times; j++) { diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index b86475e..98d8834 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -49,7 +49,7 @@ void setupLeds(); void setupLedTask(); void blinkDelay(int d, int times); void blinkDelayColor(int d, int times, uint r, uint g, uint b); -void blinkDelayTwoColor(int d, int times, uint32_t c1, uint32_t c2); +void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2); void clearLeds(); void saveLedState(); void restoreLedState(); diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index d19002c..a919e0e 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -63,9 +63,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) nostrIsConnected = (status == nostr::ConnectionStatus::CONNECTED); if (!nostrIsConnected) { nostrIsSubscribed = false; - } - - Serial.println("[ Nostr ] Connection status changed: " + String(STATUS_STRINGS[statusIndex])); + } }); } diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 33104a5..e0b5966 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -34,11 +34,12 @@ namespace V2Notify switch (type) { case WStype_DISCONNECTED: - Serial.printf("[WSc] Disconnected!\n"); + Serial.print(F("[WSc] Disconnected!\n")); break; case WStype_CONNECTED: { - Serial.printf("[WSc] Connected to url: %s\n", payload); + Serial.print(F("[WSc] Connected to url:")); + Serial.println((char *)payload); JsonDocument response; @@ -81,7 +82,8 @@ namespace V2Notify break; } case WStype_TEXT: - Serial.printf("[WSc] get text: %s\n", payload); + Serial.print(F("[WSc] get text: ")); + Serial.println((char *)payload); // send message to server // webSocket.sendTXT("message here"); diff --git a/src/main.cpp b/src/main.cpp index 845de01..ea24213 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,6 +102,7 @@ void checkMissedBlocks() { void monitorDataConnections() { + // Price notification monitoring if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) { handlePriceNotifyDisconnection(); @@ -134,6 +135,9 @@ extern "C" void app_main() { Serial.begin(115200); setup(); + bool ownDataSource = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); + + while (true) { if (eventSourceTaskHandle != NULL) { xTaskNotifyGive(eventSourceTaskHandle); @@ -142,7 +146,10 @@ extern "C" void app_main() { if (!getIsOTAUpdating()) { handleFrontlight(); checkWiFiConnection(); - monitorDataConnections(); + + if (!ownDataSource) { + monitorDataConnections(); + } if (getUptime() - getLastTimeSync() > 24 * 60 * 60) { Serial.println(F("Last time update is longer than 24 hours ago, sync again")); From 17fef80253137d39c08156fd04f4988ee43b8f0f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 26 Dec 2024 23:08:46 +0100 Subject: [PATCH 141/188] Make ScreenHandler class, fix button handling --- src/lib/bitaxe_fetch.cpp | 2 +- src/lib/block_notify.cpp | 4 +- src/lib/button_handler.cpp | 160 +++++--- src/lib/button_handler.hpp | 34 ++ src/lib/config.cpp | 6 +- src/lib/mining_pool_stats_fetch.cpp | 4 +- src/lib/nostr_notify.cpp | 2 +- src/lib/price_notify.cpp | 6 +- src/lib/screen_handler.cpp | 614 +++++++++++++--------------- src/lib/screen_handler.hpp | 44 +- src/lib/webserver.cpp | 18 +- 11 files changed, 484 insertions(+), 410 deletions(-) diff --git a/src/lib/bitaxe_fetch.cpp b/src/lib/bitaxe_fetch.cpp index 71ab90e..f439cb4 100644 --- a/src/lib/bitaxe_fetch.cpp +++ b/src/lib/bitaxe_fetch.cpp @@ -36,7 +36,7 @@ void taskBitaxeFetch(void *pvParameters) bitaxeHashrate = std::to_string(static_cast(std::round(doc["hashRate"].as()))); bitaxeBestDiff = doc["bestDiff"].as(); - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BITAXE_HASHRATE || getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) { WorkItem priceUpdate = {TASK_BITAXE_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 48989bd..e41fc4b 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -200,7 +200,7 @@ void processNewBlock(uint newBlockHeight) { xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); // xTaskNotifyGive(blockUpdateTaskHandle); - if (getCurrentScreen() != SCREEN_BLOCK_HEIGHT && + if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) { uint64_t timerPeriod = 0; @@ -210,7 +210,7 @@ void processNewBlock(uint newBlockHeight) { timerPeriod = getTimerSeconds(); esp_timer_stop(screenRotateTimer); } - setCurrentScreen(SCREEN_BLOCK_HEIGHT); + ScreenHandler::setCurrentScreen(SCREEN_BLOCK_HEIGHT); if (timerPeriod > 0) { esp_timer_start_periodic(screenRotateTimer, diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index 61e6c6d..ff7e259 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -1,8 +1,7 @@ #include "button_handler.hpp" TaskHandle_t buttonTaskHandle = NULL; -const TickType_t debounceDelay = pdMS_TO_TICKS(50); -TickType_t lastDebounceTime = 0; +ButtonState buttonStates[4]; #ifdef IS_BTCLOCK_V8 #define BTN_1 256 @@ -17,65 +16,134 @@ TickType_t lastDebounceTime = 0; #endif void buttonTask(void *parameter) { - while (1) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + TickType_t currentTime = xTaskGetTickCount(); + + std::lock_guard lock(mcpMutex); + + if (!digitalRead(MCP_INT_PIN)) { + uint16_t intFlags = mcp1.getInterruptFlagRegister(); + uint16_t intCap = mcp1.getInterruptCaptureRegister(); + + // Check button states + if (intFlags & BTN_1) handleButtonPress(0); + if (intFlags & BTN_2) handleButtonPress(1); + if (intFlags & BTN_3) handleButtonPress(2); + if (intFlags & BTN_4) handleButtonPress(3); + + // Check for button releases + for (int i = 0; i < 4; i++) { + if (buttonStates[i].isPressed) { + bool currentlyPressed = false; + switch (i) { + case 0: currentlyPressed = (intCap & BTN_1); break; + case 1: currentlyPressed = (intCap & BTN_2); break; + case 2: currentlyPressed = (intCap & BTN_3); break; + case 3: currentlyPressed = (intCap & BTN_4); break; + } + if (!currentlyPressed) { + handleButtonRelease(i); + } + } + } + + // Check for long press on pressed buttons + for (int i = 0; i < 4; i++) { + if (buttonStates[i].isPressed && !buttonStates[i].longPressHandled) { + if ((currentTime - buttonStates[i].pressStartTime) >= longPressDelay) { + handleLongPress(i); + buttonStates[i].longPressHandled = true; + } + } + } + } + + // Clear interrupt state + while (!digitalRead(MCP_INT_PIN)) { + mcp1.getInterruptCaptureRegister(); + delay(1); + } + } +} + +void handleButtonPress(int buttonIndex) { TickType_t currentTime = xTaskGetTickCount(); + ButtonState &state = buttonStates[buttonIndex]; - if ((currentTime - lastDebounceTime) >= debounceDelay) { - lastDebounceTime = currentTime; - - std::lock_guard lock(mcpMutex); - - if (!digitalRead(MCP_INT_PIN)) { - uint16_t intFlags = mcp1.getInterruptFlagRegister(); - uint16_t intCap = mcp1.getInterruptCaptureRegister(); - - // Check each button individually - if (intFlags & BTN_1) handleButton1(); - if (intFlags & BTN_2) handleButton2(); - if (intFlags & BTN_3) handleButton3(); - if (intFlags & BTN_4) handleButton4(); - } + if ((currentTime - state.lastPressTime) >= debounceDelay) { + state.isPressed = true; + state.pressStartTime = currentTime; + state.longPressHandled = false; + + // Check for double click + if ((currentTime - state.lastPressTime) <= doubleClickDelay) { + state.clickCount++; + if (state.clickCount == 2) { + handleDoubleClick(buttonIndex); + state.clickCount = 0; + } + } else { + state.clickCount = 1; + } + + state.lastPressTime = currentTime; } +} + +void handleButtonRelease(int buttonIndex) { + TickType_t currentTime = xTaskGetTickCount(); + ButtonState &state = buttonStates[buttonIndex]; - // Clear interrupt state - while (!digitalRead(MCP_INT_PIN)) { - std::lock_guard lock(mcpMutex); - mcp1.getInterruptCaptureRegister(); - delay(1); // Small delay to prevent tight loop + state.isPressed = false; + + // If this wasn't a long press or double click, handle as single click + if (!state.longPressHandled && state.clickCount == 1 && + (currentTime - state.pressStartTime) < longPressDelay) { + handleSingleClick(buttonIndex); + state.clickCount = 0; } - } } -// Helper functions to handle each button -void handleButton1() { - toggleTimerActive(); +// Button action handlers +void handleSingleClick(int buttonIndex) { + switch (buttonIndex) { + case 0: + toggleTimerActive(); + break; + case 1: + Serial.println("Button 2 single click"); + ScreenHandler::nextScreen(); + break; + case 2: + Serial.println("Button 3 single click"); + ScreenHandler::previousScreen(); + break; + case 3: + ScreenHandler::showSystemStatusScreen(); + break; + } } -void handleButton2() { - nextScreen(); +void handleDoubleClick(int buttonIndex) { + Serial.printf("Button %d double clicked\n", buttonIndex + 1); } -void handleButton3() { - previousScreen(); -} - -void handleButton4() { - showSystemStatusScreen(); +void handleLongPress(int buttonIndex) { + Serial.printf("Button %d long press detected\n", buttonIndex + 1); } void IRAM_ATTR handleButtonInterrupt() { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xTaskNotifyFromISR(buttonTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); - } + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xTaskNotifyFromISR(buttonTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } } void setupButtonTask() { - xTaskCreate(buttonTask, "ButtonTask", 3072, NULL, tskIDLE_PRIORITY, - &buttonTaskHandle); // Create the FreeRTOS task - // Use interrupt instead of task - attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, CHANGE); + xTaskCreate(buttonTask, "ButtonTask", 3072, NULL, tskIDLE_PRIORITY, + &buttonTaskHandle); + attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, FALLING); } diff --git a/src/lib/button_handler.hpp b/src/lib/button_handler.hpp index ed8ae38..f3f41a1 100644 --- a/src/lib/button_handler.hpp +++ b/src/lib/button_handler.hpp @@ -18,3 +18,37 @@ void handleButton1(); void handleButton2(); void handleButton3(); void handleButton4(); + +// New features +const TickType_t debounceDelay = pdMS_TO_TICKS(50); +const TickType_t doubleClickDelay = pdMS_TO_TICKS(300); // Maximum time between clicks for double click +const TickType_t longPressDelay = pdMS_TO_TICKS(1000); // Time to hold for long press + +// Track timing for each button +struct ButtonState { + TickType_t lastPressTime = 0; + TickType_t pressStartTime = 0; + bool isPressed = false; + uint8_t clickCount = 0; + bool longPressHandled = false; +}; + +extern ButtonState buttonStates[4]; + +#ifdef IS_BTCLOCK_V8 +#define BTN_1 256 +#define BTN_2 512 +#define BTN_3 1024 +#define BTN_4 2048 +#else +#define BTN_1 2048 +#define BTN_2 1024 +#define BTN_3 512 +#define BTN_4 256 +#endif + +void handleButtonPress(int buttonIndex); +void handleButtonRelease(int buttonIndex); +void handleSingleClick(int buttonIndex); +void handleDoubleClick(int buttonIndex); +void handleLongPress(int buttonIndex); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 08f645e..4ae7e4b 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -285,9 +285,9 @@ void setupPreferences() setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) - setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD)); + ScreenHandler::setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD)); else - setCurrentCurrency(CURRENCY_USD); + ScreenHandler::setCurrentCurrency(CURRENCY_USD); if (!preferences.isKey("flDisable")) { preferences.putBool("flDisable", isWhiteVersion() ? false : true); @@ -482,7 +482,7 @@ void setupHardware() Serial.printf("Error setting pin %d to input pull up\n", i); } // Enable interrupt on CHANGE for each pin - if (!mcp1.enableInterrupt(i, CHANGE)) { + if (!mcp1.enableInterrupt(i, FALLING)) { Serial.printf("Error enabling interrupt for pin %d\n", i); } } diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index b8fed5c..9687e03 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -57,7 +57,7 @@ void taskMiningPoolStatsFetch(void *pvParameters) miningPoolStatsDailyEarnings = 0; // or any other default value } - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) { WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); @@ -78,7 +78,7 @@ void downloadMiningPoolLogoTask(void *pvParameters) { PoolFactory::downloadPoolLogo(poolName, poolInterface.get()); // If we're on the mining pool stats screen, trigger a display update - if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { + if (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index a919e0e..20bdd01 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -276,7 +276,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) timerPeriod = getTimerSeconds(); esp_timer_stop(screenRotateTimer); } - setCurrentScreen(SCREEN_CUSTOM); + ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); setEpdContent(textEpdContent); vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index ab5192b..780f651 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -133,9 +133,9 @@ void processNewPrice(uint newPrice, char currency) } lastUpdateMap[currency] = currentTime; // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || - getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || - getCurrentScreen() == SCREEN_MARKET_CAP)) + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER || + ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || + ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP)) { WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index ea55e02..8c7213c 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -1,343 +1,315 @@ #include "screen_handler.hpp" -// TaskHandle_t priceUpdateTaskHandle; -// TaskHandle_t blockUpdateTaskHandle; -// TaskHandle_t timeUpdateTaskHandle; TaskHandle_t taskScreenRotateTaskHandle; TaskHandle_t workerTaskHandle; - - -std::array taskEpdContent = {}; -std::string priceString; - -#define WORK_QUEUE_SIZE 10 QueueHandle_t workQueue = NULL; -uint currentScreen = SCREEN_BLOCK_HEIGHT; -uint currentCurrency = CURRENCY_USD; +// Initialize static members +uint ScreenHandler::currentScreen = SCREEN_BLOCK_HEIGHT; +uint ScreenHandler::currentCurrency = CURRENCY_USD; -void workerTask(void *pvParameters) { - WorkItem receivedItem; +std::array taskEpdContent = {}; - while (1) { - // Wait for a work item to be available in the queue - if (xQueueReceive(workQueue, &receivedItem, portMAX_DELAY)) { - // Process the work item based on its type - switch (receivedItem.type) { - case TASK_BITAXE_UPDATE: { - if (getCurrentScreen() == SCREEN_BITAXE_HASHRATE) { - taskEpdContent = - parseBitaxeHashRate(getBitAxeHashRate()); - } else if (getCurrentScreen() == SCREEN_BITAXE_BESTDIFF) { - taskEpdContent = - parseBitaxeBestDiff(getBitaxeBestDiff()); - } - setEpdContent(taskEpdContent); - break; - } - case TASK_MINING_POOL_STATS_UPDATE: { - if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { - taskEpdContent = - parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()); - } else if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS) { - taskEpdContent = - parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), getMiningPool()->getDailyEarningsLabel(), *getMiningPool()); - } - setEpdContent(taskEpdContent); - break; - } - case TASK_PRICE_UPDATE: { - uint currency = getCurrentCurrency(); - uint price = getPrice(currency); - - if (getCurrentScreen() == SCREEN_BTC_TICKER) { - taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE), - preferences.getBool("mowMode", DEFAULT_MOW_MODE), - preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT) - ); - } else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) { - taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); - } else { - taskEpdContent = - parseMarketCap(getBlockHeight(), price, currency, - preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); - } - - setEpdContent(taskEpdContent); - break; - } - case TASK_FEE_UPDATE: { - if (getCurrentScreen() == SCREEN_BLOCK_FEE_RATE) { - taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); - setEpdContent(taskEpdContent); - } - break; - } - case TASK_BLOCK_UPDATE: { - if (getCurrentScreen() != SCREEN_HALVING_COUNTDOWN) { - taskEpdContent = parseBlockHeight(getBlockHeight()); - } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); - } - - if (getCurrentScreen() == SCREEN_HALVING_COUNTDOWN || - getCurrentScreen() == SCREEN_BLOCK_HEIGHT) { - setEpdContent(taskEpdContent); - } - break; - } - case TASK_TIME_UPDATE: { - if (getCurrentScreen() == SCREEN_TIME) { - time_t currentTime; - struct tm timeinfo; - time(¤tTime); - localtime_r(¤tTime, &timeinfo); - std::string timeString; - - String minute = String(timeinfo.tm_min); - if (minute.length() < 2) { - minute = "0" + minute; - } - - timeString = - std::to_string(timeinfo.tm_hour) + ":" + minute.c_str(); - timeString.insert(timeString.begin(), - NUM_SCREENS - timeString.length(), ' '); - taskEpdContent[0] = std::to_string(timeinfo.tm_mday) + "/" + - std::to_string(timeinfo.tm_mon + 1); - - for (uint i = 1; i < NUM_SCREENS; i++) { - taskEpdContent[i] = timeString[i]; - } - setEpdContent(taskEpdContent); - } - - break; - } - // Add more cases for additional task types - } +// Convert existing functions to static member functions +void ScreenHandler::setCurrentScreen(uint newScreen) { + if (newScreen != SCREEN_CUSTOM) { + preferences.putUInt("currentScreen", newScreen); + } + currentScreen = newScreen; + + switch (currentScreen) { + case SCREEN_TIME: { + WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; + xQueueSend(workQueue, &timeUpdate, portMAX_DELAY); + break; + } + case SCREEN_HALVING_COUNTDOWN: + case SCREEN_BLOCK_HEIGHT: { + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + break; + } + case SCREEN_MARKET_CAP: + case SCREEN_SATS_PER_CURRENCY: + case SCREEN_BTC_TICKER: { + WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; + xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); + break; + } + case SCREEN_BLOCK_FEE_RATE: { + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + break; + } + case SCREEN_BITAXE_BESTDIFF: + case SCREEN_BITAXE_HASHRATE: { + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { + WorkItem bitaxeUpdate = {TASK_BITAXE_UPDATE, 0}; + xQueueSend(workQueue, &bitaxeUpdate, portMAX_DELAY); + } else { + setCurrentScreen(SCREEN_BLOCK_HEIGHT); + return; + } + break; + } + case SCREEN_MINING_POOL_STATS_HASHRATE: + case SCREEN_MINING_POOL_STATS_EARNINGS: { + if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { + WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; + xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY); + } else { + setCurrentScreen(SCREEN_BLOCK_HEIGHT); + return; + } + break; + } + } + + if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); +} + +void ScreenHandler::setCurrentCurrency(char currency) { + currentCurrency = currency; + preferences.putUChar("lastCurrency", currency); +} + +bool ScreenHandler::isCurrencySpecific(uint screen) { + switch (screen) { + case SCREEN_BTC_TICKER: + case SCREEN_SATS_PER_CURRENCY: + case SCREEN_MARKET_CAP: + return true; + default: + return false; + } +} + +bool ScreenHandler::handleCurrencyRotation(bool forward) { + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { + std::vector ac = getActiveCurrencies(); + if (ac.empty()) return false; + + std::string curCode = getCurrencyCode(getCurrentCurrency()); + auto it = std::find(ac.begin(), ac.end(), curCode); + + if (it == ac.end()) { + // Current currency not found in active currencies - initialize based on direction + setCurrentCurrency(getCurrencyChar(forward ? ac.front() : ac.back())); + setCurrentScreen(getCurrentScreen()); + return true; + } else if (forward && curCode != ac.back()) { + // Moving forward and not at last currency + setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) + 1))); + setCurrentScreen(getCurrentScreen()); + return true; + } else if (!forward && curCode != ac.front()) { + // Moving backward and not at first currency + setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) - 1))); + setCurrentScreen(getCurrentScreen()); + return true; + } + return false; + } + return false; +} + +int ScreenHandler::findNextVisibleScreen(int currentScreen, bool forward) { + std::vector screenMappings = getScreenNameMap(); + int newScreen; + + if (forward) { + newScreen = (currentScreen < screenMappings.size() - 1) ? + screenMappings[currentScreen + 1].value : screenMappings.front().value; + } else { + newScreen = (currentScreen > 0) ? + screenMappings[currentScreen - 1].value : screenMappings.back().value; + } + + String key = "screen" + String(newScreen) + "Visible"; + while (!preferences.getBool(key.c_str(), true)) { + currentScreen = findScreenIndexByValue(newScreen); + if (forward) { + newScreen = (currentScreen < screenMappings.size() - 1) ? + screenMappings[currentScreen + 1].value : screenMappings.front().value; + } else { + newScreen = (currentScreen > 0) ? + screenMappings[currentScreen - 1].value : screenMappings.back().value; + } + key = "screen" + String(newScreen) + "Visible"; + } + + return newScreen; +} + +void ScreenHandler::nextScreen() { + if (handleCurrencyRotation(true)) return; + int currentIndex = findScreenIndexByValue(getCurrentScreen()); + setCurrentScreen(findNextVisibleScreen(currentIndex, true)); +} + +void ScreenHandler::previousScreen() { + if (handleCurrencyRotation(false)) return; + int currentIndex = findScreenIndexByValue(getCurrentScreen()); + setCurrentScreen(findNextVisibleScreen(currentIndex, false)); +} + +void ScreenHandler::showSystemStatusScreen() { + std::array sysStatusEpdContent; + std::fill(sysStatusEpdContent.begin(), sysStatusEpdContent.end(), ""); + + String ipAddr = WiFi.localIP().toString(); + String subNet = WiFi.subnetMask().toString(); + + sysStatusEpdContent[0] = "IP/Subnet"; + + int ipAddrPos = 0; + int subnetPos = 0; + for (int i = 0; i < 4; i++) { + sysStatusEpdContent[1 + i] = ipAddr.substring(0, ipAddr.indexOf('.')) + + "/" + subNet.substring(0, subNet.indexOf('.')); + ipAddrPos = ipAddr.indexOf('.') + 1; + subnetPos = subNet.indexOf('.') + 1; + ipAddr = ipAddr.substring(ipAddrPos); + subNet = subNet.substring(subnetPos); + } + sysStatusEpdContent[NUM_SCREENS - 2] = "RAM/Status"; + + sysStatusEpdContent[NUM_SCREENS - 1] = + String((int)round(ESP.getFreeHeap() / 1024)) + "/" + + (int)round(ESP.getHeapSize() / 1024); + setCurrentScreen(SCREEN_CUSTOM); + setEpdContent(sysStatusEpdContent); +} + +// Keep these as free functions +void workerTask(void *pvParameters) { + WorkItem receivedItem; + + while (1) { + if (xQueueReceive(workQueue, &receivedItem, portMAX_DELAY)) { + uint currentScreenValue = ScreenHandler::getCurrentScreen(); + + switch (receivedItem.type) { + case TASK_BITAXE_UPDATE: { + if (currentScreenValue != SCREEN_BITAXE_HASHRATE && + currentScreenValue != SCREEN_BITAXE_BESTDIFF) break; + + taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ? + parseBitaxeHashRate(getBitAxeHashRate()) : + parseBitaxeBestDiff(getBitaxeBestDiff()); + setEpdContent(taskEpdContent); + break; + } + + case TASK_MINING_POOL_STATS_UPDATE: { + if (currentScreenValue != SCREEN_MINING_POOL_STATS_HASHRATE && + currentScreenValue != SCREEN_MINING_POOL_STATS_EARNINGS) break; + + taskEpdContent = (currentScreenValue == SCREEN_MINING_POOL_STATS_HASHRATE) ? + parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()) : + parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), + getMiningPool()->getDailyEarningsLabel(), *getMiningPool()); + setEpdContent(taskEpdContent); + break; + } + + case TASK_PRICE_UPDATE: { + uint currency = ScreenHandler::getCurrentCurrency(); + uint price = getPrice(currency); + + if (currentScreenValue == SCREEN_BTC_TICKER) { + taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE), + preferences.getBool("mowMode", DEFAULT_MOW_MODE), + preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT) + ); + } else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) { + taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); + } else { + taskEpdContent = + parseMarketCap(getBlockHeight(), price, currency, + preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); + } + + setEpdContent(taskEpdContent); + break; + } + case TASK_FEE_UPDATE: { + if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { + taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); + setEpdContent(taskEpdContent); + } + break; + } + case TASK_BLOCK_UPDATE: { + if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) { + taskEpdContent = parseBlockHeight(getBlockHeight()); + } else { + taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); + } + + if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || + currentScreenValue == SCREEN_BLOCK_HEIGHT) { + setEpdContent(taskEpdContent); + } + break; + } + case TASK_TIME_UPDATE: { + if (currentScreenValue == SCREEN_TIME) { + time_t currentTime; + struct tm timeinfo; + time(¤tTime); + localtime_r(¤tTime, &timeinfo); + std::string timeString; + + String minute = String(timeinfo.tm_min); + if (minute.length() < 2) { + minute = "0" + minute; + } + + timeString = + std::to_string(timeinfo.tm_hour) + ":" + minute.c_str(); + timeString.insert(timeString.begin(), + NUM_SCREENS - timeString.length(), ' '); + taskEpdContent[0] = std::to_string(timeinfo.tm_mday) + "/" + + std::to_string(timeinfo.tm_mon + 1); + + for (uint i = 1; i < NUM_SCREENS; i++) { + taskEpdContent[i] = timeString[i]; + } + setEpdContent(taskEpdContent); + } + + break; + } + // Add more cases for additional task types + } + } } - } } void taskScreenRotate(void *pvParameters) { - for (;;) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - nextScreen(); - } + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + ScreenHandler::nextScreen(); + } } void setupTasks() { - workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem)); + workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem)); - xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY, - &workerTaskHandle); + xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY, + &workerTaskHandle); - xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, - &taskScreenRotateTaskHandle); + xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, + &taskScreenRotateTaskHandle); - waitUntilNoneBusy(); + waitUntilNoneBusy(); - if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) - setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); + if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) + ScreenHandler::setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } -uint getCurrentScreen() { return currentScreen; } - -void setCurrentScreen(uint newScreen) { - if (newScreen != SCREEN_CUSTOM) { - preferences.putUInt("currentScreen", newScreen); - } - - currentScreen = newScreen; - - switch (currentScreen) { - case SCREEN_TIME: { - WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; - xQueueSend(workQueue, &timeUpdate, portMAX_DELAY); - // xTaskNotifyGive(timeUpdateTaskHandle); - break; - } - case SCREEN_HALVING_COUNTDOWN: - case SCREEN_BLOCK_HEIGHT: { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - // xTaskNotifyGive(blockUpdateTaskHandle); - break; - } - case SCREEN_MARKET_CAP: - case SCREEN_SATS_PER_CURRENCY: - case SCREEN_BTC_TICKER: { - WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; - xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); - // xTaskNotifyGive(priceUpdateTaskHandle); - break; - } - case SCREEN_BLOCK_FEE_RATE: { - WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - break; - } - case SCREEN_BITAXE_BESTDIFF: - case SCREEN_BITAXE_HASHRATE: { - if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { - WorkItem bitaxeUpdate = {TASK_BITAXE_UPDATE, 0}; - xQueueSend(workQueue, &bitaxeUpdate, portMAX_DELAY); - } else { - setCurrentScreen(SCREEN_BLOCK_HEIGHT); - return; - } - break; - } - case SCREEN_MINING_POOL_STATS_HASHRATE: - case SCREEN_MINING_POOL_STATS_EARNINGS: { - if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { - WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; - xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY); - } else { - setCurrentScreen(SCREEN_BLOCK_HEIGHT); - return; - } - break; - } - } - - if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); -} - -bool isCurrencySpecific(uint screen) { - switch (screen) { - case SCREEN_BTC_TICKER: - case SCREEN_SATS_PER_CURRENCY: - case SCREEN_MARKET_CAP: - return true; - default: - return false; - } -} - -void nextScreen() { - int currentIndex = findScreenIndexByValue(getCurrentScreen()); - std::vector screenMappings = getScreenNameMap(); - - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { - std::vector ac = getActiveCurrencies(); - std::string curCode = getCurrencyCode(getCurrentCurrency()); - if (getCurrencyCode(getCurrentCurrency()) != ac.back()) { - auto it = std::find(ac.begin(), ac.end(), curCode); - if (it != ac.end()) { - size_t index = std::distance(ac.begin(), it); - setCurrentCurrency(getCurrencyChar(ac.at(index+1))); - setCurrentScreen(getCurrentScreen()); - return; - } - } - setCurrentCurrency(getCurrencyChar(ac.front())); - } - - int newCurrentScreen; - - if (currentIndex < screenMappings.size() - 1) { - newCurrentScreen = (screenMappings[currentIndex + 1].value); - } else { - newCurrentScreen = screenMappings.front().value; - } - - String key = "screen" + String(newCurrentScreen) + "Visible"; - - while (!preferences.getBool(key.c_str(), true)) { - currentIndex = findScreenIndexByValue(newCurrentScreen); - if (currentIndex < screenMappings.size() - 1) { - newCurrentScreen = (screenMappings[currentIndex + 1].value); - } else { - newCurrentScreen = screenMappings.front().value; - } - - key = "screen" + String(newCurrentScreen) + "Visible"; - } - - setCurrentScreen(newCurrentScreen); -} - -void previousScreen() { - int currentIndex = findScreenIndexByValue(getCurrentScreen()); - std::vector screenMappings = getScreenNameMap(); - - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { - std::vector ac = getActiveCurrencies(); - std::string curCode = getCurrencyCode(getCurrentCurrency()); - if (getCurrencyCode(getCurrentCurrency()) != ac.front()) { - auto it = std::find(ac.begin(), ac.end(), curCode); - if (it != ac.end()) { - size_t index = std::distance(ac.begin(), it); - setCurrentCurrency(getCurrencyChar(ac.at(index-1))); - setCurrentScreen(getCurrentScreen()); - return; - } - } - setCurrentCurrency(getCurrencyChar(ac.back())); - - } - - - int newCurrentScreen; - - if (currentIndex > 0) { - newCurrentScreen = screenMappings[currentIndex - 1].value; - } else { - newCurrentScreen = screenMappings.back().value; - } - - String key = "screen" + String(newCurrentScreen) + "Visible"; - - while (!preferences.getBool(key.c_str(), true)) { - int currentIndex = findScreenIndexByValue(newCurrentScreen); - if (currentIndex > 0) { - newCurrentScreen = screenMappings[currentIndex - 1].value; - } else { - newCurrentScreen = screenMappings.back().value; - } - - key = "screen" + String(newCurrentScreen) + "Visible"; - } - setCurrentScreen(newCurrentScreen); -} - -void showSystemStatusScreen() { - std::array sysStatusEpdContent; - std::fill(sysStatusEpdContent.begin(), sysStatusEpdContent.end(), ""); - - - String ipAddr = WiFi.localIP().toString(); - String subNet = WiFi.subnetMask().toString(); - - sysStatusEpdContent[0] = "IP/Subnet"; - - int ipAddrPos = 0; - int subnetPos = 0; - for (int i = 0; i < 4; i++) { - sysStatusEpdContent[1 + i] = ipAddr.substring(0, ipAddr.indexOf('.')) + - "/" + subNet.substring(0, subNet.indexOf('.')); - ipAddrPos = ipAddr.indexOf('.') + 1; - subnetPos = subNet.indexOf('.') + 1; - ipAddr = ipAddr.substring(ipAddrPos); - subNet = subNet.substring(subnetPos); - } - sysStatusEpdContent[NUM_SCREENS - 2] = "RAM/Status"; - - sysStatusEpdContent[NUM_SCREENS - 1] = - String((int)round(ESP.getFreeHeap() / 1024)) + "/" + - (int)round(ESP.getHeapSize() / 1024); - setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(sysStatusEpdContent); -} - -void setCurrentCurrency(char currency) { - currentCurrency = currency; - preferences.putUChar("lastCurrency", currency); -} - -uint getCurrentCurrency() { - return currentCurrency; +void cleanup() { + vQueueDelete(workQueue); + // Add any other cleanup needed } \ No newline at end of file diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index fcdf2ba..914af09 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -11,12 +11,10 @@ #include "lib/epd.hpp" #include "lib/shared.hpp" -// extern TaskHandle_t priceUpdateTaskHandle; -// extern TaskHandle_t blockUpdateTaskHandle; -// extern TaskHandle_t timeUpdateTaskHandle; +#define WORK_QUEUE_SIZE 10 + extern TaskHandle_t workerTaskHandle; extern TaskHandle_t taskScreenRotateTaskHandle; - extern QueueHandle_t workQueue; typedef enum { @@ -33,24 +31,26 @@ typedef struct { char data; } WorkItem; +class ScreenHandler { +private: + static uint currentScreen; + static uint currentCurrency; + +public: + static uint getCurrentScreen() { return currentScreen; } + static uint getCurrentCurrency() { return currentCurrency; } + static void setCurrentScreen(uint newScreen); + static void setCurrentCurrency(char currency); + static void nextScreen(); + static void previousScreen(); + static void showSystemStatusScreen(); + static bool isCurrencySpecific(uint screen); + static bool handleCurrencyRotation(bool forward); + static int findNextVisibleScreen(int currentScreen, bool forward); +}; + +// Keep as free functions since they deal with FreeRTOS tasks void workerTask(void *pvParameters); -uint getCurrentScreen(); -void setCurrentScreen(uint newScreen); -void nextScreen(); -void previousScreen(); - -void showSystemStatusScreen(); - - - -// void taskPriceUpdate(void *pvParameters); -// void taskBlockUpdate(void *pvParameters); -// void taskTimeUpdate(void *pvParameters); void taskScreenRotate(void *pvParameters); - - - void setupTasks(); -void setCurrentCurrency(char currency); - -uint getCurrentCurrency(); \ No newline at end of file +void cleanup(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index f1810cc..4a8a8f5 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -228,7 +228,7 @@ JsonDocument getStatusObject() { JsonDocument root; - root["currentScreen"] = getCurrentScreen(); + root["currentScreen"] = ScreenHandler::getCurrentScreen(); root["numScreens"] = NUM_SCREENS; root["timerRunning"] = isTimerActive(); root["espUptime"] = esp_timer_get_time() / 1000000; @@ -248,7 +248,7 @@ JsonDocument getStatusObject() conStatus["nostr"] = nostrConnected(); root["rssi"] = WiFi.RSSI(); - root["currency"] = getCurrencyCode(getCurrentCurrency()); + root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency()); #ifdef HAS_FRONTLIGHT std::vector statuses = frontlightGetStatus(); uint16_t arr[NUM_SCREENS]; @@ -386,7 +386,7 @@ void onApiShowScreen(AsyncWebServerRequest *request) { const AsyncWebParameter *p = request->getParam("s"); uint currentScreen = p->value().toInt(); - setCurrentScreen(currentScreen); + ScreenHandler::setCurrentScreen(currentScreen); } request->send(HTTP_OK); } @@ -398,9 +398,9 @@ void onApiShowScreen(AsyncWebServerRequest *request) void onApiScreenControl(AsyncWebServerRequest *request) { const String& action = request->url(); if (action.endsWith("/next")) { - nextScreen(); + ScreenHandler::nextScreen(); } else if (action.endsWith("/previous")) { - previousScreen(); + ScreenHandler::previousScreen(); } request->send(HTTP_OK); } @@ -421,7 +421,7 @@ void onApiShowText(AsyncWebServerRequest *request) setEpdContent(textEpdContent); } - setCurrentScreen(SCREEN_CUSTOM); + ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); } @@ -439,7 +439,7 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) setEpdContent(epdContent); - setCurrentScreen(SCREEN_CUSTOM); + ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); } @@ -998,8 +998,8 @@ void onApiShowCurrency(AsyncWebServerRequest *request) char curChar = getCurrencyChar(currency); - setCurrentCurrency(curChar); - setCurrentScreen(getCurrentScreen()); + ScreenHandler::setCurrentCurrency(curChar); + ScreenHandler::setCurrentScreen(ScreenHandler::getCurrentScreen()); request->send(HTTP_OK); return; From cff6131fc4df948376b8d037bf2adf20e0318430 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 27 Dec 2024 00:05:45 +0100 Subject: [PATCH 142/188] Update WebUI and convert ButtonHandler to class --- data | 2 +- src/lib/button_handler.cpp | 63 +++++++--------------------------- src/lib/button_handler.hpp | 70 +++++++++++++++++++------------------- src/lib/config.cpp | 4 +-- src/lib/ota.cpp | 7 ++-- 5 files changed, 53 insertions(+), 93 deletions(-) diff --git a/data b/data index 65b6df5..2ce53eb 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 65b6df5d92f3f936e823a5c1413681aee85ab0c5 +Subproject commit 2ce53eb499e00a990be5cb0ea078e146f467ceb4 diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index ff7e259..bd4204d 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -1,7 +1,8 @@ #include "button_handler.hpp" -TaskHandle_t buttonTaskHandle = NULL; -ButtonState buttonStates[4]; +// Initialize static members +TaskHandle_t ButtonHandler::buttonTaskHandle = NULL; +ButtonState ButtonHandler::buttonStates[4] = {}; #ifdef IS_BTCLOCK_V8 #define BTN_1 256 @@ -15,7 +16,7 @@ ButtonState buttonStates[4]; #define BTN_4 256 #endif -void buttonTask(void *parameter) { +void ButtonHandler::buttonTask(void *parameter) { while (1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); @@ -48,16 +49,6 @@ void buttonTask(void *parameter) { } } } - - // Check for long press on pressed buttons - for (int i = 0; i < 4; i++) { - if (buttonStates[i].isPressed && !buttonStates[i].longPressHandled) { - if ((currentTime - buttonStates[i].pressStartTime) >= longPressDelay) { - handleLongPress(i); - buttonStates[i].longPressHandled = true; - } - } - } } // Clear interrupt state @@ -68,56 +59,34 @@ void buttonTask(void *parameter) { } } -void handleButtonPress(int buttonIndex) { +void ButtonHandler::handleButtonPress(int buttonIndex) { TickType_t currentTime = xTaskGetTickCount(); ButtonState &state = buttonStates[buttonIndex]; if ((currentTime - state.lastPressTime) >= debounceDelay) { state.isPressed = true; - state.pressStartTime = currentTime; - state.longPressHandled = false; - - // Check for double click - if ((currentTime - state.lastPressTime) <= doubleClickDelay) { - state.clickCount++; - if (state.clickCount == 2) { - handleDoubleClick(buttonIndex); - state.clickCount = 0; - } - } else { - state.clickCount = 1; - } - state.lastPressTime = currentTime; } } -void handleButtonRelease(int buttonIndex) { - TickType_t currentTime = xTaskGetTickCount(); +void ButtonHandler::handleButtonRelease(int buttonIndex) { ButtonState &state = buttonStates[buttonIndex]; - state.isPressed = false; + if (!state.isPressed) return; // Ignore if button wasn't pressed - // If this wasn't a long press or double click, handle as single click - if (!state.longPressHandled && state.clickCount == 1 && - (currentTime - state.pressStartTime) < longPressDelay) { - handleSingleClick(buttonIndex); - state.clickCount = 0; - } + state.isPressed = false; + handleSingleClick(buttonIndex); } -// Button action handlers -void handleSingleClick(int buttonIndex) { +void ButtonHandler::handleSingleClick(int buttonIndex) { switch (buttonIndex) { case 0: toggleTimerActive(); break; case 1: - Serial.println("Button 2 single click"); ScreenHandler::nextScreen(); break; case 2: - Serial.println("Button 3 single click"); ScreenHandler::previousScreen(); break; case 3: @@ -126,15 +95,7 @@ void handleSingleClick(int buttonIndex) { } } -void handleDoubleClick(int buttonIndex) { - Serial.printf("Button %d double clicked\n", buttonIndex + 1); -} - -void handleLongPress(int buttonIndex) { - Serial.printf("Button %d long press detected\n", buttonIndex + 1); -} - -void IRAM_ATTR handleButtonInterrupt() { +void IRAM_ATTR ButtonHandler::handleButtonInterrupt() { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xTaskNotifyFromISR(buttonTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); if (xHigherPriorityTaskWoken == pdTRUE) { @@ -142,7 +103,7 @@ void IRAM_ATTR handleButtonInterrupt() { } } -void setupButtonTask() { +void ButtonHandler::setup() { xTaskCreate(buttonTask, "ButtonTask", 3072, NULL, tskIDLE_PRIORITY, &buttonTaskHandle); attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, FALLING); diff --git a/src/lib/button_handler.hpp b/src/lib/button_handler.hpp index f3f41a1..17fab6b 100644 --- a/src/lib/button_handler.hpp +++ b/src/lib/button_handler.hpp @@ -6,24 +6,6 @@ #include "lib/shared.hpp" #include "lib/timers.hpp" -extern TaskHandle_t buttonTaskHandle; - -// Task and setup functions -void buttonTask(void *pvParameters); -void IRAM_ATTR handleButtonInterrupt(); -void setupButtonTask(); - -// Individual button handlers -void handleButton1(); -void handleButton2(); -void handleButton3(); -void handleButton4(); - -// New features -const TickType_t debounceDelay = pdMS_TO_TICKS(50); -const TickType_t doubleClickDelay = pdMS_TO_TICKS(300); // Maximum time between clicks for double click -const TickType_t longPressDelay = pdMS_TO_TICKS(1000); // Time to hold for long press - // Track timing for each button struct ButtonState { TickType_t lastPressTime = 0; @@ -33,22 +15,40 @@ struct ButtonState { bool longPressHandled = false; }; -extern ButtonState buttonStates[4]; +class ButtonHandler { +private: + static const TickType_t debounceDelay = pdMS_TO_TICKS(50); + static const TickType_t doubleClickDelay = pdMS_TO_TICKS(1000); // Maximum time between clicks for double click + static const TickType_t longPressDelay = pdMS_TO_TICKS(1500); // Time to hold for long press -#ifdef IS_BTCLOCK_V8 -#define BTN_1 256 -#define BTN_2 512 -#define BTN_3 1024 -#define BTN_4 2048 -#else -#define BTN_1 2048 -#define BTN_2 1024 -#define BTN_3 512 -#define BTN_4 256 -#endif + static ButtonState buttonStates[4]; + static TaskHandle_t buttonTaskHandle; -void handleButtonPress(int buttonIndex); -void handleButtonRelease(int buttonIndex); -void handleSingleClick(int buttonIndex); -void handleDoubleClick(int buttonIndex); -void handleLongPress(int buttonIndex); + // Button handlers + static void handleButtonPress(int buttonIndex); + static void handleButtonRelease(int buttonIndex); + static void handleSingleClick(int buttonIndex); + static void handleDoubleClick(int buttonIndex); + static void handleLongPress(int buttonIndex); + + // Task function + static void buttonTask(void *pvParameters); + +public: + static void setup(); + static void IRAM_ATTR handleButtonInterrupt(); + static void suspendTask() { if (buttonTaskHandle != NULL) vTaskSuspend(buttonTaskHandle); } + static void resumeTask() { if (buttonTaskHandle != NULL) vTaskResume(buttonTaskHandle); } + + #ifdef IS_BTCLOCK_V8 + static const uint16_t BTN_1 = 256; + static const uint16_t BTN_2 = 512; + static const uint16_t BTN_3 = 1024; + static const uint16_t BTN_4 = 2048; + #else + static const uint16_t BTN_1 = 2048; + static const uint16_t BTN_2 = 1024; + static const uint16_t BTN_3 = 512; + static const uint16_t BTN_4 = 256; + #endif +}; diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 4ae7e4b..4c7303c 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -100,7 +100,7 @@ void setup() setupMiningPoolStatsFetchTask(); } - setupButtonTask(); + ButtonHandler::setup(); setupOTA(); waitUntilNoneBusy(); @@ -482,7 +482,7 @@ void setupHardware() Serial.printf("Error setting pin %d to input pull up\n", i); } // Enable interrupt on CHANGE for each pin - if (!mcp1.enableInterrupt(i, FALLING)) { + if (!mcp1.enableInterrupt(i, CHANGE)) { Serial.printf("Error enabling interrupt for pin %d\n", i); } } diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 05c45e1..0d6a57f 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -64,11 +64,10 @@ void onOTAStart() // Stop or suspend all tasks // vTaskSuspend(priceUpdateTaskHandle); // vTaskSuspend(blockUpdateTaskHandle); - vTaskSuspend(workerTaskHandle); vTaskSuspend(taskScreenRotateTaskHandle); - -// vTaskSuspend(ledTaskHandle); - vTaskSuspend(buttonTaskHandle); + vTaskSuspend(workerTaskHandle); + vTaskSuspend(eventSourceTaskHandle); + ButtonHandler::suspendTask(); // stopWebServer(); stopBlockNotify(); From 10fe5b505354ec9e0b048c6ab89fd690eae11a20 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 27 Dec 2024 00:41:41 +0100 Subject: [PATCH 143/188] Fix function definition and rev.b definition --- platformio.ini | 6 +----- src/lib/led_handler.cpp | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 90f1cc0..0dcacd0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -71,11 +71,7 @@ board = btclock_rev_b board_build.partitions = partition_8mb.csv build_flags = ${btclock_base.build_flags} - -D MCP_INT_PIN=8docker run --rm -e RENOVATE_TOKEN -e LOG_LEVEL=debug \ - -e "RENOVATE_REPOSITORIES=[\"your-repo-name\"]" \ - renovate/renovate \ - --platform local \ - --dry-run + -D MCP_INT_PIN=8 -D NEOPIXEL_PIN=15 -D NEOPIXEL_COUNT=4 -D NUM_SCREENS=7 diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index c2c56f4..42ef5e5 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -176,7 +176,7 @@ std::vector frontlightGetStatus() return statuses; } -inline bool frontlightIsOn() +bool frontlightIsOn() { return frontlightOn; } From 4140b05a7dbaf662f507b91fa691d36933b04f05 Mon Sep 17 00:00:00 2001 From: Ticktock Depbot Date: Fri, 27 Dec 2024 08:28:10 +0000 Subject: [PATCH 144/188] Add renovate.json --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..5db72dd --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} From b7ff9d810123efefa67d79c676e163d236c83993 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 28 Dec 2024 13:40:08 +0100 Subject: [PATCH 145/188] Better handling of unexpected pool stats responses, add CKPool --- src/lib/mining_pool/braiins/brains_pool.cpp | 47 +++++++++------ src/lib/mining_pool/ckpool/ckpool.cpp | 47 +++++++++++++++ src/lib/mining_pool/ckpool/ckpool.hpp | 25 ++++++++ src/lib/mining_pool/ckpool/eu_ckpool.hpp | 16 ++++++ .../noderunners/noderunners_pool.cpp | 57 +++++++++++++------ src/lib/mining_pool/ocean/ocean_pool.cpp | 31 ++++++---- src/lib/mining_pool/pool_factory.cpp | 6 +- src/lib/mining_pool/pool_factory.hpp | 8 ++- .../mining_pool/public_pool/public_pool.cpp | 27 ++++++--- .../satoshi_radio/satoshi_radio_pool.cpp | 3 +- 10 files changed, 210 insertions(+), 57 deletions(-) create mode 100644 src/lib/mining_pool/ckpool/ckpool.cpp create mode 100644 src/lib/mining_pool/ckpool/ckpool.hpp create mode 100644 src/lib/mining_pool/ckpool/eu_ckpool.hpp diff --git a/src/lib/mining_pool/braiins/brains_pool.cpp b/src/lib/mining_pool/braiins/brains_pool.cpp index 47b6e61..6449b29 100644 --- a/src/lib/mining_pool/braiins/brains_pool.cpp +++ b/src/lib/mining_pool/braiins/brains_pool.cpp @@ -1,32 +1,43 @@ #include "brains_pool.hpp" -void BraiinsPool::prepareRequest(HTTPClient& http) const { +void BraiinsPool::prepareRequest(HTTPClient &http) const +{ http.addHeader("Pool-Auth-Token", poolUser.c_str()); } -std::string BraiinsPool::getApiUrl() const { +std::string BraiinsPool::getApiUrl() const +{ return "https://pool.braiins.com/accounts/profile/json/btc/"; } PoolStats BraiinsPool::parseResponse(const JsonDocument &doc) const { - if (doc["btc"].isNull()) { + try + { + if (doc["btc"].isNull()) + { + return PoolStats{ + .hashrate = "0", + .dailyEarnings = 0}; + } + + std::string unit = doc["btc"]["hash_rate_unit"].as(); + + static const std::unordered_map multipliers = { + {"Zh/s", 21}, {"Eh/s", 18}, {"Ph/s", 15}, {"Th/s", 12}, {"Gh/s", 9}, {"Mh/s", 6}, {"Kh/s", 3}}; + + int multiplier = multipliers.at(unit); + float hashValue = doc["btc"]["hash_rate_5m"].as(); + + return PoolStats{ + .hashrate = std::to_string(static_cast(std::round(hashValue))) + std::string(multiplier, '0'), + .dailyEarnings = static_cast(doc["btc"]["today_reward"].as() * 100000000)}; + } + catch (const std::exception &e) + { + Serial.printf("Error parsing %s response: %s\n", getPoolName().c_str(), e.what()); return PoolStats{ .hashrate = "0", - .dailyEarnings = 0 - }; + .dailyEarnings = std::nullopt}; } - - std::string unit = doc["btc"]["hash_rate_unit"].as(); - - static const std::unordered_map multipliers = { - {"Zh/s", 21}, {"Eh/s", 18}, {"Ph/s", 15}, {"Th/s", 12}, {"Gh/s", 9}, {"Mh/s", 6}, {"Kh/s", 3}}; - - int multiplier = multipliers.at(unit); - float hashValue = doc["btc"]["hash_rate_5m"].as(); - - return PoolStats{ - .hashrate = std::to_string(static_cast(std::round(hashValue))) + std::string(multiplier, '0'), - .dailyEarnings = static_cast(doc["btc"]["today_reward"].as() * 100000000)}; } - diff --git a/src/lib/mining_pool/ckpool/ckpool.cpp b/src/lib/mining_pool/ckpool/ckpool.cpp new file mode 100644 index 0000000..4c17c7d --- /dev/null +++ b/src/lib/mining_pool/ckpool/ckpool.cpp @@ -0,0 +1,47 @@ +#include "ckpool.hpp" + +void CKPool::prepareRequest(HTTPClient &http) const +{ + // Empty as CKPool doesn't need special headers +} + +std::string CKPool::getApiUrl() const +{ + return getBaseUrl() + "/users/" + poolUser; +} + +PoolStats CKPool::parseResponse(const JsonDocument &doc) const +{ + try + { + std::string hashrateStr = doc["hashrate1m"].as(); + + // Special case for "0" + if (hashrateStr == "0") { + return PoolStats{ + .hashrate = "0", + .dailyEarnings = std::nullopt + }; + } + + char unit = hashrateStr.back(); + std::string value = hashrateStr.substr(0, hashrateStr.size() - 1); + + int multiplier = getHashrateMultiplier(unit); + double hashrate = std::stod(value) * std::pow(10, multiplier); + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.0f", hashrate); + + return PoolStats{ + .hashrate = buffer, + .dailyEarnings = std::nullopt}; + } + catch (const std::exception &e) + { + Serial.printf("Error parsing %s response: %s\n", getPoolName().c_str(), e.what()); + return PoolStats{ + .hashrate = "0", + .dailyEarnings = std::nullopt}; + } +} \ No newline at end of file diff --git a/src/lib/mining_pool/ckpool/ckpool.hpp b/src/lib/mining_pool/ckpool/ckpool.hpp new file mode 100644 index 0000000..bbf75d1 --- /dev/null +++ b/src/lib/mining_pool/ckpool/ckpool.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "lib/mining_pool/mining_pool_interface.hpp" +#include + +class CKPool : public MiningPoolInterface { +public: + void setPoolUser(const std::string& user) override { poolUser = user; } + + void prepareRequest(HTTPClient& http) const override; + std::string getApiUrl() const override; + PoolStats parseResponse(const JsonDocument& doc) const override; + bool supportsDailyEarnings() const override { return false; } + std::string getDailyEarningsLabel() const override { return ""; } + bool hasLogo() const override { return false; } + std::string getDisplayLabel() const override { return "CK/POOL"; } + std::string getPoolName() const override { + return "ckpool"; + } + +protected: + virtual std::string getBaseUrl() const { + return "https://solo.ckpool.org"; + } +}; \ No newline at end of file diff --git a/src/lib/mining_pool/ckpool/eu_ckpool.hpp b/src/lib/mining_pool/ckpool/eu_ckpool.hpp new file mode 100644 index 0000000..95a9b75 --- /dev/null +++ b/src/lib/mining_pool/ckpool/eu_ckpool.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "ckpool.hpp" + +class EUCKPool : public CKPool { +public: + std::string getDisplayLabel() const override { return "CK/POOL"; } + std::string getPoolName() const override { + return "eu_ckpool"; + } + +protected: + std::string getBaseUrl() const override { + return "https://eusolo.ckpool.org"; + } +}; \ No newline at end of file diff --git a/src/lib/mining_pool/noderunners/noderunners_pool.cpp b/src/lib/mining_pool/noderunners/noderunners_pool.cpp index da34e38..e7e10ca 100644 --- a/src/lib/mining_pool/noderunners/noderunners_pool.cpp +++ b/src/lib/mining_pool/noderunners/noderunners_pool.cpp @@ -1,27 +1,48 @@ // src/noderunners/noderunners_pool.cpp #include "noderunners_pool.hpp" -void NoderunnersPool::prepareRequest(HTTPClient& http) const { - // Empty as NodeRunners doesn't need special headers +void NoderunnersPool::prepareRequest(HTTPClient &http) const +{ + // Empty as Noderunners doesn't need special headers } -std::string NoderunnersPool::getApiUrl() const { +std::string NoderunnersPool::getApiUrl() const +{ return "https://pool.noderunners.network/api/v1/users/" + poolUser; } -PoolStats NoderunnersPool::parseResponse(const JsonDocument& doc) const { - std::string hashrateStr = doc["hashrate1m"].as(); - char unit = hashrateStr.back(); - std::string value = hashrateStr.substr(0, hashrateStr.size() - 1); - - int multiplier = getHashrateMultiplier(unit); - double hashrate = std::stod(value) * std::pow(10, multiplier); - - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%.0f", hashrate); - - return PoolStats{ - .hashrate = buffer, - .dailyEarnings = std::nullopt - }; +PoolStats NoderunnersPool::parseResponse(const JsonDocument &doc) const +{ + try + { + std::string hashrateStr = doc["hashrate1m"].as(); + + // Special case for "0" + if (hashrateStr == "0") { + return PoolStats{ + .hashrate = "0", + .dailyEarnings = std::nullopt + }; + } + + char unit = hashrateStr.back(); + std::string value = hashrateStr.substr(0, hashrateStr.size() - 1); + + int multiplier = getHashrateMultiplier(unit); + double hashrate = std::stod(value) * std::pow(10, multiplier); + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.0f", hashrate); + + return PoolStats{ + .hashrate = buffer, + .dailyEarnings = std::nullopt}; + } + catch (const std::exception &e) + { + Serial.printf("Error parsing %s response: %s\n", getPoolName().c_str(), e.what()); + return PoolStats{ + .hashrate = "0", + .dailyEarnings = std::nullopt}; + } } \ No newline at end of file diff --git a/src/lib/mining_pool/ocean/ocean_pool.cpp b/src/lib/mining_pool/ocean/ocean_pool.cpp index f6050af..cf9bf49 100644 --- a/src/lib/mining_pool/ocean/ocean_pool.cpp +++ b/src/lib/mining_pool/ocean/ocean_pool.cpp @@ -1,18 +1,29 @@ #include "ocean_pool.hpp" -void OceanPool::prepareRequest(HTTPClient& http) const { +void OceanPool::prepareRequest(HTTPClient &http) const +{ // Empty as Ocean doesn't need special headers } -std::string OceanPool::getApiUrl() const { +std::string OceanPool::getApiUrl() const +{ return "https://api.ocean.xyz/v1/statsnap/" + poolUser; } -PoolStats OceanPool::parseResponse(const JsonDocument& doc) const { - return PoolStats{ - .hashrate = doc["result"]["hashrate_300s"].as(), - .dailyEarnings = static_cast( - doc["result"]["estimated_earn_next_block"].as() * 100000000 - ) - }; -} +PoolStats OceanPool::parseResponse(const JsonDocument &doc) const +{ + try + { + return PoolStats{ + .hashrate = doc["result"]["hashrate_300s"].as(), + .dailyEarnings = static_cast( + doc["result"]["estimated_earn_next_block"].as() * 100000000)}; + } + catch (const std::exception &e) + { + Serial.printf("Error parsing %s response: %s\n", getPoolName().c_str(), e.what()); + return PoolStats{ + .hashrate = "0", + .dailyEarnings = std::nullopt}; + } +} \ No newline at end of file diff --git a/src/lib/mining_pool/pool_factory.cpp b/src/lib/mining_pool/pool_factory.cpp index 150793a..c353996 100644 --- a/src/lib/mining_pool/pool_factory.cpp +++ b/src/lib/mining_pool/pool_factory.cpp @@ -6,6 +6,8 @@ const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins"; const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio"; const char* PoolFactory::MINING_POOL_NAME_PUBLIC_POOL = "public_pool"; const char* PoolFactory::MINING_POOL_NAME_GOBRRR_POOL = "gobrrr_pool"; +const char* PoolFactory::MINING_POOL_NAME_CKPOOL = "ckpool"; +const char* PoolFactory::MINING_POOL_NAME_EU_CKPOOL = "eu_ckpool"; const char* PoolFactory::LOGOS_DIR = "/logos"; std::unique_ptr PoolFactory::createPool(const std::string& poolName) { @@ -15,7 +17,9 @@ std::unique_ptr PoolFactory::createPool(const std::string& {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique(); }}, {MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique(); }}, {MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique(); }}, - {MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique(); }} + {MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_CKPOOL, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique(); }} }; auto it = poolFactories.find(poolName); diff --git a/src/lib/mining_pool/pool_factory.hpp b/src/lib/mining_pool/pool_factory.hpp index 96231c9..951dbe5 100644 --- a/src/lib/mining_pool/pool_factory.hpp +++ b/src/lib/mining_pool/pool_factory.hpp @@ -11,6 +11,8 @@ #include "satoshi_radio/satoshi_radio_pool.hpp" #include "public_pool/public_pool.hpp" #include "gobrrr_pool/gobrrr_pool.hpp" +#include "ckpool/ckpool.hpp" +#include "ckpool/eu_ckpool.hpp" #include #include @@ -26,7 +28,9 @@ class PoolFactory { MINING_POOL_NAME_SATOSHI_RADIO, MINING_POOL_NAME_BRAIINS, MINING_POOL_NAME_PUBLIC_POOL, - MINING_POOL_NAME_GOBRRR_POOL + MINING_POOL_NAME_GOBRRR_POOL, + MINING_POOL_NAME_CKPOOL, + MINING_POOL_NAME_EU_CKPOOL }; } @@ -52,5 +56,7 @@ class PoolFactory { static const char* MINING_POOL_NAME_SATOSHI_RADIO; static const char* MINING_POOL_NAME_PUBLIC_POOL; static const char* MINING_POOL_NAME_GOBRRR_POOL; + static const char* MINING_POOL_NAME_CKPOOL; + static const char* MINING_POOL_NAME_EU_CKPOOL; static const char* LOGOS_DIR; }; \ No newline at end of file diff --git a/src/lib/mining_pool/public_pool/public_pool.cpp b/src/lib/mining_pool/public_pool/public_pool.cpp index 9a5d56f..8c7c6e2 100644 --- a/src/lib/mining_pool/public_pool/public_pool.cpp +++ b/src/lib/mining_pool/public_pool/public_pool.cpp @@ -1,21 +1,32 @@ // src/noderunners/noderunners_pool.cpp #include "public_pool.hpp" -std::string PublicPool::getApiUrl() const { +std::string PublicPool::getApiUrl() const +{ return "https://public-pool.io:40557/api/client/" + poolUser; } -PoolStats PublicPool::parseResponse(const JsonDocument& doc) const { +PoolStats PublicPool::parseResponse(const JsonDocument &doc) const +{ uint64_t totalHashrate = 0; - - for (JsonVariantConst worker : doc["workers"].as()) { - totalHashrate += static_cast(std::llround(worker["hashRate"].as())); - } - + try + { + for (JsonVariantConst worker : doc["workers"].as()) + { + totalHashrate += static_cast(std::llround(worker["hashRate"].as())); + } + } + catch (const std::exception &e) + { + Serial.printf("Error parsing %s response: %s\n", getPoolName().c_str(), e.what()); + return PoolStats{ + .hashrate = "0", + .dailyEarnings = std::nullopt}; + } return PoolStats{ .hashrate = std::to_string(totalHashrate), - .dailyEarnings = std::nullopt // Public Pool doesn't support daily earnings + .dailyEarnings = std::nullopt // Public Pool doesn't support daily earnings }; } \ No newline at end of file diff --git a/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp b/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp index 5b8f4a3..39fa107 100644 --- a/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp +++ b/src/lib/mining_pool/satoshi_radio/satoshi_radio_pool.cpp @@ -1,6 +1,7 @@ // src/noderunners/noderunners_pool.cpp #include "satoshi_radio_pool.hpp" -std::string SatoshiRadioPool::getApiUrl() const { +std::string SatoshiRadioPool::getApiUrl() const +{ return "https://pool.satoshiradio.nl/api/v1/users/" + poolUser; } From bc3e5afe51b6592787c38cced638ed33105bf757 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 28 Dec 2024 16:29:34 +0100 Subject: [PATCH 146/188] Add debug log setting and custom endpoint settings --- src/lib/config.cpp | 21 +++++++++++++ src/lib/config.hpp | 2 ++ src/lib/defaults.hpp | 13 +++++++- src/lib/mining_pool_stats_fetch.cpp | 14 +++++++++ src/lib/nostr_notify.cpp | 5 ++- src/lib/v2_notify.cpp | 23 +++++++++++--- src/lib/webserver.cpp | 47 +++++++++++++++-------------- 7 files changed, 95 insertions(+), 30 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 4c7303c..38fba9e 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -284,6 +284,22 @@ void setupPreferences() setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); + if (!preferences.isKey("enableDebugLog")) { + preferences.putBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG); + } + + if (!preferences.isKey("ceEnabled")) { + preferences.putBool("ceEnabled", DEFAULT_CUSTOM_ENDPOINT_ENABLED); + } + + if (!preferences.isKey("ceEndpoint")) { + preferences.putString("ceEndpoint", DEFAULT_CUSTOM_ENDPOINT); + } + + if (!preferences.isKey("ceDisableSSL")) { + preferences.putBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); + } + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) ScreenHandler::setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD)); else @@ -767,3 +783,8 @@ const char* getWebUiFilename() { return "littlefs_4MB.bin"; } } + +bool debugLogEnabled() +{ + return preferences.getBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG); +} diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 2c001cd..806497c 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -77,6 +77,8 @@ String getHwRev(); bool isWhiteVersion(); String getFsRev(); +bool debugLogEnabled(); + void addScreenMapping(int value, const char* name); // void addScreenMapping(int value, const String& name); // void addScreenMapping(int value, const std::string& name); diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index d880f2d..da4d405 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -17,7 +17,9 @@ #define DEFAULT_DISABLE_LEDS false #define DEFAULT_DISABLE_FL false #define DEFAULT_OWN_DATA_SOURCE true -#define DEFAULT_STAGING_SOURCE false +#define DEFAULT_CUSTOM_SOURCE false +#define DEFAULT_CUSTOM_EP "ws-staging.btclock.dev" +#define DEFAULT_CUSTOM_SSL true #define DEFAULT_MOW_MODE false #define DEFAULT_SUFFIX_SHARE_DOT false @@ -77,3 +79,12 @@ #define DEFAULT_VERTICAL_DESC true #define DEFAULT_MINING_POOL_LOGOS_URL "https://git.btclock.dev/btclock/mining-pool-logos/raw/branch/main" + +#define DEFAULT_ENABLE_DEBUG_LOG false + +#define DEFAULT_DISABLE_FL false +#define DEFAULT_OWN_DATA_SOURCE true +#define DEFAULT_CUSTOM_ENDPOINT_ENABLED false +#define DEFAULT_CUSTOM_ENDPOINT "ws-staging.btclock.dev" +#define DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL false +#define DEFAULT_MOW_MODE false diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index 9687e03..a35a00e 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -36,6 +36,10 @@ void taskMiningPoolStatsFetch(void *pvParameters) poolInterface->setPoolUser(poolUser); std::string apiUrl = poolInterface->getApiUrl(); http.begin(apiUrl.c_str()); + if (debugLogEnabled()) + { + Serial.printf("Fetching mining pool stats from %s\r\n", apiUrl.c_str()); + } poolInterface->prepareRequest(http); int httpCode = http.GET(); if (httpCode == 200) @@ -44,10 +48,20 @@ void taskMiningPoolStatsFetch(void *pvParameters) JsonDocument doc; deserializeJson(doc, payload); + if (debugLogEnabled()) + { + Serial.printf("Mining pool stats response: %s\r\n", payload.c_str()); + } + PoolStats stats = poolInterface->parseResponse(doc); miningPoolStatsHashrate = stats.hashrate; + if (debugLogEnabled()) + { + Serial.printf("Mining pool stats parsed hashrate: %s\r\n", stats.hashrate.c_str()); + } + if (stats.dailyEarnings) { miningPoolStatsDailyEarnings = *stats.dailyEarnings; diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 20bdd01..46c6ecf 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -267,7 +267,10 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) std::array textEpdContent = parseZapNotify(zapAmount, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); - Serial.printf("Got a zap of %llu sats for %s\n", zapAmount, zapPubkey.c_str()); + if (debugLogEnabled()) + { + Serial.printf("Got a zap of %llu sats for %s\n", zapAmount, zapPubkey.c_str()); + } uint64_t timerPeriod = 0; if (isTimerActive()) diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index e0b5966..5c30ad2 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -8,25 +8,36 @@ namespace V2Notify TaskHandle_t v2NotifyTaskHandle; + String currentHostname; + void setupV2Notify() { String hostname = "ws.btclock.dev"; - if (preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) + if (preferences.getBool("ceEnabled", DEFAULT_CUSTOM_ENDPOINT_ENABLED)) { - Serial.println(F("Connecting to V2 staging source")); - hostname = "ws-staging.btclock.dev"; + Serial.println(F("Connecting to custom source")); + hostname = preferences.getString("ceEndpoint", DEFAULT_CUSTOM_ENDPOINT); + bool useSSL = !preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); + + if (useSSL) { + webSocket.beginSSL(hostname, 443, "/api/v2/ws"); + } else { + webSocket.begin(hostname, 80, "/api/v2/ws"); + } } else { Serial.println(F("Connecting to V2 source")); + webSocket.beginSSL(hostname, 443, "/api/v2/ws"); } - webSocket.beginSSL(hostname, 443, "/api/v2/ws"); webSocket.onEvent(V2Notify::onWebsocketV2Event); webSocket.setReconnectInterval(5000); webSocket.enableHeartbeat(15000, 3000, 2); V2Notify::setupV2NotifyTask(); + + currentHostname = hostname; } void onWebsocketV2Event(WStype_t type, uint8_t *payload, size_t length) @@ -38,7 +49,9 @@ namespace V2Notify break; case WStype_CONNECTED: { - Serial.print(F("[WSc] Connected to url:")); + Serial.print(F("[WSc] Connected to ")); + Serial.print(currentHostname); + Serial.print(F(": ")); Serial.println((char *)payload); JsonDocument response; diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 4a8a8f5..2571505 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -3,7 +3,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout", "srcV2Currency"}; @@ -15,7 +15,8 @@ static const char *const PROGMEM boolSettings[] = {"fetchEurPrice", "ledTestOnPo "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "miningPoolStats", "verticalDesc", - "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; + "nostrZapNotify", "ceEnabled", "httpAuthEnabled", + "enableDebugLog", "ceDisableSSL"}; AsyncWebServer server(80); AsyncEventSource events("/events"); @@ -458,25 +459,22 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) bool settingsChanged = true; - if (settings["fgColor"].is()) + if (settings["invertedColor"].is()) { - String fgColor = settings["fgColor"].as(); - uint32_t color = strtol(fgColor.c_str(), NULL, 16); - preferences.putUInt("fgColor", color); - setFgColor(color); - Serial.print(F("Setting foreground color to ")); - Serial.println(color); - settingsChanged = true; - } - if (settings["bgColor"].is()) - { - String bgColor = settings["bgColor"].as(); - - uint32_t color = strtol(bgColor.c_str(), NULL, 16); - preferences.putUInt("bgColor", color); - setBgColor(color); - Serial.print(F("Setting background color to ")); - Serial.println(bgColor.c_str()); + bool inverted = settings["invertedColor"].as(); + preferences.putBool("invertedColor", inverted); + if (inverted) { + preferences.putUInt("fgColor", GxEPD_WHITE); + preferences.putUInt("bgColor", GxEPD_BLACK); + setFgColor(GxEPD_WHITE); + setBgColor(GxEPD_BLACK); + } else { + preferences.putUInt("fgColor", GxEPD_BLACK); + preferences.putUInt("bgColor", GxEPD_WHITE); + setFgColor(GxEPD_BLACK); + setBgColor(GxEPD_WHITE); + } + Serial.printf("Setting invertedColor to %d\r\n", inverted); settingsChanged = true; } @@ -627,8 +625,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) JsonDocument root; root["numScreens"] = NUM_SCREENS; - root["fgColor"] = getFgColor(); - root["bgColor"] = getBgColor(); + root["invertedColor"] = preferences.getBool("invertedColor", getFgColor() == GxEPD_WHITE); root["timerSeconds"] = getTimerSeconds(); root["timerRunning"] = isTimerActive(); root["minSecPriceUpd"] = preferences.getUInt( @@ -657,13 +654,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["verticalDesc"] = preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC); root["suffixShareDot"] = preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT); + root["enableDebugLog"] = preferences.getBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG); root["hostnamePrefix"] = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX); root["hostname"] = getMyHostname(); root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); - root["stagingSource"] = preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE); root["srcV2Currency"] = preferences.getChar("srcV2Currency", DEFAULT_V2_SOURCE_CURRENCY); root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); @@ -734,6 +731,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["poolLogosUrl"] = preferences.getString("poolLogosUrl", DEFAULT_MINING_POOL_LOGOS_URL); + root["ceEnabled"] = preferences.getBool("ceEnabled", DEFAULT_CUSTOM_ENDPOINT_ENABLED); + root["ceEndpoint"] = preferences.getString("ceEndpoint", DEFAULT_CUSTOM_ENDPOINT); + root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); + AsyncResponseStream *response = request->beginResponseStream(JSON_CONTENT); serializeJson(root, *response); From 64e518bf58f89749753167a8b6826e10bb6455c5 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 28 Dec 2024 18:19:31 +0100 Subject: [PATCH 147/188] Improve data source selection, clean up unused preferences --- data | 2 +- src/lib/block_notify.cpp | 5 --- src/lib/config.cpp | 64 ++++++++++++++++++++----------- src/lib/config.hpp | 15 +++++++- src/lib/defaults.hpp | 21 +++++++---- src/lib/epd.cpp | 2 +- src/lib/nostr_notify.cpp | 3 +- src/lib/ota.cpp | 38 +++++++++---------- src/lib/price_notify.cpp | 24 +++--------- src/lib/screen_handler.cpp | 2 +- src/lib/v2_notify.cpp | 2 +- src/lib/webserver.cpp | 77 ++++++++++++++++++++++++++------------ src/main.cpp | 4 +- 13 files changed, 155 insertions(+), 104 deletions(-) diff --git a/data b/data index 2ce53eb..4057e18 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 2ce53eb499e00a990be5cb0ea078e146f467ceb4 +Subproject commit 4057e1875508b47d876478d97cff544213fd63d4 diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index e41fc4b..a1ce1fc 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -82,11 +82,6 @@ void setupBlockNotify() xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } - if (!preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) - { - return; - } - // std::strcpy(wsServer, String("wss://" + mempoolInstance + // "/api/v1/ws").c_str()); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 38fba9e..5870ec4 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -25,6 +25,23 @@ void addScreenMapping(int value, const char *name) screenMappings.push_back({value, name}); } +void setupDataSource() +{ + DataSourceType dataSource = getDataSource(); + bool zapNotifyEnabled = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); + + // Setup Nostr if it's either the data source or zap notifications are enabled + if (dataSource == NOSTR_SOURCE || zapNotifyEnabled) { + setupNostrNotify(dataSource == NOSTR_SOURCE, zapNotifyEnabled); + setupNostrTask(); + } + // Setup other data sources if Nostr is not the data source + if (dataSource != NOSTR_SOURCE) { + xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, + tskIDLE_PRIORITY, NULL); + } +} + void setup() { setupPreferences(); @@ -78,17 +95,8 @@ void setup() setupTasks(); setupTimers(); - if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR) || preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED)) - { - setupNostrNotify(preferences.getBool("useNostr", DEFAULT_USE_NOSTR), preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED)); - setupNostrTask(); - } - - if (!preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) - { - xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, - tskIDLE_PRIORITY, NULL); - } + // Setup data sources (includes Nostr zap notifications if enabled) + setupDataSource(); if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { @@ -288,22 +296,26 @@ void setupPreferences() preferences.putBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG); } - if (!preferences.isKey("ceEnabled")) { - preferences.putBool("ceEnabled", DEFAULT_CUSTOM_ENDPOINT_ENABLED); + if (!preferences.isKey("dataSource")) { + preferences.putUChar("dataSource", DEFAULT_DATA_SOURCE); } - if (!preferences.isKey("ceEndpoint")) { - preferences.putString("ceEndpoint", DEFAULT_CUSTOM_ENDPOINT); + // Initialize custom endpoint settings if not set + if (!preferences.isKey("customEndpoint")) { + preferences.putString("customEndpoint", DEFAULT_CUSTOM_ENDPOINT); } - if (!preferences.isKey("ceDisableSSL")) { - preferences.putBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); + if (!preferences.isKey("customEndpointDisableSSL")) { + preferences.putBool("customEndpointDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); } - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) + // Set currency based on data source + DataSourceType dataSource = static_cast(preferences.getUChar("dataSource", DEFAULT_DATA_SOURCE)); + if (dataSource == BTCLOCK_SOURCE || dataSource == CUSTOM_SOURCE) { ScreenHandler::setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD)); - else + } else { ScreenHandler::setCurrentCurrency(CURRENCY_USD); + } if (!preferences.isKey("flDisable")) { preferences.putBool("flDisable", isWhiteVersion() ? false : true); @@ -377,11 +389,13 @@ String replaceAmbiguousChars(String input) void setupWebsocketClients(void *pvParameters) { - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) + DataSourceType dataSource = getDataSource(); + + if (dataSource == BTCLOCK_SOURCE || dataSource == CUSTOM_SOURCE) { V2Notify::setupV2Notify(); } - else + else if (dataSource == THIRD_PARTY_SOURCE) { setupBlockNotify(); setupPriceNotify(); @@ -788,3 +802,11 @@ bool debugLogEnabled() { return preferences.getBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG); } + +DataSourceType getDataSource() { + return static_cast(preferences.getUChar("dataSource", DEFAULT_DATA_SOURCE)); +} + +void setDataSource(DataSourceType source) { + preferences.putUChar("dataSource", static_cast(source)); +} diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 806497c..d8e27b6 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -31,6 +31,9 @@ #include "BH1750.h" #endif +#include "shared.hpp" +#include "defaults.hpp" + #define NTP_SERVER "pool.ntp.org" #define DEFAULT_TIME_OFFSET_SECONDS 3600 #ifndef MCP_DEV_ADDR @@ -87,4 +90,14 @@ int findScreenIndexByValue(int value); String replaceAmbiguousChars(String input); const char* getFirmwareFilename(); const char* getWebUiFilename(); -// void loadIcons(); \ No newline at end of file +// void loadIcons(); + +extern Preferences preferences; +extern MCP23017 mcp1; +#ifdef IS_BTCLOCK_V8 +extern MCP23017 mcp2; +#endif + +// Expose DataSourceType enum +extern DataSourceType getDataSource(); +extern void setDataSource(DataSourceType source); \ No newline at end of file diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index da4d405..8849817 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -1,4 +1,6 @@ -#define INITIAL_BLOCK_HEIGHT 851500 +#pragma once + +#define INITIAL_BLOCK_HEIGHT 876600 #define INITIAL_LAST_PRICE 50000 #define DEFAULT_TX_POWER 0 @@ -16,16 +18,11 @@ #define DEFAULT_SUFFIX_PRICE false #define DEFAULT_DISABLE_LEDS false #define DEFAULT_DISABLE_FL false -#define DEFAULT_OWN_DATA_SOURCE true -#define DEFAULT_CUSTOM_SOURCE false -#define DEFAULT_CUSTOM_EP "ws-staging.btclock.dev" -#define DEFAULT_CUSTOM_SSL true #define DEFAULT_MOW_MODE false #define DEFAULT_SUFFIX_SHARE_DOT false #define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD - #define DEFAULT_TIME_OFFSET_SECONDS 3600 #define DEFAULT_HOSTNAME_PREFIX "btclock" @@ -83,8 +80,16 @@ #define DEFAULT_ENABLE_DEBUG_LOG false #define DEFAULT_DISABLE_FL false -#define DEFAULT_OWN_DATA_SOURCE true -#define DEFAULT_CUSTOM_ENDPOINT_ENABLED false #define DEFAULT_CUSTOM_ENDPOINT "ws-staging.btclock.dev" #define DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL false #define DEFAULT_MOW_MODE false + +// Define data source types +enum DataSourceType { + BTCLOCK_SOURCE = 0, // BTClock's own data source + THIRD_PARTY_SOURCE = 1, // Third party data sources like mempool.space + NOSTR_SOURCE = 2, // Nostr data source + CUSTOM_SOURCE = 3 // Custom data source endpoint +}; + +#define DEFAULT_DATA_SOURCE BTCLOCK_SOURCE diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 528db1d..151b1c0 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -550,7 +550,7 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) LogoData logo = getMiningPoolLogo(); if (logo.size == 0) { - Serial.println("No logo found"); + Serial.println(F("No logo found")); return false; } diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 46c6ecf..b9a7a46 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -76,7 +76,8 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) void nostrTask(void *pvParameters) { - if(preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) { + DataSourceType dataSource = getDataSource(); + if(dataSource == NOSTR_SOURCE) { int blockFetch = getBlockFetch(); processNewBlock(blockFetch); } diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 0d6a57f..9a0a695 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -181,7 +181,7 @@ int downloadUpdateHandler(char updateType) String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl); if (expectedSHA256.isEmpty()) { - Serial.println("Failed to get SHA256 checksum. Aborting update."); + Serial.println(F("Failed to get SHA256 checksum. Aborting update.")); return false; } @@ -217,7 +217,7 @@ int downloadUpdateHandler(char updateType) if (bytesRead != contentLength) { - Serial.println("Failed to read entire firmware"); + Serial.println(F("Failed to read entire firmware")); free(firmware); return false; } @@ -225,14 +225,14 @@ int downloadUpdateHandler(char updateType) // Calculate SHA256 String calculated_sha256 = calculateSHA256(firmware, contentLength); - Serial.print("Calculated checksum: "); + Serial.print(F("Calculated checksum: ")); Serial.println(calculated_sha256); - Serial.print("Expected checksum: "); + Serial.print(F("Expected checksum: ")); Serial.println(expectedSHA256); if (calculated_sha256 != expectedSHA256) { - Serial.println("Checksum mismatch. Aborting update."); + Serial.println(F("Checksum mismatch. Aborting update.")); free(firmware); return false; } @@ -258,15 +258,15 @@ int downloadUpdateHandler(char updateType) if (Update.end()) { - Serial.println("OTA done!"); + Serial.println(F("OTA done!")); if (Update.isFinished()) { - Serial.println("Update successfully completed. Rebooting."); + Serial.println(F("Update successfully completed. Rebooting.")); // ESP.restart(); } else { - Serial.println("Update not finished? Something went wrong!"); + Serial.println(F("Update not finished? Something went wrong!")); free(firmware); return 503; } @@ -280,14 +280,14 @@ int downloadUpdateHandler(char updateType) } else { - Serial.println("Not enough space to begin OTA"); + Serial.println(F("Not enough space to begin OTA")); free(firmware); return 503; } } else { - Serial.println("Invalid content length"); + Serial.println(F("Invalid content length")); return 503; } } @@ -337,7 +337,7 @@ void updateWebUi(String latestRelease, int command) Serial.println(calculated_sha256); if ((command == U_FLASH && expectedSHA256.equals(calculated_sha256)) || command == U_SPIFFS) { - Serial.println("Checksum verified. Proceeding with update."); + Serial.println(F("Checksum verified. Proceeding with update.")); Update.onProgress(onOTAProgress); @@ -348,38 +348,38 @@ void updateWebUi(String latestRelease, int command) Update.write(buffer, contentLength); if (Update.end()) { - Serial.println("Update complete. Rebooting."); + Serial.println(F("Update complete. Rebooting.")); ESP.restart(); } else { - Serial.println("Error in update process."); + Serial.println(F("Error in update process.")); } } else { - Serial.println("Not enough space to begin OTA"); + Serial.println(F("Not enough space to begin OTA")); } } else { - Serial.println("Checksum mismatch. Aborting update."); + Serial.println(F("Checksum mismatch. Aborting update.")); } } else { - Serial.println("Error downloading firmware"); + Serial.println(F("Error downloading firmware")); } free(buffer); } else { - Serial.println("Not enough memory to allocate buffer"); + Serial.println(F("Not enough memory to allocate buffer")); } } else { - Serial.println("Invalid content length"); + Serial.println(F("Invalid content length")); } } else @@ -417,7 +417,7 @@ String downloadSHA256(const String &sha256Url) { if (sha256Url.isEmpty()) { - Serial.println("Failed to get SHA256 file URL"); + Serial.println(F("Failed to get SHA256 file URL")); return ""; } diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 780f651..0d56a90 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -1,8 +1,5 @@ #include "price_notify.hpp" -const char *wsOwnServerPrice = "wss://ws.btclock.dev/ws?assets=bitcoin"; -const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws"; - const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; // WebsocketsClient client; @@ -17,19 +14,12 @@ WebSocketsClient priceNotifyWs; void setupPriceNotify() { - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) - { - config = {.uri = wsOwnServerPrice, - .user_agent = USER_AGENT}; - } - else - { - config = {.uri = wsServerPrice, - .user_agent = USER_AGENT}; - config.cert_pem = isrg_root_x1cert; + config = {.uri = wsServerPrice, + .user_agent = USER_AGENT}; + config.cert_pem = isrg_root_x1cert; + + config.task_stack = (6*1024); - config.task_stack = (6*1024); - } clientPrice = esp_websocket_client_init(&config); esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, @@ -86,10 +76,6 @@ void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, break; case WEBSOCKET_EVENT_DATA: onWebsocketPriceMessage(data); - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) - { - onWebsocketBlockMessage(data); - } break; case WEBSOCKET_EVENT_ERROR: Serial.println(F("Price WS Connnection error")); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 8c7213c..44c0fe0 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -85,7 +85,7 @@ bool ScreenHandler::isCurrencySpecific(uint screen) { } bool ScreenHandler::handleCurrencyRotation(bool forward) { - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { + if ((getDataSource() == BTCLOCK_SOURCE || getDataSource() == CUSTOM_SOURCE) && isCurrencySpecific(getCurrentScreen())) { std::vector ac = getActiveCurrencies(); if (ac.empty()) return false; diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 5c30ad2..469e6e1 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -13,7 +13,7 @@ namespace V2Notify void setupV2Notify() { String hostname = "ws.btclock.dev"; - if (preferences.getBool("ceEnabled", DEFAULT_CUSTOM_ENDPOINT_ENABLED)) + if (getDataSource() == CUSTOM_SOURCE) { Serial.println(F("Connecting to custom source")); hostname = preferences.getString("ceEndpoint", DEFAULT_CUSTOM_ENDPOINT); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 2571505..d1ed533 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -5,17 +5,17 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint"}; -static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout", "srcV2Currency"}; +static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; -static const char *const PROGMEM boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", +static const char *const PROGMEM boolSettings[] = {"ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", + "suffixPrice", "disableLeds", "mowMode", "suffixShareDot", "flOffWhenDark", "flAlwaysOn", "flDisable", "flFlashOnUpd", - "mempoolSecure", "useNostr", "bitaxeEnabled", + "mempoolSecure", "bitaxeEnabled", "miningPoolStats", "verticalDesc", - "nostrZapNotify", "ceEnabled", "httpAuthEnabled", + "nostrZapNotify", "httpAuthEnabled", "enableDebugLog", "ceDisableSSL"}; AsyncWebServer server(80); @@ -581,6 +581,29 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } } + // Handle data source setting + if (settings["dataSource"].is()) { + uint8_t dataSource = settings["dataSource"].as(); + if (dataSource <= CUSTOM_SOURCE) { // Validate including custom source + preferences.putUChar("dataSource", dataSource); + Serial.printf("Setting dataSource to %d\r\n", dataSource); + settingsChanged = true; + } + } + + // Handle custom endpoint settings + if (settings["customEndpoint"].is()) { + preferences.putString("customEndpoint", settings["customEndpoint"].as()); + Serial.printf("Setting customEndpoint to %s\r\n", settings["customEndpoint"].as().c_str()); + settingsChanged = true; + } + + if (settings["customEndpointDisableSSL"].is()) { + preferences.putBool("customEndpointDisableSSL", settings["customEndpointDisableSSL"].as()); + Serial.printf("Setting customEndpointDisableSSL to %d\r\n", settings["customEndpointDisableSSL"].as()); + settingsChanged = true; + } + request->send(HTTP_OK); if (settingsChanged) { @@ -590,14 +613,17 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) void onApiRestart(AsyncWebServerRequest *request) { + request->onDisconnect([]() { + delay(500); + + noInterrupts(); + esp_restart(); + }); + request->send(HTTP_OK); if (events.count()) events.send("closing"); - - delay(500); - - esp_restart(); } void onApiIdentify(AsyncWebServerRequest *request) @@ -634,10 +660,25 @@ void onApiSettingsGet(AsyncWebServerRequest *request) preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH); root["wpTimeout"] = preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT); root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; - root["mempoolInstance"] = - preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + + // Add data source settings + root["dataSource"] = preferences.getUChar("dataSource", DEFAULT_DATA_SOURCE); + + // Mempool settings (only used for THIRD_PARTY_SOURCE) + root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); - root["useNostr"] = preferences.getBool("useNostr", DEFAULT_USE_NOSTR); + + // Nostr settings (used for NOSTR_SOURCE or when zapNotify is enabled) + root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); + root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); + root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); + root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); + root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP); + + // Custom endpoint settings (only used for CUSTOM_SOURCE) + root["customEndpoint"] = preferences.getString("customEndpoint", DEFAULT_CUSTOM_ENDPOINT); + root["customEndpointDisableSSL"] = preferences.getBool("customEndpointDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); + root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER); root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD); root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS); @@ -645,7 +686,6 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["mcapBigChar"] = preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR); root["mdnsEnabled"] = preferences.getBool("mdnsEnabled", DEFAULT_MDNS_ENABLED); root["otaEnabled"] = preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED); - // root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE); root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL); root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN); root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE); @@ -660,15 +700,6 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["hostname"] = getMyHostname(); root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); - root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); - root["srcV2Currency"] = preferences.getChar("srcV2Currency", DEFAULT_V2_SOURCE_CURRENCY); - - root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); - root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); - - root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); - root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); - root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP); root["gitReleaseUrl"] = preferences.getString("gitReleaseUrl", DEFAULT_GIT_RELEASE_URL); @@ -730,8 +761,6 @@ void onApiSettingsGet(AsyncWebServerRequest *request) } root["poolLogosUrl"] = preferences.getString("poolLogosUrl", DEFAULT_MINING_POOL_LOGOS_URL); - - root["ceEnabled"] = preferences.getBool("ceEnabled", DEFAULT_CUSTOM_ENDPOINT_ENABLED); root["ceEndpoint"] = preferences.getString("ceEndpoint", DEFAULT_CUSTOM_ENDPOINT); root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); diff --git a/src/main.cpp b/src/main.cpp index ea24213..4943a51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,7 +135,7 @@ extern "C" void app_main() { Serial.begin(115200); setup(); - bool ownDataSource = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); + bool thirdPartySource = getDataSource() == THIRD_PARTY_SOURCE; while (true) { @@ -147,7 +147,7 @@ extern "C" void app_main() { handleFrontlight(); checkWiFiConnection(); - if (!ownDataSource) { + if (thirdPartySource) { monitorDataConnections(); } From 833d46fa5ad1c5b54c91000650f07b364991462f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 29 Dec 2024 01:31:33 +0100 Subject: [PATCH 148/188] Improve WebUI feedback --- data | 2 +- src/lib/ota.cpp | 1 + src/lib/webserver.cpp | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data b/data index 4057e18..468e105 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 4057e1875508b47d876478d97cff544213fd63d4 +Subproject commit 468e105adfaded0440ff8bba61a8241d54f28cd4 diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 9a0a695..c481b72 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -83,6 +83,7 @@ void handleOTATask(void *parameter) if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE) { if (msg.updateType == UPDATE_ALL) { + isOtaUpdating = true; queueLedEffect(LED_FLASH_UPDATE); int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI); queueLedEffect(LED_FLASH_UPDATE); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index d1ed533..66c6895 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -232,6 +232,7 @@ JsonDocument getStatusObject() root["currentScreen"] = ScreenHandler::getCurrentScreen(); root["numScreens"] = NUM_SCREENS; root["timerRunning"] = isTimerActive(); + root["isOTAUpdating"] = getIsOTAUpdating(); root["espUptime"] = esp_timer_get_time() / 1000000; // root["currentPrice"] = getPrice(); // root["currentBlockHeight"] = getBlockHeight(); From 73a20cf9a74e360257fbbe56b5bcc332d0600cbc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 29 Dec 2024 17:11:34 +0100 Subject: [PATCH 149/188] Screen handler bugfix for multi currency --- data | 2 +- src/lib/screen_handler.cpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/data b/data index 468e105..48e585d 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 468e105adfaded0440ff8bba61a8241d54f28cd4 +Subproject commit 48e585d4ec12bbc441499936d7cbf53d4307b9ec diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 44c0fe0..f837e5e 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -108,6 +108,7 @@ bool ScreenHandler::handleCurrencyRotation(bool forward) { setCurrentScreen(getCurrentScreen()); return true; } + // If we're at the last/first currency of current screen, let nextScreen/previousScreen handle it return false; } return false; @@ -143,14 +144,38 @@ int ScreenHandler::findNextVisibleScreen(int currentScreen, bool forward) { void ScreenHandler::nextScreen() { if (handleCurrencyRotation(true)) return; + int currentIndex = findScreenIndexByValue(getCurrentScreen()); - setCurrentScreen(findNextVisibleScreen(currentIndex, true)); + int nextScreen = findNextVisibleScreen(currentIndex, true); + + // If moving from a currency-specific screen to another currency-specific screen + // reset to first currency + if (isCurrencySpecific(getCurrentScreen()) && isCurrencySpecific(nextScreen)) { + std::vector ac = getActiveCurrencies(); + if (!ac.empty()) { + setCurrentCurrency(getCurrencyChar(ac.front())); + } + } + + setCurrentScreen(nextScreen); } void ScreenHandler::previousScreen() { if (handleCurrencyRotation(false)) return; + int currentIndex = findScreenIndexByValue(getCurrentScreen()); - setCurrentScreen(findNextVisibleScreen(currentIndex, false)); + int prevScreen = findNextVisibleScreen(currentIndex, false); + + // If moving from a currency-specific screen to another currency-specific screen + // reset to last currency + if (isCurrencySpecific(getCurrentScreen()) && isCurrencySpecific(prevScreen)) { + std::vector ac = getActiveCurrencies(); + if (!ac.empty()) { + setCurrentCurrency(getCurrencyChar(ac.back())); + } + } + + setCurrentScreen(prevScreen); } void ScreenHandler::showSystemStatusScreen() { From 90d91ba21617f6194fdd73a87d2cfe728981e151 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 29 Dec 2024 23:28:22 +0100 Subject: [PATCH 150/188] Compress fonts and add Oswald font --- platformio.ini | 1 + src/fonts/antonio-semibold20.h | 578 +-- src/fonts/antonio-semibold40.h | 2109 +++-------- src/fonts/antonio-semibold90.h | 6284 ++++++-------------------------- src/fonts/fonts.hpp | 80 +- src/fonts/oswald-medium20.h | 323 ++ src/fonts/oswald-medium30.h | 497 +++ src/fonts/oswald-medium80.h | 1083 ++++++ src/lib/defaults.hpp | 5 + src/lib/epd.cpp | 741 ++-- src/lib/epd.hpp | 14 +- src/lib/gzip_decompressor.hpp | 49 + src/lib/webserver.cpp | 4 +- 13 files changed, 4197 insertions(+), 7571 deletions(-) create mode 100644 src/fonts/oswald-medium20.h create mode 100644 src/fonts/oswald-medium30.h create mode 100644 src/fonts/oswald-medium80.h create mode 100644 src/lib/gzip_decompressor.hpp diff --git a/platformio.ini b/platformio.ini index 0dcacd0..b65defd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,6 +30,7 @@ build_flags = -DLAST_BUILD_TIME=$UNIX_TIME -DARDUINO_USB_CDC_ON_BOOT -DCORE_DEBUG_LEVEL=0 + -D DEFAULT_BOOT_TEXT=\"BTCLOCK\" -fexceptions build_unflags = -Werror=all diff --git a/src/fonts/antonio-semibold20.h b/src/fonts/antonio-semibold20.h index d544b08..be8b1ec 100644 --- a/src/fonts/antonio-semibold20.h +++ b/src/fonts/antonio-semibold20.h @@ -1,384 +1,191 @@ +#pragma once + #include #include +#include "fonts.hpp" -const uint8_t Antonio_SemiBold20pt7bBitmaps[] PROGMEM = { - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x66, 0x66, 0x66, - 0x66, 0x66, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7B, 0xDE, 0xF7, - 0x98, 0xC6, 0x00, 0x03, 0x8F, 0x01, 0xC7, 0x80, 0xE3, 0x80, 0xF1, 0xC0, - 0x70, 0xE0, 0x38, 0x70, 0x1C, 0x78, 0x0E, 0x3C, 0x07, 0x1C, 0x07, 0x8E, - 0x03, 0x87, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x38, 0xE0, 0x3C, - 0x70, 0x1E, 0x38, 0x0E, 0x1C, 0x07, 0x0E, 0x03, 0x8F, 0x0F, 0xFF, 0xF7, - 0xFF, 0xF8, 0xF1, 0xC0, 0x70, 0xE0, 0x38, 0x70, 0x1C, 0x78, 0x0E, 0x3C, - 0x0F, 0x1C, 0x07, 0x8E, 0x03, 0x87, 0x01, 0xC3, 0x80, 0xE3, 0xC0, 0x71, - 0xE0, 0x78, 0xE0, 0x00, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, - 0x1F, 0xC1, 0xFF, 0x9F, 0xFC, 0xFB, 0xF7, 0x87, 0xBC, 0x3D, 0xE1, 0xEF, - 0x0F, 0x78, 0x7B, 0xE3, 0xDF, 0x00, 0x7C, 0x01, 0xF8, 0x0F, 0xE0, 0x3F, - 0x80, 0x7C, 0x01, 0xF0, 0x07, 0xC0, 0x3E, 0xF0, 0xF7, 0x87, 0xFC, 0x3F, - 0xE1, 0xFF, 0x0F, 0xFC, 0x7B, 0xFF, 0xCF, 0xFE, 0x3F, 0xE0, 0xFE, 0x01, - 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0x1F, 0x80, 0x01, 0xC0, - 0x07, 0xFE, 0x00, 0x3C, 0x00, 0x7F, 0xE0, 0x03, 0xC0, 0x0F, 0x9F, 0x00, - 0x38, 0x00, 0xF0, 0xF0, 0x07, 0x80, 0x0F, 0x0F, 0x00, 0x70, 0x00, 0xF0, - 0xF0, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0xF0, 0x00, 0xF0, 0xF0, 0x0E, 0x00, - 0x0F, 0x0F, 0x01, 0xE0, 0x00, 0xF0, 0xF0, 0x1C, 0x00, 0x0F, 0x0F, 0x03, - 0xC0, 0x60, 0xF0, 0xF0, 0x3C, 0x3F, 0xCF, 0x0F, 0x03, 0x87, 0xFE, 0xF0, - 0xF0, 0x78, 0xFF, 0xEF, 0x0F, 0x07, 0x8F, 0x0E, 0xF0, 0xF0, 0xF0, 0xF0, - 0xFF, 0x0F, 0x0F, 0x0E, 0x0F, 0xF0, 0xF0, 0xE0, 0xE0, 0xF7, 0xDF, 0x1E, - 0x0E, 0x0F, 0x7F, 0xE1, 0xE0, 0xE0, 0xF3, 0xFC, 0x1C, 0x0E, 0x0F, 0x1F, - 0x83, 0xC0, 0xE0, 0xF0, 0x00, 0x38, 0x0E, 0x0F, 0x00, 0x07, 0x80, 0xE0, - 0xF0, 0x00, 0x78, 0x0E, 0x0F, 0x00, 0x07, 0x00, 0xE0, 0xF0, 0x00, 0xF0, - 0x0E, 0x0F, 0x00, 0x0E, 0x00, 0xE0, 0xF0, 0x01, 0xE0, 0x0F, 0x0F, 0x00, - 0x1E, 0x00, 0xF1, 0xE0, 0x01, 0xC0, 0x0F, 0xFE, 0x00, 0x3C, 0x00, 0x7F, - 0xC0, 0x03, 0x80, 0x03, 0xF8, 0x03, 0xE0, 0x03, 0xFE, 0x00, 0xFF, 0x80, - 0x7C, 0xF0, 0x1E, 0x1C, 0x07, 0x87, 0x01, 0xE1, 0xC0, 0x78, 0x70, 0x1E, - 0x1C, 0x07, 0x8F, 0x00, 0xF3, 0x80, 0x3D, 0xE0, 0x0F, 0x78, 0x01, 0xFC, - 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xC0, 0x03, 0xF0, 0x00, 0xFE, 0x08, - 0x7F, 0x86, 0x3F, 0xE3, 0x8F, 0x3C, 0xF7, 0x8F, 0x79, 0xE1, 0xFC, 0x78, - 0x7F, 0x3C, 0x1F, 0x8F, 0x03, 0xE3, 0xC0, 0xF0, 0xF0, 0x3E, 0x1E, 0x1F, - 0x87, 0xFF, 0xF1, 0xFF, 0xFC, 0x3F, 0xE7, 0x87, 0xF1, 0xE0, 0xFF, 0xFF, - 0xF7, 0x76, 0x66, 0x66, 0x3E, 0xFF, 0xFF, 0xCF, 0x1E, 0x3C, 0x78, 0xF1, - 0xE3, 0xC7, 0x8F, 0x1E, 0x3C, 0x78, 0xF1, 0xE3, 0xC7, 0x8F, 0x1E, 0x3C, - 0x78, 0xF1, 0xE3, 0xC7, 0x8F, 0x1E, 0x3C, 0x78, 0xF1, 0xE3, 0xFB, 0xF7, - 0xE1, 0xC0, 0xF1, 0xF3, 0xF1, 0xE3, 0xC7, 0xCF, 0x9F, 0x3E, 0x7C, 0xF9, - 0xF3, 0xE7, 0xCF, 0x9F, 0x3E, 0x7C, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3E, - 0x7C, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3E, 0x7B, 0xF7, 0xEF, 0x98, 0x00, - 0x07, 0x00, 0x1C, 0x04, 0x71, 0x39, 0xCE, 0xFB, 0x7D, 0xFF, 0xC1, 0xFC, - 0x03, 0xE0, 0x3F, 0xE3, 0xEF, 0xFF, 0x73, 0x99, 0xC6, 0x07, 0x08, 0x1C, - 0x00, 0x70, 0x00, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, - 0x8C, 0xE7, 0x31, 0x9C, 0xC0, 0xFF, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0x00, - 0xF8, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0E, 0x00, 0xF0, 0x07, 0x80, 0x3C, - 0x01, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x38, 0x03, 0xC0, 0x1E, 0x00, - 0xF0, 0x07, 0x80, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x0F, 0x00, 0x78, - 0x03, 0xC0, 0x1E, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x3C, 0x01, - 0xE0, 0x0F, 0x00, 0x78, 0x07, 0x80, 0x00, 0x0F, 0xC1, 0xFF, 0x9F, 0xFC, - 0xFF, 0xF7, 0x8F, 0xFC, 0x3F, 0xE1, 0xFF, 0x0F, 0xF8, 0x7F, 0xC3, 0xFE, - 0x1F, 0xF0, 0xFF, 0x87, 0xFC, 0x3F, 0xE1, 0xFF, 0x0F, 0xF8, 0x7F, 0xC3, - 0xFE, 0x1F, 0xF0, 0xFF, 0x87, 0xFC, 0x3F, 0xE1, 0xFF, 0x0F, 0xF8, 0x7F, - 0xC3, 0xFE, 0x1F, 0xF0, 0xFF, 0x87, 0xBC, 0x7D, 0xF7, 0xEF, 0xFE, 0x3F, - 0xF0, 0xFF, 0x00, 0x03, 0x83, 0xC3, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, - 0x0F, 0x87, 0xC3, 0xE1, 0xF0, 0xF8, 0x7C, 0x3E, 0x1F, 0x0F, 0x87, 0xC3, - 0xE1, 0xF0, 0xF8, 0x7C, 0x3E, 0x1F, 0x0F, 0x87, 0xC3, 0xE1, 0xF0, 0xF8, - 0x7C, 0x3E, 0x1F, 0x0F, 0x87, 0xC0, 0x0F, 0x81, 0xFF, 0x1F, 0xF8, 0xFF, - 0xEF, 0x8F, 0x7C, 0x7B, 0xC3, 0xDE, 0x1E, 0xF0, 0xFF, 0x87, 0xFC, 0x3F, - 0xE1, 0xEF, 0x0F, 0x78, 0x78, 0x07, 0xC0, 0x3C, 0x03, 0xE0, 0x1E, 0x01, - 0xF0, 0x0F, 0x00, 0xF8, 0x07, 0x80, 0x7C, 0x07, 0xC0, 0x3E, 0x03, 0xE0, - 0x1F, 0x00, 0xF0, 0x07, 0x80, 0x7C, 0x03, 0xE0, 0x1F, 0xFE, 0xFF, 0xF7, - 0xFF, 0x80, 0x0F, 0xC0, 0xFF, 0x87, 0xFF, 0x1F, 0xFE, 0x78, 0xFB, 0xE1, - 0xEF, 0x87, 0xBC, 0x1E, 0xF0, 0x7B, 0xC1, 0xE0, 0x07, 0x80, 0x1E, 0x00, - 0x78, 0x03, 0xE0, 0x3F, 0x01, 0xF8, 0x07, 0xE0, 0x1F, 0xC0, 0x0F, 0x00, - 0x3E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0xBC, 0x1E, 0xF0, 0x7F, 0xC1, 0xFF, - 0x07, 0xBC, 0x1E, 0xF0, 0x7B, 0xE1, 0xE7, 0xDF, 0x9F, 0xFC, 0x3F, 0xE0, - 0x7F, 0x00, 0x01, 0xF0, 0x07, 0xC0, 0x3F, 0x00, 0xFC, 0x03, 0xF0, 0x1F, - 0xC0, 0x7F, 0x01, 0xFC, 0x06, 0xF0, 0x3B, 0xC0, 0xEF, 0x03, 0xBC, 0x1C, - 0xF0, 0x73, 0xC1, 0xCF, 0x0F, 0x3C, 0x38, 0xF0, 0xE3, 0xC7, 0x8F, 0x1C, - 0x3C, 0x70, 0xF1, 0xC3, 0xCF, 0x0F, 0x38, 0x3C, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, - 0x03, 0xC0, 0xFF, 0xEF, 0xFE, 0xFF, 0xEF, 0xFE, 0xF0, 0x0F, 0x00, 0xF0, - 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF3, 0x0F, 0xFC, 0xFF, 0xEF, 0xFE, 0xF9, - 0xEF, 0x1F, 0xF0, 0xF1, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, - 0xF0, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x1F, 0xFB, - 0xEF, 0xFE, 0x7F, 0xC3, 0xF8, 0x0F, 0xC0, 0xFF, 0x83, 0xFF, 0x1F, 0xFC, - 0x78, 0xF9, 0xE1, 0xEF, 0x87, 0xBE, 0x1E, 0xF8, 0x7B, 0xE1, 0xEF, 0x80, - 0x3E, 0x00, 0xF8, 0x03, 0xEF, 0x8F, 0xFF, 0x3F, 0xFE, 0xFF, 0xFB, 0xE1, - 0xEF, 0x87, 0xBE, 0x1E, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, - 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x79, 0xE1, 0xE7, 0xFF, 0x9F, - 0xFE, 0x3F, 0xF0, 0x7F, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x7C, 0x01, 0xE0, 0x0F, 0x80, 0x3C, 0x00, 0xF0, 0x07, 0xC0, 0x1E, - 0x00, 0x78, 0x03, 0xE0, 0x0F, 0x80, 0x3C, 0x00, 0xF0, 0x07, 0xC0, 0x1E, - 0x00, 0x78, 0x03, 0xE0, 0x0F, 0x80, 0x3C, 0x00, 0xF0, 0x07, 0xC0, 0x1F, - 0x00, 0x7C, 0x01, 0xF0, 0x07, 0x80, 0x1E, 0x00, 0xF8, 0x03, 0xE0, 0x0F, - 0x80, 0x3E, 0x00, 0xF8, 0x00, 0x1F, 0xC1, 0xFF, 0x1F, 0xFC, 0xFB, 0xFF, - 0x8F, 0xFC, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, 0xE0, - 0xFF, 0x87, 0xBC, 0x79, 0xFF, 0xC7, 0xFC, 0x3F, 0xE3, 0xFF, 0x9E, 0x3D, - 0xF1, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, - 0x1F, 0xE0, 0xFF, 0x07, 0xFC, 0x3F, 0xF7, 0xEF, 0xFE, 0x3F, 0xF0, 0xFE, - 0x00, 0x1F, 0x81, 0xFF, 0x1F, 0xFD, 0xFF, 0xEF, 0x8F, 0x78, 0x7F, 0xC3, - 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, - 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0xC7, 0xDF, 0xFE, 0xFF, 0xF3, 0xFF, 0x80, - 0x3C, 0x01, 0xE0, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x0F, - 0xFC, 0x79, 0xF7, 0xCF, 0xFE, 0x3F, 0xE0, 0xFE, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF3, 0x9C, 0xCE, 0x73, 0x98, - 0x00, 0x20, 0x0C, 0x07, 0x81, 0xF0, 0xFE, 0x3F, 0x9F, 0xC7, 0xF0, 0xF8, - 0x1C, 0x03, 0xE0, 0x7E, 0x07, 0xF0, 0x3F, 0x03, 0xF8, 0x1F, 0x01, 0xE0, - 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xC0, 0x80, 0x18, 0x03, 0x80, 0x7C, 0x0F, 0xC0, 0xFE, 0x07, - 0xF0, 0x7F, 0x03, 0xE0, 0x3C, 0x0F, 0x83, 0xF1, 0xFC, 0xFE, 0x3F, 0x87, - 0xC0, 0xF0, 0x18, 0x02, 0x00, 0x00, 0x1F, 0x87, 0xFC, 0x7F, 0xEF, 0xFE, - 0xF1, 0xEF, 0x1F, 0xF1, 0xFF, 0x1F, 0xF1, 0xFF, 0x1F, 0xF1, 0xFF, 0x1F, - 0x01, 0xF0, 0x1E, 0x01, 0xE0, 0x3E, 0x03, 0xE0, 0xFC, 0x3F, 0x83, 0xF0, - 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x00, 0x3F, 0x80, - 0x00, 0xFF, 0xF0, 0x00, 0xFF, 0xFC, 0x00, 0xF8, 0x0F, 0x00, 0xF0, 0x03, - 0xC0, 0xF0, 0x00, 0xF0, 0xF0, 0x00, 0x38, 0x78, 0x00, 0x1C, 0x38, 0x1F, - 0x87, 0x3C, 0x3F, 0xE3, 0x9E, 0x1E, 0xF1, 0xCE, 0x1C, 0x38, 0xE7, 0x0E, - 0x1C, 0x77, 0x8E, 0x0E, 0x1F, 0xC7, 0x07, 0x0F, 0xE3, 0x83, 0x87, 0xF1, - 0xC1, 0xC3, 0xF8, 0xE0, 0xE1, 0xFC, 0x70, 0x70, 0xFE, 0x38, 0x38, 0x7F, - 0x1C, 0x1C, 0x77, 0x8E, 0x0E, 0x39, 0xC7, 0x07, 0x1C, 0xE3, 0xC7, 0xCE, - 0x70, 0xF6, 0xFE, 0x3C, 0x7F, 0x3F, 0x1E, 0x1F, 0x0F, 0x07, 0x81, 0x00, - 0x03, 0xC0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x1F, 0x80, - 0x70, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xFE, 0x00, 0x1F, 0xF0, 0x00, 0x07, - 0xC0, 0x07, 0xE0, 0x0F, 0xE0, 0x0F, 0xE0, 0x0F, 0xE0, 0x0F, 0xE0, 0x0F, - 0xE0, 0x0F, 0xF0, 0x0E, 0xF0, 0x1E, 0xF0, 0x1E, 0xF0, 0x1E, 0xF0, 0x1E, - 0xF0, 0x1E, 0x70, 0x1E, 0x78, 0x1C, 0x78, 0x3C, 0x78, 0x3C, 0x78, 0x3C, - 0x78, 0x3C, 0x78, 0x3C, 0x3C, 0x3C, 0x3C, 0x7C, 0x3C, 0x78, 0x3C, 0x7F, - 0xFC, 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFE, 0x78, 0x1E, 0xF8, 0x1E, 0xF8, - 0x1E, 0xF0, 0x1E, 0xF0, 0x1F, 0xF0, 0x1F, 0xFF, 0x03, 0xFF, 0x8F, 0xFF, - 0x3F, 0xFC, 0xF0, 0xFB, 0xC3, 0xEF, 0x07, 0xBC, 0x1E, 0xF0, 0x7B, 0xC1, - 0xEF, 0x07, 0xBC, 0x3E, 0xF0, 0xFB, 0xC3, 0xCF, 0xFF, 0x3F, 0xF8, 0xFF, - 0xF3, 0xFF, 0xCF, 0x0F, 0xBC, 0x1E, 0xF0, 0x7B, 0xC1, 0xEF, 0x07, 0xFC, - 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xBC, 0x1E, 0xF0, 0x7B, 0xC3, 0xEF, - 0xFF, 0xBF, 0xFC, 0xFF, 0xE3, 0xFF, 0x00, 0x0F, 0xC0, 0x7F, 0xC3, 0xFF, - 0x9F, 0xFE, 0x7C, 0x79, 0xE1, 0xFF, 0x87, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, - 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0x0F, 0x80, 0x3E, 0x00, 0xF8, - 0x03, 0xE0, 0x0F, 0x80, 0x3E, 0x00, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x1F, 0x78, 0x7D, 0xE1, 0xE7, - 0xEF, 0x8F, 0xFE, 0x3F, 0xF0, 0x3F, 0x80, 0xFF, 0x07, 0xFF, 0x3F, 0xFD, - 0xFF, 0xEF, 0x0F, 0xF8, 0x7F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, - 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, - 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, - 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x7F, 0xFF, 0xDF, 0xFE, 0xFF, - 0xE7, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3C, 0x0F, 0x03, - 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, - 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, - 0xC0, 0xF0, 0x3C, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0xF0, - 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, - 0x03, 0xC0, 0xF0, 0x3C, 0x00, 0x0F, 0xE0, 0x7F, 0xC3, 0xFF, 0x9F, 0xFE, - 0x7C, 0x7D, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, - 0xFE, 0x0F, 0xF8, 0x03, 0xE0, 0x0F, 0x80, 0x3E, 0x00, 0xF9, 0xFF, 0xE7, - 0xFF, 0x9F, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, - 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0x78, 0x3D, 0xE1, 0xF7, 0xEF, 0xCF, - 0xFF, 0x3F, 0xEC, 0x3F, 0x30, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xFC, 0x1F, - 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, - 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x07, 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xFC, 0x1F, 0xF0, - 0x7F, 0xC1, 0xFF, 0x07, 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xFC, - 0x1F, 0xF0, 0x7F, 0xC1, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF8, - 0x07, 0xC0, 0x3E, 0x01, 0xF0, 0x0F, 0x80, 0x7C, 0x03, 0xE0, 0x1F, 0x00, - 0xF8, 0x07, 0xC0, 0x3E, 0x01, 0xF0, 0x0F, 0x80, 0x7C, 0x03, 0xE0, 0x1F, - 0x00, 0xF8, 0x07, 0xC0, 0x3E, 0x01, 0xF0, 0x0F, 0xF8, 0x7F, 0xC3, 0xFE, - 0x1F, 0xF0, 0xFF, 0x87, 0xFC, 0x3F, 0xE1, 0xEF, 0x0F, 0x78, 0x7B, 0xF7, - 0xCF, 0xFC, 0x7F, 0xE1, 0xFE, 0x00, 0xF0, 0x3D, 0xE0, 0xFB, 0xC1, 0xE7, - 0x83, 0xCF, 0x0F, 0x9E, 0x1E, 0x3C, 0x3C, 0x78, 0xF8, 0xF1, 0xE1, 0xE7, - 0xC3, 0xCF, 0x87, 0x9E, 0x0F, 0x7C, 0x1E, 0xF8, 0x3D, 0xE0, 0x7F, 0xC0, - 0xFF, 0x01, 0xFE, 0x03, 0xFE, 0x07, 0xBC, 0x0F, 0x7C, 0x1E, 0xF8, 0x3C, - 0xF0, 0x79, 0xF0, 0xF1, 0xE1, 0xE3, 0xC3, 0xC7, 0xC7, 0x87, 0x8F, 0x0F, - 0x9E, 0x1F, 0x3C, 0x1E, 0x78, 0x3E, 0xF0, 0x7D, 0xE0, 0x7C, 0xF0, 0x3C, - 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, - 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, - 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF8, 0x01, 0xFF, 0x80, 0x1F, 0xF8, 0x03, - 0xFF, 0xC0, 0x3F, 0xFC, 0x03, 0xFF, 0xC0, 0x3F, 0xFC, 0x03, 0xFF, 0xC0, - 0x7F, 0xFE, 0x07, 0xFF, 0xE0, 0x7F, 0xFE, 0x07, 0xFE, 0xE0, 0x7F, 0xEE, - 0x0F, 0xFE, 0xF0, 0xFF, 0xEF, 0x0E, 0xFE, 0x70, 0xEF, 0xE7, 0x0E, 0xFE, - 0x70, 0xEF, 0xE7, 0x1E, 0xFE, 0x79, 0xCF, 0xE3, 0x9C, 0xFF, 0x39, 0xCF, - 0xF3, 0x9C, 0xFF, 0x3B, 0xCF, 0xF3, 0xF8, 0xFF, 0x1F, 0x8F, 0xF1, 0xF8, - 0xFF, 0x1F, 0x8F, 0xF1, 0xF8, 0xFF, 0x1F, 0x8F, 0xF0, 0xF0, 0xFF, 0x0F, - 0x0F, 0xF0, 0xF0, 0xFF, 0x0F, 0x0F, 0xE0, 0x3F, 0x80, 0xFF, 0x03, 0xFC, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFF, 0x0F, 0xFC, 0x3F, 0xF0, 0xFF, - 0xE3, 0xFF, 0x8F, 0xFE, 0x3F, 0xFC, 0xFF, 0xF3, 0xFD, 0xCF, 0xF7, 0xBF, - 0xDE, 0xFF, 0x3B, 0xFC, 0xFF, 0xF3, 0xDF, 0xC7, 0x7F, 0x1F, 0xFC, 0x7F, - 0xF0, 0xFF, 0xC3, 0xFF, 0x0F, 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x03, - 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0x70, 0x0F, 0xE0, 0x7F, 0xC3, 0xFF, 0x9F, - 0xFE, 0x7C, 0x7D, 0xE1, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, - 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, - 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, - 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0x78, 0x7D, 0xE1, 0xF7, 0xFF, - 0x8F, 0xFE, 0x3F, 0xF0, 0x3F, 0x80, 0xFF, 0x87, 0xFF, 0x3F, 0xFD, 0xFF, - 0xEF, 0x0F, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, - 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x1F, 0xF0, 0xFF, 0xFF, 0xBF, - 0xFD, 0xFF, 0xCF, 0xF8, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, - 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, - 0x80, 0x00, 0x0F, 0xE0, 0x7F, 0xC3, 0xFF, 0x9F, 0xFE, 0x7C, 0x7D, 0xE1, - 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, - 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, - 0x83, 0xFE, 0x0F, 0x78, 0x7D, 0xE1, 0xF7, 0xFF, 0x8F, 0xFE, 0x3F, 0xF0, - 0x3F, 0x80, 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x01, 0xE0, 0x03, 0x00, 0xFF, - 0x83, 0xFF, 0x8F, 0xFF, 0x3F, 0xFE, 0xF0, 0xFB, 0xC1, 0xEF, 0x07, 0xBC, - 0x1E, 0xF0, 0x7B, 0xC1, 0xEF, 0x07, 0xBC, 0x1E, 0xF0, 0x7B, 0xC3, 0xEF, - 0x1F, 0x3F, 0xFC, 0xFF, 0xE3, 0xFF, 0xCF, 0x0F, 0xBC, 0x1E, 0xF0, 0x7B, - 0xC1, 0xEF, 0x07, 0xBC, 0x1E, 0xF0, 0x7B, 0xC1, 0xEF, 0x07, 0xBC, 0x1E, - 0xF0, 0x7B, 0xC1, 0xEF, 0x07, 0xBC, 0x1E, 0xF0, 0x7B, 0xC1, 0xF0, 0x0F, - 0xC0, 0xFF, 0xC3, 0xFF, 0x1F, 0xFE, 0x78, 0x79, 0xE1, 0xEF, 0x87, 0xBE, - 0x1F, 0xF8, 0x7D, 0xE1, 0xF7, 0x87, 0xDF, 0x1F, 0x7E, 0x00, 0xFC, 0x01, - 0xF8, 0x03, 0xF0, 0x0F, 0xE0, 0x1F, 0x80, 0x3F, 0x00, 0x7E, 0x00, 0xF9, - 0xE1, 0xF7, 0x87, 0xDE, 0x0F, 0x78, 0x3D, 0xE0, 0xF7, 0x83, 0xDE, 0x0F, - 0x78, 0x3D, 0xE1, 0xF7, 0xEF, 0x8F, 0xFE, 0x3F, 0xF0, 0x3F, 0x80, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, - 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, - 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, - 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, 0x80, 0xF8, 0x0F, - 0x80, 0xF8, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, - 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, - 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, - 0x83, 0xFE, 0x0F, 0x78, 0x3D, 0xE1, 0xF7, 0xEF, 0xCF, 0xFE, 0x3F, 0xF8, - 0x7F, 0x80, 0xF0, 0x1F, 0xF0, 0x1E, 0xF0, 0x1E, 0xF0, 0x1E, 0xF8, 0x1E, - 0x78, 0x1E, 0x78, 0x1E, 0x78, 0x3C, 0x78, 0x3C, 0x78, 0x3C, 0x7C, 0x3C, - 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x78, 0x3C, 0x78, 0x3C, 0x78, - 0x1E, 0x78, 0x1E, 0x78, 0x1E, 0x78, 0x1E, 0x78, 0x1E, 0x70, 0x1E, 0xF0, - 0x0E, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xE0, - 0x0F, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0xF0, 0x3C, - 0x0F, 0xF0, 0x3C, 0x0F, 0xF0, 0x3C, 0x0F, 0xF0, 0x3C, 0x1E, 0xF0, 0x3C, - 0x1E, 0x78, 0x3E, 0x1E, 0x78, 0x7E, 0x1E, 0x78, 0x7E, 0x1E, 0x78, 0x7E, - 0x1E, 0x78, 0x7E, 0x1E, 0x78, 0x7E, 0x1E, 0x78, 0x7E, 0x1E, 0x38, 0x7F, - 0x1C, 0x38, 0xF7, 0x1C, 0x3C, 0xF7, 0x3C, 0x3C, 0xE7, 0x3C, 0x3C, 0xE7, - 0x3C, 0x3C, 0xE7, 0x3C, 0x3C, 0xE7, 0x3C, 0x3C, 0xE7, 0xBC, 0x1D, 0xE3, - 0xB8, 0x1D, 0xC3, 0xB8, 0x1D, 0xC3, 0xB8, 0x1F, 0xC3, 0xB8, 0x1F, 0xC3, - 0xB8, 0x1F, 0xC3, 0xF8, 0x1F, 0xC3, 0xF8, 0x1F, 0xC1, 0xF8, 0x0F, 0x81, - 0xF8, 0x0F, 0x81, 0xF0, 0x0F, 0x81, 0xF0, 0x0F, 0x81, 0xF0, 0x0F, 0x81, - 0xF0, 0x0F, 0x81, 0xF0, 0xF0, 0x3F, 0xC0, 0xF7, 0x07, 0x9E, 0x1E, 0x78, - 0x78, 0xE3, 0xE3, 0xCF, 0x0F, 0x3C, 0x3C, 0xF0, 0x7F, 0x81, 0xFE, 0x07, - 0xF8, 0x0F, 0xE0, 0x3F, 0x00, 0xFC, 0x01, 0xF0, 0x07, 0x80, 0x3E, 0x00, - 0xF8, 0x03, 0xF0, 0x0F, 0xC0, 0x7F, 0x01, 0xFE, 0x07, 0xF8, 0x3F, 0xE0, - 0xF3, 0xC3, 0xCF, 0x0F, 0x3C, 0x78, 0x71, 0xE1, 0xE7, 0x87, 0x9E, 0x0E, - 0xF0, 0x3F, 0xC0, 0xF0, 0xF8, 0x0F, 0x78, 0x1F, 0x78, 0x1E, 0x7C, 0x1E, - 0x3C, 0x1E, 0x3C, 0x3E, 0x3C, 0x3C, 0x3E, 0x3C, 0x1E, 0x3C, 0x1E, 0x78, - 0x1E, 0x78, 0x0F, 0x78, 0x0F, 0x78, 0x0F, 0xF0, 0x0F, 0xF0, 0x07, 0xF0, - 0x07, 0xF0, 0x07, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, - 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, - 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, - 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x01, 0xF0, 0x1E, 0x01, 0xE0, 0x3E, - 0x03, 0xE0, 0x3C, 0x07, 0xC0, 0x7C, 0x07, 0xC0, 0x78, 0x0F, 0x80, 0xF8, - 0x0F, 0x80, 0xF0, 0x1F, 0x01, 0xF0, 0x1E, 0x01, 0xE0, 0x3E, 0x03, 0xE0, - 0x3C, 0x07, 0xC0, 0x7C, 0x07, 0xC0, 0x78, 0x0F, 0x80, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, - 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, - 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, - 0xFF, 0xFF, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x07, 0x00, 0xF0, 0x1E, 0x03, - 0xC0, 0x78, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x3C, 0x07, 0x80, 0xF0, - 0x1E, 0x01, 0xC0, 0x3C, 0x07, 0x80, 0xF0, 0x0E, 0x01, 0xE0, 0x3C, 0x07, - 0x80, 0xF0, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0x78, 0x0F, 0x01, 0xE0, - 0x3C, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, - 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, - 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, - 0x0F, 0xC0, 0x1F, 0x80, 0x3F, 0x80, 0x7F, 0x01, 0xFE, 0x03, 0xDC, 0x07, - 0xBC, 0x0E, 0x78, 0x3C, 0xF0, 0x79, 0xE0, 0xF1, 0xE1, 0xE3, 0xC7, 0x87, - 0x8F, 0x0F, 0x1E, 0x0F, 0x3C, 0x1E, 0xF8, 0x3D, 0xE0, 0x7C, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0xF9, 0xE7, 0x8E, 0x38, 0x71, 0xC3, 0x0F, 0xC0, 0xFF, - 0xC3, 0xFF, 0x1F, 0xFE, 0x78, 0x79, 0xE1, 0xEF, 0x87, 0xBE, 0x1E, 0xF8, - 0x7C, 0x01, 0xF0, 0x07, 0xC0, 0x7F, 0x0F, 0xFC, 0x7F, 0xF3, 0xE7, 0xDE, - 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, - 0x87, 0xFE, 0x1F, 0xF8, 0x7D, 0xE3, 0xF7, 0xFF, 0xDF, 0xFF, 0x3F, 0x7C, - 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x79, 0xE3, 0xDF, 0x9F, - 0xFE, 0xFF, 0xFF, 0xC7, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, - 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, - 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xFC, - 0x7F, 0xF7, 0xFF, 0xFE, 0xF7, 0xF7, 0x9F, 0x00, 0x0F, 0xC1, 0xFF, 0x8F, - 0xFC, 0xFF, 0xF7, 0x87, 0xBC, 0x3F, 0xE1, 0xFF, 0x0F, 0xF8, 0x7F, 0xC3, - 0xFE, 0x1F, 0xF0, 0x0F, 0x80, 0x7C, 0x03, 0xE0, 0x1F, 0x00, 0xF8, 0x07, - 0xC0, 0x3E, 0x1F, 0xF0, 0xFF, 0x87, 0xFC, 0x3F, 0xE1, 0xEF, 0x0F, 0x78, - 0x7B, 0xE7, 0xDF, 0xFC, 0x7F, 0xE1, 0xFE, 0x00, 0x00, 0x7C, 0x01, 0xF0, - 0x07, 0xC0, 0x1F, 0x00, 0x7C, 0x7D, 0xF3, 0xFF, 0xDF, 0xFF, 0x7F, 0xFD, - 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, - 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, - 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7D, 0xE1, - 0xF7, 0xDF, 0xDF, 0xFF, 0x3F, 0xFC, 0x7D, 0xF0, 0x0F, 0xC0, 0x7F, 0xC3, - 0xFF, 0x1F, 0xFE, 0x78, 0x79, 0xE1, 0xEF, 0x87, 0xBE, 0x1E, 0xF8, 0x7B, - 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0xF8, 0x03, 0xE0, 0x0F, 0x87, 0xBE, 0x1E, 0xF8, 0x7B, 0xE1, 0xEF, 0x87, - 0x9E, 0x1E, 0x78, 0x79, 0xF3, 0xE7, 0xFF, 0x0F, 0xFC, 0x1F, 0xE0, 0x07, - 0xC3, 0xF1, 0xFC, 0x7C, 0x1E, 0x07, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, - 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x78, - 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, - 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x80, 0x1F, 0x7C, 0xFF, 0xF7, 0xFF, 0xDF, - 0xFF, 0x78, 0x7D, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, - 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, - 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7D, 0xE1, 0xF7, 0xCF, 0xDF, 0xFF, - 0x3F, 0xFC, 0x7D, 0xF0, 0x07, 0xC0, 0x1F, 0x00, 0x79, 0xC3, 0xE7, 0xFF, - 0xBF, 0xFC, 0x3F, 0xC0, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, - 0x79, 0xF3, 0xDF, 0xDF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFC, 0x1F, 0xE0, 0xFF, - 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, 0xE0, - 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, - 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xC0, - 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3C, 0x78, 0xF1, 0xE0, 0x00, 0x0F, - 0x9F, 0x3E, 0x7C, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3E, 0x7C, 0xF9, 0xF3, - 0xE7, 0xCF, 0x9F, 0x3E, 0x7C, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3E, 0x7C, - 0xF9, 0xFF, 0xDF, 0xBE, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, - 0xF0, 0x03, 0xC1, 0xFF, 0x07, 0xBC, 0x1E, 0xF0, 0xFB, 0xC3, 0xCF, 0x1F, - 0x3C, 0x78, 0xF1, 0xE3, 0xCF, 0x8F, 0x3C, 0x3D, 0xF0, 0xF7, 0x83, 0xDE, - 0x0F, 0xF8, 0x3F, 0xC0, 0xFF, 0x83, 0xDE, 0x0F, 0x78, 0x3D, 0xF0, 0xF3, - 0xC3, 0xCF, 0x8F, 0x1E, 0x3C, 0x7C, 0xF0, 0xF3, 0xC3, 0xCF, 0x0F, 0xBC, - 0x1E, 0xF0, 0x7F, 0xC0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xE1, - 0xF3, 0xDF, 0xCF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xFC, 0x3F, - 0xC1, 0xE0, 0xFF, 0x07, 0x83, 0xFC, 0x1E, 0x0F, 0xF0, 0x78, 0x3F, 0xC1, - 0xE0, 0xFF, 0x07, 0x83, 0xFC, 0x1E, 0x0F, 0xF0, 0x78, 0x3F, 0xC1, 0xE0, - 0xFF, 0x07, 0x83, 0xFC, 0x1E, 0x0F, 0xF0, 0x78, 0x3F, 0xC1, 0xE0, 0xFF, - 0x07, 0x83, 0xFC, 0x1E, 0x0F, 0xF0, 0x78, 0x3F, 0xC1, 0xE0, 0xFF, 0x07, - 0x83, 0xFC, 0x1E, 0x0F, 0xF0, 0x78, 0x3F, 0xC1, 0xE0, 0xFF, 0x07, 0x83, - 0xFC, 0x1E, 0x0F, 0xF0, 0x78, 0x3C, 0xF3, 0xE7, 0xBF, 0xBF, 0xFF, 0xFF, - 0xFF, 0x87, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, 0x1F, - 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xFC, - 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, - 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0x80, 0x0F, 0xC0, 0x7F, 0x83, 0xFF, 0x1F, - 0xFE, 0x78, 0x79, 0xE1, 0xEF, 0x87, 0xBE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, - 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, - 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xDE, 0x1E, - 0x78, 0x79, 0xF3, 0xE7, 0xFF, 0x0F, 0xFC, 0x1F, 0xE0, 0xF3, 0xC7, 0xBF, - 0xBF, 0xFD, 0xFF, 0xFF, 0x8F, 0xF8, 0x7F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, - 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, - 0x7F, 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, - 0xF8, 0xFF, 0xEF, 0xFF, 0xFD, 0xFF, 0xEF, 0x7E, 0x78, 0x03, 0xC0, 0x1E, - 0x00, 0xF0, 0x07, 0x80, 0x00, 0x1E, 0x7C, 0xFF, 0xF7, 0xFF, 0xDF, 0xFF, - 0x78, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, - 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, - 0xFF, 0x87, 0xFE, 0x1F, 0xF8, 0x7F, 0xE1, 0xFF, 0x87, 0xFE, 0x1F, 0x78, - 0x7D, 0xF7, 0xF7, 0xFF, 0xCF, 0xFF, 0x1F, 0x7C, 0x01, 0xF0, 0x07, 0xC0, - 0x1F, 0x00, 0x7C, 0x01, 0xF0, 0xF3, 0xFB, 0xFF, 0xFF, 0xFF, 0xC7, 0x83, - 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, - 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1E, - 0x0F, 0x00, 0x1F, 0x83, 0xFE, 0x1F, 0xF9, 0xFF, 0xCF, 0x0F, 0x78, 0x7B, - 0xC3, 0xDE, 0x1E, 0xF0, 0xF7, 0xC7, 0xBE, 0x00, 0xF8, 0x07, 0xE0, 0x1F, - 0x80, 0x7E, 0x01, 0xF8, 0x07, 0xE0, 0x1F, 0x80, 0x7D, 0xE1, 0xEF, 0x0F, - 0xF8, 0x7F, 0xC3, 0xFE, 0x1F, 0xF0, 0xF7, 0xEF, 0x9F, 0xFC, 0xFF, 0xC1, - 0xFC, 0x00, 0x3E, 0x0F, 0x83, 0xE0, 0xF8, 0x3E, 0x0F, 0x8F, 0xFF, 0xFF, - 0xFF, 0xCF, 0x83, 0xE0, 0xF8, 0x3E, 0x0F, 0x83, 0xE0, 0xF8, 0x3E, 0x0F, - 0x83, 0xE0, 0xF8, 0x3E, 0x0F, 0x83, 0xE0, 0xF8, 0x3E, 0x0F, 0x83, 0xE0, - 0xF8, 0x3E, 0x0F, 0x83, 0xF0, 0xFF, 0x1F, 0xC3, 0xF0, 0xF8, 0x3F, 0xE0, - 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, - 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, - 0x83, 0xFE, 0x0F, 0x78, 0x7D, 0xF3, 0xF7, 0xFF, 0xCF, 0xEF, 0x1F, 0x3C, - 0xF0, 0x7F, 0xC1, 0xEF, 0x07, 0xBC, 0x1E, 0x78, 0x79, 0xE1, 0xE7, 0x87, - 0x9E, 0x1C, 0x78, 0x71, 0xE3, 0xC7, 0x8F, 0x0E, 0x3C, 0x3C, 0xF0, 0xF3, - 0xC3, 0xCE, 0x0F, 0x38, 0x3C, 0xE0, 0xF3, 0x81, 0xDE, 0x07, 0x78, 0x1F, - 0xE0, 0x7F, 0x01, 0xFC, 0x07, 0xF0, 0x1F, 0xC0, 0x3F, 0x00, 0xFC, 0x03, - 0xF0, 0x0F, 0x80, 0xF0, 0x78, 0x7F, 0x87, 0xC3, 0xFC, 0x3E, 0x1F, 0xE1, - 0xF0, 0xF7, 0x0F, 0x87, 0xB8, 0x7C, 0x39, 0xC3, 0xE1, 0xCE, 0x1F, 0x8E, - 0x79, 0xDC, 0x73, 0xCE, 0xE7, 0x9E, 0x77, 0x3C, 0xF3, 0xB9, 0xE3, 0x9D, - 0xCF, 0x1C, 0xE6, 0x70, 0xE7, 0x3B, 0x87, 0x71, 0xDC, 0x3B, 0x8E, 0xE1, - 0xDC, 0x77, 0x0F, 0xE3, 0xB8, 0x7F, 0x1D, 0xC3, 0xF8, 0xFE, 0x0F, 0x83, - 0xE0, 0x7C, 0x1F, 0x03, 0xE0, 0xF8, 0x1F, 0x07, 0xC0, 0xF8, 0x3E, 0x07, - 0xC1, 0xF0, 0x3E, 0x0F, 0x81, 0xE0, 0x7C, 0x00, 0xF0, 0x7B, 0xC1, 0xE7, - 0x87, 0x9E, 0x3C, 0x78, 0xF0, 0xF3, 0xC3, 0xCE, 0x0F, 0x78, 0x1F, 0xE0, - 0x7F, 0x01, 0xFC, 0x03, 0xF0, 0x0F, 0x80, 0x3E, 0x00, 0xF8, 0x03, 0xE0, - 0x0F, 0x80, 0x7F, 0x01, 0xFC, 0x07, 0xF0, 0x3D, 0xE0, 0xF7, 0x83, 0xDE, - 0x1E, 0x3C, 0x78, 0xF1, 0xE3, 0xCF, 0x07, 0xBC, 0x1E, 0xF0, 0x7C, 0xF0, - 0x3F, 0xC0, 0xFF, 0x07, 0xFE, 0x1E, 0x78, 0x79, 0xE1, 0xE7, 0x87, 0x9E, - 0x1E, 0x78, 0x79, 0xE1, 0xC3, 0x87, 0x0F, 0x3C, 0x3C, 0xF0, 0xF3, 0xC3, - 0xCF, 0x0F, 0x3C, 0x1C, 0xE0, 0x73, 0x81, 0xCE, 0x07, 0xB8, 0x1F, 0xE0, - 0x7F, 0x80, 0xFC, 0x03, 0xF0, 0x0F, 0xC0, 0x3F, 0x00, 0xFC, 0x03, 0xF0, - 0x07, 0x80, 0x1E, 0x00, 0xF8, 0x1F, 0xE0, 0x7F, 0x01, 0xFC, 0x00, 0x7F, - 0xEF, 0xFD, 0xFF, 0xBF, 0xF0, 0x1E, 0x07, 0xC0, 0xF0, 0x1E, 0x07, 0xC0, - 0xF0, 0x1E, 0x03, 0xC0, 0xF8, 0x1E, 0x03, 0xC0, 0xF8, 0x1E, 0x03, 0xC0, - 0xF8, 0x1F, 0x03, 0xC0, 0x78, 0x1F, 0x03, 0xC0, 0x78, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x07, 0xC3, 0xF1, 0xFC, 0x7E, 0x1E, 0x07, 0x81, 0xE0, - 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0xF8, 0x7E, - 0x3F, 0x0F, 0x83, 0xF0, 0xFE, 0x0F, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, - 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0xC1, 0xFC, 0x3F, - 0x07, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF8, 0x3F, 0x0F, - 0xC1, 0xF8, 0x3E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x78, - 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1F, 0x83, 0xF0, 0x7C, 0x3F, 0x1F, 0xC7, - 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, - 0x78, 0x3E, 0x0F, 0x8F, 0xC3, 0xF0, 0xF8, 0x00, 0x1E, 0x07, 0x9F, 0xF3, - 0xDF, 0xFF, 0xDF, 0xFF, 0xEF, 0xFF, 0xE1, 0x03, 0xE0}; +const uint8_t Antonio_SemiBold20pt7bBitmaps_Gzip[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xa5, 0x57, + 0x4f, 0x6f, 0xe3, 0xba, 0x11, 0xa7, 0xaa, 0xe2, 0xf1, 0x1d, 0x16, 0xe6, + 0xf5, 0x1d, 0xbc, 0x62, 0x3f, 0xc2, 0xeb, 0xcd, 0x8b, 0x55, 0xac, 0xaf, + 0xf2, 0x4e, 0x7b, 0x76, 0xb0, 0x40, 0x57, 0x46, 0x94, 0x48, 0x81, 0x81, + 0xea, 0x52, 0xac, 0xaf, 0x5b, 0x60, 0x9b, 0x7c, 0x8d, 0x2d, 0x10, 0x6c, + 0x64, 0x08, 0x78, 0xbe, 0x45, 0x5f, 0x60, 0x11, 0x53, 0x10, 0x50, 0x5f, + 0x8a, 0x8a, 0x86, 0x80, 0x4a, 0x41, 0x14, 0xb1, 0x3f, 0xd2, 0x72, 0xfe, + 0x6c, 0xb2, 0xd9, 0xd7, 0x96, 0xa4, 0x39, 0x43, 0x8a, 0x96, 0x86, 0x33, + 0xc3, 0xdf, 0x0c, 0x89, 0xea, 0xcb, 0x1b, 0x5d, 0x08, 0xd3, 0x6c, 0x79, + 0x70, 0x59, 0x7f, 0xf8, 0x95, 0xd8, 0x73, 0x6b, 0x19, 0x15, 0xd1, 0x26, + 0x99, 0x88, 0xd1, 0x64, 0xe8, 0x0f, 0x5c, 0x3a, 0xa4, 0xef, 0xed, 0x98, + 0x9b, 0xe5, 0xed, 0x48, 0xb8, 0x13, 0x67, 0x34, 0x18, 0xd2, 0x81, 0x3d, + 0x67, 0xaa, 0x56, 0xcd, 0xdd, 0x3a, 0x66, 0xd6, 0x59, 0x69, 0x54, 0x24, + 0xfb, 0xc2, 0x17, 0xe4, 0x07, 0xf2, 0xb3, 0x15, 0xbd, 0x20, 0xbf, 0xf0, + 0x85, 0x3a, 0x6d, 0xaf, 0xeb, 0xf8, 0x6c, 0x2f, 0x2f, 0x99, 0x7f, 0x50, + 0xac, 0x48, 0x60, 0x35, 0x4c, 0x78, 0x51, 0x60, 0x49, 0x9a, 0x8c, 0x65, + 0x1d, 0xb7, 0x5e, 0xae, 0x58, 0x7b, 0xa0, 0xb2, 0xce, 0x13, 0x9d, 0x95, + 0x0c, 0xc8, 0xc4, 0x8e, 0x86, 0x84, 0x47, 0x56, 0x42, 0x3b, 0xe2, 0x92, + 0x50, 0xd8, 0x09, 0x3b, 0x25, 0x23, 0x22, 0x25, 0x8d, 0x18, 0x23, 0x13, + 0x30, 0x8c, 0x80, 0x91, 0x60, 0x06, 0x60, 0x2c, 0x01, 0x66, 0x08, 0xc6, + 0x4e, 0x7e, 0x91, 0xd2, 0xf5, 0x32, 0x66, 0xc7, 0x9d, 0x94, 0xbe, 0x2a, + 0x19, 0x9d, 0x0f, 0x24, 0x8a, 0x62, 0x6c, 0xc0, 0xa4, 0x14, 0xa2, 0x5e, + 0x39, 0x03, 0x16, 0xe6, 0x42, 0x54, 0xed, 0x70, 0xc0, 0xf8, 0x2c, 0x11, + 0x92, 0x8c, 0x06, 0x8c, 0xd0, 0x08, 0x8c, 0xaf, 0x19, 0x02, 0x46, 0x82, + 0x19, 0x80, 0xb1, 0x04, 0xbe, 0xe2, 0x90, 0x8d, 0xb0, 0x12, 0x66, 0x24, + 0x49, 0xec, 0xc8, 0x6e, 0x6c, 0x61, 0x77, 0x44, 0x45, 0x81, 0x74, 0x86, + 0x34, 0xb6, 0xf2, 0xc4, 0x9f, 0x80, 0x99, 0x93, 0x2a, 0xda, 0x13, 0xcc, + 0xb7, 0x5a, 0x72, 0x04, 0xd1, 0x69, 0x62, 0x4b, 0xd2, 0xfd, 0x18, 0xfe, + 0xd9, 0x2b, 0xe6, 0x6e, 0x3d, 0x9f, 0xe6, 0xad, 0x1f, 0xba, 0x7c, 0x6e, + 0x17, 0x89, 0x94, 0x63, 0x87, 0xc7, 0x6a, 0xa3, 0x5a, 0x6f, 0x1d, 0x6f, + 0x84, 0x52, 0xf5, 0x9f, 0xde, 0xbc, 0x19, 0x2b, 0x95, 0x39, 0xae, 0xbf, + 0x29, 0x96, 0xf3, 0x27, 0xc8, 0x75, 0x9d, 0x27, 0x9b, 0x0a, 0xc3, 0xec, + 0x74, 0x1c, 0x5c, 0x55, 0xeb, 0x47, 0xe4, 0xa0, 0x2e, 0x3f, 0x40, 0xf2, + 0xe1, 0xef, 0xf7, 0x5f, 0x5d, 0x5c, 0x1f, 0xaa, 0x45, 0x6b, 0x0b, 0xaf, + 0x28, 0xd5, 0xdb, 0xbf, 0xfe, 0x4a, 0x7f, 0x1c, 0x42, 0x5f, 0x50, 0x15, + 0x9a, 0x36, 0xa0, 0xd6, 0x5c, 0xcf, 0xff, 0x65, 0xfd, 0xc7, 0x8f, 0x09, + 0x28, 0x44, 0x20, 0x0d, 0x8d, 0x5c, 0x4b, 0x0c, 0x88, 0xd4, 0x34, 0x71, + 0x34, 0x1d, 0xd9, 0x5b, 0xea, 0x1b, 0xca, 0x88, 0xa6, 0xd0, 0x87, 0xa6, + 0xae, 0xa1, 0x34, 0x22, 0x4c, 0x9b, 0x56, 0xd5, 0x73, 0x63, 0xc0, 0x26, + 0x4c, 0x3b, 0x2e, 0x55, 0xfc, 0xed, 0xc1, 0xd9, 0x61, 0x5d, 0x76, 0x9e, + 0x54, 0xc4, 0x9e, 0xa5, 0x85, 0x96, 0xe6, 0x94, 0xc5, 0x69, 0x2e, 0x9b, + 0x60, 0xcc, 0x9f, 0x62, 0x12, 0x76, 0xac, 0x78, 0xa3, 0xca, 0x79, 0x70, + 0x90, 0x5e, 0x3a, 0xdb, 0x97, 0xc1, 0x83, 0x7c, 0x9a, 0xb8, 0xb6, 0x70, + 0x2c, 0xc8, 0x04, 0xb1, 0x03, 0xb8, 0x90, 0x2d, 0xb8, 0x96, 0x34, 0x00, + 0xed, 0xe0, 0x93, 0x11, 0x4b, 0x54, 0xac, 0x78, 0xe7, 0x5f, 0xe7, 0x65, + 0x7c, 0xe6, 0xc8, 0x83, 0x85, 0xa0, 0x91, 0x03, 0xc1, 0x85, 0x67, 0x35, + 0x54, 0xf0, 0x84, 0x91, 0x31, 0xf1, 0x2d, 0x41, 0xf1, 0x2c, 0x5c, 0x28, + 0x4d, 0x0e, 0xf2, 0xf5, 0xea, 0xb4, 0xf5, 0x44, 0x48, 0xb4, 0x4f, 0x7a, + 0xa4, 0xb5, 0x25, 0x4f, 0x42, 0xab, 0xfd, 0x41, 0xbe, 0x4e, 0x4a, 0xfb, + 0x6c, 0x28, 0xdf, 0x2e, 0x32, 0xe6, 0x8e, 0x24, 0x2c, 0x32, 0x74, 0x27, + 0x9b, 0x34, 0x63, 0x23, 0xb7, 0x3f, 0x41, 0x44, 0xc2, 0x3b, 0xe1, 0x1b, + 0x20, 0xaa, 0xec, 0xd0, 0x7a, 0x0d, 0x33, 0x52, 0xb1, 0x16, 0xc3, 0xab, + 0x92, 0xcb, 0xcd, 0x6e, 0x4a, 0x32, 0xb8, 0xa2, 0x69, 0xfc, 0xba, 0xec, + 0xc2, 0xb4, 0x81, 0xa8, 0x33, 0xc5, 0x5b, 0xff, 0x0a, 0xa2, 0x7e, 0x76, + 0x9a, 0x83, 0xbc, 0x8c, 0xc6, 0xa4, 0xb1, 0xcb, 0xb9, 0xf2, 0x3a, 0x75, + 0xbd, 0x9d, 0x0c, 0x73, 0x15, 0x77, 0x7c, 0x47, 0xa6, 0xf9, 0x5a, 0x9d, + 0x42, 0x8b, 0x61, 0xb4, 0x13, 0x20, 0x80, 0x39, 0x22, 0x08, 0x40, 0x13, + 0xb3, 0xc9, 0x27, 0x78, 0x4e, 0xf4, 0x59, 0x83, 0x0e, 0x1a, 0x3d, 0x85, + 0x0f, 0x10, 0x1c, 0x4a, 0xde, 0x5e, 0x2b, 0x58, 0x6e, 0xd1, 0x31, 0x19, + 0xce, 0x5a, 0x2e, 0x60, 0x9f, 0xa9, 0x5a, 0xb6, 0x5e, 0xa1, 0x4e, 0xf6, + 0x36, 0x8a, 0x36, 0xf7, 0x9e, 0xd0, 0xd6, 0x33, 0x96, 0xeb, 0x08, 0x87, + 0x49, 0x6e, 0x60, 0x12, 0x1f, 0x26, 0xbd, 0x7d, 0xda, 0x2f, 0x5d, 0xae, + 0x3a, 0x55, 0x29, 0xed, 0x48, 0xbb, 0x27, 0xac, 0x9d, 0xd6, 0xe6, 0x50, + 0x13, 0x78, 0x1e, 0x41, 0x31, 0x40, 0xa3, 0x22, 0xcd, 0x12, 0x20, 0x4a, + 0xf5, 0xf1, 0xe2, 0xed, 0x07, 0xf2, 0x87, 0x17, 0xf4, 0x58, 0x76, 0xde, + 0xe9, 0x52, 0x36, 0x43, 0x5b, 0x1c, 0x51, 0xe9, 0xd9, 0x0d, 0xb7, 0xc4, + 0x0b, 0xa2, 0x77, 0xd8, 0xf6, 0xff, 0x4a, 0xa2, 0x9f, 0xec, 0x28, 0x60, + 0x49, 0x47, 0x65, 0x68, 0x0b, 0x97, 0xcd, 0x36, 0x6d, 0xe7, 0xc5, 0x89, + 0xfc, 0xe9, 0x77, 0x78, 0x53, 0xdc, 0x86, 0x65, 0xb7, 0x29, 0xf9, 0x46, + 0xf5, 0xcd, 0x92, 0xf0, 0x55, 0xf8, 0x45, 0xeb, 0xcd, 0xa4, 0x0b, 0x87, + 0x45, 0x33, 0xdf, 0xec, 0x79, 0x3b, 0x21, 0x5e, 0x44, 0x20, 0x12, 0x5e, + 0xdf, 0x30, 0x6d, 0x3a, 0x0d, 0x25, 0x64, 0xe4, 0x93, 0xe1, 0x88, 0xc7, + 0xae, 0x57, 0x9c, 0x38, 0x9b, 0x8b, 0xe1, 0x68, 0x3d, 0x18, 0xbe, 0x7b, + 0x3f, 0xe0, 0x4b, 0xca, 0x8a, 0x59, 0xbc, 0x59, 0xa4, 0x8d, 0xc8, 0xdb, + 0xc9, 0xa4, 0x1b, 0x8d, 0xc2, 0xa1, 0x7e, 0xf0, 0x6a, 0x49, 0x87, 0xc5, + 0xf2, 0x62, 0xf2, 0xef, 0xce, 0x0d, 0x3d, 0x87, 0x33, 0x7a, 0x4c, 0xf0, + 0x62, 0xa0, 0x12, 0xbc, 0x01, 0x10, 0x30, 0xa1, 0xaa, 0x21, 0x0a, 0x2a, + 0x93, 0x84, 0x26, 0x54, 0xb0, 0xbb, 0x2a, 0x07, 0xd2, 0xd9, 0xd5, 0x89, + 0xe3, 0x0f, 0x7d, 0x77, 0x57, 0x51, 0x02, 0x90, 0xb0, 0x45, 0xed, 0xc2, + 0xce, 0x77, 0x1a, 0x54, 0x2c, 0xe3, 0x92, 0x2b, 0x5b, 0xc1, 0x29, 0x5a, + 0x79, 0x9d, 0x96, 0xc6, 0x63, 0x17, 0x20, 0x63, 0x8c, 0x32, 0xe5, 0x35, + 0x50, 0x7b, 0xc6, 0xfa, 0xc9, 0x96, 0xdf, 0x3a, 0x75, 0x5a, 0xaa, 0xf3, + 0x56, 0x15, 0x8a, 0xb0, 0x24, 0x4c, 0xe1, 0x34, 0xc1, 0x54, 0xbb, 0x10, + 0x6b, 0x3c, 0xa1, 0x66, 0x86, 0x18, 0x5f, 0xe8, 0x5d, 0xe2, 0x76, 0x12, + 0x84, 0xfb, 0x87, 0xf9, 0xba, 0x9c, 0xc3, 0xe2, 0x5e, 0xa4, 0xa8, 0xf2, + 0x60, 0x71, 0x1c, 0xe2, 0xc5, 0x23, 0x8b, 0x3f, 0x35, 0x08, 0x15, 0xdc, + 0x60, 0xdd, 0x12, 0x63, 0x69, 0xe9, 0x02, 0x93, 0x6f, 0x3b, 0x33, 0x95, + 0x3c, 0x98, 0x33, 0x8b, 0x1e, 0x2f, 0x35, 0x33, 0xf6, 0xc3, 0xa5, 0xba, + 0x23, 0x4c, 0x6c, 0x77, 0x72, 0x78, 0x4f, 0x5a, 0xd6, 0x6f, 0xe1, 0x4a, + 0xe9, 0x93, 0xc1, 0x1e, 0xec, 0x84, 0xf9, 0x7b, 0x79, 0x5d, 0x42, 0x47, + 0xff, 0xf4, 0x7e, 0x36, 0x7a, 0xe9, 0xd5, 0xf3, 0x80, 0xf4, 0xe5, 0xc9, + 0x67, 0x5b, 0x22, 0xd5, 0xd7, 0x05, 0xd8, 0x93, 0x8c, 0x81, 0x41, 0x06, + 0x75, 0x9e, 0x1c, 0xdc, 0x83, 0x40, 0x1d, 0xf7, 0xea, 0xac, 0x0d, 0xf3, + 0x8e, 0xc8, 0x3d, 0x71, 0xbd, 0x58, 0xcf, 0x32, 0x76, 0xe2, 0xb8, 0xae, + 0xdf, 0x6c, 0xf2, 0x75, 0x9a, 0xc5, 0x27, 0x2c, 0x70, 0x9a, 0x3d, 0x11, + 0x26, 0xca, 0xea, 0xec, 0x8e, 0x9e, 0xe9, 0xa1, 0x2b, 0xa7, 0x72, 0x93, + 0x17, 0xe9, 0x72, 0x19, 0xcf, 0xd9, 0x09, 0x77, 0x1d, 0x7f, 0x2c, 0x0f, + 0x45, 0xf0, 0x48, 0x25, 0x4f, 0x76, 0x46, 0xa1, 0x8d, 0xa5, 0x22, 0xde, + 0xd8, 0x2a, 0xf1, 0xda, 0x5d, 0x17, 0x76, 0x54, 0x09, 0x74, 0x9d, 0x08, + 0xff, 0xc5, 0x3a, 0xa9, 0xca, 0x41, 0x37, 0x29, 0xd7, 0xa6, 0x73, 0xba, + 0x69, 0x56, 0x7c, 0x54, 0xaf, 0xb2, 0xea, 0xa3, 0x7a, 0x9d, 0x55, 0x8d, + 0xe2, 0xf3, 0xcd, 0x6d, 0x67, 0x22, 0xa8, 0xe9, 0x10, 0xb7, 0x95, 0xdd, + 0x6e, 0xb5, 0x8b, 0xf3, 0x0d, 0x34, 0x2f, 0x14, 0x1c, 0xa5, 0x55, 0xd5, + 0x4d, 0x56, 0x9f, 0x5f, 0xaa, 0xd7, 0xe0, 0x56, 0xcb, 0x90, 0xb7, 0xa1, + 0x54, 0x29, 0x16, 0x18, 0x25, 0xe2, 0x0f, 0xd2, 0x4b, 0x26, 0xb7, 0xc6, + 0xcb, 0xef, 0x1b, 0xef, 0x19, 0x02, 0x37, 0xac, 0x55, 0xef, 0x86, 0x71, + 0xef, 0x86, 0x5f, 0x7b, 0x1e, 0x54, 0xac, 0xce, 0x6f, 0x54, 0xd6, 0xf8, + 0x7d, 0xc0, 0x72, 0xfb, 0x28, 0x65, 0x06, 0xe4, 0xff, 0xf9, 0xa6, 0x83, + 0x83, 0x8c, 0x38, 0x61, 0x13, 0x6c, 0x55, 0x03, 0xb2, 0xbc, 0x5e, 0xdc, + 0x9d, 0x3d, 0x73, 0xbe, 0xb8, 0xa7, 0xcf, 0xd7, 0xed, 0xd9, 0x7b, 0x82, + 0x48, 0x20, 0x7c, 0xaa, 0x83, 0xd1, 0x54, 0x83, 0x39, 0x6f, 0xf0, 0xf6, + 0x78, 0xc5, 0x8f, 0x48, 0x6b, 0x35, 0xb6, 0x64, 0x82, 0x47, 0x1e, 0xf2, + 0x85, 0x2b, 0x4c, 0x5e, 0xc2, 0x55, 0x45, 0x3d, 0xbb, 0xdc, 0x7a, 0x6c, + 0xbf, 0x69, 0x53, 0x58, 0xd4, 0xfc, 0xf6, 0xf6, 0x1b, 0x76, 0xf8, 0xd4, + 0x01, 0xe9, 0xbc, 0x26, 0x8c, 0x00, 0x33, 0x06, 0x93, 0x1a, 0xc7, 0xd7, + 0xd5, 0x20, 0x52, 0xe0, 0xf6, 0xc5, 0x0c, 0x9d, 0x5d, 0x9d, 0x38, 0x00, + 0x31, 0xb6, 0xab, 0x00, 0x35, 0xda, 0x57, 0x38, 0xdf, 0xae, 0x39, 0x52, + 0x3b, 0xad, 0xe3, 0x1f, 0x7d, 0xdd, 0x80, 0x9f, 0xa3, 0x7a, 0xe8, 0xd6, + 0xae, 0xbb, 0xbe, 0xdf, 0xce, 0x5e, 0x16, 0x9f, 0x5e, 0xa6, 0xba, 0xf1, + 0xbe, 0x35, 0xba, 0x2d, 0x1a, 0x76, 0x8c, 0x26, 0xef, 0x37, 0x78, 0x53, + 0x4d, 0x4f, 0x1c, 0xdf, 0x2f, 0x0a, 0xc4, 0x65, 0x57, 0x86, 0xc7, 0x1d, + 0xd5, 0x39, 0x25, 0xd4, 0x0a, 0xa3, 0x6b, 0x50, 0x83, 0xde, 0x43, 0x0b, + 0x93, 0x9e, 0xa8, 0x10, 0xad, 0x5d, 0x7f, 0x3f, 0x5f, 0xc7, 0x27, 0x03, + 0xfc, 0x4f, 0x36, 0xcc, 0xe7, 0xbe, 0x13, 0x38, 0xae, 0xe3, 0x8e, 0x5d, + 0x34, 0x30, 0xd8, 0x11, 0xd3, 0x15, 0x7b, 0xa1, 0xba, 0x0a, 0xfb, 0x3b, + 0x35, 0xac, 0x15, 0xda, 0x2e, 0xca, 0xb8, 0x34, 0x41, 0x1a, 0xe2, 0x1b, + 0x03, 0x48, 0xfe, 0x68, 0xf6, 0x0e, 0x34, 0xe4, 0x33, 0x45, 0x3f, 0x76, + 0xec, 0xc4, 0xa7, 0x64, 0x4b, 0x22, 0x4d, 0x5c, 0x4d, 0x2c, 0x43, 0x06, + 0x96, 0xd0, 0x84, 0x19, 0xe2, 0x6b, 0x62, 0x7c, 0xe3, 0x99, 0xa2, 0x1f, + 0x27, 0x70, 0xb0, 0x08, 0x8a, 0xb0, 0xbf, 0xd0, 0xb3, 0x81, 0x0f, 0x4c, + 0x11, 0xc0, 0x14, 0x8d, 0x28, 0x0e, 0x73, 0x35, 0xe4, 0x04, 0x46, 0xac, + 0xab, 0xf5, 0xfb, 0xd1, 0x7e, 0xfa, 0xc0, 0x53, 0x9d, 0xc6, 0xa4, 0xe7, + 0x21, 0x6b, 0xc3, 0x6a, 0x7d, 0xc9, 0xbf, 0x4a, 0x42, 0x0e, 0x8b, 0x5a, + 0xad, 0x94, 0x17, 0xec, 0x8e, 0xdb, 0xb4, 0x58, 0x9d, 0x76, 0x0a, 0xd9, + 0xc3, 0xf3, 0xc1, 0xa1, 0x85, 0xd2, 0xba, 0xba, 0x3e, 0xd5, 0x89, 0xe3, + 0x1c, 0x89, 0x63, 0x7c, 0x76, 0x97, 0x1e, 0xde, 0x03, 0xcf, 0x7b, 0xa8, + 0xb9, 0x5e, 0x19, 0xd4, 0x34, 0xf9, 0x8b, 0x4e, 0x63, 0x0e, 0x2b, 0x7c, + 0x37, 0xbc, 0x79, 0x20, 0xcc, 0x93, 0x04, 0xe7, 0x6c, 0x05, 0x09, 0xdb, + 0x43, 0x69, 0x82, 0xdf, 0xdd, 0xb6, 0x0e, 0xcc, 0x73, 0x63, 0x8e, 0xce, + 0x04, 0xbf, 0x3e, 0xef, 0x8a, 0xe1, 0x4e, 0xd3, 0x6a, 0xad, 0x21, 0x4b, + 0xd0, 0x74, 0xd3, 0x06, 0x0e, 0x9d, 0xeb, 0x45, 0x1b, 0xe1, 0x3b, 0xf4, + 0xf8, 0x5b, 0x5d, 0xc4, 0x03, 0xa5, 0x55, 0x01, 0xd4, 0x78, 0x56, 0x96, + 0x6c, 0x2b, 0x8b, 0xde, 0xc3, 0x34, 0x5d, 0x23, 0x2c, 0xc3, 0x11, 0x77, + 0xba, 0xab, 0x20, 0xa8, 0x52, 0xe9, 0xf7, 0x02, 0x6b, 0x3f, 0x40, 0xca, + 0x4e, 0x1e, 0x86, 0x22, 0x89, 0x3b, 0x82, 0x20, 0xec, 0x1b, 0x77, 0x83, + 0xe0, 0x4a, 0xad, 0x3e, 0xdf, 0x25, 0xa9, 0xdb, 0xd4, 0x00, 0x69, 0x03, + 0xd7, 0x37, 0x8b, 0x6c, 0xee, 0xee, 0x49, 0x0d, 0x3a, 0x8d, 0x87, 0x44, + 0x54, 0x63, 0x8f, 0xc4, 0x71, 0xc1, 0xad, 0x23, 0xd0, 0x54, 0xc3, 0x5a, + 0x98, 0x3c, 0x8e, 0x7d, 0x55, 0x5e, 0xad, 0xb2, 0xd2, 0xb0, 0x0d, 0x12, + 0x48, 0x08, 0x37, 0x6b, 0x1d, 0x26, 0xfd, 0xff, 0x91, 0x75, 0xab, 0xf5, + 0xf9, 0x39, 0xde, 0x15, 0x7f, 0x7f, 0xf3, 0x66, 0x80, 0xfc, 0x3e, 0x9c, + 0xdd, 0x83, 0xd4, 0x6f, 0xba, 0xc1, 0xe5, 0x9d, 0x45, 0xab, 0xe5, 0x39, + 0x82, 0x84, 0x9a, 0x7f, 0x27, 0x8b, 0xc1, 0x6d, 0x43, 0x21, 0xca, 0x1c, + 0xdd, 0x86, 0x0f, 0xa7, 0x37, 0xef, 0x73, 0xae, 0xb6, 0x25, 0xfe, 0x61, + 0x5d, 0xab, 0x4c, 0xf1, 0x9d, 0xa7, 0x5a, 0xb2, 0xba, 0xc6, 0xa6, 0x96, + 0xb3, 0x85, 0xc0, 0x16, 0x1d, 0x46, 0x1f, 0x31, 0x84, 0x23, 0xd3, 0xba, + 0x42, 0xf4, 0xf0, 0xcd, 0xfd, 0xa6, 0x5e, 0x7e, 0x26, 0xfa, 0x7a, 0x12, + 0x1d, 0x99, 0x5b, 0x4a, 0x74, 0x98, 0x97, 0xfd, 0xd1, 0xa8, 0x4b, 0xdc, + 0xb2, 0x16, 0x2d, 0x19, 0xb3, 0x99, 0x68, 0xc6, 0x4c, 0xbb, 0x65, 0x66, + 0xb8, 0x27, 0x3b, 0xdc, 0x2d, 0x52, 0xf9, 0x5f, 0xe0, 0xff, 0x61, 0x05, + 0xb9, 0x4b, 0x0e, 0x10, 0x35, 0x31, 0x0b, 0x5a, 0x05, 0x52, 0x0e, 0xfd, + 0x7d, 0xdc, 0x75, 0x06, 0x80, 0xd6, 0x2a, 0xbd, 0xc0, 0x65, 0x47, 0x54, + 0xc7, 0x97, 0xd4, 0xe7, 0x02, 0x77, 0x22, 0x8a, 0xab, 0x91, 0xb9, 0x21, + 0x01, 0xec, 0xfc, 0x30, 0x4e, 0xdb, 0x31, 0xcf, 0x65, 0xcd, 0xe2, 0x4f, + 0xc1, 0xab, 0x34, 0xbf, 0xe0, 0xef, 0xa7, 0x5f, 0xde, 0x5e, 0xac, 0x4f, + 0xde, 0xb9, 0xd5, 0xdf, 0x8b, 0xbf, 0x65, 0xc3, 0x7f, 0x4c, 0xd6, 0xaf, + 0xe3, 0xfd, 0x2f, 0xaf, 0xdf, 0xe7, 0x5f, 0xde, 0xb1, 0xe2, 0x53, 0xf8, + 0x32, 0x6d, 0x3a, 0x08, 0x1a, 0x70, 0x5b, 0x34, 0x9c, 0x26, 0xcd, 0x98, + 0x2e, 0xe4, 0x98, 0x1d, 0x8b, 0x80, 0x20, 0x58, 0xe2, 0xc3, 0xae, 0x6f, + 0xbe, 0xb8, 0xfd, 0x94, 0xfe, 0x46, 0x9f, 0xa1, 0x9a, 0x0f, 0x9b, 0x00, + 0x69, 0xae, 0xc4, 0x99, 0xf6, 0xe2, 0x00, 0x70, 0xae, 0x68, 0xd7, 0x8b, + 0xac, 0x49, 0x1a, 0x33, 0x23, 0x32, 0x10, 0x7f, 0x28, 0xde, 0x1e, 0x5f, + 0xd0, 0x4f, 0x78, 0x4f, 0xa4, 0xdf, 0xb3, 0x15, 0xd9, 0xdc, 0x81, 0xcc, + 0xab, 0x49, 0x58, 0xde, 0xa8, 0x73, 0xe9, 0xd0, 0x64, 0xfb, 0xb3, 0x93, + 0x66, 0xf7, 0xe3, 0x80, 0x5f, 0xf3, 0x33, 0x18, 0xa1, 0xb1, 0xe0, 0xe8, + 0xe1, 0xb9, 0x6f, 0x8e, 0x3c, 0xa8, 0xb9, 0x63, 0x5f, 0x61, 0xc1, 0xa2, + 0xf5, 0x68, 0xf2, 0xe8, 0xb0, 0x20, 0x0f, 0xf3, 0xd8, 0x02, 0xfb, 0x7c, + 0xb0, 0x9a, 0xcf, 0x64, 0xe0, 0xf1, 0xe5, 0x83, 0x39, 0x58, 0x16, 0x86, + 0x23, 0x0e, 0x3d, 0xad, 0x56, 0xf0, 0xb9, 0x52, 0xe5, 0xb6, 0xf8, 0x0f, + 0x50, 0xdd, 0x7d, 0xcc, 0xa9, 0x11, 0x00, 0x00 +}; const GFXglyph Antonio_SemiBold20pt7bGlyphs[] PROGMEM = { {0, 1, 1, 8, 0, 0}, // 0x20 ' ' @@ -477,8 +284,13 @@ const GFXglyph Antonio_SemiBold20pt7bGlyphs[] PROGMEM = { {4461, 10, 37, 14, 2, -33}, // 0x7D '}' {4508, 17, 6, 21, 2, -21}}; // 0x7E '~' -const GFXfont Antonio_SemiBold20pt7b PROGMEM = { - (uint8_t *)Antonio_SemiBold20pt7bBitmaps, - (GFXglyph *)Antonio_SemiBold20pt7bGlyphs, 0x20, 0x7E, 51}; - -// Approx. 5193 bytes +// Font properties +static constexpr FontData Antonio_SemiBold20pt7b_Properties = { + Antonio_SemiBold20pt7bBitmaps_Gzip, + Antonio_SemiBold20pt7bGlyphs, + sizeof(Antonio_SemiBold20pt7bBitmaps_Gzip), + 4521, // Original size + 0x20, // First char + 0x7E, // Last char + 51 // yAdvance +}; diff --git a/src/fonts/antonio-semibold40.h b/src/fonts/antonio-semibold40.h index 13725f9..3790765 100644 --- a/src/fonts/antonio-semibold40.h +++ b/src/fonts/antonio-semibold40.h @@ -1,1603 +1,488 @@ +#pragma once + #include #include -const uint8_t Antonio_SemiBold40pt7bBitmaps[] PROGMEM = { - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFE, 0x7F, - 0x9F, 0xE7, 0xF9, 0xFE, 0x7F, 0x9F, 0xE7, 0xF9, 0xFE, 0x7F, 0x9F, 0xE7, - 0xF9, 0xFE, 0x7F, 0x9F, 0xE7, 0xF8, 0xFC, 0x3F, 0x0F, 0xC3, 0xF0, 0xFC, - 0x3F, 0x0F, 0xC3, 0xF0, 0xFC, 0x3F, 0x0F, 0xC3, 0xF0, 0xFC, 0x3F, 0x0F, - 0xC3, 0xF0, 0xFC, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x1E, 0x07, 0x81, 0xE0, - 0x78, 0x1E, 0x07, 0x81, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, - 0xF8, 0xFF, 0xFF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, - 0x1F, 0xC7, 0xF1, 0xFC, 0x7F, 0x1F, 0xC7, 0xF1, 0xF8, 0x7E, 0x1F, 0x87, - 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xC1, 0xF0, 0x7C, 0x00, 0x00, 0x07, 0xE0, - 0x3F, 0x80, 0x01, 0xF8, 0x0F, 0xC0, 0x00, 0xFE, 0x03, 0xF0, 0x00, 0x3F, - 0x81, 0xFC, 0x00, 0x0F, 0xE0, 0x7F, 0x00, 0x03, 0xF0, 0x1F, 0xC0, 0x00, - 0xFC, 0x07, 0xF0, 0x00, 0x3F, 0x01, 0xF8, 0x00, 0x1F, 0xC0, 0x7E, 0x00, - 0x07, 0xF0, 0x3F, 0x80, 0x01, 0xFC, 0x0F, 0xE0, 0x00, 0x7E, 0x03, 0xF8, - 0x00, 0x1F, 0x80, 0xFE, 0x00, 0x07, 0xE0, 0x3F, 0x00, 0x03, 0xF8, 0x0F, - 0xC0, 0x00, 0xFE, 0x07, 0xF0, 0x00, 0x3F, 0x81, 0xFC, 0x00, 0x0F, 0xC0, - 0x7F, 0x00, 0x03, 0xF0, 0x1F, 0xC0, 0x00, 0xFC, 0x07, 0xE0, 0x00, 0x7F, - 0x01, 0xF8, 0x00, 0x1F, 0xC0, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xF3, - 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xF8, 0x1F, 0xC0, 0x00, 0xFE, 0x07, 0xF0, - 0x00, 0x3F, 0x01, 0xFC, 0x00, 0x0F, 0xC0, 0x7F, 0x00, 0x03, 0xF0, 0x1F, - 0x80, 0x01, 0xFC, 0x07, 0xE0, 0x00, 0x7F, 0x01, 0xF8, 0x00, 0x1F, 0xC0, - 0xFE, 0x00, 0x07, 0xE0, 0x3F, 0x80, 0x01, 0xF8, 0x0F, 0xE0, 0x00, 0x7E, - 0x03, 0xF0, 0x00, 0x3F, 0x80, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xF3, - 0xFF, 0xFF, 0xFF, 0xFC, 0x07, 0xF0, 0x1F, 0x80, 0x01, 0xFC, 0x07, 0xE0, - 0x00, 0x7F, 0x03, 0xF8, 0x00, 0x1F, 0x80, 0xFE, 0x00, 0x07, 0xE0, 0x3F, - 0x80, 0x01, 0xF8, 0x0F, 0xC0, 0x00, 0xFE, 0x03, 0xF0, 0x00, 0x3F, 0x80, - 0xFC, 0x00, 0x0F, 0xE0, 0x7F, 0x00, 0x03, 0xF8, 0x1F, 0xC0, 0x00, 0xFC, - 0x07, 0xF0, 0x00, 0x3F, 0x01, 0xFC, 0x00, 0x1F, 0xC0, 0x7E, 0x00, 0x07, - 0xF0, 0x1F, 0x80, 0x01, 0xFC, 0x0F, 0xE0, 0x00, 0x7F, 0x03, 0xF8, 0x00, - 0x1F, 0x80, 0xFE, 0x00, 0x07, 0xE0, 0x3F, 0x80, 0x01, 0xF8, 0x0F, 0xC0, - 0x00, 0xFE, 0x03, 0xF0, 0x00, 0x3F, 0x81, 0xFC, 0x00, 0x0F, 0xE0, 0x7F, - 0x00, 0x03, 0xF0, 0x1F, 0xC0, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x07, 0xC0, - 0x00, 0x01, 0xF0, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x07, - 0xC0, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x1F, 0x00, 0x00, - 0x7F, 0xF8, 0x00, 0x7F, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, - 0x0F, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xFC, 0x7F, 0xC1, - 0xFF, 0x1F, 0xF0, 0x3F, 0xEF, 0xF8, 0x0F, 0xFB, 0xFE, 0x01, 0xFE, 0xFF, - 0x80, 0x7F, 0xBF, 0xE0, 0x1F, 0xEF, 0xF8, 0x07, 0xFB, 0xFE, 0x01, 0xFE, - 0xFF, 0x80, 0x7F, 0x9F, 0xE0, 0x1F, 0xE7, 0xFC, 0x07, 0xF9, 0xFF, 0x01, - 0xFE, 0x7F, 0xE0, 0x7F, 0x8F, 0xFC, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x7F, - 0xE0, 0x00, 0x1F, 0xFE, 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x7F, 0xF8, 0x00, - 0x1F, 0xFF, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x7F, 0xFC, 0x00, 0x0F, 0xFF, - 0x80, 0x00, 0xFF, 0xF0, 0x00, 0x1F, 0xFC, 0x00, 0x03, 0xFF, 0x80, 0x00, - 0x7F, 0xF0, 0x00, 0x0F, 0xFC, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x7F, 0xE7, - 0xF8, 0x0F, 0xF9, 0xFE, 0x03, 0xFF, 0x7F, 0x80, 0x7F, 0xDF, 0xE0, 0x1F, - 0xF7, 0xF8, 0x07, 0xFD, 0xFE, 0x01, 0xFF, 0x7F, 0x80, 0x3F, 0xDF, 0xE0, - 0x0F, 0xF7, 0xF8, 0x03, 0xFD, 0xFE, 0x01, 0xFF, 0x7F, 0xC0, 0x7F, 0xDF, - 0xF0, 0x1F, 0xF7, 0xFE, 0x0F, 0xFC, 0xFF, 0xC7, 0xFE, 0x3F, 0xFF, 0xFF, - 0x87, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xF8, 0x07, 0xFF, - 0xFC, 0x00, 0xFF, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x3C, 0x00, 0x00, - 0x0F, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, - 0x00, 0x0F, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, - 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xE0, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x07, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xF0, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x7F, 0xFF, - 0xFE, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x7F, 0xF7, 0xFE, 0x00, 0x00, - 0x0F, 0xE0, 0x00, 0x00, 0x7F, 0x81, 0xFF, 0x00, 0x00, 0x1F, 0xC0, 0x00, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, - 0x3F, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x00, 0x3F, 0x80, 0x00, - 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0xFF, 0x00, - 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x00, - 0xFE, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x00, - 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0x00, - 0x7F, 0x00, 0x01, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x01, - 0xFC, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x03, 0xFC, 0x00, 0x00, - 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0x00, - 0x7F, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x07, - 0xF0, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x07, 0xF0, 0x03, 0xFF, - 0x80, 0xFF, 0x00, 0x7F, 0x00, 0x0F, 0xF0, 0x0F, 0xFF, 0xE0, 0xFF, 0x00, - 0x7F, 0x00, 0x0F, 0xE0, 0x1F, 0xFF, 0xF0, 0xFF, 0x00, 0x7F, 0x00, 0x0F, - 0xE0, 0x3F, 0xFF, 0xF8, 0xFF, 0x00, 0x7F, 0x00, 0x1F, 0xC0, 0x7F, 0xFF, - 0xFC, 0xFF, 0x00, 0x7F, 0x00, 0x1F, 0xC0, 0x7F, 0xFF, 0xFC, 0xFF, 0x00, - 0x7F, 0x00, 0x3F, 0xC0, 0xFF, 0xEF, 0xFE, 0xFF, 0x00, 0x7F, 0x00, 0x3F, - 0x80, 0xFF, 0x01, 0xFE, 0xFF, 0x00, 0x7F, 0x00, 0x3F, 0x80, 0xFF, 0x01, - 0xFE, 0xFF, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0xFE, 0x00, 0xFE, 0xFF, 0x00, - 0x7F, 0x00, 0x7F, 0x01, 0xFE, 0x00, 0xFE, 0xFF, 0x00, 0x7F, 0x00, 0xFF, - 0x01, 0xFE, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x01, 0xFE, 0x00, - 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x01, 0xFE, 0x00, 0xFF, 0x7F, 0x00, - 0xFF, 0x01, 0xFC, 0x01, 0xFE, 0x00, 0xFF, 0x7F, 0x81, 0xFE, 0x01, 0xFC, - 0x01, 0xFE, 0x00, 0xFF, 0x7F, 0xFF, 0xFE, 0x03, 0xFC, 0x01, 0xFE, 0x00, - 0xFF, 0x3F, 0xFF, 0xFE, 0x03, 0xF8, 0x01, 0xFE, 0x00, 0xFF, 0x3F, 0xFF, - 0xFC, 0x03, 0xF8, 0x01, 0xFE, 0x00, 0xFF, 0x1F, 0xFF, 0xF8, 0x07, 0xF0, - 0x01, 0xFE, 0x00, 0xFF, 0x0F, 0xFF, 0xF0, 0x07, 0xF0, 0x01, 0xFE, 0x00, - 0xFF, 0x03, 0xFF, 0xE0, 0x0F, 0xF0, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x7F, - 0x00, 0x0F, 0xE0, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xE0, - 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x01, 0xFE, 0x00, - 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, - 0x00, 0x3F, 0xC0, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0x80, - 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x01, 0xFE, 0x00, - 0xFF, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, - 0x00, 0x7F, 0x00, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, - 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x01, 0xFE, 0x00, - 0xFF, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x00, - 0x01, 0xFE, 0x00, 0x01, 0xFE, 0x00, 0xFE, 0x00, 0x00, 0x01, 0xFC, 0x00, - 0x00, 0xFE, 0x00, 0xFE, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFE, 0x01, - 0xFE, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x00, 0xFF, 0x01, 0xFE, 0x00, 0x00, - 0x03, 0xF8, 0x00, 0x00, 0xFF, 0x83, 0xFE, 0x00, 0x00, 0x07, 0xF8, 0x00, - 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x7F, 0xFF, - 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, - 0x0F, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xE0, 0x00, - 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x07, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x0F, - 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0xFF, 0xDF, 0xF0, 0x00, 0x3F, 0xE0, 0x7F, 0x00, 0x07, 0xF8, 0x0F, - 0xE0, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x1F, 0xE0, 0x1F, 0x80, 0x03, 0xFC, - 0x03, 0xF0, 0x00, 0xFF, 0x80, 0x7E, 0x00, 0x1F, 0xF0, 0x0F, 0xC0, 0x01, - 0xFE, 0x01, 0xF8, 0x00, 0x3F, 0xC0, 0x3F, 0x00, 0x07, 0xF8, 0x0F, 0xE0, - 0x00, 0xFF, 0x01, 0xFC, 0x00, 0x1F, 0xE0, 0x3F, 0x80, 0x01, 0xFE, 0x07, - 0xF0, 0x00, 0x3F, 0xC1, 0xFC, 0x00, 0x07, 0xF8, 0x3F, 0x80, 0x00, 0x7F, - 0x8F, 0xE0, 0x00, 0x0F, 0xF3, 0xFC, 0x00, 0x01, 0xFE, 0x7F, 0x00, 0x00, - 0x1F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFE, - 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x0F, - 0xFC, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x00, - 0x1F, 0xFF, 0x00, 0x10, 0x07, 0xFF, 0xE0, 0x06, 0x01, 0xFF, 0xFE, 0x01, - 0xC0, 0x3F, 0xFF, 0xC0, 0x3C, 0x0F, 0xF3, 0xFC, 0x0F, 0xC3, 0xFE, 0x7F, - 0x83, 0xF8, 0x7F, 0x8F, 0xF0, 0x7F, 0x9F, 0xF0, 0xFF, 0x1F, 0xE3, 0xFC, - 0x1F, 0xE3, 0xF8, 0xFF, 0x81, 0xFE, 0xFF, 0x1F, 0xE0, 0x3F, 0xFF, 0xC3, - 0xFC, 0x07, 0xFF, 0xF0, 0xFF, 0x00, 0x7F, 0xFE, 0x1F, 0xE0, 0x0F, 0xFF, - 0x83, 0xFC, 0x00, 0xFF, 0xF0, 0x7F, 0x80, 0x1F, 0xFC, 0x0F, 0xF0, 0x01, - 0xFF, 0x81, 0xFE, 0x00, 0x3F, 0xE0, 0x3F, 0xC0, 0x07, 0xFC, 0x07, 0xF8, - 0x00, 0xFF, 0x80, 0xFF, 0x00, 0x3F, 0xF0, 0x1F, 0xE0, 0x07, 0xFF, 0x03, - 0xFC, 0x01, 0xFF, 0xE0, 0x7F, 0xC0, 0x7F, 0xFE, 0x0F, 0xFC, 0x1F, 0xFF, - 0xC0, 0xFF, 0xFF, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, - 0xEF, 0xF0, 0x3F, 0xFF, 0xF8, 0xFF, 0x03, 0xFF, 0xFE, 0x1F, 0xE0, 0x3F, - 0xFF, 0x81, 0xFE, 0x01, 0xFF, 0xC0, 0x3F, 0xC0, 0x07, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, 0x7F, 0x7F, 0x7E, 0x7E, 0x7E, 0x7E, - 0x7E, 0x7E, 0x7E, 0x3E, 0x3E, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, - 0x00, 0x7C, 0x0F, 0xF0, 0xFF, 0xC7, 0xFF, 0x3F, 0xFC, 0xFF, 0xF7, 0xFF, - 0xDF, 0xF8, 0x7F, 0xC1, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, - 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, - 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, - 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, - 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, - 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, - 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x1F, 0xF0, 0x7F, - 0xC1, 0xFF, 0x87, 0xFF, 0xCF, 0xFF, 0x3F, 0xFC, 0x7F, 0xF0, 0xFF, 0xC0, - 0xFF, 0x00, 0x0C, 0xF8, 0x03, 0xFE, 0x0F, 0xFC, 0x3F, 0xF8, 0xFF, 0xF3, - 0xFF, 0xEF, 0xFF, 0x83, 0xFE, 0x07, 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, - 0x07, 0xFC, 0x1F, 0xF0, 0x7F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, - 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, - 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, - 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, - 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, - 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, - 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, - 0x03, 0xFC, 0x0F, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xFC, 0x1F, 0xF0, 0x7F, - 0xC1, 0xFF, 0x0F, 0xFC, 0x7F, 0xEF, 0xFF, 0xBF, 0xFE, 0xFF, 0xF3, 0xFF, - 0x8F, 0xFC, 0x3F, 0xC0, 0xC0, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00, 0xFE, - 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x3F, 0x00, 0x01, 0x00, 0xF8, 0x04, - 0x0C, 0x07, 0xC0, 0xE0, 0xF8, 0x3E, 0x0F, 0x8F, 0xE1, 0xF0, 0xFC, 0x7F, - 0x8F, 0x9F, 0xF7, 0xFF, 0x7D, 0xFF, 0xDF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, - 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x7F, 0xFC, 0x00, 0x00, 0xFF, 0x80, - 0x00, 0x1F, 0xFF, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFC, 0x0F, - 0xFF, 0xFF, 0xF9, 0xFF, 0xDF, 0x7F, 0xF7, 0xF8, 0xF9, 0xFF, 0x1F, 0x87, - 0xC3, 0xF8, 0xF8, 0x3E, 0x0F, 0x83, 0x01, 0xF0, 0x38, 0x10, 0x0F, 0x80, - 0x40, 0x00, 0xFC, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x3F, 0x80, 0x00, - 0x01, 0xFC, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, - 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, - 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, - 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, - 0x00, 0x00, 0x7E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFC, 0x3E, 0x0F, 0x83, 0xE1, 0xF0, 0x7C, - 0x1F, 0x07, 0xC3, 0xE0, 0xF8, 0x3E, 0x0F, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, - 0xFF, 0x00, 0x00, 0xFE, 0x00, 0x01, 0xFE, 0x00, 0x01, 0xFE, 0x00, 0x01, - 0xFE, 0x00, 0x01, 0xFE, 0x00, 0x03, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x03, - 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, - 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, - 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x1F, 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x1F, - 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x3F, 0xC0, 0x00, 0x3F, 0xC0, 0x00, 0x3F, - 0xC0, 0x00, 0x3F, 0xC0, 0x00, 0x7F, 0x80, 0x00, 0x7F, 0x80, 0x00, 0x7F, - 0x80, 0x00, 0x7F, 0x80, 0x00, 0x7F, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFE, 0x00, 0x01, 0xFE, 0x00, 0x01, 0xFE, - 0x00, 0x01, 0xFE, 0x00, 0x01, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x03, 0xFC, - 0x00, 0x03, 0xFC, 0x00, 0x03, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, - 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, - 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x1F, 0xE0, - 0x00, 0x1F, 0xE0, 0x00, 0x1F, 0xC0, 0x00, 0x3F, 0xC0, 0x00, 0x3F, 0xC0, - 0x00, 0x3F, 0xC0, 0x00, 0x3F, 0x80, 0x00, 0x7F, 0x80, 0x00, 0x7F, 0x80, - 0x00, 0x7F, 0x80, 0x00, 0x7F, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, - 0x00, 0xFF, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x7F, - 0xFF, 0x00, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xF0, - 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFC, 0x7F, 0xE0, - 0xFF, 0xCF, 0xF8, 0x0F, 0xF9, 0xFF, 0x01, 0xFF, 0x3F, 0xC0, 0x1F, 0xE7, - 0xF8, 0x03, 0xFC, 0xFF, 0x00, 0x7F, 0xBF, 0xE0, 0x0F, 0xF7, 0xFC, 0x01, - 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, - 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, - 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, - 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, - 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, - 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, - 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, - 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, - 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, - 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xDF, 0xE0, 0x0F, - 0xF3, 0xFC, 0x01, 0xFE, 0x7F, 0x80, 0x3F, 0xCF, 0xF0, 0x0F, 0xF9, 0xFF, - 0x01, 0xFF, 0x3F, 0xE0, 0x3F, 0xE3, 0xFE, 0x0F, 0xFC, 0x7F, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0x01, 0xFF, - 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x00, 0xFF, 0xF8, 0x00, 0x03, 0xF8, 0x00, - 0x00, 0x0F, 0xE0, 0x03, 0xFC, 0x00, 0x7F, 0x80, 0x1F, 0xF0, 0x0F, 0xFE, - 0x03, 0xFF, 0xC1, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xF8, 0x7F, 0xF8, 0x0F, 0xF8, - 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, - 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, - 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, - 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, - 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, - 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, - 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, - 0xFF, 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, - 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, - 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, - 0xFC, 0x00, 0xFF, 0x80, 0x00, 0x7F, 0x80, 0x01, 0xFF, 0xF0, 0x01, 0xFF, - 0xFE, 0x01, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xF8, 0xFF, 0xFF, 0xFE, 0x7F, 0xC1, 0xFF, 0x3F, 0xE0, 0x7F, 0x9F, - 0xE0, 0x3F, 0xCF, 0xF0, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFC, 0x03, 0xFF, - 0xFE, 0x01, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0x80, 0x3F, - 0xFF, 0xC0, 0x1F, 0xFF, 0xE0, 0x0F, 0xFF, 0xF0, 0x07, 0xFF, 0xF8, 0x03, - 0xFF, 0xFC, 0x03, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x80, - 0x7F, 0xBF, 0xC0, 0x3F, 0xC0, 0x00, 0x3F, 0xE0, 0x00, 0x1F, 0xF0, 0x00, - 0x0F, 0xF0, 0x00, 0x0F, 0xF8, 0x00, 0x07, 0xFC, 0x00, 0x07, 0xFC, 0x00, - 0x03, 0xFE, 0x00, 0x03, 0xFE, 0x00, 0x03, 0xFF, 0x00, 0x01, 0xFF, 0x00, - 0x01, 0xFF, 0x80, 0x00, 0xFF, 0x80, 0x00, 0xFF, 0x80, 0x00, 0xFF, 0xC0, - 0x00, 0x7F, 0xC0, 0x00, 0x7F, 0xE0, 0x00, 0x3F, 0xE0, 0x00, 0x3F, 0xF0, - 0x00, 0x1F, 0xF0, 0x00, 0x1F, 0xF8, 0x00, 0x0F, 0xF8, 0x00, 0x0F, 0xF8, - 0x00, 0x07, 0xFC, 0x00, 0x07, 0xFC, 0x00, 0x03, 0xFE, 0x00, 0x03, 0xFE, - 0x00, 0x01, 0xFF, 0x00, 0x00, 0xFF, 0x80, 0x00, 0xFF, 0x80, 0x00, 0x7F, - 0xC0, 0x00, 0x3F, 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x0F, 0xF0, 0x00, 0x07, - 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFE, - 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, - 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0x7F, 0xFE, 0x00, 0x1F, 0xFF, 0xF0, 0x07, - 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, - 0xE3, 0xFF, 0xFF, 0xFC, 0x7F, 0xE0, 0xFF, 0xCF, 0xF8, 0x0F, 0xF9, 0xFF, - 0x01, 0xFF, 0x3F, 0xC0, 0x1F, 0xEF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, - 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, - 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0x80, 0x00, 0x1F, 0xF0, 0x00, 0x03, 0xFE, - 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x0F, 0xF8, 0x00, 0x01, 0xFE, 0x00, 0x00, - 0x3F, 0xC0, 0x00, 0x0F, 0xF8, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x7F, 0xC0, - 0x00, 0x1F, 0xF8, 0x00, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0x80, 0x03, 0xFF, - 0xE0, 0x00, 0x7F, 0xF0, 0x00, 0x0F, 0xFF, 0x80, 0x01, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0x80, 0x00, 0x3F, 0xF0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x1F, - 0xE0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x07, 0xF8, 0x00, - 0x00, 0xFF, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x7F, - 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, - 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, - 0x3F, 0xCF, 0xF0, 0x07, 0xF9, 0xFE, 0x01, 0xFF, 0x3F, 0xE0, 0x3F, 0xE7, - 0xFE, 0x0F, 0xF8, 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, - 0xF8, 0x1F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xE0, 0x00, - 0xFF, 0xF8, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x07, - 0xFC, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x03, 0xFF, - 0x00, 0x00, 0x1F, 0xFC, 0x00, 0x00, 0x7F, 0xF0, 0x00, 0x01, 0xFF, 0xC0, - 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0xFF, 0xF0, 0x00, - 0x07, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x7F, 0xFC, 0x00, 0x01, - 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0xFB, - 0xFC, 0x00, 0x07, 0xEF, 0xF0, 0x00, 0x1F, 0xBF, 0xC0, 0x00, 0x7E, 0xFF, - 0x00, 0x03, 0xF3, 0xFC, 0x00, 0x0F, 0xCF, 0xF0, 0x00, 0x3F, 0x3F, 0xC0, - 0x00, 0xF8, 0xFF, 0x00, 0x07, 0xE3, 0xFC, 0x00, 0x1F, 0x8F, 0xF0, 0x00, - 0x7E, 0x3F, 0xC0, 0x03, 0xF0, 0xFF, 0x00, 0x0F, 0xC3, 0xFC, 0x00, 0x3F, - 0x0F, 0xF0, 0x01, 0xFC, 0x3F, 0xC0, 0x07, 0xE0, 0xFF, 0x00, 0x1F, 0x83, - 0xFC, 0x00, 0xFE, 0x0F, 0xF0, 0x03, 0xF8, 0x3F, 0xC0, 0x0F, 0xC0, 0xFF, - 0x00, 0x3F, 0x03, 0xFC, 0x01, 0xFC, 0x0F, 0xF0, 0x07, 0xE0, 0x3F, 0xC0, - 0x1F, 0x80, 0xFF, 0x00, 0xFE, 0x03, 0xFC, 0x03, 0xF8, 0x0F, 0xF0, 0x0F, - 0xC0, 0x3F, 0xC0, 0x7F, 0x00, 0xFF, 0x01, 0xFC, 0x03, 0xFC, 0x07, 0xF0, - 0x0F, 0xF0, 0x3F, 0x80, 0x3F, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xC0, - 0x00, 0x00, 0xFF, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00, - 0x00, 0x3F, 0xC0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, - 0x0F, 0xF0, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x03, - 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFF, - 0x1F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, - 0xF1, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFC, 0x7F, 0x80, 0x00, 0x3F, 0xC0, - 0x00, 0x1F, 0xE0, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF8, 0x00, 0x03, 0xFC, - 0x00, 0x01, 0xFE, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x3F, - 0xC0, 0x00, 0x1F, 0xE0, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF9, 0xFE, 0x03, - 0xFD, 0xFF, 0xC1, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFE, - 0x3F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xCF, 0xFC, 0x7F, 0xE7, 0xFC, 0x1F, - 0xF3, 0xFC, 0x07, 0xF9, 0xFE, 0x03, 0xFC, 0xFF, 0x01, 0xFF, 0x7F, 0x80, - 0xFF, 0x8F, 0x80, 0x7F, 0xC0, 0x00, 0x1F, 0xE0, 0x00, 0x0F, 0xF0, 0x00, - 0x07, 0xF8, 0x00, 0x03, 0xFC, 0x00, 0x01, 0xFE, 0x00, 0x00, 0xFF, 0x00, - 0x00, 0x7F, 0x80, 0x00, 0x3F, 0xC0, 0x00, 0x1F, 0xE0, 0x00, 0x0F, 0xF0, - 0x00, 0x07, 0xF8, 0x00, 0x03, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0xFF, - 0xFF, 0x80, 0x7F, 0xFF, 0xC0, 0x3F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF0, 0x0F, - 0xFF, 0xF8, 0x07, 0xFF, 0xFC, 0x03, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x01, - 0xFF, 0xFF, 0x80, 0xFF, 0x7F, 0xC0, 0x7F, 0xBF, 0xE0, 0x3F, 0xCF, 0xF8, - 0x3F, 0xE7, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF8, 0x7F, - 0xFF, 0xF8, 0x1F, 0xFF, 0xFC, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xF8, 0x00, - 0x07, 0xC0, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x3F, 0xFF, 0x00, 0x1F, 0xFF, - 0xF0, 0x07, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFE, 0x0F, - 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFC, 0x3F, 0xE0, 0x7F, 0x8F, 0xF8, 0x0F, - 0xF9, 0xFF, 0x00, 0xFF, 0x3F, 0xC0, 0x1F, 0xE7, 0xF8, 0x03, 0xFC, 0xFF, - 0x00, 0x7F, 0x9F, 0xE0, 0x0F, 0xF3, 0xFC, 0x01, 0xFE, 0xFF, 0x80, 0x3F, - 0xDF, 0xF0, 0x07, 0xFB, 0xFE, 0x00, 0xFF, 0x7F, 0xC0, 0x1F, 0xEF, 0xF8, - 0x00, 0x01, 0xFF, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x07, 0xFC, 0x00, 0x00, - 0xFF, 0x80, 0x00, 0x1F, 0xF0, 0x00, 0x03, 0xFE, 0x3F, 0xC0, 0x7F, 0xDF, - 0xFE, 0x0F, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFE, 0x3F, 0xFF, 0xFF, 0xE7, - 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0x3F, 0xFB, 0xFF, 0x01, - 0xFF, 0x7F, 0xC0, 0x1F, 0xEF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, - 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, - 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, - 0x01, 0xFE, 0xFF, 0x00, 0x3F, 0xDF, 0xE0, 0x07, 0xFB, 0xFC, 0x00, 0xFF, - 0x7F, 0x80, 0x1F, 0xEF, 0xF8, 0x03, 0xFD, 0xFF, 0x00, 0xFF, 0xBF, 0xF0, - 0x1F, 0xE3, 0xFF, 0x07, 0xFC, 0x7F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xE0, - 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, - 0xF0, 0x00, 0xFF, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFE, - 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x7F, 0xC0, 0x00, - 0x07, 0xFC, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, - 0x80, 0x00, 0x0F, 0xF0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x1F, 0xF0, 0x00, - 0x01, 0xFE, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x3F, - 0xC0, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x07, 0xFC, 0x00, - 0x00, 0x7F, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, - 0xF0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x01, 0xFE, 0x00, - 0x00, 0x3F, 0xE0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x03, - 0xFC, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x7F, 0xC0, - 0x00, 0x0F, 0xFC, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, - 0xFF, 0x80, 0x00, 0x1F, 0xF8, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x1F, 0xF0, - 0x00, 0x01, 0xFF, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x03, 0xFF, 0x00, 0x00, - 0x3F, 0xF0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x03, 0xFE, - 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x7F, 0xE0, 0x00, - 0x07, 0xFE, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x7F, - 0xC0, 0x00, 0x07, 0xFC, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x0F, 0xFC, 0x00, - 0x00, 0xFF, 0xC0, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x0F, - 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x00, 0x3F, 0xFF, - 0xF0, 0x0F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0x1F, - 0xFF, 0xFF, 0xE3, 0xFF, 0x07, 0xFC, 0x7F, 0xC0, 0x7F, 0xCF, 0xF0, 0x0F, - 0xFB, 0xFE, 0x01, 0xFF, 0x7F, 0xC0, 0x1F, 0xEF, 0xF8, 0x03, 0xFD, 0xFF, - 0x00, 0x7F, 0xBF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, - 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xEF, 0xF8, - 0x03, 0xFD, 0xFF, 0x00, 0x7F, 0xBF, 0xE0, 0x0F, 0xF3, 0xFC, 0x01, 0xFE, - 0x7F, 0x80, 0x7F, 0xCF, 0xF0, 0x0F, 0xF1, 0xFF, 0x01, 0xFE, 0x1F, 0xE0, - 0x7F, 0xC3, 0xFE, 0x1F, 0xF0, 0x3F, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x80, - 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, - 0xFC, 0x1F, 0xF8, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0xFF, 0x81, 0xFF, 0x1F, - 0xF0, 0x1F, 0xE3, 0xFC, 0x03, 0xFE, 0xFF, 0x80, 0x7F, 0xDF, 0xF0, 0x0F, - 0xFB, 0xFE, 0x00, 0xFF, 0x7F, 0xC0, 0x1F, 0xEF, 0xF8, 0x03, 0xFF, 0xFE, - 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x3F, - 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, - 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFE, - 0xFF, 0x80, 0x3F, 0xDF, 0xF0, 0x0F, 0xFB, 0xFE, 0x01, 0xFF, 0x7F, 0xE0, - 0x3F, 0xE7, 0xFC, 0x0F, 0xFC, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xE1, - 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, - 0xF0, 0x00, 0xFF, 0xF8, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x7F, 0xC0, 0x00, - 0x7F, 0xFF, 0x00, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, - 0xF0, 0x3F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFE, 0x7F, - 0xE0, 0xFF, 0xCF, 0xF8, 0x0F, 0xF9, 0xFE, 0x01, 0xFF, 0x3F, 0xC0, 0x1F, - 0xE7, 0xF8, 0x03, 0xFE, 0xFF, 0x00, 0x7F, 0xDF, 0xE0, 0x0F, 0xFF, 0xFC, - 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, - 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, - 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, - 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xF7, 0xF8, 0x03, 0xFE, 0xFF, 0x00, - 0x7F, 0xDF, 0xE0, 0x0F, 0xFB, 0xFC, 0x01, 0xFF, 0x7F, 0x80, 0x3F, 0xEF, - 0xF8, 0x07, 0xFC, 0xFF, 0x81, 0xFF, 0x9F, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, - 0xFE, 0x3F, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0x07, - 0xFF, 0xFF, 0xE0, 0x7F, 0xF7, 0xFC, 0x00, 0xF0, 0xFF, 0x80, 0x00, 0x1F, - 0xF0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x0F, 0xF8, 0x00, - 0x01, 0xFF, 0x7F, 0xC0, 0x3F, 0xEF, 0xF8, 0x07, 0xFD, 0xFF, 0x00, 0xFF, - 0xBF, 0xE0, 0x1F, 0xF7, 0xFC, 0x03, 0xFE, 0xFF, 0x80, 0x7F, 0xDF, 0xF0, - 0x0F, 0xFB, 0xFE, 0x01, 0xFE, 0x3F, 0xC0, 0x3F, 0xC7, 0xF8, 0x07, 0xF8, - 0xFF, 0x00, 0xFF, 0x1F, 0xF0, 0x3F, 0xE3, 0xFE, 0x07, 0xFC, 0x3F, 0xFF, - 0xFF, 0x07, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xF8, 0x00, 0x01, 0xF8, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFD, 0xFF, 0x7F, 0xDF, 0xF7, 0xFD, 0xFF, - 0x7F, 0xDF, 0xF7, 0xFD, 0xFF, 0x0F, 0xC3, 0xF0, 0xFC, 0x7E, 0x1F, 0x87, - 0xE1, 0xF0, 0x7C, 0x3F, 0x0F, 0xC3, 0xE0, 0xF8, 0x7E, 0x1F, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x30, 0x00, 0x01, 0xC0, 0x00, 0x1F, 0x00, 0x00, - 0xFC, 0x00, 0x0F, 0xF0, 0x00, 0x7F, 0xC0, 0x07, 0xFF, 0x00, 0x3F, 0xFC, - 0x03, 0xFF, 0xF0, 0x1F, 0xFF, 0x81, 0xFF, 0xF8, 0x0F, 0xFF, 0x80, 0xFF, - 0xFC, 0x0F, 0xFF, 0xC0, 0x3F, 0xFE, 0x00, 0xFF, 0xE0, 0x03, 0xFE, 0x00, - 0x0F, 0xF0, 0x00, 0x3F, 0x80, 0x00, 0xFF, 0x80, 0x03, 0xFF, 0x00, 0x0F, - 0xFF, 0x00, 0x3F, 0xFE, 0x00, 0x7F, 0xFE, 0x00, 0x7F, 0xFE, 0x00, 0xFF, - 0xFC, 0x00, 0xFF, 0xFC, 0x01, 0xFF, 0xF8, 0x01, 0xFF, 0xF0, 0x01, 0xFF, - 0xC0, 0x03, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x07, 0xF0, 0x00, 0x07, 0xC0, - 0x00, 0x0F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x80, 0x00, - 0x03, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xFE, 0x00, 0x03, - 0xFE, 0x00, 0x0F, 0xFC, 0x00, 0x3F, 0xFC, 0x00, 0xFF, 0xF8, 0x01, 0xFF, - 0xF8, 0x03, 0xFF, 0xF0, 0x03, 0xFF, 0xF0, 0x07, 0xFF, 0xE0, 0x07, 0xFF, - 0xE0, 0x07, 0xFF, 0xC0, 0x0F, 0xFF, 0x00, 0x0F, 0xFC, 0x00, 0x1F, 0xF0, - 0x00, 0x1F, 0xC0, 0x00, 0x3F, 0x00, 0x03, 0xFC, 0x00, 0x1F, 0xF0, 0x01, - 0xFF, 0xC0, 0x1F, 0xFF, 0x00, 0xFF, 0xFC, 0x0F, 0xFF, 0xC0, 0x7F, 0xFC, - 0x07, 0xFF, 0xE0, 0x7F, 0xFE, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0x00, 0x3F, - 0xF8, 0x00, 0xFF, 0x80, 0x03, 0xFC, 0x00, 0x0F, 0xC0, 0x00, 0x3E, 0x00, - 0x00, 0xE0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x03, 0xFF, - 0xF0, 0x0F, 0xFF, 0xF8, 0x1F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, - 0xFE, 0x3F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFE, 0x7F, 0xC1, 0xFF, 0x7F, 0xC1, - 0xFF, 0x7F, 0x81, 0xFF, 0x7F, 0x81, 0xFF, 0x7F, 0x81, 0xFF, 0xFF, 0x81, - 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81, - 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81, - 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x81, 0xFF, 0x00, 0x01, 0xFF, 0x00, 0x01, - 0xFF, 0x00, 0x01, 0xFF, 0x00, 0x01, 0xFF, 0x00, 0x01, 0xFF, 0x00, 0x03, - 0xFE, 0x00, 0x03, 0xFE, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFE, 0x00, 0x0F, - 0xFC, 0x00, 0x1F, 0xFC, 0x00, 0x7F, 0xF8, 0x03, 0xFF, 0xF0, 0x07, 0xFF, - 0xE0, 0x07, 0xFF, 0xC0, 0x07, 0xFF, 0x00, 0x07, 0xFC, 0x00, 0x07, 0xF8, - 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, - 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF8, - 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, - 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, - 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xF0, 0x01, 0xFF, - 0x80, 0x00, 0xFF, 0xC0, 0x00, 0x7F, 0xC0, 0x01, 0xFF, 0x00, 0x00, 0x1F, - 0xE0, 0x03, 0xFE, 0x00, 0x00, 0x0F, 0xE0, 0x07, 0xF8, 0x00, 0x00, 0x07, - 0xF0, 0x07, 0xF8, 0x00, 0x00, 0x03, 0xF0, 0x0F, 0xF0, 0x00, 0x00, 0x03, - 0xF8, 0x0F, 0xE0, 0x00, 0x00, 0x01, 0xF8, 0x1F, 0xC0, 0x00, 0x00, 0x00, - 0xFC, 0x1F, 0xC0, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0xC0, 0x07, 0xFE, 0x00, - 0xFC, 0x3F, 0x80, 0x1F, 0xFF, 0xC0, 0x7E, 0x3F, 0x80, 0x3F, 0xFF, 0xF0, - 0x7E, 0x7F, 0x00, 0x7F, 0xFF, 0xF0, 0x7E, 0x7F, 0x00, 0xFF, 0xFF, 0xF0, - 0x7E, 0x7F, 0x00, 0xFE, 0x03, 0xF0, 0x3E, 0x7F, 0x01, 0xFC, 0x03, 0xF0, - 0x3F, 0x7E, 0x01, 0xF8, 0x03, 0xF0, 0x3F, 0xFE, 0x03, 0xF8, 0x03, 0xF0, - 0x3F, 0xFE, 0x03, 0xF0, 0x03, 0xF0, 0x3F, 0xFE, 0x03, 0xF0, 0x03, 0xF0, - 0x3F, 0xFE, 0x03, 0xE0, 0x03, 0xF0, 0x3F, 0xFE, 0x07, 0xE0, 0x03, 0xF0, - 0x3F, 0xFE, 0x07, 0xE0, 0x03, 0xF0, 0x3F, 0xFE, 0x07, 0xE0, 0x03, 0xF0, - 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, - 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, - 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, - 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, - 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, 0x3F, 0xFC, 0x07, 0xE0, 0x03, 0xF0, - 0x3F, 0xFE, 0x07, 0xE0, 0x07, 0xF0, 0x3F, 0xFE, 0x07, 0xE0, 0x07, 0xF0, - 0x3F, 0xFE, 0x07, 0xE0, 0x07, 0xF0, 0x3E, 0xFE, 0x07, 0xE0, 0x07, 0xF0, - 0x7E, 0xFE, 0x07, 0xF0, 0x07, 0xF0, 0x7E, 0xFE, 0x03, 0xF0, 0x0F, 0xF0, - 0x7E, 0x7E, 0x03, 0xF0, 0x1D, 0xF0, 0xFC, 0x7F, 0x01, 0xF8, 0x1D, 0xFD, - 0xFC, 0x7F, 0x01, 0xFF, 0xFC, 0xFF, 0xF8, 0x7F, 0x00, 0xFF, 0xF8, 0xFF, - 0xF8, 0x3F, 0x80, 0xFF, 0xF8, 0x7F, 0xF0, 0x3F, 0x80, 0x7F, 0xF0, 0x3F, - 0xE0, 0x3F, 0x80, 0x3F, 0xE0, 0x1F, 0xC0, 0x1F, 0xC0, 0x0F, 0xC0, 0x00, - 0x00, 0x1F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x80, 0x01, 0xFF, 0xC0, 0x00, 0x07, - 0x80, 0x00, 0xFF, 0xF8, 0x00, 0x7F, 0xC0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xF8, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xE0, - 0x00, 0x03, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0x80, - 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xFF, 0xFC, 0x00, - 0x01, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xF0, 0x00, - 0x0F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x7F, 0xBF, 0x80, 0x00, - 0xFF, 0x7F, 0x00, 0x01, 0xFE, 0xFE, 0x00, 0x03, 0xFD, 0xFE, 0x00, 0x07, - 0xF3, 0xFC, 0x00, 0x0F, 0xE7, 0xF8, 0x00, 0x3F, 0xCF, 0xF0, 0x00, 0x7F, - 0x8F, 0xE0, 0x00, 0xFF, 0x1F, 0xC0, 0x01, 0xFE, 0x3F, 0xC0, 0x03, 0xFC, - 0x7F, 0x80, 0x07, 0xF8, 0xFF, 0x00, 0x0F, 0xE1, 0xFE, 0x00, 0x3F, 0xC3, - 0xFC, 0x00, 0x7F, 0x83, 0xF8, 0x00, 0xFF, 0x07, 0xF0, 0x01, 0xFE, 0x0F, - 0xF0, 0x03, 0xFC, 0x1F, 0xE0, 0x07, 0xF8, 0x3F, 0xC0, 0x0F, 0xE0, 0x7F, - 0x80, 0x3F, 0xC0, 0xFF, 0x00, 0x7F, 0x80, 0xFE, 0x00, 0xFF, 0x01, 0xFE, - 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFC, 0x07, 0xF8, 0x07, 0xF8, 0x0F, 0xF0, - 0x1F, 0xF0, 0x1F, 0xE0, 0x3F, 0xC0, 0x3F, 0xC0, 0x7F, 0x80, 0x3F, 0xC0, - 0xFF, 0x00, 0x7F, 0x81, 0xFE, 0x00, 0xFF, 0x03, 0xFC, 0x01, 0xFE, 0x07, - 0xF8, 0x03, 0xFC, 0x1F, 0xFF, 0xFF, 0xF8, 0x3F, 0xFF, 0xFF, 0xF8, 0x7F, - 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, - 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFE, 0x3F, 0xE0, - 0x03, 0xFE, 0x7F, 0x80, 0x03, 0xFC, 0xFF, 0x00, 0x07, 0xF9, 0xFE, 0x00, - 0x0F, 0xF3, 0xFC, 0x00, 0x1F, 0xE7, 0xF8, 0x00, 0x3F, 0xDF, 0xF0, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x01, - 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x07, 0xF8, 0xFF, 0xFF, 0xF0, - 0x0F, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xCF, 0xF8, 0x0F, - 0xFC, 0xFF, 0x80, 0x7F, 0xCF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, - 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, - 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x03, 0xFE, - 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, - 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xCF, 0xF8, 0x03, 0xFC, 0xFF, 0x80, 0x3F, - 0xCF, 0xF8, 0x07, 0xFC, 0xFF, 0x80, 0x7F, 0x8F, 0xFF, 0xFF, 0xF8, 0xFF, - 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, - 0xE0, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xF8, 0xFF, 0x80, 0xFF, 0xCF, - 0xF8, 0x07, 0xFC, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, - 0x1F, 0xEF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x7F, 0xEF, - 0xF8, 0x0F, 0xFC, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, - 0xFF, 0x8F, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x3F, 0xFE, 0x00, 0x1F, - 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFE, - 0x0F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFC, 0x7F, 0xE0, 0xFF, 0xCF, 0xF8, - 0x0F, 0xFB, 0xFE, 0x01, 0xFF, 0x7F, 0xC0, 0x1F, 0xEF, 0xF8, 0x03, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, - 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, - 0x80, 0x00, 0x0F, 0xF0, 0x00, 0x01, 0xFE, 0x00, 0x00, 0x3F, 0xC0, 0x00, - 0x07, 0xF8, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x03, 0xFC, - 0x00, 0x00, 0x7F, 0x80, 0x00, 0x0F, 0xF0, 0x00, 0x01, 0xFE, 0x00, 0x00, - 0x3F, 0xC0, 0x00, 0x07, 0xF8, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x1F, 0xE0, - 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, - 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, - 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF0, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, - 0xFF, 0xFF, 0x80, 0x3F, 0xDF, 0xF0, 0x07, 0xF9, 0xFE, 0x01, 0xFF, 0x3F, - 0xE0, 0x3F, 0xE7, 0xFE, 0x0F, 0xFC, 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, - 0xE0, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x1F, - 0xFF, 0xF0, 0x00, 0xFF, 0xF8, 0x00, 0x03, 0xF8, 0x00, 0xFF, 0xFF, 0xF0, - 0x0F, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xCF, 0xF8, 0x0F, - 0xFC, 0xFF, 0x80, 0x7F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, - 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFE, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, 0x03, - 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x7F, 0xCF, - 0xF8, 0x0F, 0xFC, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, - 0xFF, 0x0F, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0x80, - 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, - 0xBF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, - 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, - 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, - 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, - 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, - 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, - 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFE, 0xFF, 0x80, 0x03, 0xFE, 0x00, - 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, - 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, - 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, - 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, - 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, - 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, - 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, - 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, - 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF, - 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, 0xE0, 0x00, - 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, - 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, - 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, - 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, - 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, - 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, - 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, - 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x3F, 0xFF, 0x00, 0x1F, 0xFF, 0xF8, - 0x07, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, - 0xFF, 0xE3, 0xFF, 0xFF, 0xFE, 0x7F, 0xE0, 0x7F, 0xCF, 0xF8, 0x07, 0xFB, - 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, - 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, - 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x01, - 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, - 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x7F, 0x80, 0x00, - 0x0F, 0xF0, 0x00, 0x01, 0xFE, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x07, 0xF8, - 0x00, 0x00, 0xFF, 0x03, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFC, 0x0F, 0xFF, - 0xFF, 0x81, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, 0xFF, - 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xFE, 0x00, - 0x3F, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, - 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, - 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFD, 0xFF, 0x00, 0xFF, 0xBF, 0xE0, 0x3F, - 0xF7, 0xFE, 0x0F, 0xFE, 0x7F, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0xF8, 0xFF, - 0xFF, 0xDF, 0x1F, 0xFF, 0xF3, 0xE1, 0xFF, 0xFE, 0x7C, 0x1F, 0xFF, 0x8F, - 0x80, 0xFF, 0xC0, 0xF0, 0x01, 0x80, 0x00, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x1F, - 0xF0, 0x00, 0x07, 0xFC, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x7F, 0xC0, 0x00, - 0x1F, 0xF0, 0x00, 0x07, 0xFC, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x7F, 0xC0, - 0x00, 0x1F, 0xF0, 0x00, 0x07, 0xFC, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x7F, - 0xC0, 0x00, 0x1F, 0xF0, 0x00, 0x07, 0xFC, 0x00, 0x01, 0xFF, 0x00, 0x00, - 0x7F, 0xC0, 0x00, 0x1F, 0xF0, 0x00, 0x07, 0xFC, 0x00, 0x01, 0xFF, 0x00, - 0x00, 0x7F, 0xC0, 0x00, 0x1F, 0xF0, 0x00, 0x07, 0xFC, 0x00, 0x01, 0xFF, - 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x1F, 0xF0, 0x00, 0x07, 0xFC, 0x00, 0x01, - 0xFF, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x1F, 0xF0, 0x00, 0x07, 0xFC, 0x00, - 0x01, 0xFF, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x1F, 0xF0, 0x00, 0x07, 0xFC, - 0x00, 0x01, 0xFF, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x1F, 0xF0, 0x00, 0x07, - 0xFC, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x1F, 0xFF, 0xF8, - 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, - 0xF8, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x1F, - 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, - 0x1F, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0x7F, 0x80, 0x7F, 0xDF, - 0xF0, 0x1F, 0xE7, 0xFC, 0x0F, 0xF9, 0xFF, 0x87, 0xFE, 0x3F, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xF8, 0x0F, 0xFF, - 0xFC, 0x01, 0xFF, 0xFE, 0x00, 0x1F, 0xFE, 0x00, 0x00, 0xFC, 0x00, 0xFF, - 0x80, 0x0F, 0xFF, 0xFE, 0x00, 0x3F, 0xEF, 0xF8, 0x01, 0xFF, 0x3F, 0xE0, - 0x07, 0xFC, 0xFF, 0x80, 0x1F, 0xF3, 0xFE, 0x00, 0xFF, 0x8F, 0xF8, 0x03, - 0xFE, 0x3F, 0xE0, 0x0F, 0xF8, 0xFF, 0x80, 0x7F, 0xC3, 0xFE, 0x01, 0xFF, - 0x0F, 0xF8, 0x07, 0xFC, 0x3F, 0xE0, 0x3F, 0xE0, 0xFF, 0x80, 0xFF, 0x83, - 0xFE, 0x03, 0xFE, 0x0F, 0xF8, 0x1F, 0xF0, 0x3F, 0xE0, 0x7F, 0xC0, 0xFF, - 0x83, 0xFE, 0x03, 0xFE, 0x0F, 0xF8, 0x0F, 0xF8, 0x3F, 0xE0, 0x3F, 0xE1, - 0xFF, 0x00, 0xFF, 0x87, 0xFC, 0x03, 0xFE, 0x1F, 0xF0, 0x0F, 0xF8, 0xFF, - 0x80, 0x3F, 0xE3, 0xFE, 0x00, 0xFF, 0x8F, 0xF8, 0x03, 0xFE, 0x7F, 0xC0, - 0x0F, 0xF9, 0xFF, 0x00, 0x3F, 0xE7, 0xF8, 0x00, 0xFF, 0xBF, 0xE0, 0x03, - 0xFE, 0xFF, 0x80, 0x0F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0xFF, - 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, - 0xF8, 0x00, 0xFF, 0xBF, 0xE0, 0x03, 0xFE, 0xFF, 0x80, 0x0F, 0xF9, 0xFF, - 0x00, 0x3F, 0xE7, 0xFC, 0x00, 0xFF, 0x9F, 0xF0, 0x03, 0xFE, 0x3F, 0xE0, - 0x0F, 0xF8, 0xFF, 0x80, 0x3F, 0xE3, 0xFF, 0x00, 0xFF, 0x87, 0xFC, 0x03, - 0xFE, 0x1F, 0xF0, 0x0F, 0xF8, 0x7F, 0xE0, 0x3F, 0xE0, 0xFF, 0x80, 0xFF, - 0x83, 0xFE, 0x03, 0xFE, 0x07, 0xFC, 0x0F, 0xF8, 0x1F, 0xF0, 0x3F, 0xE0, - 0x7F, 0xE0, 0xFF, 0x80, 0xFF, 0x83, 0xFE, 0x03, 0xFE, 0x0F, 0xF8, 0x0F, - 0xFC, 0x3F, 0xE0, 0x1F, 0xF0, 0xFF, 0x80, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, - 0x8F, 0xF8, 0x03, 0xFE, 0x3F, 0xE0, 0x0F, 0xFC, 0xFF, 0x80, 0x1F, 0xF3, - 0xFE, 0x00, 0x7F, 0xCF, 0xF8, 0x01, 0xFF, 0xBF, 0xE0, 0x03, 0xFE, 0xFF, - 0x80, 0x0F, 0xFB, 0xFE, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x7F, 0xC0, 0xFF, - 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, - 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, - 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, - 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, - 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, - 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, - 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, - 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, - 0xE0, 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, - 0x00, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, - 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, - 0x80, 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, - 0x03, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0x80, 0x03, - 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0x00, - 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFF, - 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, - 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, - 0x1F, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, - 0xFF, 0xBF, 0x80, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, - 0xE0, 0x01, 0xFB, 0xFF, 0xFF, 0xF0, 0x01, 0xFD, 0xFF, 0xFD, 0xF8, 0x00, - 0xFE, 0xFF, 0xFE, 0xFE, 0x00, 0x7F, 0x7F, 0xFF, 0x7F, 0x00, 0x3F, 0xBF, - 0xFF, 0xBF, 0x80, 0x1F, 0x9F, 0xFF, 0xDF, 0xC0, 0x0F, 0xCF, 0xFF, 0xE7, - 0xE0, 0x0F, 0xE7, 0xFF, 0xF3, 0xF8, 0x07, 0xF3, 0xFF, 0xF9, 0xFC, 0x03, - 0xF9, 0xFF, 0xFC, 0xFE, 0x01, 0xF8, 0xFF, 0xFE, 0x7F, 0x00, 0xFC, 0x7F, - 0xFF, 0x1F, 0x80, 0xFE, 0x3F, 0xFF, 0x8F, 0xC0, 0x7F, 0x1F, 0xFF, 0xC7, - 0xF0, 0x3F, 0x8F, 0xFF, 0xE3, 0xF8, 0x1F, 0x87, 0xFF, 0xF1, 0xFC, 0x0F, - 0xC3, 0xFF, 0xF8, 0x7E, 0x07, 0xE1, 0xFF, 0xFC, 0x3F, 0x07, 0xF1, 0xFF, - 0xFE, 0x1F, 0xC3, 0xF8, 0xFF, 0xFF, 0x0F, 0xE1, 0xF8, 0x7F, 0xFF, 0x87, - 0xF0, 0xFC, 0x3F, 0xFF, 0xC1, 0xF8, 0x7E, 0x1F, 0xFF, 0xE0, 0xFC, 0x7F, - 0x0F, 0xFF, 0xF0, 0x7F, 0x3F, 0x87, 0xFF, 0xF8, 0x3F, 0x9F, 0x83, 0xFF, - 0xFC, 0x1F, 0xCF, 0xC1, 0xFF, 0xFE, 0x07, 0xE7, 0xE0, 0xFF, 0xFF, 0x03, - 0xF3, 0xF0, 0x7F, 0xFF, 0x81, 0xFF, 0xF8, 0x3F, 0xFF, 0xC0, 0xFF, 0xF8, - 0x1F, 0xFF, 0xE0, 0x7F, 0xFC, 0x0F, 0xFF, 0xF0, 0x1F, 0xFE, 0x07, 0xFF, - 0xF8, 0x0F, 0xFF, 0x03, 0xFF, 0xFC, 0x07, 0xFF, 0x81, 0xFF, 0xFE, 0x03, - 0xFF, 0xC0, 0xFF, 0xFF, 0x01, 0xFF, 0xC0, 0x7F, 0xFF, 0x80, 0x7F, 0xE0, - 0x3F, 0xFF, 0xC0, 0x3F, 0xF0, 0x1F, 0xFF, 0xE0, 0x1F, 0xF8, 0x0F, 0xFF, - 0xF0, 0x0F, 0xFC, 0x07, 0xFF, 0xF8, 0x07, 0xFC, 0x03, 0xFF, 0xFC, 0x01, - 0xFE, 0x01, 0xFF, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x7F, 0x80, - 0x7F, 0xFF, 0x80, 0x3F, 0xC0, 0x3F, 0xFF, 0xC0, 0x1F, 0xC0, 0x1F, 0xE0, - 0xFE, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xC0, 0x01, 0xFF, 0xFE, - 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0x00, 0x3F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, - 0xF8, 0x03, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF8, - 0x07, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xF8, 0x0F, - 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, - 0xFF, 0xC0, 0xFF, 0xFF, 0x7F, 0x07, 0xFF, 0xFB, 0xF8, 0x3F, 0xFF, 0xDF, - 0xC0, 0xFF, 0xFE, 0x7F, 0x07, 0xFF, 0xF3, 0xF8, 0x3F, 0xFF, 0x9F, 0xC1, - 0xFF, 0xFC, 0x7F, 0x0F, 0xFF, 0xE3, 0xF8, 0x7F, 0xFF, 0x1F, 0xC3, 0xFF, - 0xF8, 0x7F, 0x1F, 0xFF, 0xC3, 0xF8, 0xFF, 0xFE, 0x1F, 0xE7, 0xFF, 0xF0, - 0x7F, 0x3F, 0xFF, 0x83, 0xF9, 0xFF, 0xFC, 0x1F, 0xEF, 0xFF, 0xE0, 0x7F, - 0x7F, 0xFF, 0x83, 0xFB, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, - 0xFF, 0x03, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, 0xFE, - 0x03, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xFC, 0x03, - 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xF8, 0x03, 0xFF, - 0xFF, 0xC0, 0x1F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xC0, 0x03, 0xFF, 0xFE, 0x00, 0x1F, - 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0x80, 0x03, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, - 0xE0, 0x00, 0x7E, 0x00, 0x7F, 0xE0, 0x00, 0x7F, 0xFF, 0x00, 0x1F, 0xFF, - 0xF8, 0x07, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0x0F, - 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFE, 0x7F, 0xE0, 0x7F, 0xCF, 0xF8, 0x0F, - 0xFB, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, - 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, - 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, - 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, - 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, - 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, - 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, - 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, - 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xFE, - 0x00, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, - 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xBF, 0xE0, - 0x3F, 0xE7, 0xFE, 0x0F, 0xFC, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xE1, - 0xFF, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, - 0xF0, 0x00, 0xFF, 0xF8, 0x00, 0x03, 0xF8, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, - 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, - 0xFF, 0x8F, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xCF, 0xF8, 0x0F, 0xFC, - 0xFF, 0x80, 0x7F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, - 0x03, 0xFE, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, 0x01, 0xFE, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, 0x01, 0xFE, 0xFF, 0x80, - 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x07, 0xFE, - 0xFF, 0x80, 0xFF, 0xCF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, - 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFC, - 0x0F, 0xFF, 0xFF, 0x80, 0xFF, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, - 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, - 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, - 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, - 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, - 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, - 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, - 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x7F, 0xFF, 0x00, 0x1F, 0xFF, - 0xF8, 0x07, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0x0F, - 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFE, 0x7F, 0xE0, 0x7F, 0xCF, 0xF8, 0x0F, - 0xFB, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, - 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, - 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, - 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, - 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, - 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, - 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, - 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, - 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x01, 0xFF, 0xFE, - 0x00, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, - 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xBF, 0xE0, - 0x3F, 0xE7, 0xFE, 0x0F, 0xFC, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xE1, - 0xFF, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, - 0xF0, 0x00, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x0F, 0xF0, 0x00, - 0x00, 0xFF, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x0F, - 0xF0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x01, 0xF0, 0x00, - 0x00, 0x1C, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, - 0xE0, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xCF, - 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xEF, 0xF8, 0x07, 0xFE, 0xFF, 0x80, - 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, 0x01, 0xFE, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xEF, 0xF8, 0x01, 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x03, - 0xFE, 0xFF, 0x80, 0x3F, 0xEF, 0xF8, 0x07, 0xFC, 0xFF, 0xFF, 0xFF, 0xCF, - 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, - 0xFF, 0x0F, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xCF, 0xF8, 0x0F, 0xFC, - 0xFF, 0x80, 0x3F, 0xCF, 0xF8, 0x03, 0xFE, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, - 0x01, 0xFE, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, 0x01, 0xFE, 0xFF, 0x80, 0x1F, - 0xEF, 0xF8, 0x01, 0xFE, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xF0, 0x00, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x00, 0x3F, 0xFF, 0xF0, 0x0F, - 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, - 0xE3, 0xFF, 0xDF, 0xFC, 0x7F, 0xC0, 0xFF, 0x8F, 0xF0, 0x0F, 0xF3, 0xFE, - 0x01, 0xFF, 0x7F, 0xC0, 0x3F, 0xEF, 0xF8, 0x03, 0xFD, 0xFE, 0x00, 0x7F, - 0xBF, 0xC0, 0x0F, 0xF7, 0xF8, 0x01, 0xFE, 0xFF, 0x00, 0x3F, 0xDF, 0xF0, - 0x07, 0xFB, 0xFE, 0x00, 0xFF, 0x7F, 0xC0, 0x1F, 0xEF, 0xF8, 0x03, 0xFC, - 0xFF, 0x80, 0x7F, 0x9F, 0xF8, 0x0F, 0xF3, 0xFF, 0x80, 0x00, 0x3F, 0xF0, - 0x00, 0x07, 0xFF, 0x00, 0x00, 0x7F, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, - 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, - 0x00, 0x01, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0x00, 0x01, 0xFF, 0xF0, 0x00, - 0x1F, 0xFE, 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x1F, 0xFE, 0x00, 0x01, 0xFF, - 0xC0, 0x00, 0x1F, 0xFC, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x1F, 0xFB, 0xFE, - 0x03, 0xFF, 0x7F, 0xC0, 0x3F, 0xEF, 0xF8, 0x07, 0xFD, 0xFF, 0x00, 0x7F, - 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, - 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x03, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFB, 0xFC, 0x01, 0xFF, 0x7F, 0x80, - 0x3F, 0xEF, 0xF0, 0x07, 0xFD, 0xFF, 0x00, 0xFF, 0x3F, 0xE0, 0x3F, 0xE3, - 0xFE, 0x0F, 0xFC, 0x7F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, - 0xFC, 0x0F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x00, - 0xFF, 0xF8, 0x00, 0x01, 0xF8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0xFF, 0x80, 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, - 0xFC, 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, - 0xC0, 0x00, 0xFF, 0x80, 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, - 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xC0, - 0x00, 0xFF, 0x80, 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, - 0x0F, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xC0, 0x00, - 0xFF, 0x80, 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0x0F, - 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0xFF, - 0x80, 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0x0F, 0xF8, - 0x00, 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0xFF, 0x80, - 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0x0F, 0xF8, 0x00, - 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0xFF, 0x80, 0x01, - 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0x0F, 0xF8, 0x00, 0x1F, - 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0xFF, 0x80, 0x01, 0xFF, - 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFE, 0xFF, 0x80, 0x1F, 0xEF, 0xF8, 0x01, 0xFE, 0xFF, - 0x80, 0x3F, 0xEF, 0xF8, 0x03, 0xFE, 0x7F, 0xC0, 0x3F, 0xE7, 0xFE, 0x07, - 0xFC, 0x7F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0x81, - 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x01, 0xFF, - 0xF8, 0x00, 0x01, 0xFC, 0x00, 0xFF, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x07, - 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x1F, 0xEF, 0xF8, 0x00, 0x3F, - 0xDF, 0xF0, 0x00, 0x7F, 0x9F, 0xE0, 0x00, 0xFF, 0x3F, 0xC0, 0x01, 0xFE, - 0x7F, 0x80, 0x03, 0xFC, 0xFF, 0x00, 0x0F, 0xF9, 0xFF, 0x00, 0x1F, 0xE3, - 0xFE, 0x00, 0x3F, 0xC3, 0xFC, 0x00, 0x7F, 0x87, 0xF8, 0x00, 0xFF, 0x0F, - 0xF0, 0x01, 0xFE, 0x1F, 0xE0, 0x03, 0xFC, 0x3F, 0xE0, 0x0F, 0xF8, 0x7F, - 0xC0, 0x1F, 0xE0, 0x7F, 0x80, 0x3F, 0xC0, 0xFF, 0x00, 0x7F, 0x81, 0xFE, - 0x00, 0xFF, 0x03, 0xFC, 0x01, 0xFE, 0x07, 0xF8, 0x03, 0xFC, 0x0F, 0xF8, - 0x07, 0xF0, 0x0F, 0xF0, 0x1F, 0xE0, 0x1F, 0xE0, 0x3F, 0xC0, 0x3F, 0xC0, - 0x7F, 0x80, 0x7F, 0x80, 0xFF, 0x00, 0xFF, 0x01, 0xFE, 0x01, 0xFF, 0x03, - 0xFC, 0x03, 0xFE, 0x07, 0xF0, 0x03, 0xFC, 0x0F, 0xE0, 0x07, 0xF8, 0x3F, - 0xC0, 0x0F, 0xF0, 0x7F, 0x80, 0x1F, 0xE0, 0xFF, 0x00, 0x3F, 0xC1, 0xFE, - 0x00, 0x7F, 0xC3, 0xFC, 0x00, 0x7F, 0x87, 0xF0, 0x00, 0xFF, 0x0F, 0xE0, - 0x01, 0xFE, 0x1F, 0xC0, 0x03, 0xFC, 0x7F, 0x80, 0x07, 0xF8, 0xFF, 0x00, - 0x0F, 0xF1, 0xFE, 0x00, 0x0F, 0xF3, 0xFC, 0x00, 0x1F, 0xE7, 0xF0, 0x00, - 0x3F, 0xCF, 0xE0, 0x00, 0x7F, 0x9F, 0xC0, 0x00, 0xFF, 0x3F, 0x80, 0x01, - 0xFE, 0xFF, 0x00, 0x01, 0xFD, 0xFE, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x07, - 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x3F, - 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xFF, - 0xF8, 0x00, 0x01, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x07, 0xFF, - 0xC0, 0x00, 0x07, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x1F, 0xFE, - 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0xFF, 0xE0, - 0x00, 0xFF, 0x00, 0x0F, 0xF0, 0x01, 0xFF, 0xFF, 0x00, 0x0F, 0xF0, 0x01, - 0xFE, 0xFF, 0x00, 0x0F, 0xF0, 0x01, 0xFE, 0xFF, 0x00, 0x0F, 0xF0, 0x01, - 0xFE, 0x7F, 0x80, 0x0F, 0xF0, 0x01, 0xFE, 0x7F, 0x80, 0x0F, 0xF8, 0x01, - 0xFE, 0x7F, 0x80, 0x0F, 0xF8, 0x01, 0xFE, 0x7F, 0x80, 0x1F, 0xF8, 0x01, - 0xFE, 0x7F, 0x80, 0x1F, 0xF8, 0x01, 0xFE, 0x7F, 0x80, 0x1F, 0xF8, 0x01, - 0xFE, 0x7F, 0x80, 0x1F, 0xF8, 0x01, 0xFC, 0x7F, 0x80, 0x1F, 0xF8, 0x03, - 0xFC, 0x3F, 0x80, 0x1F, 0xFC, 0x03, 0xFC, 0x3F, 0xC0, 0x1F, 0xFC, 0x03, - 0xFC, 0x3F, 0xC0, 0x3F, 0xFC, 0x03, 0xFC, 0x3F, 0xC0, 0x3F, 0xFC, 0x03, - 0xFC, 0x3F, 0xC0, 0x3F, 0xFC, 0x03, 0xFC, 0x3F, 0xC0, 0x3F, 0xFC, 0x03, - 0xFC, 0x3F, 0xC0, 0x3F, 0xFC, 0x03, 0xF8, 0x3F, 0xC0, 0x3F, 0xFE, 0x03, - 0xF8, 0x1F, 0xC0, 0x3F, 0xFE, 0x03, 0xF8, 0x1F, 0xC0, 0x7F, 0x7E, 0x03, - 0xF8, 0x1F, 0xC0, 0x7F, 0x7E, 0x07, 0xF8, 0x1F, 0xE0, 0x7F, 0x7E, 0x07, - 0xF8, 0x1F, 0xE0, 0x7F, 0x7E, 0x07, 0xF8, 0x1F, 0xE0, 0x7E, 0x7E, 0x07, - 0xF8, 0x1F, 0xE0, 0x7E, 0x7F, 0x07, 0xF8, 0x0F, 0xE0, 0x7E, 0x7F, 0x07, - 0xF0, 0x0F, 0xE0, 0xFE, 0x3F, 0x07, 0xF0, 0x0F, 0xE0, 0xFE, 0x3F, 0x07, - 0xF0, 0x0F, 0xE0, 0xFE, 0x3F, 0x07, 0xF0, 0x0F, 0xE0, 0xFE, 0x3F, 0x07, - 0xF0, 0x0F, 0xF0, 0xFC, 0x3F, 0x0F, 0xF0, 0x0F, 0xF0, 0xFC, 0x3F, 0x8F, - 0xF0, 0x0F, 0xF0, 0xFC, 0x3F, 0x8F, 0xF0, 0x07, 0xF1, 0xFC, 0x1F, 0x8F, - 0xE0, 0x07, 0xF1, 0xFC, 0x1F, 0x8F, 0xE0, 0x07, 0xF1, 0xFC, 0x1F, 0x8F, - 0xE0, 0x07, 0xF1, 0xF8, 0x1F, 0x8F, 0xE0, 0x07, 0xF1, 0xF8, 0x1F, 0x8F, - 0xE0, 0x07, 0xF1, 0xF8, 0x1F, 0xCF, 0xE0, 0x07, 0xF9, 0xF8, 0x1F, 0xCF, - 0xE0, 0x07, 0xFB, 0xF8, 0x0F, 0xCF, 0xE0, 0x03, 0xFB, 0xF8, 0x0F, 0xDF, - 0xE0, 0x03, 0xFB, 0xF8, 0x0F, 0xDF, 0xC0, 0x03, 0xFB, 0xF0, 0x0F, 0xDF, - 0xC0, 0x03, 0xFB, 0xF0, 0x0F, 0xDF, 0xC0, 0x03, 0xFB, 0xF0, 0x0F, 0xFF, - 0xC0, 0x03, 0xFB, 0xF0, 0x0F, 0xFF, 0xC0, 0x03, 0xFF, 0xF0, 0x07, 0xFF, - 0xC0, 0x03, 0xFF, 0xF0, 0x07, 0xFF, 0xC0, 0x01, 0xFF, 0xF0, 0x07, 0xFF, - 0xC0, 0x01, 0xFF, 0xE0, 0x07, 0xFF, 0xC0, 0x01, 0xFF, 0xE0, 0x07, 0xFF, - 0x80, 0x01, 0xFF, 0xE0, 0x07, 0xFF, 0x80, 0x01, 0xFF, 0xE0, 0x07, 0xFF, - 0x80, 0x01, 0xFF, 0xE0, 0x03, 0xFF, 0x80, 0x01, 0xFF, 0xE0, 0x03, 0xFF, - 0x80, 0x01, 0xFF, 0xE0, 0x03, 0xFF, 0x80, 0x00, 0xFF, 0xC0, 0x03, 0xFF, - 0x80, 0x00, 0xFF, 0xC0, 0x03, 0xFF, 0x80, 0x00, 0xFF, 0xC0, 0x03, 0xFF, - 0x00, 0x00, 0xFF, 0xC0, 0x03, 0xFF, 0x00, 0x00, 0xFF, 0xC0, 0x03, 0xFF, - 0x00, 0x00, 0xFF, 0xC0, 0x01, 0xFF, 0x00, 0x00, 0xFF, 0xC0, 0x01, 0xFF, - 0x00, 0x00, 0xFF, 0x80, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0xFF, 0xF0, - 0x01, 0xFE, 0x7F, 0x00, 0x1F, 0xE7, 0xF8, 0x01, 0xFE, 0x7F, 0x80, 0x3F, - 0xE3, 0xF8, 0x03, 0xFC, 0x3F, 0xC0, 0x3F, 0xC3, 0xFC, 0x03, 0xFC, 0x1F, - 0xC0, 0x7F, 0x81, 0xFE, 0x07, 0xF8, 0x1F, 0xE0, 0x7F, 0x81, 0xFE, 0x07, - 0xF8, 0x0F, 0xE0, 0xFF, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xF0, 0xFF, 0x00, - 0x7F, 0x0F, 0xE0, 0x07, 0xF9, 0xFE, 0x00, 0x7F, 0x9F, 0xE0, 0x03, 0xF9, - 0xFE, 0x00, 0x3F, 0xDF, 0xC0, 0x03, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xC0, - 0x01, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0xFF, 0xF8, 0x00, 0x0F, - 0xFF, 0x80, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x7F, 0xF0, - 0x00, 0x07, 0xFF, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x07, 0xFE, 0x00, 0x00, - 0x7F, 0xE0, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x0F, 0xFE, - 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0xFF, 0xF0, 0x00, - 0x1F, 0xFF, 0x00, 0x01, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0x00, 0x03, 0xFF, - 0xF8, 0x00, 0x3F, 0xFF, 0x80, 0x03, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xC0, - 0x07, 0xFB, 0xFC, 0x00, 0x7F, 0x9F, 0xC0, 0x07, 0xF9, 0xFE, 0x00, 0xFF, - 0x9F, 0xE0, 0x0F, 0xF0, 0xFE, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xF0, 0xFF, - 0x01, 0xFF, 0x07, 0xF0, 0x1F, 0xE0, 0x7F, 0x81, 0xFE, 0x07, 0xF8, 0x1F, - 0xE0, 0x7F, 0x83, 0xFE, 0x03, 0xF8, 0x3F, 0xC0, 0x3F, 0xC3, 0xFC, 0x03, - 0xFC, 0x7F, 0xC0, 0x1F, 0xC7, 0xF8, 0x01, 0xFE, 0x7F, 0x80, 0x1F, 0xE7, - 0xF8, 0x00, 0xFE, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x00, - 0x07, 0xF0, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x07, 0xFB, 0xFE, 0x00, - 0x1F, 0xF3, 0xFC, 0x00, 0x3F, 0xE7, 0xFC, 0x00, 0x7F, 0xCF, 0xF8, 0x00, - 0xFF, 0x0F, 0xF0, 0x03, 0xFE, 0x1F, 0xE0, 0x07, 0xFC, 0x3F, 0xE0, 0x0F, - 0xF0, 0x7F, 0xC0, 0x1F, 0xE0, 0x7F, 0x80, 0x3F, 0xC0, 0xFF, 0x80, 0xFF, - 0x81, 0xFF, 0x01, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFC, 0x07, 0xF8, - 0x07, 0xFC, 0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xC0, 0x0F, 0xF0, 0x7F, 0x80, - 0x1F, 0xE0, 0xFF, 0x00, 0x3F, 0xE3, 0xFC, 0x00, 0x3F, 0xC7, 0xF8, 0x00, - 0x7F, 0x8F, 0xF0, 0x00, 0xFF, 0x9F, 0xE0, 0x00, 0xFF, 0x7F, 0x80, 0x01, - 0xFE, 0xFF, 0x00, 0x03, 0xFD, 0xFE, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x07, - 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x1F, - 0xFF, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x7F, 0xFC, 0x00, 0x00, 0xFF, - 0xF8, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x03, 0xFF, - 0x80, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, - 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, - 0x00, 0x00, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00, - 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, - 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0xFF, 0x80, 0x00, - 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, - 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, - 0x7F, 0xC0, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, - 0xFE, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, - 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x7F, 0xFF, - 0xFE, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, - 0xEF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x07, 0xFC, - 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0xC0, 0x00, 0xFF, 0x80, - 0x01, 0xFF, 0x00, 0x07, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0xF0, 0x00, - 0x3F, 0xE0, 0x00, 0xFF, 0xC0, 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, - 0xFC, 0x00, 0x1F, 0xF8, 0x00, 0x3F, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0xFF, - 0x80, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xF0, - 0x00, 0x7F, 0xC0, 0x00, 0xFF, 0x80, 0x01, 0xFF, 0x00, 0x07, 0xFE, 0x00, - 0x0F, 0xF8, 0x00, 0x1F, 0xF0, 0x00, 0x3F, 0xE0, 0x00, 0xFF, 0xC0, 0x01, - 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, 0xFC, 0x00, 0x1F, 0xF8, 0x00, 0x3F, - 0xE0, 0x00, 0x7F, 0xC0, 0x01, 0xFF, 0x80, 0x03, 0xFE, 0x00, 0x07, 0xFC, - 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xF0, 0x00, 0x7F, 0xC0, 0x00, 0xFF, 0x80, - 0x01, 0xFF, 0x00, 0x07, 0xFE, 0x00, 0x0F, 0xF8, 0x00, 0x1F, 0xF0, 0x00, - 0x3F, 0xE0, 0x00, 0xFF, 0xC0, 0x01, 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x07, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0x7F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, - 0x0F, 0xF8, 0x07, 0xFC, 0x03, 0xFE, 0x01, 0xFF, 0x00, 0xFF, 0x80, 0x7F, - 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F, 0xF8, 0x07, 0xFC, 0x03, 0xFE, 0x01, - 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F, 0xF8, - 0x07, 0xFC, 0x03, 0xFE, 0x01, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0, 0x3F, - 0xE0, 0x1F, 0xF0, 0x0F, 0xF8, 0x07, 0xFC, 0x03, 0xFE, 0x01, 0xFF, 0x00, - 0xFF, 0x80, 0x7F, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F, 0xF8, 0x07, 0xFC, - 0x03, 0xFE, 0x01, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0, 0x3F, 0xE0, 0x1F, - 0xF0, 0x0F, 0xF8, 0x07, 0xFC, 0x03, 0xFE, 0x01, 0xFF, 0x00, 0xFF, 0x80, - 0x7F, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F, 0xF8, 0x07, 0xFC, 0x03, 0xFE, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x7F, 0x80, - 0x00, 0x7F, 0x80, 0x00, 0x7F, 0x80, 0x00, 0x3F, 0x80, 0x00, 0x3F, 0xC0, - 0x00, 0x3F, 0xC0, 0x00, 0x3F, 0xC0, 0x00, 0x3F, 0xC0, 0x00, 0x1F, 0xE0, - 0x00, 0x1F, 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x0F, 0xE0, - 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF0, - 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, - 0x00, 0x03, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x03, 0xFC, - 0x00, 0x01, 0xFC, 0x00, 0x01, 0xFE, 0x00, 0x01, 0xFE, 0x00, 0x01, 0xFE, - 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, - 0x00, 0x00, 0xFF, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x7F, 0x80, 0x00, 0x7F, - 0x80, 0x00, 0x7F, 0x80, 0x00, 0x3F, 0x80, 0x00, 0x3F, 0xC0, 0x00, 0x3F, - 0xC0, 0x00, 0x3F, 0xC0, 0x00, 0x1F, 0xC0, 0x00, 0x1F, 0xE0, 0x00, 0x1F, - 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x0F, 0xE0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, - 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF8, 0x00, 0x07, - 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x07, 0xF8, 0x00, 0x03, 0xF8, 0x00, 0x03, - 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x03, 0xFC, 0x00, 0x01, 0xFC, 0x00, 0x01, - 0xFE, 0x00, 0x01, 0xFE, 0x00, 0x01, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xF0, - 0x00, 0x03, 0xFF, 0x80, 0x00, 0x3F, 0xFE, 0x00, 0x01, 0xFF, 0xF0, 0x00, - 0x0F, 0xFF, 0x80, 0x00, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x3F, - 0xBF, 0x80, 0x01, 0xFD, 0xFC, 0x00, 0x1F, 0xEF, 0xE0, 0x00, 0xFF, 0x7F, - 0x80, 0x07, 0xF1, 0xFC, 0x00, 0x3F, 0x8F, 0xE0, 0x03, 0xFC, 0x7F, 0x80, - 0x1F, 0xE3, 0xFC, 0x00, 0xFF, 0x1F, 0xE0, 0x07, 0xF0, 0x7F, 0x00, 0x7F, - 0x83, 0xFC, 0x03, 0xFC, 0x1F, 0xE0, 0x1F, 0xE0, 0xFF, 0x00, 0xFF, 0x07, - 0xF8, 0x0F, 0xF0, 0x1F, 0xE0, 0x7F, 0x80, 0xFF, 0x03, 0xFC, 0x07, 0xF8, - 0x1F, 0xE0, 0x3F, 0xC1, 0xFE, 0x00, 0xFF, 0x0F, 0xF0, 0x07, 0xF8, 0x7F, - 0x80, 0x3F, 0xC7, 0xFC, 0x01, 0xFE, 0x3F, 0xE0, 0x0F, 0xF9, 0xFE, 0x00, - 0x3F, 0xCF, 0xF0, 0x01, 0xFE, 0xFF, 0x80, 0x0F, 0xF7, 0xFC, 0x00, 0x7F, - 0xFF, 0xE0, 0x03, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0xFF, 0x8F, 0xF8, 0x7F, 0x87, 0xFC, 0x3F, 0xC3, 0xFC, 0x1F, - 0xC0, 0xFE, 0x0F, 0xE0, 0x7E, 0x07, 0xE0, 0x3F, 0x03, 0xF0, 0x1F, 0x01, - 0xF0, 0x00, 0x7F, 0xE0, 0x00, 0x3F, 0xFF, 0x00, 0x1F, 0xFF, 0xF8, 0x07, - 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, - 0xE1, 0xFF, 0x87, 0xFE, 0x3F, 0xE0, 0x7F, 0xCF, 0xF8, 0x07, 0xF9, 0xFF, - 0x00, 0xFF, 0xBF, 0xE0, 0x1F, 0xF7, 0xFC, 0x03, 0xFE, 0xFF, 0x80, 0x7F, - 0xDF, 0xF0, 0x0F, 0xFB, 0xFE, 0x01, 0xFF, 0x7F, 0xC0, 0x3F, 0xEF, 0xF8, - 0x07, 0xFD, 0xFF, 0x00, 0xFF, 0x80, 0x00, 0x1F, 0xF0, 0x00, 0x03, 0xFE, - 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x7F, 0xF8, 0x00, 0x7F, 0xFF, 0x00, 0x3F, - 0xFF, 0xE0, 0x1F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0x83, 0xFF, 0x9F, 0xF0, - 0xFF, 0xC3, 0xFE, 0x3F, 0xE0, 0x7F, 0xCF, 0xF8, 0x0F, 0xFB, 0xFE, 0x01, - 0xFF, 0x7F, 0xC0, 0x3F, 0xEF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, - 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, - 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, - 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, - 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0x7F, 0xC0, 0x3F, 0xEF, 0xF8, - 0x0F, 0xFD, 0xFF, 0x03, 0xFF, 0xBF, 0xFB, 0xFF, 0xF3, 0xFF, 0xFF, 0xFE, - 0x7F, 0xFF, 0xFF, 0xC7, 0xFF, 0xEF, 0xF8, 0xFF, 0xFD, 0xFF, 0x0F, 0xFF, - 0x3F, 0xE0, 0x7F, 0x87, 0xFC, 0x00, 0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, - 0x1F, 0xF0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x0F, 0xF8, - 0x00, 0x01, 0xFF, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x07, 0xFC, 0x00, 0x00, - 0xFF, 0x80, 0x00, 0x1F, 0xF0, 0x00, 0x03, 0xFE, 0x0F, 0xC0, 0x7F, 0xC7, - 0xFE, 0x0F, 0xF9, 0xFF, 0xF1, 0xFF, 0x7F, 0xFE, 0x3F, 0xFF, 0xFF, 0xE7, - 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0x1F, 0xFB, 0xFF, 0x81, - 0xFF, 0x7F, 0xE0, 0x1F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, - 0xF0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, - 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, - 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, - 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, - 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, - 0xE0, 0x0F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xF8, 0x07, - 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFE, 0x07, 0xFD, 0xFF, - 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFC, 0xFF, 0xBF, 0xFF, - 0x9F, 0xF3, 0xFF, 0xE3, 0xFE, 0x3F, 0xF8, 0x7F, 0xC3, 0xFE, 0x00, 0x00, - 0x04, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xC0, - 0x7F, 0xFF, 0xF0, 0x1F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, - 0xF1, 0xFF, 0xCF, 0xFE, 0x7F, 0xC0, 0xFF, 0x9F, 0xE0, 0x1F, 0xEF, 0xF8, - 0x07, 0xFB, 0xFE, 0x01, 0xFE, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, - 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x1F, - 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, - 0x1F, 0xFF, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, - 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, - 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, - 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, - 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, - 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF8, 0x07, 0xFB, 0xFE, - 0x01, 0xFE, 0xFF, 0x80, 0x7F, 0x9F, 0xE0, 0x3F, 0xE7, 0xFC, 0x1F, 0xF9, - 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, - 0xE0, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xF8, 0x00, 0x7F, 0xF8, 0x00, 0x01, - 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, - 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, - 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x1F, - 0xC3, 0xFC, 0x1F, 0xF8, 0xFF, 0x0F, 0xFF, 0x3F, 0xC7, 0xFF, 0xEF, 0xF3, - 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xDF, 0xF8, 0x7F, - 0xFF, 0xFC, 0x0F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, - 0x1F, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, - 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x00, 0x3F, - 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x00, - 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, - 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x00, 0xFF, - 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x00, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, - 0x00, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, - 0xFC, 0x00, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, - 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF8, - 0x07, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xC0, 0xFF, 0xDF, 0xFF, 0xFF, 0xF7, - 0xFF, 0xFF, 0xFD, 0xFF, 0xFE, 0xFF, 0x3F, 0xFF, 0xBF, 0xC7, 0xFF, 0xCF, - 0xF1, 0xFF, 0xE3, 0xFC, 0x1F, 0xF0, 0xFF, 0x00, 0xE0, 0x00, 0x00, 0x00, - 0x7F, 0xC0, 0x00, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, - 0x1F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xF1, 0xFF, 0x87, - 0xFE, 0x7F, 0xC0, 0xFF, 0xBF, 0xE0, 0x1F, 0xEF, 0xF8, 0x07, 0xFB, 0xFE, - 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, - 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, - 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, - 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0xC0, - 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, 0x7F, 0xFF, - 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, - 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, - 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF8, 0x07, 0xFB, 0xFE, 0x01, 0xFE, 0xFF, - 0x80, 0x7F, 0x9F, 0xE0, 0x3F, 0xE7, 0xFC, 0x0F, 0xF9, 0xFF, 0xFF, 0xFC, - 0x3F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, - 0xF0, 0x07, 0xFF, 0xF8, 0x00, 0xFF, 0xF8, 0x00, 0x01, 0xE0, 0x00, 0x00, - 0x7F, 0xE0, 0x0F, 0xFF, 0x01, 0xFF, 0xF0, 0x3F, 0xFF, 0x07, 0xFF, 0xF0, - 0x7F, 0xFF, 0x07, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x00, 0xFF, 0x80, - 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, - 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, - 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, - 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, - 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, - 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, - 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, - 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, - 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, - 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, - 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, 0x80, 0x0F, 0xF8, 0x00, 0xFF, - 0x80, 0x00, 0xFC, 0x00, 0x00, 0x7F, 0xE3, 0xFC, 0x1F, 0xFE, 0x7F, 0x87, - 0xFF, 0xEF, 0xF1, 0xFF, 0xFD, 0xFE, 0x3F, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, - 0xF9, 0xFF, 0x87, 0xFF, 0x3F, 0xE0, 0x7F, 0xEF, 0xF8, 0x07, 0xFD, 0xFF, - 0x00, 0xFF, 0xBF, 0xE0, 0x1F, 0xF7, 0xFC, 0x03, 0xFE, 0xFF, 0x00, 0x7F, - 0xDF, 0xE0, 0x0F, 0xFB, 0xFC, 0x00, 0xFF, 0x7F, 0x80, 0x1F, 0xEF, 0xF0, - 0x03, 0xFD, 0xFE, 0x00, 0x7F, 0xBF, 0xC0, 0x0F, 0xF7, 0xF8, 0x01, 0xFE, - 0xFF, 0x00, 0x3F, 0xDF, 0xE0, 0x07, 0xFB, 0xFC, 0x00, 0xFF, 0x7F, 0x80, - 0x1F, 0xEF, 0xF0, 0x03, 0xFD, 0xFE, 0x00, 0x7F, 0xBF, 0xC0, 0x0F, 0xF7, - 0xF8, 0x01, 0xFE, 0xFF, 0x00, 0x3F, 0xDF, 0xE0, 0x07, 0xFB, 0xFC, 0x00, - 0xFF, 0x7F, 0x80, 0x1F, 0xEF, 0xF0, 0x03, 0xFD, 0xFE, 0x00, 0x7F, 0xBF, - 0xC0, 0x0F, 0xF7, 0xF8, 0x01, 0xFE, 0xFF, 0x00, 0x7F, 0xDF, 0xF0, 0x0F, - 0xFB, 0xFE, 0x01, 0xFF, 0x7F, 0xC0, 0x3F, 0xEF, 0xF8, 0x07, 0xFD, 0xFF, - 0x00, 0xFF, 0xBF, 0xF0, 0x1F, 0xF3, 0xFE, 0x07, 0xFE, 0x7F, 0xF3, 0xFF, - 0xCF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xDF, 0xE1, 0xFF, - 0xFB, 0xFC, 0x1F, 0xFE, 0x7F, 0x81, 0xFF, 0x0F, 0xF0, 0x00, 0x01, 0xFE, - 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF8, 0x00, 0x01, 0xFF, 0x00, 0x00, - 0x3F, 0xE2, 0x00, 0x0F, 0xFC, 0x70, 0x01, 0xFF, 0x1F, 0xC0, 0xFF, 0xE7, - 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, - 0xF0, 0x1F, 0xFF, 0xFC, 0x00, 0xFF, 0xFE, 0x00, 0x01, 0xF8, 0x00, 0x00, - 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, - 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, - 0x0F, 0xF8, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0xFF, - 0x81, 0xFC, 0x0F, 0xF8, 0x7F, 0xF0, 0xFF, 0x8F, 0xFF, 0x8F, 0xF9, 0xFF, - 0xFC, 0xFF, 0xBF, 0xFF, 0xCF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xEF, - 0xFF, 0x9F, 0xFE, 0xFF, 0xE0, 0x3F, 0xEF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, - 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0x7F, 0xDF, - 0xF7, 0xFD, 0xFF, 0x7F, 0xDF, 0xF7, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, - 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, - 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, - 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, - 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, - 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, 0xBF, 0xEF, 0xFB, 0xFE, 0xFF, - 0xBF, 0xEF, 0xF8, 0x07, 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xFC, - 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, - 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, - 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, - 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, - 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, - 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, - 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, - 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, - 0x83, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x83, 0xFE, 0xFF, 0xFB, 0xFF, - 0xEF, 0xFF, 0x3F, 0xFC, 0xFF, 0xE3, 0xFF, 0x07, 0xF0, 0x00, 0xFF, 0x80, - 0x00, 0x03, 0xFE, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, - 0x00, 0xFF, 0x80, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, - 0x3F, 0xE0, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x0F, - 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x03, 0xFE, 0xFF, 0x80, 0x1F, 0xF3, 0xFE, - 0x00, 0x7F, 0xCF, 0xF8, 0x03, 0xFE, 0x3F, 0xE0, 0x0F, 0xF8, 0xFF, 0x80, - 0x3F, 0xC3, 0xFE, 0x01, 0xFF, 0x0F, 0xF8, 0x07, 0xFC, 0x3F, 0xE0, 0x3F, - 0xE0, 0xFF, 0x80, 0xFF, 0x83, 0xFE, 0x03, 0xFC, 0x0F, 0xF8, 0x1F, 0xF0, - 0x3F, 0xE0, 0x7F, 0xC0, 0xFF, 0x83, 0xFE, 0x03, 0xFE, 0x0F, 0xF8, 0x0F, - 0xF8, 0x3F, 0xC0, 0x3F, 0xE1, 0xFF, 0x00, 0xFF, 0x87, 0xFC, 0x03, 0xFE, - 0x3F, 0xE0, 0x0F, 0xF8, 0xFF, 0x80, 0x3F, 0xE3, 0xFC, 0x00, 0xFF, 0x9F, - 0xF0, 0x03, 0xFE, 0x7F, 0x80, 0x0F, 0xFB, 0xFE, 0x00, 0x3F, 0xEF, 0xF8, - 0x00, 0xFF, 0xBF, 0xC0, 0x03, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xC0, 0x03, 0xFE, 0xFF, 0x00, 0x0F, - 0xFB, 0xFE, 0x00, 0x3F, 0xE7, 0xF8, 0x00, 0xFF, 0x9F, 0xF0, 0x03, 0xFE, - 0x7F, 0xC0, 0x0F, 0xF8, 0xFF, 0x00, 0x3F, 0xE3, 0xFE, 0x00, 0xFF, 0x87, - 0xF8, 0x03, 0xFE, 0x1F, 0xF0, 0x0F, 0xF8, 0x7F, 0xC0, 0x3F, 0xE0, 0xFF, - 0x80, 0xFF, 0x83, 0xFE, 0x03, 0xFE, 0x07, 0xF8, 0x0F, 0xF8, 0x1F, 0xF0, - 0x3F, 0xE0, 0x7F, 0xC0, 0xFF, 0x80, 0xFF, 0x83, 0xFE, 0x03, 0xFE, 0x0F, - 0xF8, 0x07, 0xF8, 0x3F, 0xE0, 0x1F, 0xF0, 0xFF, 0x80, 0x7F, 0xC3, 0xFE, - 0x00, 0xFF, 0x8F, 0xF8, 0x03, 0xFE, 0x3F, 0xE0, 0x07, 0xFC, 0xFF, 0x80, - 0x1F, 0xF3, 0xFE, 0x00, 0x7F, 0xCF, 0xF8, 0x00, 0xFF, 0xBF, 0xE0, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFC, 0x00, 0xFF, 0x03, - 0xFE, 0x1F, 0xF8, 0x0F, 0xFF, 0x0F, 0xF9, 0xFF, 0xF8, 0x7F, 0xFE, 0x3F, - 0xE7, 0xFF, 0xE3, 0xFF, 0xFC, 0xFF, 0xBF, 0xFF, 0xDF, 0xFF, 0xF3, 0xFF, - 0xFF, 0xFF, 0x7F, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, - 0x7F, 0xFF, 0xCF, 0xFE, 0xFF, 0xE0, 0x7F, 0xF8, 0x0F, 0xFB, 0xFF, 0x00, - 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x07, - 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0x7F, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFE, - 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xE0, - 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFE, 0x00, - 0x7F, 0xFF, 0xE0, 0x07, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xE0, 0x07, - 0xFF, 0xFE, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFE, 0x00, 0x7F, - 0xFF, 0xE0, 0x07, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xE0, 0x07, 0xFF, - 0xFE, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFE, 0x00, 0x7F, 0xFF, - 0xE0, 0x07, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xE0, 0x07, 0xFF, 0xFE, - 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, - 0x07, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xE0, 0x07, 0xFF, 0xFE, 0x00, - 0x7F, 0x80, 0x1F, 0xF0, 0x00, 0x01, 0xFC, 0x0F, 0xF8, 0x7F, 0xF0, 0xFF, - 0x8F, 0xFF, 0x8F, 0xF9, 0xFF, 0xFC, 0xFF, 0xBF, 0xFF, 0xCF, 0xFB, 0xFF, - 0xFE, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0x8F, 0xFE, 0xFF, 0xE0, 0x3F, 0xEF, - 0xFC, 0x03, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF8, 0x01, 0xFF, 0x00, 0x7F, 0x80, 0x00, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, - 0x80, 0x3F, 0xFF, 0xF0, 0x1F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xC3, 0xFF, - 0xFF, 0xF1, 0xFF, 0x87, 0xFE, 0x7F, 0xC0, 0xFF, 0x9F, 0xE0, 0x3F, 0xEF, - 0xF8, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x1F, - 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, - 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, - 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, - 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, - 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, - 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, - 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, - 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF8, 0x07, 0xFF, - 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xDF, 0xF0, 0x3F, 0xE7, 0xFC, 0x0F, - 0xF9, 0xFF, 0xFF, 0xFE, 0x3F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xC1, 0xFF, - 0xFF, 0xE0, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xF8, 0x00, 0x7F, 0xF8, 0x00, - 0x01, 0xE0, 0x00, 0x00, 0x01, 0xF0, 0x1F, 0xF1, 0xFF, 0xC3, 0xFE, 0x7F, - 0xFC, 0x7F, 0xDF, 0xFF, 0x8F, 0xFB, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xBF, - 0xFF, 0xFF, 0xF7, 0xFF, 0x87, 0xFE, 0xFF, 0xE0, 0x7F, 0xDF, 0xF8, 0x07, - 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, - 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, - 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, - 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, - 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, - 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, - 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, - 0xE0, 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x07, - 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, - 0x00, 0x7F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x3F, - 0xFF, 0xF8, 0x0F, 0xFB, 0xFF, 0x81, 0xFF, 0x7F, 0xFF, 0xFF, 0xEF, 0xFF, - 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xE7, 0xFD, 0xFF, 0xF8, - 0xFF, 0x9F, 0xFE, 0x1F, 0xF1, 0xFF, 0x03, 0xFE, 0x07, 0x00, 0x7F, 0xC0, - 0x00, 0x0F, 0xF8, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x07, - 0xFC, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x1F, 0xF0, 0x00, 0x03, 0xFE, 0x00, - 0x00, 0x7F, 0xC0, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x01, - 0xFF, 0x8F, 0xF1, 0xFF, 0xF3, 0xFC, 0x7F, 0xFE, 0xFF, 0x3F, 0xFF, 0xBF, - 0xDF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFD, 0xFF, 0x8F, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, - 0x80, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x00, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, - 0x00, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, 0xFF, - 0xFC, 0x00, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, 0x03, - 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF0, - 0x03, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, 0xFF, - 0xF0, 0x03, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x0F, - 0xFF, 0xF0, 0x07, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, - 0x1F, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, - 0xE0, 0x3F, 0xF7, 0xFC, 0x1F, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, - 0xCF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFB, 0xFC, 0x7F, 0xFE, 0xFF, 0x0F, 0xFF, - 0x3F, 0xC1, 0xFF, 0x8F, 0xF0, 0x0F, 0x03, 0xFC, 0x00, 0x00, 0xFF, 0x00, - 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0xFF, - 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x0F, 0xF0, 0x00, 0x03, 0xFC, 0x00, 0x00, - 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x01, 0xFF, 0xF0, 0xFF, 0xFE, 0x3F, - 0xFF, 0xCF, 0xFF, 0xFB, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0xFF, 0xF8, 0x1F, 0xFC, 0x03, 0xFF, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, - 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, - 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, - 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, - 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, - 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, - 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, - 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, - 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, - 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x00, 0x00, - 0x7F, 0x80, 0x00, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF0, - 0x1F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xF1, 0xFF, 0xCF, - 0xFC, 0x7F, 0xC0, 0xFF, 0x1F, 0xE0, 0x3F, 0xEF, 0xF8, 0x07, 0xFB, 0xFE, - 0x01, 0xFE, 0xFF, 0x80, 0x7F, 0xBF, 0xE0, 0x1F, 0xEF, 0xF8, 0x07, 0xFB, - 0xFE, 0x01, 0xFE, 0xFF, 0x80, 0x7F, 0x9F, 0xE0, 0x1F, 0xE7, 0xFC, 0x07, - 0xF9, 0xFF, 0x01, 0xFE, 0x7F, 0xE0, 0x00, 0x0F, 0xFC, 0x00, 0x03, 0xFF, - 0x80, 0x00, 0x7F, 0xF0, 0x00, 0x0F, 0xFE, 0x00, 0x03, 0xFF, 0xC0, 0x00, - 0x7F, 0xF8, 0x00, 0x0F, 0xFF, 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x3F, 0xFC, - 0x00, 0x07, 0xFF, 0x80, 0x00, 0xFF, 0xF0, 0x00, 0x1F, 0xFE, 0x00, 0x03, - 0xFF, 0x80, 0x00, 0x7F, 0xF0, 0x00, 0x0F, 0xFC, 0x00, 0x01, 0xFF, 0x80, - 0x00, 0x7F, 0xE7, 0xF8, 0x0F, 0xF9, 0xFE, 0x01, 0xFF, 0x7F, 0x80, 0x7F, - 0xDF, 0xE0, 0x1F, 0xF7, 0xF8, 0x07, 0xFD, 0xFE, 0x00, 0xFF, 0x7F, 0x80, - 0x3F, 0xDF, 0xE0, 0x0F, 0xF7, 0xF8, 0x03, 0xFD, 0xFE, 0x01, 0xFF, 0x7F, - 0xC0, 0x7F, 0xDF, 0xF0, 0x1F, 0xF3, 0xFE, 0x0F, 0xFC, 0xFF, 0xFF, 0xFE, - 0x3F, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, - 0xF8, 0x03, 0xFF, 0xFC, 0x00, 0x7F, 0xFC, 0x00, 0x03, 0xF8, 0x00, 0x07, - 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, - 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, - 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, - 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, - 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, - 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, - 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, - 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, - 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, - 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, - 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, 0x7F, 0xC0, - 0x07, 0xFC, 0x00, 0x7F, 0xC0, 0x07, 0xFF, 0xF0, 0x7F, 0xFF, 0x07, 0xFF, - 0xF0, 0x7F, 0xFF, 0x03, 0xFF, 0xF0, 0x1F, 0xFF, 0x00, 0xFF, 0xF0, 0xFF, - 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, - 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, - 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, - 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, - 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, - 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x01, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, - 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x0F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, - 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x7F, - 0xFF, 0xC0, 0x0F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, - 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, 0xFC, 0x1F, 0xFD, - 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xEF, 0xF7, 0xFF, 0xFD, 0xFE, 0x7F, 0xFF, - 0x3F, 0xC7, 0xFF, 0xE7, 0xF8, 0xFF, 0xF8, 0xFF, 0x07, 0xFE, 0x1F, 0xE0, - 0x1E, 0x00, 0x00, 0xFF, 0x00, 0x1F, 0xFF, 0xE0, 0x03, 0xFF, 0xFC, 0x00, - 0x7F, 0xFF, 0x80, 0x1F, 0xE7, 0xF8, 0x03, 0xFC, 0xFF, 0x00, 0x7F, 0x9F, - 0xE0, 0x0F, 0xF3, 0xFC, 0x01, 0xFE, 0x7F, 0x80, 0x3F, 0xCF, 0xF0, 0x07, - 0xF9, 0xFE, 0x00, 0xFE, 0x1F, 0xE0, 0x1F, 0xC3, 0xFC, 0x07, 0xF8, 0x7F, - 0x80, 0xFF, 0x0F, 0xF0, 0x1F, 0xE1, 0xFE, 0x03, 0xFC, 0x3F, 0xC0, 0x7F, - 0x87, 0xF8, 0x0F, 0xE0, 0x7F, 0x01, 0xFC, 0x0F, 0xF0, 0x3F, 0x81, 0xFE, - 0x07, 0xF0, 0x3F, 0xC1, 0xFE, 0x07, 0xF8, 0x3F, 0xC0, 0xFF, 0x07, 0xF8, - 0x1F, 0xE0, 0xFE, 0x01, 0xFC, 0x1F, 0xC0, 0x3F, 0xC3, 0xF8, 0x07, 0xF8, - 0x7F, 0x00, 0xFF, 0x0F, 0xE0, 0x1F, 0xE3, 0xFC, 0x03, 0xFC, 0x7F, 0x80, - 0x7F, 0x8F, 0xE0, 0x07, 0xF1, 0xFC, 0x00, 0xFE, 0x3F, 0x80, 0x1F, 0xC7, - 0xF0, 0x03, 0xFC, 0xFE, 0x00, 0x7F, 0x9F, 0xC0, 0x0F, 0xF3, 0xF8, 0x01, - 0xFE, 0x7E, 0x00, 0x1F, 0xCF, 0xC0, 0x03, 0xFB, 0xF8, 0x00, 0x7F, 0x7F, - 0x00, 0x0F, 0xEF, 0xE0, 0x01, 0xFD, 0xFC, 0x00, 0x3F, 0xFF, 0x80, 0x07, - 0xFF, 0xE0, 0x00, 0x7F, 0xFC, 0x00, 0x0F, 0xFF, 0x80, 0x01, 0xFF, 0xF0, - 0x00, 0x3F, 0xFE, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0xFF, 0xF8, 0x00, 0x1F, - 0xFE, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x3F, 0xF8, 0x00, 0x07, 0xFF, 0x00, - 0x00, 0xFF, 0xE0, 0x00, 0xFF, 0x00, 0x7F, 0x80, 0x1F, 0xFF, 0xC0, 0x1F, - 0xE0, 0x07, 0xFF, 0xF0, 0x07, 0xF8, 0x03, 0xFF, 0xFC, 0x01, 0xFE, 0x00, - 0xFF, 0x7F, 0x00, 0xFF, 0xC0, 0x3F, 0xDF, 0xC0, 0x3F, 0xF0, 0x0F, 0xE7, - 0xF0, 0x0F, 0xFC, 0x03, 0xF9, 0xFC, 0x03, 0xFF, 0x00, 0xFE, 0x7F, 0x00, - 0xFF, 0xC0, 0x3F, 0x9F, 0xC0, 0x3F, 0xF0, 0x0F, 0xE7, 0xF8, 0x0F, 0xFE, - 0x03, 0xF9, 0xFE, 0x07, 0xFF, 0x80, 0xFE, 0x3F, 0x81, 0xFF, 0xE0, 0x7F, - 0x8F, 0xE0, 0x7F, 0xF8, 0x1F, 0xC3, 0xF8, 0x1F, 0xBE, 0x07, 0xF0, 0xFE, - 0x07, 0xEF, 0x81, 0xFC, 0x3F, 0x81, 0xFB, 0xE0, 0x7F, 0x0F, 0xE0, 0x7E, - 0xFC, 0x1F, 0xC3, 0xF8, 0x3F, 0xBF, 0x07, 0xF0, 0xFE, 0x0F, 0xCF, 0xC1, - 0xFC, 0x1F, 0xC3, 0xF3, 0xF0, 0x7F, 0x07, 0xF0, 0xFC, 0xFC, 0x1F, 0x81, - 0xFC, 0x3F, 0x1F, 0x0F, 0xE0, 0x7F, 0x0F, 0xC7, 0xC3, 0xF8, 0x1F, 0xC3, - 0xF1, 0xF8, 0xFE, 0x07, 0xF1, 0xF8, 0x7E, 0x3F, 0x81, 0xFC, 0x7E, 0x1F, - 0x8F, 0xE0, 0x7F, 0x1F, 0x87, 0xE3, 0xF8, 0x0F, 0xC7, 0xE1, 0xF8, 0xFE, - 0x03, 0xF1, 0xF8, 0x3E, 0x3F, 0x00, 0xFE, 0x7E, 0x0F, 0x8F, 0xC0, 0x3F, - 0x9F, 0x83, 0xF3, 0xF0, 0x0F, 0xE7, 0xC0, 0xFC, 0xFC, 0x03, 0xFB, 0xF0, - 0x3F, 0x3F, 0x00, 0xFE, 0xFC, 0x0F, 0xDF, 0xC0, 0x3F, 0xBF, 0x03, 0xF7, - 0xF0, 0x07, 0xEF, 0xC0, 0xFD, 0xFC, 0x01, 0xFB, 0xF0, 0x1F, 0x7E, 0x00, - 0x7E, 0xFC, 0x07, 0xFF, 0x80, 0x1F, 0xBE, 0x01, 0xFF, 0xE0, 0x07, 0xFF, - 0x80, 0x7F, 0xF8, 0x01, 0xFF, 0xE0, 0x1F, 0xFE, 0x00, 0x7F, 0xF8, 0x07, - 0xFF, 0x80, 0x1F, 0xFE, 0x01, 0xFF, 0xE0, 0x03, 0xFF, 0x80, 0x7F, 0xF8, - 0x00, 0xFF, 0xC0, 0x0F, 0xFC, 0x00, 0x3F, 0xF0, 0x03, 0xFF, 0x00, 0x0F, - 0xFC, 0x00, 0xFF, 0xC0, 0x03, 0xFF, 0x00, 0x3F, 0xF0, 0x00, 0xFF, 0xC0, - 0x0F, 0xFC, 0x00, 0x3F, 0xF0, 0x03, 0xFF, 0x00, 0x0F, 0xFC, 0x00, 0xFF, - 0xC0, 0x01, 0xFE, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0x80, 0x07, 0xF8, 0x00, - 0x1F, 0xE0, 0x01, 0xFE, 0x00, 0x07, 0xF8, 0x00, 0x7F, 0x80, 0x01, 0xFE, - 0x00, 0x1F, 0xE0, 0x00, 0x7F, 0x80, 0x1F, 0xF7, 0xF8, 0x01, 0xFE, 0x3F, - 0xC0, 0x1F, 0xE3, 0xFC, 0x01, 0xFC, 0x3F, 0xC0, 0x3F, 0xC1, 0xFE, 0x03, - 0xFC, 0x1F, 0xE0, 0x3F, 0x81, 0xFE, 0x07, 0xF8, 0x0F, 0xF0, 0x7F, 0x80, - 0xFF, 0x07, 0xF0, 0x0F, 0xF0, 0x7F, 0x00, 0x7F, 0x8F, 0xF0, 0x07, 0xF8, - 0xFE, 0x00, 0x7F, 0x8F, 0xE0, 0x03, 0xFD, 0xFE, 0x00, 0x3F, 0xDF, 0xC0, - 0x03, 0xFD, 0xFC, 0x00, 0x1F, 0xFF, 0xC0, 0x01, 0xFF, 0xF8, 0x00, 0x1F, - 0xFF, 0x80, 0x00, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, - 0x00, 0x07, 0xFF, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x07, 0xFE, 0x00, 0x00, - 0x3F, 0xE0, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x07, 0xFE, - 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x07, 0xFE, 0x00, 0x00, 0xFF, 0xF0, 0x00, - 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0x80, 0x01, 0xFF, - 0xF8, 0x00, 0x1F, 0xFF, 0x80, 0x03, 0xFF, 0xFC, 0x00, 0x3F, 0xBF, 0xC0, - 0x03, 0xFB, 0xFC, 0x00, 0x7F, 0x9F, 0xE0, 0x07, 0xF9, 0xFE, 0x00, 0x7F, - 0x1F, 0xE0, 0x0F, 0xF0, 0xFF, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xE0, 0xFF, - 0x01, 0xFE, 0x07, 0xF8, 0x1F, 0xE0, 0x7F, 0x81, 0xFC, 0x07, 0xFC, 0x3F, - 0xC0, 0x3F, 0xC3, 0xFC, 0x03, 0xFC, 0x3F, 0xC0, 0x3F, 0xE7, 0xF8, 0x01, - 0xFE, 0x7F, 0x80, 0x1F, 0xE7, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x0F, 0xF0, - 0xFF, 0x00, 0x1F, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, - 0x01, 0xFE, 0x7F, 0x80, 0x1F, 0xE7, 0xF8, 0x01, 0xFE, 0x7F, 0x80, 0x1F, - 0xE7, 0xF8, 0x01, 0xFE, 0x7F, 0x80, 0x3F, 0xE7, 0xF8, 0x03, 0xFC, 0x3F, - 0xC0, 0x3F, 0xC3, 0xFC, 0x03, 0xFC, 0x3F, 0xC0, 0x3F, 0xC3, 0xFC, 0x03, - 0xFC, 0x3F, 0xC0, 0x3F, 0xC3, 0xFC, 0x03, 0xF8, 0x1F, 0xC0, 0x7F, 0x81, - 0xFE, 0x07, 0xF8, 0x1F, 0xE0, 0x7F, 0x81, 0xFE, 0x07, 0xF8, 0x1F, 0xE0, - 0x7F, 0x81, 0xFE, 0x07, 0xF0, 0x0F, 0xE0, 0x7F, 0x00, 0xFE, 0x07, 0xF0, - 0x0F, 0xE0, 0x7F, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xF0, 0xFF, 0x00, 0xFF, - 0x0F, 0xE0, 0x07, 0xF0, 0xFE, 0x00, 0x7F, 0x0F, 0xE0, 0x07, 0xF0, 0xFE, - 0x00, 0x7F, 0x0F, 0xE0, 0x07, 0xF0, 0xFE, 0x00, 0x7F, 0x8F, 0xC0, 0x03, - 0xF9, 0xFC, 0x00, 0x3F, 0x9F, 0xC0, 0x03, 0xF9, 0xFC, 0x00, 0x3F, 0x9F, - 0xC0, 0x03, 0xF9, 0xFC, 0x00, 0x3F, 0x9F, 0x80, 0x01, 0xF9, 0xF8, 0x00, - 0x1F, 0x9F, 0x80, 0x01, 0xFD, 0xF8, 0x00, 0x1F, 0xDF, 0x80, 0x01, 0xFD, - 0xF8, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, - 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, - 0xFE, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x7F, 0xE0, - 0x00, 0x07, 0xFE, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x07, 0xFC, 0x00, 0x00, - 0x7F, 0xC0, 0x00, 0x07, 0xFC, 0x00, 0x3F, 0xFF, 0xC0, 0x03, 0xFF, 0xFC, - 0x00, 0x3F, 0xFF, 0x80, 0x03, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0x80, 0x03, - 0xFF, 0xF0, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, - 0xDF, 0xFF, 0xFE, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xBF, 0xFF, 0xFD, 0xFF, - 0xFF, 0xE0, 0x01, 0xFF, 0x00, 0x0F, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xFC, - 0x00, 0x3F, 0xE0, 0x01, 0xFF, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0x80, 0x07, - 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFE, 0x00, 0x1F, 0xF0, 0x00, 0xFF, 0x00, - 0x0F, 0xF8, 0x00, 0x7F, 0xC0, 0x03, 0xFE, 0x00, 0x1F, 0xE0, 0x01, 0xFF, - 0x00, 0x0F, 0xF8, 0x00, 0x7F, 0x80, 0x07, 0xFC, 0x00, 0x3F, 0xE0, 0x01, - 0xFF, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0x80, 0x07, 0xFC, 0x00, 0x3F, 0xC0, - 0x03, 0xFE, 0x00, 0x1F, 0xF0, 0x00, 0xFF, 0x80, 0x07, 0xF8, 0x00, 0x7F, - 0xC0, 0x03, 0xFE, 0x00, 0x1F, 0xE0, 0x01, 0xFF, 0x00, 0x0F, 0xF8, 0x00, - 0x7F, 0xC0, 0x07, 0xFC, 0x00, 0x3F, 0xE0, 0x01, 0xFF, 0x00, 0x0F, 0xF0, - 0x00, 0xFF, 0x80, 0x07, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x0F, 0xF0, 0x03, 0xFF, 0x00, 0xFF, 0xF0, 0x1F, 0xFF, 0x01, - 0xFF, 0xF0, 0x1F, 0xFF, 0x03, 0xFF, 0xF0, 0x3F, 0xF0, 0x03, 0xFE, 0x00, - 0x3F, 0xE0, 0x03, 0xFE, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, - 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, - 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, - 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x07, - 0xFC, 0x00, 0x7F, 0xC0, 0x07, 0xFC, 0x00, 0x7F, 0xC0, 0x0F, 0xFC, 0x0F, - 0xFF, 0x80, 0xFF, 0xF8, 0x0F, 0xFF, 0x00, 0xFF, 0xC0, 0x0F, 0xF8, 0x00, - 0xFF, 0xE0, 0x0F, 0xFF, 0x00, 0xFF, 0xF8, 0x0F, 0xFF, 0x80, 0x0F, 0xFC, - 0x00, 0x7F, 0xC0, 0x07, 0xFC, 0x00, 0x7F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, - 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, - 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, - 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, - 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x03, 0xFE, 0x00, 0x3F, 0xE0, 0x03, 0xFE, - 0x00, 0x3F, 0xF8, 0x03, 0xFF, 0xF0, 0x1F, 0xFF, 0x01, 0xFF, 0xF0, 0x0F, - 0xFF, 0x00, 0x7F, 0xF0, 0x03, 0xFF, 0x00, 0x07, 0xF0, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xFF, 0x00, - 0x1F, 0xF8, 0x03, 0xFF, 0x80, 0x7F, 0xF8, 0x0F, 0xFF, 0x81, 0xFF, 0xF0, - 0x3F, 0xFE, 0x00, 0x7F, 0xC0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, - 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, - 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, - 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, - 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, - 0x1F, 0xF0, 0x03, 0xFF, 0x00, 0x7F, 0xFC, 0x07, 0xFF, 0x80, 0x7F, 0xF0, - 0x07, 0xFE, 0x00, 0x3F, 0xC0, 0x1F, 0xF8, 0x0F, 0xFF, 0x03, 0xFF, 0xE0, - 0x7F, 0xFC, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, - 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, - 0xFE, 0x00, 0x7F, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, - 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xF0, 0x03, 0xFE, 0x00, 0x7F, 0xC0, 0x0F, - 0xF8, 0x01, 0xFF, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x3F, - 0xF0, 0x0F, 0xFC, 0x0F, 0xFF, 0x81, 0xFF, 0xF0, 0x3F, 0xFC, 0x07, 0xFF, - 0x80, 0xFF, 0xE0, 0x1F, 0xF0, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x7C, 0x00, 0x07, 0xE0, 0x7F, 0xF0, 0x01, 0xFC, 0x3F, 0xFF, - 0xC0, 0xFF, 0x1F, 0xFF, 0xFE, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, - 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFE, 0x3F, - 0xFF, 0xFF, 0xFF, 0x8F, 0xF0, 0x3F, 0xFF, 0xC0, 0xF8, 0x01, 0xFF, 0xE0, - 0x0E, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x07, 0xFC, - 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, - 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFE, - 0x01, 0xFF, 0xFF, 0xFE, 0x07, 0xFE, 0x07, 0xFC, 0x0F, 0xF8, 0x0F, 0xF8, - 0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xE0, 0x1F, 0xF0, 0xFF, 0x80, 0x3F, 0xE1, - 0xFF, 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC, 0x00, 0xFF, 0x0F, - 0xF8, 0x01, 0xFE, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F, - 0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC3, 0xFE, - 0x00, 0x7F, 0x87, 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0, - 0x03, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x07, 0xFC, 0x00, - 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, - 0x07, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFE, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x0F, 0xF8, 0x00, 0x00, - 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, - 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC3, 0xFE, 0x00, 0x7F, 0x87, - 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, - 0xE0, 0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, - 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC, 0x01, 0xFF, 0x07, 0xF8, - 0x03, 0xFE, 0x0F, 0xF8, 0x07, 0xFC, 0x1F, 0xF0, 0x0F, 0xF0, 0x3F, 0xE0, - 0x3F, 0xE0, 0x3F, 0xE0, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, - 0xFE, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, - 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0x80, - 0x00, - // euro - 0x00, 0x07, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x00, - 0x07, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFE, 0x07, 0xFE, 0x07, 0xFC, 0x0F, - 0xF8, 0x0F, 0xF8, 0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xE0, 0x1F, 0xF0, 0xFF, - 0x80, 0x3F, 0xE1, 0xFF, 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC, - 0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0, - 0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00, - 0x3F, 0xC3, 0xFE, 0x00, 0x7F, 0x87, 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01, - 0xFE, 0x1F, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0x80, - 0x0F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, - 0x3F, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x0F, - 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F, - 0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC3, 0xFE, - 0x00, 0x7F, 0x87, 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0, - 0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, 0xFF, 0x80, - 0x1F, 0xE1, 0xFF, 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC, 0x01, - 0xFF, 0x0F, 0xF8, 0x03, 0xFE, 0x0F, 0xF8, 0x07, 0xFC, 0x1F, 0xF0, 0x0F, - 0xF0, 0x3F, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, - 0x80, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0x80, 0x00, - // pound - 0x00, 0x0F, 0xFC, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x7F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xC0, 0x7F, - 0xFF, 0xFF, 0x83, 0xFF, 0x03, 0xFE, 0x0F, 0xF8, 0x07, 0xF8, 0x3F, 0xC0, - 0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC7, 0xFC, 0x00, 0xFF, 0x1F, 0xE0, 0x03, - 0xFC, 0x7F, 0x80, 0x0F, 0xF1, 0xFE, 0x00, 0x3F, 0xC7, 0xF8, 0x00, 0xFF, - 0x1F, 0xF0, 0x03, 0xFC, 0x7F, 0xC0, 0x0F, 0xF1, 0xFF, 0x00, 0x3F, 0xC3, - 0xFC, 0x00, 0xFF, 0x0F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0xFF, - 0x80, 0x00, 0x01, 0xFE, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x1F, 0xF0, - 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x03, 0xFC, 0x00, - 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, - 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFE, 0x3F, - 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFE, 0x00, 0x00, 0x0F, - 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x01, 0xFE, - 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x7F, 0xC0, - 0x00, 0x01, 0xFF, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x1F, 0xF0, 0x00, - 0x00, 0x7F, 0xC0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, - 0x1F, 0xF0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x0F, - 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x0F, 0xF8, - 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x1F, 0xF0, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, - 0x7F, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF, - 0xFF, 0xFF, 0xFF, - // yen - 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x07, 0xFB, 0xFE, 0x00, 0x1F, 0xF3, - 0xFC, 0x00, 0x3F, 0xE7, 0xFC, 0x00, 0x7F, 0xCF, 0xF8, 0x00, 0xFF, 0x0F, - 0xF0, 0x03, 0xFE, 0x1F, 0xE0, 0x07, 0xFC, 0x3F, 0xE0, 0x0F, 0xF0, 0x7F, - 0xC0, 0x1F, 0xE0, 0x7F, 0x80, 0x7F, 0xC0, 0xFF, 0x80, 0xFF, 0x81, 0xFF, - 0x01, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFC, 0x07, 0xF8, 0x07, 0xFC, - 0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xC0, 0x0F, 0xF0, 0x7F, 0x80, 0x1F, 0xE0, - 0xFF, 0x00, 0x3F, 0xE3, 0xFC, 0x00, 0x3F, 0xC7, 0xF8, 0x00, 0x7F, 0x8F, - 0xF0, 0x00, 0xFF, 0x9F, 0xE0, 0x00, 0xFF, 0x7F, 0x80, 0x01, 0xFE, 0xFF, - 0x00, 0x03, 0xFD, 0xFE, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, - 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0x00, - 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x7F, 0xFC, 0x00, 0x00, 0xFF, 0xF8, 0x00, - 0x00, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0x80, 0x07, - 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0xF8, 0x3F, - 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0x00, - 0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x07, - 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, - 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, - 0xFF, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, - 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, - 0x00, 0x00, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00, - 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, - 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, 0x00 - }; +const uint8_t Antonio_SemiBold40pt7bBitmaps_Gzip[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe5, 0x5c, + 0xbd, 0x72, 0xec, 0x3a, 0x72, 0x06, 0x8b, 0x5b, 0x66, 0xb2, 0x65, 0xdc, + 0xd0, 0x81, 0x77, 0xf1, 0x0a, 0x1b, 0x3a, 0xb8, 0x75, 0xe0, 0x47, 0x72, + 0xe8, 0xe0, 0x78, 0x40, 0x95, 0x02, 0x85, 0xf3, 0x08, 0x7a, 0x93, 0x15, + 0x55, 0x0a, 0x14, 0xce, 0x23, 0x0c, 0x65, 0x05, 0x0a, 0x05, 0x95, 0x02, + 0x41, 0x25, 0x0a, 0xed, 0xef, 0x6b, 0x90, 0x1c, 0xce, 0x48, 0x3a, 0xd2, + 0xf9, 0xdd, 0x5b, 0xde, 0x39, 0x73, 0x40, 0x70, 0x48, 0x82, 0x0d, 0xa0, + 0x7f, 0xbe, 0xee, 0x06, 0x64, 0x64, 0xfc, 0x3c, 0xe6, 0x70, 0x7a, 0x73, + 0x50, 0xa4, 0xc1, 0xdb, 0x8b, 0x78, 0x58, 0xfc, 0xb5, 0x39, 0xea, 0xff, + 0x7b, 0x2e, 0xcc, 0xf8, 0xb1, 0x4f, 0x59, 0xce, 0x6e, 0xb5, 0x48, 0x22, + 0xb7, 0xd3, 0x99, 0xbb, 0xbc, 0x1b, 0x02, 0x8a, 0xb4, 0x72, 0x27, 0x57, + 0x2c, 0xce, 0xe3, 0x67, 0x63, 0x9a, 0xde, 0xb7, 0x55, 0xb2, 0x9d, 0xc9, + 0x75, 0x34, 0xfe, 0x68, 0x30, 0xb6, 0x0f, 0xa6, 0x8e, 0xae, 0x33, 0x43, + 0x83, 0x1f, 0xaa, 0x64, 0x5c, 0xb7, 0x32, 0x4d, 0xc4, 0x5d, 0x83, 0xed, + 0xcd, 0xaa, 0xc6, 0x0f, 0x6d, 0xe6, 0x63, 0xa6, 0xd6, 0xc7, 0x9a, 0xf1, + 0xb1, 0x6e, 0x7e, 0xac, 0x37, 0x41, 0x1f, 0xcb, 0x35, 0x3a, 0x33, 0xb0, + 0x47, 0x1e, 0xff, 0x37, 0xf8, 0x7f, 0xcf, 0x1f, 0xea, 0xe4, 0xc6, 0xc7, + 0xaa, 0xf9, 0x31, 0x34, 0xbe, 0x7b, 0x6c, 0xa2, 0x89, 0x6f, 0xc3, 0x5d, + 0xed, 0xf0, 0x7a, 0x3b, 0xcd, 0xfc, 0xd8, 0x8e, 0xa6, 0x45, 0x57, 0xda, + 0xb1, 0x2b, 0x69, 0xee, 0xca, 0x30, 0x76, 0xc5, 0x95, 0xae, 0xbc, 0xfe, + 0xd8, 0x72, 0x04, 0x8c, 0xc3, 0xf8, 0x74, 0xa6, 0x8a, 0xc6, 0x7c, 0x7e, + 0x79, 0x12, 0x92, 0x09, 0xd2, 0x7a, 0x89, 0x4e, 0x06, 0x2b, 0x72, 0x2c, + 0x72, 0x07, 0xaa, 0xc2, 0xb9, 0xb8, 0xe8, 0x6f, 0x13, 0x26, 0xa1, 0xca, + 0xd2, 0x86, 0xb3, 0xde, 0xdd, 0xa6, 0x66, 0x3c, 0x39, 0xed, 0xdd, 0xcd, + 0xd0, 0x3c, 0x4a, 0x95, 0x43, 0x1f, 0xd6, 0x83, 0xa9, 0x05, 0xcd, 0xf4, + 0xc6, 0x65, 0xd4, 0x3a, 0x36, 0xe8, 0x04, 0x35, 0x90, 0x06, 0x22, 0xa4, + 0x35, 0x12, 0x8d, 0xe3, 0x4d, 0xad, 0x09, 0xd1, 0xd8, 0xc1, 0x54, 0xac, + 0xdd, 0x24, 0xfb, 0x88, 0x91, 0x0d, 0x6d, 0xd8, 0xf6, 0xee, 0x21, 0x35, + 0xcf, 0xb9, 0xc2, 0x89, 0xdf, 0xf6, 0xf6, 0x21, 0xd5, 0x7a, 0xd2, 0x85, + 0x6d, 0x74, 0x0f, 0xd9, 0x0e, 0x72, 0x99, 0x31, 0x62, 0x27, 0x22, 0x57, + 0x22, 0xd1, 0x4b, 0x6a, 0x64, 0x30, 0x82, 0x0e, 0x0f, 0xc6, 0xfc, 0x0e, + 0x36, 0x31, 0xa6, 0x46, 0x1f, 0xe3, 0x8b, 0x13, 0xbe, 0x46, 0x3f, 0x0d, + 0x48, 0x31, 0x4d, 0x42, 0x6f, 0x05, 0x05, 0xc6, 0xd0, 0x78, 0xb4, 0xb0, + 0xab, 0x60, 0x10, 0x31, 0x04, 0x79, 0xac, 0x3c, 0x4c, 0x95, 0x23, 0xf4, + 0x8a, 0xa3, 0x27, 0x66, 0x59, 0xf1, 0xed, 0x5e, 0x25, 0x2c, 0x2a, 0xf8, + 0x1e, 0x54, 0xf2, 0x9b, 0x15, 0xcc, 0xe2, 0x41, 0xa5, 0x9e, 0x2b, 0xe9, + 0xb0, 0x42, 0x4a, 0xc7, 0x0a, 0x46, 0x91, 0x15, 0x1b, 0xad, 0xf4, 0x5a, + 0xe9, 0x9d, 0xc4, 0x52, 0xc1, 0xc8, 0xb0, 0xe2, 0xba, 0x00, 0x2e, 0x5b, + 0x56, 0x7c, 0x27, 0xb7, 0x59, 0x2b, 0x2d, 0xa6, 0x6c, 0xaf, 0x12, 0x40, + 0x50, 0xa9, 0x54, 0x63, 0x05, 0x17, 0x8c, 0xb0, 0x83, 0x79, 0xaf, 0xc2, + 0x0b, 0x83, 0x56, 0x8e, 0xf2, 0x58, 0x91, 0x5c, 0x6b, 0xc5, 0xa3, 0x92, + 0x4a, 0x65, 0x28, 0x15, 0x8c, 0x73, 0x13, 0x59, 0xb1, 0x12, 0x4b, 0x05, + 0xdc, 0x60, 0xb5, 0xa2, 0x94, 0x6a, 0xc5, 0xec, 0x2a, 0x6e, 0xae, 0x74, + 0x63, 0xc5, 0xcf, 0x95, 0xf6, 0xb0, 0x12, 0xe6, 0x8a, 0x19, 0x2b, 0x32, + 0x55, 0xf2, 0x61, 0xa5, 0xd2, 0x0a, 0xc6, 0x9c, 0xa3, 0xac, 0x15, 0x8e, + 0x32, 0x3b, 0xa4, 0x83, 0x2b, 0x73, 0xe5, 0x38, 0x17, 0x0e, 0x09, 0x13, + 0x63, 0x68, 0xc5, 0x2a, 0x87, 0xa4, 0xc2, 0x0f, 0x73, 0x05, 0x03, 0x4e, + 0x92, 0xc9, 0x57, 0xdd, 0xa8, 0xaa, 0x20, 0x49, 0x36, 0x95, 0x1f, 0x6a, + 0x3e, 0x28, 0xa8, 0x40, 0x9c, 0xf0, 0x03, 0xd9, 0x7f, 0x0b, 0x79, 0x84, + 0x2c, 0x36, 0x54, 0x05, 0x62, 0x20, 0xbe, 0xbd, 0x6b, 0xeb, 0x01, 0x52, + 0x2a, 0xed, 0xca, 0xb8, 0x68, 0xd1, 0x57, 0x68, 0x0c, 0xdf, 0xf9, 0xf1, + 0x16, 0x4a, 0x38, 0x85, 0x59, 0x95, 0xcb, 0x39, 0x5a, 0x49, 0x60, 0xb1, + 0xb0, 0xee, 0x8d, 0xbd, 0x87, 0xf0, 0xe4, 0x40, 0x12, 0x7a, 0xbc, 0x48, + 0xe9, 0x1d, 0xc9, 0x50, 0x9a, 0xd8, 0x51, 0x32, 0xbd, 0x07, 0x71, 0x90, + 0x33, 0x15, 0x39, 0x23, 0xca, 0xf8, 0xe6, 0x37, 0x48, 0xc1, 0xbf, 0x54, + 0x92, 0xab, 0xce, 0x4b, 0xf7, 0x3b, 0x5a, 0xb2, 0x17, 0x39, 0x1c, 0xa7, + 0xb0, 0x8e, 0xe1, 0x34, 0x8a, 0xbb, 0x1e, 0xdc, 0x75, 0x92, 0x23, 0xa8, + 0x57, 0xf0, 0xd1, 0xc5, 0xd0, 0x28, 0x53, 0x65, 0xd7, 0x5b, 0x39, 0x86, + 0xb0, 0xc5, 0xd0, 0xba, 0x01, 0x13, 0x88, 0x1b, 0xd0, 0xb8, 0xef, 0x9a, + 0x01, 0x83, 0x45, 0x4e, 0xf4, 0xd1, 0xf5, 0x8d, 0x80, 0x0f, 0xa4, 0x87, + 0xbc, 0x42, 0x56, 0x9d, 0x74, 0x54, 0x6a, 0x0e, 0xc5, 0x11, 0x94, 0x36, + 0xc5, 0x15, 0x93, 0x9f, 0xd9, 0x2a, 0x18, 0x47, 0x3a, 0x3c, 0xdb, 0x73, + 0x7c, 0x44, 0x02, 0x3e, 0xab, 0xf2, 0xf9, 0xf4, 0xe9, 0xf7, 0xf2, 0x31, + 0x9f, 0x6d, 0x94, 0x4b, 0xf1, 0x83, 0x3c, 0xc8, 0x36, 0x85, 0xf3, 0x6c, + 0x93, 0xef, 0x31, 0x39, 0x3f, 0xf5, 0xe0, 0x22, 0x94, 0xdd, 0x09, 0x14, + 0xb2, 0x1f, 0x42, 0x04, 0xf9, 0xe6, 0xcf, 0xa9, 0x46, 0x4f, 0x7c, 0x82, + 0x7e, 0xbe, 0xc5, 0x1d, 0xcd, 0xa0, 0x77, 0xe8, 0xa1, 0x43, 0x5f, 0x6d, + 0xf4, 0x3f, 0xf2, 0x30, 0x35, 0x7d, 0x2e, 0x76, 0x08, 0xb7, 0x72, 0x96, + 0xf1, 0xda, 0xf5, 0xe0, 0xbb, 0xae, 0x28, 0x1d, 0x32, 0x26, 0xb9, 0xcf, + 0x54, 0x26, 0xfd, 0xe9, 0xcf, 0x4d, 0xd7, 0xa7, 0x4f, 0x76, 0x7d, 0x15, + 0x87, 0xb0, 0x3e, 0x7d, 0x90, 0xff, 0x91, 0xad, 0x48, 0xc2, 0x60, 0x83, + 0x3b, 0x55, 0xdd, 0x62, 0x52, 0x8a, 0xf2, 0xcd, 0xe4, 0x60, 0xa8, 0xf3, + 0x47, 0xd9, 0x86, 0x87, 0xf4, 0x28, 0xee, 0xe4, 0x22, 0xe1, 0xc9, 0xe3, + 0x2a, 0xfe, 0xc7, 0x6f, 0xb6, 0xfd, 0x4f, 0x33, 0x8c, 0xcd, 0xb6, 0x45, + 0x01, 0xad, 0xde, 0xfc, 0xca, 0x8b, 0xcf, 0x07, 0x6f, 0x8e, 0x03, 0x5e, + 0x77, 0x15, 0x3f, 0xbb, 0xe6, 0x82, 0x34, 0xd7, 0xdd, 0x5e, 0x23, 0x71, + 0x6e, 0xcd, 0x94, 0x6f, 0x1e, 0x85, 0xb5, 0x7c, 0x21, 0xa6, 0xf3, 0x17, + 0xac, 0x36, 0x7f, 0x21, 0x91, 0xf3, 0x17, 0x22, 0x38, 0x7f, 0x7d, 0xb7, + 0xfb, 0x86, 0x76, 0xf1, 0x9d, 0x9a, 0x3f, 0x78, 0xc3, 0xa2, 0xf9, 0x7a, + 0xd1, 0x7c, 0xb3, 0x68, 0xde, 0x2e, 0x9a, 0x77, 0x8b, 0xe6, 0xfd, 0x9b, + 0xcd, 0xab, 0x11, 0x84, 0x44, 0x42, 0x41, 0x40, 0xf0, 0x2b, 0x74, 0x32, + 0x08, 0x54, 0x20, 0xcd, 0xd6, 0x10, 0x7a, 0xd9, 0xc0, 0xe2, 0xe1, 0x57, + 0xdf, 0xb9, 0x9b, 0x54, 0x53, 0x31, 0x9f, 0xc1, 0xd6, 0x41, 0x72, 0xd4, + 0xf8, 0x36, 0x42, 0x6d, 0xdb, 0x41, 0x73, 0x02, 0x25, 0xa0, 0x11, 0x08, + 0xde, 0xaf, 0xba, 0x04, 0x93, 0x7b, 0x0f, 0x45, 0x0e, 0xe3, 0xbb, 0x89, + 0x85, 0xc2, 0xde, 0x5f, 0x43, 0x00, 0x0a, 0xf1, 0x7d, 0x61, 0xb0, 0x4a, + 0x1f, 0x88, 0xd4, 0x26, 0xb5, 0xea, 0x41, 0x8c, 0x1c, 0x94, 0x42, 0xb4, + 0xb0, 0xe1, 0xe7, 0x90, 0xf1, 0xc5, 0xe7, 0x5e, 0x52, 0x48, 0x36, 0x55, + 0x60, 0xca, 0x1e, 0x36, 0x5a, 0x70, 0x57, 0x0d, 0x76, 0xec, 0x7e, 0xe1, + 0x4f, 0x34, 0x12, 0x12, 0xa9, 0xed, 0x94, 0xf2, 0x73, 0xe5, 0x37, 0x10, + 0x99, 0x21, 0x65, 0x50, 0xc5, 0xa7, 0x3d, 0xba, 0x8a, 0xee, 0x63, 0x48, + 0x6a, 0xbd, 0xc7, 0xe8, 0x10, 0x61, 0x6c, 0xd0, 0xc7, 0x9e, 0x46, 0x8b, + 0x43, 0xb3, 0xbb, 0x06, 0xf8, 0xd3, 0x71, 0xe6, 0xc1, 0x08, 0x85, 0x35, + 0x12, 0xc1, 0x07, 0xbe, 0x78, 0x31, 0xbf, 0x30, 0x40, 0xa2, 0x9a, 0x77, + 0xfc, 0x82, 0x07, 0x3b, 0xf2, 0x02, 0x1e, 0xf0, 0x91, 0xcf, 0xb8, 0xc4, + 0x67, 0x0e, 0x1e, 0xab, 0xc4, 0x8c, 0x0f, 0x84, 0xd2, 0x76, 0xcf, 0xb6, + 0xc9, 0x85, 0x22, 0x4f, 0x22, 0xcf, 0x20, 0x97, 0x6a, 0x52, 0xe4, 0x4c, + 0x28, 0xe6, 0xbd, 0x36, 0x9a, 0xf7, 0xb8, 0x2b, 0x63, 0x82, 0xae, 0x0f, + 0xb9, 0xeb, 0xf6, 0x8d, 0x19, 0x6f, 0x49, 0x09, 0xde, 0xcb, 0x76, 0x40, + 0x0a, 0x6d, 0x9f, 0x2f, 0x35, 0xd1, 0xdf, 0x40, 0xa4, 0xb0, 0xf9, 0x56, + 0xa1, 0x5b, 0x24, 0x74, 0xab, 0x30, 0xdd, 0x1e, 0x0f, 0xa2, 0x17, 0xbc, + 0x09, 0x14, 0x8e, 0xcf, 0xd3, 0x56, 0xf2, 0x87, 0xb1, 0x41, 0xe9, 0xac, + 0xa4, 0xaa, 0xa8, 0x21, 0x45, 0x67, 0x98, 0x15, 0x89, 0x07, 0x64, 0x6c, + 0x62, 0xf3, 0x98, 0x95, 0xbb, 0x6e, 0xa0, 0x75, 0x67, 0xd1, 0x00, 0x77, + 0xe5, 0xc2, 0x5d, 0xbd, 0x72, 0x57, 0x34, 0xfa, 0x2e, 0xa2, 0x3b, 0xbc, + 0xd6, 0xa8, 0x49, 0x35, 0x04, 0x90, 0xa4, 0x09, 0x16, 0x04, 0x74, 0xe1, + 0xca, 0xa0, 0x26, 0xaf, 0x51, 0x3b, 0x6b, 0xa8, 0xf9, 0x30, 0xdf, 0xb8, + 0x82, 0x81, 0xc4, 0xe9, 0x13, 0x46, 0xf9, 0x16, 0xa3, 0x7e, 0xd6, 0x99, + 0x15, 0x94, 0x21, 0xac, 0xa6, 0xdd, 0xc0, 0x94, 0xa2, 0xb7, 0x40, 0x48, + 0xcd, 0x35, 0xcc, 0xeb, 0x3a, 0x9a, 0x95, 0xef, 0x6a, 0x18, 0x39, 0x7b, + 0x31, 0x18, 0x0f, 0xd3, 0x36, 0xd0, 0x32, 0x41, 0x76, 0x61, 0xee, 0xb2, + 0x8d, 0x75, 0xf2, 0x9d, 0x85, 0x29, 0xf0, 0x30, 0x6d, 0xd0, 0xd1, 0xc0, + 0xd6, 0x9d, 0x83, 0xb9, 0x03, 0xe2, 0x81, 0xb7, 0x00, 0x8b, 0xed, 0x3b, + 0x05, 0x44, 0x35, 0x10, 0x39, 0x14, 0x79, 0xeb, 0xf7, 0x95, 0xdb, 0xde, + 0x47, 0x95, 0x06, 0x07, 0xab, 0x9e, 0x10, 0xc5, 0x97, 0x4e, 0x31, 0xa7, + 0xc0, 0xba, 0x10, 0xb8, 0xb5, 0xc0, 0x10, 0x72, 0x72, 0xef, 0x94, 0x73, + 0x07, 0x68, 0x1b, 0xdc, 0x39, 0xf3, 0x48, 0x3d, 0xe8, 0x04, 0xb2, 0xef, + 0xcb, 0x0b, 0x80, 0xd4, 0xcf, 0x13, 0xbb, 0xcf, 0x4d, 0x6d, 0x86, 0x70, + 0x33, 0xb8, 0xfb, 0x81, 0x57, 0x07, 0x21, 0xcc, 0x96, 0x75, 0x1b, 0xde, + 0x6f, 0xac, 0x5c, 0x18, 0x25, 0x00, 0xa6, 0x5b, 0x88, 0x35, 0xad, 0xe2, + 0x6f, 0x88, 0x46, 0xad, 0x4c, 0xd8, 0x12, 0xa7, 0x9f, 0x41, 0x9e, 0x92, + 0xbf, 0x51, 0x87, 0xa6, 0xbc, 0x19, 0x93, 0x0a, 0x0c, 0x91, 0x38, 0xa1, + 0x70, 0x36, 0x54, 0x16, 0x96, 0x7a, 0xd1, 0x2b, 0xe7, 0x52, 0x2f, 0x42, + 0x1a, 0xd7, 0xe4, 0x5c, 0xb3, 0xd0, 0x8b, 0xa7, 0x45, 0x21, 0x81, 0x65, + 0xb6, 0x11, 0x2e, 0x07, 0xa0, 0x26, 0x99, 0x5a, 0xb9, 0x02, 0x62, 0xd2, + 0x8c, 0x56, 0x8e, 0xac, 0x87, 0x69, 0xd8, 0x8e, 0x2d, 0xb1, 0xaf, 0x37, + 0xc5, 0xbf, 0xda, 0x8a, 0x7f, 0x62, 0x37, 0x27, 0x51, 0x38, 0x60, 0xca, + 0xfc, 0x36, 0xbf, 0xce, 0x97, 0x00, 0x85, 0xe1, 0x8a, 0x34, 0x60, 0x24, + 0x8c, 0x16, 0x9b, 0x79, 0x06, 0x85, 0x67, 0xd1, 0x5d, 0xc3, 0x56, 0x07, + 0x9d, 0x1c, 0xe8, 0x45, 0xda, 0x57, 0x34, 0x42, 0x85, 0x61, 0x44, 0x6d, + 0xc9, 0x5b, 0x3c, 0x10, 0x8b, 0xb4, 0xed, 0xc4, 0x67, 0x98, 0xa4, 0x90, + 0x5d, 0xb1, 0xa3, 0x74, 0xed, 0xdd, 0xe5, 0xe7, 0xbb, 0xf4, 0xd0, 0x7e, + 0xf9, 0xe6, 0x5e, 0xf9, 0x67, 0xd1, 0x72, 0x19, 0xa4, 0xf1, 0x19, 0x97, + 0xe6, 0x67, 0x46, 0xa1, 0x15, 0x15, 0xae, 0x7a, 0x41, 0x14, 0x06, 0x76, + 0x71, 0xd8, 0x7b, 0xb9, 0x8c, 0xed, 0xed, 0x1d, 0x54, 0xcb, 0x71, 0x00, + 0xa3, 0x9d, 0x6d, 0x9d, 0x23, 0xc3, 0x62, 0x80, 0xba, 0x00, 0x4b, 0xf2, + 0x94, 0xa7, 0x29, 0x78, 0x2e, 0xb6, 0xee, 0x85, 0xfd, 0xd9, 0x5d, 0x2a, + 0xf6, 0x87, 0x4f, 0xdd, 0x01, 0x93, 0xbb, 0x3e, 0x5c, 0x64, 0x47, 0x26, + 0xa9, 0xf5, 0x81, 0x4e, 0xf5, 0xb4, 0xd0, 0x9d, 0x71, 0x49, 0x21, 0x1b, + 0xb0, 0xa5, 0xc3, 0x64, 0x0c, 0x35, 0xbd, 0xd1, 0x2d, 0xdf, 0x65, 0xa6, + 0xe9, 0x7e, 0x67, 0x76, 0x95, 0x8c, 0xc2, 0x5a, 0x85, 0x42, 0x68, 0xa3, + 0xc1, 0x92, 0x6b, 0x66, 0x6d, 0x74, 0x68, 0xeb, 0x42, 0xb7, 0x6f, 0xd4, + 0xfd, 0x74, 0x6f, 0x1e, 0xd5, 0x6e, 0x9e, 0x8c, 0x7a, 0x2e, 0xd6, 0xf4, + 0x6b, 0x6c, 0xf0, 0xc3, 0xfc, 0xd4, 0xd3, 0xa0, 0x0e, 0x30, 0xbc, 0xec, + 0x01, 0xdd, 0x3b, 0x55, 0x61, 0x22, 0x53, 0x5f, 0xaa, 0x40, 0x09, 0x5e, + 0xde, 0x87, 0x87, 0xc1, 0xc4, 0x17, 0x7a, 0x1c, 0x3d, 0xe7, 0x53, 0xca, + 0xa1, 0x70, 0xa5, 0x17, 0x83, 0x52, 0x41, 0x40, 0xfc, 0x65, 0x6a, 0xa0, + 0xf5, 0xe8, 0xcd, 0x5f, 0x03, 0xca, 0xfa, 0xd2, 0x92, 0xd0, 0xf8, 0x51, + 0xb0, 0x3b, 0x5b, 0x3a, 0x0a, 0x07, 0x64, 0x9f, 0x61, 0xdf, 0xf8, 0xd8, + 0xb7, 0x59, 0xfc, 0x8d, 0x0f, 0x08, 0x0b, 0xdb, 0x87, 0xb1, 0x60, 0xb4, + 0x87, 0x71, 0x9b, 0xf8, 0xd9, 0x5b, 0xc0, 0xc2, 0x95, 0xc3, 0x0d, 0x7f, + 0x32, 0xe6, 0x6f, 0xa6, 0xea, 0x18, 0x8b, 0x18, 0x54, 0x05, 0x76, 0x0d, + 0xa6, 0x0e, 0x33, 0x0e, 0x13, 0x7d, 0x44, 0x3a, 0x5b, 0x4a, 0x5a, 0xe7, + 0x31, 0x60, 0x3d, 0xba, 0x6d, 0x19, 0x14, 0x31, 0xb4, 0x52, 0xb4, 0x04, + 0xf8, 0x35, 0xe8, 0x97, 0xf3, 0x8c, 0x11, 0x4c, 0x6a, 0xf7, 0x3b, 0x5c, + 0x24, 0x72, 0x8c, 0xd4, 0x3e, 0xd6, 0x98, 0x3f, 0x1b, 0xf3, 0xdb, 0x4b, + 0x92, 0xfb, 0x03, 0x4a, 0xc3, 0xfe, 0xe5, 0xdc, 0x9a, 0x5a, 0x23, 0x0a, + 0x9f, 0x08, 0xee, 0xf8, 0xe6, 0x81, 0x76, 0x87, 0xaf, 0xc0, 0x7c, 0x82, + 0x9f, 0x30, 0x89, 0xbd, 0x7e, 0x31, 0x88, 0xbc, 0x48, 0x43, 0x0f, 0x6d, + 0xcf, 0x37, 0x3b, 0x12, 0x01, 0xcb, 0xa4, 0x94, 0x07, 0xa8, 0xc2, 0x3e, + 0x80, 0x99, 0x23, 0x09, 0xa6, 0xe3, 0x54, 0x33, 0x62, 0xc4, 0x76, 0x7b, + 0xbe, 0xc2, 0x68, 0x6f, 0xa8, 0x59, 0x1d, 0x3d, 0x27, 0xcc, 0x11, 0xe6, + 0x3d, 0x07, 0x45, 0x2b, 0xfc, 0x1e, 0x8d, 0x5f, 0x79, 0xef, 0x5b, 0x60, + 0xc8, 0xee, 0x3b, 0xc2, 0x93, 0x4c, 0x99, 0x26, 0x7d, 0x80, 0x6c, 0x69, + 0x22, 0x9b, 0x83, 0xdc, 0xec, 0x83, 0xeb, 0xe5, 0xf7, 0xcd, 0xd9, 0x7c, + 0xe3, 0xfe, 0xc5, 0x83, 0x54, 0x20, 0x8a, 0x09, 0xe8, 0x80, 0xaa, 0xd3, + 0x2c, 0xf4, 0x89, 0x21, 0x58, 0x54, 0x37, 0x42, 0xc8, 0x1f, 0x62, 0x35, + 0xe1, 0xa4, 0x82, 0x2b, 0x6a, 0x0d, 0xba, 0x34, 0x1a, 0x9e, 0x61, 0x59, + 0x47, 0x9a, 0xc6, 0x9a, 0xee, 0x30, 0x58, 0x93, 0x7e, 0x90, 0x19, 0x4a, + 0x09, 0x33, 0x9d, 0x51, 0x42, 0xa2, 0xbb, 0x95, 0xa7, 0x24, 0xad, 0x02, + 0xde, 0xc5, 0x52, 0xb4, 0xcc, 0x75, 0xfc, 0x14, 0x60, 0x99, 0xa3, 0x5f, + 0x55, 0x09, 0x65, 0xae, 0x4b, 0x19, 0x17, 0x65, 0xcf, 0xb2, 0x39, 0x2c, + 0x87, 0xef, 0x28, 0xd1, 0x42, 0xb3, 0x57, 0x7e, 0xd2, 0x72, 0x05, 0x17, + 0x9e, 0x25, 0xbb, 0xb3, 0x5a, 0xd5, 0xf1, 0x2f, 0xf0, 0xdb, 0xaa, 0xf4, + 0x97, 0x67, 0x94, 0xb0, 0x54, 0x09, 0x34, 0x27, 0x81, 0x73, 0x8f, 0x1a, + 0x40, 0x04, 0xfe, 0xc3, 0xeb, 0xf7, 0xbd, 0xeb, 0x1c, 0xb0, 0x47, 0xf1, + 0xfe, 0x4a, 0xa0, 0xa1, 0x00, 0x85, 0x12, 0x98, 0x30, 0xe3, 0xec, 0xd4, + 0x3a, 0xca, 0x1c, 0x3d, 0xd3, 0x12, 0x15, 0x35, 0x2d, 0xc5, 0x58, 0x75, + 0x55, 0x89, 0x3e, 0x88, 0x42, 0x25, 0x51, 0xfc, 0x24, 0x1a, 0x08, 0xe0, + 0x2c, 0x10, 0xdf, 0xcd, 0xfc, 0x9e, 0x14, 0x44, 0x55, 0x1a, 0x4c, 0x40, + 0x0b, 0x34, 0x18, 0x04, 0x7b, 0x9c, 0x20, 0xc2, 0x01, 0x1a, 0x7f, 0xa8, + 0x40, 0xc5, 0x59, 0x04, 0x57, 0x34, 0xe2, 0x68, 0xff, 0xac, 0x65, 0x38, + 0xa8, 0xca, 0xe0, 0xac, 0x67, 0xb0, 0x16, 0xd1, 0xd5, 0x0d, 0x60, 0x22, + 0x10, 0x16, 0x63, 0x14, 0xc2, 0x18, 0x0e, 0xa0, 0x15, 0x00, 0x0b, 0xf5, + 0x8e, 0xbd, 0xca, 0xc6, 0x03, 0x62, 0x85, 0x63, 0xf0, 0x3d, 0x63, 0x41, + 0xc0, 0x56, 0x83, 0xc3, 0x4c, 0x03, 0x5f, 0xf5, 0x81, 0xc0, 0x09, 0xb6, + 0x2d, 0x33, 0x12, 0x53, 0x11, 0x5c, 0x0d, 0x0d, 0xb4, 0x95, 0x8d, 0x50, + 0xee, 0x40, 0x5c, 0xb0, 0xee, 0xe5, 0x86, 0x23, 0x46, 0x90, 0xa0, 0xb6, + 0x1b, 0xe0, 0x03, 0x74, 0x2c, 0xf9, 0xa2, 0x12, 0xe9, 0x43, 0x42, 0x0d, + 0xcb, 0x85, 0x68, 0x74, 0x90, 0xe6, 0x27, 0x7b, 0xb0, 0x52, 0x68, 0x09, + 0x23, 0x80, 0x76, 0x34, 0x60, 0xe2, 0x48, 0xdc, 0x96, 0xc8, 0xaa, 0xd7, + 0xb8, 0x8c, 0x50, 0x34, 0xb2, 0xc6, 0x69, 0x40, 0xa0, 0x9a, 0x2f, 0xa0, + 0x37, 0xa2, 0x08, 0xb6, 0xb7, 0x56, 0xb4, 0x45, 0xb5, 0x3e, 0x40, 0x8b, + 0x6e, 0xa8, 0x9d, 0xa9, 0x93, 0x3f, 0x7a, 0xd8, 0x10, 0xc1, 0xf0, 0xd0, + 0xf0, 0xf1, 0xb1, 0x31, 0x3b, 0xa3, 0x85, 0x7e, 0x7a, 0x43, 0x2b, 0xe5, + 0x96, 0xf1, 0x39, 0x58, 0xae, 0x4a, 0xad, 0xd4, 0xb7, 0x1d, 0xc6, 0x56, + 0xc2, 0x6d, 0x52, 0x4b, 0xb6, 0x29, 0x30, 0x68, 0xad, 0x3d, 0xca, 0xa5, + 0x7f, 0xbd, 0x9a, 0x0b, 0xff, 0x25, 0xf7, 0x61, 0x67, 0xb0, 0x97, 0xc6, + 0xea, 0x63, 0xe8, 0x49, 0x61, 0xc9, 0x88, 0x5a, 0x46, 0xd7, 0x60, 0x84, + 0x24, 0xaf, 0x5e, 0xfa, 0x20, 0x1c, 0xdb, 0xb3, 0x99, 0xdb, 0xa5, 0xfb, + 0xb0, 0xe7, 0x9c, 0xe6, 0x7d, 0x83, 0xfd, 0xce, 0x9c, 0xee, 0x4d, 0xd8, + 0x77, 0x0e, 0xfc, 0x0f, 0x3f, 0x14, 0x92, 0x16, 0x04, 0x86, 0xcd, 0x3c, + 0xa7, 0x23, 0x2b, 0xc5, 0xc2, 0x4a, 0x6d, 0xe1, 0x65, 0x38, 0x8a, 0xb7, + 0xf4, 0x10, 0xf3, 0x5c, 0xed, 0x69, 0x4d, 0x32, 0x91, 0x81, 0xff, 0xea, + 0xea, 0x8b, 0xc6, 0x50, 0xfd, 0xe6, 0xc6, 0x4a, 0xf5, 0x55, 0xb0, 0xf0, + 0xaa, 0xbb, 0xf4, 0xad, 0x2f, 0xc8, 0x3f, 0x7a, 0x14, 0xbe, 0x50, 0xdd, + 0xb9, 0x32, 0xf0, 0x82, 0x68, 0x74, 0xc7, 0x10, 0xcf, 0xb5, 0xa2, 0x41, + 0x4c, 0x96, 0xfa, 0x2b, 0x33, 0xe4, 0x9b, 0xf9, 0x99, 0x11, 0x5b, 0x6a, + 0x9c, 0x20, 0xad, 0xd5, 0xc0, 0x04, 0x55, 0xa9, 0x3a, 0x48, 0x52, 0x82, + 0xb9, 0xaf, 0x8a, 0x4a, 0x4d, 0xcc, 0xa7, 0xb3, 0x7d, 0x54, 0x9c, 0xa6, + 0x46, 0x87, 0x2e, 0xbd, 0xdf, 0xe0, 0x7c, 0xe9, 0x40, 0xac, 0x88, 0x3c, + 0x0b, 0x50, 0xf4, 0x0f, 0xd9, 0x02, 0x63, 0x80, 0xb1, 0x32, 0x18, 0x6b, + 0xeb, 0xe4, 0xfe, 0x4a, 0xf2, 0x67, 0x07, 0xcf, 0x50, 0xba, 0x58, 0x11, + 0x60, 0xfd, 0x74, 0x8e, 0x7f, 0xeb, 0xf3, 0x8b, 0xc5, 0x2e, 0xbe, 0x4d, + 0xc9, 0x57, 0x7f, 0x8a, 0x9a, 0x05, 0x10, 0x6c, 0x86, 0x39, 0xac, 0xf2, + 0x0f, 0x39, 0x21, 0x7f, 0x52, 0x2f, 0xc2, 0x71, 0xef, 0xdf, 0x3f, 0x61, + 0x16, 0x2e, 0x3a, 0x38, 0x42, 0x8f, 0x72, 0x42, 0x9f, 0xc3, 0x6a, 0xcc, + 0x0c, 0xbc, 0x97, 0x54, 0xfb, 0x66, 0x4d, 0x2d, 0x30, 0xb0, 0x66, 0xc9, + 0x68, 0xd4, 0x99, 0x0c, 0xb4, 0x61, 0xec, 0xee, 0xc1, 0xed, 0x6b, 0x28, + 0x2a, 0xdf, 0xc3, 0x29, 0x6b, 0xe1, 0xb6, 0x55, 0x62, 0x61, 0xd8, 0xa0, + 0xa4, 0x7b, 0x68, 0xa8, 0xe3, 0x5c, 0x6b, 0x7c, 0x1d, 0x4e, 0x7e, 0x37, + 0x9e, 0x30, 0xea, 0xee, 0xaf, 0xc0, 0x81, 0x27, 0xf0, 0x53, 0x5c, 0xe4, + 0x53, 0xf0, 0x4b, 0x4a, 0x23, 0xa1, 0x63, 0x20, 0xc0, 0xc3, 0x5e, 0x83, + 0x3f, 0xa9, 0xfc, 0xf0, 0x72, 0x3a, 0x98, 0x14, 0x28, 0x4a, 0x53, 0x39, + 0xdd, 0x5d, 0xd5, 0x9b, 0x41, 0xd5, 0x69, 0x9c, 0x08, 0xf0, 0xd7, 0x8b, + 0x96, 0xc3, 0x8e, 0x88, 0x66, 0x18, 0xc9, 0xd8, 0x51, 0x65, 0x41, 0x24, + 0xa6, 0x9e, 0x34, 0xcf, 0x5d, 0x18, 0x7b, 0x04, 0x31, 0xae, 0xa6, 0x77, + 0x3c, 0x65, 0xcd, 0x1a, 0x82, 0xfe, 0x9f, 0xa1, 0x4e, 0x7e, 0x78, 0xf5, + 0xe5, 0xa7, 0x53, 0x74, 0xa7, 0x30, 0x8f, 0x58, 0x5c, 0x41, 0x78, 0x20, + 0x06, 0xf7, 0xa2, 0x58, 0xaf, 0x60, 0xc5, 0x9e, 0x50, 0x51, 0x23, 0x8f, + 0x4c, 0xd4, 0x2a, 0x3c, 0x52, 0x9c, 0xa4, 0xf9, 0x2b, 0x8d, 0x3d, 0xb2, + 0x09, 0xcf, 0x7b, 0x1c, 0xef, 0x71, 0xbc, 0x07, 0x8f, 0x9c, 0xb5, 0x8d, + 0x94, 0xd9, 0x91, 0xbe, 0x82, 0xea, 0x8d, 0xd5, 0xb3, 0x3c, 0x27, 0x93, + 0x05, 0xe8, 0x30, 0x04, 0xc0, 0x44, 0x7f, 0x86, 0x7b, 0xdc, 0xa9, 0x6c, + 0x3b, 0xbb, 0x91, 0x9b, 0xde, 0xde, 0xc8, 0x7d, 0x6a, 0xee, 0xe5, 0x71, + 0xa8, 0x1f, 0x65, 0xc8, 0x55, 0x82, 0xda, 0x34, 0x30, 0xe6, 0xae, 0x05, + 0xdb, 0xad, 0xbb, 0xe0, 0xe4, 0x32, 0xfa, 0xb5, 0x5c, 0x27, 0x77, 0x22, + 0x77, 0x83, 0xbd, 0x90, 0xb4, 0x6a, 0xae, 0xe0, 0x11, 0x35, 0x77, 0x92, + 0xdd, 0x05, 0x14, 0x95, 0xbd, 0x02, 0xf2, 0x3b, 0x89, 0xf0, 0x91, 0xce, + 0xe1, 0x3d, 0x4a, 0x3f, 0x04, 0xa8, 0xbf, 0xe0, 0x4f, 0x00, 0x0a, 0x4f, + 0x8f, 0xe1, 0x3d, 0x6d, 0xce, 0xa1, 0x25, 0x6f, 0x80, 0x0b, 0xea, 0x7b, + 0xa8, 0xe6, 0x23, 0x62, 0xc5, 0x4e, 0x12, 0x6e, 0x0c, 0x50, 0xa3, 0xd1, + 0x41, 0x83, 0x82, 0xa5, 0x6b, 0x06, 0xa7, 0x8e, 0x18, 0x5e, 0xe8, 0x08, + 0x85, 0x3a, 0x68, 0x4a, 0x30, 0x0b, 0x5c, 0x4c, 0x78, 0x9c, 0xbd, 0xc3, + 0x0d, 0xd1, 0x32, 0x7a, 0xd5, 0xc0, 0x07, 0x05, 0xea, 0x24, 0xff, 0x97, + 0x08, 0x71, 0xcb, 0x18, 0x31, 0xf3, 0x67, 0x80, 0xe8, 0x3d, 0xa0, 0xaf, + 0x46, 0x47, 0xbb, 0x4a, 0x87, 0x82, 0x59, 0x74, 0xe5, 0x52, 0xa7, 0x4a, + 0x18, 0xba, 0x98, 0x7a, 0x97, 0x1a, 0xa6, 0xb3, 0x0a, 0x64, 0x68, 0x10, + 0x60, 0x17, 0xd8, 0x12, 0x6d, 0x07, 0x9a, 0x21, 0x0a, 0xa3, 0x1b, 0x8e, + 0xf7, 0x53, 0xd5, 0x83, 0x4a, 0xd2, 0x13, 0x1a, 0x79, 0x02, 0xd5, 0xdb, + 0x0e, 0x43, 0xd3, 0x60, 0xb4, 0xbc, 0x9c, 0x9e, 0x0b, 0xbb, 0x79, 0x8d, + 0x9e, 0x3b, 0x8c, 0x08, 0x06, 0x09, 0x23, 0x91, 0xdd, 0x0d, 0x3b, 0x2e, + 0xc7, 0x18, 0x47, 0x77, 0x8b, 0xfe, 0x05, 0x39, 0x7e, 0xd2, 0xc4, 0x5b, + 0xbf, 0x68, 0x2e, 0x68, 0x0c, 0x05, 0xdd, 0xa2, 0xb8, 0x33, 0x88, 0xd2, + 0xbb, 0x25, 0x31, 0xc4, 0x5e, 0xc4, 0x59, 0xad, 0x53, 0x7b, 0xd1, 0x4f, + 0xf4, 0x6b, 0x57, 0x14, 0x2b, 0xd2, 0x8a, 0xd4, 0x3a, 0xe1, 0xbd, 0x59, + 0xcd, 0xf9, 0x8d, 0x37, 0x8c, 0x9f, 0x7d, 0x7a, 0x35, 0xde, 0xf1, 0x51, + 0x5b, 0xf5, 0x83, 0x2f, 0xbd, 0x84, 0x90, 0x63, 0xd8, 0xe5, 0xac, 0x40, + 0x48, 0x51, 0x64, 0x78, 0x55, 0xb2, 0x95, 0xef, 0x43, 0x48, 0x45, 0x61, + 0x6f, 0x40, 0xc8, 0xfc, 0x55, 0x06, 0xa7, 0x3c, 0xb0, 0xf3, 0x1c, 0x9a, + 0x4c, 0x7f, 0x60, 0x7c, 0xc3, 0xa1, 0xcb, 0xd0, 0x4a, 0x9e, 0x43, 0x7f, + 0x3f, 0xe1, 0x60, 0xfe, 0x49, 0xe6, 0x54, 0x8a, 0x0f, 0xbd, 0x77, 0xa8, + 0xca, 0xba, 0x0e, 0x2e, 0x85, 0xf9, 0x77, 0xb8, 0xd2, 0xa6, 0x48, 0x63, + 0x5f, 0x86, 0x3f, 0xc9, 0xec, 0x48, 0x95, 0x19, 0xfa, 0xfa, 0xe9, 0x7e, + 0x31, 0xcf, 0xfb, 0x13, 0xdc, 0x2d, 0x5e, 0xa4, 0x7c, 0xe5, 0x37, 0x7b, + 0x6f, 0xd8, 0x3f, 0xfc, 0x58, 0xc8, 0xb3, 0x17, 0xbd, 0xad, 0x8b, 0x33, + 0xe8, 0x38, 0xeb, 0xdb, 0x01, 0xa6, 0x6e, 0x1d, 0xed, 0x7d, 0x1e, 0xe3, + 0x8a, 0xf4, 0xf1, 0xc3, 0x59, 0x67, 0x1f, 0xc6, 0xa0, 0xf8, 0x22, 0x22, + 0x4f, 0x57, 0x37, 0x9c, 0x26, 0x7b, 0x5f, 0x32, 0x44, 0x8d, 0x06, 0x76, + 0x34, 0x3d, 0x23, 0xf3, 0xc1, 0x89, 0x66, 0x66, 0xa6, 0x43, 0xd6, 0x68, + 0x83, 0x1e, 0x3a, 0xc6, 0xa3, 0x18, 0x06, 0x72, 0x4f, 0x5c, 0x07, 0x34, + 0xc5, 0x30, 0x5f, 0x4c, 0x6d, 0xf1, 0x0e, 0xa7, 0x59, 0x9f, 0xa2, 0xa4, + 0x05, 0xc6, 0xee, 0x32, 0x95, 0x7b, 0x11, 0xf9, 0x37, 0x82, 0x9a, 0xd3, + 0xa7, 0x65, 0x62, 0x4b, 0xc3, 0x62, 0x03, 0xc5, 0xc0, 0x71, 0x79, 0x06, + 0x41, 0xd3, 0x1f, 0xf2, 0xe7, 0x3f, 0x82, 0x57, 0xfa, 0x7d, 0xde, 0xec, + 0x2c, 0x04, 0x98, 0xe3, 0x9b, 0xac, 0xf9, 0x93, 0x0b, 0x66, 0x7e, 0x8a, + 0xba, 0x29, 0xc6, 0xb1, 0x63, 0xa4, 0xa9, 0x1a, 0x4c, 0x89, 0xcd, 0xd0, + 0x59, 0x21, 0xd8, 0x63, 0xd6, 0x47, 0x43, 0x37, 0xa7, 0x3d, 0xf3, 0x43, + 0xd5, 0x18, 0xd9, 0x21, 0xf0, 0x73, 0xd7, 0x63, 0x5c, 0xe9, 0x04, 0x73, + 0xcc, 0xa5, 0x45, 0xae, 0xaf, 0x07, 0x82, 0x40, 0x70, 0x66, 0xff, 0x4a, + 0xd8, 0x08, 0xa8, 0x34, 0xda, 0xe8, 0xfa, 0x29, 0xae, 0x14, 0x5a, 0xd1, + 0xa0, 0x13, 0x6e, 0x00, 0x3c, 0x8c, 0xb8, 0xa1, 0xc4, 0xa4, 0xb8, 0xd2, + 0xa5, 0x07, 0x9f, 0x9f, 0x83, 0xf1, 0xb4, 0x79, 0x30, 0x12, 0xd7, 0x29, + 0xb9, 0x5d, 0x44, 0xeb, 0x6e, 0x8a, 0x2b, 0x61, 0xb6, 0x36, 0x98, 0xae, + 0x53, 0xcc, 0x17, 0x57, 0xeb, 0x80, 0xf6, 0xe7, 0x31, 0xae, 0x34, 0x86, + 0xcb, 0x9c, 0xe6, 0x23, 0x17, 0x11, 0xb5, 0xa4, 0x52, 0xc0, 0xfc, 0x6a, + 0x33, 0x07, 0xdc, 0xca, 0xba, 0x1d, 0x9f, 0x54, 0x78, 0x0c, 0xbd, 0x60, + 0xc3, 0x95, 0x36, 0x5a, 0xe6, 0x65, 0x19, 0xda, 0xb1, 0x4c, 0xbb, 0xd2, + 0xbd, 0x56, 0x0e, 0x2c, 0x6b, 0xc6, 0x48, 0x07, 0x94, 0x5d, 0x29, 0xfd, + 0xdb, 0x25, 0xfa, 0xcd, 0x30, 0xa9, 0x1b, 0xcb, 0xb0, 0x2a, 0x65, 0x93, + 0x30, 0x92, 0x8b, 0x72, 0x55, 0xca, 0xc0, 0x45, 0x4a, 0x28, 0xa3, 0xed, + 0xb3, 0x7f, 0xb5, 0xe4, 0xaa, 0x4e, 0x2d, 0xd7, 0x63, 0xd9, 0xdc, 0x0d, + 0x6e, 0xdd, 0xef, 0x97, 0x69, 0xaf, 0xdc, 0xf4, 0xcd, 0xa3, 0x96, 0x4f, + 0xc9, 0x6e, 0xfa, 0x1a, 0xe5, 0xb6, 0x94, 0x5d, 0xfd, 0x14, 0xf7, 0x4a, + 0x99, 0x4a, 0x6a, 0x87, 0xb1, 0xac, 0xc6, 0xb2, 0x9f, 0xca, 0xf6, 0xa0, + 0xac, 0xf7, 0x4b, 0xc3, 0x07, 0x97, 0xa5, 0x39, 0x28, 0xab, 0x5d, 0x49, + 0x49, 0xd4, 0xec, 0x0f, 0x17, 0x5e, 0xb9, 0x1b, 0x1d, 0x63, 0x7f, 0x9d, + 0x74, 0xe8, 0x2e, 0x30, 0x82, 0x18, 0xa9, 0xa3, 0xac, 0x83, 0xc4, 0x83, + 0x05, 0xef, 0x08, 0x3b, 0x0f, 0xf6, 0x03, 0x47, 0x3d, 0x66, 0xf2, 0x6e, + 0x8d, 0x83, 0x07, 0xf5, 0x0a, 0xa3, 0x94, 0xd3, 0x9d, 0x68, 0xb4, 0x76, + 0x5c, 0x1c, 0x69, 0x8b, 0xe2, 0x6c, 0x64, 0x2f, 0x0d, 0x37, 0x26, 0xf2, + 0xb2, 0xf2, 0x84, 0x1e, 0x96, 0xda, 0xb4, 0x2e, 0x99, 0xf9, 0x72, 0xe8, + 0x98, 0xa8, 0x04, 0x1f, 0xf2, 0x7d, 0x72, 0xda, 0xdb, 0x98, 0x47, 0x22, + 0x2a, 0x69, 0xe2, 0x48, 0x19, 0x0e, 0x70, 0x7c, 0xd2, 0x48, 0x35, 0xa4, + 0xe4, 0xb2, 0xb0, 0xcb, 0x0d, 0xd1, 0x7a, 0xab, 0x0a, 0x53, 0x4c, 0x13, + 0xd9, 0x32, 0x8e, 0x74, 0x7b, 0xc0, 0xe3, 0x74, 0xaf, 0x80, 0x01, 0xd8, + 0x1a, 0x7c, 0xaa, 0x9e, 0x8e, 0x1d, 0x24, 0x64, 0x92, 0xb0, 0x16, 0x02, + 0xbc, 0x88, 0xdb, 0x0e, 0x74, 0xba, 0x16, 0x22, 0x74, 0x8d, 0xe7, 0x2f, + 0x81, 0x25, 0xd7, 0x91, 0x44, 0x31, 0x91, 0x4a, 0x4e, 0xd6, 0x98, 0xf1, + 0x81, 0x8c, 0x30, 0x85, 0xef, 0xb3, 0x29, 0x0b, 0x98, 0x92, 0x76, 0xb8, + 0xd2, 0xb8, 0x75, 0xab, 0x86, 0x9b, 0x43, 0x41, 0xb8, 0xe2, 0x62, 0x59, + 0xda, 0x16, 0x3a, 0x85, 0x2e, 0x9c, 0x1e, 0x46, 0xc1, 0x7f, 0xf8, 0xe5, + 0xc0, 0x38, 0xd2, 0xb3, 0x86, 0x92, 0x1e, 0x34, 0x9a, 0xb4, 0x15, 0xf5, + 0xb1, 0x66, 0x25, 0x3d, 0x69, 0xe8, 0x26, 0xef, 0xb4, 0xb6, 0xb2, 0x4b, + 0xb9, 0xc9, 0xa5, 0x59, 0x8d, 0xcf, 0x4f, 0xf9, 0x0f, 0x3f, 0x55, 0x7d, + 0xc5, 0x53, 0x13, 0x75, 0x67, 0x5c, 0x27, 0x32, 0x13, 0xfd, 0x22, 0xa0, + 0x81, 0x16, 0xa0, 0x7b, 0x7b, 0xce, 0x0f, 0x7c, 0x9a, 0x5c, 0xfd, 0xa2, + 0x1f, 0xf6, 0x3e, 0x83, 0xe8, 0xba, 0xd8, 0x79, 0xe9, 0x94, 0x6f, 0xf7, + 0x16, 0x6d, 0xb9, 0xc5, 0x6a, 0x2b, 0xdb, 0xef, 0x56, 0x61, 0x35, 0x71, + 0x2f, 0xbb, 0xb4, 0x5c, 0x17, 0x56, 0x0d, 0xbb, 0x05, 0x5d, 0xa6, 0x2c, + 0x5f, 0x98, 0xbf, 0xcb, 0x35, 0x60, 0xcb, 0x37, 0xb9, 0xee, 0xf5, 0xd7, + 0xd8, 0x83, 0xd7, 0xa4, 0x2f, 0xbc, 0xe6, 0x70, 0x6c, 0x7f, 0xed, 0xbf, + 0x83, 0xb7, 0x87, 0xa8, 0x62, 0xe2, 0xf3, 0xb8, 0x00, 0xa6, 0x35, 0xa3, + 0x6c, 0xf9, 0xb3, 0xb6, 0x7a, 0xa6, 0x09, 0x55, 0xc9, 0x83, 0xd6, 0x35, + 0x7e, 0xdd, 0xd3, 0x86, 0x39, 0xc8, 0xa5, 0x40, 0x96, 0x63, 0x30, 0xe1, + 0x98, 0x0a, 0xac, 0xa7, 0xb4, 0x8a, 0xa6, 0x5d, 0x20, 0xd7, 0x42, 0x59, + 0x86, 0x95, 0x3c, 0x57, 0x1d, 0xd2, 0x24, 0x48, 0xfa, 0x25, 0x6c, 0x28, + 0x04, 0x9f, 0x5a, 0x6c, 0x43, 0x6b, 0xd4, 0xda, 0x87, 0xe2, 0x2d, 0xe6, + 0xd7, 0xd0, 0x15, 0xf8, 0x70, 0x9d, 0xc2, 0xc9, 0x00, 0x3d, 0xe3, 0xba, + 0x0c, 0x53, 0xd1, 0xf4, 0xbe, 0x8e, 0x0e, 0x28, 0xfb, 0x30, 0x74, 0xea, + 0xc7, 0x44, 0xfa, 0x09, 0x5a, 0x67, 0xe8, 0xf4, 0xf1, 0x95, 0x0c, 0xf6, + 0x22, 0xb9, 0x3d, 0xe7, 0xbd, 0xcb, 0x1a, 0x73, 0x85, 0xaf, 0xfd, 0xb4, + 0x68, 0xfd, 0x34, 0xca, 0x45, 0x69, 0x66, 0x7e, 0xea, 0xeb, 0x57, 0xc7, + 0xe9, 0xbb, 0xec, 0x33, 0x00, 0xf1, 0xd9, 0x93, 0xa6, 0xdf, 0x03, 0xd3, + 0xef, 0xb7, 0x09, 0x72, 0x65, 0xb9, 0x6e, 0xec, 0x64, 0x30, 0xad, 0x79, + 0xb9, 0x90, 0xea, 0xe5, 0xba, 0x14, 0xdb, 0x85, 0xcb, 0x0c, 0x98, 0x72, + 0x27, 0x61, 0x6f, 0x5d, 0x8a, 0x7b, 0x42, 0xc7, 0x03, 0x89, 0xae, 0xd5, + 0x23, 0x8f, 0x2f, 0x28, 0x7a, 0x67, 0x5d, 0xca, 0x57, 0x5e, 0x3a, 0xe8, + 0xa2, 0x26, 0x4a, 0xbc, 0x8e, 0xbf, 0x19, 0x03, 0xca, 0xd0, 0x18, 0xd4, + 0x1e, 0x0f, 0xa4, 0xf0, 0x4c, 0x4e, 0xef, 0xe5, 0x3a, 0xfb, 0xc4, 0x98, + 0x18, 0xd3, 0xf2, 0xcc, 0x30, 0xb0, 0xb9, 0x2e, 0x30, 0x84, 0x40, 0x5f, + 0xf8, 0x92, 0xeb, 0x91, 0x36, 0x40, 0x72, 0xd0, 0xe8, 0x8b, 0x0d, 0x01, + 0xba, 0x32, 0xa8, 0x29, 0x8e, 0x61, 0x50, 0xa7, 0x6b, 0xef, 0x44, 0xc3, + 0xdb, 0x52, 0x96, 0x9b, 0xbd, 0x7f, 0xf2, 0x4a, 0x03, 0xf3, 0x89, 0x46, + 0x2c, 0xe7, 0x5d, 0x08, 0x30, 0x4f, 0xee, 0xb1, 0x40, 0xc9, 0x12, 0x23, + 0xef, 0x75, 0x20, 0x13, 0xd9, 0xa3, 0x62, 0xde, 0xf4, 0x9d, 0x17, 0xba, + 0x0b, 0x2e, 0x28, 0xc1, 0xd4, 0x62, 0x92, 0xef, 0xcb, 0x1c, 0x61, 0xc6, + 0xb7, 0x49, 0x63, 0xee, 0x2f, 0x02, 0xa6, 0x46, 0x57, 0xa2, 0xc4, 0xba, + 0x04, 0xf4, 0x7f, 0xce, 0xc9, 0xde, 0x4b, 0xe9, 0x98, 0x75, 0x54, 0xef, + 0x0f, 0xd4, 0xec, 0x59, 0xbc, 0x9c, 0x5d, 0xca, 0xe6, 0x4e, 0xae, 0x61, + 0x69, 0xc5, 0xb0, 0x7f, 0x41, 0x27, 0x28, 0x8c, 0xdb, 0x37, 0x38, 0x41, + 0x17, 0x9c, 0xa0, 0x13, 0x4e, 0xd0, 0xbc, 0x63, 0x43, 0x5b, 0x7c, 0x7b, + 0x48, 0x77, 0x27, 0xaf, 0x7c, 0xbe, 0x7f, 0x82, 0xec, 0x6e, 0x82, 0xce, + 0x77, 0x13, 0x24, 0x65, 0x82, 0x02, 0x98, 0xb2, 0x22, 0x23, 0x36, 0xf4, + 0x40, 0x9b, 0xb2, 0x8c, 0x93, 0x61, 0xe6, 0x52, 0x1c, 0x2c, 0x36, 0x89, + 0xf3, 0x85, 0x5f, 0x5e, 0x70, 0xbd, 0x74, 0xc0, 0xb8, 0xe7, 0x70, 0x22, + 0xb7, 0x77, 0xf2, 0x4c, 0xa9, 0xde, 0x70, 0x75, 0xf5, 0x09, 0xf5, 0xc2, + 0xc1, 0xea, 0x9b, 0x71, 0x39, 0x4f, 0x59, 0x3a, 0x16, 0x0f, 0x1d, 0xe8, + 0xdd, 0xaa, 0xb2, 0x8f, 0x5f, 0x7a, 0xa9, 0x0c, 0xcf, 0xa2, 0xbb, 0xcf, + 0x4d, 0x0e, 0xf7, 0x53, 0x40, 0xc1, 0xc9, 0xf6, 0x4a, 0x9e, 0x48, 0xe1, + 0x91, 0xec, 0xd2, 0x48, 0x93, 0x7a, 0xfa, 0x5f, 0x63, 0x87, 0xff, 0xaa, + 0xc4, 0x75, 0xd4, 0x45, 0xbc, 0xdd, 0x2b, 0xab, 0x70, 0xcf, 0x0f, 0x17, + 0x7d, 0x56, 0x1f, 0x8c, 0x17, 0x1d, 0xc1, 0x8f, 0x0a, 0x11, 0xce, 0xf6, + 0xfa, 0x51, 0xf5, 0xc5, 0xe6, 0x49, 0xd7, 0xa8, 0xde, 0xca, 0x69, 0xc6, + 0xd4, 0xde, 0x0e, 0x25, 0xaa, 0x38, 0xfc, 0x31, 0xbc, 0xcf, 0x2f, 0x1e, + 0x76, 0x4b, 0x92, 0xa6, 0x85, 0x4d, 0xbb, 0xdd, 0x66, 0xdf, 0x53, 0xa4, + 0xe5, 0x6e, 0x81, 0xf3, 0xa9, 0x71, 0x5d, 0xe3, 0xf3, 0x93, 0xb7, 0x31, + 0xe8, 0x01, 0xf0, 0xef, 0x96, 0xdb, 0x27, 0xae, 0x01, 0xfc, 0x39, 0x6b, + 0xba, 0xae, 0x26, 0x15, 0x64, 0xfb, 0xa5, 0x53, 0x8d, 0x3b, 0x95, 0xd4, + 0xc7, 0x9c, 0x4b, 0x79, 0x99, 0xcc, 0x99, 0xb2, 0x28, 0x8b, 0x64, 0x4e, + 0x37, 0x27, 0x73, 0xa6, 0x0c, 0xcc, 0x98, 0x90, 0x09, 0x9a, 0x3b, 0x01, + 0xbb, 0x82, 0x57, 0xc7, 0xec, 0x4d, 0xd2, 0x5d, 0x0a, 0x4c, 0x17, 0xd0, + 0xa3, 0xe5, 0x55, 0xa6, 0x7a, 0xf4, 0x66, 0x88, 0x3d, 0x3d, 0x06, 0x58, + 0xac, 0x93, 0x34, 0x66, 0x6f, 0xba, 0x5d, 0xf6, 0x26, 0xcd, 0xef, 0x9d, + 0xb2, 0x37, 0xf0, 0xd5, 0x0f, 0xb3, 0x37, 0xcd, 0x2e, 0x7b, 0xa3, 0x19, + 0xa2, 0x57, 0xf5, 0xd9, 0xb7, 0x7e, 0xa2, 0x06, 0x27, 0x40, 0x19, 0x74, + 0x12, 0x34, 0x5a, 0x82, 0x59, 0xbf, 0xc1, 0x20, 0x53, 0x06, 0xb6, 0xba, + 0x8d, 0x2f, 0x10, 0x89, 0xab, 0x2d, 0x0d, 0xb0, 0x8f, 0xd2, 0x07, 0xe0, + 0x10, 0x0c, 0x4c, 0xc7, 0x3d, 0x51, 0xaa, 0x17, 0x9b, 0x22, 0x15, 0x25, + 0xa4, 0x55, 0xa2, 0x23, 0xfa, 0x73, 0x61, 0xc9, 0xfe, 0x9f, 0xea, 0x67, + 0x8e, 0xe6, 0x1b, 0x8a, 0x64, 0xbd, 0x54, 0x24, 0xff, 0x70, 0x2d, 0xf1, + 0x81, 0x03, 0x3d, 0x8c, 0x37, 0x6d, 0x30, 0x2c, 0xe0, 0xed, 0x5e, 0xba, + 0xf5, 0x5d, 0x1b, 0xfc, 0x23, 0x4e, 0x76, 0x90, 0x85, 0x28, 0x7a, 0xb4, + 0xc1, 0x8b, 0xb4, 0xee, 0x3e, 0x48, 0xaa, 0xa2, 0xbb, 0x03, 0x76, 0x0e, + 0x43, 0xd8, 0xca, 0xfa, 0x09, 0x26, 0x6d, 0x04, 0x84, 0x27, 0xe4, 0xe2, + 0xed, 0x88, 0x15, 0xfb, 0x8f, 0x83, 0xe8, 0x1f, 0x78, 0x29, 0xee, 0x20, + 0xab, 0x55, 0xe4, 0xac, 0xfe, 0xee, 0x73, 0x31, 0x5f, 0x37, 0xcf, 0x30, + 0x64, 0xa7, 0x19, 0xc4, 0x43, 0x43, 0xbc, 0x0d, 0xc5, 0xe7, 0x85, 0xd6, + 0x15, 0x03, 0xfe, 0xeb, 0x3b, 0xb9, 0x1f, 0x82, 0x42, 0xa9, 0x09, 0x57, + 0xad, 0xcb, 0xfa, 0x28, 0x2f, 0xaf, 0x4e, 0xd4, 0xcf, 0x80, 0x79, 0xaf, + 0x4e, 0x14, 0x28, 0x78, 0x18, 0xdc, 0x73, 0x81, 0xa0, 0x1b, 0xae, 0xfe, + 0x7d, 0x22, 0xa1, 0x00, 0xa7, 0xe7, 0x8c, 0xc7, 0x7f, 0x04, 0x41, 0x03, + 0x49, 0x71, 0x9a, 0x37, 0x8c, 0x01, 0x04, 0x2a, 0xa3, 0x2c, 0xc9, 0xa9, + 0xfa, 0xf9, 0x86, 0x2d, 0x34, 0xdf, 0xf1, 0x93, 0x19, 0x65, 0xa2, 0xb8, + 0x38, 0x45, 0x26, 0x4e, 0xd4, 0x71, 0x60, 0x72, 0xc1, 0xa9, 0x4c, 0xbc, + 0xb7, 0xad, 0x98, 0xeb, 0x55, 0xa7, 0x1d, 0xc3, 0xf3, 0xb6, 0x62, 0x2b, + 0x9a, 0x3e, 0xf0, 0x83, 0x86, 0x4e, 0x4b, 0x3e, 0xe1, 0xb5, 0x6d, 0xc5, + 0xd5, 0x72, 0x5b, 0xb1, 0x79, 0x6d, 0x5b, 0xf1, 0xbd, 0xe6, 0x8a, 0xc6, + 0x6d, 0xc5, 0x5d, 0xd9, 0x56, 0xac, 0x29, 0xa6, 0x69, 0x3f, 0x18, 0x63, + 0xbd, 0x2f, 0x0b, 0xf2, 0x5f, 0x5e, 0x16, 0xfd, 0xeb, 0xf7, 0xfd, 0xe0, + 0xa2, 0xc3, 0xc0, 0x72, 0x55, 0x34, 0x31, 0x32, 0x0b, 0x5d, 0x1a, 0x6d, + 0x88, 0x87, 0xbf, 0xcb, 0x3b, 0xfc, 0x49, 0x97, 0xf6, 0xc4, 0x5a, 0x17, + 0xed, 0x17, 0x27, 0xf3, 0xf6, 0x01, 0xb8, 0x39, 0xd0, 0xd9, 0xba, 0x49, + 0x10, 0xdd, 0x26, 0xbb, 0xfe, 0xaf, 0xc6, 0xec, 0x65, 0xf7, 0xf6, 0x37, + 0x80, 0x84, 0x71, 0xcf, 0x90, 0xc1, 0x9d, 0x70, 0xd5, 0x18, 0x8b, 0x00, + 0xb4, 0x75, 0x57, 0x99, 0xc1, 0xd7, 0x70, 0x92, 0x6c, 0x1f, 0xb8, 0x39, + 0xc7, 0x1f, 0xe5, 0x26, 0xfa, 0xf3, 0xcc, 0xf8, 0xbd, 0x30, 0x76, 0x91, + 0xab, 0xc1, 0x75, 0xfe, 0x02, 0x20, 0x21, 0x30, 0x78, 0xcf, 0x7d, 0x02, + 0x03, 0x18, 0x42, 0x63, 0xcf, 0x26, 0xfb, 0xd6, 0x5d, 0xc6, 0x7a, 0x60, + 0x64, 0xb6, 0xb3, 0xf7, 0x30, 0x50, 0x2b, 0xe3, 0x36, 0x5d, 0xfd, 0x84, + 0x71, 0x0e, 0xc6, 0xde, 0xf6, 0x8c, 0x9a, 0x78, 0x69, 0x1b, 0x99, 0xf6, + 0xaf, 0x33, 0xb0, 0xe2, 0xb3, 0x46, 0xec, 0x19, 0xba, 0x2d, 0xb9, 0x2b, + 0x9f, 0x34, 0x5e, 0xab, 0xe1, 0x7a, 0x1a, 0xba, 0x8e, 0xc6, 0x2d, 0x36, + 0x49, 0x57, 0x0c, 0xe8, 0x76, 0xe9, 0xce, 0x6f, 0x3b, 0x1f, 0xed, 0x4d, + 0xb4, 0x43, 0xfd, 0x48, 0x29, 0xcc, 0xfa, 0xe3, 0xa9, 0xfe, 0x98, 0x6c, + 0xae, 0x1f, 0x33, 0xb8, 0x38, 0xfb, 0x23, 0x30, 0xd1, 0x1a, 0x78, 0xc1, + 0x5d, 0x24, 0xf7, 0xf7, 0x26, 0xe6, 0xe6, 0xf6, 0x68, 0xf0, 0x47, 0x4f, + 0x7d, 0xb0, 0xfd, 0x6a, 0xc0, 0x8f, 0xfe, 0x0c, 0x3f, 0xda, 0xcd, 0x39, + 0xea, 0xf7, 0x31, 0x34, 0x71, 0x18, 0x1c, 0x6e, 0x70, 0xe8, 0xbc, 0xbd, + 0xc4, 0x23, 0x17, 0x77, 0x29, 0x37, 0x77, 0x69, 0xe5, 0x8f, 0x86, 0x95, + 0x43, 0x3b, 0xee, 0xe4, 0x3a, 0xd9, 0xcb, 0xab, 0x94, 0xeb, 0xbb, 0xf4, + 0xc9, 0x9b, 0xbc, 0xb2, 0x6b, 0xbc, 0xf4, 0xf8, 0x1e, 0x2f, 0xed, 0x86, + 0xa1, 0x7e, 0x8a, 0x1e, 0x3f, 0x0e, 0x16, 0xb4, 0x9d, 0xd5, 0x0f, 0xb1, + 0xb9, 0xed, 0x9e, 0x87, 0xea, 0x29, 0xba, 0x95, 0x59, 0x0d, 0x20, 0xc7, + 0xfd, 0xbd, 0xc4, 0xcc, 0x03, 0x26, 0xb8, 0x47, 0x57, 0x43, 0xe2, 0x8f, + 0xb9, 0x84, 0xcd, 0x21, 0x81, 0x98, 0x60, 0x8c, 0x4e, 0xac, 0x75, 0xbd, + 0xbb, 0x46, 0xcb, 0x7d, 0x3c, 0xfc, 0x11, 0xfd, 0x67, 0x8c, 0x94, 0x9c, + 0xab, 0x1b, 0xbb, 0x95, 0x8d, 0xf9, 0x63, 0xcf, 0x91, 0xa2, 0x43, 0xe3, + 0x3b, 0xcc, 0x09, 0x77, 0x5c, 0x61, 0xd6, 0x18, 0x80, 0xf2, 0x1a, 0x3a, + 0x8f, 0x98, 0x5e, 0x66, 0x0f, 0x02, 0x43, 0xc6, 0x4d, 0xca, 0x5c, 0x4e, + 0x4c, 0x37, 0x88, 0xa1, 0xf3, 0xe7, 0x97, 0xa1, 0x73, 0x53, 0x36, 0x80, + 0xcd, 0x31, 0x73, 0x5f, 0x56, 0xd1, 0x71, 0xed, 0xdc, 0x6e, 0x23, 0xcb, + 0x32, 0x11, 0xd9, 0x8e, 0x8f, 0x93, 0xd9, 0x3c, 0x30, 0xa9, 0xc6, 0xcc, + 0x4b, 0x8c, 0xde, 0xf5, 0x8c, 0xd8, 0x33, 0x66, 0xde, 0x4b, 0x35, 0x46, + 0xf3, 0x87, 0x66, 0x0a, 0xf1, 0xe3, 0x70, 0x33, 0x05, 0xcb, 0x4b, 0x62, + 0xa6, 0x24, 0x04, 0xa6, 0x2c, 0x57, 0xb9, 0xb0, 0x3b, 0xf8, 0x9b, 0x5d, + 0x7a, 0x60, 0xef, 0x90, 0xf6, 0x93, 0x05, 0xe5, 0x10, 0xf9, 0x77, 0x1a, + 0xc6, 0xc3, 0x98, 0x3a, 0x00, 0xd3, 0x62, 0xce, 0x35, 0x83, 0xb0, 0x38, + 0xac, 0x3b, 0xf0, 0x91, 0x01, 0x03, 0xed, 0x1d, 0xda, 0xea, 0x11, 0xbd, + 0xc2, 0xe1, 0x19, 0x87, 0x6d, 0x39, 0xec, 0x65, 0x60, 0xa7, 0xc3, 0xcb, + 0x4d, 0x3e, 0x73, 0x92, 0xa1, 0x21, 0xc7, 0x6b, 0x7e, 0x62, 0x4e, 0x29, + 0x70, 0x43, 0x82, 0xee, 0xb2, 0x0b, 0xb4, 0x26, 0x5b, 0xea, 0xba, 0x07, + 0x0a, 0xf1, 0x33, 0x17, 0xfa, 0x08, 0x0d, 0x2a, 0x26, 0x18, 0xf7, 0xeb, + 0x09, 0x5d, 0x0d, 0x9e, 0x00, 0xd8, 0x73, 0xea, 0xcb, 0x65, 0x3d, 0x79, + 0xff, 0xde, 0xa2, 0xe0, 0x96, 0xf7, 0x76, 0x2f, 0xee, 0x7d, 0x01, 0xc7, + 0xb9, 0x11, 0xb8, 0xa6, 0x06, 0x74, 0x0c, 0x1f, 0x38, 0x2a, 0x44, 0x4f, + 0x4b, 0x44, 0x47, 0x86, 0x4d, 0x0f, 0x1f, 0x2b, 0x8a, 0x5a, 0x65, 0x61, + 0x07, 0x6e, 0x33, 0x01, 0xb8, 0x27, 0x33, 0x83, 0xcd, 0x7b, 0xd6, 0xb8, + 0xf5, 0xc4, 0xce, 0xb7, 0x7c, 0xb4, 0xd1, 0x7a, 0xec, 0x5c, 0xa1, 0x25, + 0xd5, 0x23, 0x91, 0x96, 0xb1, 0xda, 0x5a, 0x53, 0x2c, 0x3f, 0xe1, 0xc3, + 0x9d, 0x8b, 0x49, 0x45, 0xd4, 0x02, 0x27, 0x45, 0xee, 0x8a, 0xe9, 0xbe, + 0xd1, 0x8c, 0x3b, 0x52, 0xc9, 0x2d, 0x2b, 0x6d, 0x88, 0x0d, 0x47, 0xd3, + 0xe9, 0x8a, 0x29, 0x2e, 0xa0, 0x52, 0xb8, 0xfb, 0x0d, 0x90, 0x00, 0x1a, + 0x6f, 0x28, 0x74, 0xb1, 0x59, 0x28, 0x96, 0x58, 0x97, 0xed, 0x42, 0xff, + 0x66, 0x3e, 0x9b, 0xa6, 0x0f, 0xdc, 0x7d, 0x09, 0x2b, 0xec, 0x18, 0x3a, + 0x16, 0x5d, 0x7b, 0xa2, 0x30, 0x94, 0x99, 0x62, 0x9a, 0x68, 0xe0, 0x1f, + 0x5c, 0xa6, 0x46, 0xfa, 0xd7, 0xb2, 0x11, 0xe2, 0x6f, 0xe3, 0xf6, 0x50, + 0x5d, 0xa1, 0xc6, 0x2c, 0x08, 0x58, 0x9e, 0xe6, 0x88, 0x48, 0x95, 0x68, + 0x2a, 0x37, 0xba, 0xe2, 0x8f, 0x5e, 0xa3, 0x1d, 0x1d, 0x45, 0xba, 0xa9, + 0xea, 0x2c, 0x9e, 0x80, 0x20, 0xe6, 0x4e, 0x41, 0x03, 0x3c, 0x5b, 0x98, + 0x88, 0xce, 0xd2, 0x8e, 0xe2, 0x32, 0x1c, 0x5e, 0x13, 0x16, 0x97, 0x45, + 0xc3, 0x9d, 0xcc, 0x8d, 0xb1, 0xfd, 0xac, 0x7f, 0x2b, 0xe4, 0x20, 0x5b, + 0xd4, 0x1c, 0x5c, 0x0e, 0x92, 0xc6, 0xcb, 0xef, 0x35, 0x7e, 0x70, 0x79, + 0x24, 0xad, 0x82, 0xfd, 0x52, 0xf7, 0x96, 0xb9, 0x34, 0x6e, 0x07, 0xa1, + 0xeb, 0xcd, 0xe5, 0x61, 0xad, 0x28, 0x34, 0xa6, 0xb1, 0x8d, 0xd3, 0x2e, + 0x86, 0xf2, 0x97, 0x1d, 0xda, 0xff, 0xbf, 0x63, 0x61, 0xbf, 0x72, 0x2c, + 0xf8, 0x87, 0x58, 0x14, 0x07, 0x96, 0xd5, 0x03, 0x41, 0xa1, 0x08, 0x30, + 0xb6, 0x3e, 0x75, 0x2c, 0x63, 0xdc, 0xa0, 0x53, 0x12, 0x2e, 0x35, 0x09, + 0x42, 0x5c, 0xc0, 0x7c, 0x3e, 0xf3, 0x94, 0x42, 0x4a, 0x40, 0xc5, 0x9d, + 0xe8, 0xc2, 0x02, 0xb1, 0x71, 0x8e, 0x93, 0x54, 0xe3, 0x5f, 0x07, 0x51, + 0x68, 0xdb, 0x99, 0x79, 0xbb, 0x6e, 0x89, 0xa4, 0xf8, 0x12, 0x51, 0x23, + 0xd7, 0xae, 0x0b, 0xc3, 0xf2, 0xb4, 0x5f, 0xc6, 0x5a, 0xc2, 0xd8, 0xc6, + 0x30, 0xb7, 0x31, 0x6d, 0x71, 0x7e, 0xfd, 0x74, 0xf1, 0x2c, 0xaf, 0xb0, + 0x3a, 0xfe, 0xaa, 0x7b, 0x38, 0x55, 0x17, 0x33, 0x30, 0x4c, 0x98, 0xff, + 0xbc, 0x38, 0xfd, 0x70, 0xf6, 0x36, 0xfc, 0x92, 0xec, 0x6d, 0x53, 0x16, + 0xd0, 0x4d, 0x7b, 0x76, 0xb8, 0xb4, 0xac, 0x7f, 0x23, 0xff, 0x3a, 0x5d, + 0x16, 0x75, 0x51, 0xf9, 0x57, 0x88, 0xe4, 0x1b, 0xb3, 0xb7, 0xff, 0x07, + 0xcb, 0x4b, 0x74, 0x5c, 0x80, 0x4a, 0x00, 0x00 +}; const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = { {0, 1, 1, 17, 0, 0}, // 0x20 ' ' @@ -1633,7 +518,7 @@ const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = { {4810, 22, 38, 28, 3, -47}, // 0x3E '>' {4915, 24, 68, 33, 5, -67}, // 0x3F '?' {5119, 48, 71, 58, 5, -67}, // 0x40 '@' - {5545, 31, 67, 35, 2, -66}, // 0x41 'A' + {5545, 31, 69, 37, 2, -67}, // 0x41 'A' {5805, 28, 67, 36, 5, -66}, // 0x42 'B' {6040, 27, 69, 36, 5, -67}, // 0x43 'C' {6273, 28, 67, 37, 5, -66}, // 0x44 'D' @@ -1699,8 +584,30 @@ const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = { //, {18021, 31, 69, 37, 2, -67} -const GFXfont Antonio_SemiBold40pt7b PROGMEM = { - (uint8_t *)Antonio_SemiBold40pt7bBitmaps, - (GFXglyph *)Antonio_SemiBold40pt7bGlyphs, 0x20, 0x7E, 100}; +// const GFXfont Antonio_SemiBold40pt7b PROGMEM = { +// (uint8_t *)Antonio_SemiBold40pt7bBitmaps, +// (GFXglyph *)Antonio_SemiBold40pt7bGlyphs, 0x20, 0x7E, 100}; // Approx. 18961 bytes + +// // Font metadata structure +// struct FontData { +// const uint8_t* compressedData; +// const GFXglyph* glyphs; +// const size_t compressedSize; +// const size_t originalSize; +// const uint16_t first; +// const uint16_t last; +// const uint8_t yAdvance; +// }; + +// Font properties +static constexpr FontData Antonio_SemiBold40pt7b_Properties = { + Antonio_SemiBold40pt7bBitmaps_Gzip, + Antonio_SemiBold40pt7bGlyphs, + sizeof(Antonio_SemiBold40pt7bBitmaps_Gzip), + 19072, // Original size + 0x20, // First char + 0x7E, // Last char + 100 // yAdvance +}; diff --git a/src/fonts/antonio-semibold90.h b/src/fonts/antonio-semibold90.h index 4ad6ab3..4f57cb4 100644 --- a/src/fonts/antonio-semibold90.h +++ b/src/fonts/antonio-semibold90.h @@ -1,5259 +1,1023 @@ +#pragma once + #include #include +// #include "fonts.hpp" -const uint8_t Antonio_SemiBold90pt7bBitmaps[] PROGMEM = { - 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, - 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0x7F, - 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0x7F, 0xFF, - 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, - 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0xFF, - 0xFF, 0xE3, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, - 0xE3, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xE3, - 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xC1, 0xFF, - 0xFF, 0x07, 0xFF, 0xFC, 0x1F, 0xFF, 0xF0, 0x7F, 0xFF, 0xC1, 0xFF, 0xFF, - 0x07, 0xFF, 0xFC, 0x1F, 0xFF, 0xF0, 0x7F, 0xFF, 0xC1, 0xFF, 0xFF, 0x07, - 0xFF, 0xFC, 0x1F, 0xFF, 0xF0, 0x7F, 0xFF, 0xC1, 0xFF, 0xFF, 0x07, 0xFF, - 0xFC, 0x1F, 0xFF, 0xF0, 0x7F, 0xFF, 0xC0, 0xFF, 0xFE, 0x03, 0xFF, 0xF8, - 0x0F, 0xFF, 0xE0, 0x3F, 0xFF, 0x80, 0xFF, 0xFE, 0x03, 0xFF, 0xF8, 0x0F, - 0xFF, 0xE0, 0x3F, 0xFF, 0x80, 0xFF, 0xFE, 0x03, 0xFF, 0xF8, 0x0F, 0xFF, - 0xE0, 0x3F, 0xFF, 0x80, 0xFF, 0xFE, 0x03, 0xFF, 0xF8, 0x0F, 0xFF, 0xE0, - 0x3F, 0xFF, 0x80, 0xFF, 0xFE, 0x01, 0xFF, 0xF0, 0x07, 0xFF, 0xC0, 0x1F, - 0xFF, 0x00, 0x7F, 0xFC, 0x01, 0xFF, 0xF0, 0x07, 0xFF, 0xC0, 0x1F, 0xFF, - 0x00, 0x7F, 0xFC, 0x01, 0xFF, 0xF0, 0x07, 0xFF, 0xC0, 0x1F, 0xFF, 0x00, - 0x7F, 0xFC, 0x01, 0xFF, 0xF0, 0x07, 0xFF, 0xC0, 0x1F, 0xFF, 0x00, 0x7F, - 0xFC, 0x01, 0xFF, 0xF0, 0x03, 0xFF, 0x80, 0x0F, 0xFE, 0x00, 0x3F, 0xF8, - 0x00, 0xFF, 0xE0, 0x03, 0xFF, 0x80, 0x0F, 0xFE, 0x00, 0x3F, 0xF8, 0x00, - 0xFF, 0xE0, 0x03, 0xFF, 0x80, 0x0F, 0xFE, 0x00, 0x3F, 0xF8, 0x00, 0xFF, - 0xE0, 0x03, 0xFF, 0x80, 0x0F, 0xFE, 0x00, 0x3F, 0xF8, 0x00, 0xFF, 0xE0, - 0x03, 0xFF, 0x80, 0x07, 0xFC, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0xC0, 0x01, - 0xFF, 0x00, 0x07, 0xFC, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0xC0, 0x01, 0xFF, - 0x00, 0x07, 0xFC, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0xC0, 0x01, 0xFF, 0x00, - 0x07, 0xFC, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xDF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, - 0xDF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xDF, - 0xFF, 0xFF, 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xDF, 0xFF, - 0xFF, 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, - 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFE, 0xFF, 0xFF, 0xF9, 0xFF, - 0xFF, 0xE7, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, - 0xC7, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF1, 0xFF, 0xFF, 0xC7, - 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE1, 0xFF, 0xFF, 0x87, 0xFF, - 0xFE, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xE1, 0xFF, 0xFF, 0x87, 0xFF, 0xFE, - 0x1F, 0xFF, 0xF0, 0x7F, 0xFF, 0xC1, 0xFF, 0xFF, 0x07, 0xFF, 0xFC, 0x1F, - 0xFF, 0xF0, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, 0x03, 0xFF, 0xF8, 0x0F, 0xFF, - 0xE0, 0x3F, 0xFF, 0x80, 0xFF, 0xFE, 0x03, 0xFF, 0xF8, 0x0F, 0xFF, 0xE0, - 0x3F, 0xFF, 0x80, 0xFF, 0xFC, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x3F, - 0xFF, 0x00, 0xFF, 0xFC, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x3F, 0xFE, - 0x00, 0xFF, 0xF8, 0x03, 0xFF, 0xE0, 0x0F, 0xFF, 0x80, 0x3F, 0xFE, 0x00, - 0xFF, 0xF8, 0x03, 0xFF, 0xE0, 0x07, 0xFF, 0x00, 0x1F, 0xFC, 0x00, 0x7F, - 0xF0, 0x01, 0xFF, 0xC0, 0x07, 0xFF, 0x00, 0x1F, 0xFC, 0x00, 0x7F, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC0, - 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x03, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC0, - 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x03, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC0, - 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x03, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC0, - 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x03, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, - 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x03, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, - 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x03, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, - 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, - 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, - 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, - 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, - 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, - 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, - 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, - 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, - 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0x7F, - 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x7F, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF8, - 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x7F, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xF8, - 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x7F, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFC, - 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x3F, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFC, - 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFC, - 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFC, - 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, - 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, - 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x1F, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, - 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, - 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x1F, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xC0, - 0x7F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0x00, 0x07, - 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xF0, 0x7F, 0xFF, - 0xF8, 0x00, 0x0F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, - 0xC1, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xF8, 0x00, - 0x03, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xF1, 0xFF, - 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, - 0xFF, 0xC7, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0xFF, 0xFF, 0xE0, - 0x00, 0x03, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF3, - 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x01, - 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xF9, 0xFF, 0xFF, - 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xE3, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, 0x00, - 0x01, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xF1, 0xFF, - 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, - 0xFF, 0xC3, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF8, - 0x00, 0x03, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xE1, - 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0x00, 0x01, - 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, - 0x00, 0x01, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFE, - 0x3F, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x00, 0x00, - 0x7F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0x9F, 0xFF, - 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0x00, - 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xBF, - 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, - 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, - 0x7F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0x00, 0x00, - 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, - 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, - 0x00, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, - 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0x80, 0x00, 0x7F, - 0xFF, 0xF8, 0x7F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, - 0x00, 0x03, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF8, - 0x1F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xF8, 0x3F, - 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, - 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, - 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, - 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, - 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, - 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, - 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7E, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, - 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xC0, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, - 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x01, 0xFF, 0xFE, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0x80, 0x00, - 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFE, 0x00, - 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, - 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xF8, - 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x7F, - 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xE3, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x3F, - 0xFF, 0xFC, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x07, - 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0x3F, 0xFF, - 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x03, - 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, - 0x1F, 0xFF, 0xF3, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, - 0x3F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFE, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, - 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xBF, 0xFF, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFE, 0x00, - 0x00, 0x3F, 0xFF, 0xEF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, - 0x01, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFB, - 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0x00, - 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFE, 0xFF, 0xFF, 0xC0, 0x00, - 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0xFF, 0xFF, 0x9F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0x80, - 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, - 0xE7, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xF8, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFD, 0xFF, 0xFF, 0x00, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x7F, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, - 0xFF, 0xDF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, - 0xC0, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF7, 0xFF, 0xFE, - 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, - 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, - 0xFF, 0xFF, 0x3F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, - 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xCF, 0xFF, - 0xFC, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x07, - 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF3, 0xFF, 0xFF, 0x80, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, - 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, - 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x1F, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xC0, 0x00, - 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFE, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, - 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, - 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, - 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x07, - 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFC, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, - 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x7F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, - 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0x80, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0x01, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xF0, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x3F, - 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0x07, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, - 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xC0, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x3F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, - 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF1, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xEF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x08, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x38, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x70, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xF0, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xF0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x1F, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x3F, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, - 0xC0, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xC0, 0x03, - 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0x80, 0x1F, 0xFF, 0xFC, 0x1F, 0xFF, - 0xF8, 0x00, 0x7F, 0xFF, 0x80, 0x3F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF8, 0x00, - 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, - 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x07, 0xFF, - 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, - 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, - 0xC0, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0x83, 0xFF, - 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x8F, 0xFF, 0xFC, 0x03, - 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFE, - 0x00, 0x07, 0xFF, 0xFE, 0x7F, 0xFF, 0xC0, 0x1F, 0xFF, 0xF8, 0x00, 0x07, - 0xFF, 0xFE, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, - 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFE, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFC, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xF8, - 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF8, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, - 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0x80, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x01, - 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, - 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x3F, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, - 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x83, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, - 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, - 0xFF, 0xFF, 0x3F, 0xFF, 0xE7, 0xFF, 0xFC, 0xFF, 0xFF, 0x9F, 0xFF, 0xF3, - 0xFF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFF, 0xF9, 0xFF, 0xFF, 0x3F, 0xFF, 0xC7, - 0xFF, 0xF8, 0x7F, 0xFF, 0x0F, 0xFF, 0xE1, 0xFF, 0xFC, 0x3F, 0xFF, 0x87, - 0xFF, 0xF0, 0xFF, 0xFE, 0x1F, 0xFF, 0xC3, 0xFF, 0xF0, 0x7F, 0xFE, 0x0F, - 0xFF, 0xC1, 0xFF, 0xF8, 0x1F, 0xFF, 0x03, 0xFF, 0xE0, 0x7F, 0xFC, 0x0F, - 0xFF, 0x81, 0xFF, 0xF0, 0x3F, 0xFE, 0x07, 0xFF, 0x80, 0xFF, 0xF0, 0x1F, - 0xFE, 0x03, 0xFF, 0xC0, 0x3F, 0xF8, 0x07, 0xFF, 0x00, 0xFF, 0xE0, 0x1F, - 0xFC, 0x03, 0xFF, 0x80, 0x7F, 0xF0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, - 0xF0, 0x07, 0xFE, 0x00, 0xFF, 0xC0, 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x3F, - 0xE0, 0x07, 0xFC, 0x00, 0xFF, 0x80, 0x1F, 0xE0, 0x03, 0xFC, 0x00, 0x7F, - 0x80, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x3F, 0xFF, - 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, - 0x00, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, - 0x03, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, - 0x1F, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0x80, - 0x3F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFE, 0x00, - 0x3F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, - 0x1F, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, - 0x07, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, - 0x01, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFF, - 0x00, 0x3F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xE0, - 0xFF, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xF0, - 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xFC, - 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFC, - 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xF8, - 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xE0, - 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0x80, - 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF8, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x80, - 0x01, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x80, 0x01, 0xC0, 0x00, 0x3F, - 0xFE, 0x00, 0x03, 0xC0, 0x03, 0xE0, 0x00, 0x3F, 0xFC, 0x00, 0x07, 0xE0, - 0x07, 0xF0, 0x00, 0x3F, 0xFC, 0x00, 0x0F, 0xE0, 0x07, 0xFC, 0x00, 0x3F, - 0xFC, 0x00, 0x3F, 0xF0, 0x0F, 0xFE, 0x00, 0x3F, 0xFC, 0x00, 0x7F, 0xF0, - 0x0F, 0xFF, 0x00, 0x1F, 0xFC, 0x00, 0xFF, 0xF8, 0x1F, 0xFF, 0xC0, 0x1F, - 0xFC, 0x03, 0xFF, 0xFC, 0x3F, 0xFF, 0xE0, 0x1F, 0xFC, 0x07, 0xFF, 0xFC, - 0x3F, 0xFF, 0xF0, 0x1F, 0xFC, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFC, 0x1F, - 0xFC, 0x3F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFE, 0x1F, 0xFC, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xDF, - 0xFB, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xDF, 0xFB, 0xFF, 0xFF, 0xF8, - 0xFF, 0xFF, 0xFF, 0x9F, 0xF8, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFE, 0x1F, - 0xFC, 0x7F, 0xFF, 0xFF, 0x7F, 0xFF, 0xFC, 0x1F, 0xFC, 0x3F, 0xFF, 0xFF, - 0x3F, 0xFF, 0xF8, 0x1F, 0xFC, 0x1F, 0xFF, 0xFE, 0x1F, 0xFF, 0xF0, 0x1F, - 0xFC, 0x07, 0xFF, 0xFC, 0x1F, 0xFF, 0xC0, 0x1F, 0xFC, 0x03, 0xFF, 0xFC, - 0x0F, 0xFF, 0x80, 0x1F, 0xFC, 0x01, 0xFF, 0xF8, 0x0F, 0xFF, 0x00, 0x3F, - 0xFC, 0x00, 0x7F, 0xF8, 0x07, 0xFC, 0x00, 0x3F, 0xFC, 0x00, 0x3F, 0xF0, - 0x03, 0xF8, 0x00, 0x3F, 0xFC, 0x00, 0x1F, 0xF0, 0x03, 0xF0, 0x00, 0x3F, - 0xFC, 0x00, 0x0F, 0xE0, 0x01, 0xE0, 0x00, 0x3F, 0xFE, 0x00, 0x03, 0xC0, - 0x01, 0x80, 0x00, 0x3F, 0xFE, 0x00, 0x01, 0xC0, 0x01, 0x00, 0x00, 0x3F, - 0xFE, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, - 0xC0, 0x07, 0xFF, 0x80, 0x1F, 0xFF, 0x00, 0x3F, 0xFC, 0x00, 0x7F, 0xF8, - 0x00, 0xFF, 0xF0, 0x01, 0xFF, 0xC0, 0x07, 0xFF, 0x80, 0x0F, 0xFF, 0x00, - 0x1F, 0xFE, 0x00, 0x3F, 0xF8, 0x00, 0xFF, 0xF0, 0x01, 0xFF, 0xE0, 0x03, - 0xFF, 0x80, 0x07, 0xFF, 0x00, 0x0F, 0xFE, 0x00, 0x3F, 0xF8, 0x00, 0x7F, - 0xF0, 0x00, 0xFF, 0xE0, 0x01, 0xFF, 0xC0, 0x03, 0xFF, 0x00, 0x0F, 0xFE, - 0x00, 0x1F, 0xFC, 0x00, 0x3F, 0xF0, 0x00, 0x7F, 0xE0, 0x00, 0xFF, 0xC0, - 0x03, 0xFF, 0x80, 0x07, 0xFE, 0x00, 0x0F, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, - 0xF0, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0x00, - 0x07, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xF8, 0xFF, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0xFF, - 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, - 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF9, - 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, - 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, - 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xF3, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF0, 0x00, - 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xF9, 0xFF, - 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, - 0xFF, 0xE3, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF8, - 0x00, 0x0F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xE1, - 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, - 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, - 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, 0xF8, 0x3F, - 0xFF, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, - 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, - 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x01, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, - 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, - 0xFF, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xC0, - 0x00, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xF0, 0xFF, - 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, - 0xFC, 0x3F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x00, - 0x03, 0xFF, 0xFF, 0x8F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, - 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, - 0xF3, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xF9, 0xFF, 0xFF, 0xC0, 0x00, - 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, - 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xBF, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, - 0xDF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0x00, 0x00, - 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFB, 0xFF, 0xFF, - 0x80, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFB, 0xFF, - 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, - 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, - 0x03, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, - 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, - 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, - 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, - 0xFC, 0x1F, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, - 0x00, 0x07, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x3F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, - 0x07, 0xFF, 0xFF, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0xFF, - 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, 0x80, - 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFD, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFE, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, - 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x80, - 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF8, - 0x7F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0x80, 0x00, - 0x1F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0xFF, - 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, - 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, - 0xFF, 0x87, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xBF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF7, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xDF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xF3, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xCF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xF1, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFE, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC7, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xF8, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFE, 0x1F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC3, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xF8, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC1, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0xFF, 0xF8, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xF8, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xE0, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xE0, - 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x07, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, - 0xE0, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x03, - 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x3F, 0xFF, 0xF0, 0x00, 0x00, - 0x03, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x7F, 0xFC, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x1F, 0xFF, 0xF8, 0x00, - 0x00, 0x03, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFE, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x0F, 0xFF, 0xFC, - 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x7F, - 0xFE, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x07, 0xFF, - 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x7F, 0xFE, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x03, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xE0, 0x00, - 0x00, 0x7F, 0xFE, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xC0, - 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xC0, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, - 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x0F, - 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, - 0x0F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xF8, 0x00, - 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xF8, - 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, - 0xE0, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, - 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x3F, - 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x01, - 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x80, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x00, - 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x80, - 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, - 0x80, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, - 0x80, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0xC0, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x60, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x1F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0xFF, 0xFF, 0xE0, - 0x00, 0x7F, 0xFF, 0xE3, 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xE7, 0xFF, - 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xEF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, - 0xFE, 0x00, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF8, - 0x7F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF8, 0x00, 0x1F, - 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, - 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFC, 0x7F, - 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, - 0xFC, 0x7F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, 0x00, - 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, - 0xC0, 0x00, 0x0F, 0xFF, 0xFE, 0x0F, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0xC0, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, - 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, - 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, - 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, - 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, - 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, - 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, - 0x00, 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, - 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, - 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF8, 0x7F, 0xFF, - 0xFE, 0x00, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xF8, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFC, 0x07, 0xFF, - 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, - 0xE0, 0x00, 0x7F, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFF, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xF0, 0x3F, 0xFF, 0xF8, 0x00, - 0x03, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xF8, 0x3F, - 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x7F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, - 0x87, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF0, 0x00, - 0x01, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, - 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, - 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, - 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, - 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x3C, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x01, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFC, 0x00, - 0x0F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, - 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, - 0xFE, 0x01, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, - 0xCF, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF8, 0x00, - 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, - 0xEF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, - 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF0, 0x00, - 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x07, - 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, - 0xC0, 0x00, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, - 0xE3, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFE, 0x00, - 0x03, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFC, 0x1F, - 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, - 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, - 0xFC, 0x00, 0x3F, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, - 0xF8, 0x3F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, - 0x00, 0x1F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xF0, - 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x80, 0x00, - 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF1, 0xFF, - 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, - 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, - 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, - 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFB, - 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, - 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, - 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, - 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, - 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, - 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, - 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, - 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, - 0xE3, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFC, 0x00, - 0x00, 0x7F, 0xFF, 0xF0, 0x7F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x83, - 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFC, 0x00, 0x00, - 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0x03, 0xFF, - 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, - 0x80, 0x01, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFC, - 0x00, 0x7F, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, - 0x3F, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, - 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, - 0xF8, 0x01, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFE, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xF8, - 0x0F, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xF8, 0x00, - 0x07, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xF8, 0x1F, - 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xE0, 0x00, 0x07, - 0xFF, 0xFF, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, - 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, - 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, - 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, 0x80, 0x00, - 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFD, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF3, - 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xE3, 0xFF, - 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, - 0xE0, 0x00, 0x7F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, - 0xC0, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, - 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, - 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, - 0xC0, 0x00, 0x7F, 0xFF, 0xF8, 0x7F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, - 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0x80, 0x00, - 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, - 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, - 0xFF, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0x00, - 0x00, 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, - 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xDF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF0, - 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFD, - 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x00, - 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, - 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, - 0xF8, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xFF, - 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xF8, 0x3F, 0xFF, 0xF8, 0x00, - 0x00, 0x7F, 0xF0, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, 0x00, 0x01, 0xFF, - 0xFF, 0xCF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0x80, - 0x00, 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, - 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, 0xC0, 0x00, 0x03, - 0xFF, 0xFF, 0x9F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, - 0xCF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0x80, 0x00, - 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, - 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, - 0xFF, 0x9F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0x8F, - 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, - 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, - 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xC0, 0x00, - 0x3F, 0xFF, 0xF8, 0x3F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0x07, 0xFF, - 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, - 0xF8, 0x0F, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, - 0xBF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, - 0xFF, 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF, - 0xFE, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFE, - 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFE, 0xFF, - 0xFF, 0xFB, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xF0, - 0x0F, 0xFF, 0x80, 0x3F, 0xFE, 0x01, 0xFF, 0xF8, 0x07, 0xFF, 0xC0, 0x1F, - 0xFF, 0x00, 0x7F, 0xFC, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0x80, 0x3F, 0xFE, - 0x00, 0xFF, 0xF8, 0x03, 0xFF, 0xC0, 0x1F, 0xFF, 0x00, 0x7F, 0xFC, 0x01, - 0xFF, 0xF0, 0x07, 0xFF, 0x80, 0x1F, 0xFE, 0x00, 0xFF, 0xF8, 0x03, 0xFF, - 0xC0, 0x0F, 0xFF, 0x00, 0x3F, 0xFC, 0x01, 0xFF, 0xF0, 0x07, 0xFF, 0x80, - 0x1F, 0xFE, 0x00, 0x7F, 0xF8, 0x01, 0xFF, 0xC0, 0x0F, 0xFF, 0x00, 0x3F, - 0xFC, 0x00, 0xFF, 0xE0, 0x03, 0xFF, 0x80, 0x1F, 0xFE, 0x00, 0x7F, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, - 0xC3, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, - 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, - 0xFC, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xF9, - 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x00, 0x03, 0xFF, - 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF0, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x01, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x00, - 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x03, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFC, 0x00, - 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xF8, 0x00, 0x7F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x00, 0x00, - 0x07, 0xFF, 0xE0, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xF0, 0x07, 0xFF, - 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xC0, - 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x1F, - 0xFF, 0x80, 0x7F, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x3F, 0xFE, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0xFF, 0xF8, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xE0, 0x3F, 0xFF, 0xC0, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0x80, 0xFF, 0xFE, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0x03, 0xFF, - 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xE0, 0x00, 0x7F, 0xFC, - 0x1F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0x80, 0x01, - 0xFF, 0xF0, 0x7F, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x3F, 0xFE, - 0x00, 0x07, 0xFF, 0xC1, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, - 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0x07, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xF0, - 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x7F, 0xFC, 0x1F, 0xFF, 0xC0, 0x00, 0x1F, - 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0x80, 0x01, 0xFF, 0xF8, 0xFF, 0xFF, 0x00, - 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x07, 0xFF, 0xE3, 0xFF, - 0xFC, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, - 0x8F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xE0, 0x00, - 0x3F, 0xFE, 0x3F, 0xFF, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0x80, 0x00, 0xFF, 0xF8, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFE, 0x00, 0x03, 0xFF, 0xE3, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0x9F, 0xFF, 0xE0, 0x00, 0x3F, - 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x3F, 0xFE, 0x7F, 0xFF, 0x80, - 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0xFF, 0xF9, 0xFF, - 0xFE, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x03, 0xFF, - 0xF7, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xF8, 0x00, - 0x0F, 0xFF, 0xDF, 0xFF, 0xC0, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xE0, 0x00, 0x3F, 0xFF, 0x7F, 0xFF, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x00, - 0x0F, 0xFF, 0x80, 0x00, 0xFF, 0xFD, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xC0, - 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x03, 0xFF, 0xF7, 0xFF, 0xF0, 0x00, 0x1F, - 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xDF, 0xFF, 0xC0, - 0x00, 0x7F, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x7F, - 0xFF, 0x00, 0x01, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFE, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0xFF, - 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFC, 0x00, 0x00, - 0x03, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xE0, - 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, - 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0x80, - 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x3F, - 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFE, 0x00, 0x00, - 0x00, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xF8, - 0x00, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x03, - 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xE0, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x00, 0x0F, - 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0x80, 0x00, - 0x00, 0x3F, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFE, - 0x00, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x3F, 0xFE, 0xFF, 0xFF, - 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0xFF, 0xFB, - 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x03, - 0xFF, 0xEF, 0xFF, 0xF0, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xF8, - 0x00, 0x0F, 0xFF, 0xBF, 0xFF, 0xC0, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x03, - 0xFF, 0xE0, 0x00, 0x3F, 0xFE, 0x7F, 0xFF, 0x00, 0x03, 0xFF, 0xE0, 0x00, - 0x00, 0x0F, 0xFF, 0x80, 0x00, 0xFF, 0xF9, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, - 0xC0, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x03, 0xFF, 0xE7, 0xFF, 0xF0, 0x00, - 0x3F, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0x1F, 0xFF, - 0xC0, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x7F, 0xFC, - 0x7F, 0xFF, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0x80, 0x01, - 0xFF, 0xF1, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFE, - 0x00, 0x07, 0xFF, 0xC7, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0x1F, 0xFF, 0xE0, 0x00, 0x7F, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x7F, 0xF8, 0x7F, 0xFF, 0x80, 0x01, 0xFF, - 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x03, 0xFF, 0xE0, 0xFF, 0xFE, 0x00, - 0x07, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0x83, 0xFF, - 0xF8, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x3F, 0xFE, - 0x0F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, - 0xFF, 0xF0, 0x3F, 0xFF, 0xC0, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x07, 0xFF, 0xC0, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xF0, 0x00, 0x03, - 0xF7, 0xFF, 0x80, 0x1F, 0xFF, 0x03, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xE0, - 0x00, 0x1F, 0xDF, 0xFE, 0x00, 0xFF, 0xF8, 0x07, 0xFF, 0xF0, 0x00, 0x1F, - 0xFF, 0xC0, 0x00, 0x7F, 0x7F, 0xFC, 0x07, 0xFF, 0xE0, 0x1F, 0xFF, 0xC0, - 0x00, 0x3F, 0xFF, 0x00, 0x03, 0xF8, 0xFF, 0xF8, 0x3F, 0xFF, 0x80, 0x7F, - 0xFF, 0x80, 0x00, 0xFF, 0xFE, 0x00, 0x1F, 0xE3, 0xFF, 0xFB, 0xFF, 0xFC, - 0x01, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFE, 0x01, 0xFF, 0x8F, 0xFF, 0xFF, - 0xFF, 0xF0, 0x03, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFC, 0x1F, - 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xF0, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFC, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x07, - 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0, - 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, - 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xC0, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF7, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xEF, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xCF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0x9F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x3F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF9, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE3, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC3, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x87, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x0F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x1F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x07, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x0F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x3F, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xC0, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x07, - 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xF8, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x03, 0xFF, - 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF8, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0x00, - 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, - 0x80, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xF8, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFE, 0x00, - 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFF, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, - 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, - 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, - 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x07, - 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x01, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x1F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x3F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xC1, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x83, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xE3, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, - 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, - 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, - 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xF8, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, - 0x00, 0x01, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, - 0xC3, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x80, - 0x00, 0x07, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF8, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xE0, 0x00, - 0x01, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xC7, - 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF1, 0xFF, - 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0x03, - 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x8F, 0xFF, - 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x7F, 0xFF, - 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0xE1, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, - 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, - 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xF0, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, - 0x00, 0x01, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0x3F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x1F, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFE, 0x00, 0x07, 0xFF, - 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, - 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, - 0xFF, 0xE1, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, - 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, - 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, - 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, - 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, - 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, - 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, - 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, - 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, - 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, - 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, - 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xE0, 0x00, - 0x01, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xC7, - 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, - 0x3F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFC, 0x3F, - 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, - 0xFE, 0x00, 0x7F, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, - 0xF8, 0x1F, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFC, - 0x00, 0x03, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, - 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x7F, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xE3, - 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x03, - 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, - 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, - 0xFF, 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xDF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, - 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, - 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, - 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x07, - 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, - 0xFF, 0xC3, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, - 0x80, 0x00, 0x3F, 0xFF, 0xFC, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x7F, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xF0, - 0x00, 0x3F, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xF8, - 0x0F, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE1, - 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x3F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, - 0x3F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0x8F, - 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xF3, 0xFF, - 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, - 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, - 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFB, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, - 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, - 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, - 0x07, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x07, - 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, - 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, - 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFE, 0x00, 0x3F, - 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xFC, 0x1F, 0xFF, - 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, - 0xFF, 0xE3, 0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, - 0x80, 0x00, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, - 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, - 0x00, 0x01, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xDF, - 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xF7, 0xFF, - 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, - 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, - 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xCF, - 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, - 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x80, 0x00, 0x1F, - 0xFF, 0xFF, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x7F, 0xFF, - 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, - 0xFF, 0xC3, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, - 0xE0, 0x07, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, - 0xF0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFB, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x7F, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xC0, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, - 0x0F, 0xFC, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xF0, 0x00, 0x1F, - 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xE0, 0x03, - 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF0, 0x00, 0x03, - 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, - 0x00, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, - 0x7F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, 0x00, 0x00, - 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, - 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, - 0x8F, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, - 0x7F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFC, 0x3F, 0xFF, - 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, - 0xC1, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0x87, - 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x7F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x80, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, - 0xFC, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, - 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x80, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, - 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x3F, - 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xF8, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFE, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF7, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, - 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x80, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, - 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x03, - 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFE, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x07, - 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFC, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, - 0xE0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, - 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, - 0xC0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x3F, - 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x01, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFE, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, - 0xEF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xF8, 0x00, 0x00, 0x01, 0xFF, 0xFE, - 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xCF, - 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xF8, 0x00, 0x00, 0x01, 0xFF, 0xFD, 0xFF, - 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xDF, 0xFF, - 0xFF, 0xFF, 0xF3, 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, 0xFD, 0xFF, 0xFF, - 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xDF, 0xFF, 0xFF, - 0xFF, 0xF1, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, - 0xFF, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, - 0xF1, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, - 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, 0xF1, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, - 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, - 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFE, - 0x00, 0x00, 0x07, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xF0, - 0x00, 0x00, 0x7F, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, 0xF0, 0x00, - 0x00, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0x00, 0x00, - 0x0F, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xFF, 0xF0, 0x00, 0x00, - 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0x80, 0x00, 0x0F, - 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xF8, 0x00, 0x00, 0xFF, - 0xFC, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x3F, 0xFF, 0x80, 0x00, 0x1F, 0xFF, - 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFC, - 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x3F, 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xC1, - 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFC, 0x1F, - 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0x81, 0xFF, - 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xF8, 0x1F, 0xFF, - 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0x81, 0xFF, 0xFF, - 0xFF, 0xFF, 0x81, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, - 0xFF, 0xF8, 0x1F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, - 0xF8, 0x0F, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0xFF, 0xFE, 0x00, 0x07, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, - 0x0F, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, - 0xFF, 0xF0, 0x00, 0x7F, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x7F, - 0xFF, 0x00, 0x07, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, - 0xF0, 0x00, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, - 0x00, 0x0F, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xF0, - 0x00, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0x80, - 0x0F, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xF8, 0x00, - 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0x80, 0x0F, - 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xF8, 0x01, 0xFF, - 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0x80, 0x1F, 0xFF, - 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFC, 0x01, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xC0, 0x1F, 0xFF, 0x80, - 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFC, 0x01, 0xFF, 0xF8, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xC0, 0x3F, 0xFF, 0x80, 0x3F, - 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFC, 0x03, 0xFF, 0xF8, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xE0, 0x3F, 0xFF, 0x80, 0x3F, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFE, 0x03, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xE0, 0x3F, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0xFF, 0xFE, 0x03, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x07, 0xFF, 0xE0, 0x7F, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x7F, 0xFF, 0x07, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x07, 0xFF, 0xF0, 0x7F, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x7F, 0xFF, 0x07, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x07, 0xFF, 0xF0, 0x7F, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0x0F, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, - 0xFF, 0xF8, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x3F, - 0xFF, 0x8F, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, - 0xF8, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, - 0x8F, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xF9, - 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xDF, - 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFD, 0xFF, - 0xF8, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xDF, 0xFF, - 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFD, 0xFF, 0xF8, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xDF, 0xFF, 0x80, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x3F, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x03, 0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x3F, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x03, 0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x1F, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x01, - 0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x1F, - 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x01, 0xFF, - 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, - 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, - 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, - 0x80, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFB, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, - 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, - 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xF0, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF9, - 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFE, 0x3F, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, - 0x1F, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xF0, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE1, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x3F, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFC, 0x1F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xF0, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0x81, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, - 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, - 0xFF, 0xF0, 0x1F, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, - 0xF8, 0x1F, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFE, 0x01, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, - 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xC1, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F, - 0xFF, 0xF8, 0x3F, 0xFF, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x1F, 0xFF, - 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xC3, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x0F, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFC, 0x7F, - 0xFF, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFE, 0x3F, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xCF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x0F, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFD, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE0, 0x7F, - 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, - 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0x83, 0xFF, - 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x1F, - 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, - 0xFF, 0xF9, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, - 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, - 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, - 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, - 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, - 0xE1, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, - 0x00, 0x0F, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFE, - 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xC0, - 0x01, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xE0, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, - 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, - 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, - 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, - 0x00, 0x01, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, - 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF3, - 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x01, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, - 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, - 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, - 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, - 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, - 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, 0xFF, - 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, - 0x00, 0x01, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, - 0x8F, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, 0xE3, - 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x07, - 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xE0, 0x1F, - 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0xFF, - 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, - 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xFF, 0x0F, 0xFF, - 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x7F, 0xFF, - 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, - 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, - 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, - 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, - 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0, - 0x00, 0x01, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, - 0xC1, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xC0, - 0x00, 0x1F, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFC, - 0x0F, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xE0, - 0x07, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xFF, - 0xF0, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xE0, - 0x00, 0x07, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, - 0xC7, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF9, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, - 0x01, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xCF, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, - 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, - 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, - 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, - 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, - 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF1, - 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, - 0x03, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x3F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xFF, - 0xE0, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xE0, - 0x00, 0x07, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, - 0x87, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFC, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF1, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, - 0x01, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0x8F, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0x80, 0x00, 0x01, - 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, - 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, - 0xFF, 0xF3, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, - 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFD, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xEF, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, - 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, - 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, - 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, - 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xBF, 0xFF, 0xFC, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFD, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, - 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, - 0xF8, 0x00, 0x1F, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, - 0xF8, 0x3F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xC0, - 0x00, 0x0F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xF0, - 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x80, 0x00, - 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF1, 0xFF, - 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, - 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, - 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF9, - 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, - 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF3, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xFC, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, - 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0x00, - 0x00, 0x0F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF3, - 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x80, 0x00, - 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xE3, 0xFF, - 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, - 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xE0, - 0x00, 0x07, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xC0, 0x00, - 0x07, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x7F, - 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x07, - 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xBF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, - 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, - 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, - 0x00, 0x03, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x7F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xC0, 0x00, - 0x0F, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x7F, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, - 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, - 0xFF, 0xE7, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, - 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xDF, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xBF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x7F, 0xFF, 0xF8, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, - 0xFD, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, - 0xEF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xCF, 0xFF, 0xFE, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x9F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x7F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xE0, 0x00, - 0x07, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0x07, - 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xE0, 0x01, - 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xF0, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF8, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFD, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xF7, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE1, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x1F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x3F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xC0, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x0F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0x80, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, - 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, - 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xC0, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, - 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xE0, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, - 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xE0, 0x00, - 0x03, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFE, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, - 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x3F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xC0, - 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, - 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x01, 0xFF, 0xFF, - 0xC0, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x7F, - 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, - 0x07, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFC, - 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xE0, - 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, - 0x80, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xF8, - 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x80, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x1F, - 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xE0, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x01, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x03, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, - 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xC0, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x7F, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x03, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x07, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x7F, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE1, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xC3, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x87, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x0F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x3F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF9, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xF3, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xE7, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xCF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xDF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xBF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x7F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF7, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xE7, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE7, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE7, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE3, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xC3, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x3F, 0xFF, 0xF0, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x1F, 0xFF, - 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xC1, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x0F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x80, - 0x00, 0x01, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, - 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x80, - 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00, 0x01, - 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x07, 0xFF, - 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0x00, - 0x7F, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x03, - 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x3F, 0xFF, 0xF0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xF0, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xE0, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xE0, 0x03, 0xFF, - 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFE, 0x00, - 0x3F, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, - 0xE0, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x03, - 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x7F, 0xFF, 0xE0, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x00, 0x00, - 0xFF, 0xFE, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xF0, - 0x00, 0x0F, 0xFF, 0xEF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xC0, 0x01, 0xFF, - 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFC, 0x00, - 0x1F, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0xCF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, - 0xC0, 0x01, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0xFF, 0xFC, 0x00, 0x07, - 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xCF, 0xFF, 0xC0, - 0x00, 0x7F, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFC, 0xFF, - 0xFC, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, - 0xCF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x00, 0x01, - 0xFF, 0xFC, 0x7F, 0xFE, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xF8, - 0x00, 0x1F, 0xFF, 0x87, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0xFF, - 0xFF, 0x80, 0x01, 0xFF, 0xF8, 0x7F, 0xFE, 0x00, 0x0F, 0xFF, 0xF8, 0x00, - 0x0F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0x87, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, - 0x80, 0x00, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xF8, 0x7F, 0xFE, 0x00, 0x0F, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0x87, 0xFF, 0xE0, - 0x00, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xF8, 0x7F, - 0xFF, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, - 0x83, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0x80, 0x03, - 0xFF, 0xF0, 0x3F, 0xFF, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xF8, - 0x00, 0x3F, 0xFF, 0x03, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, - 0xFF, 0xC0, 0x07, 0xFF, 0xF0, 0x3F, 0xFF, 0x00, 0x1F, 0xFF, 0xF0, 0x00, - 0x07, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x03, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, - 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x07, 0xFF, 0xF0, 0x3F, 0xFF, 0x00, 0x1F, - 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0x03, 0xFF, 0xF8, - 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x07, 0xFF, 0xF0, 0x1F, - 0xFF, 0x80, 0x1F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x7F, 0xFE, - 0x01, 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x07, - 0xFF, 0xE0, 0x1F, 0xFF, 0x80, 0x1F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFC, - 0x00, 0xFF, 0xFE, 0x01, 0xFF, 0xF8, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x3F, - 0xFF, 0xC0, 0x0F, 0xFF, 0xE0, 0x1F, 0xFF, 0x80, 0x1F, 0xFF, 0xE0, 0x00, - 0x03, 0xFF, 0xFE, 0x00, 0xFF, 0xFE, 0x01, 0xFF, 0xF8, 0x01, 0xFF, 0xFE, - 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x0F, 0xFF, 0xE0, 0x1F, 0xFF, 0xC0, 0x1F, - 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0xFF, 0xFC, 0x00, 0xFF, 0xFC, - 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x0F, 0xFF, 0xC0, 0x0F, - 0xFF, 0xC0, 0x3F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0xFF, 0xFC, - 0x00, 0xFF, 0xFC, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x1F, - 0xFF, 0xC0, 0x0F, 0xFF, 0xC0, 0x3F, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFE, - 0x01, 0xFF, 0xFC, 0x00, 0xFF, 0xFC, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xE0, 0x1F, 0xFF, 0xC0, 0x0F, 0xFF, 0xC0, 0x3F, 0xFF, 0xC0, 0x00, - 0x01, 0xFF, 0xFE, 0x01, 0xFF, 0xFC, 0x00, 0xFF, 0xFE, 0x03, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x1F, 0xFF, 0x80, 0x07, 0xFF, 0xE0, 0x3F, - 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x01, 0xFF, 0xF8, 0x00, 0x7F, 0xFE, - 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x1F, 0xFF, 0x80, 0x07, - 0xFF, 0xE0, 0x3F, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0x03, 0xFF, 0xF8, - 0x00, 0x7F, 0xFE, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x3F, - 0xFF, 0x80, 0x07, 0xFF, 0xE0, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0x03, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xF0, 0x3F, 0xFF, 0x80, 0x07, 0xFF, 0xF0, 0x7F, 0xFF, 0x80, 0x00, - 0x00, 0xFF, 0xFF, 0x03, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0x07, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x3F, 0xFF, 0x00, 0x03, 0xFF, 0xF0, 0x7F, - 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, - 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x7F, 0xFF, 0x00, 0x03, - 0xFF, 0xF0, 0x7F, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0x87, 0xFF, 0xF0, - 0x00, 0x3F, 0xFF, 0x07, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x7F, - 0xFF, 0x00, 0x03, 0xFF, 0xF8, 0x7F, 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, - 0x87, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0x87, 0xFF, 0xF8, 0x00, 0x00, 0x07, - 0xFF, 0xF8, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xF8, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0x87, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0x8F, 0xFF, 0xF0, - 0x00, 0x00, 0x07, 0xFF, 0xF8, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xF8, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x8F, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, - 0x8F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xF8, 0xFF, 0xFE, 0x00, 0x01, - 0xFF, 0xF8, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xCF, 0xFF, 0xE0, - 0x00, 0x1F, 0xFF, 0xCF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFC, 0xFF, - 0xFE, 0x00, 0x01, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xCF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xCF, 0xFF, 0xF0, 0x00, 0x00, 0x03, - 0xFF, 0xFC, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xCF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xCF, 0xFF, 0xE0, - 0x00, 0x00, 0x03, 0xFF, 0xFD, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFC, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xDF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, - 0xDF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFD, 0xFF, 0xFC, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xDF, 0xFF, 0x80, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x0F, 0xFF, 0xF8, - 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x01, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, - 0x80, 0x00, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, - 0x80, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, - 0xC0, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, - 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFC, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xF8, - 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, - 0xF8, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, - 0xFC, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x7F, 0xFF, 0xC0, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, - 0xFC, 0x3F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x1F, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x1F, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x0F, 0xFF, 0xF8, - 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xE0, 0x07, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xFF, 0xFC, - 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, - 0xE0, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFE, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, - 0xC0, 0x01, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, - 0x80, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, - 0x80, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, - 0xC0, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, - 0xE0, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xF0, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x3F, 0xFF, 0xF8, - 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, - 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x03, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x87, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xF0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, - 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x7F, 0xFF, 0xF0, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xC0, - 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, - 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, - 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, - 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x01, - 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0x80, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, - 0xF0, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, - 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFC, 0x00, - 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, - 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xC0, - 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xF8, 0x00, - 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, - 0xF0, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x83, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF1, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF7, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xEF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xDF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, - 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFE, 0x00, 0x07, - 0xFF, 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xFF, 0x00, - 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, - 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x07, - 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x1F, 0xFF, 0xFE, 0x00, - 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, - 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xFE, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, - 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, - 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, - 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, - 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, - 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x1F, 0xFF, 0xFE, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, - 0xFF, 0xF8, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x03, - 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, - 0xF8, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x7F, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0xFF, - 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xFF, 0xE0, 0x00, - 0x1F, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, - 0x00, - - // Pound sign (start 60325) - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x0F, - 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xE0, 0x00, - 0x0F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xE0, - 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x07, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, - 0xE0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, - 0xFF, 0xF0, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x7F, 0xFF, 0xF0, 0x00, 0x00, - 0x0F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x01, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x00, - 0x00, 0x07, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, - 0x80, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFE, - 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, - 0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - // yen-sign (start 61607) - 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE3, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x1F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0xFF, - 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xE0, 0x00, - 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, - 0xF8, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, - 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0x00, 0x00, - 0x03, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFE, - 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, - 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xC0, - 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, - 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, - 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xF8, 0x00, - 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF, - 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x01, 0xFF, - 0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, - 0x1F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF8, - 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xC0, - 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, - 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0xC0, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x81, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFF, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFE, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF3, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xDF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, - 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, - 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 - }; +//unsigned int antonio_90_c_len = 6 +const uint8_t Antonio_SemiBold90pt7bBitmaps_Gzip[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0x7d, + 0x4b, 0x8e, 0xde, 0x38, 0x97, 0x25, 0x05, 0x15, 0xa0, 0x1e, 0x34, 0xa0, + 0x1d, 0x94, 0x7a, 0x19, 0x35, 0x68, 0xb4, 0x6a, 0x29, 0xbd, 0x84, 0x5a, + 0x40, 0xa3, 0x28, 0xc3, 0x03, 0x0f, 0xbd, 0x04, 0x2f, 0x25, 0x65, 0x78, + 0xe0, 0x61, 0x2c, 0xc1, 0x4a, 0x78, 0xe0, 0x61, 0xca, 0xf0, 0xc0, 0x32, + 0x52, 0x16, 0x5b, 0xbc, 0xe7, 0x1c, 0x92, 0xfa, 0x1e, 0xf1, 0x48, 0xdb, + 0xe9, 0xf0, 0x9f, 0xf1, 0x01, 0x11, 0x94, 0x48, 0xf1, 0xf2, 0x75, 0x79, + 0xdf, 0xa2, 0x5c, 0x08, 0x5f, 0x43, 0xf8, 0x1c, 0xc2, 0xbb, 0x10, 0xfc, + 0xdd, 0x97, 0x4b, 0x08, 0xef, 0x43, 0x78, 0x19, 0xb6, 0xfe, 0x5e, 0x97, + 0xaf, 0x43, 0x68, 0xc2, 0xda, 0x85, 0xd9, 0xdf, 0x7d, 0x39, 0x86, 0xad, + 0x0e, 0x4b, 0x1b, 0xa6, 0x3e, 0x0c, 0xf7, 0xb8, 0xac, 0xc2, 0xdc, 0x84, + 0xb1, 0x0b, 0xce, 0xaf, 0xf7, 0xb9, 0xac, 0xc3, 0xd0, 0x6e, 0xae, 0x5f, + 0x5c, 0x98, 0xee, 0x73, 0xd9, 0xac, 0xae, 0x9b, 0x9d, 0x1f, 0xab, 0xe0, + 0x6e, 0xbf, 0x74, 0xf7, 0xfa, 0x35, 0xf7, 0x9d, 0xdf, 0xb3, 0xcb, 0x31, + 0xf0, 0xf7, 0x5b, 0xd8, 0x42, 0xf8, 0x12, 0xc2, 0x87, 0x10, 0x5e, 0x85, + 0xcd, 0xc7, 0xcb, 0xb7, 0x21, 0x74, 0x61, 0xf5, 0xe1, 0x63, 0xbe, 0xfc, + 0x3d, 0x84, 0x17, 0x61, 0xeb, 0xc2, 0x92, 0x2f, 0xcb, 0x59, 0xee, 0x23, + 0xbc, 0x8b, 0xf3, 0xb9, 0xd6, 0x61, 0x6e, 0xc3, 0xd8, 0x07, 0x97, 0x2e, + 0x37, 0x17, 0x96, 0x3a, 0x4c, 0x6d, 0x18, 0x74, 0xd9, 0x04, 0xd7, 0xad, + 0xce, 0xcf, 0x55, 0x18, 0x75, 0x19, 0x47, 0xb7, 0x83, 0x70, 0x3b, 0x8c, + 0xfd, 0xca, 0x07, 0x57, 0x85, 0x75, 0xbf, 0xd8, 0x01, 0x5b, 0xb6, 0x73, + 0x5d, 0x18, 0x9d, 0xdf, 0xe2, 0x63, 0x7b, 0xc1, 0x0e, 0xd9, 0x66, 0xe3, + 0xae, 0xe7, 0x83, 0x3d, 0xbf, 0x59, 0x36, 0x9e, 0xef, 0xec, 0xf9, 0xfe, + 0x9e, 0xcf, 0xcf, 0x96, 0x6d, 0xcf, 0x3b, 0x7b, 0xbe, 0xba, 0xef, 0xf3, + 0x43, 0xcc, 0xc6, 0xf3, 0x8d, 0x3d, 0xdf, 0xde, 0xf3, 0xf9, 0xd5, 0xb2, + 0xf1, 0x7c, 0x6f, 0xcf, 0xfb, 0x7b, 0x3e, 0x9f, 0x26, 0x63, 0xb3, 0x66, + 0x9d, 0xcd, 0xd2, 0x7d, 0x9e, 0xcf, 0x93, 0x31, 0x5b, 0xb3, 0xce, 0x66, + 0xe9, 0x3e, 0xcf, 0xe7, 0xc9, 0x18, 0xf2, 0xe2, 0x1c, 0x9f, 0xf7, 0x42, + 0xbc, 0x37, 0xba, 0xd8, 0x71, 0x89, 0xbf, 0x59, 0x17, 0x3b, 0x86, 0xf1, + 0xb7, 0xf6, 0xba, 0xfa, 0x3d, 0x15, 0xb6, 0xba, 0x58, 0x6e, 0x05, 0xf6, + 0x97, 0x16, 0xb3, 0xf9, 0xcb, 0x8b, 0xd9, 0xff, 0xd5, 0xc5, 0x7c, 0xd8, + 0xe2, 0x6c, 0x17, 0x17, 0xc7, 0xdf, 0xb5, 0x38, 0xdf, 0x75, 0x66, 0x6f, + 0x03, 0x36, 0x3d, 0x9a, 0x9d, 0xf6, 0x8b, 0xee, 0x9c, 0xf1, 0xda, 0xce, + 0xb9, 0x0f, 0x59, 0x13, 0x19, 0x7c, 0xf8, 0x64, 0x0f, 0xdf, 0x32, 0xd9, + 0x36, 0xb9, 0x93, 0x25, 0xed, 0x6a, 0x49, 0x65, 0x00, 0xf7, 0x47, 0x8c, + 0x92, 0xbb, 0x86, 0x4f, 0x59, 0xb5, 0x1d, 0xea, 0x62, 0x49, 0x1d, 0x7e, + 0x50, 0x55, 0x9b, 0x87, 0xfd, 0x19, 0x1b, 0xc2, 0x9e, 0xc4, 0xec, 0x2a, + 0xa2, 0xa9, 0x4d, 0xa2, 0x65, 0x47, 0x84, 0xdd, 0xe2, 0xe4, 0xda, 0x6d, + 0x1d, 0x70, 0x4b, 0x14, 0x36, 0x6c, 0x5e, 0x51, 0x18, 0xc6, 0x0a, 0x28, + 0x4e, 0xd2, 0x35, 0x20, 0x77, 0xa9, 0x81, 0xef, 0xcc, 0x9d, 0xb1, 0x3b, + 0x76, 0xce, 0x17, 0x96, 0x9d, 0x2b, 0x8f, 0xde, 0x9e, 0xdf, 0x79, 0x63, + 0x88, 0x4c, 0xfa, 0x79, 0xb0, 0x69, 0xdc, 0x59, 0x66, 0x9c, 0xd7, 0xad, + 0x45, 0x53, 0xaf, 0x63, 0x6e, 0x17, 0xf7, 0xce, 0x12, 0x3b, 0xb0, 0x6f, + 0xa6, 0xd8, 0xad, 0x8f, 0xb1, 0x03, 0x8d, 0x09, 0x3a, 0xf1, 0xa1, 0xb7, + 0x31, 0xb7, 0x8b, 0x42, 0x8f, 0xf5, 0xb2, 0x8b, 0x63, 0xf0, 0xe1, 0x53, + 0x1c, 0x52, 0x1b, 0xf9, 0xf5, 0x1c, 0x07, 0x76, 0x83, 0x87, 0xbe, 0x1c, + 0x1f, 0x7a, 0x8f, 0x87, 0x56, 0x3e, 0xf4, 0xf2, 0x14, 0xbe, 0x91, 0x85, + 0x37, 0xe8, 0xc4, 0xe2, 0xd1, 0x89, 0x36, 0x18, 0x8d, 0xfe, 0x3d, 0x18, + 0x06, 0x5b, 0xff, 0x2b, 0xeb, 0xbf, 0xcd, 0xa8, 0x0f, 0x36, 0xed, 0x0d, + 0x26, 0xd6, 0x05, 0x2c, 0x7e, 0x8b, 0xdb, 0x8a, 0xb7, 0x5d, 0x9a, 0x76, + 0xbb, 0xed, 0x51, 0x37, 0x4e, 0x17, 0x41, 0x00, 0x49, 0x6f, 0xbb, 0x6d, + 0x79, 0x1b, 0x1e, 0x72, 0x7b, 0x15, 0xf2, 0xc6, 0xdb, 0xe9, 0x78, 0x3b, + 0xe2, 0x76, 0x3d, 0xde, 0x2e, 0xbc, 0x1d, 0x70, 0x7b, 0xfb, 0xb0, 0x47, + 0xdc, 0x2e, 0x1c, 0xf6, 0x2e, 0x9e, 0xc6, 0x59, 0xf8, 0x18, 0x8c, 0xe2, + 0x6c, 0x5c, 0xd7, 0x0f, 0xb1, 0xf6, 0x8e, 0x0b, 0xb1, 0x76, 0x1b, 0xa5, + 0x2c, 0x9b, 0x77, 0x5b, 0xc1, 0x0e, 0x2b, 0xb8, 0x3f, 0xf4, 0x47, 0x84, + 0xd9, 0x47, 0xe1, 0x6c, 0x8c, 0xed, 0xfc, 0x86, 0xc5, 0xff, 0x8c, 0x75, + 0xdd, 0xb8, 0xae, 0xef, 0xb0, 0xae, 0x7f, 0x62, 0x5d, 0x03, 0xd7, 0xf5, + 0xa4, 0xea, 0x65, 0xf8, 0x37, 0xf1, 0x21, 0x93, 0xee, 0xc6, 0x58, 0xb5, + 0x37, 0xa1, 0x29, 0x2e, 0x7e, 0x5c, 0xed, 0x05, 0x78, 0x19, 0x17, 0x3f, + 0xe6, 0xda, 0xe2, 0xef, 0xa3, 0x8a, 0x8b, 0xdf, 0x47, 0xb9, 0x2e, 0x2c, + 0x7b, 0x32, 0x01, 0xd1, 0x23, 0x76, 0x1b, 0xde, 0x93, 0xdc, 0x73, 0x37, + 0x54, 0xcc, 0xc5, 0x36, 0x18, 0xb1, 0x39, 0x66, 0x6c, 0xa4, 0x0d, 0xdb, + 0x6b, 0x6f, 0xaa, 0x2f, 0x76, 0xdb, 0x82, 0x3d, 0xb8, 0x62, 0x67, 0x06, + 0x2e, 0xe4, 0x90, 0x17, 0xa3, 0xc2, 0xdd, 0x4f, 0x26, 0x25, 0x57, 0xaa, + 0x62, 0xcd, 0x0f, 0x3f, 0x76, 0xfb, 0x34, 0x4f, 0xf4, 0xb1, 0x03, 0xf2, + 0x24, 0x02, 0x64, 0x50, 0x05, 0xc6, 0x93, 0x38, 0x59, 0x77, 0x78, 0xd1, + 0x07, 0xce, 0x47, 0x6c, 0x36, 0x08, 0x08, 0x91, 0xde, 0xca, 0xd1, 0x5c, + 0x4b, 0xda, 0x55, 0x36, 0x82, 0x09, 0x1f, 0x0f, 0x8d, 0x54, 0x21, 0x57, + 0x4e, 0x8d, 0x84, 0xa2, 0xb2, 0x1a, 0xe9, 0x43, 0x6e, 0xb9, 0x4f, 0x03, + 0xce, 0x95, 0xd5, 0x48, 0x43, 0xba, 0x78, 0xd2, 0xc8, 0xc6, 0xdd, 0x53, + 0x36, 0xb2, 0x8b, 0xf6, 0xac, 0x94, 0x1b, 0x59, 0xd2, 0x2c, 0xe6, 0x91, + 0x18, 0x56, 0x9e, 0x8c, 0xc4, 0x68, 0xe5, 0x78, 0xd2, 0x48, 0xc4, 0xed, + 0xf9, 0xd0, 0x88, 0x37, 0xf4, 0x5f, 0x4f, 0xa6, 0xcb, 0x76, 0xc8, 0xa1, + 0x91, 0x06, 0x9b, 0x68, 0x2a, 0x1b, 0xb1, 0x67, 0x5a, 0x40, 0x2f, 0xd6, + 0x64, 0x44, 0xeb, 0x87, 0x35, 0x99, 0x23, 0xf4, 0xa1, 0x6c, 0xa4, 0x05, + 0x2a, 0x4f, 0x65, 0x23, 0x35, 0x78, 0xcb, 0x52, 0x36, 0x62, 0xf5, 0x08, + 0x30, 0x35, 0x62, 0xf5, 0x00, 0xb0, 0xd6, 0x3a, 0x94, 0x00, 0xd5, 0x48, + 0x09, 0x50, 0x8d, 0x94, 0x00, 0x53, 0x23, 0x05, 0xc0, 0xd4, 0x48, 0x01, + 0xb0, 0x52, 0x23, 0x25, 0x40, 0x35, 0x52, 0x02, 0x4c, 0x8d, 0x14, 0x00, + 0x53, 0x23, 0x05, 0xc0, 0xd4, 0x48, 0x01, 0x30, 0x35, 0x52, 0x02, 0x4c, + 0x8d, 0x14, 0x00, 0x53, 0x23, 0x05, 0xc0, 0xd4, 0x48, 0x01, 0x30, 0x35, + 0x52, 0x00, 0xcc, 0x8d, 0x14, 0x00, 0x53, 0x23, 0x05, 0xc0, 0xd4, 0x48, + 0x01, 0x30, 0x35, 0x52, 0x00, 0xcc, 0x8d, 0x64, 0x80, 0xb9, 0x91, 0x02, + 0x60, 0x6a, 0xa4, 0x00, 0x88, 0x46, 0xfe, 0xfb, 0x00, 0xd0, 0x1a, 0x69, + 0x41, 0xf7, 0x04, 0xd0, 0x1a, 0xe9, 0x20, 0x5c, 0x08, 0xa0, 0x35, 0xd2, + 0x81, 0x6c, 0x26, 0x80, 0xa0, 0xfc, 0xf1, 0xc1, 0x0c, 0xb0, 0x07, 0xf1, + 0xde, 0x33, 0x32, 0xc0, 0x88, 0xe2, 0xb6, 0x11, 0xe7, 0x0c, 0xb0, 0x01, + 0xe3, 0x31, 0x71, 0x44, 0x00, 0x2b, 0xf0, 0xa6, 0x60, 0xaa, 0xbf, 0x00, + 0xc6, 0x46, 0x40, 0xbf, 0x33, 0xc0, 0xde, 0x44, 0x95, 0x58, 0x39, 0x03, + 0xec, 0x4c, 0xe4, 0x8b, 0xbf, 0x37, 0x09, 0x60, 0x03, 0x9e, 0x11, 0x4c, + 0x1f, 0x20, 0xc0, 0x2a, 0xd1, 0xfa, 0x2d, 0x03, 0x84, 0x08, 0x11, 0x7f, + 0x2f, 0x13, 0x40, 0x34, 0x12, 0xa5, 0xa5, 0xf7, 0x09, 0x20, 0x1a, 0x59, + 0xf7, 0xb2, 0x35, 0x01, 0x6c, 0x4c, 0xc5, 0x32, 0xa0, 0x09, 0x20, 0x1b, + 0x89, 0x37, 0x37, 0x09, 0x20, 0xee, 0xa3, 0x24, 0xf3, 0x29, 0x01, 0xec, + 0x8d, 0x0d, 0x9b, 0xb0, 0x93, 0x00, 0x76, 0x26, 0xd7, 0x18, 0x73, 0xfd, + 0x4d, 0x00, 0xd5, 0xc8, 0xfe, 0xf0, 0x1f, 0x02, 0xc8, 0xe9, 0x8a, 0x90, + 0xfe, 0x4c, 0x00, 0xc1, 0xc8, 0x8d, 0x2a, 0x04, 0x11, 0x87, 0xde, 0x1a, + 0x35, 0xaa, 0xf0, 0x4a, 0xc4, 0x01, 0x6b, 0xb2, 0xc6, 0xc2, 0x0f, 0x48, + 0x26, 0x6b, 0x84, 0x00, 0xbf, 0x52, 0xa4, 0xc3, 0xc2, 0x47, 0x80, 0x55, + 0x64, 0xe6, 0x24, 0x5f, 0x89, 0xb6, 0xf9, 0x9d, 0xdf, 0x93, 0x7c, 0xf5, + 0x2a, 0xec, 0x76, 0x91, 0x80, 0x75, 0x12, 0x6d, 0x6b, 0x30, 0x5f, 0xbd, + 0x35, 0xba, 0x24, 0x80, 0x3d, 0x24, 0x38, 0x61, 0x97, 0x01, 0xbc, 0x31, + 0xa5, 0x62, 0xd3, 0xb0, 0x0d, 0x60, 0x14, 0x14, 0x6a, 0x00, 0x18, 0x13, + 0x40, 0x6f, 0xf2, 0xed, 0xa4, 0x7a, 0x56, 0x20, 0x16, 0xd0, 0x68, 0xb1, + 0xf6, 0x82, 0xb7, 0x64, 0x01, 0x15, 0x16, 0xcb, 0x00, 0xce, 0xe2, 0x29, + 0x58, 0x2c, 0x00, 0x14, 0x4f, 0xb1, 0x7d, 0x42, 0x80, 0x0d, 0x99, 0x94, + 0xc1, 0x52, 0x4b, 0x65, 0x23, 0x02, 0xd8, 0x43, 0x5e, 0xa8, 0x80, 0x62, + 0x00, 0xd8, 0x90, 0xf7, 0x18, 0x8a, 0x11, 0xa0, 0x63, 0xc3, 0xd6, 0x88, + 0x00, 0x76, 0x90, 0x2e, 0xec, 0x56, 0x00, 0x6b, 0x74, 0xa6, 0xc1, 0xc6, + 0x00, 0x40, 0x63, 0x7b, 0x23, 0x77, 0xbc, 0x00, 0xd6, 0xe0, 0xa2, 0x69, + 0xa7, 0x59, 0x41, 0x9f, 0xf6, 0x49, 0x02, 0xb8, 0x17, 0xac, 0x8e, 0x57, + 0x09, 0x20, 0x7e, 0x20, 0x2b, 0x02, 0x88, 0x5f, 0x45, 0xfa, 0x40, 0x80, + 0x99, 0x12, 0x15, 0x00, 0x49, 0x89, 0xc6, 0x33, 0x80, 0xad, 0x95, 0x1e, + 0x01, 0xa2, 0xf4, 0x08, 0xb0, 0x22, 0x55, 0x3b, 0x00, 0x94, 0x2a, 0x54, + 0x02, 0x04, 0xed, 0x3a, 0x02, 0x44, 0x23, 0x47, 0x80, 0xa8, 0x71, 0x04, + 0x58, 0x91, 0x16, 0x1f, 0x00, 0x4a, 0x8c, 0x2b, 0x01, 0xf6, 0x54, 0x87, + 0x4b, 0x80, 0x2d, 0x25, 0xf4, 0x12, 0x60, 0x43, 0x05, 0xb4, 0x04, 0x58, + 0x49, 0x66, 0x2f, 0x01, 0x8a, 0x25, 0x95, 0x00, 0x7b, 0x4a, 0xf1, 0x25, + 0x40, 0x34, 0x72, 0x04, 0xd8, 0x48, 0xea, 0x2f, 0x00, 0x56, 0x62, 0xa4, + 0x25, 0x40, 0x31, 0xd2, 0x12, 0x60, 0x2f, 0xe9, 0x14, 0x02, 0x4b, 0xd1, + 0x08, 0x89, 0x43, 0x38, 0x34, 0xd2, 0x15, 0x22, 0x83, 0x1a, 0x69, 0x44, + 0x15, 0x8a, 0x46, 0x4a, 0x19, 0x24, 0x15, 0x7a, 0x93, 0x96, 0xb7, 0x43, + 0x23, 0x24, 0x45, 0x43, 0xd9, 0x08, 0x49, 0xd1, 0x54, 0x36, 0x42, 0x4a, + 0xb2, 0x94, 0x8d, 0x90, 0x4a, 0x24, 0x69, 0x66, 0x02, 0xc0, 0x2c, 0x76, + 0xa9, 0x91, 0x28, 0x62, 0x55, 0x59, 0x82, 0x44, 0x05, 0x6f, 0x7a, 0xc1, + 0xb1, 0x91, 0x7d, 0x7b, 0x0d, 0xe1, 0xa4, 0x91, 0xba, 0x94, 0x2a, 0xd5, + 0x2b, 0xb0, 0x80, 0x43, 0x23, 0xd2, 0x16, 0x0e, 0x8d, 0x80, 0x2a, 0x1c, + 0x1b, 0x21, 0x93, 0x3a, 0x34, 0x22, 0x25, 0xfd, 0xd0, 0x48, 0x73, 0xa1, + 0x11, 0x57, 0x88, 0xb3, 0x69, 0xe8, 0x5d, 0x6e, 0xb8, 0x10, 0x89, 0x52, + 0xc3, 0xa9, 0x91, 0x52, 0x5a, 0x4f, 0x0b, 0xd6, 0x64, 0x21, 0x3c, 0xef, + 0x2c, 0x9f, 0xf2, 0xf2, 0xaf, 0xbe, 0xa0, 0x12, 0x24, 0x55, 0xa2, 0x2e, + 0xe4, 0x8d, 0xa2, 0x07, 0x03, 0x3b, 0xaf, 0x2b, 0xaf, 0x8e, 0x56, 0xa9, + 0xcb, 0x4d, 0xba, 0xca, 0xc3, 0xf0, 0x1a, 0x64, 0x95, 0x86, 0x5b, 0xa7, + 0xab, 0x36, 0x4d, 0x4b, 0x9f, 0xae, 0xbc, 0xd6, 0x28, 0x56, 0xf0, 0x34, + 0x13, 0x45, 0x5b, 0x3c, 0x36, 0x79, 0x20, 0xe1, 0xea, 0x92, 0x1d, 0xaa, + 0x0f, 0x32, 0x0a, 0x79, 0xd3, 0x64, 0x27, 0x27, 0x96, 0x3b, 0x5a, 0xd5, + 0x01, 0x56, 0xaa, 0x06, 0x0c, 0xca, 0x71, 0x77, 0x90, 0xd2, 0x2d, 0xb4, + 0x2c, 0xf5, 0xc6, 0xb5, 0x16, 0x27, 0xd4, 0x9e, 0x09, 0xa4, 0x21, 0xe5, + 0x1c, 0x00, 0xb8, 0x06, 0xc9, 0x1e, 0x9d, 0x18, 0xf3, 0x40, 0x70, 0x06, + 0x18, 0xe0, 0xb6, 0x33, 0x70, 0x0d, 0xc1, 0x9d, 0xf5, 0xae, 0x4a, 0xbd, + 0xab, 0xc1, 0xc3, 0x39, 0x44, 0x2c, 0x7d, 0x9b, 0x4c, 0x5b, 0x6d, 0x1a, + 0x6c, 0x67, 0xea, 0xc9, 0xc4, 0x61, 0xf7, 0x94, 0x56, 0x67, 0xa7, 0xb9, + 0x9b, 0x2a, 0x49, 0xb5, 0x63, 0x43, 0x99, 0x37, 0x8c, 0x89, 0xd4, 0x40, + 0x3e, 0xb3, 0xc6, 0xbc, 0x2c, 0x09, 0x95, 0x8c, 0x08, 0x1b, 0xb5, 0x89, + 0xbd, 0xb1, 0xa4, 0x21, 0xac, 0x9d, 0x14, 0x92, 0x85, 0xb4, 0xc7, 0x0c, + 0x35, 0x44, 0x80, 0x4f, 0xc4, 0x0f, 0x0f, 0xf9, 0xc4, 0x01, 0xa5, 0xb5, + 0x4f, 0x4b, 0xf4, 0x18, 0x84, 0x45, 0x5b, 0xc2, 0x27, 0x54, 0xc8, 0x28, + 0xdb, 0x26, 0xc4, 0x97, 0xe1, 0xc4, 0xd0, 0x28, 0xed, 0xd6, 0x4c, 0x81, + 0x0a, 0x85, 0x15, 0x15, 0x5c, 0x28, 0xa8, 0x6f, 0xa2, 0xcd, 0x42, 0xfb, + 0x3a, 0x55, 0x68, 0xd3, 0xee, 0xe9, 0x8b, 0x0a, 0x19, 0xc7, 0x79, 0xd5, + 0x84, 0x62, 0x37, 0x26, 0x2d, 0x2e, 0xeb, 0x78, 0x49, 0x57, 0xb5, 0xab, + 0xff, 0x81, 0x0e, 0xef, 0x4d, 0xfc, 0x07, 0xd0, 0x78, 0xbf, 0xfa, 0x2f, + 0xcc, 0xc4, 0x4e, 0xc5, 0x67, 0x8d, 0xbf, 0xa1, 0x38, 0xbc, 0x93, 0xbe, + 0xc9, 0x91, 0xf7, 0xf7, 0xb4, 0xe8, 0x99, 0xfa, 0x1e, 0x0d, 0x32, 0x71, + 0xbd, 0x47, 0x5a, 0x45, 0x9a, 0xb8, 0x82, 0x21, 0xae, 0x7c, 0x17, 0x06, + 0x5b, 0x89, 0x25, 0x2e, 0xda, 0xbe, 0xec, 0xf1, 0xca, 0x84, 0x8a, 0xa9, + 0x37, 0x91, 0x21, 0x2e, 0xac, 0x8f, 0x72, 0x76, 0x14, 0x3e, 0x22, 0x0e, + 0x74, 0x76, 0xe3, 0xcc, 0xdc, 0x37, 0xf5, 0xf6, 0xf4, 0x68, 0xcf, 0x4c, + 0x66, 0x33, 0xdb, 0x2a, 0x18, 0x0f, 0x5f, 0x46, 0xd7, 0x57, 0x80, 0x61, + 0x6e, 0x6e, 0xad, 0xc9, 0xcd, 0x04, 0x93, 0x85, 0xa2, 0x29, 0x44, 0xb5, + 0x38, 0x2f, 0xc0, 0x5d, 0xd8, 0x53, 0x06, 0xeb, 0xfe, 0x5c, 0x43, 0x00, + 0x34, 0x44, 0xdb, 0x70, 0x35, 0x74, 0x90, 0xf3, 0xf6, 0xfb, 0x1e, 0xec, + 0xca, 0xac, 0x8d, 0x13, 0x4d, 0x62, 0x14, 0x59, 0xac, 0x17, 0x98, 0xc8, + 0xc1, 0xaa, 0x9a, 0x09, 0xce, 0x76, 0x51, 0x1d, 0x6c, 0xb8, 0x0b, 0x2d, + 0x5e, 0x3d, 0x64, 0xa3, 0x1d, 0x80, 0xa7, 0xca, 0x32, 0x02, 0x48, 0x0b, + 0xd9, 0x7d, 0x88, 0x2b, 0x44, 0xa1, 0xdb, 0xf6, 0x43, 0x43, 0x21, 0x69, + 0x26, 0x07, 0x84, 0xa8, 0xb8, 0x10, 0x07, 0x0c, 0x5c, 0x0b, 0x93, 0xe7, + 0x64, 0x8b, 0x3b, 0x82, 0x6d, 0xf8, 0xc4, 0x54, 0x02, 0xae, 0x06, 0xf4, + 0xa9, 0xa6, 0x41, 0x6b, 0xb3, 0x4d, 0x09, 0x6c, 0x6c, 0xb1, 0x51, 0xad, + 0x9f, 0x81, 0xc6, 0xdb, 0x0e, 0xd6, 0x54, 0xeb, 0xb1, 0x99, 0xa6, 0x6c, + 0xa9, 0xa2, 0x04, 0x1b, 0x87, 0x6d, 0x04, 0x8d, 0x13, 0x80, 0xdf, 0x54, + 0xe9, 0x4a, 0x36, 0xdc, 0x74, 0xf5, 0x11, 0x52, 0x6f, 0x30, 0x2b, 0x2c, + 0xaf, 0x9e, 0x53, 0xe0, 0x33, 0xbd, 0x04, 0x58, 0xb4, 0x34, 0xe8, 0x94, + 0x11, 0x46, 0xea, 0x36, 0x03, 0xf9, 0xa5, 0x75, 0x68, 0x25, 0x7a, 0x42, + 0x9c, 0x97, 0x1c, 0x8e, 0xed, 0xc1, 0xb5, 0xe1, 0xa6, 0x70, 0xcd, 0x94, + 0x36, 0x51, 0x34, 0xe1, 0x99, 0x6e, 0x10, 0x95, 0x86, 0x4f, 0x11, 0x1b, + 0x6e, 0xa2, 0x69, 0xae, 0xdf, 0xe5, 0xe9, 0x7d, 0xff, 0xb7, 0xe1, 0xf7, + 0x28, 0x30, 0xbf, 0xd8, 0x25, 0xe9, 0xad, 0xdb, 0x35, 0xae, 0xd9, 0xef, + 0xaa, 0xd8, 0xeb, 0x88, 0x8f, 0x75, 0x98, 0xfc, 0x4e, 0x34, 0x9e, 0xed, + 0x22, 0xf1, 0xd6, 0xec, 0x7c, 0x77, 0xee, 0xf6, 0xae, 0x8e, 0xfd, 0xde, + 0x4b, 0xb7, 0x63, 0xe3, 0x8e, 0x65, 0x83, 0x9f, 0xdb, 0xb5, 0xda, 0x31, + 0x6a, 0xde, 0xb9, 0xcc, 0x4e, 0x90, 0x76, 0x3c, 0xda, 0xb1, 0xbf, 0xd9, + 0x71, 0x74, 0xe8, 0xa6, 0x7a, 0x47, 0x92, 0xa1, 0x85, 0xa0, 0x62, 0xc6, + 0xfe, 0xda, 0xf0, 0xc1, 0x66, 0xd2, 0x6c, 0x9c, 0xc1, 0x70, 0x3c, 0x1a, + 0xfc, 0xec, 0x2f, 0x8e, 0xbb, 0xe5, 0x5f, 0x97, 0xff, 0xa2, 0x38, 0x3e, + 0xf4, 0x86, 0x7b, 0x6b, 0xfa, 0xf3, 0xb6, 0x14, 0xa7, 0x7f, 0xf3, 0xd9, + 0x5f, 0x78, 0xfa, 0xfb, 0x25, 0xff, 0x2e, 0xad, 0xe5, 0xa5, 0x35, 0x07, + 0xb7, 0x14, 0x5e, 0x6c, 0x86, 0x5a, 0x3b, 0xce, 0x4c, 0xc2, 0x1f, 0xe1, + 0x53, 0xc3, 0xbf, 0x9a, 0x38, 0x17, 0x71, 0xcf, 0x70, 0xb0, 0x27, 0x4e, + 0x26, 0x46, 0x0d, 0x46, 0x2b, 0x2b, 0x20, 0xfa, 0x13, 0x69, 0xee, 0xe6, + 0x58, 0x6d, 0x08, 0x46, 0x76, 0xc3, 0xc4, 0xbf, 0x99, 0x7f, 0x0b, 0xf6, + 0xaf, 0x3d, 0x6c, 0xce, 0x98, 0xd4, 0xa9, 0xee, 0xe4, 0xaf, 0x4d, 0x7f, + 0xe1, 0xe9, 0xef, 0x5f, 0xfa, 0x0f, 0xeb, 0x7c, 0xba, 0xfe, 0x22, 0x66, + 0x1e, 0xb8, 0x52, 0x1b, 0xa3, 0x00, 0xfe, 0xf0, 0x4f, 0x38, 0x35, 0x11, + 0xd7, 0x06, 0xfc, 0x6d, 0xc4, 0xc5, 0x05, 0xb8, 0xb9, 0x25, 0xff, 0x80, + 0x97, 0x4f, 0x61, 0xbb, 0x5f, 0xda, 0x5f, 0x48, 0x87, 0x4a, 0xe9, 0x18, + 0xd3, 0x7a, 0xac, 0x77, 0xd6, 0xba, 0xcb, 0xa7, 0xd3, 0x2e, 0xe2, 0xec, + 0x69, 0x1b, 0xc9, 0xfa, 0x9e, 0xf6, 0x73, 0x8c, 0x2f, 0x8a, 0x31, 0x33, + 0xad, 0xc5, 0xce, 0x44, 0x3e, 0x31, 0x46, 0x5e, 0xb0, 0xb3, 0x90, 0x9d, + 0x27, 0x98, 0xed, 0x65, 0xee, 0x56, 0x73, 0x16, 0xae, 0xdd, 0x7e, 0xb3, + 0xa7, 0x5b, 0xb7, 0x82, 0x5b, 0x76, 0x71, 0xa0, 0xfb, 0xe5, 0xbb, 0x9d, + 0x21, 0xad, 0x2d, 0xf9, 0x27, 0x3d, 0x9b, 0x71, 0xb3, 0xc5, 0x1f, 0x95, + 0x86, 0x25, 0x39, 0x4b, 0xb3, 0xa8, 0xe9, 0xb3, 0xcb, 0x6f, 0xa1, 0x44, + 0x37, 0x95, 0xa9, 0xf2, 0x7d, 0xd6, 0x34, 0xa0, 0x22, 0x00, 0x4e, 0x47, + 0xb8, 0xd6, 0x17, 0x99, 0x27, 0xa3, 0xb0, 0x62, 0xbf, 0x48, 0x33, 0x62, + 0xbf, 0xe2, 0xfc, 0xbf, 0x5a, 0x42, 0xee, 0x37, 0xc7, 0x11, 0xa2, 0x88, + 0xb6, 0x8b, 0x6b, 0x31, 0x16, 0xc9, 0xc6, 0xa9, 0x71, 0xb7, 0x3b, 0xb3, + 0xdb, 0x99, 0xe0, 0xb2, 0xcf, 0x47, 0x9c, 0x97, 0x45, 0xf3, 0x54, 0x2f, + 0x31, 0xed, 0xe6, 0x9a, 0xf3, 0x57, 0x4d, 0x98, 0xd7, 0x6a, 0x88, 0x69, + 0x35, 0x6a, 0xbe, 0x2f, 0xac, 0xc7, 0x7d, 0xd7, 0x31, 0xa5, 0x72, 0x62, + 0x0c, 0xc6, 0xf1, 0x57, 0x4c, 0x8a, 0xcd, 0x49, 0x6f, 0x45, 0x50, 0x5a, + 0x5c, 0x4b, 0xe5, 0x8d, 0x5e, 0x86, 0xf9, 0xbb, 0x57, 0xe2, 0x9a, 0xfe, + 0x98, 0xdf, 0xf8, 0x73, 0xc6, 0x74, 0xb1, 0xd2, 0xb7, 0x8c, 0x63, 0xaa, + 0xa3, 0x70, 0xbd, 0x4b, 0xd5, 0xc0, 0x17, 0x17, 0x10, 0x89, 0x36, 0xc4, + 0x0d, 0x85, 0xd8, 0xbd, 0x3d, 0x63, 0x8a, 0xe1, 0x7b, 0x3b, 0x0d, 0xb1, + 0x0c, 0xbf, 0x73, 0xa1, 0x5d, 0xba, 0xdc, 0x75, 0x88, 0x98, 0xd1, 0x45, + 0xec, 0x8a, 0xfe, 0xc3, 0x98, 0x31, 0xec, 0x92, 0x57, 0xbb, 0x7e, 0x53, + 0x7f, 0xd2, 0xfc, 0xde, 0x7b, 0x00, 0x98, 0x21, 0xba, 0xa5, 0x17, 0x4c, + 0x14, 0x2d, 0x52, 0x56, 0x96, 0x3c, 0xaa, 0x87, 0x07, 0x69, 0x9f, 0xe2, + 0xf3, 0xc7, 0x07, 0x07, 0xe7, 0x64, 0x14, 0x6a, 0x4e, 0x1e, 0x3c, 0x02, + 0x1e, 0xae, 0x03, 0x9e, 0xaf, 0x03, 0xde, 0x6e, 0x01, 0x3c, 0x66, 0xc0, + 0xf5, 0x2d, 0x80, 0x4f, 0x87, 0x56, 0x00, 0x3e, 0x1d, 0x5a, 0x01, 0xf8, + 0x74, 0x68, 0x3f, 0x6d, 0xce, 0xfe, 0xe2, 0xd0, 0x1e, 0x3e, 0x67, 0xcb, + 0x2d, 0x80, 0xdd, 0xf5, 0x39, 0x9b, 0xae, 0xcf, 0xd9, 0x7a, 0xcb, 0x9c, + 0x0d, 0x8f, 0x70, 0xce, 0xfe, 0x22, 0x3a, 0x3c, 0x86, 0x39, 0xfb, 0x96, + 0x2d, 0x54, 0x34, 0x4b, 0xeb, 0x96, 0x51, 0x0a, 0x63, 0xaf, 0x64, 0xc6, + 0x73, 0x32, 0x1c, 0x76, 0x31, 0x59, 0x64, 0x40, 0x55, 0xac, 0x80, 0xc7, + 0x6d, 0x87, 0x5b, 0xd9, 0x75, 0x49, 0x74, 0x90, 0xbb, 0x36, 0x07, 0x62, + 0xb5, 0x20, 0x37, 0x0a, 0xf7, 0x3b, 0x03, 0x8e, 0x42, 0x53, 0x94, 0xab, + 0x62, 0xf0, 0x60, 0x84, 0xfd, 0x06, 0x56, 0x81, 0x25, 0x36, 0xd5, 0x46, + 0x8d, 0x60, 0x63, 0xac, 0x4a, 0x34, 0xd4, 0x6d, 0x8c, 0x41, 0xba, 0x81, + 0x9d, 0xe3, 0x0b, 0x18, 0x4c, 0x8c, 0x0e, 0x8f, 0x0f, 0xfd, 0x11, 0xcc, + 0x34, 0xf0, 0x35, 0x98, 0xdd, 0x30, 0xfc, 0x06, 0x6b, 0x60, 0xa0, 0xe9, + 0x20, 0xe6, 0x5a, 0xa7, 0xd7, 0x14, 0x38, 0xd1, 0x70, 0x80, 0xe8, 0xb3, + 0x4c, 0xa5, 0x8a, 0x3d, 0xf1, 0x98, 0x85, 0xa7, 0xaa, 0x4f, 0x55, 0x7f, + 0x4c, 0x55, 0xda, 0xea, 0x2e, 0xe1, 0xf0, 0x4a, 0x1c, 0x7e, 0x15, 0xce, + 0x82, 0xf9, 0x0e, 0xa1, 0x5a, 0x66, 0x0b, 0xec, 0xcd, 0xbd, 0x8a, 0x60, + 0xbe, 0xfe, 0x10, 0xaa, 0x55, 0x31, 0x54, 0x8b, 0x71, 0x86, 0x9d, 0x69, + 0xd3, 0xf1, 0x72, 0xb2, 0xee, 0xa4, 0x9d, 0x79, 0xb2, 0x5f, 0x15, 0xaa, + 0xd5, 0x23, 0x17, 0x9b, 0x7a, 0xc1, 0xad, 0xdc, 0xf7, 0x0c, 0x01, 0xa0, + 0x2d, 0x55, 0xb3, 0x40, 0x97, 0xc2, 0x90, 0xec, 0xaf, 0x8e, 0x49, 0x1d, + 0x02, 0x83, 0x71, 0x48, 0x78, 0xfa, 0xe4, 0x21, 0xed, 0x20, 0xe8, 0xcf, + 0xa4, 0x51, 0x46, 0xd8, 0x2c, 0xfe, 0xb1, 0xe5, 0x84, 0xd9, 0xf6, 0xb5, + 0xb8, 0x06, 0xb3, 0xd7, 0xce, 0x50, 0x1a, 0x7a, 0x06, 0x5c, 0x82, 0xd8, + 0x58, 0x97, 0x5f, 0x84, 0x87, 0xfd, 0xbe, 0x5a, 0xd7, 0x6d, 0x70, 0x71, + 0x30, 0x4b, 0xaf, 0x39, 0x18, 0xe3, 0x32, 0xc5, 0x91, 0xcd, 0x70, 0xd0, + 0x20, 0x36, 0x89, 0xc4, 0x70, 0x71, 0xa6, 0x0a, 0x9a, 0x5e, 0x88, 0x3e, + 0x36, 0xb4, 0x53, 0x3e, 0x3d, 0xf8, 0xf4, 0xe0, 0x83, 0x1e, 0xac, 0xa4, + 0xc6, 0xd0, 0x7b, 0x14, 0xe0, 0xb7, 0x0f, 0x52, 0x57, 0x86, 0xe4, 0xc7, + 0xe1, 0x26, 0x23, 0xd9, 0x08, 0x28, 0xc4, 0xf6, 0xc4, 0x46, 0x5c, 0x90, + 0xac, 0xf2, 0xdf, 0x21, 0x19, 0x3a, 0x3c, 0x83, 0x64, 0x86, 0xae, 0x17, + 0xb9, 0xee, 0x4e, 0x00, 0xb6, 0x06, 0x88, 0x1e, 0xfb, 0xb8, 0xff, 0x3d, + 0x83, 0x3e, 0xff, 0x3b, 0x20, 0xce, 0xb1, 0x55, 0x23, 0x26, 0x66, 0xc1, + 0x87, 0x49, 0x3f, 0xd0, 0x9a, 0xfd, 0x12, 0x04, 0xe7, 0x06, 0x5d, 0xf9, + 0x00, 0x03, 0x00, 0xc3, 0x49, 0x19, 0x3f, 0x6a, 0x04, 0xac, 0x41, 0x04, + 0x6a, 0x8d, 0x68, 0xd5, 0x8a, 0xe1, 0xa9, 0x8c, 0x47, 0xf5, 0x88, 0x5d, + 0xed, 0x11, 0xac, 0xda, 0x81, 0xa6, 0x90, 0x3c, 0x92, 0x94, 0x92, 0x76, + 0x8a, 0xce, 0x72, 0xf0, 0x1e, 0x84, 0xa6, 0xc7, 0x1c, 0x3c, 0xbc, 0xa2, + 0x35, 0x3c, 0xa0, 0xe1, 0x51, 0xf2, 0x42, 0x1b, 0xdd, 0x2c, 0xb3, 0x08, + 0x31, 0xa5, 0x07, 0x23, 0xc0, 0x1b, 0x86, 0x1a, 0xe0, 0xc3, 0x70, 0x5c, + 0x48, 0xf8, 0x36, 0x1c, 0x9c, 0x1d, 0x8e, 0x6b, 0x6a, 0x94, 0x1b, 0xe2, + 0x14, 0x65, 0x34, 0x4a, 0x6c, 0x4a, 0x28, 0xef, 0x8d, 0x87, 0x64, 0x82, + 0xc8, 0xc6, 0x64, 0x2e, 0x9a, 0x48, 0xb6, 0x11, 0x26, 0x6b, 0x91, 0xd0, + 0x23, 0xdc, 0x27, 0xf3, 0xca, 0x79, 0xd2, 0x26, 0xd7, 0xee, 0x80, 0xe6, + 0xc7, 0x9c, 0xd4, 0x89, 0x2c, 0x17, 0xd4, 0xb9, 0x3a, 0x24, 0x6a, 0x3e, + 0x25, 0x65, 0xf3, 0xfe, 0xd0, 0xfc, 0xc5, 0x5e, 0xdc, 0xbf, 0xf9, 0xb3, + 0x76, 0xaf, 0x8f, 0x3e, 0xcd, 0xf3, 0x56, 0x24, 0xed, 0xa1, 0xc1, 0x8b, + 0xf3, 0x5c, 0x5d, 0x5c, 0x3c, 0x7f, 0xcb, 0xe2, 0x5d, 0x5c, 0x35, 0x05, + 0x3c, 0x04, 0x09, 0xf2, 0x25, 0x0e, 0xa4, 0xfa, 0xf6, 0x23, 0x33, 0x7a, + 0x83, 0xe4, 0x3d, 0x92, 0x8f, 0x48, 0x16, 0x24, 0x34, 0xb1, 0x6d, 0xd8, + 0xab, 0x0c, 0x9f, 0x8a, 0x51, 0xf8, 0xf1, 0xf7, 0xf6, 0x1e, 0x15, 0x5d, + 0x8a, 0x11, 0xa8, 0x82, 0xe6, 0x47, 0xde, 0x79, 0xcb, 0xc7, 0x76, 0x40, + 0x97, 0x46, 0xca, 0xec, 0xbc, 0xe5, 0xbe, 0x08, 0x33, 0x3b, 0x9c, 0x8c, + 0x79, 0x90, 0x09, 0x48, 0x4f, 0xc4, 0xfd, 0xa3, 0x3f, 0xc7, 0x6a, 0x51, + 0x36, 0x20, 0x29, 0x31, 0x33, 0x67, 0x05, 0x41, 0x22, 0x3e, 0x39, 0xf7, + 0x30, 0x92, 0x3f, 0x8f, 0x13, 0xd4, 0x47, 0x51, 0xc3, 0xb6, 0xcf, 0xcc, + 0xbd, 0xf5, 0x02, 0x1b, 0xd7, 0xc8, 0x09, 0x24, 0x96, 0x11, 0x64, 0xc5, + 0x44, 0xa1, 0x95, 0x92, 0xd1, 0x07, 0xf6, 0xb1, 0x87, 0xdc, 0xf4, 0x85, + 0x5d, 0xbf, 0x01, 0x69, 0x35, 0x21, 0x48, 0x32, 0x91, 0x44, 0x24, 0xee, + 0x62, 0x09, 0x50, 0x92, 0xa7, 0xfa, 0x62, 0xdc, 0x1b, 0xc7, 0x9d, 0x15, + 0x9a, 0x9a, 0xd3, 0x90, 0x2b, 0x4f, 0xc5, 0xc2, 0x6b, 0x55, 0xb5, 0xe4, + 0x44, 0x0b, 0xad, 0x79, 0x9d, 0xc2, 0x65, 0x56, 0x57, 0xe0, 0xd7, 0x95, + 0xa7, 0xba, 0xf4, 0xd4, 0x25, 0x58, 0x27, 0x2d, 0x2a, 0x26, 0x84, 0xa0, + 0xeb, 0x84, 0x6c, 0x88, 0x67, 0xb0, 0xdb, 0x0a, 0x2b, 0x69, 0x43, 0x22, + 0xd3, 0x08, 0x2e, 0x79, 0xc2, 0xe3, 0x16, 0x64, 0xf0, 0x81, 0xd5, 0x6d, + 0x32, 0x76, 0x70, 0xa3, 0x59, 0x0b, 0x6d, 0x38, 0x18, 0x69, 0x15, 0x0b, + 0xd0, 0x85, 0x24, 0xba, 0x31, 0x0c, 0x22, 0x21, 0x76, 0xc9, 0xae, 0xb4, + 0xeb, 0xb5, 0xa7, 0xea, 0xb3, 0xc1, 0xdd, 0x31, 0x7f, 0xb7, 0x4d, 0x6b, + 0x77, 0x84, 0xdd, 0x1c, 0xe7, 0xf1, 0xfa, 0x52, 0x71, 0x11, 0x30, 0x02, + 0x8f, 0x11, 0x48, 0x04, 0xef, 0x80, 0x0d, 0x62, 0x07, 0x92, 0xd7, 0x7b, + 0x60, 0x83, 0x98, 0xc5, 0x77, 0xaa, 0xfc, 0x99, 0x95, 0x5f, 0xa1, 0xf2, + 0xca, 0xca, 0x1f, 0x88, 0x87, 0x3d, 0xf0, 0xf0, 0x0b, 0x55, 0x84, 0x1b, + 0x68, 0x0c, 0xc0, 0x6d, 0x8a, 0xf2, 0x0d, 0x62, 0x0e, 0xe2, 0x3c, 0x92, + 0x8b, 0xbe, 0x01, 0xc3, 0xd3, 0xae, 0x9a, 0x28, 0x0b, 0xd7, 0xf0, 0x59, + 0x2f, 0x1d, 0xf8, 0x7d, 0xdc, 0x92, 0x2f, 0xe0, 0xa4, 0xb0, 0x3e, 0x5b, + 0xcf, 0xb3, 0x4e, 0xce, 0x6c, 0x0c, 0x28, 0x29, 0xf0, 0x43, 0xc5, 0x5d, + 0x8f, 0x7d, 0x3d, 0x60, 0x60, 0xa2, 0x11, 0x59, 0x8d, 0x69, 0x51, 0x4a, + 0xd7, 0x7a, 0xc2, 0x3b, 0x21, 0x9a, 0x88, 0x76, 0x7c, 0x45, 0xd9, 0xb9, + 0x8c, 0xb5, 0xf4, 0xf6, 0x63, 0x8d, 0x16, 0xae, 0x59, 0x50, 0xc1, 0xc4, + 0x82, 0x55, 0x05, 0x43, 0x89, 0x6e, 0x95, 0xc2, 0x41, 0xfa, 0x84, 0x32, + 0x69, 0xb9, 0xb5, 0xfc, 0x69, 0xdd, 0xb5, 0xcd, 0x84, 0xa1, 0x09, 0x7d, + 0x36, 0x15, 0x8c, 0x2c, 0x58, 0x58, 0x10, 0x54, 0x30, 0xb1, 0x60, 0x55, + 0xc1, 0xe0, 0x8a, 0x20, 0x93, 0x3e, 0x24, 0xb6, 0x20, 0x16, 0x20, 0xe6, + 0x91, 0x18, 0x85, 0xa6, 0x20, 0x6d, 0x36, 0xf2, 0xa3, 0xdf, 0xc8, 0xd0, + 0x3e, 0x93, 0x5b, 0x6e, 0xe2, 0x85, 0xef, 0xc8, 0x5a, 0x3f, 0x01, 0x44, + 0xbf, 0xaf, 0xff, 0x64, 0x05, 0x37, 0x00, 0x61, 0x41, 0x08, 0x71, 0x36, + 0x37, 0xd8, 0x6d, 0x9a, 0x9d, 0x2b, 0x60, 0x5f, 0x2f, 0x00, 0xd1, 0x6d, + 0xb0, 0xeb, 0x34, 0x11, 0x37, 0x50, 0x00, 0x10, 0x1d, 0x70, 0x67, 0x2f, + 0x78, 0x0d, 0x10, 0x51, 0xbb, 0x01, 0x65, 0x80, 0x01, 0xa9, 0x89, 0xe8, + 0x33, 0x5a, 0x41, 0xc7, 0x5d, 0x5f, 0x93, 0x84, 0x4c, 0x00, 0x11, 0xdd, + 0x24, 0x88, 0x19, 0x85, 0x41, 0xa9, 0xb6, 0x28, 0x17, 0x2b, 0x00, 0x88, + 0x2e, 0xc0, 0xe0, 0x54, 0x47, 0xf9, 0xd2, 0xb8, 0xcd, 0x0a, 0x10, 0x9d, + 0x45, 0xb8, 0x58, 0x01, 0xa3, 0x3f, 0x56, 0xc5, 0x88, 0x0c, 0x06, 0xa2, + 0x8e, 0x6f, 0xc8, 0x5b, 0x80, 0xc8, 0x0a, 0x10, 0x1d, 0x23, 0x56, 0x6a, + 0x46, 0xb3, 0x44, 0x8b, 0xbd, 0x87, 0x5e, 0x38, 0x30, 0x1e, 0x65, 0x06, + 0x08, 0xcf, 0x88, 0x96, 0x2e, 0x85, 0xac, 0x30, 0x9c, 0xc5, 0x6f, 0xd2, + 0x1f, 0xc7, 0x1a, 0x7b, 0x6f, 0x06, 0x08, 0xbf, 0x49, 0xa7, 0x1c, 0x19, + 0x50, 0xc2, 0x78, 0x16, 0x6f, 0x71, 0x36, 0x70, 0x12, 0x00, 0xc7, 0xf9, + 0xc2, 0x89, 0xa7, 0x73, 0xb9, 0x65, 0x80, 0x0a, 0x5e, 0x8e, 0x44, 0x6c, + 0x59, 0x05, 0x7d, 0x74, 0x34, 0x10, 0x78, 0xbf, 0x77, 0x83, 0x37, 0x07, + 0x62, 0xe8, 0xe4, 0x10, 0xe8, 0xb1, 0x64, 0xff, 0xb4, 0x0f, 0x08, 0x37, + 0x43, 0xac, 0xf5, 0x0a, 0x10, 0x3e, 0xc0, 0x18, 0xd7, 0x9a, 0xd4, 0xca, + 0x77, 0x76, 0xa1, 0x56, 0x0f, 0x06, 0xa2, 0x85, 0x05, 0x00, 0x05, 0xde, + 0xd8, 0xe9, 0x60, 0x20, 0x5a, 0x70, 0xc9, 0xa1, 0x0a, 0x8c, 0x20, 0xe9, + 0x8d, 0x58, 0xc4, 0x51, 0x1a, 0xf1, 0x18, 0x2b, 0x30, 0x35, 0x2a, 0xec, + 0x3f, 0xfd, 0x37, 0xb8, 0x64, 0xe4, 0xcc, 0xc1, 0x2e, 0x8a, 0x8b, 0xa5, + 0xad, 0x32, 0x19, 0x26, 0x1d, 0xcd, 0x9b, 0xb2, 0x65, 0xd2, 0x28, 0xf0, + 0x8b, 0x80, 0xf0, 0x36, 0xe0, 0xf9, 0xa7, 0x25, 0x53, 0xd1, 0xb3, 0x5f, + 0x29, 0xf9, 0xbf, 0x96, 0x74, 0xd8, 0x2b, 0xc1, 0x14, 0xd0, 0xf7, 0xe0, + 0x4d, 0x1f, 0x60, 0xe5, 0xf9, 0x23, 0x52, 0x5f, 0x0c, 0xf3, 0x98, 0x0c, + 0x48, 0xc6, 0x43, 0x32, 0x1d, 0x92, 0x8b, 0x93, 0x15, 0x0d, 0x3e, 0x6f, + 0x2c, 0x89, 0x8c, 0x71, 0xf1, 0xd6, 0xca, 0xc2, 0xf8, 0x14, 0x6f, 0x84, + 0xc0, 0xde, 0x8a, 0x2d, 0x13, 0xdb, 0xc1, 0x97, 0x92, 0x2d, 0x25, 0x63, + 0x91, 0xb4, 0x48, 0x9c, 0x1b, 0xb3, 0x3e, 0x78, 0xff, 0x24, 0x3c, 0xc2, + 0x24, 0xd8, 0x30, 0xef, 0x9f, 0x6c, 0x7f, 0x57, 0xb2, 0x9e, 0x26, 0x33, + 0x44, 0xae, 0x63, 0x92, 0xd6, 0xd6, 0x17, 0xeb, 0x1e, 0x55, 0x15, 0x33, + 0xfc, 0x05, 0xbe, 0x45, 0x92, 0x92, 0x0e, 0x48, 0xc4, 0x84, 0x4a, 0x12, + 0x13, 0xe8, 0x48, 0x8c, 0x0a, 0x4c, 0x7a, 0x55, 0xfc, 0x4f, 0x3b, 0x03, + 0x0d, 0xa2, 0xd4, 0xbd, 0xa6, 0x1c, 0xb8, 0xed, 0x25, 0xa2, 0x38, 0x24, + 0x12, 0x58, 0xc0, 0xef, 0x21, 0x6c, 0xc4, 0x9a, 0x23, 0x67, 0x9c, 0xa2, + 0x7e, 0x56, 0x66, 0x1a, 0xb6, 0x82, 0x6e, 0x51, 0x8c, 0x73, 0x12, 0xdf, + 0xd0, 0xf7, 0x01, 0xfd, 0xd3, 0x0b, 0xe9, 0xec, 0x75, 0x12, 0x01, 0x79, + 0x4b, 0xbd, 0x2d, 0x0e, 0x34, 0xb6, 0x55, 0x61, 0xe3, 0xcd, 0x51, 0xed, + 0x68, 0x20, 0x55, 0x46, 0x7a, 0xd8, 0x83, 0x95, 0x3d, 0xa7, 0x75, 0x99, + 0xaa, 0xdb, 0x0b, 0xd8, 0xa9, 0x4d, 0x32, 0xbd, 0xf3, 0xf6, 0x6d, 0x32, + 0x6a, 0x97, 0xef, 0xa2, 0xaf, 0x34, 0x6c, 0xdd, 0x80, 0xd5, 0xda, 0xe2, + 0xb9, 0xa4, 0xe6, 0x7d, 0xfb, 0x6d, 0xd6, 0x2d, 0xbe, 0xf3, 0xed, 0xff, + 0xb6, 0xdb, 0x8a, 0x26, 0x21, 0x6f, 0x2c, 0x39, 0xba, 0xaf, 0x66, 0x18, + 0xc3, 0xe2, 0x74, 0x6f, 0xc5, 0x74, 0x97, 0xaa, 0x72, 0x80, 0xf5, 0xcd, + 0x16, 0x05, 0xc9, 0xc6, 0xdb, 0x09, 0x89, 0x0e, 0xd8, 0xd0, 0x09, 0x1a, + 0x34, 0x06, 0x2c, 0xf9, 0xd6, 0x96, 0x2d, 0x40, 0x4f, 0xbf, 0xc1, 0x1a, + 0xad, 0x34, 0x76, 0x71, 0x62, 0x37, 0xce, 0xc6, 0x1f, 0x54, 0x98, 0xcb, + 0xdb, 0x86, 0xb7, 0xfe, 0xf4, 0x36, 0x6b, 0x35, 0xf7, 0xbd, 0xad, 0x79, + 0xdb, 0xff, 0xe2, 0xb7, 0xd2, 0xe2, 0xf2, 0x2d, 0xb5, 0x37, 0x9a, 0x2f, + 0xaf, 0xde, 0x9a, 0x37, 0x12, 0x3c, 0x6a, 0x48, 0xb7, 0xb4, 0x6a, 0xb4, + 0xe9, 0xf8, 0x84, 0xf7, 0xd8, 0xa5, 0x31, 0xac, 0x2a, 0x36, 0xf0, 0x1a, + 0x86, 0xd5, 0xe8, 0x84, 0x89, 0xdb, 0x72, 0xbf, 0x8d, 0x87, 0x2e, 0x2d, + 0x97, 0x71, 0x44, 0x02, 0x05, 0xb6, 0xf3, 0x26, 0x87, 0x0c, 0x36, 0xfb, + 0xca, 0xbd, 0x4f, 0x3b, 0x70, 0xf2, 0xcc, 0xe2, 0xa9, 0xec, 0x9e, 0x6a, + 0x90, 0x9b, 0xd4, 0x7a, 0x7f, 0x34, 0xe8, 0xe1, 0x28, 0xa5, 0xf0, 0x18, + 0x7e, 0x52, 0xf3, 0x16, 0x6e, 0x35, 0xa9, 0x7f, 0xb6, 0x03, 0x65, 0x19, + 0x20, 0x3b, 0x4a, 0x16, 0x82, 0x2b, 0x8f, 0x37, 0x57, 0x1e, 0xf7, 0x67, + 0x8f, 0x8f, 0x97, 0x1f, 0x5f, 0x4f, 0x1e, 0x9f, 0x2e, 0x3f, 0xbe, 0x7d, + 0x9f, 0xc7, 0x1f, 0xd8, 0x99, 0xc3, 0xe3, 0xe7, 0x33, 0x23, 0x45, 0xfa, + 0x7b, 0x4c, 0x64, 0x9d, 0x3b, 0x75, 0xf2, 0xb8, 0x3a, 0x75, 0xed, 0xf1, + 0xe1, 0xf2, 0xe3, 0x87, 0x56, 0x4e, 0x1f, 0x57, 0xa7, 0xfa, 0xab, 0xcb, + 0x35, 0xf1, 0xf1, 0x91, 0x8f, 0x5f, 0x6c, 0x25, 0x5b, 0x08, 0x2e, 0xb6, + 0xd2, 0x9d, 0xb5, 0xa2, 0x6a, 0x77, 0xb4, 0xa2, 0x6a, 0xd7, 0x5a, 0xb9, + 0x58, 0xed, 0xbc, 0x95, 0x8b, 0xd5, 0x64, 0x21, 0xb9, 0xde, 0xb9, 0x43, + 0xb5, 0x26, 0x57, 0xbb, 0x63, 0x4c, 0x0b, 0x5b, 0x39, 0xad, 0x76, 0x9f, + 0xd6, 0x6e, 0xa9, 0xc6, 0xb1, 0x85, 0xfb, 0x8c, 0xad, 0xb9, 0x5a, 0x4d, + 0x6c, 0x55, 0x72, 0xd0, 0xb5, 0x56, 0xad, 0x5a, 0x7e, 0x15, 0x2f, 0x24, + 0x97, 0x99, 0x4c, 0x94, 0xf2, 0x99, 0xc9, 0x0a, 0x37, 0x27, 0xdb, 0x15, + 0x88, 0x29, 0x6f, 0x75, 0x66, 0x8f, 0xde, 0x01, 0x19, 0x44, 0x5b, 0x3d, + 0xb2, 0x65, 0x35, 0xeb, 0x91, 0x4d, 0x73, 0x77, 0x24, 0xc8, 0x91, 0x5c, + 0x8f, 0xf0, 0xb0, 0x99, 0xeb, 0xcc, 0x99, 0xe0, 0xb8, 0x92, 0xa8, 0x47, + 0x1a, 0xbf, 0x75, 0x10, 0xff, 0xcc, 0xdc, 0x5d, 0x9b, 0xdc, 0x13, 0x75, + 0x79, 0x1d, 0xd4, 0xf3, 0x91, 0x86, 0xb4, 0x57, 0x30, 0xf6, 0x49, 0xfe, + 0x21, 0xfb, 0x30, 0x37, 0xbe, 0xeb, 0xe1, 0xd5, 0xaf, 0x68, 0xc8, 0x6e, + 0x69, 0xc8, 0xe6, 0x91, 0x2e, 0x35, 0x5d, 0x68, 0x99, 0xc7, 0x8b, 0x33, + 0xfd, 0x06, 0x09, 0xf1, 0x2b, 0xcd, 0xdd, 0x64, 0xfd, 0x62, 0x58, 0x77, + 0x56, 0x3e, 0xb4, 0x4c, 0x81, 0x6c, 0xa3, 0xfd, 0x5d, 0xdd, 0x7e, 0xc9, + 0x6e, 0x97, 0x36, 0x4a, 0x13, 0x7c, 0xcc, 0x3e, 0x61, 0xda, 0xa7, 0xe4, + 0x44, 0xb3, 0x51, 0xae, 0x14, 0x8f, 0x4c, 0x4e, 0xa7, 0x31, 0x64, 0xa6, + 0x07, 0xd1, 0xec, 0x43, 0xbd, 0xbd, 0x42, 0x1e, 0x2d, 0x8f, 0x23, 0x5e, + 0xae, 0x8a, 0xf2, 0xaa, 0x9d, 0x03, 0x12, 0xff, 0xe8, 0x49, 0x08, 0xe0, + 0x97, 0xc5, 0x0a, 0xd2, 0xbd, 0x17, 0xf2, 0x3a, 0xf7, 0xb8, 0x95, 0xa0, + 0xdc, 0xc5, 0x94, 0xfc, 0x4f, 0x1e, 0x42, 0xc5, 0x0e, 0x9f, 0x78, 0x3b, + 0xd4, 0x84, 0x79, 0x3b, 0xa2, 0x25, 0xb2, 0x81, 0xc4, 0x6f, 0xdb, 0xc1, + 0xdb, 0x4b, 0x56, 0xd6, 0x99, 0x0e, 0x4f, 0x3a, 0x74, 0x26, 0x62, 0x83, + 0x11, 0x09, 0x0f, 0xae, 0x5b, 0x53, 0x36, 0xee, 0x60, 0xdc, 0x31, 0x17, + 0xaa, 0x59, 0x6f, 0xcd, 0xad, 0x21, 0x29, 0xf8, 0x0d, 0x23, 0x19, 0x7a, + 0x4c, 0x98, 0x8e, 0x62, 0xd2, 0x6c, 0x73, 0x9d, 0xaf, 0xb8, 0x35, 0x02, + 0x97, 0xaa, 0xd4, 0x04, 0xee, 0x74, 0x6b, 0x84, 0x47, 0x53, 0x39, 0xa3, + 0xe7, 0x01, 0xc3, 0x28, 0x31, 0x7d, 0xa2, 0x5e, 0xf3, 0x8a, 0x7e, 0x20, + 0x4e, 0x98, 0x04, 0x29, 0xc6, 0x80, 0xcd, 0x94, 0x71, 0x9e, 0x43, 0x4f, + 0x89, 0x12, 0x54, 0xdc, 0xbc, 0x51, 0x8e, 0x7e, 0x16, 0x0c, 0xc3, 0xac, + 0x37, 0x1d, 0x52, 0xee, 0x67, 0x1d, 0xc2, 0x35, 0x60, 0x9d, 0x93, 0x46, + 0x24, 0x11, 0x0a, 0xbb, 0x7f, 0x12, 0x92, 0xa0, 0x38, 0xc7, 0xe7, 0x48, + 0xe4, 0xed, 0x30, 0xb0, 0x64, 0xd3, 0xf5, 0xc9, 0x2a, 0x0c, 0xb2, 0xd7, + 0x82, 0x4b, 0x1d, 0xa3, 0xe9, 0x9a, 0x40, 0xf3, 0xa8, 0x81, 0xaa, 0xca, + 0xb9, 0xe2, 0x1c, 0xcd, 0x92, 0xd9, 0x84, 0x9c, 0xe8, 0x1d, 0x91, 0x92, + 0xa6, 0x7a, 0x91, 0xa6, 0x11, 0x23, 0x93, 0x5d, 0x5f, 0xa2, 0x22, 0xc7, + 0x5b, 0xe3, 0x72, 0xc2, 0x18, 0x6d, 0xce, 0x9a, 0x1c, 0x4d, 0xe7, 0x81, + 0xf8, 0xdc, 0x94, 0x97, 0x4e, 0x7e, 0xb2, 0xed, 0x4f, 0x3a, 0x34, 0x27, + 0x75, 0xa1, 0x03, 0x45, 0x91, 0xf5, 0x9f, 0x7d, 0x97, 0x97, 0xa3, 0x5c, + 0xee, 0x2c, 0x5b, 0xfb, 0x90, 0x89, 0xf0, 0x90, 0x86, 0xdd, 0x17, 0x13, + 0x3a, 0xa2, 0xea, 0xf2, 0x78, 0xab, 0x2e, 0x89, 0xa8, 0x08, 0x03, 0xca, + 0xb1, 0x0e, 0x07, 0x4d, 0x41, 0x4a, 0xd6, 0x88, 0x08, 0x87, 0x25, 0xc5, + 0x34, 0x48, 0x61, 0x38, 0x9b, 0xcb, 0x57, 0x41, 0x51, 0x5d, 0x23, 0x96, + 0x61, 0x91, 0x32, 0x57, 0x9b, 0xaa, 0x67, 0xb8, 0xcc, 0x75, 0xa5, 0xff, + 0x57, 0xc7, 0x27, 0xbe, 0x40, 0x42, 0x2b, 0x96, 0x94, 0x47, 0xea, 0x92, + 0x52, 0x1b, 0xc8, 0xb4, 0x68, 0xba, 0x90, 0xcb, 0x76, 0xc0, 0xad, 0x31, + 0xbe, 0x3f, 0x80, 0x1e, 0x3c, 0x87, 0x6c, 0x01, 0xa5, 0x9f, 0x25, 0xe2, + 0x91, 0xeb, 0x76, 0xc9, 0xff, 0x38, 0x80, 0x57, 0xcf, 0x40, 0x6e, 0xb9, + 0x52, 0x46, 0xec, 0x80, 0x85, 0x9c, 0xfa, 0x5e, 0x55, 0xbd, 0x08, 0xc0, + 0x7a, 0x01, 0xe5, 0x3e, 0x24, 0xc6, 0x38, 0x26, 0xd7, 0x99, 0x87, 0x73, + 0xe4, 0x9b, 0xaa, 0xae, 0x44, 0xf4, 0x97, 0xd8, 0xe1, 0x1f, 0x93, 0x82, + 0xb6, 0x90, 0x2e, 0x93, 0x4c, 0x67, 0xaa, 0xcd, 0xa3, 0xf7, 0x38, 0x35, + 0x8d, 0x05, 0xab, 0x0e, 0xa2, 0xf8, 0x60, 0x00, 0x03, 0x4c, 0x26, 0xc6, + 0x35, 0x30, 0xb3, 0x27, 0x07, 0xfe, 0x4d, 0xd8, 0xdb, 0x8b, 0xfc, 0xea, + 0x64, 0x3c, 0xda, 0xea, 0x56, 0x67, 0x3b, 0x72, 0xa9, 0xe9, 0xe0, 0x6e, + 0x14, 0xd1, 0x00, 0x45, 0xb1, 0xe9, 0xf6, 0x87, 0xf0, 0x09, 0x57, 0xcb, + 0xc9, 0xfb, 0xd7, 0x7e, 0xa0, 0x5d, 0x7f, 0xef, 0xaf, 0xff, 0xcb, 0xbd, + 0xb5, 0xad, 0xf1, 0xa7, 0x09, 0x46, 0xbf, 0xc5, 0xfd, 0xf6, 0xc0, 0xcb, + 0xe9, 0x27, 0x0c, 0xd6, 0x7e, 0xed, 0x37, 0x8c, 0x38, 0x04, 0x1e, 0x8a, + 0x3c, 0xf4, 0xbb, 0x28, 0xba, 0xe8, 0x58, 0x69, 0x65, 0xd9, 0xe1, 0xc8, + 0xc8, 0xb2, 0x43, 0xa7, 0x87, 0x8e, 0x59, 0xf6, 0x62, 0x53, 0xca, 0xf2, + 0xbb, 0x1c, 0x83, 0x2c, 0x9c, 0x31, 0x6d, 0x59, 0xec, 0xdd, 0x60, 0xff, + 0x47, 0xfb, 0x3f, 0xc5, 0x7f, 0x15, 0x54, 0x20, 0xc8, 0xe1, 0xd8, 0xc5, + 0x74, 0x7c, 0x62, 0x30, 0x83, 0xfd, 0x8f, 0x15, 0x3a, 0x9b, 0xd3, 0x8e, + 0x26, 0xcb, 0xc5, 0x89, 0x1d, 0x42, 0xa8, 0xf7, 0x86, 0xb8, 0x41, 0x41, + 0x6c, 0x23, 0x7d, 0xac, 0x70, 0xa8, 0x22, 0xd4, 0xa0, 0x0e, 0x62, 0xc1, + 0xf0, 0x3b, 0xcf, 0xfc, 0xdf, 0x05, 0xb1, 0xe4, 0x91, 0xb6, 0xd0, 0x3e, + 0xc8, 0xbd, 0x22, 0x97, 0x73, 0x90, 0x11, 0x14, 0x4c, 0x7b, 0x95, 0x4f, + 0xb7, 0x93, 0x9b, 0x7a, 0x96, 0xfa, 0x80, 0xcd, 0x42, 0x7a, 0x25, 0xf6, + 0x3d, 0x58, 0x6f, 0x9d, 0xf5, 0x1f, 0x23, 0x9a, 0x9c, 0xf8, 0x37, 0xa8, + 0x16, 0x95, 0xc0, 0xc0, 0x21, 0x05, 0x27, 0x3f, 0x73, 0x6a, 0x21, 0xc8, + 0xe9, 0x5b, 0x85, 0xc4, 0x1d, 0xb5, 0x55, 0xeb, 0x32, 0xab, 0x2b, 0xb3, + 0xd0, 0x65, 0x64, 0x35, 0x41, 0x7a, 0xca, 0x26, 0xab, 0x2f, 0x20, 0xd2, + 0x12, 0x8c, 0x21, 0x35, 0xd6, 0x07, 0xba, 0xc1, 0x7b, 0xd1, 0x83, 0x21, + 0x8d, 0xc9, 0x5b, 0x47, 0x71, 0xb4, 0x04, 0xc3, 0x81, 0x3c, 0x02, 0x9a, + 0x6c, 0xe1, 0x78, 0x7a, 0x21, 0xb5, 0x44, 0x5b, 0xe9, 0xc6, 0x56, 0xda, + 0xc1, 0xd7, 0xf5, 0x1f, 0xf6, 0xff, 0xdf, 0xbe, 0x0d, 0x4b, 0x2f, 0xff, + 0xee, 0xb1, 0x3b, 0x1e, 0xfc, 0xfb, 0x11, 0xfd, 0xb4, 0xa9, 0x71, 0xff, + 0x59, 0x4c, 0xc7, 0xbf, 0xdb, 0xff, 0x16, 0xb3, 0x05, 0x55, 0x96, 0x6c, + 0xcf, 0xb2, 0x18, 0x3e, 0x06, 0x77, 0x3e, 0x5c, 0xf4, 0x96, 0x25, 0xa7, + 0x3b, 0xfc, 0xe5, 0xc4, 0xbb, 0x15, 0xbd, 0x76, 0x86, 0x0e, 0x53, 0x81, + 0xa8, 0x09, 0x5b, 0xb0, 0x05, 0x6e, 0x47, 0x8d, 0xe1, 0x7a, 0x56, 0xc4, + 0xbf, 0xb5, 0xcc, 0xd2, 0x66, 0x63, 0x00, 0x04, 0xe4, 0xd1, 0x4a, 0x22, + 0xec, 0xe6, 0x64, 0x67, 0xa9, 0xb4, 0x1d, 0x46, 0xa7, 0x2d, 0x50, 0x17, + 0x68, 0xa4, 0x93, 0x5e, 0x72, 0x85, 0x8e, 0x6e, 0xfc, 0x58, 0xa1, 0x97, + 0xf0, 0x34, 0xcb, 0xa6, 0x68, 0xdd, 0xa8, 0x8a, 0xff, 0x40, 0xf9, 0x26, + 0xfd, 0x9f, 0xe9, 0x0d, 0xc1, 0x7f, 0x0c, 0xb4, 0x0f, 0xc7, 0x0d, 0xae, + 0x6d, 0xde, 0x07, 0x6e, 0x1d, 0xe8, 0x19, 0x9b, 0xf6, 0x41, 0xad, 0xdd, + 0xb0, 0x38, 0x99, 0x1b, 0x3c, 0x49, 0x10, 0xe2, 0x1b, 0x26, 0x27, 0x02, + 0xd5, 0x90, 0x59, 0x0e, 0x36, 0x54, 0x5b, 0x64, 0x9a, 0x1d, 0xec, 0xff, + 0xff, 0x29, 0x56, 0xfa, 0x7f, 0xda, 0xff, 0x7f, 0x13, 0x92, 0x75, 0x9c, + 0x05, 0x82, 0x67, 0xbb, 0x12, 0x0b, 0x17, 0xc9, 0x97, 0x14, 0x84, 0x63, + 0x87, 0xc5, 0xd8, 0x91, 0x47, 0x99, 0x9d, 0xa2, 0x17, 0x3d, 0x00, 0x53, + 0x6f, 0xc9, 0x33, 0xfb, 0xbf, 0x41, 0x3a, 0x88, 0x5a, 0x4c, 0x30, 0xdd, + 0x26, 0xbe, 0x5c, 0xfb, 0x06, 0x56, 0x86, 0x38, 0xd7, 0x34, 0x1b, 0x40, + 0x4f, 0xac, 0x60, 0x25, 0x68, 0x20, 0x3a, 0x32, 0x06, 0x8e, 0xef, 0xbf, + 0x20, 0x5e, 0xae, 0x56, 0x51, 0xf0, 0xd2, 0x97, 0xa9, 0xe2, 0x4a, 0x2b, + 0xa3, 0xae, 0xf6, 0x68, 0x8a, 0xe8, 0xf0, 0xe2, 0x3c, 0x8f, 0x09, 0xc5, + 0xc8, 0x4a, 0x48, 0x7e, 0x1b, 0x06, 0xc0, 0x40, 0xe8, 0xf4, 0x17, 0x8a, + 0x68, 0xb8, 0xba, 0x04, 0x09, 0x46, 0x29, 0xda, 0xa2, 0xda, 0xf2, 0xf1, + 0x2a, 0x69, 0x68, 0xa3, 0x13, 0x4d, 0xe5, 0xfe, 0x68, 0x01, 0xdd, 0xdb, + 0xe3, 0x4d, 0x52, 0xfb, 0xe6, 0xe4, 0xc2, 0xeb, 0x93, 0x55, 0x9d, 0xda, + 0x9c, 0x69, 0xf5, 0xda, 0x80, 0x13, 0x28, 0x54, 0x0a, 0xc5, 0x6b, 0x53, + 0x25, 0xe0, 0x2b, 0x29, 0xc3, 0x52, 0x8e, 0x77, 0xc2, 0x00, 0x06, 0x1b, + 0x00, 0xfd, 0xbf, 0xcb, 0x8f, 0x2f, 0xfa, 0x09, 0xbf, 0x56, 0xa1, 0x51, + 0x8c, 0xf0, 0x1d, 0x5d, 0x0a, 0xa9, 0x78, 0x94, 0x45, 0xec, 0xf4, 0x54, + 0x8e, 0x80, 0x16, 0xc8, 0xf8, 0x6b, 0xb0, 0xd4, 0xfc, 0x19, 0x9e, 0x0f, + 0xf9, 0xb9, 0x7c, 0x27, 0x0d, 0x0c, 0x77, 0xdd, 0xe1, 0x0e, 0x37, 0xa1, + 0xac, 0xa5, 0x3b, 0x8f, 0x9b, 0x2d, 0xb7, 0x15, 0x8a, 0x20, 0x39, 0xfb, + 0x2d, 0x05, 0xf8, 0x20, 0x2d, 0x0b, 0x14, 0x03, 0xbd, 0xf6, 0xe2, 0x03, + 0x1c, 0x23, 0x22, 0xba, 0xb8, 0xa9, 0xca, 0xcd, 0x42, 0xc8, 0x9d, 0x38, + 0xd8, 0x88, 0xbb, 0x2c, 0xd6, 0x71, 0x52, 0x64, 0x1c, 0x07, 0xd7, 0x68, + 0x72, 0x5f, 0x87, 0xec, 0x42, 0x70, 0xd4, 0x5d, 0x0c, 0x86, 0xab, 0xb1, + 0x5f, 0x36, 0xcd, 0xd9, 0x52, 0x4c, 0xaf, 0x82, 0x99, 0x34, 0x33, 0x83, + 0x4b, 0x07, 0xb0, 0x99, 0x7c, 0xd6, 0xa7, 0xb9, 0x87, 0xe9, 0x22, 0x4d, + 0xe8, 0x52, 0x3c, 0x88, 0xf3, 0xec, 0xb5, 0x60, 0x08, 0xe7, 0x1a, 0x32, + 0x90, 0x74, 0x36, 0xaf, 0xc9, 0x45, 0xe9, 0xd0, 0x37, 0x03, 0x02, 0x49, + 0xb0, 0xb3, 0x91, 0x4c, 0x3a, 0x74, 0xd1, 0xc4, 0xbb, 0xa1, 0xe5, 0x9e, + 0x8f, 0xcc, 0x36, 0xba, 0xde, 0x46, 0xf1, 0xa4, 0x10, 0x83, 0xd6, 0xa4, + 0x9b, 0x99, 0x90, 0xbd, 0xc8, 0x7a, 0x60, 0x02, 0xfa, 0xa4, 0x48, 0x00, + 0x8b, 0x83, 0x1b, 0x64, 0x3c, 0x8b, 0x12, 0xfc, 0x9a, 0xbc, 0x73, 0x51, + 0x87, 0x94, 0x89, 0x7a, 0xb0, 0xd0, 0xba, 0xe4, 0xda, 0x6f, 0xe3, 0x07, + 0x52, 0xf8, 0x60, 0xd4, 0x36, 0x6b, 0x30, 0x9c, 0x21, 0x42, 0xf6, 0x76, + 0x0e, 0x18, 0x4e, 0x56, 0xdd, 0x15, 0xd2, 0xd9, 0x9b, 0xe5, 0x71, 0x8c, + 0x3d, 0x8c, 0xa1, 0x7e, 0x1b, 0xe7, 0x29, 0xc6, 0xaf, 0x34, 0x66, 0x5e, + 0x8a, 0xb2, 0x36, 0xaa, 0x8d, 0x36, 0xbd, 0x56, 0x6d, 0xe1, 0x30, 0x50, + 0xed, 0x7d, 0xe0, 0xf7, 0x51, 0x0c, 0xea, 0x4b, 0xe3, 0x6b, 0x83, 0x55, + 0xeb, 0xb7, 0x28, 0xdc, 0xf8, 0x0d, 0x72, 0xff, 0x5e, 0x2d, 0xea, 0xab, + 0xb3, 0x55, 0xab, 0xf7, 0x6a, 0x0b, 0x38, 0xac, 0x55, 0x7b, 0x65, 0x06, + 0x5d, 0xc7, 0x6a, 0xde, 0x1e, 0x67, 0xb5, 0x2f, 0xe6, 0xf9, 0x9c, 0x58, + 0xed, 0x33, 0xfa, 0xce, 0x6a, 0xef, 0xa2, 0x7d, 0x6a, 0x63, 0xb5, 0x9d, + 0x77, 0xd9, 0xa2, 0xa0, 0xda, 0x57, 0xd3, 0x0d, 0xc6, 0x54, 0x2d, 0xea, + 0x05, 0xae, 0xac, 0xb6, 0x1e, 0xaa, 0xcd, 0xac, 0x06, 0x26, 0x83, 0x6a, + 0x15, 0x79, 0x0d, 0xaa, 0x41, 0x32, 0x0a, 0xac, 0xd6, 0x81, 0x29, 0x4d, + 0xa8, 0xe6, 0x51, 0x6d, 0x28, 0xab, 0x6d, 0x87, 0x6a, 0xcb, 0xa5, 0x6a, + 0xa1, 0xac, 0x56, 0x1f, 0xaa, 0xb5, 0x87, 0x6a, 0xfd, 0xdf, 0x5b, 0x6d, + 0x3b, 0x54, 0xfb, 0xf3, 0x50, 0xed, 0x8f, 0x43, 0xb5, 0xdf, 0x0e, 0xd5, + 0x7c, 0x59, 0xed, 0x4b, 0x31, 0x93, 0xf5, 0x2e, 0x65, 0x60, 0x26, 0x2b, + 0x43, 0xae, 0x8e, 0x33, 0xd9, 0x18, 0x72, 0x59, 0x35, 0x04, 0x65, 0x56, + 0xe1, 0xa3, 0xd6, 0xcd, 0x47, 0xe4, 0x7a, 0x6b, 0x8f, 0xe7, 0x6a, 0x93, + 0xad, 0x5b, 0xc4, 0x7d, 0xbf, 0x78, 0xc3, 0xc5, 0x88, 0xe9, 0xe3, 0xde, + 0xaa, 0x99, 0xa4, 0x6c, 0xef, 0xec, 0xc5, 0xcf, 0x89, 0x25, 0x76, 0xec, + 0xea, 0xd6, 0x12, 0xb9, 0xe2, 0x26, 0xb7, 0x6f, 0x09, 0x91, 0x1a, 0x8c, + 0x4d, 0x34, 0x7d, 0xd9, 0xe2, 0xd7, 0x9f, 0xe3, 0x06, 0xa9, 0xad, 0xed, + 0x7d, 0x95, 0xde, 0x45, 0x45, 0x58, 0xdf, 0xa1, 0xf0, 0x7e, 0x6d, 0xa2, + 0x91, 0x66, 0x34, 0x6a, 0xb0, 0x44, 0xa3, 0x8e, 0x6d, 0xcc, 0xb8, 0x45, + 0xdf, 0xc7, 0xd9, 0xc1, 0x59, 0xbe, 0xfb, 0xfe, 0x8c, 0xe1, 0x16, 0x73, + 0xcd, 0x45, 0x37, 0xd9, 0x6c, 0x68, 0x29, 0xaf, 0x46, 0x93, 0x1b, 0xc8, + 0x40, 0x5c, 0x94, 0x31, 0xc0, 0x92, 0x66, 0x04, 0xd6, 0xbc, 0x44, 0x83, + 0xbe, 0xa2, 0x12, 0x4f, 0x14, 0xda, 0x9c, 0x4e, 0x58, 0x1e, 0x5b, 0x68, + 0xb4, 0x66, 0x5b, 0xda, 0x40, 0x7a, 0x7b, 0x88, 0x0c, 0x38, 0x07, 0xc9, + 0x51, 0xcc, 0x1d, 0x8d, 0xa0, 0xd6, 0xf4, 0xd0, 0x80, 0x84, 0x66, 0x5a, + 0x09, 0xc5, 0x73, 0xa2, 0xd2, 0xfa, 0xbf, 0x44, 0x7e, 0xbb, 0x44, 0x1d, + 0x4b, 0x5d, 0x2b, 0x9d, 0x07, 0x4a, 0xea, 0x58, 0xda, 0x79, 0xba, 0x82, + 0xb1, 0x95, 0xe4, 0x15, 0x40, 0xc6, 0x7c, 0x53, 0x1d, 0x40, 0x36, 0x07, + 0x90, 0xed, 0x09, 0xc8, 0xa5, 0xb8, 0xf3, 0x87, 0x06, 0x0e, 0x76, 0x97, + 0xea, 0x70, 0xd7, 0x1c, 0x9a, 0xcb, 0x2e, 0xbe, 0xd1, 0x25, 0x0e, 0xb5, + 0x5f, 0xa0, 0x97, 0xb8, 0x83, 0x39, 0xc2, 0xf3, 0x4e, 0xcc, 0x9b, 0x22, + 0xdc, 0xe4, 0x52, 0xb4, 0x76, 0x27, 0xd1, 0x27, 0xe0, 0xd3, 0x35, 0xb4, + 0x5f, 0x94, 0x2c, 0x33, 0x19, 0x65, 0x26, 0x8c, 0x20, 0x14, 0x3c, 0x39, + 0xdd, 0x39, 0x36, 0x7e, 0x81, 0xef, 0xce, 0x87, 0x3b, 0x57, 0xde, 0x4d, + 0xe5, 0x5d, 0xe6, 0x65, 0xf9, 0x39, 0xb6, 0x9e, 0x06, 0x5f, 0x87, 0xc3, + 0xbc, 0xf4, 0x57, 0x84, 0xb3, 0xba, 0x90, 0x04, 0x00, 0xb6, 0x2b, 0x7a, + 0x85, 0x25, 0xf0, 0xea, 0x99, 0x40, 0x56, 0x6a, 0x48, 0xf1, 0x67, 0x36, + 0x0f, 0x69, 0x98, 0xaa, 0x2d, 0xdf, 0x6f, 0xf2, 0xe2, 0x66, 0xc1, 0x42, + 0x7e, 0xdc, 0xe4, 0x91, 0xcd, 0x53, 0x36, 0x6a, 0x64, 0x03, 0x6b, 0xcb, + 0x06, 0x9c, 0x4c, 0xc4, 0xc9, 0x0c, 0x1c, 0x52, 0x54, 0xfd, 0xa2, 0xda, + 0x0a, 0xab, 0x4f, 0x72, 0x0a, 0x55, 0x6b, 0xc2, 0xab, 0x74, 0x51, 0x6b, + 0xe6, 0x5a, 0x01, 0x8e, 0x67, 0x5c, 0x53, 0x4a, 0xe3, 0x71, 0x97, 0xf1, + 0x78, 0x3c, 0xa2, 0xc3, 0xab, 0xc0, 0x57, 0x5c, 0x7a, 0xea, 0xb3, 0x9b, + 0xd4, 0xdb, 0x55, 0x62, 0xcf, 0x17, 0x09, 0xde, 0xef, 0xa5, 0x56, 0xbc, + 0x91, 0x12, 0xf1, 0x42, 0x2a, 0x03, 0x04, 0xe6, 0xda, 0xce, 0x0e, 0x42, + 0x6d, 0xc0, 0xeb, 0x74, 0xfe, 0x66, 0x1f, 0x7d, 0x2d, 0x83, 0xd5, 0x1e, + 0x71, 0x61, 0xaa, 0x1d, 0x6a, 0x63, 0x07, 0xd6, 0x51, 0xcb, 0x83, 0x4e, + 0xb1, 0xea, 0x54, 0xf9, 0x45, 0xf6, 0xb2, 0x19, 0x32, 0x90, 0x8f, 0xae, + 0xe6, 0xc9, 0x6a, 0x0f, 0x06, 0xb8, 0x32, 0x37, 0xe3, 0x60, 0x5b, 0xae, + 0xa2, 0xb1, 0x67, 0xd5, 0x71, 0xf1, 0x4b, 0x43, 0x23, 0xd0, 0xdc, 0xd2, + 0x2e, 0x35, 0xe9, 0x30, 0xf8, 0xa1, 0xa3, 0x74, 0x86, 0xbd, 0xd0, 0x98, + 0x0e, 0x3a, 0x62, 0xaf, 0x52, 0x6b, 0xc7, 0x99, 0x91, 0xb1, 0x76, 0x4d, + 0x73, 0xc4, 0xa4, 0x53, 0xde, 0x87, 0x86, 0x5a, 0xb5, 0xe3, 0xd1, 0xed, + 0x1b, 0x28, 0x45, 0x6b, 0xfe, 0xee, 0x19, 0x9b, 0xdc, 0x83, 0x00, 0x29, + 0xe0, 0x3e, 0x4a, 0x31, 0xb0, 0xee, 0x8d, 0x15, 0x05, 0x22, 0xc7, 0x33, + 0xd9, 0x53, 0x64, 0xfd, 0x0a, 0x5a, 0xd1, 0x05, 0x86, 0xd6, 0xf7, 0xc6, + 0x45, 0x66, 0xd4, 0xee, 0x41, 0xee, 0x45, 0xf9, 0x02, 0x6e, 0x10, 0x0d, + 0x6f, 0x24, 0x92, 0xa7, 0x0e, 0x77, 0x21, 0x1d, 0xbf, 0xce, 0xf3, 0x24, + 0xbd, 0x11, 0xe5, 0x05, 0x15, 0x3a, 0x18, 0x14, 0x07, 0x2b, 0x66, 0x38, + 0xfe, 0x84, 0xe7, 0x50, 0x0c, 0xf1, 0xc9, 0x8a, 0xe9, 0x87, 0x9e, 0x5c, + 0xfa, 0x6a, 0x04, 0x4f, 0x6d, 0x44, 0x73, 0x8d, 0x9a, 0x6b, 0x03, 0x83, + 0xeb, 0xad, 0xd7, 0xe8, 0xec, 0xac, 0xce, 0xb2, 0xd7, 0xb4, 0xb3, 0x0c, + 0x35, 0x3d, 0x42, 0xa8, 0x8d, 0x48, 0xfc, 0x15, 0x83, 0xef, 0x44, 0x72, + 0x7a, 0x5d, 0x78, 0xd1, 0x0f, 0xd1, 0x1f, 0x3a, 0x0d, 0x14, 0x53, 0x9a, + 0x5c, 0x95, 0x41, 0xaf, 0xe8, 0x04, 0xc5, 0x7d, 0x25, 0x4f, 0x60, 0x72, + 0xe9, 0x24, 0x97, 0x83, 0x42, 0xc4, 0x82, 0x82, 0x3b, 0x93, 0xbb, 0x30, + 0x85, 0x32, 0x84, 0xf4, 0x52, 0x46, 0x80, 0x64, 0x6e, 0xe7, 0xcc, 0xc6, + 0xf5, 0xe3, 0x71, 0xa5, 0x36, 0xf0, 0x96, 0xf8, 0xb5, 0x74, 0xc4, 0x2f, + 0xa2, 0x69, 0x48, 0x6f, 0x85, 0xc0, 0x33, 0x12, 0x07, 0xfe, 0x5c, 0xca, + 0x2c, 0x6a, 0x37, 0xf4, 0x9d, 0x38, 0x05, 0x05, 0xb8, 0x0e, 0x6f, 0xfa, + 0x25, 0xef, 0x4a, 0xbc, 0xf8, 0x28, 0x9b, 0xe5, 0x7b, 0x6d, 0x9a, 0xb7, + 0xda, 0x34, 0x37, 0xdc, 0x34, 0xf0, 0x8f, 0x45, 0x78, 0x3d, 0xf5, 0xe4, + 0x4d, 0x2a, 0xfc, 0xd7, 0x40, 0xcb, 0xf3, 0x9f, 0xd2, 0xe6, 0x83, 0x54, + 0xfd, 0xa0, 0x2d, 0x1c, 0xb4, 0x85, 0x03, 0xb7, 0x30, 0x5d, 0xa5, 0xd2, + 0x93, 0xe8, 0x64, 0x09, 0x0a, 0x98, 0x53, 0x44, 0x88, 0x56, 0x6b, 0x4d, + 0xa4, 0xdb, 0x33, 0x3d, 0x4e, 0xba, 0x1c, 0xb0, 0xfa, 0xce, 0x8d, 0xbc, + 0x6e, 0xf2, 0x3c, 0x33, 0x7e, 0x58, 0xe1, 0xe7, 0x78, 0x4d, 0xab, 0x4f, + 0x41, 0x22, 0xf8, 0xd4, 0x53, 0x07, 0x97, 0x52, 0x4c, 0x5b, 0x58, 0xbb, + 0xe0, 0xe6, 0xee, 0xe3, 0x94, 0x19, 0x82, 0x2f, 0x74, 0x8e, 0xcf, 0xb4, + 0xf0, 0x59, 0x54, 0x41, 0x45, 0x97, 0xae, 0x82, 0x4b, 0x1a, 0x7e, 0xb4, + 0xa9, 0xe5, 0x4b, 0x94, 0x1d, 0xc3, 0x0e, 0xcd, 0x89, 0x6e, 0x88, 0xbb, + 0xd0, 0x66, 0xf8, 0x91, 0xd5, 0xdf, 0xff, 0x2d, 0xd5, 0x17, 0x56, 0x9f, + 0x59, 0xfd, 0xb4, 0xf3, 0x2f, 0x58, 0xbd, 0xa5, 0x6f, 0x8f, 0x63, 0x5f, + 0x38, 0xf6, 0x99, 0x63, 0x9f, 0x38, 0x76, 0x9b, 0xba, 0x86, 0x31, 0xc9, + 0xad, 0xd9, 0xce, 0xe2, 0x76, 0xb6, 0x38, 0x0c, 0x67, 0x33, 0x6f, 0xee, + 0x4c, 0x61, 0xfa, 0xe0, 0xb9, 0x5d, 0xb8, 0x40, 0xf2, 0x87, 0xca, 0x73, + 0xde, 0xb0, 0x58, 0xe1, 0x25, 0x1d, 0xd3, 0x9e, 0x78, 0xf1, 0xb0, 0x75, + 0x5f, 0xd8, 0xba, 0x05, 0x4e, 0x57, 0x01, 0x9d, 0xaf, 0x72, 0xe7, 0x6f, + 0x5d, 0xb8, 0x86, 0x53, 0xa7, 0x78, 0x51, 0xba, 0x83, 0xe3, 0x0a, 0xd0, + 0x03, 0x60, 0x71, 0x22, 0xf2, 0x16, 0x3b, 0x86, 0x0b, 0x29, 0x3e, 0xc8, + 0x62, 0x7a, 0xb6, 0x1c, 0xc4, 0xa3, 0x78, 0xa1, 0x8e, 0x5b, 0x86, 0x21, + 0x17, 0x08, 0x01, 0xb2, 0xea, 0x9f, 0xef, 0x57, 0x3d, 0x24, 0xf7, 0x39, + 0xaa, 0x87, 0xe4, 0x3f, 0x97, 0x8b, 0x5c, 0x0e, 0x74, 0xb9, 0xd7, 0xbf, + 0x67, 0xeb, 0xb7, 0x56, 0x57, 0xcc, 0x4c, 0xc3, 0x80, 0x11, 0x85, 0xd4, + 0x9c, 0x4e, 0xdd, 0x09, 0xd2, 0x36, 0x9c, 0xf9, 0x36, 0x7d, 0xe7, 0xcc, + 0xb0, 0xae, 0x0a, 0xd8, 0x71, 0x51, 0x1e, 0xcf, 0x1b, 0xd6, 0x7e, 0xaf, + 0x99, 0x72, 0xdd, 0xb7, 0x2b, 0xfb, 0x5d, 0x58, 0x77, 0x82, 0x36, 0x33, + 0xd1, 0x66, 0x94, 0x57, 0xb8, 0x21, 0x76, 0x29, 0x00, 0xa9, 0xe3, 0xac, + 0xf5, 0x98, 0x54, 0xfb, 0x79, 0x85, 0x31, 0x26, 0xc9, 0x67, 0x74, 0xf2, + 0x0c, 0x61, 0xba, 0x07, 0xae, 0x8e, 0x4b, 0xab, 0x40, 0x83, 0x33, 0x9a, + 0x5f, 0x4e, 0xe2, 0x9c, 0xa6, 0x63, 0xc0, 0x8b, 0xbc, 0xd0, 0xe2, 0x24, + 0xda, 0x1a, 0x62, 0x1f, 0x71, 0x2c, 0x91, 0x6e, 0x39, 0x3c, 0x12, 0x87, + 0x1c, 0x99, 0xfc, 0xd2, 0x05, 0x05, 0x3c, 0xe5, 0x78, 0xb7, 0x96, 0xdf, + 0x82, 0xcb, 0x24, 0x4b, 0xa4, 0x4a, 0xf3, 0xcd, 0x40, 0x37, 0x2d, 0x0b, + 0x30, 0x99, 0x18, 0x5c, 0xa7, 0xd5, 0x3b, 0x2d, 0xfe, 0x9c, 0x70, 0xa3, + 0x08, 0xe5, 0xeb, 0x31, 0x67, 0xae, 0xc4, 0xbc, 0x5f, 0xa3, 0x38, 0x47, + 0x9f, 0xb6, 0x49, 0xde, 0x96, 0x94, 0xfd, 0xaf, 0x58, 0xfc, 0x18, 0xe6, + 0xfc, 0x7b, 0x17, 0xbf, 0x2b, 0xc9, 0x53, 0x9f, 0x82, 0xf2, 0x3e, 0xdf, + 0x82, 0xc8, 0x55, 0x0e, 0xec, 0x7c, 0x59, 0xf2, 0x46, 0xb2, 0x54, 0xf1, + 0x83, 0x96, 0x72, 0x40, 0x8f, 0xb7, 0xba, 0x9d, 0xb1, 0x0f, 0x93, 0x43, + 0x5f, 0x83, 0x23, 0x6d, 0x35, 0xb7, 0x60, 0x0b, 0x5b, 0xc2, 0xd4, 0x41, + 0xa7, 0x15, 0xb5, 0x39, 0xe5, 0x6d, 0x47, 0x59, 0x46, 0x3c, 0x6d, 0xc0, + 0x60, 0x72, 0x14, 0x64, 0xcd, 0xec, 0x82, 0x74, 0x07, 0xd8, 0x18, 0x82, + 0x1c, 0x7d, 0x94, 0xdc, 0x92, 0xa0, 0x25, 0xc5, 0xcd, 0x61, 0xe9, 0x81, + 0x07, 0x79, 0x02, 0x31, 0x98, 0x20, 0x67, 0x5e, 0x72, 0xfc, 0xe8, 0x2d, + 0xa9, 0x24, 0xd7, 0xb2, 0x58, 0xdc, 0x59, 0x6c, 0x55, 0xaf, 0xc9, 0x4c, + 0x28, 0x4e, 0x47, 0x5b, 0x8a, 0xfa, 0x8a, 0xdb, 0xf6, 0x10, 0xcc, 0x78, + 0x40, 0x0d, 0x66, 0xb1, 0x27, 0x4d, 0xf7, 0xe9, 0xad, 0x8b, 0x85, 0x83, + 0x54, 0x68, 0xe1, 0x0d, 0xad, 0x2c, 0x12, 0x6f, 0xc8, 0x21, 0x3e, 0x91, + 0x27, 0xff, 0x41, 0x86, 0xf2, 0x1b, 0x19, 0x09, 0x7d, 0x3b, 0x7f, 0x52, + 0xc4, 0xbf, 0xad, 0xf8, 0x91, 0xa0, 0xe8, 0x53, 0xf1, 0x3f, 0xba, 0x58, + 0xa8, 0x28, 0xc1, 0x8a, 0x02, 0xd5, 0x3d, 0x11, 0xf9, 0x13, 0x8b, 0x6f, + 0x28, 0x4f, 0x5c, 0xda, 0x25, 0x4d, 0x96, 0x9b, 0xca, 0x3d, 0xd6, 0xa4, + 0x20, 0x53, 0x13, 0xe6, 0x9b, 0x90, 0x42, 0x15, 0x25, 0x46, 0x3d, 0x47, + 0x92, 0x0e, 0x8f, 0x66, 0x31, 0x94, 0xb2, 0x1c, 0xb7, 0xcb, 0xe2, 0x01, + 0xc5, 0x92, 0xa2, 0x44, 0xd7, 0x92, 0xac, 0x8e, 0x62, 0xd2, 0x33, 0x7e, + 0xc4, 0x4e, 0x42, 0xd0, 0x20, 0x92, 0xf4, 0x43, 0x7f, 0x73, 0x62, 0x80, + 0x4f, 0xff, 0xaf, 0xff, 0x8f, 0x6b, 0xf8, 0xe3, 0xff, 0x3f, 0x86, 0x91, + 0xfe, 0x8a, 0xff, 0xff, 0x95, 0x7e, 0x8f, 0x61, 0x3e, 0x1f, 0xc3, 0xff, + 0xf0, 0xb4, 0xe3, 0x9e, 0xfe, 0x5f, 0xfc, 0xef, 0x22, 0x8f, 0xb7, 0xa4, + 0x83, 0xc6, 0x26, 0x8f, 0x53, 0x8f, 0xd4, 0x98, 0xe8, 0x44, 0xe1, 0x7f, + 0x24, 0x2e, 0x51, 0x37, 0x08, 0x14, 0x3e, 0xa4, 0x3a, 0xe4, 0xf7, 0xe7, + 0xbc, 0xa5, 0x93, 0x62, 0x5b, 0x8e, 0xd2, 0xbd, 0xf4, 0x12, 0x19, 0xcb, + 0xa2, 0x54, 0x1f, 0xff, 0x06, 0x9a, 0xb3, 0x1b, 0x98, 0x26, 0xec, 0x58, + 0xda, 0xd8, 0x4b, 0x1f, 0x74, 0x86, 0xed, 0x44, 0x49, 0x63, 0x48, 0x36, + 0xb1, 0x3e, 0x59, 0x21, 0xdf, 0x53, 0x4e, 0x79, 0x45, 0x2f, 0x03, 0xcd, + 0x45, 0x32, 0x13, 0xc9, 0xba, 0x24, 0xab, 0xd2, 0x79, 0x31, 0xad, 0x26, + 0x8c, 0xdf, 0x8a, 0x40, 0x35, 0x66, 0x8f, 0xb1, 0x3e, 0xa6, 0xe2, 0x9f, + 0xa8, 0xdc, 0x63, 0xd1, 0xc6, 0xa3, 0xf0, 0x25, 0xc3, 0xf8, 0x0f, 0x2a, + 0x06, 0x1a, 0xce, 0xd9, 0x3b, 0xda, 0x63, 0x5a, 0xdc, 0xbf, 0x60, 0xf1, + 0x67, 0x3a, 0xce, 0x0c, 0x53, 0x6b, 0x62, 0x6a, 0x4b, 0x4c, 0x3d, 0x29, + 0xbe, 0x88, 0xc8, 0x43, 0x12, 0xd7, 0x9b, 0xab, 0x4a, 0xed, 0x5b, 0x3a, + 0x18, 0xf8, 0x56, 0xea, 0xea, 0x61, 0xbc, 0xfb, 0x18, 0x1f, 0x6b, 0x4d, + 0x6c, 0x9f, 0x28, 0xb6, 0x4f, 0x14, 0xdb, 0xbf, 0x84, 0xa0, 0x57, 0x8b, + 0x4e, 0xad, 0x9f, 0x10, 0xdf, 0x37, 0x5a, 0x3d, 0xff, 0xa4, 0xd5, 0xf3, + 0x86, 0x47, 0x47, 0x77, 0xd8, 0xef, 0x8b, 0xc7, 0xfb, 0xb3, 0xaf, 0xe9, + 0xcd, 0x6a, 0xb0, 0xba, 0x4b, 0x0b, 0x7b, 0xc4, 0xd0, 0x9b, 0xad, 0x7d, + 0x75, 0x50, 0xd9, 0xa7, 0x1a, 0xea, 0x44, 0xfc, 0x18, 0xc6, 0x9e, 0x3f, + 0x0a, 0x35, 0xad, 0x85, 0x29, 0x2b, 0x3d, 0xd6, 0xa1, 0x81, 0x64, 0xc9, + 0xa5, 0x77, 0xf3, 0xa4, 0x04, 0x59, 0x4b, 0x4b, 0x56, 0x86, 0x9e, 0xaa, + 0xff, 0x4a, 0xd5, 0x1f, 0xc7, 0xef, 0x97, 0x9c, 0xba, 0xa7, 0xea, 0x3f, + 0xa7, 0xfa, 0x10, 0x9e, 0x7e, 0x3f, 0xe7, 0x77, 0x90, 0x5b, 0x78, 0x4b, + 0xb1, 0x85, 0x52, 0xcc, 0x53, 0xe1, 0x53, 0xe1, 0x23, 0x2a, 0x0c, 0xc9, + 0x2d, 0x32, 0x27, 0x0a, 0x62, 0x98, 0xfc, 0xa8, 0x0b, 0x3d, 0x0a, 0xdf, + 0xa1, 0x90, 0xc7, 0xf4, 0x7f, 0xbd, 0x56, 0x38, 0xc8, 0x23, 0xd6, 0xa6, + 0x57, 0xd9, 0x5f, 0x42, 0x5d, 0x7c, 0x0f, 0xc3, 0xb0, 0x69, 0x6f, 0x3d, + 0x4e, 0xc0, 0x6a, 0xe1, 0x34, 0xa6, 0x7f, 0xab, 0x86, 0xb8, 0xf9, 0x02, + 0x1e, 0xe6, 0x90, 0xad, 0xc1, 0xd2, 0x11, 0x90, 0x3b, 0xc9, 0x75, 0x1e, + 0xd0, 0x63, 0xcb, 0xe5, 0x57, 0x77, 0x65, 0x0a, 0x1e, 0x45, 0xc8, 0x25, + 0x6c, 0xfb, 0xa0, 0x97, 0x27, 0xcd, 0x73, 0xe5, 0x6d, 0xa8, 0x66, 0x9f, + 0xc6, 0xd2, 0x30, 0x60, 0x72, 0xd4, 0x72, 0xc9, 0x49, 0x94, 0x82, 0x5e, + 0x5a, 0x1a, 0xbc, 0x63, 0xb1, 0x32, 0x5a, 0xb2, 0x84, 0x89, 0xac, 0x63, + 0x6b, 0x69, 0x6f, 0x47, 0x46, 0x6f, 0x62, 0x32, 0xf0, 0x80, 0x9d, 0x40, + 0x06, 0xb8, 0x86, 0x65, 0x20, 0x68, 0xa5, 0x55, 0x46, 0x30, 0xcb, 0x37, + 0x30, 0x28, 0x66, 0x54, 0xca, 0xe8, 0xcc, 0x43, 0x66, 0x19, 0x53, 0x8b, + 0x25, 0x58, 0x1d, 0x97, 0x6d, 0x6c, 0xe1, 0xed, 0x42, 0x46, 0x03, 0xed, + 0x1b, 0xaf, 0x47, 0x51, 0xb8, 0x47, 0x06, 0xb4, 0x2d, 0xcb, 0xb0, 0xbf, + 0x4a, 0x19, 0x1d, 0x96, 0x02, 0x11, 0xb9, 0x5c, 0x1e, 0xcb, 0xa8, 0xe1, + 0xb7, 0xcb, 0x19, 0x0d, 0x66, 0x18, 0xdf, 0x72, 0x25, 0x9c, 0xe1, 0x0c, + 0x70, 0xbc, 0xa9, 0x94, 0x21, 0x47, 0x66, 0xc5, 0x28, 0x01, 0x93, 0xf1, + 0x1d, 0x96, 0x7b, 0x64, 0x86, 0x21, 0xcf, 0xdc, 0x30, 0x20, 0xc3, 0x29, + 0xd6, 0x29, 0x65, 0x9c, 0xce, 0x4d, 0xa5, 0x0c, 0x46, 0xcb, 0xe0, 0xb4, + 0xc2, 0x98, 0xd1, 0x33, 0xa3, 0xa6, 0xe9, 0xc2, 0xc2, 0x70, 0x92, 0x5f, + 0x15, 0xb6, 0x85, 0xe5, 0x98, 0x71, 0x58, 0xcd, 0x32, 0xa3, 0x63, 0x34, + 0xa4, 0x05, 0x8e, 0xc0, 0x11, 0x82, 0x0c, 0x04, 0xdf, 0xc8, 0x33, 0x02, + 0xbc, 0x1f, 0xb3, 0x4b, 0x58, 0x1b, 0x21, 0x09, 0x15, 0xda, 0x53, 0xc7, + 0x8c, 0x4d, 0x19, 0xd3, 0xb5, 0x8c, 0xb3, 0x2a, 0x29, 0xe3, 0xa4, 0x95, + 0xa2, 0xd9, 0x63, 0xc7, 0x3e, 0x9d, 0x76, 0x3d, 0x8d, 0xa5, 0xbb, 0x88, + 0xcc, 0xc8, 0x58, 0x4e, 0x67, 0x2c, 0x4d, 0xe1, 0x71, 0x4e, 0xeb, 0xd3, + 0x59, 0x4f, 0xcb, 0xd0, 0x95, 0x48, 0xcc, 0x95, 0xd3, 0x52, 0x2e, 0xa7, + 0x8b, 0x9d, 0x56, 0xff, 0x88, 0x0e, 0xb5, 0x10, 0xa6, 0xc0, 0x20, 0xa1, + 0xd4, 0x39, 0x36, 0x0b, 0x0b, 0x97, 0x53, 0x3c, 0x3d, 0xc3, 0xe4, 0x4d, + 0x19, 0xd3, 0x29, 0xf2, 0x6b, 0x37, 0x64, 0x2c, 0x3e, 0xdd, 0x40, 0x8b, + 0xb6, 0xd8, 0x71, 0xcf, 0xa5, 0x4d, 0xd8, 0x73, 0x9b, 0xd6, 0xda, 0xa6, + 0xdd, 0xf9, 0x46, 0x4e, 0x93, 0x72, 0xb2, 0xf7, 0x45, 0x0c, 0x9a, 0x53, + 0x72, 0x51, 0x10, 0x94, 0x13, 0x0a, 0xd3, 0x65, 0x1a, 0x74, 0xb6, 0x8e, + 0x27, 0x64, 0xcb, 0xd3, 0x20, 0x50, 0x10, 0x36, 0x64, 0xc8, 0xdf, 0x1d, + 0xa1, 0x2b, 0x63, 0x16, 0xca, 0x3d, 0xfd, 0x7f, 0xfa, 0xff, 0xf4, 0xff, + 0xda, 0xff, 0xbf, 0xe9, 0xe7, 0xe2, 0xcf, 0xc8, 0xbe, 0xb5, 0xab, 0x17, + 0x3c, 0x2d, 0xbb, 0x0b, 0x62, 0x07, 0x87, 0xd7, 0x4a, 0xef, 0xce, 0xa6, + 0x68, 0x35, 0xc6, 0xeb, 0xde, 0x2e, 0xd7, 0x7b, 0x66, 0x7b, 0xbb, 0x2c, + 0xdf, 0xa2, 0x99, 0x1e, 0x94, 0x6d, 0x63, 0x28, 0x86, 0xd6, 0xde, 0x3b, + 0x7b, 0x48, 0x43, 0xb0, 0x31, 0x54, 0x7f, 0x29, 0x7b, 0x4c, 0x23, 0xb3, + 0xa1, 0xd5, 0xf7, 0xce, 0xde, 0xf2, 0x10, 0x6c, 0x68, 0xfe, 0xde, 0xd9, + 0x73, 0xce, 0x0e, 0x79, 0x64, 0x40, 0xa2, 0x78, 0x36, 0x70, 0x7c, 0x65, + 0x31, 0x66, 0xef, 0x74, 0xd8, 0xde, 0x6c, 0xcc, 0xd9, 0x43, 0xce, 0x5e, + 0xec, 0xb8, 0xc3, 0x94, 0xdd, 0x45, 0x32, 0xbe, 0xf3, 0x77, 0xcb, 0xb6, + 0x4f, 0xd7, 0xf5, 0xc8, 0x7e, 0x67, 0xd9, 0x6b, 0x91, 0x3d, 0x2a, 0xfb, + 0x23, 0xcc, 0xcf, 0xd1, 0xae, 0x6b, 0xaf, 0xec, 0xed, 0x23, 0x7b, 0x75, + 0x96, 0x3d, 0xe5, 0xec, 0x2d, 0x65, 0xb7, 0x30, 0x35, 0xc7, 0xe9, 0x9c, + 0x61, 0x85, 0xfe, 0x78, 0x92, 0xbd, 0x14, 0xd9, 0xf1, 0xa5, 0x51, 0x65, + 0x7b, 0x63, 0x71, 0x31, 0x46, 0x6d, 0x17, 0xec, 0xe7, 0x38, 0xb2, 0xdb, + 0xb3, 0x07, 0x65, 0x3f, 0x37, 0x41, 0xc1, 0xde, 0xba, 0x8b, 0x6f, 0xe8, + 0xed, 0x43, 0x78, 0xad, 0xec, 0xea, 0x52, 0xf6, 0x9a, 0xb2, 0x3b, 0x13, + 0xff, 0x9f, 0xc5, 0xec, 0x67, 0x66, 0xe4, 0x5e, 0x52, 0x76, 0x7f, 0x29, + 0x7b, 0x52, 0xf6, 0x60, 0x2f, 0x7f, 0x98, 0x4d, 0x7b, 0xc1, 0x4b, 0x86, + 0x95, 0xb2, 0x9b, 0x22, 0xdb, 0x2b, 0x3b, 0xa4, 0xec, 0xf8, 0x6e, 0xa1, + 0xc7, 0xf1, 0x90, 0x83, 0x8f, 0xd9, 0x53, 0xca, 0x0e, 0x39, 0xbb, 0xbd, + 0x90, 0xdd, 0x47, 0xd9, 0xc2, 0x34, 0x9b, 0x25, 0xbe, 0x69, 0x48, 0x57, + 0xd9, 0x49, 0x76, 0x55, 0x64, 0x77, 0xcc, 0xb6, 0x3c, 0x44, 0xec, 0x22, + 0x6f, 0x28, 0xb2, 0x6b, 0x65, 0xf7, 0x08, 0xa1, 0x89, 0xd9, 0xb5, 0xb2, + 0xdb, 0xe8, 0x7b, 0x43, 0xb6, 0x8b, 0x3a, 0xd6, 0x5c, 0x64, 0xbb, 0xb3, + 0xec, 0x26, 0xc6, 0xf9, 0x32, 0xdb, 0xc7, 0xd7, 0x88, 0x95, 0x3d, 0xfb, + 0xad, 0xc8, 0x9e, 0xce, 0xb3, 0xfb, 0x88, 0x1f, 0xc8, 0xae, 0x0d, 0x3f, + 0x94, 0xfd, 0x92, 0xee, 0x1e, 0xcb, 0x5e, 0xcf, 0xb3, 0x2b, 0x7b, 0xd9, + 0x14, 0xd9, 0x11, 0x73, 0x53, 0xf6, 0xd7, 0xb0, 0x14, 0xd9, 0xc3, 0x79, + 0x76, 0x9b, 0xb3, 0x1d, 0xa1, 0x2a, 0xc8, 0xda, 0xe5, 0xec, 0xf9, 0x42, + 0xb6, 0xcf, 0xd9, 0xcd, 0x31, 0x7b, 0xca, 0xd9, 0xdb, 0x85, 0xec, 0xba, + 0xc8, 0xee, 0x0f, 0xd9, 0x6b, 0x91, 0x3d, 0x5e, 0xc8, 0xee, 0x72, 0x76, + 0x75, 0xcc, 0x1e, 0x72, 0xf6, 0x72, 0x21, 0xdb, 0x15, 0xd9, 0xed, 0x21, + 0x7b, 0x2e, 0xb2, 0xdd, 0x85, 0xec, 0xa6, 0xc8, 0xf6, 0x65, 0xf6, 0x56, + 0x64, 0x4f, 0xca, 0xae, 0x8b, 0xec, 0xfe, 0x5a, 0xf6, 0x98, 0xb3, 0xd7, + 0x94, 0xdd, 0xe5, 0x6c, 0x7d, 0x98, 0xc4, 0x48, 0x1f, 0xd0, 0x95, 0x14, + 0x7d, 0x24, 0x59, 0x9c, 0x5d, 0xaa, 0x67, 0x04, 0x7b, 0x15, 0x8d, 0x14, + 0x09, 0x1d, 0x48, 0x8f, 0xc7, 0x7c, 0x6e, 0xa2, 0x01, 0xa0, 0x26, 0x6f, + 0x15, 0x7a, 0x56, 0xe8, 0x58, 0x41, 0x8a, 0x94, 0xdc, 0xa7, 0x35, 0x2b, + 0xc8, 0x82, 0x0b, 0x9e, 0xe2, 0xb2, 0x85, 0xb7, 0x67, 0x85, 0x8e, 0x15, + 0xa4, 0x67, 0x35, 0xac, 0x50, 0xb3, 0x02, 0xf8, 0xc8, 0x40, 0x00, 0x13, + 0x01, 0xcc, 0x04, 0xb0, 0x38, 0xd9, 0x23, 0x48, 0xe6, 0xc5, 0x05, 0x06, + 0x02, 0xd0, 0x49, 0xa4, 0x33, 0x01, 0x2c, 0x04, 0xa0, 0x57, 0xfc, 0x83, + 0x98, 0xd6, 0x40, 0x00, 0x13, 0x01, 0x28, 0x4a, 0x77, 0x25, 0x80, 0x4d, + 0x6c, 0x51, 0xbc, 0x65, 0x24, 0x80, 0x89, 0x00, 0x14, 0xf4, 0xaf, 0x63, + 0x2d, 0xf5, 0xad, 0x56, 0x9d, 0x2f, 0x69, 0x0a, 0x11, 0x2a, 0x78, 0x56, + 0x88, 0x00, 0xfe, 0xa4, 0x49, 0xe5, 0x2b, 0x5f, 0x89, 0xd9, 0x02, 0x5f, + 0x56, 0xe8, 0xf9, 0xda, 0xc0, 0x2b, 0x53, 0x9c, 0x82, 0xbd, 0x63, 0x19, + 0x2b, 0xbe, 0xe7, 0x5e, 0xfd, 0x62, 0x6f, 0x09, 0x5a, 0x58, 0x08, 0x2a, + 0xf6, 0xf4, 0xfb, 0x75, 0xa4, 0x02, 0x2f, 0xf9, 0x16, 0xc4, 0x1b, 0xc6, + 0xf3, 0xfd, 0xce, 0x17, 0x0f, 0xe6, 0x40, 0xfa, 0xd2, 0x83, 0xb4, 0xc5, + 0x43, 0x0d, 0x0c, 0x40, 0xcb, 0x37, 0x11, 0x6a, 0x86, 0x05, 0x3f, 0x23, + 0xd1, 0x1c, 0x69, 0x75, 0xdf, 0xe9, 0xd5, 0x68, 0x00, 0x3a, 0xc4, 0x09, + 0xef, 0xd4, 0x79, 0x31, 0x00, 0x35, 0x1c, 0x94, 0xfb, 0xd3, 0x78, 0x4d, + 0xc3, 0x31, 0xee, 0x70, 0x27, 0x7a, 0xc6, 0x2d, 0xf6, 0x66, 0xec, 0x7d, + 0xac, 0xbd, 0x19, 0xfb, 0xda, 0xc3, 0x4e, 0xd3, 0xc0, 0x3a, 0x2a, 0x44, + 0x16, 0xc7, 0xfe, 0xbd, 0x08, 0x08, 0x4e, 0x78, 0x63, 0x00, 0x3a, 0x30, + 0xa5, 0x9d, 0x48, 0xda, 0xe1, 0xfb, 0x3b, 0x45, 0xb2, 0xaf, 0xee, 0x4d, + 0x15, 0xbe, 0xd7, 0x17, 0x15, 0xc5, 0x57, 0x01, 0x04, 0xe7, 0x26, 0xc0, + 0x2a, 0xf5, 0x21, 0xe0, 0xad, 0xa2, 0x3f, 0x0d, 0x40, 0x0d, 0x6e, 0x3c, + 0x90, 0x87, 0xe7, 0xb9, 0xef, 0x39, 0xf7, 0x58, 0xb4, 0xfc, 0x1a, 0x5a, + 0xcd, 0x45, 0x4b, 0xd2, 0x1e, 0x16, 0x69, 0x60, 0x05, 0x5a, 0xb8, 0xce, + 0xd0, 0x43, 0xaf, 0x7c, 0xac, 0xd7, 0xf1, 0xaa, 0x21, 0x80, 0x33, 0x84, + 0xf4, 0x00, 0x80, 0x0a, 0x6b, 0xc6, 0xe4, 0x86, 0x00, 0x6a, 0x02, 0xa8, + 0x08, 0x00, 0x15, 0x26, 0x56, 0x98, 0x59, 0x41, 0x7b, 0x46, 0xae, 0xee, + 0x2d, 0xbf, 0x9c, 0x72, 0xdc, 0x6c, 0x63, 0x76, 0xb7, 0x5c, 0xdc, 0xa5, + 0xab, 0xcb, 0x6f, 0xf2, 0xf5, 0x04, 0xd0, 0x11, 0x40, 0x43, 0x00, 0x35, + 0x01, 0x24, 0xba, 0xe0, 0x43, 0x0e, 0x55, 0x5f, 0x59, 0x21, 0xc9, 0x62, + 0x12, 0xec, 0x06, 0x56, 0x10, 0x05, 0x9a, 0x58, 0x61, 0x66, 0x85, 0x85, + 0x15, 0x24, 0x09, 0x6e, 0x22, 0x5d, 0x26, 0xd1, 0x6e, 0xe9, 0xa8, 0xac, + 0xf8, 0xf0, 0xcc, 0xb2, 0x85, 0x54, 0x6b, 0xcd, 0x8d, 0x8b, 0xd6, 0x88, + 0x84, 0x68, 0x3e, 0xb9, 0x30, 0xda, 0xc7, 0xda, 0xd7, 0xda, 0xae, 0x58, + 0x2f, 0xbd, 0x78, 0x96, 0x5e, 0xca, 0xe1, 0xee, 0x4d, 0x91, 0xf1, 0x1d, + 0xff, 0x3c, 0xf6, 0xdb, 0x48, 0x10, 0xcf, 0xb8, 0xf3, 0x9f, 0x13, 0x47, + 0x5a, 0x30, 0x8f, 0xd3, 0x17, 0x0a, 0xf5, 0x4a, 0xdf, 0x17, 0x92, 0x22, + 0x05, 0xfd, 0x7e, 0x08, 0xb7, 0xbc, 0x54, 0xd5, 0xa6, 0x20, 0x9b, 0x62, + 0xdd, 0xc7, 0xbc, 0xac, 0x5c, 0xe6, 0x47, 0xe7, 0xa3, 0x7b, 0xaa, 0xfe, + 0xcf, 0xae, 0xee, 0x51, 0x5d, 0x2f, 0x12, 0x1e, 0x5e, 0xa2, 0xbc, 0x0b, + 0xe7, 0x3b, 0x1e, 0x7d, 0xde, 0x73, 0xcb, 0x84, 0xe3, 0x3b, 0xb8, 0x75, + 0x7e, 0xff, 0x8d, 0x3b, 0x0e, 0xc7, 0xc1, 0xc3, 0x78, 0x17, 0x39, 0xa6, + 0x7d, 0x60, 0xb9, 0x09, 0x3a, 0x17, 0x36, 0xbd, 0x8a, 0x6d, 0x12, 0x38, + 0x03, 0xe0, 0xac, 0xc7, 0x36, 0x9e, 0x90, 0x42, 0xe9, 0xc8, 0x21, 0x06, + 0xc7, 0xc7, 0x1c, 0x1f, 0x73, 0x7c, 0x0c, 0xe9, 0x98, 0x25, 0x19, 0x16, + 0x3b, 0x4e, 0x93, 0xc1, 0x71, 0xa0, 0x46, 0xa6, 0xf2, 0xb7, 0x24, 0x5a, + 0xae, 0x1f, 0x5d, 0xaa, 0x2d, 0x26, 0x20, 0x61, 0xa3, 0x3f, 0x34, 0x3e, + 0x4a, 0xf1, 0x6f, 0x0f, 0x7d, 0x96, 0x47, 0x26, 0xbd, 0x04, 0x2b, 0x65, + 0x9d, 0xc5, 0x23, 0x12, 0x05, 0xfb, 0xeb, 0x6d, 0x1d, 0xa3, 0x3d, 0x3d, + 0x27, 0xb0, 0xe3, 0xab, 0x02, 0xdd, 0xe5, 0x37, 0x09, 0xaa, 0x93, 0xb7, + 0x75, 0xda, 0x1f, 0xf1, 0xb6, 0x4e, 0x75, 0xe5, 0x15, 0x89, 0xfe, 0x38, + 0xb3, 0xc2, 0x28, 0x21, 0xe2, 0xdf, 0x54, 0x7c, 0xb1, 0x6b, 0x0f, 0x1b, + 0xd8, 0x79, 0xb1, 0x66, 0xed, 0xd2, 0xa4, 0x56, 0x77, 0xbe, 0xdc, 0x41, + 0x1f, 0xde, 0xe9, 0xcb, 0x1d, 0xfa, 0x3e, 0xd1, 0xc9, 0x7a, 0x9f, 0xa0, + 0xc3, 0x76, 0x82, 0x2d, 0x23, 0x8a, 0xd3, 0x97, 0x91, 0x58, 0x3c, 0x1c, + 0xf9, 0xe0, 0x24, 0xfc, 0x3f, 0x60, 0xea, 0x92, 0x9c, 0xad, 0xb4, 0xc6, + 0x25, 0x6f, 0x6b, 0x4b, 0xcb, 0xd8, 0x53, 0xf1, 0xaf, 0x56, 0xec, 0x9e, + 0xe4, 0x2a, 0x60, 0xf8, 0x2f, 0xc7, 0x5f, 0x9f, 0xaa, 0xff, 0x33, 0xab, + 0xff, 0xe3, 0xe5, 0x2a, 0x85, 0xfb, 0x2c, 0xa4, 0x6f, 0x0f, 0x4d, 0xe7, + 0x07, 0xa5, 0x3e, 0xa5, 0xd3, 0x49, 0x3a, 0x20, 0x05, 0x79, 0xc5, 0x37, + 0xdc, 0x1c, 0xc3, 0x59, 0x62, 0x4a, 0xea, 0xfa, 0xff, 0x98, 0xfe, 0x17, + 0xd3, 0xff, 0xd4, 0x90, 0x8f, 0x14, 0x53, 0x87, 0x53, 0xa4, 0xb7, 0x38, + 0x86, 0xe3, 0xcc, 0xea, 0x44, 0x95, 0x14, 0xfa, 0x28, 0xd7, 0x8a, 0x9c, + 0x26, 0xac, 0x9e, 0xc4, 0x3e, 0x56, 0x97, 0xb4, 0xc0, 0x0f, 0xab, 0x62, + 0xbd, 0x1b, 0xc6, 0x24, 0x34, 0x27, 0x07, 0x73, 0x34, 0x67, 0xa7, 0xc9, + 0x5c, 0x3e, 0xd7, 0xc3, 0xa7, 0x93, 0x22, 0x2e, 0x62, 0x5d, 0x9d, 0x5e, + 0x0f, 0x39, 0xa5, 0xb4, 0xd7, 0x4e, 0x54, 0xf9, 0x1e, 0x87, 0x92, 0x54, + 0xf7, 0x6c, 0xfd, 0x8e, 0xce, 0x9f, 0x6f, 0x99, 0x95, 0x63, 0xbf, 0x76, + 0x10, 0xcf, 0x0b, 0xda, 0x0d, 0x5b, 0xd8, 0x7b, 0xf4, 0x09, 0x2a, 0x7d, + 0x99, 0x4a, 0xd1, 0xfc, 0x92, 0xcf, 0x9f, 0x71, 0xa1, 0x24, 0x98, 0x71, + 0xe1, 0xa6, 0xe3, 0xba, 0x67, 0x49, 0xcc, 0x33, 0x45, 0xf1, 0x7c, 0xc2, + 0x41, 0x55, 0x5d, 0x9c, 0xf4, 0xc2, 0x11, 0x48, 0xb6, 0xee, 0x13, 0x39, + 0xe9, 0x6b, 0xae, 0xfb, 0x73, 0xae, 0xfb, 0x0b, 0xae, 0x7b, 0x8b, 0x75, + 0xdf, 0x18, 0xdc, 0xb3, 0x5e, 0x1c, 0xbb, 0x67, 0x28, 0x4b, 0x7e, 0x0d, + 0xe8, 0x2d, 0x67, 0xfe, 0xe5, 0x5f, 0x5b, 0xf7, 0x0f, 0x0f, 0x59, 0xb8, + 0x1f, 0x86, 0x36, 0xff, 0xf8, 0xea, 0x55, 0x7e, 0x0d, 0x46, 0xdc, 0x66, + 0xb0, 0xb7, 0xbb, 0x22, 0xc1, 0xaa, 0xe9, 0x49, 0x0e, 0xd9, 0xb6, 0x27, + 0x63, 0xa1, 0xf8, 0x98, 0x34, 0xda, 0x64, 0xc5, 0x94, 0x75, 0x13, 0xc8, + 0x38, 0x88, 0xd4, 0x01, 0x47, 0xf5, 0x39, 0x16, 0xea, 0xb7, 0xe9, 0x20, + 0x1c, 0xa9, 0xbd, 0xc4, 0xdc, 0xca, 0x6c, 0xc9, 0x38, 0x75, 0xa3, 0xb1, + 0xed, 0x13, 0x3d, 0x50, 0x81, 0x0c, 0xc9, 0x76, 0x57, 0xc5, 0xa3, 0xd0, + 0xec, 0x53, 0xcf, 0xb6, 0xe9, 0xc4, 0xce, 0xde, 0x62, 0x4f, 0xde, 0xe7, + 0x03, 0x6a, 0x35, 0x3f, 0xca, 0xd4, 0x45, 0x24, 0x37, 0xe4, 0x7e, 0x0b, + 0x3a, 0x20, 0x56, 0xf9, 0x85, 0xb8, 0xce, 0x4f, 0xdb, 0xda, 0xc9, 0xef, + 0xee, 0xec, 0x33, 0x61, 0xfa, 0x00, 0xee, 0x07, 0x12, 0x11, 0xea, 0x78, + 0xa7, 0x5f, 0x74, 0xf3, 0x90, 0x58, 0x3f, 0x91, 0xdd, 0xbe, 0x84, 0x40, + 0x6b, 0x2f, 0x06, 0xa1, 0x43, 0x33, 0x77, 0x63, 0xf4, 0x2c, 0xc4, 0xe3, + 0x90, 0x62, 0xe5, 0x37, 0x99, 0xe9, 0x4d, 0xe0, 0x3d, 0xc6, 0x7c, 0x2a, + 0xae, 0x8c, 0x78, 0x61, 0x8f, 0xe2, 0x38, 0xdb, 0xb1, 0xb8, 0x61, 0x71, + 0xcf, 0xe2, 0x80, 0xe2, 0x9a, 0xc5, 0x1d, 0x8b, 0x3d, 0x8a, 0x2b, 0x16, + 0x37, 0x2c, 0xee, 0xd9, 0x26, 0x96, 0xd9, 0x6a, 0x8d, 0x68, 0xeb, 0xc0, + 0x87, 0xef, 0x2e, 0x2e, 0xdb, 0x6c, 0xcf, 0xbb, 0x34, 0xb2, 0x98, 0x03, + 0x59, 0x8b, 0x81, 0x38, 0xd6, 0xaa, 0x99, 0xdd, 0x51, 0x87, 0xf1, 0xa8, + 0xa5, 0xe1, 0x37, 0x04, 0xd6, 0x85, 0x14, 0x2b, 0x41, 0xbc, 0x95, 0x05, + 0x66, 0x24, 0x50, 0x87, 0x5a, 0x0b, 0x69, 0xe0, 0x5b, 0x62, 0x6c, 0x0f, + 0x51, 0xe8, 0x4b, 0x32, 0x7d, 0xe8, 0xcc, 0x24, 0x43, 0x12, 0x7d, 0x48, + 0xea, 0x5d, 0xd2, 0xe9, 0x27, 0x6e, 0x20, 0xee, 0x37, 0xdb, 0x67, 0x5f, + 0x93, 0xf4, 0x22, 0x1d, 0x49, 0x66, 0x85, 0x21, 0x19, 0xd6, 0x5b, 0x10, + 0x72, 0xe0, 0x7b, 0xb6, 0xbb, 0x73, 0x0b, 0xdd, 0x56, 0x79, 0xcb, 0x92, + 0xdf, 0x9d, 0x9f, 0x12, 0xbc, 0x8a, 0xb5, 0xe7, 0x3b, 0x81, 0x62, 0x60, + 0x95, 0x4e, 0xc2, 0x89, 0x01, 0x01, 0x26, 0x15, 0xc6, 0xec, 0x36, 0x7d, + 0xe8, 0x6d, 0xe9, 0xb0, 0x5b, 0xa3, 0xe3, 0xe7, 0x43, 0x66, 0x5d, 0x62, + 0x59, 0x27, 0xca, 0xdd, 0x89, 0x2d, 0x61, 0x94, 0x6f, 0x04, 0x9c, 0x6a, + 0x3a, 0x6a, 0x8e, 0xd3, 0x51, 0xb1, 0x9c, 0x8e, 0x7a, 0xe7, 0x80, 0xe5, + 0xe4, 0xaa, 0x43, 0xd2, 0x6a, 0x25, 0x16, 0xfe, 0x3d, 0x3f, 0xc4, 0xf4, + 0x26, 0xc5, 0xb9, 0x13, 0x31, 0x9c, 0x9d, 0xe8, 0x24, 0x55, 0xec, 0x0a, + 0x48, 0x4b, 0xc5, 0xfa, 0xa9, 0xd2, 0x53, 0xa5, 0xa7, 0x4a, 0xdf, 0x5e, + 0xe9, 0x97, 0x56, 0xd5, 0x9f, 0xaa, 0x3f, 0x55, 0xff, 0xd5, 0xab, 0xff, + 0x20, 0x75, 0xe5, 0x86, 0xd5, 0xa9, 0x6b, 0xeb, 0xe8, 0xd5, 0xd3, 0x43, + 0x6f, 0x0f, 0xe6, 0xa9, 0x86, 0x02, 0x49, 0xc7, 0xc3, 0x2b, 0x7a, 0x9c, + 0x8e, 0xb9, 0x17, 0x47, 0xf3, 0xd4, 0xd2, 0x65, 0xed, 0x77, 0x62, 0x2a, + 0xe3, 0x09, 0x45, 0x91, 0xa4, 0x6a, 0x24, 0xa5, 0x1a, 0xe9, 0x48, 0x79, + 0xe2, 0xba, 0x59, 0xca, 0xa3, 0xf8, 0x10, 0xe9, 0x30, 0x39, 0xcc, 0x93, + 0x03, 0xe5, 0x8a, 0xb7, 0x0e, 0x5f, 0xf5, 0x03, 0x35, 0x6b, 0x29, 0x9a, + 0x25, 0xe9, 0xd0, 0xf5, 0x3a, 0x34, 0x99, 0x27, 0x61, 0xc0, 0xe1, 0xc4, + 0x40, 0x07, 0x93, 0xc8, 0x92, 0x36, 0x92, 0x94, 0x97, 0xa4, 0xae, 0x40, + 0x0b, 0x9f, 0x5c, 0x92, 0xe2, 0x62, 0xed, 0xdf, 0x15, 0x57, 0xf1, 0x3c, + 0x30, 0x62, 0xab, 0x31, 0xc0, 0x0d, 0x35, 0xa2, 0x58, 0xbb, 0xa3, 0x38, + 0x85, 0x6f, 0x8c, 0x46, 0x25, 0x09, 0xf0, 0x4c, 0x9e, 0x9b, 0x60, 0xff, + 0x37, 0xc0, 0xf5, 0xf5, 0x73, 0xa9, 0x7b, 0x9d, 0x4b, 0xed, 0x75, 0xee, + 0xb9, 0xd9, 0xfe, 0xe0, 0x17, 0x18, 0x0c, 0x70, 0x6d, 0x82, 0xe0, 0x0c, + 0xbd, 0x91, 0x92, 0xdc, 0x0a, 0x78, 0xbd, 0xce, 0x3d, 0xf7, 0x3a, 0xf7, + 0x1c, 0x3a, 0x1d, 0x6a, 0xb7, 0x44, 0x44, 0xd7, 0x01, 0x13, 0x37, 0xd8, + 0xea, 0xce, 0xcf, 0x3d, 0x8f, 0xd0, 0xc9, 0x14, 0xc6, 0x8a, 0xe2, 0xed, + 0x50, 0x53, 0x1a, 0x76, 0x0d, 0xb5, 0x36, 0x77, 0xf5, 0xdc, 0xf3, 0xc9, + 0x51, 0xd2, 0x1f, 0x0d, 0x54, 0x05, 0x7b, 0xa4, 0xe9, 0x61, 0xc8, 0xbd, + 0xf5, 0xdc, 0xf3, 0x68, 0x16, 0x35, 0xa9, 0x75, 0xb4, 0x8e, 0xda, 0x73, + 0x11, 0x5e, 0x0d, 0x2d, 0xe2, 0x96, 0x73, 0xcf, 0x47, 0xab, 0x6d, 0x1d, + 0xf5, 0x50, 0x31, 0x50, 0x9b, 0xe7, 0x9e, 0xa3, 0xb3, 0x0d, 0xb4, 0x8e, + 0xf3, 0x73, 0xcf, 0x67, 0xab, 0xed, 0x80, 0xad, 0x23, 0x00, 0x73, 0x33, + 0x0d, 0x35, 0x77, 0x13, 0x6a, 0x9f, 0x9c, 0x7b, 0x6e, 0xfb, 0x71, 0x45, + 0x6d, 0xcc, 0xe1, 0x08, 0xc0, 0x5c, 0x8a, 0xb8, 0x71, 0xb4, 0x14, 0x9b, + 0x96, 0x62, 0xd5, 0x52, 0xcc, 0xb8, 0xb0, 0xda, 0xd7, 0x4e, 0xb2, 0x37, + 0xc4, 0x98, 0x6b, 0x22, 0xc6, 0x54, 0x20, 0x06, 0xf1, 0xab, 0x17, 0x7e, + 0x59, 0xed, 0x08, 0x4f, 0x96, 0x05, 0xd4, 0x3e, 0x9e, 0xab, 0x0f, 0xf3, + 0xc2, 0xdc, 0x12, 0xdb, 0xe7, 0x9e, 0xd8, 0x3e, 0x79, 0x62, 0xfb, 0x28, + 0x6c, 0x7f, 0x66, 0xb5, 0xeb, 0xb3, 0x53, 0xfe, 0x9b, 0x48, 0x09, 0xb0, + 0xf7, 0xec, 0x2b, 0x3d, 0x06, 0xd8, 0x73, 0xef, 0xcd, 0xda, 0x7b, 0xbf, + 0x4b, 0x0e, 0x29, 0xbe, 0x39, 0x40, 0x15, 0x58, 0x9f, 0x70, 0x44, 0x4c, + 0xb6, 0x33, 0x23, 0xc0, 0x08, 0x78, 0xd2, 0x60, 0xf9, 0xe9, 0x82, 0x6e, + 0xd7, 0xe1, 0xa9, 0xcb, 0x7e, 0xb0, 0xda, 0xae, 0xf8, 0x02, 0xc2, 0x3b, + 0x29, 0xe2, 0xbf, 0x49, 0xbf, 0xf5, 0xd2, 0x64, 0x13, 0xbd, 0x48, 0xba, + 0x7b, 0x90, 0xf6, 0x8a, 0xda, 0x22, 0x2d, 0x46, 0x6b, 0x46, 0x5d, 0x0c, + 0x00, 0x9c, 0x74, 0xd8, 0x64, 0x9b, 0x41, 0xed, 0x46, 0xb5, 0x5b, 0xd5, + 0x6e, 0x55, 0xbb, 0x53, 0xed, 0x9e, 0xb5, 0x23, 0x3c, 0xf9, 0x04, 0x51, + 0xbb, 0x52, 0x6d, 0x19, 0x01, 0x92, 0x3e, 0xef, 0x64, 0x0e, 0x48, 0x9a, + 0x7d, 0x84, 0x87, 0xda, 0x7d, 0x36, 0xc1, 0x27, 0x1b, 0x39, 0xa9, 0xa0, + 0x6a, 0xcb, 0x14, 0x91, 0x34, 0xfc, 0x08, 0x8f, 0x6a, 0x9c, 0x6a, 0xb7, + 0xaa, 0xdd, 0xa1, 0x36, 0x49, 0x11, 0x70, 0xca, 0xbe, 0xed, 0x9c, 0x90, + 0xd0, 0x97, 0x45, 0x1f, 0x2e, 0x15, 0x85, 0x43, 0xd1, 0x7c, 0xad, 0x48, + 0x74, 0x8a, 0x8f, 0xa0, 0xe8, 0x3d, 0xd3, 0x0b, 0x45, 0x6f, 0x98, 0x2e, + 0xb4, 0x8d, 0xce, 0x92, 0x55, 0xc3, 0xa5, 0xa2, 0xf6, 0x50, 0x44, 0xbe, + 0xba, 0x76, 0x65, 0xd1, 0xeb, 0x93, 0xa2, 0x45, 0x76, 0x0a, 0x2b, 0xb2, + 0x47, 0x6c, 0x1f, 0x2f, 0x87, 0xa2, 0x67, 0xdc, 0xf7, 0x20, 0x6b, 0xdc, + 0xe1, 0xa0, 0x83, 0xc7, 0xa2, 0xb6, 0x2c, 0x1a, 0x58, 0xb4, 0x1d, 0x8a, + 0x26, 0x16, 0x8d, 0xa9, 0x68, 0x26, 0xe5, 0x41, 0x91, 0xa3, 0xa1, 0xe0, + 0xce, 0xa2, 0x86, 0x45, 0x33, 0x39, 0xc0, 0xc8, 0xc5, 0xee, 0xef, 0x28, + 0xda, 0xa8, 0xda, 0xd7, 0x46, 0x5c, 0x8c, 0xbb, 0x5b, 0xd1, 0x74, 0x2c, + 0xea, 0x59, 0x14, 0x89, 0xed, 0x54, 0xb3, 0x68, 0xb9, 0x4f, 0x51, 0x1b, + 0x40, 0x71, 0x27, 0x7e, 0x68, 0x22, 0xe0, 0x83, 0x65, 0x3d, 0x0c, 0xe1, + 0x20, 0xc6, 0x20, 0xfd, 0x9b, 0xd1, 0xdf, 0xd5, 0xa8, 0x5e, 0x7c, 0xf5, + 0x23, 0xf2, 0x87, 0xd1, 0x28, 0xd4, 0xbe, 0xed, 0xcb, 0xa2, 0x9b, 0x5c, + 0x54, 0x1d, 0x8a, 0xba, 0xdb, 0x8b, 0xa2, 0xf5, 0x7e, 0x60, 0x91, 0x8f, + 0x44, 0x7a, 0xb1, 0xbf, 0x6e, 0x27, 0x3c, 0x56, 0x84, 0xe2, 0xa5, 0x28, + 0xea, 0xcb, 0xa2, 0xfa, 0xf6, 0xa2, 0x50, 0x14, 0x3d, 0x27, 0x7d, 0xb7, + 0xa2, 0x9d, 0x82, 0xba, 0xd6, 0x38, 0x13, 0x3e, 0x62, 0xa6, 0xcf, 0x25, + 0xed, 0x04, 0x3f, 0x16, 0x75, 0x56, 0x14, 0x7d, 0x75, 0xb5, 0xf9, 0x15, + 0xae, 0x14, 0x2d, 0x45, 0x51, 0x87, 0x8f, 0x9c, 0x58, 0xd1, 0x56, 0x95, + 0x45, 0x53, 0x87, 0xcf, 0xa3, 0xc5, 0x88, 0xf4, 0x80, 0x22, 0x63, 0x9b, + 0x63, 0x5b, 0x14, 0x6d, 0x87, 0xa2, 0xc9, 0x8a, 0xc6, 0x54, 0xb4, 0x57, + 0x5c, 0xeb, 0x54, 0xb4, 0x57, 0x1c, 0xfb, 0x4b, 0x45, 0x5d, 0x2a, 0xb2, + 0x0f, 0x46, 0xa1, 0xc8, 0xf0, 0x3e, 0x15, 0x8d, 0xa9, 0x68, 0xcb, 0x45, + 0x43, 0x83, 0x6f, 0xbc, 0x55, 0x51, 0xcc, 0xd9, 0xd9, 0xa1, 0x8a, 0xe6, + 0x43, 0x51, 0x9d, 0x8b, 0xda, 0x38, 0x15, 0x43, 0x43, 0xfe, 0xc3, 0xa2, + 0xc0, 0x6f, 0xcd, 0xa1, 0xc8, 0xbe, 0xfe, 0x66, 0x45, 0x91, 0x4d, 0xe7, + 0xa2, 0xbd, 0xd7, 0x17, 0x8b, 0xe2, 0x62, 0xe5, 0xa2, 0x17, 0xb9, 0xa8, + 0x61, 0x91, 0xbd, 0x8d, 0xb3, 0xb7, 0x82, 0xa2, 0x17, 0x2a, 0x8a, 0x72, + 0xc9, 0x42, 0x4a, 0xf3, 0xc2, 0x44, 0xe0, 0x97, 0x20, 0x42, 0xc7, 0xa2, + 0x97, 0x87, 0xa2, 0x50, 0x16, 0xdd, 0x58, 0xd1, 0x0d, 0xf8, 0xfa, 0x6a, + 0x45, 0x2b, 0x39, 0xf8, 0x8d, 0xd9, 0xfe, 0x52, 0xd1, 0x6a, 0x68, 0xbf, + 0x1d, 0x8a, 0x8c, 0x82, 0x7e, 0x3d, 0x14, 0xbd, 0xb3, 0xa2, 0x77, 0x65, + 0x51, 0x48, 0x45, 0x83, 0x18, 0x51, 0x05, 0x1e, 0xe3, 0x03, 0xd9, 0x3c, + 0x8d, 0xa9, 0xb7, 0x15, 0x8d, 0x65, 0xd1, 0x7a, 0xa9, 0x68, 0x3e, 0x14, + 0x39, 0x15, 0xd1, 0x18, 0xdd, 0x07, 0x0a, 0xd0, 0x32, 0xb2, 0xde, 0xb7, + 0x68, 0x71, 0xb2, 0x50, 0xcb, 0x5e, 0x8d, 0x8e, 0x1e, 0x8a, 0xc4, 0xd2, + 0xc0, 0xeb, 0x3a, 0x14, 0x35, 0x0f, 0x28, 0x1a, 0xad, 0x68, 0x4e, 0xbc, + 0xb0, 0x0a, 0x12, 0x47, 0x20, 0x61, 0x49, 0x02, 0xa1, 0x99, 0x93, 0x0c, + 0xf6, 0xbe, 0x45, 0xcb, 0xa1, 0x68, 0x38, 0x2d, 0xf2, 0xc9, 0xfe, 0x3e, + 0x24, 0xa5, 0xa5, 0x3a, 0x14, 0x75, 0x27, 0x45, 0xf3, 0xa1, 0xc8, 0xdd, + 0x5a, 0x54, 0x1f, 0x8a, 0xfa, 0x64, 0xb5, 0x77, 0x39, 0xee, 0xe8, 0x50, + 0xd4, 0x4a, 0xe5, 0x19, 0x21, 0xd6, 0x82, 0x5b, 0x8f, 0xb4, 0x57, 0x5b, + 0xba, 0x7a, 0x6a, 0x12, 0x7d, 0x99, 0x76, 0xe4, 0xb4, 0xc6, 0x05, 0x2f, + 0xa4, 0x7d, 0xe4, 0x58, 0xcb, 0x79, 0x2a, 0xed, 0xc5, 0x47, 0x2e, 0x73, + 0x9a, 0x1a, 0x93, 0x18, 0xeb, 0xb3, 0x34, 0x48, 0xe1, 0x30, 0x8e, 0x79, + 0x9a, 0x92, 0xb2, 0x93, 0xe2, 0xda, 0xb3, 0x4a, 0x51, 0xd7, 0x13, 0x96, + 0xa7, 0x9c, 0xad, 0xb6, 0xfa, 0x9c, 0x4a, 0xe4, 0xa7, 0xc2, 0x52, 0xa4, + 0x0b, 0x09, 0xd4, 0x52, 0x0a, 0x03, 0x45, 0x6a, 0x1b, 0x5d, 0x02, 0x49, + 0x91, 0x4e, 0x94, 0x61, 0x6c, 0x73, 0x26, 0xb9, 0x49, 0x92, 0x90, 0xe7, + 0x46, 0x52, 0x1a, 0xca, 0xd4, 0xa4, 0xcc, 0xe1, 0x42, 0xea, 0x33, 0xa2, + 0x5e, 0x4c, 0xfb, 0xec, 0xdc, 0x39, 0xa4, 0x12, 0x18, 0x4f, 0xd3, 0x36, + 0xa7, 0x4b, 0x99, 0x36, 0x39, 0x9d, 0x2f, 0xa5, 0x75, 0x4e, 0xa7, 0x32, + 0x6d, 0x72, 0x3a, 0x5e, 0x4a, 0xdb, 0x3b, 0xd2, 0x2e, 0xa7, 0x53, 0x99, + 0xf6, 0x39, 0x9d, 0x2f, 0xa5, 0x3e, 0x48, 0xfe, 0x3f, 0xa6, 0x21, 0xfb, + 0xc0, 0x4e, 0x53, 0xaa, 0xfc, 0x57, 0x53, 0xee, 0x98, 0xf3, 0x74, 0xa0, + 0xa9, 0xc6, 0xd2, 0x2d, 0xa7, 0x87, 0xbd, 0xb3, 0x5e, 0xdc, 0x3b, 0x5d, + 0x4a, 0x97, 0x8b, 0x7b, 0xa7, 0xcf, 0x7b, 0xa7, 0x7b, 0xc8, 0xde, 0x09, + 0x69, 0xef, 0x4c, 0x97, 0xf6, 0x4e, 0x95, 0xf7, 0xce, 0x61, 0x0f, 0xd5, + 0xdc, 0x37, 0xf5, 0x95, 0xbd, 0xd3, 0x24, 0xc9, 0x22, 0x5c, 0xdc, 0x3b, + 0xed, 0x31, 0x4d, 0x7b, 0xa6, 0x3b, 0xa6, 0x4b, 0x4e, 0xb1, 0x67, 0xfa, + 0x63, 0x8a, 0xcf, 0x63, 0x35, 0xb4, 0xb1, 0xe4, 0x14, 0xde, 0xbf, 0x62, + 0xef, 0x50, 0x2d, 0xc9, 0xac, 0x2b, 0xa5, 0xb3, 0xe1, 0x0a, 0x6d, 0x3d, + 0x0a, 0x52, 0x70, 0x81, 0x46, 0x9a, 0x8a, 0x6e, 0xe6, 0x58, 0xf7, 0x85, + 0x08, 0x28, 0x2c, 0xe1, 0x9d, 0x8c, 0x34, 0x3d, 0xbb, 0x19, 0xe7, 0xb8, + 0x27, 0x75, 0x9f, 0x64, 0x54, 0x19, 0xbd, 0x34, 0x91, 0x80, 0xe5, 0xdc, + 0x2a, 0xf2, 0x17, 0x5a, 0x57, 0xbc, 0xb9, 0xce, 0x07, 0xae, 0x04, 0x64, + 0x7d, 0x4c, 0x5b, 0x2d, 0xeb, 0x4a, 0xfc, 0xc7, 0x89, 0x58, 0x1d, 0x3d, + 0xd3, 0x0b, 0xac, 0x21, 0x11, 0x3a, 0xad, 0x21, 0xa3, 0xac, 0x21, 0x30, + 0x8b, 0xd0, 0x4e, 0x61, 0x7e, 0x41, 0xe4, 0xc2, 0x1a, 0x62, 0x92, 0xf3, + 0x0c, 0xdd, 0x28, 0x04, 0x98, 0x45, 0x2a, 0x14, 0x27, 0xab, 0xc9, 0xea, + 0xb8, 0x2e, 0x0b, 0xda, 0x85, 0x19, 0x63, 0x56, 0x73, 0x93, 0xd5, 0x94, + 0x0d, 0x67, 0x94, 0x0d, 0x67, 0x68, 0xc8, 0x7c, 0x61, 0x88, 0x68, 0xcc, + 0xfe, 0xb0, 0xa0, 0xfb, 0xc2, 0x65, 0x47, 0x0e, 0xba, 0xc2, 0xa2, 0xd4, + 0x5b, 0x04, 0x8c, 0x2b, 0x0c, 0x53, 0x9e, 0xf8, 0x8b, 0x39, 0x9c, 0x6d, + 0x05, 0x46, 0xad, 0xd6, 0x33, 0x2d, 0xc5, 0x73, 0x2d, 0x05, 0xa2, 0x1c, + 0xcc, 0xf5, 0x09, 0x26, 0x0f, 0xcd, 0xcf, 0x75, 0xdc, 0x3e, 0x30, 0xbb, + 0x8d, 0x76, 0xf1, 0x51, 0x04, 0xef, 0xb3, 0x0c, 0x07, 0x7f, 0x68, 0x4b, + 0xbf, 0xb3, 0xda, 0xc2, 0x0b, 0x20, 0xca, 0xe8, 0xf2, 0x6e, 0x75, 0x07, + 0x8d, 0x5f, 0x74, 0x30, 0x9c, 0xab, 0xfe, 0x23, 0x49, 0xc8, 0x51, 0xe3, + 0x17, 0xd1, 0x90, 0x64, 0x84, 0x16, 0x44, 0x06, 0x13, 0xdd, 0x3b, 0x57, + 0xd5, 0x37, 0x5d, 0x24, 0x9d, 0xfd, 0x44, 0x55, 0xf7, 0x6c, 0xd2, 0xa9, + 0x76, 0xa5, 0x4a, 0x72, 0xbb, 0x4b, 0x76, 0x28, 0xac, 0x9c, 0x22, 0x99, + 0xd4, 0x67, 0x9d, 0xb8, 0xbc, 0x4b, 0xac, 0x5d, 0x6e, 0x1e, 0x27, 0x96, + 0x9f, 0xbc, 0x40, 0x59, 0x16, 0x00, 0x2e, 0x11, 0x8c, 0x23, 0x98, 0xed, + 0x09, 0xcc, 0x3f, 0x07, 0x4c, 0xc4, 0xfc, 0x80, 0xf3, 0x0b, 0xec, 0x74, + 0xa4, 0x1f, 0x75, 0xe3, 0x92, 0x50, 0xda, 0x49, 0xe4, 0x9c, 0xac, 0xeb, + 0x59, 0xfe, 0xe4, 0x38, 0x28, 0x2c, 0x78, 0xdd, 0x0c, 0x69, 0x60, 0xad, + 0xa4, 0xa1, 0x25, 0xf5, 0x3f, 0xc1, 0xbc, 0xb3, 0x81, 0xe9, 0x5f, 0xb8, + 0x81, 0xf1, 0xe7, 0x35, 0x30, 0x7c, 0xbf, 0x06, 0xc2, 0xc3, 0x1a, 0x08, + 0x3f, 0xaf, 0x81, 0xed, 0xa1, 0x0d, 0xc0, 0x8b, 0x65, 0x47, 0x9a, 0x04, + 0xbc, 0x09, 0xfd, 0x43, 0x6f, 0x62, 0x2f, 0xf5, 0x19, 0x3d, 0x6a, 0x9a, + 0x85, 0x41, 0xb9, 0xe7, 0x85, 0xc4, 0x59, 0xd0, 0x80, 0xd4, 0x4b, 0x47, + 0x96, 0xb9, 0xf1, 0x51, 0x69, 0xc8, 0x81, 0xd6, 0x53, 0xc0, 0xf7, 0x76, + 0x31, 0xb2, 0x24, 0xa8, 0x64, 0x63, 0x9d, 0xa0, 0x17, 0xb2, 0x21, 0xa6, + 0xe0, 0xd9, 0xbd, 0x64, 0xf2, 0x90, 0x6d, 0xec, 0xc3, 0x7a, 0xe0, 0x66, + 0xc1, 0x24, 0x14, 0x9e, 0x44, 0xe6, 0x21, 0xc0, 0x8c, 0x15, 0xc4, 0x1a, + 0x9e, 0x71, 0x96, 0x64, 0x26, 0x1f, 0x70, 0x36, 0x59, 0x65, 0xa2, 0x2e, + 0x3c, 0x39, 0x43, 0x0b, 0x81, 0x67, 0x03, 0x86, 0xc2, 0xd3, 0x35, 0x65, + 0x59, 0x2c, 0x0a, 0x33, 0x9d, 0x44, 0x30, 0x3e, 0xb2, 0xea, 0x91, 0x59, + 0x8f, 0x8c, 0x7c, 0x84, 0x1e, 0xb4, 0xde, 0x5e, 0xf2, 0x1b, 0xed, 0x91, + 0x96, 0x82, 0xdc, 0xd8, 0xc3, 0x24, 0xc5, 0x93, 0xcc, 0x1e, 0xe9, 0x23, + 0xf8, 0xd5, 0x4c, 0xf5, 0xc1, 0x21, 0x3a, 0x93, 0x82, 0x6c, 0x15, 0x81, + 0x86, 0xa7, 0x90, 0x4c, 0x19, 0x0f, 0x7e, 0x24, 0x33, 0xa2, 0xc4, 0x7f, + 0xc4, 0x76, 0x12, 0xb7, 0xb9, 0xfb, 0x91, 0xef, 0xd3, 0x97, 0x87, 0x3c, + 0xe2, 0x7e, 0xfe, 0x1a, 0xfd, 0x7d, 0x8f, 0x5c, 0x42, 0xef, 0x3b, 0x76, + 0xc0, 0xc9, 0x3e, 0xe2, 0x56, 0x1b, 0x8e, 0x5b, 0x8d, 0xbb, 0x71, 0x39, + 0xee, 0xc6, 0x58, 0xdc, 0xc0, 0x1b, 0xb0, 0x82, 0x05, 0x68, 0x4f, 0x37, + 0xd0, 0x6d, 0x70, 0xee, 0x60, 0xad, 0x6d, 0xbf, 0x3f, 0x3f, 0xc5, 0xbf, + 0xb8, 0xff, 0x7f, 0x0f, 0x50, 0x33, 0xb1, 0x80, 0x24, 0x1e, 0x13, 0xd7, + 0x8f, 0xc6, 0x47, 0x3a, 0x3c, 0xb8, 0xb4, 0x3d, 0x4b, 0x9c, 0x4a, 0x12, + 0x95, 0xaa, 0x55, 0xd2, 0x08, 0x1d, 0x5a, 0x40, 0x63, 0x6d, 0xe7, 0x0a, + 0xd9, 0xbf, 0x0f, 0x24, 0x90, 0x9d, 0x84, 0xec, 0x4e, 0x58, 0x5b, 0x53, + 0x56, 0x72, 0x0c, 0xc4, 0x2e, 0x0d, 0x26, 0x06, 0xad, 0x21, 0xb4, 0x9a, + 0x0d, 0x3b, 0x35, 0x6c, 0x7d, 0x5b, 0x84, 0x75, 0x03, 0x7b, 0xa4, 0x70, + 0x86, 0xa0, 0xce, 0xcf, 0x69, 0x58, 0x69, 0xc0, 0x9a, 0x01, 0x6e, 0xdc, + 0x91, 0x13, 0x60, 0x01, 0x13, 0xb1, 0x67, 0x43, 0x0b, 0x0a, 0xaa, 0x83, + 0x22, 0xb9, 0x5c, 0x81, 0x7a, 0xb8, 0x74, 0x52, 0xb8, 0x7c, 0x65, 0x3c, + 0xd8, 0xe4, 0xf1, 0x1d, 0x4d, 0x93, 0x97, 0xe6, 0x6d, 0x7a, 0x1b, 0x9c, + 0x50, 0x53, 0x36, 0x3b, 0x20, 0xf6, 0xc0, 0x06, 0x33, 0x7a, 0xda, 0x2b, + 0x5a, 0x72, 0x01, 0xa9, 0x7c, 0x93, 0x0c, 0x5d, 0x98, 0x19, 0x81, 0x90, + 0xd6, 0x5f, 0x2b, 0x7c, 0xc1, 0x53, 0x79, 0x6e, 0x19, 0xbd, 0x50, 0x29, + 0xc0, 0xa1, 0xa7, 0x7a, 0xdd, 0x18, 0xb2, 0xda, 0x22, 0x0e, 0x1e, 0xc6, + 0x90, 0x99, 0xe1, 0x08, 0x65, 0x7f, 0x93, 0x1a, 0x84, 0x05, 0xf1, 0xa4, + 0x22, 0x2d, 0xd7, 0x25, 0xc9, 0xb9, 0xdd, 0xf5, 0x27, 0x83, 0x08, 0xd0, + 0x95, 0x27, 0xfb, 0xac, 0x62, 0x5d, 0x7e, 0xb2, 0x12, 0x55, 0xbb, 0xfa, + 0x64, 0xd1, 0x9d, 0x8b, 0x4f, 0x62, 0xd1, 0xd3, 0x47, 0x74, 0xd3, 0x0b, + 0x29, 0x4b, 0xc7, 0x0b, 0x91, 0xed, 0x09, 0x58, 0x90, 0xc9, 0xf7, 0x33, + 0xa6, 0x7a, 0xa9, 0xe4, 0x01, 0x20, 0x6e, 0x99, 0x8c, 0xfb, 0x4c, 0xdb, + 0xfd, 0x27, 0xf8, 0x6c, 0xec, 0xab, 0xc6, 0x7e, 0xe7, 0x93, 0x77, 0xcc, + 0xfc, 0x7d, 0x1a, 0x3b, 0x82, 0x48, 0xfd, 0x3d, 0xeb, 0x85, 0x18, 0x50, + 0x77, 0xbd, 0x60, 0xbb, 0x5c, 0xd0, 0x9f, 0x14, 0x54, 0xb9, 0x20, 0x39, + 0xd4, 0x07, 0x76, 0x77, 0x62, 0xcd, 0xe5, 0x22, 0xc8, 0xea, 0xf4, 0x81, + 0x3e, 0xd3, 0x97, 0x81, 0x0f, 0x8e, 0x7c, 0x10, 0x8b, 0xc8, 0xd4, 0x4e, + 0xdb, 0x89, 0xbf, 0x4f, 0x4c, 0x37, 0xae, 0xb2, 0x1d, 0xbf, 0x13, 0x7f, + 0x5f, 0xf4, 0x24, 0x11, 0xc5, 0xce, 0xe3, 0xf9, 0x46, 0x10, 0xd3, 0x3f, + 0xc9, 0xc2, 0x16, 0x2b, 0x34, 0xff, 0x60, 0x0b, 0xdb, 0xa7, 0x7f, 0xa2, + 0x85, 0xad, 0x13, 0xad, 0x24, 0xca, 0x2b, 0x3c, 0x31, 0xe8, 0x43, 0x4d, + 0xe9, 0x13, 0x5f, 0x89, 0x21, 0xdb, 0xf9, 0xed, 0xf6, 0x13, 0xab, 0xde, + 0xee, 0x03, 0xa6, 0xa0, 0x06, 0x7f, 0xbf, 0xf9, 0xe7, 0x47, 0x0d, 0x2a, + 0x81, 0xf9, 0x19, 0x83, 0x7a, 0xec, 0x60, 0xfe, 0x3f, 0xa1, 0xf4, 0x77, + 0x98, 0xe4, 0xf5, 0x00, 0x00 +}; const GFXglyph Antonio_SemiBold90pt7bGlyphs[] PROGMEM = { {0, 1, 1, 38, 0, 0}, // 0x20 ' ' @@ -5322,8 +1086,16 @@ const GFXglyph Antonio_SemiBold90pt7bGlyphs[] PROGMEM = { }; -const GFXfont Antonio_SemiBold90pt7b PROGMEM = { - (uint8_t *)Antonio_SemiBold90pt7bBitmaps, - (GFXglyph *)Antonio_SemiBold90pt7bGlyphs, 0x20, 0x5E, 231}; -// Approx. 60745 bytes + + +// Font properties +static constexpr FontData Antonio_SemiBold90pt7b_Properties = { + Antonio_SemiBold90pt7bBitmaps_Gzip, + Antonio_SemiBold90pt7bGlyphs, + sizeof(Antonio_SemiBold90pt7bBitmaps_Gzip), + 62948, // Original size + 0x20, // First char + 0x5E, // Last char + 231 // yAdvance +}; \ No newline at end of file diff --git a/src/fonts/fonts.hpp b/src/fonts/fonts.hpp index 099c4e0..a1f03bd 100644 --- a/src/fonts/fonts.hpp +++ b/src/fonts/fonts.hpp @@ -1,15 +1,71 @@ #pragma once -#include "antonio-semibold20.h" -//#include "antonio-semibold30.h" -#include "antonio-semibold40.h" -#include "antonio-semibold90.h" -#include "sats-symbol.h" -//#include "icons.h" +#include +#include +#include -// #include "oswald-20.h" -// #include "oswald-30.h" -// #include "oswald-90.h" -// #include "ubuntu-italic40.h" -// #include "ubuntu-italic60.h" -// #include "ubuntu-italic70.h" \ No newline at end of file +// Font metadata structure +struct FontData { + const uint8_t* compressedData; + const GFXglyph* glyphs; + const size_t compressedSize; + const size_t originalSize; + const uint16_t first; + const uint16_t last; + const uint8_t yAdvance; +}; + + +class FontLoader { +public: + static GFXfont* loadCompressedFont( + const uint8_t* compressedData, + const GFXglyph* glyphs, + const size_t compressedSize, + const size_t originalSize, + const uint16_t first, + const uint16_t last, + const uint8_t yAdvance) + { + uint8_t* decompressedData = (uint8_t*)malloc(originalSize); + if (!decompressedData) { + Serial.println(F("Failed to allocate memory for font decompression")); + return nullptr; + } + + size_t decompressedSize = originalSize; + if (GzipDecompressor::decompressData(compressedData, + compressedSize, + decompressedData, + &decompressedSize)) + { + GFXfont* font = (GFXfont*)malloc(sizeof(GFXfont)); + if (!font) { + free(decompressedData); + Serial.println(F("Failed to allocate memory for font structure")); + return nullptr; + } + + font->bitmap = decompressedData; + font->glyph = (GFXglyph*)glyphs; + font->first = first; + font->last = last; + font->yAdvance = yAdvance; + + return font; + } + + Serial.println(F("Font decompression failed")); + free(decompressedData); + return nullptr; + } + + static void unloadFont(GFXfont* font) { + if (font) { + if (font->bitmap) { + free((void*)font->bitmap); + } + free(font); + } + } +}; diff --git a/src/fonts/oswald-medium20.h b/src/fonts/oswald-medium20.h new file mode 100644 index 0000000..0509361 --- /dev/null +++ b/src/fonts/oswald-medium20.h @@ -0,0 +1,323 @@ +#pragma once + +#include +#include +#include "fonts.hpp" + +const uint8_t Oswald_Medium20pt7bBitmaps_Gzip[] = { + 0x1f,0x8b,0x08,0x00,0xf7,0xa4,0x71,0x67,0x02,0xff,0xad,0x58, + 0xcf,0x8e,0xdb,0xb8,0x19,0xa7,0xa0,0xa2,0xbc,0x04,0xe1,0xb5, + 0x87,0x81,0x98,0x37,0xe8,0x1e,0xb3,0x80,0x22,0xf6,0x51,0xda, + 0x37,0x48,0xb1,0x87,0x3a,0x18,0x45,0xd4,0xc0,0x07,0xdf,0x56, + 0x2f,0xb0,0x88,0x5f,0x63,0x81,0xa6,0x19,0x1a,0x2e,0xe0,0x4b, + 0xb1,0x7e,0x81,0x36,0xa6,0xa1,0x83,0x6f,0x2b,0x1a,0x3e,0x98, + 0x86,0x39,0x64,0x7f,0x94,0xec,0x19,0x4f,0x32,0x49,0x16,0x45, + 0xc5,0xb1,0xf4,0xf1,0xff,0xc7,0xef,0xef,0x8f,0x43,0x42,0x7c, + 0x8e,0xff,0xde,0x7c,0xff,0xcb,0x0f,0x3f,0x6d,0xbe,0xff,0xd7, + 0x8b,0x24,0x56,0xbd,0x5c,0xbe,0xe9,0x5e,0x6d,0x7e,0xde,0x67, + 0xed,0x4f,0x3f,0xfc,0x48,0x58,0x43,0x27,0xe3,0xc5,0xac,0x55, + 0x5b,0x3d,0x32,0xf9,0xa8,0xc8,0xb3,0x8c,0x31,0xd1,0xcf,0x34, + 0x5b,0x6d,0xcd,0x68,0x94,0xe7,0x32,0xdc,0x86,0x55,0xe8,0xc2, + 0xae,0x9d,0xad,0xb5,0x31,0x23,0x9b,0x8f,0xb2,0x9c,0x65,0xb4, + 0xa1,0x8b,0xf1,0x7c,0xd6,0x6a,0x52,0x93,0x3f,0x91,0x17,0xc4, + 0x27,0x61,0x16,0xb6,0xc1,0xba,0xca,0xf1,0x8a,0xdd,0xd2,0xd5, + 0xb8,0x53,0xa9,0x49,0x1c,0x09,0x44,0x28,0x66,0xd2,0x33,0x81, + 0x96,0x8a,0x88,0x9c,0x79,0x1a,0xc6,0x61,0x76,0xd4,0x77,0xd6, + 0x49,0x2f,0x3c,0x0b,0xa9,0x27,0x7f,0x20,0xcf,0xc8,0xef,0x49, + 0x4a,0x58,0x9d,0x11,0x66,0x69,0x4d,0x43,0xa2,0xd2,0xa0,0x5f, + 0x13,0x6b,0x73,0x92,0x17,0x8c,0x30,0x3a,0xae,0x53,0xb0,0x41, + 0xc0,0x19,0xc9,0xb3,0x2b,0x34,0x34,0x24,0x05,0xff,0xc4,0x58, + 0x4d,0x8a,0xe2,0x25,0x61,0x21,0x23,0x49,0x58,0x4c,0xac,0x58, + 0xcf,0x43,0xaa,0xb6,0x41,0x93,0x7c,0x64,0xc9,0xf3,0x22,0x23, + 0x69,0x33,0x51,0x49,0xbb,0x36,0x64,0x64,0x4b,0x72,0x55,0x70, + 0xc2,0xd8,0x44,0xa5,0x73,0x34,0x68,0x3b,0x22,0x23,0x8c,0xc8, + 0x68,0xa8,0xe9,0x4d,0x50,0x89,0x12,0x86,0x18,0x6a,0xa8,0x4e, + 0x03,0x09,0x86,0x7b,0xda,0x6a,0x5b,0x72,0x36,0x5e,0x9b,0xb2, + 0x60,0x93,0x7a,0x6b,0x78,0x9e,0x74,0x44,0xe8,0xd4,0xe2,0x30, + 0xac,0x4e,0x6d,0x1e,0x26,0x1f,0xb6,0xfb,0x95,0xd8,0x6e,0x3e, + 0x14,0xfb,0xb9,0xb7,0x72,0x4a,0x77,0xba,0x2c,0xf9,0x26,0xf8, + 0x10,0x96,0xce,0x06,0x06,0x91,0xfe,0xfa,0xab,0xa6,0x8c,0x8b, + 0xaa,0x2c,0xed,0x97,0x9e,0xb2,0x2c,0x85,0xe0,0x54,0x97,0x82, + 0xcf,0x35,0x36,0xc3,0x9e,0x33,0x63,0xe3,0x77,0x02,0x46,0xef, + 0x89,0x81,0x62,0xcd,0x72,0x31,0x57,0xac,0x7e,0x99,0xa8,0x1f, + 0xff,0xf8,0x37,0x6c,0xd0,0x5a,0x56,0x7b,0x36,0x3a,0xb6,0x3f, + 0x3e,0xfb,0x73,0x5a,0x53,0xf2,0x9c,0x5c,0x91,0x97,0xe4,0x75, + 0xaf,0x50,0x7f,0x5f,0x25,0x3a,0x51,0x68,0xf8,0xe9,0xd7,0x3d, + 0xc1,0xc7,0xe1,0x57,0x93,0x51,0xaa,0xae,0x88,0x66,0xf8,0xd6, + 0x79,0xa2,0x9f,0x93,0xd7,0x14,0x5f,0x95,0x41,0x08,0xe4,0x65, + 0x8a,0xef,0xd0,0x75,0x95,0xe0,0x7b,0xee,0xa2,0x86,0x41,0x6b, + 0x4d,0x58,0x84,0xdd,0xfa,0x68,0xde,0x5a,0xe1,0x38,0xea,0xbd, + 0x56,0x83,0x0e,0x46,0x7e,0xa1,0x61,0x45,0x97,0x8b,0x0d,0x0c, + 0xc5,0x48,0xc3,0x4d,0x3a,0xdb,0xde,0x45,0xe6,0x2a,0x4e,0x67, + 0xa6,0xfc,0xca,0xcb,0x70,0x27,0xbc,0xf4,0x52,0x3a,0x61,0xf9, + 0xa9,0x08,0x22,0x48,0x01,0xf9,0x3b,0xe2,0x12,0x9b,0x9a,0x14, + 0xea,0xa2,0x8a,0x29,0x5e,0xf3,0x5a,0xa0,0x39,0x76,0x40,0xfa, + 0x7d,0x61,0x46,0x58,0x09,0x3b,0x73,0x95,0x15,0xf6,0xb4,0x02, + 0x89,0xf3,0xab,0xd4,0xa5,0x16,0xc5,0xc1,0xfa,0xaa,0xbe,0xe5, + 0x7e,0x75,0xb0,0x5b,0x05,0x8f,0x0d,0x1d,0x37,0xc4,0x13,0x51, + 0x33,0x4d,0x6d,0xe2,0x49,0xa8,0x85,0xce,0x2c,0xfd,0x90,0x74, + 0xf5,0x4e,0xe7,0x36,0x2b,0x68,0x33,0x6e,0xb5,0xb1,0x79,0xd4, + 0x0d,0x28,0x1b,0xce,0x0f,0xb8,0x83,0x6d,0x68,0x62,0xef,0x89, + 0x33,0x3f,0xc1,0x5b,0xf2,0x50,0x0e,0x26,0x58,0xe8,0xe1,0xc4, + 0x1d,0xe1,0xe7,0x72,0xe6,0xc4,0x15,0xd2,0xf7,0x07,0x37,0x83, + 0xcc,0x27,0x61,0xd1,0xee,0xf4,0x01,0x32,0x2f,0x7a,0x67,0x22, + 0xbc,0x66,0xff,0xa4,0xa1,0x0d,0x87,0x68,0x6d,0x15,0x8f,0xce, + 0xf7,0x48,0xe6,0xd3,0x5e,0xe6,0xeb,0x41,0xe6,0x67,0xe6,0x5c, + 0xaa,0x59,0x5d,0x24,0x96,0x2a,0x4e,0xca,0xc4,0x30,0x55,0x10, + 0x9b,0x6a,0x5e,0x83,0xa6,0x4a,0x44,0xfa,0xa1,0x3b,0x35,0xac, + 0x66,0x26,0xb2,0x22,0x7d,0x55,0xb9,0xe2,0x2c,0x43,0x27,0xca, + 0xa2,0xac,0xb0,0x87,0x1d,0xfa,0xca,0xe2,0x42,0x3f,0xa7,0x11, + 0xe0,0xdd,0x0b,0xc7,0xa0,0x9d,0xd3,0x98,0xca,0xdd,0xeb,0xe0, + 0x61,0x14,0x54,0x1b,0x64,0x10,0x2b,0x36,0xbd,0x38,0xbd,0xb8, + 0x3c,0x7b,0xb4,0x55,0x12,0x9f,0x34,0x46,0xa7,0xfb,0x1a,0x21, + 0xd1,0xbc,0xaf,0xfe,0xfa,0xee,0x35,0x79,0xf1,0x8c,0x8e,0xb7, + 0x90,0xc0,0x42,0x1b,0x9e,0x3a,0xd1,0x7a,0xae,0x2c,0x25,0x2f, + 0x86,0xf3,0x9a,0x61,0x70,0xef,0x0a,0xf5,0x33,0x82,0xf3,0x06, + 0x0a,0xcd,0x4a,0x62,0x04,0xdb,0xf9,0xd0,0x68,0xfb,0x1c,0x91, + 0x8c,0x69,0xb9,0x0d,0x7b,0x44,0xbb,0x00,0x41,0xe3,0x58,0x42, + 0x95,0xc4,0x26,0x91,0x77,0xd8,0x94,0x83,0x18,0x20,0x99,0x92, + 0x98,0x44,0xa7,0x8a,0xd6,0xc3,0x8a,0xbd,0xf1,0x51,0xcd,0x14, + 0x41,0x68,0x43,0xfc,0x51,0x84,0x07,0x8b,0x1f,0x4c,0x06,0xb6, + 0x28,0xa0,0x77,0x4e,0x08,0xcc,0x8d,0xe4,0x3c,0xd9,0x34,0x2c, + 0x39,0x2e,0x9a,0x71,0x68,0x17,0x37,0xc1,0xec,0x67,0xd6,0x5c, + 0xcf,0xec,0xe8,0x95,0xb6,0x79,0x66,0x6c,0xc1,0x46,0xb0,0x74, + 0x57,0xb0,0xb1,0xcf,0xe8,0xcc,0xb3,0x54,0x75,0xf4,0x46,0xef, + 0xc7,0xca,0x5c,0xc3,0xf5,0xdf,0x18,0xeb,0x72,0x2b,0x7d,0x9e, + 0xf3,0xce,0x65,0x74,0xef,0xd8,0x8d,0xb1,0xa9,0x22,0x24,0x89, + 0x27,0x2b,0xa3,0x1c,0xf0,0x63,0x08,0xc7,0x69,0x98,0x2a,0x9c, + 0x53,0x83,0x07,0x83,0x58,0xa8,0x48,0x6a,0x88,0x44,0x0f,0x42, + 0xb0,0x84,0x16,0x13,0xc4,0x61,0xcd,0x5c,0xb2,0xaf,0x0b,0x43, + 0x57,0xe4,0xa8,0x8b,0x92,0x2e,0x6b,0x6b,0x38,0x4f,0x5b,0xed, + 0x86,0x28,0x27,0x11,0xaf,0xda,0xe0,0x64,0x68,0x6e,0xb6,0x46, + 0x14,0x69,0xa7,0xde,0x5a,0x76,0x9b,0xec,0x95,0x08,0x5a,0xa0, + 0x6f,0x17,0x9c,0x93,0xb7,0x68,0x3e,0x9a,0xc2,0x21,0x74,0x0f, + 0x04,0xbf,0x0d,0xcb,0x30,0x87,0x2d,0x07,0xc1,0x3a,0x15,0x8c, + 0x70,0x08,0xe0,0x41,0xc1,0xe8,0x06,0x02,0xf6,0x17,0x42,0x1c, + 0xd3,0x06,0xb8,0x52,0x0c,0x0c,0xdb,0xe0,0x2a,0x3e,0xa5,0xa7, + 0x75,0x6e,0xd3,0xd0,0xcf,0xc2,0x49,0x18,0x72,0x07,0x79,0x20, + 0xb0,0x45,0xec,0xea,0xc7,0x74,0xea,0x60,0xab,0xc8,0x20,0x2c, + 0x5c,0x98,0x4b,0x7e,0xe8,0xb0,0xe9,0x69,0xf0,0x37,0x89,0xd9, + 0xd1,0xc0,0x58,0xc4,0xc0,0xcf,0xc9,0x2b,0x4e,0x2a,0xbe,0xff, + 0xa0,0x53,0x38,0x2c,0xbf,0xbc,0x6c,0xec,0x3f,0xe1,0xd1,0xa3, + 0xaa,0xd4,0xf0,0xda,0x51,0x2d,0x12,0xcb,0x50,0x09,0x2b,0x0f, + 0x33,0xba,0x1d,0x2a,0x8f,0x7a,0x50,0x39,0x9f,0xdd,0xe3,0xec, + 0xe9,0x23,0x9e,0x2f,0x8e,0x2c,0x03,0x47,0x68,0x85,0x80,0x2e, + 0x64,0x88,0x31,0x77,0x16,0x8e,0xb2,0xf4,0xdb,0xf0,0x5e,0x2c, + 0x3f,0x11,0xef,0x67,0xc4,0x03,0x77,0x5f,0x1e,0x13,0x89,0xf0, + 0xc4,0xf3,0x8d,0x78,0x3c,0x33,0x4e,0x60,0xde,0xd1,0x21,0xf8, + 0xb2,0xd5,0xcd,0xce,0x54,0x15,0x5b,0xce,0xb6,0xb6,0x12,0x6c, + 0xd2,0x1a,0x58,0xd1,0x74,0x8c,0xb6,0xd8,0x05,0x05,0x61,0xa3, + 0x3a,0x58,0x1e,0xd2,0xa3,0xc6,0xb0,0xc5,0xcc,0x62,0xee,0x64, + 0x1d,0x57,0xc0,0xd8,0x38,0x11,0x1a,0xe5,0xb7,0xe9,0x1e,0x23, + 0x3f,0x11,0xff,0x37,0x3f,0x27,0x6e,0x2b,0x72,0x67,0x31,0x9f, + 0xad,0x94,0x94,0xc9,0x9d,0xa3,0x7b,0xc3,0x57,0x2a,0xc8,0xf1, + 0x9d,0x67,0x7b,0x2b,0x56,0xba,0xbb,0x9e,0xfc,0xa3,0xcb,0xf6, + 0xef,0xaf,0x3f,0x6e,0xbb,0xeb,0xc5,0xab,0x4d,0xbe,0x7f,0xb7, + 0xfb,0x78,0xbd,0x3c,0x74,0x62,0xf3,0x73,0x68,0x5a,0x2f,0x1a, + 0xeb,0x45,0x6b,0xc3,0x64,0xee,0x39,0xb3,0xa5,0x58,0x9b,0x30, + 0x19,0xfb,0xe7,0xcc,0x32,0x47,0x7d,0x1a,0x6e,0x7a,0xdb,0xb5, + 0xc2,0xf3,0x10,0xa3,0x6f,0x1b,0xd5,0x12,0x3e,0xca,0x4e,0xec, + 0x57,0x87,0x0e,0x81,0x5b,0x40,0x57,0x0d,0x2c,0x7f,0x7d,0x8e, + 0xb8,0x51,0x55,0x35,0x64,0x0b,0x03,0x65,0x17,0xda,0xbe,0xfd, + 0x6d,0x86,0xf9,0x40,0xdc,0x45,0x4b,0x6f,0x4e,0x96,0x6e,0x04, + 0xc8,0x1d,0xd2,0x84,0xb8,0x4d,0x3f,0xb5,0xf4,0x5b,0xd6,0x85, + 0x63,0xcc,0x21,0xa0,0x1f,0xbb,0xce,0x67,0xc4,0xff,0x8b,0x9f, + 0x98,0xd4,0x54,0x12,0x73,0x1b,0x25,0x44,0x45,0x3f,0xbc,0xe7, + 0x2e,0x3c,0x38,0xff,0x30,0xf5,0xc4,0x1d,0x34,0xce,0x16,0xed, + 0xd6,0xba,0x42,0xb0,0xe5,0x7c,0x67,0x9c,0xe3,0x82,0x76,0xf0, + 0xc3,0xea,0xec,0xfc,0x9c,0xe9,0x21,0x53,0xfb,0xca,0x9e,0xb2, + 0x0c,0x2c,0x0c,0xe9,0x57,0x22,0x76,0x09,0x8d,0x0c,0x88,0x5c, + 0x9d,0x0c,0x0d,0x62,0x74,0x9f,0x85,0xbc,0x88,0x18,0x32,0x66, + 0x99,0x7b,0x03,0xd6,0x08,0xdb,0x69,0x04,0x04,0x35,0x07,0x8b, + 0x7d,0x14,0xff,0x56,0xf5,0xb7,0xcb,0xe1,0x53,0x62,0xff,0x48, + 0x32,0x96,0x02,0x0c,0xd7,0x6f,0x0d,0x2f,0xd9,0x2a,0xdd,0xa8, + 0x83,0x29,0x0a,0xd6,0x4c,0xda,0x01,0xbf,0xd1,0xc9,0x6c,0xab, + 0x4b,0xcb,0x0b,0x04,0xdd,0x9d,0x2e,0x0c,0x7b,0x1f,0x31,0xa6, + 0x8a,0x27,0x03,0xb8,0x90,0x35,0x8f,0xa0,0x72,0x50,0x96,0xcd, + 0xa8,0xa7,0x37,0xfb,0x1b,0xfd,0x46,0xdb,0xa2,0x14,0x6c,0xca, + 0xe6,0x9b,0xb9,0x39,0x18,0x17,0x2b,0x0d,0x9b,0xcf,0x17,0xd6, + 0x1c,0x90,0x73,0xe5,0x94,0x4d,0x37,0xf3,0xcd,0xf7,0xe6,0xf0, + 0x4b,0x99,0xed,0x3e,0xd2,0xf7,0xfb,0x9b,0xee,0x95,0x96,0x4b, + 0xcb,0xd7,0x9e,0x5a,0x79,0xe3,0xb9,0x16,0x13,0xc3,0xd6,0x80, + 0x3a,0x12,0xee,0xaa,0x44,0xf4,0x1d,0x38,0x10,0x52,0x19,0xce, + 0x9f,0xea,0x12,0xe6,0x64,0x2d,0xe2,0xff,0x1c,0x49,0x88,0xd3, + 0x8d,0x2a,0x2d,0x9b,0x42,0x77,0xdc,0xa6,0x31,0x89,0xd1,0x1e, + 0xe4,0x2a,0xb0,0x25,0x09,0x20,0x1b,0x98,0x54,0xdc,0xa5,0x5d, + 0xfd,0xd6,0xf6,0x29,0xc3,0x02,0xbc,0xcd,0xb5,0xeb,0xa7,0x68, + 0x04,0x95,0xae,0x3e,0x18,0xa8,0x75,0x40,0xcc,0x73,0xdd,0x2f, + 0x8c,0x83,0x5a,0xe4,0x9a,0xa0,0x99,0xed,0xd3,0x4f,0xbf,0x16, + 0x8b,0xc8,0x88,0x13,0xe0,0xa2,0x12,0xf0,0x28,0x89,0x06,0x45, + 0xd5,0x65,0x93,0xf4,0xe1,0x0e,0x76,0x03,0x38,0x12,0xb3,0x6f, + 0x89,0x3c,0xab,0x23,0x59,0x0c,0x24,0xeb,0x49,0x77,0x22,0x49, + 0x24,0xa1,0x43,0x76,0x11,0xc3,0x1e,0x63,0xe6,0x2f,0x11,0xc1, + 0x9b,0x14,0xf7,0x0d,0xbc,0xae,0x00,0x77,0xcf,0x2f,0x7d,0x7a, + 0xe5,0xe7,0x17,0x01,0x2e,0x3e,0xbd,0x5e,0x26,0xfd,0x36,0xda, + 0x8c,0xf2,0x0c,0x37,0x92,0xd9,0x57,0x09,0x19,0x3c,0xb0,0x82, + 0x06,0x40,0x30,0x11,0x41,0x65,0xa3,0x6c,0x54,0x94,0x79,0x0e, + 0xdc,0x54,0xd8,0x73,0xe4,0x05,0xec,0xcf,0x8b,0xec,0x39,0x83, + 0xf1,0x6b,0x44,0x97,0xcd,0x72,0xc5,0x3e,0xf0,0x12,0xe7,0x4c, + 0x23,0xee,0x69,0xf7,0xcb,0xe6,0x1d,0xaa,0xf6,0xad,0x39,0xb6, + 0x11,0x9b,0x2c,0x37,0x4d,0x53,0x7f,0x66,0xc0,0xdb,0xb6,0xdb, + 0xa0,0x73,0x75,0x02,0x2e,0x46,0x6a,0x58,0xe3,0x38,0xd0,0x87, + 0xea,0x3c,0x74,0xab,0xf0,0xc1,0x97,0x30,0x01,0xa4,0x96,0x5b, + 0x6f,0x11,0xb0,0x26,0x9e,0x5b,0x19,0x91,0x5e,0x0f,0xf8,0xfa, + 0x1a,0x1a,0x43,0x21,0x6d,0x98,0x79,0x12,0x41,0xf4,0x19,0xde, + 0xf4,0xa8,0x3a,0x8f,0x0a,0xc1,0xd3,0x78,0x78,0x9d,0x95,0x06, + 0x2b,0xcc,0x31,0x9c,0x5d,0x56,0x97,0x01,0xf7,0xc5,0xe9,0xed, + 0x79,0x93,0xe3,0x69,0x93,0xe1,0xa4,0x3d,0xaa,0x3c,0x6d,0xe2, + 0x87,0x4d,0xe8,0xda,0xc9,0x29,0xb2,0x05,0x00,0x4d,0x3f,0x06, + 0x17,0x27,0x90,0x05,0x55,0x36,0xda,0xe6,0x25,0x09,0xc4,0x75, + 0x63,0xff,0x12,0xde,0x07,0x5f,0x54,0x9f,0xeb,0xb2,0x77,0x48, + 0x84,0x5c,0x8d,0xbb,0x49,0x0d,0x07,0x94,0x08,0xd7,0xb3,0x60, + 0xb4,0x7b,0xcd,0xde,0x03,0xac,0xc7,0x00,0x84,0x38,0x3d,0x0e, + 0x4f,0x88,0x6e,0xb7,0xe9,0x31,0x5f,0x3c,0xd5,0xd3,0xa2,0xbb, + 0xaf,0x0e,0xb8,0xf3,0xb3,0x74,0xa9,0x78,0x13,0x03,0x1b,0x10, + 0xd8,0x40,0xb8,0x4a,0x3c,0x41,0x40,0x30,0x9d,0xbe,0x44,0xfe, + 0xa7,0x12,0x91,0x6e,0x85,0xe2,0xac,0x3d,0xd8,0xa3,0x39,0x02, + 0xd5,0x44,0xf4,0x1e,0x8c,0x3b,0xdd,0x0a,0x51,0x62,0x3c,0xec, + 0x23,0x62,0xf8,0xc2,0x83,0x61,0x47,0xe7,0x4f,0x30,0xa6,0xe2, + 0xf0,0xe4,0xff,0xe5,0xef,0xb0,0x3b,0x02,0xb8,0x40,0x73,0xec, + 0x2b,0x1a,0xee,0xab,0xf5,0xa7,0x56,0xf4,0xd9,0x27,0x88,0x41, + 0xc1,0x76,0xbb,0xdf,0x45,0x50,0x3d,0x79,0x52,0xa2,0xb1,0xba, + 0x0e,0x7b,0x58,0xae,0x2c,0xca,0x47,0x81,0x99,0x64,0x95,0xec, + 0xaf,0x87,0x5f,0xe3,0x66,0x13,0xa2,0x47,0xac,0xfa,0x2b,0xdf, + 0xbd,0xad,0x5a,0x29,0x3a,0xcc,0xf4,0xf2,0x09,0x23,0x02,0x89, + 0x68,0x86,0x45,0xb8,0x2f,0xaf,0xd7,0x61,0xcc,0x49,0x95,0xb8, + 0xd4,0x51,0x0b,0x07,0x8d,0x46,0xfe,0x9d,0x9c,0x85,0xc9,0xea, + 0xe4,0x00,0xe7,0x59,0xfd,0x15,0xda,0x3d,0xb1,0x96,0xa0,0x2e, + 0x34,0xc6,0x3f,0x71,0xfa,0x4f,0x64,0x21,0xc3,0xdd,0xdd,0x7e, + 0xa3,0x5c,0xef,0xc6,0xeb,0xfd,0xbc,0x9b,0x34,0x53,0x5e,0xe0, + 0xc4,0xf6,0xa0,0x77,0xb3,0x4d,0x2f,0x0c,0xb0,0x80,0x50,0xad, + 0x64,0xed,0xe3,0x51,0x88,0x19,0xc9,0xf9,0xdc,0x17,0x7c,0x6b, + 0xf6,0x4d,0xf3,0xbe,0x7c,0xd3,0xee,0x96,0xfc,0xdd,0x1b,0xb7, + 0xeb,0x36,0xd3,0xbf,0xe5,0xd7,0x7f,0x6f,0xff,0xe3,0xb3,0x8d, + 0x09,0x62,0xb2,0x75,0xa2,0xd1,0x4e,0xd2,0x16,0x57,0x99,0x1b, + 0x5b,0xb2,0xb1,0x2e,0x71,0x67,0xe3,0x65,0x56,0x14,0x45,0x2e, + 0x72,0x5e,0x72,0x1b,0x97,0x8e,0xff,0xcf,0x78,0x08,0x44,0x08, + 0x45,0xc8,0x23,0x79,0x35,0x04,0x22,0x8b,0x41,0x28,0xa0,0xf3, + 0x22,0xce,0x2a,0xf2,0xac,0xcc,0x62,0xd3,0x88,0x8d,0xd8,0x79, + 0x76,0x5f,0x52,0x3d,0x14,0xa1,0x50,0x10,0x36,0xe4,0x31,0x62, + 0x5d,0xc3,0x81,0x6e,0x81,0x72,0x55,0x91,0x3e,0x26,0xa1,0x1c, + 0x8c,0x50,0x74,0xb1,0x75,0xb8,0x15,0x8c,0x91,0xc8,0x1e,0x5e, + 0xbc,0x41,0xde,0x10,0x8f,0xdb,0xf0,0x72,0x7c,0x61,0x5c,0xf2, + 0x94,0x85,0x0b,0xd6,0xe2,0x37,0x07,0x72,0x3c,0xbf,0x00,0x45, + 0x31,0x9e,0x2d,0xb6,0x26,0x26,0x9b,0x8b,0x0e,0xe0,0xb9,0x66, + 0x4e,0xf8,0xef,0xc4,0x2f,0x21,0x48,0xff,0xc2,0x91,0xef,0xfe, + 0x0b,0x06,0x62,0xc2,0xe3,0x11,0x13,0x00,0x00, +}; + +const GFXglyph Oswald_Medium20pt7bGlyphs[] PROGMEM = { + { 0, 1, 1, 10, 0, 0 }, // 0x20 ' ' + { 1, 5, 32, 9, 2, -31 }, // 0x21 '!' + { 21, 11, 11, 12, 1, -31 }, // 0x22 '"' + { 37, 17, 32, 20, 2, -31 }, // 0x23 '#' + { 105, 17, 39, 19, 1, -34 }, // 0x24 '$' + { 188, 34, 32, 37, 2, -31 }, // 0x25 '%' + { 324, 19, 32, 23, 2, -31 }, // 0x26 '&' + { 400, 4, 11, 6, 1, -31 }, // 0x27 ''' + { 406, 8, 39, 13, 3, -31 }, // 0x28 '(' + { 445, 9, 39, 12, 1, -31 }, // 0x29 ')' + { 489, 13, 14, 16, 2, -31 }, // 0x2A '*' + { 512, 15, 16, 17, 1, -23 }, // 0x2B '+' + { 542, 5, 10, 8, 2, -4 }, // 0x2C ',' + { 549, 10, 3, 12, 1, -12 }, // 0x2D '-' + { 553, 5, 5, 9, 2, -4 }, // 0x2E '.' + { 557, 13, 32, 16, 1, -31 }, // 0x2F '/' + { 609, 17, 32, 21, 2, -31 }, // 0x30 '0' + { 677, 10, 32, 15, 1, -31 }, // 0x31 '1' + { 717, 16, 32, 19, 2, -31 }, // 0x32 '2' + { 781, 16, 32, 19, 2, -31 }, // 0x33 '3' + { 845, 18, 32, 20, 1, -31 }, // 0x34 '4' + { 917, 16, 32, 19, 2, -31 }, // 0x35 '5' + { 981, 17, 32, 20, 2, -31 }, // 0x36 '6' + { 1049, 14, 32, 16, 1, -31 }, // 0x37 '7' + { 1105, 16, 32, 20, 2, -31 }, // 0x38 '8' + { 1169, 16, 32, 20, 2, -31 }, // 0x39 '9' + { 1233, 6, 18, 9, 2, -20 }, // 0x3A ':' + { 1247, 6, 25, 10, 2, -21 }, // 0x3B ';' + { 1266, 11, 17, 15, 2, -24 }, // 0x3C '<' + { 1290, 13, 11, 17, 2, -21 }, // 0x3D '=' + { 1308, 12, 17, 15, 2, -24 }, // 0x3E '>' + { 1334, 15, 32, 19, 2, -31 }, // 0x3F '?' + { 1394, 33, 37, 36, 2, -31 }, // 0x40 '@' + { 1547, 19, 32, 20, 1, -31 }, // 0x41 'A' + { 1623, 18, 32, 22, 2, -31 }, // 0x42 'B' + { 1695, 18, 32, 21, 2, -31 }, // 0x43 'C' + { 1767, 18, 32, 22, 2, -31 }, // 0x44 'D' + { 1839, 14, 32, 17, 2, -31 }, // 0x45 'E' + { 1895, 13, 32, 16, 2, -31 }, // 0x46 'F' + { 1947, 18, 32, 22, 2, -31 }, // 0x47 'G' + { 2019, 18, 32, 23, 2, -31 }, // 0x48 'H' + { 2091, 5, 32, 11, 3, -31 }, // 0x49 'I' + { 2111, 10, 33, 13, 0, -31 }, // 0x4A 'J' + { 2153, 19, 32, 21, 2, -31 }, // 0x4B 'K' + { 2229, 14, 32, 16, 2, -31 }, // 0x4C 'L' + { 2285, 22, 32, 27, 2, -31 }, // 0x4D 'M' + { 2373, 17, 32, 21, 2, -31 }, // 0x4E 'N' + { 2441, 18, 32, 22, 2, -31 }, // 0x4F 'O' + { 2513, 18, 32, 21, 2, -31 }, // 0x50 'P' + { 2585, 18, 38, 22, 2, -31 }, // 0x51 'Q' + { 2671, 18, 32, 22, 2, -31 }, // 0x52 'R' + { 2743, 16, 32, 19, 2, -31 }, // 0x53 'S' + { 2807, 15, 32, 17, 1, -31 }, // 0x54 'T' + { 2867, 18, 32, 22, 2, -31 }, // 0x55 'U' + { 2939, 18, 32, 20, 1, -31 }, // 0x56 'V' + { 3011, 26, 32, 28, 1, -31 }, // 0x57 'W' + { 3115, 19, 32, 20, 0, -31 }, // 0x58 'X' + { 3191, 19, 32, 19, 0, -31 }, // 0x59 'Y' + { 3267, 15, 32, 17, 1, -31 }, // 0x5A 'Z' + { 3327, 9, 39, 13, 2, -31 }, // 0x5B '[' + { 3371, 13, 32, 16, 1, -31 }, // 0x5C '\' + { 3423, 9, 39, 12, 1, -31 }, // 0x5D ']' + { 3467, 16, 13, 18, 1, -31 }, // 0x5E '^' + { 3493, 14, 4, 14, 0, 3 }, // 0x5F '_' + { 3500, 8, 8, 11, 2, -31 }, // 0x60 '`' + { 3508, 15, 23, 17, 1, -22 }, // 0x61 'a' + { 3552, 15, 32, 19, 2, -31 }, // 0x62 'b' + { 3612, 14, 23, 17, 2, -22 }, // 0x63 'c' + { 3653, 15, 32, 19, 2, -31 }, // 0x64 'd' + { 3713, 14, 23, 17, 2, -22 }, // 0x65 'e' + { 3754, 11, 31, 12, 1, -30 }, // 0x66 'f' + { 3797, 18, 31, 18, 1, -23 }, // 0x67 'g' + { 3867, 15, 32, 19, 2, -31 }, // 0x68 'h' + { 3927, 6, 31, 10, 2, -30 }, // 0x69 'i' + { 3951, 9, 37, 10, -1, -30 }, // 0x6A 'j' + { 3993, 16, 32, 18, 2, -31 }, // 0x6B 'k' + { 4057, 6, 32, 10, 2, -31 }, // 0x6C 'l' + { 4081, 24, 23, 28, 2, -22 }, // 0x6D 'm' + { 4150, 15, 23, 19, 2, -22 }, // 0x6E 'n' + { 4194, 14, 23, 18, 2, -22 }, // 0x6F 'o' + { 4235, 15, 30, 19, 2, -22 }, // 0x70 'p' + { 4292, 15, 30, 19, 2, -22 }, // 0x71 'q' + { 4349, 11, 23, 14, 2, -22 }, // 0x72 'r' + { 4381, 14, 23, 16, 1, -22 }, // 0x73 's' + { 4422, 11, 29, 13, 1, -28 }, // 0x74 't' + { 4462, 14, 23, 19, 2, -22 }, // 0x75 'u' + { 4503, 15, 23, 16, 0, -22 }, // 0x76 'v' + { 4547, 21, 23, 23, 1, -22 }, // 0x77 'w' + { 4608, 16, 23, 16, 0, -22 }, // 0x78 'x' + { 4654, 16, 29, 16, 0, -22 }, // 0x79 'y' + { 4712, 13, 23, 15, 1, -22 }, // 0x7A 'z' + { 4750, 10, 40, 13, 2, -31 }, // 0x7B '{' + { 4800, 4, 38, 10, 3, -31 }, // 0x7C '|' + { 4819, 10, 40, 14, 2, -31 }, // 0x7D '}' + { 4869, 16, 6, 18, 1, -18 } }; // 0x7E '~' + +// const GFXfont Oswald_Medium20pt7b PROGMEM = { +// (uint8_t *)Oswald_Medium20pt7bBitmaps, +// (GFXglyph *)Oswald_Medium20pt7bGlyphs, +// 0x20, 0x7E, 58 }; + +// // Approx. 5553 bytes + + +// Font properties +static constexpr FontData Oswald_Medium20pt7b_Properties = { + Oswald_Medium20pt7bBitmaps_Gzip, + Oswald_Medium20pt7bGlyphs, + sizeof(Oswald_Medium20pt7bBitmaps_Gzip), + 5553, // Original size + 0x20, // First char + 0x7E, // Last char + 58 // yAdvance +}; diff --git a/src/fonts/oswald-medium30.h b/src/fonts/oswald-medium30.h new file mode 100644 index 0000000..3cab489 --- /dev/null +++ b/src/fonts/oswald-medium30.h @@ -0,0 +1,497 @@ +#pragma once + +#include +#include +#include "fonts.hpp" + +const uint8_t Oswald_Medium30pt7bBitmaps_Gzip[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x5a, + 0xbb, 0x72, 0x23, 0x3b, 0x92, 0x05, 0xb7, 0x8c, 0x72, 0x26, 0x1a, 0x63, + 0x5e, 0x63, 0xe2, 0x62, 0x3f, 0x61, 0xcc, 0x36, 0xee, 0x08, 0xf7, 0x53, + 0xf6, 0x13, 0xc6, 0x6c, 0x43, 0x4b, 0x94, 0x82, 0x86, 0xbc, 0xd1, 0x27, + 0xf0, 0x53, 0x04, 0x86, 0x0c, 0x99, 0xfc, 0x83, 0x65, 0x31, 0x68, 0xd0, + 0x14, 0x14, 0x32, 0x08, 0x86, 0x4a, 0xc8, 0x3d, 0x27, 0xc1, 0xa7, 0xd4, + 0x52, 0xf7, 0xbd, 0xdd, 0x3b, 0xb1, 0x25, 0x50, 0x55, 0x85, 0x42, 0xe1, + 0x91, 0x48, 0x9c, 0x3c, 0x99, 0x28, 0x23, 0x3c, 0x0a, 0x8f, 0xf1, 0xee, + 0xb8, 0xac, 0xc7, 0x6f, 0x7a, 0x7c, 0xd6, 0xc3, 0x18, 0x23, 0xbb, 0xa3, + 0x78, 0x71, 0x72, 0xb3, 0xb8, 0x7f, 0x58, 0x6d, 0x96, 0xcf, 0xe9, 0x25, + 0x8f, 0x87, 0x8b, 0xb1, 0xbb, 0xb0, 0xd3, 0x76, 0x3e, 0x59, 0xcf, 0x56, + 0xfd, 0x63, 0xff, 0x25, 0xfd, 0x66, 0x46, 0x83, 0xeb, 0x42, 0xdb, 0xbb, + 0x49, 0x6e, 0xfb, 0x61, 0x94, 0xbd, 0x19, 0xdb, 0xe8, 0x26, 0x09, 0x37, + 0x0d, 0x6e, 0x8a, 0x8b, 0xbe, 0x4d, 0x76, 0x96, 0x9b, 0x34, 0x36, 0x28, + 0xe9, 0xdb, 0x25, 0x2a, 0x0e, 0x22, 0x0b, 0x91, 0x8d, 0xc8, 0x0b, 0x6e, + 0x76, 0xaf, 0x15, 0x1b, 0xfd, 0x35, 0x4a, 0x0e, 0x28, 0x69, 0xf7, 0x3d, + 0x90, 0x5b, 0x91, 0x07, 0x91, 0x67, 0xf4, 0xa5, 0xd6, 0x79, 0x6c, 0xc0, + 0xb2, 0x81, 0x80, 0x3a, 0xd1, 0x40, 0x7f, 0xda, 0x40, 0xef, 0xee, 0x72, + 0x9b, 0x0a, 0x4b, 0x9a, 0xbf, 0x19, 0xf3, 0xc9, 0x98, 0xd6, 0x98, 0xa6, + 0x33, 0xa3, 0x68, 0xda, 0xc1, 0x38, 0xe9, 0x9c, 0xf4, 0x4e, 0xb2, 0x43, + 0x95, 0x22, 0x76, 0x90, 0x79, 0x76, 0xeb, 0xdc, 0x3e, 0x0d, 0xcd, 0x4b, + 0x19, 0x15, 0x31, 0x12, 0xba, 0x70, 0x1b, 0xff, 0xe6, 0x92, 0xb1, 0x83, + 0x69, 0x8a, 0x19, 0x49, 0x67, 0xa4, 0x37, 0x7e, 0x30, 0x56, 0x4c, 0x23, + 0xd1, 0x48, 0x7a, 0x73, 0xd3, 0xd6, 0x62, 0x01, 0xc5, 0xf0, 0x5a, 0xfe, + 0xd2, 0xc8, 0x30, 0x12, 0x54, 0x25, 0x5d, 0x90, 0x58, 0x9b, 0xb3, 0xcf, + 0x43, 0xbb, 0x95, 0xeb, 0x21, 0x50, 0xaa, 0xc5, 0x4a, 0x69, 0xa4, 0x18, + 0x24, 0x37, 0x18, 0x93, 0x8c, 0xf9, 0xfc, 0xaa, 0xaf, 0x68, 0x1a, 0xb9, + 0xa8, 0xfd, 0xd2, 0x98, 0x20, 0xc6, 0x76, 0xe8, 0x78, 0x42, 0x16, 0xdf, + 0xba, 0xe4, 0x0c, 0xf5, 0xcc, 0xa2, 0xb0, 0x0c, 0xc4, 0xdc, 0x21, 0x6b, + 0xdc, 0x64, 0x66, 0xc5, 0x30, 0x62, 0x96, 0xed, 0xc7, 0x78, 0x71, 0x34, + 0x20, 0xcb, 0xf6, 0xfe, 0x2a, 0xa1, 0xc6, 0x16, 0x02, 0x32, 0xbe, 0x2b, + 0xc8, 0x6a, 0x93, 0x9b, 0xa1, 0xfa, 0xd2, 0x64, 0x64, 0xd9, 0x18, 0x6c, + 0xc4, 0x73, 0xcb, 0x2c, 0x3f, 0xc9, 0x17, 0xe8, 0x44, 0x66, 0x16, 0x1a, + 0x5e, 0x22, 0xcb, 0x4a, 0xbe, 0x68, 0x30, 0x18, 0x4c, 0x10, 0x86, 0x21, + 0xb3, 0x1c, 0xa4, 0x93, 0xfe, 0x02, 0xd9, 0xa3, 0xd8, 0xde, 0x61, 0x98, + 0x66, 0x94, 0xc3, 0x0d, 0x0a, 0x5e, 0xd8, 0xbe, 0xe0, 0xdd, 0xbb, 0xec, + 0x22, 0xb3, 0x9a, 0xac, 0x59, 0x01, 0x59, 0xb3, 0xa1, 0xed, 0x91, 0xe5, + 0x3b, 0x0c, 0x16, 0xea, 0xe0, 0xd0, 0x83, 0x58, 0x38, 0x40, 0x14, 0x65, + 0xa7, 0xd0, 0x5f, 0x4c, 0x4e, 0xd4, 0x57, 0xb2, 0xbe, 0xe2, 0xd1, 0x9f, + 0xde, 0x60, 0x2e, 0x67, 0x78, 0x25, 0x37, 0x03, 0xb2, 0x1c, 0xfa, 0xc3, + 0xac, 0x56, 0x98, 0x45, 0xa9, 0x20, 0xab, 0xc5, 0x1c, 0xa0, 0x6e, 0xdf, + 0x1b, 0xde, 0x72, 0x1a, 0x32, 0x24, 0xd5, 0x43, 0xfa, 0x9d, 0x45, 0x39, + 0xb1, 0xa9, 0x4d, 0xbe, 0xf3, 0x57, 0x03, 0xeb, 0x63, 0xcd, 0x68, 0xa3, + 0x41, 0xab, 0x2e, 0x16, 0x68, 0x5c, 0x4f, 0x0d, 0x72, 0xf7, 0xc9, 0x14, + 0x6f, 0x9a, 0xe7, 0x6c, 0xec, 0x02, 0x93, 0x98, 0x75, 0x2e, 0x6d, 0xa6, + 0xb0, 0x4c, 0xc3, 0x1e, 0x27, 0x37, 0x91, 0x6e, 0xf0, 0xa5, 0x5d, 0x49, + 0xf2, 0x6e, 0x31, 0xdb, 0x96, 0xb1, 0x9b, 0xaf, 0x72, 0x09, 0x76, 0xbd, + 0x4e, 0xc1, 0xfb, 0x2b, 0xd9, 0x0e, 0xad, 0xdc, 0xf4, 0x1e, 0xdd, 0x94, + 0x55, 0x6e, 0xa1, 0xf4, 0xae, 0x2f, 0x23, 0xb9, 0x86, 0x92, 0xb9, 0xa5, + 0x14, 0xe8, 0xf1, 0x13, 0x7e, 0x37, 0xf2, 0x34, 0x78, 0xb1, 0x3d, 0xf4, + 0xc1, 0xfc, 0xd3, 0x7c, 0xa6, 0x7a, 0x2f, 0x6e, 0xc7, 0x2f, 0xcf, 0xeb, + 0xf9, 0xf4, 0xe2, 0x32, 0x99, 0x2f, 0x36, 0x16, 0x9b, 0xe4, 0x66, 0x08, + 0x6d, 0x46, 0x87, 0x51, 0x03, 0xba, 0xce, 0xb3, 0xe0, 0x7e, 0x86, 0x47, + 0x61, 0x72, 0xc8, 0xc4, 0x48, 0x38, 0x0a, 0xbd, 0xd9, 0x3f, 0xa9, 0xc5, + 0xf6, 0x8f, 0xeb, 0xbf, 0x62, 0x07, 0xdf, 0x0b, 0xc5, 0xfb, 0x2b, 0x56, + 0x0f, 0xcb, 0x66, 0x1f, 0x71, 0xef, 0xf4, 0x7d, 0x3e, 0xd9, 0x55, 0x32, + 0x9c, 0x55, 0xf2, 0xb5, 0x9b, 0x93, 0x86, 0xfd, 0x8c, 0x67, 0x3c, 0x9c, + 0x0c, 0xe1, 0xae, 0xf0, 0x51, 0x0f, 0xfd, 0x1d, 0x74, 0x22, 0x0c, 0xd4, + 0xcf, 0x76, 0xbf, 0x5c, 0x7e, 0x7e, 0x5a, 0xcd, 0x65, 0x1c, 0x38, 0xc8, + 0x7b, 0x49, 0x58, 0x20, 0x0e, 0x93, 0x42, 0x6d, 0x7f, 0x4e, 0x76, 0x1e, + 0x87, 0x71, 0xbb, 0xcc, 0x9f, 0xac, 0xf9, 0xfb, 0x7f, 0x72, 0x2a, 0x9b, + 0x9e, 0x0a, 0x7e, 0x71, 0x7e, 0xd9, 0xc8, 0xe9, 0x91, 0xde, 0x16, 0x38, + 0x5c, 0xee, 0xcb, 0xb4, 0xad, 0x75, 0x17, 0x97, 0x5f, 0xfe, 0x7e, 0xf2, + 0xd6, 0xfe, 0x82, 0x65, 0x2f, 0x8d, 0xeb, 0x4e, 0x4f, 0xb6, 0x6b, 0xd2, + 0xab, 0xd3, 0x18, 0xa7, 0xd1, 0xe9, 0x29, 0xe2, 0x74, 0xf1, 0xea, 0xd4, + 0xc6, 0x51, 0x3e, 0x3d, 0xa1, 0x27, 0xfe, 0xec, 0xe4, 0x28, 0x08, 0x23, + 0x1d, 0x74, 0xb1, 0xe1, 0x3a, 0x97, 0x2b, 0x91, 0xa5, 0x76, 0x66, 0x90, + 0x49, 0x09, 0x51, 0x7c, 0x0c, 0x0b, 0x60, 0x42, 0xc2, 0x12, 0x82, 0x32, + 0xa3, 0xcc, 0x11, 0x2c, 0xbc, 0xfc, 0xd0, 0x83, 0x97, 0xd2, 0x14, 0x19, + 0x49, 0x98, 0x89, 0x7f, 0x84, 0xbe, 0xc9, 0xb5, 0xc8, 0x4c, 0x24, 0x2a, + 0x0a, 0x45, 0xe2, 0x15, 0x30, 0xd1, 0x8c, 0xb1, 0x16, 0x72, 0x78, 0x3a, + 0x91, 0xee, 0x3d, 0xa6, 0xdf, 0xf5, 0xe8, 0x56, 0x27, 0xa3, 0xd2, 0x0c, + 0x6d, 0xfe, 0x73, 0xb7, 0x26, 0x44, 0x8c, 0x19, 0x5d, 0x1b, 0x69, 0xb3, + 0x18, 0x73, 0xc6, 0x98, 0xd1, 0x19, 0x8c, 0xf9, 0x36, 0x7a, 0x8c, 0xf9, + 0x21, 0xd9, 0x4d, 0x6e, 0x9f, 0x0f, 0x58, 0x4b, 0x31, 0xe1, 0x2d, 0xe3, + 0x23, 0x97, 0x30, 0x12, 0x60, 0x97, 0xc8, 0x4b, 0x14, 0x65, 0x6a, 0x33, + 0x31, 0xbb, 0x55, 0x20, 0xae, 0x09, 0x4a, 0x54, 0x13, 0xdf, 0xac, 0x09, + 0xef, 0xef, 0xaa, 0x50, 0x7b, 0xb1, 0x50, 0x93, 0xb1, 0x51, 0xab, 0x01, + 0xfb, 0x02, 0x94, 0x8d, 0x87, 0x99, 0x98, 0x68, 0xaf, 0xb6, 0x58, 0x64, + 0x72, 0x55, 0xd0, 0xe3, 0xdb, 0x2a, 0xbe, 0xde, 0x42, 0x45, 0x59, 0x66, + 0x18, 0x01, 0xc1, 0x50, 0x77, 0xe8, 0x34, 0xb1, 0xe2, 0x86, 0xaf, 0x03, + 0xe8, 0xa3, 0xc2, 0x3b, 0x0d, 0x08, 0x40, 0xb8, 0x63, 0x5f, 0xd1, 0x4b, + 0xf4, 0x6c, 0xff, 0x86, 0x74, 0x5e, 0xb1, 0x5e, 0xab, 0xda, 0x0d, 0x70, + 0xc4, 0x91, 0xfb, 0xc4, 0x99, 0xb8, 0xdf, 0xcf, 0x04, 0x9a, 0x8b, 0x44, + 0x2d, 0xa8, 0x30, 0x2d, 0x8c, 0xda, 0x0d, 0x0c, 0x97, 0x23, 0x8b, 0x04, + 0x1f, 0x35, 0x2c, 0xbd, 0x09, 0x6a, 0xaa, 0x1a, 0x58, 0x80, 0xff, 0xc6, + 0xda, 0xb9, 0x8d, 0xcd, 0x26, 0x9b, 0x01, 0x0b, 0x68, 0xda, 0xb7, 0xab, + 0xc1, 0x0c, 0xa1, 0x73, 0x37, 0xa9, 0x5d, 0x16, 0x33, 0x78, 0x98, 0x4d, + 0x98, 0x5e, 0x81, 0x1d, 0xee, 0xb1, 0x20, 0x5b, 0x5d, 0xfa, 0x0a, 0x0b, + 0x3d, 0xd6, 0x29, 0x16, 0xa9, 0xbc, 0x3d, 0x12, 0xa5, 0x05, 0xd9, 0x2a, + 0x68, 0xf6, 0x0a, 0x72, 0x18, 0x2e, 0xc6, 0xc3, 0xc1, 0x9c, 0x3e, 0x0a, + 0x98, 0xbe, 0xaf, 0x26, 0xbc, 0x00, 0x45, 0x3a, 0x4f, 0xa5, 0x26, 0x1f, + 0x0b, 0x75, 0x9d, 0x53, 0xaf, 0xa9, 0x40, 0xe9, 0x45, 0x67, 0xbb, 0xe8, + 0x84, 0xbf, 0x4d, 0xf2, 0x3a, 0x69, 0x79, 0x4c, 0x90, 0xb6, 0xe5, 0x69, + 0xab, 0xa1, 0xe0, 0x3d, 0xa5, 0x46, 0xdb, 0x57, 0x95, 0x8c, 0x4b, 0x60, + 0xbf, 0xb0, 0xf6, 0xd3, 0x09, 0x2d, 0x9c, 0xf6, 0x6e, 0x5e, 0x57, 0x43, + 0xe6, 0xb8, 0x8e, 0xf3, 0x89, 0x61, 0xb9, 0xd5, 0x60, 0x37, 0x72, 0xfd, + 0x2c, 0x2b, 0xea, 0x00, 0xfb, 0x26, 0xa1, 0x97, 0xdb, 0xde, 0xeb, 0x6a, + 0xdc, 0x29, 0x00, 0x7b, 0x29, 0xe7, 0xd3, 0x59, 0xf5, 0x75, 0x40, 0xa7, + 0x30, 0x9d, 0x80, 0xc5, 0x1b, 0x55, 0xa4, 0xd3, 0x85, 0xa5, 0x88, 0xf4, + 0x56, 0xce, 0xb4, 0x4a, 0x6d, 0x42, 0xeb, 0xe8, 0x89, 0x7b, 0x75, 0x53, + 0x28, 0xef, 0x7a, 0x13, 0x47, 0x5f, 0xbd, 0x01, 0xa0, 0x1c, 0x6e, 0xda, + 0x93, 0x1b, 0x73, 0x72, 0x23, 0x87, 0x1b, 0x2c, 0x89, 0x56, 0xbb, 0x0b, + 0xb2, 0xe1, 0x29, 0x35, 0x62, 0x33, 0x2c, 0x00, 0x12, 0x05, 0x7a, 0x2e, + 0xdf, 0x80, 0x34, 0xd2, 0xa7, 0x93, 0xc1, 0xaf, 0x07, 0xf2, 0x13, 0x0a, + 0xe0, 0xfc, 0xf5, 0x59, 0x7d, 0xf7, 0xf8, 0x62, 0xd9, 0xa7, 0xb3, 0xda, + 0xae, 0x84, 0xf5, 0x90, 0xd6, 0xf0, 0xad, 0x5a, 0x55, 0xb3, 0x9b, 0x2a, + 0x60, 0x60, 0x3a, 0x4c, 0x55, 0xcf, 0xa9, 0x02, 0x03, 0xda, 0x4d, 0xd5, + 0x11, 0x0f, 0x5e, 0x23, 0xda, 0x11, 0x28, 0x4a, 0xf3, 0x22, 0x4d, 0x09, + 0x6b, 0x81, 0x16, 0x4f, 0x75, 0x11, 0x71, 0xbe, 0x03, 0xf8, 0xda, 0xdd, + 0xeb, 0xb9, 0xc5, 0x7a, 0x3c, 0x81, 0x16, 0xac, 0xbc, 0x6e, 0x37, 0x55, + 0x6f, 0x30, 0xf0, 0x38, 0x55, 0xe6, 0x70, 0xec, 0x18, 0x67, 0x2a, 0xbb, + 0xc3, 0x9c, 0x1f, 0x7b, 0x42, 0xfc, 0xe9, 0xd3, 0xaf, 0x17, 0xc3, 0x97, + 0x7f, 0x1a, 0xf3, 0x3b, 0x2c, 0xf8, 0x25, 0xa6, 0x22, 0xc4, 0x90, 0x3c, + 0xc6, 0xed, 0x8b, 0x2f, 0x16, 0xe0, 0x0b, 0x2b, 0xe8, 0x23, 0x2e, 0xa4, + 0x0f, 0x05, 0xf2, 0x0c, 0xe0, 0x05, 0x28, 0xd3, 0xd2, 0xc2, 0x80, 0xd9, + 0xfd, 0x7e, 0xa2, 0x1b, 0xa7, 0x4d, 0x9c, 0xdb, 0x3a, 0x68, 0xd3, 0x67, + 0xda, 0xa3, 0x0c, 0xf9, 0x79, 0x48, 0xf3, 0x8a, 0x0b, 0xe0, 0x0a, 0x33, + 0x03, 0x3e, 0x03, 0x23, 0x64, 0x93, 0x85, 0x94, 0x1d, 0xe4, 0x71, 0x03, + 0x35, 0xec, 0x50, 0xa6, 0x43, 0xe1, 0xce, 0x80, 0xb0, 0x01, 0x4c, 0x4e, + 0x27, 0x11, 0x93, 0xc2, 0x34, 0x91, 0x30, 0x3a, 0x9b, 0x32, 0x88, 0xe6, + 0xcb, 0x61, 0xf9, 0x51, 0x75, 0x0a, 0x97, 0x0a, 0x52, 0xab, 0xa0, 0x0b, + 0x00, 0x06, 0xba, 0x01, 0xfc, 0x38, 0x7d, 0x0a, 0xbb, 0xc3, 0x57, 0xd2, + 0xe9, 0xf1, 0xd5, 0x02, 0xb5, 0xcc, 0xb8, 0x8e, 0x8f, 0xd7, 0x2d, 0x66, + 0x41, 0x25, 0x3d, 0xe8, 0x35, 0x99, 0x1b, 0xfe, 0x67, 0xc2, 0x69, 0x2b, + 0x0a, 0x84, 0xa4, 0x71, 0xe0, 0x5c, 0x99, 0x8d, 0x43, 0xb8, 0x9e, 0xc5, + 0xc1, 0xda, 0xc8, 0x30, 0x21, 0x42, 0xa8, 0x7e, 0x03, 0x80, 0x06, 0xfd, + 0x87, 0x0a, 0x2e, 0x26, 0x29, 0x90, 0x10, 0x8e, 0xc1, 0xff, 0x21, 0x9f, + 0x6b, 0xac, 0x60, 0xf0, 0xb3, 0x0c, 0xbe, 0x73, 0x95, 0x87, 0x66, 0xb0, + 0xf1, 0xc2, 0xc3, 0x3a, 0x25, 0x3b, 0x8d, 0xc0, 0xc3, 0x66, 0x9d, 0x40, + 0x8c, 0xcd, 0x16, 0xdc, 0xd2, 0xc5, 0x0b, 0x30, 0x65, 0xb8, 0x24, 0xd3, + 0x0e, 0x4b, 0xba, 0x59, 0x83, 0x12, 0x8e, 0xf1, 0x00, 0x8c, 0xad, 0x1b, + 0x83, 0xe2, 0xa0, 0xc0, 0x2d, 0xca, 0xe7, 0xf6, 0x01, 0x0f, 0xca, 0xe8, + 0x09, 0x0f, 0xc0, 0x5a, 0xf5, 0x01, 0x78, 0x1e, 0xe8, 0xb6, 0xbb, 0x21, + 0xc0, 0x82, 0xbd, 0x80, 0xaa, 0xc1, 0xe5, 0xc0, 0xcc, 0xcd, 0xb2, 0xeb, + 0x80, 0x27, 0x28, 0xe3, 0xc0, 0xf4, 0x50, 0x27, 0x66, 0xa6, 0x43, 0xdf, + 0xee, 0x40, 0x41, 0x7b, 0x90, 0x5a, 0x4e, 0x24, 0x8f, 0xa0, 0xff, 0xa1, + 0xa7, 0x14, 0x48, 0xcd, 0xc2, 0x60, 0x99, 0x95, 0xaa, 0x8c, 0x6a, 0xa9, + 0x6c, 0x7e, 0xad, 0xea, 0x08, 0x2f, 0x40, 0x21, 0x04, 0x76, 0x00, 0x42, + 0xd2, 0x1b, 0x55, 0x54, 0x55, 0x1c, 0x50, 0x6c, 0x43, 0x71, 0xf1, 0x2d, + 0x47, 0xb4, 0x06, 0x33, 0xc6, 0xa5, 0xcf, 0xea, 0x95, 0x38, 0xae, 0x8c, + 0xac, 0xe4, 0xd8, 0x63, 0xfa, 0x80, 0xf7, 0x0e, 0xcc, 0x16, 0x5a, 0xdd, + 0x6e, 0x30, 0xb7, 0xb7, 0x5d, 0xf3, 0x02, 0xa0, 0x99, 0x03, 0x83, 0x7c, + 0xd7, 0x3e, 0x42, 0x63, 0x6f, 0x7a, 0x32, 0x6c, 0xb7, 0xca, 0xf0, 0xd7, + 0xfa, 0x36, 0x09, 0xdd, 0x80, 0x66, 0x50, 0x16, 0x6b, 0x40, 0x02, 0xc1, + 0xfc, 0x22, 0x0c, 0x0a, 0xc6, 0x0a, 0xc5, 0x82, 0xc6, 0x41, 0xd2, 0xb8, + 0xf4, 0x8a, 0x82, 0xb0, 0x3a, 0x60, 0xb4, 0x26, 0x5c, 0xe3, 0x05, 0x1f, + 0xdd, 0x0a, 0x2f, 0xa0, 0x8a, 0x0c, 0x4b, 0xb5, 0x86, 0xfe, 0xfa, 0x88, + 0x35, 0x6c, 0xb0, 0xa0, 0x47, 0x9c, 0x75, 0xc7, 0x85, 0x8e, 0x45, 0x41, + 0xff, 0x87, 0x0b, 0xb9, 0xf8, 0x7b, 0xb9, 0xcf, 0x2d, 0xcc, 0x1a, 0x60, + 0xdb, 0x3e, 0x61, 0x6e, 0x61, 0xd6, 0xe6, 0xa9, 0xd9, 0x16, 0x13, 0xb0, + 0x8a, 0xd6, 0xa8, 0xb2, 0x3e, 0x5a, 0x61, 0x46, 0xc3, 0x95, 0xd4, 0xb7, + 0xd0, 0x30, 0x20, 0x83, 0x68, 0x42, 0x07, 0x6e, 0x35, 0xb4, 0x78, 0x2b, + 0xcc, 0x53, 0xfb, 0x82, 0xb7, 0x6e, 0x23, 0x40, 0x80, 0xca, 0xee, 0x45, + 0xfd, 0x04, 0xa1, 0xb7, 0x97, 0xe8, 0x34, 0xd5, 0x47, 0x0d, 0xf1, 0x6c, + 0xd1, 0x7b, 0x90, 0x1f, 0x2c, 0x0d, 0xa2, 0x03, 0x49, 0x8a, 0xe7, 0x80, + 0x8a, 0x0a, 0x51, 0xd1, 0x0d, 0x58, 0x41, 0xb3, 0x89, 0xe6, 0xd6, 0xc5, + 0x6d, 0x09, 0xfb, 0x80, 0x25, 0x2c, 0xab, 0xac, 0x3e, 0x1b, 0x2d, 0xc3, + 0x9b, 0x1b, 0x2e, 0x13, 0xb3, 0xa3, 0x05, 0x1f, 0xdc, 0xbc, 0x5f, 0x01, + 0x61, 0x0b, 0x6c, 0x21, 0x4c, 0xb3, 0x5f, 0x55, 0xe3, 0x89, 0xc1, 0x35, + 0x15, 0x10, 0x39, 0x8f, 0x0e, 0x95, 0x60, 0xa6, 0x88, 0xae, 0x14, 0x7b, + 0x52, 0xef, 0x5c, 0xe6, 0xf0, 0x2c, 0x20, 0x00, 0x0c, 0xea, 0x36, 0x02, + 0x33, 0xdb, 0xea, 0x77, 0x2a, 0xd4, 0xfd, 0xec, 0x1b, 0x20, 0x6b, 0x6d, + 0x47, 0x1b, 0xad, 0x5d, 0x54, 0x4f, 0x56, 0x1a, 0x15, 0xf4, 0x5b, 0x8e, + 0x21, 0xd5, 0x40, 0xbd, 0xf7, 0x0f, 0xd2, 0xcf, 0x27, 0xff, 0x3e, 0x2e, + 0x7c, 0xf2, 0xda, 0x47, 0x47, 0x04, 0x82, 0x41, 0xf5, 0xa0, 0xce, 0x30, + 0x0b, 0x36, 0x01, 0xd8, 0xfc, 0x79, 0x16, 0xed, 0xbe, 0x97, 0x35, 0x6d, + 0xff, 0x14, 0xa2, 0x7b, 0xa7, 0xd4, 0x47, 0x59, 0xd4, 0x92, 0x20, 0x27, + 0x33, 0x01, 0x3d, 0x72, 0x83, 0x5f, 0x01, 0xfc, 0x43, 0xe7, 0xa1, 0x87, + 0x50, 0x78, 0x4c, 0xa4, 0x7b, 0xa0, 0x07, 0x63, 0x54, 0x6f, 0x28, 0xc6, + 0x8e, 0xcb, 0x9c, 0xaa, 0x00, 0x68, 0xb3, 0x59, 0x09, 0x2c, 0x34, 0x02, + 0xfc, 0xd1, 0xec, 0x2a, 0xa1, 0x24, 0x67, 0xaa, 0x8c, 0x5f, 0xd3, 0x5e, + 0x89, 0x56, 0xf6, 0x8a, 0x6d, 0x9f, 0x07, 0xaa, 0x8a, 0x7f, 0x18, 0x2c, + 0x29, 0xcc, 0x54, 0x9e, 0x1f, 0x45, 0xc6, 0x4e, 0xe6, 0x33, 0x79, 0xcc, + 0xe8, 0x8a, 0xf9, 0x8d, 0xb8, 0xfe, 0x71, 0x1d, 0xdf, 0xf3, 0xe8, 0xbd, + 0xe3, 0x4f, 0x57, 0x78, 0xfe, 0xe8, 0x0f, 0x1e, 0x4d, 0x69, 0xe1, 0xb6, + 0xb8, 0xe4, 0x7b, 0x90, 0xb0, 0xab, 0x1f, 0xb8, 0x1d, 0xe9, 0x8d, 0xdf, + 0xc0, 0x4b, 0x98, 0x0a, 0x5c, 0xf6, 0x04, 0x44, 0x23, 0x44, 0x51, 0xac, + 0x15, 0x4a, 0xec, 0x23, 0x26, 0x13, 0x5c, 0x12, 0xfe, 0x3b, 0x40, 0x1e, + 0x90, 0x48, 0x07, 0x98, 0x5e, 0x30, 0x30, 0xff, 0x0a, 0xb7, 0xfe, 0x1e, + 0xbc, 0x1a, 0x6f, 0xf9, 0xde, 0xad, 0x07, 0xc5, 0xa8, 0xdb, 0xfe, 0x14, + 0x77, 0x14, 0x43, 0x1c, 0x17, 0x38, 0x96, 0x2f, 0x98, 0xcc, 0x16, 0x4b, + 0xdb, 0xf7, 0xfe, 0x0e, 0x6f, 0xa1, 0xfd, 0x56, 0x1d, 0x6f, 0x56, 0x48, + 0xd7, 0xdd, 0x2f, 0xd1, 0x1d, 0x6d, 0xcb, 0x66, 0xb0, 0x16, 0xd7, 0x83, + 0xa4, 0xee, 0x35, 0x09, 0x88, 0x26, 0x07, 0x44, 0xfb, 0xce, 0xe5, 0xf1, + 0xc3, 0xff, 0xce, 0x0f, 0xb2, 0x80, 0x29, 0x18, 0xe6, 0x1a, 0x6c, 0x73, + 0x0b, 0x3b, 0x5d, 0x02, 0xa0, 0x1c, 0x19, 0xa0, 0x64, 0x06, 0xd0, 0x18, + 0x91, 0x81, 0x59, 0x9d, 0x62, 0x82, 0x91, 0xe1, 0xb7, 0x50, 0xce, 0x12, + 0x80, 0xce, 0x53, 0x8c, 0x78, 0x8d, 0xc1, 0x23, 0xc3, 0x8d, 0x41, 0x82, + 0x16, 0x53, 0x69, 0x36, 0x6b, 0x89, 0x2f, 0xdb, 0x97, 0xe4, 0xc7, 0xe1, + 0xd2, 0x4e, 0xa7, 0x8b, 0xc9, 0x7a, 0xbd, 0x59, 0x6e, 0xb7, 0xdb, 0x3c, + 0x1e, 0x8f, 0x2f, 0xdc, 0x74, 0x7a, 0x73, 0x7d, 0xbf, 0x5e, 0x3d, 0x3d, + 0x6e, 0xf3, 0x30, 0x8c, 0xc7, 0xde, 0xbb, 0xe9, 0xf5, 0xfc, 0x7e, 0xbd, + 0x7c, 0x5a, 0x6e, 0x73, 0x40, 0x11, 0x57, 0x90, 0x21, 0xd7, 0xeb, 0x5e, + 0x90, 0xe1, 0xd3, 0x78, 0x0c, 0x12, 0x34, 0x9d, 0x48, 0x0b, 0x03, 0x3e, + 0xdb, 0x32, 0x9e, 0x31, 0x86, 0x5c, 0x17, 0x57, 0xa5, 0xdd, 0xc0, 0x3e, + 0xbf, 0x30, 0x88, 0x34, 0x86, 0xb1, 0x96, 0x0e, 0x04, 0xac, 0xf7, 0x70, + 0x18, 0x6c, 0x84, 0x47, 0xc5, 0x54, 0xf6, 0x49, 0x6a, 0xea, 0x34, 0xc5, + 0x7d, 0xea, 0x35, 0xa5, 0x7d, 0xca, 0x9a, 0xd4, 0xe5, 0x29, 0x45, 0xd3, + 0x38, 0x94, 0xa0, 0xc9, 0x6b, 0x24, 0xcf, 0xed, 0x93, 0xd5, 0xd4, 0x6a, + 0x6a, 0xf6, 0x49, 0xb9, 0x6f, 0x39, 0x32, 0xe7, 0xda, 0xb4, 0xaf, 0xe9, + 0x35, 0x92, 0x64, 0x45, 0x92, 0xf0, 0x54, 0x91, 0x64, 0x91, 0xce, 0x14, + 0xe9, 0x04, 0x49, 0xbc, 0xfc, 0xb4, 0x47, 0xaa, 0xf2, 0x31, 0xcc, 0x07, + 0x97, 0xab, 0x39, 0x9d, 0xa9, 0x8d, 0x57, 0xdc, 0xb7, 0xc4, 0x2c, 0xf5, + 0xce, 0x40, 0x25, 0xdf, 0x5a, 0x1d, 0x5a, 0x83, 0x50, 0xad, 0xce, 0xf3, + 0x50, 0x55, 0x3f, 0x2a, 0xf3, 0x3f, 0x18, 0x8d, 0x87, 0x9d, 0xd1, 0x68, + 0xe8, 0x95, 0xe3, 0x9d, 0x6a, 0xd7, 0xb2, 0xd6, 0x96, 0xc9, 0x0e, 0x45, + 0x9d, 0x4d, 0x28, 0xdd, 0x9f, 0xbd, 0xf9, 0xff, 0x21, 0x41, 0x79, 0x5f, + 0x82, 0x51, 0xe3, 0xbb, 0xe8, 0x70, 0xb3, 0x3b, 0x31, 0xaa, 0x68, 0x18, + 0x8d, 0x62, 0xc4, 0xb7, 0x31, 0xa7, 0xa4, 0x48, 0xf6, 0xa4, 0x68, 0x26, + 0xeb, 0x03, 0x29, 0xd2, 0xea, 0x3b, 0x88, 0xb2, 0x39, 0xd2, 0x1b, 0x0a, + 0x7e, 0xb1, 0xe7, 0x4b, 0x80, 0x2b, 0xac, 0x32, 0x9d, 0x96, 0xeb, 0xea, + 0x62, 0x41, 0x17, 0x41, 0xc5, 0x7a, 0x85, 0xab, 0x0a, 0x31, 0xb3, 0x03, + 0xe6, 0xf4, 0xe1, 0x8e, 0xb1, 0x9f, 0x4e, 0x6c, 0x72, 0x4b, 0x05, 0x39, + 0x0f, 0x02, 0x06, 0xe3, 0x2b, 0xf0, 0x9b, 0x6b, 0x85, 0xaf, 0x80, 0x47, + 0x63, 0x2c, 0xb9, 0x7a, 0x66, 0xa9, 0x2a, 0xc2, 0x75, 0xf1, 0x4b, 0x54, + 0xa6, 0x0e, 0x96, 0x9b, 0xe7, 0xf6, 0x89, 0x0e, 0x57, 0x87, 0xde, 0xfe, + 0xda, 0x56, 0xc3, 0x16, 0x7a, 0x8d, 0x9d, 0xd7, 0xc8, 0x86, 0x2b, 0xfb, + 0x78, 0x06, 0x15, 0x4a, 0x76, 0x19, 0xf1, 0x24, 0xe8, 0xe1, 0x13, 0x03, + 0xe8, 0xb0, 0x83, 0x30, 0x69, 0xaa, 0x4c, 0x70, 0x4f, 0x19, 0x20, 0x83, + 0x14, 0x80, 0x9c, 0xf4, 0xb9, 0x6f, 0x72, 0x58, 0xa9, 0x91, 0x1c, 0x9c, + 0x86, 0x71, 0x30, 0xe3, 0x70, 0x19, 0x76, 0xea, 0xf9, 0x95, 0xe3, 0x10, + 0x24, 0xfa, 0xf7, 0xa7, 0x9f, 0x65, 0x1d, 0xff, 0xc4, 0xa3, 0x66, 0xa7, + 0x18, 0xdf, 0x5c, 0xd4, 0x1d, 0xa1, 0x1e, 0x9e, 0x80, 0x04, 0xfa, 0xcc, + 0x86, 0xac, 0x7b, 0x0d, 0xb5, 0xf2, 0x1d, 0x3c, 0x00, 0x9c, 0xa2, 0xbb, + 0x1b, 0x46, 0x8c, 0x2b, 0xcd, 0x06, 0x3a, 0x01, 0x3c, 0x41, 0x7d, 0xe8, + 0x00, 0x10, 0x56, 0xbb, 0xd2, 0x6a, 0x88, 0x5e, 0xe0, 0x13, 0xd1, 0x05, + 0x63, 0x84, 0x1d, 0xed, 0xc2, 0xd5, 0x49, 0xe8, 0x08, 0x3c, 0x89, 0x0c, + 0x68, 0x9b, 0xf6, 0xcd, 0x16, 0x6c, 0x75, 0x1a, 0xd5, 0xd3, 0x58, 0xc4, + 0xd1, 0x8b, 0x86, 0xb3, 0x6a, 0x74, 0x5e, 0x15, 0x6a, 0xa7, 0x0c, 0x56, + 0x74, 0x97, 0x83, 0xbe, 0x4b, 0xa8, 0x6a, 0x00, 0xe5, 0xe1, 0x89, 0xbe, + 0x4d, 0x61, 0x3c, 0x89, 0xb1, 0x3b, 0x0d, 0x4d, 0xd0, 0x7d, 0x47, 0xb3, + 0x0c, 0x3a, 0x6e, 0x19, 0x75, 0x5c, 0x33, 0xec, 0x08, 0xf7, 0xae, 0x63, + 0x28, 0x63, 0x9f, 0xe5, 0x13, 0xda, 0x96, 0xd9, 0xe0, 0x61, 0x1a, 0x52, + 0x81, 0x8d, 0xb8, 0x83, 0xb1, 0x40, 0xe7, 0x98, 0x05, 0xf3, 0x91, 0x0a, + 0xec, 0xc8, 0x1d, 0x0c, 0x4a, 0x68, 0x1f, 0x9f, 0x56, 0xd9, 0xdd, 0xcf, + 0xe1, 0xce, 0xb9, 0x0b, 0x78, 0x76, 0x03, 0x9c, 0xbc, 0xa7, 0x27, 0xf8, + 0x7b, 0xf3, 0xf9, 0xbc, 0x0f, 0xde, 0xc1, 0xeb, 0xcb, 0x97, 0x43, 0xf3, + 0xbc, 0xdc, 0x24, 0x8b, 0x95, 0x15, 0x3d, 0x04, 0x89, 0x7e, 0x07, 0x2c, + 0xd6, 0x9e, 0xd1, 0x28, 0xa8, 0xb0, 0x1b, 0xa0, 0xc0, 0x70, 0xff, 0x31, + 0x27, 0x91, 0x33, 0x02, 0x85, 0x77, 0x20, 0xe3, 0x18, 0x81, 0x43, 0x56, + 0x17, 0xfa, 0x06, 0xe0, 0x0f, 0xb2, 0x07, 0x92, 0x07, 0xa6, 0x90, 0xe8, + 0xcd, 0xc7, 0x46, 0x7d, 0xf8, 0xd4, 0x68, 0x98, 0x80, 0xb1, 0x1c, 0x0f, + 0xf2, 0x61, 0x1f, 0x92, 0xba, 0x3e, 0xf7, 0xea, 0x43, 0x81, 0x27, 0x34, + 0xd9, 0x33, 0x78, 0x0f, 0x8f, 0x92, 0x82, 0x86, 0xc3, 0x0f, 0xd1, 0x62, + 0x85, 0xb6, 0x90, 0x69, 0x80, 0x4c, 0xd5, 0x11, 0xd8, 0x0b, 0x73, 0xb4, + 0x97, 0x62, 0x7f, 0x10, 0x1f, 0x61, 0x32, 0x72, 0x09, 0x32, 0x53, 0x37, + 0x45, 0x52, 0x0d, 0x5b, 0xa6, 0xba, 0x70, 0x32, 0xdd, 0xab, 0x16, 0xec, + 0x17, 0xac, 0xc4, 0x2e, 0x0b, 0x9d, 0x3e, 0xc7, 0x00, 0x22, 0xa0, 0x22, + 0x86, 0x09, 0xf0, 0xc0, 0x77, 0xea, 0xcf, 0xa1, 0x77, 0xf7, 0x89, 0x70, + 0xe3, 0x1e, 0xe8, 0xc0, 0x71, 0x63, 0x45, 0xb9, 0x3e, 0x34, 0x64, 0xe7, + 0x9b, 0x55, 0x18, 0x21, 0x7f, 0x51, 0x70, 0x51, 0xc8, 0xe1, 0xbe, 0x8a, + 0x53, 0x8f, 0x8d, 0x8e, 0x25, 0xd6, 0x33, 0x03, 0x0e, 0x96, 0x35, 0x0c, + 0xb5, 0xf9, 0x3d, 0x18, 0x8c, 0xd8, 0xbb, 0x1d, 0x4e, 0xec, 0x7a, 0xfd, + 0x01, 0x4b, 0xfe, 0x8e, 0x47, 0x81, 0xf1, 0xde, 0x8d, 0x46, 0x80, 0xf5, + 0xb2, 0x67, 0x14, 0x4d, 0xc3, 0x64, 0x3e, 0x8e, 0x34, 0x52, 0xa6, 0x41, + 0x38, 0x46, 0xd9, 0x18, 0x53, 0x63, 0x04, 0x8d, 0xc1, 0x33, 0x06, 0xd1, + 0x34, 0x24, 0xa7, 0x01, 0xb8, 0x5a, 0xba, 0x7c, 0x58, 0x3a, 0x1f, 0x4b, + 0xbf, 0xc3, 0x56, 0x75, 0xad, 0xf8, 0xee, 0xff, 0xe0, 0x54, 0xeb, 0x8f, + 0xe8, 0x42, 0x57, 0xb7, 0x23, 0xea, 0x5e, 0x06, 0xae, 0x7d, 0xdd, 0xe5, + 0xb0, 0x75, 0x0f, 0x02, 0xd7, 0xe3, 0xdd, 0x7e, 0xc4, 0xa1, 0xe0, 0xf0, + 0x7d, 0x05, 0xe3, 0xeb, 0x82, 0xdd, 0x88, 0xaa, 0xb6, 0xc3, 0xd6, 0xa1, + 0x65, 0x30, 0xe5, 0xe7, 0x9f, 0x76, 0xf5, 0x47, 0x2c, 0xfa, 0x91, 0xc6, + 0xfc, 0x29, 0x6e, 0xa8, 0x6a, 0x82, 0xfa, 0x36, 0x9b, 0xbe, 0x7d, 0x88, + 0x00, 0x12, 0xef, 0xba, 0x82, 0xd5, 0xea, 0x5b, 0x2e, 0xea, 0xe2, 0xba, + 0x01, 0xef, 0x96, 0xe6, 0x19, 0xe6, 0x98, 0x30, 0x74, 0x7e, 0x0c, 0x02, + 0xe5, 0x26, 0x00, 0xb4, 0x69, 0xdc, 0xc6, 0xc1, 0x5e, 0x25, 0xae, 0x50, + 0xc6, 0x2f, 0x5a, 0x8d, 0x2a, 0xcb, 0xed, 0x3a, 0xc3, 0x6a, 0x49, 0x98, + 0xbc, 0x14, 0xc0, 0xa1, 0x4e, 0x2d, 0x17, 0x26, 0xa3, 0x97, 0x9e, 0x5e, + 0xad, 0x7b, 0x78, 0x2c, 0x30, 0x87, 0xe2, 0x77, 0x05, 0x16, 0x51, 0x9d, + 0x5d, 0x4b, 0x43, 0xb8, 0x94, 0xb0, 0x50, 0x6d, 0x5b, 0xcc, 0x4b, 0x70, + 0x8f, 0x40, 0x97, 0xf4, 0xd7, 0x5d, 0x38, 0x6d, 0x17, 0x48, 0x4b, 0x54, + 0x12, 0x54, 0x1a, 0xba, 0xaf, 0x64, 0xdf, 0xe7, 0x70, 0x9b, 0x35, 0x0a, + 0x0d, 0x57, 0x7a, 0x33, 0xc0, 0xf0, 0xc1, 0x03, 0x81, 0x7d, 0x67, 0x6c, + 0x0e, 0xe6, 0x0b, 0x46, 0x0e, 0xc4, 0x35, 0x6b, 0x24, 0x0f, 0x53, 0x1e, + 0xb9, 0xe5, 0xfa, 0x95, 0x6c, 0x46, 0x5e, 0x05, 0xcc, 0xa0, 0x7d, 0x16, + 0xb7, 0x51, 0x8e, 0x35, 0x05, 0x13, 0x58, 0x0c, 0xdc, 0x8e, 0xfd, 0x0f, + 0x2e, 0x2c, 0xcf, 0xb8, 0xab, 0xe5, 0x68, 0xb7, 0x82, 0x35, 0x4b, 0x6c, + 0x0c, 0x80, 0x64, 0x1d, 0x41, 0x1d, 0x4c, 0xd5, 0xf7, 0x3a, 0xf2, 0xe3, + 0xe5, 0xa1, 0x00, 0x07, 0xce, 0xd7, 0x4a, 0xb8, 0xcf, 0xca, 0xe0, 0xb4, + 0xf9, 0x08, 0xe0, 0x6b, 0x40, 0xbf, 0xbe, 0x73, 0xb0, 0x33, 0x74, 0xa8, + 0x88, 0x63, 0xc8, 0x05, 0x0e, 0xeb, 0x73, 0x71, 0x9b, 0xec, 0x8e, 0x03, + 0xbe, 0x62, 0x3f, 0xc0, 0x9a, 0xc9, 0x0a, 0x1d, 0xdb, 0x05, 0x17, 0x8a, + 0x1a, 0x30, 0x3e, 0xcb, 0xde, 0x97, 0xe6, 0x80, 0xc5, 0x3e, 0xab, 0xe0, + 0xd6, 0x0c, 0x09, 0x03, 0xaa, 0xc4, 0xc4, 0x3a, 0xda, 0x11, 0x49, 0x38, + 0x46, 0xfb, 0xb8, 0x1b, 0x6d, 0x39, 0x1f, 0xed, 0xa9, 0x72, 0x9c, 0x0f, + 0x7c, 0x37, 0xda, 0x81, 0xa3, 0x5d, 0xbc, 0x33, 0x5a, 0x54, 0xd0, 0xe2, + 0xcf, 0xf2, 0x2f, 0x1d, 0xff, 0x4e, 0x6b, 0x3d, 0xcd, 0xff, 0x23, 0x7f, + 0xa8, 0x1d, 0xe6, 0xe4, 0xb7, 0x46, 0xe0, 0xdf, 0x28, 0x11, 0x84, 0xc9, + 0xee, 0xab, 0xfd, 0xed, 0x7d, 0xdc, 0xaf, 0xff, 0xf6, 0x68, 0x7f, 0xf7, + 0x68, 0x30, 0x2b, 0x2c, 0xe2, 0x96, 0x34, 0xee, 0xe4, 0x00, 0xa4, 0xc3, + 0x44, 0x58, 0xda, 0x86, 0xca, 0x33, 0x49, 0x37, 0x69, 0x4f, 0x89, 0xdd, + 0xd4, 0x6b, 0x06, 0xda, 0x61, 0xbf, 0xa8, 0x50, 0xd9, 0x99, 0x30, 0xe9, + 0x47, 0x79, 0x6c, 0xdc, 0x3d, 0x4e, 0xc5, 0xb8, 0x1b, 0x92, 0x41, 0x65, + 0x92, 0x0c, 0x6b, 0x90, 0x97, 0x82, 0xea, 0xd2, 0x59, 0x79, 0xa5, 0x1f, + 0xef, 0x5e, 0x8a, 0x9f, 0x17, 0x09, 0xcf, 0x55, 0x1a, 0x5b, 0x92, 0xf2, + 0x53, 0x65, 0xfb, 0xc1, 0xcb, 0x7a, 0x9c, 0x06, 0xe4, 0x3f, 0x38, 0x4e, + 0xf7, 0xae, 0xf7, 0x71, 0xea, 0x6f, 0x6c, 0x71, 0xbf, 0x7b, 0xb3, 0xe4, + 0x94, 0xdc, 0xbe, 0xc8, 0x43, 0x09, 0xab, 0xd2, 0x76, 0x67, 0x9b, 0x0e, + 0x89, 0x8a, 0xfe, 0x66, 0xa7, 0xe9, 0xf8, 0x80, 0x8e, 0x20, 0xf7, 0xb3, + 0xa0, 0xe7, 0x20, 0xe2, 0xa8, 0x17, 0x42, 0xba, 0xce, 0xa8, 0x95, 0xfb, + 0x2b, 0xba, 0x17, 0xbc, 0xdf, 0x80, 0xda, 0x6f, 0xee, 0x42, 0x8b, 0x6f, + 0x3b, 0x59, 0x80, 0xc1, 0x25, 0x7f, 0x97, 0x61, 0x60, 0xf1, 0x86, 0x6e, + 0xb6, 0xd7, 0x4b, 0x86, 0x42, 0x41, 0xd2, 0x50, 0x15, 0xb0, 0x09, 0x6a, + 0x6e, 0xeb, 0x9e, 0xc7, 0xb7, 0x25, 0xf2, 0xea, 0x30, 0x4d, 0xf7, 0xc9, + 0x3e, 0x16, 0x78, 0x1a, 0x21, 0x71, 0xc7, 0xf4, 0xb6, 0x1c, 0x9f, 0x4d, + 0x84, 0x32, 0xa7, 0xf4, 0x81, 0x37, 0x64, 0x05, 0xff, 0xee, 0x8b, 0x24, + 0xf0, 0x6f, 0xe4, 0x61, 0x43, 0xd7, 0x91, 0xc7, 0x94, 0x7e, 0xe3, 0x4f, + 0x51, 0x22, 0x5e, 0xa6, 0x1d, 0x58, 0x9c, 0x42, 0xe3, 0x82, 0xd0, 0xf8, + 0x47, 0xea, 0xd9, 0x83, 0x85, 0x1c, 0xc1, 0x02, 0x0f, 0xfb, 0x0a, 0x16, + 0x82, 0xb9, 0x62, 0xde, 0x5a, 0x51, 0x79, 0x91, 0xe4, 0xf6, 0x04, 0xac, + 0xbf, 0x81, 0x74, 0x35, 0xfb, 0xa4, 0x34, 0xe1, 0x32, 0x87, 0x05, 0x23, + 0x97, 0x80, 0xd0, 0x2c, 0x01, 0x66, 0x69, 0x32, 0xfc, 0xe5, 0x43, 0xdc, + 0x35, 0x76, 0x35, 0x84, 0x87, 0xa7, 0x1d, 0x19, 0xa2, 0x1b, 0xf0, 0x43, + 0x52, 0x03, 0x22, 0xec, 0x98, 0x95, 0x7f, 0xa6, 0x79, 0xf9, 0xe5, 0x43, + 0x20, 0x80, 0x5c, 0x6f, 0x30, 0xee, 0x07, 0xbe, 0xc2, 0x63, 0x05, 0x0b, + 0x16, 0xc1, 0x1a, 0x7b, 0x38, 0x0b, 0x99, 0x7b, 0x38, 0x28, 0x05, 0x4f, + 0xf3, 0x9b, 0x19, 0x4a, 0xea, 0xa8, 0x15, 0x41, 0xee, 0x30, 0x84, 0xf5, + 0xb6, 0x58, 0x90, 0xf3, 0x30, 0x7a, 0x1a, 0x3e, 0xb9, 0xf4, 0x3b, 0xf7, + 0xe6, 0x49, 0x20, 0xc9, 0x10, 0x62, 0xbb, 0xbb, 0x34, 0xdc, 0xe3, 0x53, + 0xea, 0xd0, 0x77, 0xf2, 0x2b, 0x68, 0x81, 0x5b, 0x74, 0xa5, 0xb4, 0x1b, + 0x58, 0x7a, 0x86, 0x07, 0xee, 0x38, 0xa4, 0xe8, 0xb2, 0xf9, 0x0b, 0x58, + 0x1d, 0xa9, 0xb9, 0x72, 0x74, 0x90, 0xf5, 0x48, 0x8a, 0x6e, 0x4f, 0x76, + 0xcb, 0x4b, 0xcd, 0x78, 0x53, 0xe2, 0xbd, 0x8c, 0xa0, 0x2e, 0x82, 0xc5, + 0xd2, 0x81, 0x33, 0xe0, 0xd3, 0x4f, 0xd2, 0x54, 0x5e, 0xce, 0x74, 0xa3, + 0x82, 0xc7, 0x02, 0xd8, 0x8a, 0x65, 0x61, 0xd3, 0x7f, 0xa9, 0x35, 0xda, + 0x95, 0x84, 0x91, 0xdb, 0x0e, 0xf0, 0x7d, 0xc2, 0x74, 0x59, 0x02, 0x5d, + 0x1c, 0x38, 0x38, 0x76, 0x49, 0x46, 0x42, 0xaa, 0x04, 0xb7, 0x06, 0x4e, + 0x0d, 0x5c, 0x1a, 0xf8, 0x38, 0xcd, 0x13, 0x3f, 0xbe, 0xf1, 0xe8, 0x3d, + 0xdd, 0x18, 0x78, 0xd0, 0x70, 0x0e, 0x02, 0x7c, 0x15, 0x5c, 0xd2, 0x5d, + 0xf0, 0x47, 0xe9, 0xf1, 0x6b, 0x9f, 0xf1, 0xee, 0x17, 0xea, 0x2f, 0xec, + 0x7e, 0x63, 0x6f, 0xf3, 0xf9, 0xef, 0x26, 0x17, 0xfc, 0x06, 0x3f, 0xbd, + 0x1c, 0xdc, 0xeb, 0xdf, 0xff, 0xfc, 0x23, 0xbb, 0xf2, 0x8f, 0x6c, 0x8b, + 0x3f, 0xff, 0xc1, 0x39, 0xc5, 0x8f, 0x5b, 0x99, 0xa9, 0xdd, 0xff, 0x32, + 0x7f, 0x54, 0x00, 0x38, 0x93, 0x99, 0x1f, 0x67, 0x71, 0x67, 0xa9, 0xc0, + 0xb7, 0xc3, 0x18, 0xe0, 0xc9, 0x61, 0x44, 0x79, 0x0c, 0x1f, 0xc8, 0x3e, + 0xf1, 0x6b, 0x00, 0xc7, 0x30, 0x8c, 0xc6, 0xd1, 0x72, 0x9d, 0xf6, 0xba, + 0x5d, 0x09, 0x1f, 0x88, 0x23, 0x19, 0xb8, 0x91, 0x26, 0x4a, 0x0b, 0x51, + 0x74, 0xd1, 0xfb, 0xdb, 0x38, 0x26, 0x0f, 0x99, 0x64, 0xb8, 0xa8, 0xe2, + 0x80, 0xbb, 0x57, 0xa5, 0xc0, 0x2b, 0xa5, 0x17, 0x03, 0x37, 0x98, 0xb4, + 0x0b, 0x96, 0xd5, 0x93, 0x5c, 0x6a, 0x9a, 0x0d, 0xee, 0x90, 0xee, 0x06, + 0xbb, 0xca, 0x87, 0xd4, 0xae, 0x52, 0xbb, 0xd9, 0xa5, 0x86, 0x09, 0x76, + 0x15, 0xbe, 0x4d, 0x4d, 0xfb, 0x4f, 0x45, 0xb4, 0x37, 0xfb, 0xcf, 0x53, + 0x6a, 0xd2, 0x0d, 0x40, 0xa1, 0x3b, 0xe6, 0xf4, 0xab, 0x3f, 0xa7, 0x7e, + 0x85, 0xc3, 0xd2, 0xc5, 0xaa, 0xda, 0xd0, 0x5d, 0xe1, 0x45, 0x52, 0xdd, + 0xe7, 0x52, 0xa0, 0x1c, 0xce, 0x2f, 0xc5, 0x9c, 0x5d, 0x72, 0x9b, 0x8d, + 0x97, 0xa7, 0x47, 0xc2, 0xc4, 0x41, 0xb2, 0x00, 0xf4, 0xa5, 0xb4, 0xdf, + 0xf1, 0x51, 0x0f, 0x8c, 0x92, 0x16, 0xc3, 0xe2, 0x0c, 0xfc, 0xeb, 0x20, + 0x8a, 0x6f, 0x7c, 0xf2, 0x83, 0xbf, 0x06, 0x70, 0xd0, 0xa2, 0xa1, 0x26, + 0xff, 0x61, 0xa3, 0x23, 0x94, 0x77, 0xa4, 0x0b, 0x8d, 0x95, 0xa9, 0xd4, + 0xa7, 0x7d, 0xff, 0x2f, 0xef, 0xff, 0x1a, 0x10, 0x5f, 0x2e, 0x31, 0x36, + 0xdd, 0x94, 0x43, 0xfe, 0x07, 0xef, 0xb2, 0x66, 0x87, 0x36, 0xd0, 0x52, + 0x8f, 0xf6, 0xe8, 0xd3, 0xf8, 0xfe, 0xaf, 0x92, 0xea, 0x57, 0x4f, 0xb0, + 0x76, 0xff, 0xb2, 0xe5, 0x17, 0x7e, 0x60, 0xd4, 0xd7, 0x8f, 0x70, 0xe8, + 0xc9, 0x41, 0x67, 0x34, 0xae, 0x07, 0xd5, 0x02, 0x5e, 0x2d, 0xd1, 0x52, + 0x53, 0x2c, 0xbd, 0x61, 0x88, 0x9a, 0x3b, 0x9c, 0x98, 0x19, 0x2a, 0x6a, + 0x88, 0xee, 0x11, 0xb7, 0xf7, 0xa0, 0x5c, 0xdc, 0xb9, 0xa5, 0x3b, 0xaa, + 0x9e, 0x2d, 0x63, 0xe7, 0x91, 0xc1, 0x29, 0xba, 0xa6, 0xf4, 0x4b, 0xbf, + 0xf5, 0xf4, 0xb4, 0x2a, 0xd6, 0x9c, 0x43, 0x6d, 0x68, 0x52, 0x46, 0x68, + 0xd7, 0x32, 0x70, 0x01, 0x63, 0x48, 0x33, 0xa5, 0x11, 0xc2, 0x56, 0xc3, + 0x37, 0x35, 0x72, 0xa2, 0xc4, 0x8e, 0x5f, 0x9f, 0xea, 0x97, 0x4d, 0x40, + 0x48, 0x24, 0x8d, 0x40, 0x27, 0x4d, 0xfd, 0x18, 0x53, 0xbb, 0x4f, 0xe6, + 0xbd, 0x24, 0x04, 0x99, 0xb3, 0xf4, 0x41, 0xe1, 0x57, 0xc9, 0xf3, 0xab, + 0xc6, 0xf3, 0x64, 0x34, 0x36, 0xb9, 0x8f, 0x25, 0x45, 0x0d, 0x01, 0xd4, + 0x68, 0xc6, 0xa0, 0xd1, 0x23, 0xc6, 0x20, 0xa1, 0x40, 0x35, 0x6c, 0xa4, + 0x1f, 0x66, 0x6a, 0xbc, 0xa8, 0xdd, 0xd6, 0x78, 0xd1, 0x0b, 0xa4, 0xb4, + 0x88, 0x23, 0x39, 0x0f, 0x14, 0x1d, 0x23, 0x44, 0xf0, 0xc8, 0x35, 0xd2, + 0xd5, 0x9f, 0x9c, 0x9a, 0xb3, 0xe0, 0xea, 0x47, 0xcf, 0x8e, 0xa7, 0xff, + 0x05, 0xbc, 0x47, 0xa4, 0x19, 0x06, 0x2d, 0x00, 0x00 +}; + +const GFXglyph Oswald_Medium30pt7bGlyphs[] PROGMEM = { + { 0, 1, 1, 14, 0, 0 }, // 0x20 ' ' + { 1, 8, 48, 14, 3, -47 }, // 0x21 '!' + { 49, 17, 17, 19, 1, -47 }, // 0x22 '"' + { 86, 26, 48, 30, 2, -47 }, // 0x23 '#' + { 242, 25, 59, 29, 2, -52 }, // 0x24 '$' + { 427, 51, 48, 56, 2, -47 }, // 0x25 '%' + { 733, 29, 49, 34, 3, -47 }, // 0x26 '&' + { 911, 7, 17, 9, 1, -47 }, // 0x27 ''' + { 926, 13, 59, 19, 4, -47 }, // 0x28 '(' + { 1022, 13, 59, 17, 2, -47 }, // 0x29 ')' + { 1118, 21, 21, 24, 2, -47 }, // 0x2A '*' + { 1174, 22, 24, 25, 2, -35 }, // 0x2B '+' + { 1240, 8, 16, 13, 2, -7 }, // 0x2C ',' + { 1256, 14, 6, 18, 2, -19 }, // 0x2D '-' + { 1267, 8, 8, 13, 3, -7 }, // 0x2E '.' + { 1275, 19, 48, 23, 2, -47 }, // 0x2F '/' + { 1389, 25, 49, 31, 3, -47 }, // 0x30 '0' + { 1543, 15, 48, 22, 2, -47 }, // 0x31 '1' + { 1633, 25, 48, 29, 2, -47 }, // 0x32 '2' + { 1783, 25, 49, 29, 2, -47 }, // 0x33 '3' + { 1937, 27, 48, 30, 2, -47 }, // 0x34 '4' + { 2099, 24, 49, 29, 3, -47 }, // 0x35 '5' + { 2246, 25, 49, 31, 3, -47 }, // 0x36 '6' + { 2400, 21, 48, 24, 1, -47 }, // 0x37 '7' + { 2526, 24, 49, 30, 3, -47 }, // 0x38 '8' + { 2673, 25, 49, 31, 2, -47 }, // 0x39 '9' + { 2827, 7, 28, 14, 4, -31 }, // 0x3A ':' + { 2852, 8, 38, 15, 4, -32 }, // 0x3B ';' + { 2890, 18, 25, 23, 2, -36 }, // 0x3C '<' + { 2947, 19, 16, 25, 3, -31 }, // 0x3D '=' + { 2985, 18, 25, 23, 3, -36 }, // 0x3E '>' + { 3042, 24, 48, 28, 2, -47 }, // 0x3F '?' + { 3186, 50, 56, 55, 3, -47 }, // 0x40 '@' + { 3536, 29, 48, 31, 1, -47 }, // 0x41 'A' + { 3710, 27, 48, 33, 4, -47 }, // 0x42 'B' + { 3872, 26, 49, 32, 3, -47 }, // 0x43 'C' + { 4032, 26, 48, 33, 4, -47 }, // 0x44 'D' + { 4188, 20, 48, 25, 4, -47 }, // 0x45 'E' + { 4308, 19, 48, 24, 4, -47 }, // 0x46 'F' + { 4422, 27, 49, 33, 3, -47 }, // 0x47 'G' + { 4588, 27, 48, 34, 4, -47 }, // 0x48 'H' + { 4750, 8, 48, 16, 4, -47 }, // 0x49 'I' + { 4798, 15, 49, 19, 1, -47 }, // 0x4A 'J' + { 4890, 27, 48, 31, 4, -47 }, // 0x4B 'K' + { 5052, 20, 48, 25, 4, -47 }, // 0x4C 'L' + { 5172, 34, 48, 40, 3, -47 }, // 0x4D 'M' + { 5376, 24, 48, 32, 4, -47 }, // 0x4E 'N' + { 5520, 27, 49, 33, 3, -47 }, // 0x4F 'O' + { 5686, 26, 48, 31, 4, -47 }, // 0x50 'P' + { 5842, 27, 57, 33, 3, -47 }, // 0x51 'Q' + { 6035, 27, 48, 33, 4, -47 }, // 0x52 'R' + { 6197, 26, 49, 29, 2, -47 }, // 0x53 'S' + { 6357, 24, 48, 25, 1, -47 }, // 0x54 'T' + { 6501, 27, 49, 33, 3, -47 }, // 0x55 'U' + { 6667, 28, 48, 30, 1, -47 }, // 0x56 'V' + { 6835, 38, 48, 42, 2, -47 }, // 0x57 'W' + { 7063, 28, 48, 29, 1, -47 }, // 0x58 'X' + { 7231, 27, 48, 29, 1, -47 }, // 0x59 'Y' + { 7393, 22, 48, 25, 2, -47 }, // 0x5A 'Z' + // Euro sign ([) - ASCII code 91 + { 11030, 30, 49, 31, 0, -47 }, // 0x5B '[' + // Backslash placeholder - ASCII code 92 + { 0, 0, 0, 0, 0, 0 }, // 0x5C '\' + // Pound sign (]) - ASCII code 93 + { 11214, 24, 48, 26, 1, -47 }, // 0x5D ']' + // Yen sign (^) - ASCII code 94 + { 11358, 28, 48, 27, 0, -47 }, // 0x5E '^' + { 7905, 21, 6, 21, 0, 4 }, // 0x5F '_' + { 7921, 11, 12, 17, 3, -47 }, // 0x60 '`' + { 7938, 22, 35, 26, 1, -33 }, // 0x61 'a' + { 8035, 23, 49, 28, 3, -47 }, // 0x62 'b' + { 8176, 22, 35, 26, 2, -33 }, // 0x63 'c' + { 8273, 23, 49, 28, 2, -47 }, // 0x64 'd' + { 8414, 22, 35, 26, 2, -33 }, // 0x65 'e' + { 8511, 16, 46, 18, 1, -45 }, // 0x66 'f' + { 8603, 28, 46, 28, 1, -34 }, // 0x67 'g' + { 8764, 22, 48, 28, 3, -47 }, // 0x68 'h' + { 8896, 8, 46, 15, 3, -45 }, // 0x69 'i' + { 8942, 13, 56, 15, -1, -45 }, // 0x6A 'j' + { 9033, 25, 48, 28, 3, -47 }, // 0x6B 'k' + { 9183, 8, 48, 15, 4, -47 }, // 0x6C 'l' + { 9231, 36, 35, 42, 3, -34 }, // 0x6D 'm' + { 9389, 22, 34, 28, 3, -33 }, // 0x6E 'n' + { 9483, 22, 35, 27, 2, -33 }, // 0x6F 'o' + { 9580, 23, 45, 28, 3, -33 }, // 0x70 'p' + { 9710, 22, 45, 28, 3, -33 }, // 0x71 'q' + { 9834, 17, 34, 21, 3, -33 }, // 0x72 'r' + { 9907, 21, 35, 24, 1, -33 }, // 0x73 's' + { 9999, 17, 44, 19, 1, -43 }, // 0x74 't' + { 10093, 22, 35, 28, 3, -33 }, // 0x75 'u' + { 10190, 22, 34, 24, 1, -33 }, // 0x76 'v' + { 10284, 32, 34, 35, 1, -33 }, // 0x77 'w' + { 10420, 23, 34, 24, 1, -33 }, // 0x78 'x' + { 10518, 24, 43, 25, 0, -33 }, // 0x79 'y' + { 10647, 18, 34, 22, 2, -33 }, // 0x7A 'z' + { 10724, 15, 59, 20, 3, -47 }, // 0x7B '{' + { 10835, 7, 58, 15, 4, -47 }, // 0x7C '|' + { 10886, 16, 59, 21, 2, -47 }, // 0x7D '}' + { 11004, 23, 9, 27, 2, -28 } }; // 0x7E '~' + +// const GFXfont Oswald_Medium30pt7b PROGMEM = { +// (uint8_t *)Oswald_Medium30pt7bBitmaps, +// (GFXglyph *)Oswald_Medium30pt7bGlyphs, +// 0x20, 0x7E, 87 }; + +// Approx. 11702 bytes + + +// Font properties +static constexpr FontData Oswald_Medium30pt7b_Properties = { + Oswald_Medium30pt7bBitmaps_Gzip, + Oswald_Medium30pt7bGlyphs, + sizeof(Oswald_Medium30pt7bBitmaps_Gzip), + 11526, // Original size + 0x20, // First char + 0x7E, // Last char + 87 // yAdvance +}; diff --git a/src/fonts/oswald-medium80.h b/src/fonts/oswald-medium80.h new file mode 100644 index 0000000..dfab628 --- /dev/null +++ b/src/fonts/oswald-medium80.h @@ -0,0 +1,1083 @@ +#pragma once + +#include +#include +#include "fonts.hpp" + +const uint8_t Oswald_Medium80pt7bBitmaps_Gzip[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0x7d, + 0x4d, 0xae, 0xdc, 0x38, 0xb3, 0xa5, 0xf4, 0xd4, 0x80, 0xde, 0xa0, 0xd1, + 0xea, 0x05, 0x34, 0x9a, 0xbd, 0x84, 0x5e, 0x40, 0xe3, 0xb1, 0x97, 0xf5, + 0x06, 0x0d, 0x50, 0x05, 0x0f, 0x3c, 0xac, 0x25, 0x78, 0x29, 0x56, 0xc1, + 0x83, 0x1a, 0x7a, 0x09, 0xa6, 0x51, 0x03, 0x0f, 0x2d, 0xc3, 0x83, 0x92, + 0x61, 0x95, 0xd8, 0x8a, 0x88, 0x73, 0x82, 0x4a, 0x65, 0xa6, 0x7d, 0x7d, + 0xcb, 0xe5, 0xfa, 0xf9, 0x9c, 0xa8, 0xba, 0xa2, 0x23, 0xc9, 0x10, 0x19, + 0x8c, 0x08, 0xc6, 0x9f, 0x94, 0x4d, 0xb9, 0xf1, 0xd9, 0x4a, 0xf9, 0xb5, + 0x94, 0xe7, 0xe5, 0xb7, 0x52, 0xde, 0x96, 0x12, 0xcb, 0x87, 0x52, 0x5e, + 0x96, 0x2d, 0x95, 0xf7, 0xa5, 0x3c, 0x2b, 0x6b, 0x29, 0x6f, 0xca, 0x16, + 0xcb, 0xbb, 0x52, 0x7e, 0x2c, 0x6b, 0x2a, 0xbf, 0x94, 0x12, 0xca, 0x52, + 0xca, 0x8b, 0xb2, 0x85, 0x32, 0x97, 0xf2, 0xb4, 0xac, 0xb1, 0xbc, 0x2e, + 0xdb, 0x50, 0xe6, 0x54, 0x9e, 0x94, 0x35, 0x94, 0x5c, 0x4a, 0x5f, 0x96, + 0x58, 0x7e, 0x52, 0x60, 0x2c, 0x3f, 0x94, 0x75, 0x28, 0x53, 0xda, 0xba, + 0x32, 0x87, 0x32, 0x96, 0xb5, 0x2f, 0x39, 0x96, 0xb6, 0x2c, 0x17, 0xc0, + 0xae, 0x4c, 0x61, 0x6b, 0xca, 0xdc, 0x97, 0x31, 0xae, 0x6d, 0xc9, 0x43, + 0x69, 0xd2, 0x72, 0x02, 0x4e, 0xc3, 0xd6, 0xc4, 0xb9, 0x2d, 0xe3, 0xb0, + 0x36, 0x29, 0x77, 0xa5, 0x09, 0x4b, 0x53, 0xa6, 0xfe, 0x00, 0x9c, 0xba, + 0xad, 0x09, 0x73, 0x53, 0xc6, 0x6e, 0x6d, 0x42, 0x6e, 0x4a, 0xd3, 0x2f, + 0x4d, 0x9c, 0xda, 0xad, 0x19, 0xe6, 0x26, 0x09, 0xf0, 0xce, 0x27, 0x62, + 0xed, 0xa9, 0x7c, 0x2c, 0xe5, 0xd5, 0x89, 0x20, 0xf7, 0x81, 0xd3, 0xfe, + 0x5f, 0xda, 0x09, 0xb8, 0x2f, 0x65, 0xff, 0xb4, 0xf2, 0x67, 0x8a, 0xf2, + 0xef, 0x5e, 0x9a, 0x8d, 0xf4, 0xce, 0x61, 0x1f, 0xb5, 0xaf, 0x6e, 0x1f, + 0x3d, 0xa6, 0x1d, 0xcb, 0xbe, 0xb0, 0x8f, 0x65, 0x5f, 0xcb, 0x8e, 0x60, + 0x8c, 0x3b, 0x99, 0xf7, 0xb5, 0x7d, 0x28, 0xfb, 0x1a, 0x77, 0xb2, 0x8f, + 0x61, 0xa7, 0xf4, 0xdc, 0xed, 0x14, 0xdf, 0x9a, 0xb4, 0x13, 0x79, 0x1a, + 0x76, 0x62, 0x2f, 0xed, 0x4e, 0xf4, 0x7d, 0x85, 0x3b, 0xf1, 0xa7, 0x7e, + 0xdf, 0x84, 0x7d, 0xc5, 0xfb, 0x66, 0x34, 0x61, 0xa7, 0xfb, 0x4e, 0x81, + 0x79, 0xff, 0x77, 0xda, 0xf7, 0xa1, 0x19, 0xf6, 0xfd, 0xc8, 0x3b, 0x4d, + 0xd3, 0xda, 0xc4, 0x9d, 0xea, 0x63, 0xbf, 0x53, 0x7f, 0xa7, 0xc3, 0x12, + 0x77, 0x3a, 0xec, 0xbb, 0x31, 0x76, 0xfb, 0xae, 0xec, 0x24, 0x58, 0x76, + 0x72, 0x0e, 0x65, 0x27, 0xeb, 0x4e, 0xe1, 0x34, 0x37, 0x71, 0xdd, 0x29, + 0xda, 0xef, 0x1b, 0x34, 0xed, 0x44, 0x8e, 0x4b, 0x13, 0xf6, 0xbd, 0x69, + 0xba, 0x7d, 0x3b, 0xa6, 0x26, 0xcd, 0x3b, 0x71, 0x87, 0x7d, 0x7b, 0x9a, + 0x9d, 0xee, 0x3b, 0x85, 0x82, 0x0c, 0xdf, 0x5b, 0xfb, 0xcc, 0xf7, 0x11, + 0x0b, 0x01, 0xe3, 0x11, 0x90, 0x01, 0x90, 0x7b, 0x00, 0xb0, 0xaf, 0x7a, + 0x47, 0x08, 0x40, 0x54, 0x40, 0x03, 0xc0, 0x7c, 0x13, 0x30, 0xb5, 0x06, + 0x58, 0xf5, 0x06, 0x0e, 0xe8, 0x09, 0x48, 0x02, 0xd8, 0x08, 0xc8, 0x47, + 0xc0, 0x42, 0xc0, 0xd8, 0x11, 0x20, 0x37, 0x90, 0x39, 0x28, 0x60, 0x50, + 0xc0, 0x04, 0xc0, 0x7a, 0x13, 0x60, 0x8b, 0x6c, 0x74, 0x4d, 0x07, 0x40, + 0x4b, 0xc0, 0x8d, 0x55, 0xdf, 0x20, 0x43, 0xb9, 0x41, 0x86, 0x04, 0x61, + 0xfb, 0x95, 0x52, 0xf7, 0x6d, 0x00, 0x8f, 0x5f, 0xe4, 0xe7, 0xf6, 0xfa, + 0xd6, 0x22, 0xcf, 0x7b, 0x3d, 0x5e, 0xed, 0xf5, 0x50, 0x75, 0x8e, 0x7d, + 0xde, 0x7e, 0x13, 0x40, 0x3e, 0x90, 0xe1, 0x72, 0xd5, 0xcd, 0x97, 0xac, + 0x7a, 0x7e, 0xe4, 0xaa, 0xbf, 0x12, 0x87, 0xaf, 0xc6, 0xd0, 0x57, 0x1c, + 0x9e, 0x2e, 0x38, 0x3c, 0x7f, 0x92, 0xc3, 0x97, 0xdb, 0x9b, 0xff, 0x30, + 0x32, 0x0c, 0x5f, 0x42, 0x06, 0x53, 0xad, 0xdd, 0x02, 0x1d, 0x9b, 0x70, + 0x1d, 0xb2, 0x5d, 0x5b, 0x6a, 0xe3, 0x38, 0xda, 0xb5, 0x9f, 0x01, 0xd8, + 0x70, 0x0d, 0xd3, 0x17, 0xa2, 0xd8, 0xa7, 0xa8, 0x43, 0xba, 0x62, 0x77, + 0x97, 0xeb, 0x6a, 0x70, 0xfb, 0x46, 0x79, 0x42, 0xf5, 0xbd, 0xf0, 0x84, + 0xcc, 0xdc, 0x00, 0xbd, 0x31, 0x89, 0x75, 0x14, 0x40, 0x02, 0x20, 0x00, + 0xd0, 0x19, 0x1b, 0x4d, 0x8d, 0x5d, 0x57, 0xc3, 0x50, 0x44, 0xd1, 0xea, + 0x67, 0x69, 0x29, 0x6f, 0xf8, 0x62, 0xde, 0x19, 0x7d, 0x6b, 0xf1, 0xbf, + 0x70, 0x7d, 0x4e, 0x42, 0xa2, 0x22, 0x67, 0xe1, 0x8e, 0x77, 0xff, 0xf3, + 0xc3, 0x3e, 0x4a, 0x6e, 0xb8, 0x9f, 0x94, 0x45, 0x67, 0x32, 0x08, 0xfe, + 0xfd, 0x46, 0xaf, 0x8b, 0xd1, 0x70, 0x8d, 0xc6, 0x17, 0xbb, 0xaa, 0xb7, + 0x3d, 0x5d, 0x8a, 0xed, 0xd8, 0x7e, 0x36, 0x14, 0x5d, 0xd4, 0x7e, 0x16, + 0x2b, 0x07, 0xed, 0xc7, 0xb2, 0x2d, 0xe2, 0xc7, 0x62, 0x5b, 0xf9, 0xae, + 0xd8, 0xbe, 0x6c, 0xd1, 0x78, 0xad, 0xfc, 0x5c, 0x8c, 0x4f, 0xb2, 0xa2, + 0xe8, 0xe5, 0xac, 0x91, 0x9e, 0xab, 0x7c, 0xb7, 0xf7, 0xfc, 0x5f, 0xba, + 0x62, 0x25, 0x62, 0xaf, 0x38, 0x8d, 0x56, 0xb6, 0x0b, 0x00, 0x90, 0xa8, + 0xb2, 0xc2, 0x19, 0x3d, 0x0b, 0x7b, 0xea, 0x86, 0x0c, 0x46, 0x5c, 0xa5, + 0xe2, 0x8c, 0xa1, 0xbe, 0x0f, 0x33, 0x86, 0x5e, 0x02, 0x12, 0x00, 0xfd, + 0x11, 0x30, 0x02, 0xb0, 0x00, 0xb9, 0x02, 0x86, 0x07, 0x01, 0x56, 0xe2, + 0x98, 0x80, 0xe3, 0x0b, 0x00, 0x23, 0x26, 0xb6, 0x70, 0xa6, 0x9f, 0x07, + 0xdc, 0x5c, 0x6d, 0x4b, 0x7a, 0x84, 0x03, 0x3d, 0x26, 0x00, 0x56, 0x02, + 0x46, 0x00, 0x96, 0x33, 0x6d, 0x33, 0x00, 0x1b, 0x01, 0x3b, 0x53, 0xe8, + 0x4c, 0x17, 0xe5, 0x7b, 0xd9, 0xa7, 0x68, 0x6c, 0xf0, 0x4e, 0xb1, 0xa8, + 0x96, 0x9b, 0x15, 0xd7, 0x2b, 0x19, 0xb4, 0x23, 0xff, 0xa8, 0xf7, 0x8b, + 0x72, 0x1c, 0x2c, 0x3a, 0xf4, 0x2d, 0x7a, 0x16, 0xf6, 0x54, 0xf4, 0x1d, + 0x78, 0xda, 0xb8, 0x7d, 0x51, 0x9c, 0x6f, 0x4d, 0xb2, 0xc5, 0xca, 0x51, + 0xd6, 0xda, 0xed, 0x12, 0x63, 0xad, 0x37, 0x60, 0xc2, 0xb5, 0x98, 0x96, + 0x30, 0x5e, 0xeb, 0xc8, 0x6b, 0x45, 0x79, 0x6d, 0x94, 0x2f, 0x5e, 0x08, + 0x40, 0xd8, 0x48, 0xa6, 0x2a, 0xec, 0x3f, 0x14, 0xd5, 0x3a, 0x62, 0x2e, + 0xa9, 0x18, 0xcc, 0x26, 0x55, 0x2a, 0x1b, 0x26, 0x45, 0x14, 0x1a, 0x88, + 0xd9, 0x95, 0x78, 0x65, 0x08, 0xe4, 0x02, 0x49, 0x75, 0x09, 0x1d, 0x5d, + 0x74, 0x7d, 0x11, 0x3d, 0x3a, 0x18, 0x60, 0xc4, 0x32, 0x27, 0x6c, 0x50, + 0x06, 0xc7, 0xcc, 0xd8, 0x07, 0xfd, 0xf7, 0xde, 0x77, 0xb4, 0xeb, 0xef, + 0xd7, 0x41, 0x0f, 0x47, 0x11, 0x6c, 0xef, 0x9b, 0x02, 0x0c, 0xad, 0xf3, + 0x0f, 0x00, 0x3d, 0xe6, 0xdb, 0xa2, 0xa3, 0x2e, 0x60, 0x3e, 0x02, 0x52, + 0xa1, 0x82, 0x03, 0x80, 0x6b, 0xef, 0x00, 0x30, 0x0a, 0x2b, 0xc0, 0xa6, + 0xd5, 0x15, 0xa7, 0xca, 0x11, 0x30, 0xc9, 0xdd, 0x66, 0xbf, 0x2b, 0x00, + 0xb6, 0xc4, 0x81, 0x14, 0xbe, 0x0b, 0x08, 0xe4, 0xac, 0xe1, 0x1a, 0x30, + 0x11, 0xf0, 0x04, 0x2c, 0x66, 0x80, 0x5d, 0x69, 0x35, 0xc9, 0x94, 0x14, + 0x01, 0x8b, 0xfe, 0xbf, 0x03, 0x46, 0x00, 0x66, 0x23, 0xd1, 0x25, 0x60, + 0x95, 0xc6, 0x88, 0xd5, 0x3b, 0x80, 0xe4, 0x10, 0xcd, 0xfa, 0x20, 0x40, + 0xba, 0x02, 0x6c, 0x47, 0xc0, 0x76, 0x03, 0x50, 0xae, 0x00, 0x2b, 0x89, + 0x9e, 0x77, 0x52, 0x3c, 0x04, 0xd0, 0x5e, 0x03, 0x96, 0xcf, 0x00, 0xba, + 0x6b, 0xc0, 0xfc, 0x19, 0x40, 0xff, 0x00, 0x40, 0xfe, 0x0c, 0x60, 0xb8, + 0x06, 0x4c, 0x77, 0x00, 0xa4, 0x47, 0xb8, 0x06, 0x8c, 0x9f, 0x04, 0xac, + 0xd7, 0x80, 0x78, 0x0d, 0x68, 0xee, 0x02, 0x6c, 0xf7, 0xb9, 0x93, 0xce, + 0x1f, 0xdc, 0x38, 0x65, 0x27, 0xe1, 0xa0, 0x0b, 0x40, 0xbc, 0x04, 0x18, + 0x17, 0x16, 0x97, 0x55, 0xf5, 0xc5, 0x66, 0xdf, 0x38, 0xe7, 0x64, 0x07, + 0x18, 0xf3, 0xcf, 0xed, 0x09, 0x90, 0x75, 0xe3, 0x5a, 0x93, 0x8a, 0x0a, + 0x88, 0x38, 0xc2, 0x45, 0xa2, 0x3a, 0x3b, 0xef, 0xf7, 0x2e, 0x10, 0xc2, + 0x0e, 0xfa, 0x6c, 0x81, 0x98, 0x8e, 0xb2, 0x93, 0x83, 0x4a, 0x30, 0xe4, + 0x56, 0x00, 0xd1, 0x24, 0xd8, 0x24, 0xbb, 0x57, 0x63, 0x4c, 0x25, 0xd8, + 0x64, 0xbf, 0xa7, 0x6e, 0x9c, 0x74, 0xaa, 0x8b, 0x02, 0x20, 0x9f, 0x03, + 0xfe, 0xe6, 0x16, 0x8b, 0xe8, 0x74, 0xe0, 0x20, 0x53, 0x81, 0x7c, 0x26, + 0xb3, 0x28, 0x00, 0x58, 0x44, 0x1d, 0x8c, 0x0a, 0xe8, 0x09, 0x00, 0x15, + 0x00, 0xa8, 0x64, 0x19, 0xaf, 0x00, 0x83, 0x2a, 0x72, 0x57, 0x87, 0x02, + 0xd8, 0x94, 0xa0, 0x04, 0xe8, 0xfe, 0x0d, 0x47, 0x40, 0xd0, 0x15, 0x10, + 0x90, 0x8a, 0x6e, 0xd7, 0x0d, 0x80, 0xef, 0xf9, 0x76, 0x1b, 0xd0, 0x39, + 0xa0, 0x5c, 0x01, 0xd6, 0x6b, 0x40, 0x34, 0x5a, 0xd8, 0xbf, 0xdb, 0x07, + 0x00, 0x96, 0xcf, 0x00, 0xba, 0x6b, 0xc0, 0xfc, 0x19, 0x40, 0x7f, 0x0d, + 0xc8, 0x5f, 0x0a, 0x18, 0xae, 0x01, 0xd3, 0x67, 0x00, 0xe1, 0x1a, 0x30, + 0x7e, 0x06, 0x10, 0x1f, 0x00, 0x68, 0x2e, 0x89, 0x7c, 0x05, 0x48, 0x57, + 0x80, 0xed, 0x0e, 0xc0, 0xf7, 0xb6, 0x28, 0xe0, 0xc0, 0x1f, 0xd7, 0x00, + 0xe5, 0xdd, 0x03, 0x4b, 0x19, 0xa0, 0x32, 0x5d, 0x0b, 0x7b, 0xb9, 0x21, + 0x5b, 0xb6, 0x30, 0x10, 0xa6, 0x72, 0x02, 0x38, 0x27, 0x77, 0x26, 0x04, + 0x95, 0xf9, 0x6f, 0x00, 0x66, 0x9e, 0xab, 0x3c, 0x30, 0x1d, 0x90, 0x2b, + 0xa0, 0xb9, 0x00, 0x64, 0x02, 0xa6, 0x03, 0x00, 0x82, 0xcc, 0x9d, 0x6c, + 0x28, 0xea, 0xdc, 0x38, 0xd3, 0x3e, 0xdb, 0x11, 0xd0, 0x57, 0x91, 0x33, + 0x75, 0xeb, 0xd6, 0xa7, 0x9f, 0xa4, 0x89, 0x37, 0xa1, 0x76, 0x25, 0x79, + 0xaa, 0xd0, 0x10, 0x67, 0x24, 0x32, 0x5a, 0x1a, 0xb6, 0x8c, 0x11, 0xdf, + 0xd9, 0x97, 0xbd, 0xaf, 0xa3, 0x70, 0x7a, 0x81, 0xd4, 0x70, 0xe3, 0xc1, + 0xd6, 0xb2, 0xb1, 0x7b, 0x25, 0x40, 0x69, 0xa8, 0x21, 0x17, 0x12, 0x49, + 0xf0, 0x07, 0x92, 0xbc, 0x33, 0x57, 0x69, 0x92, 0x1b, 0x6e, 0xb6, 0x4f, + 0x3b, 0x68, 0x6a, 0x61, 0xd2, 0x2d, 0x36, 0xfb, 0xde, 0x7c, 0xa7, 0x6c, + 0x5a, 0x9f, 0x20, 0x3b, 0x14, 0x14, 0xd4, 0x9f, 0x41, 0x99, 0xa0, 0xed, + 0xd3, 0xa0, 0xc1, 0x41, 0x37, 0xd0, 0xc3, 0x38, 0x9e, 0xcd, 0x42, 0x10, + 0x50, 0xeb, 0xa0, 0x80, 0xa9, 0x0a, 0x68, 0x3c, 0x98, 0x2f, 0x9d, 0xea, + 0xf1, 0xd2, 0x10, 0x64, 0xbc, 0x31, 0x1a, 0x5b, 0xdb, 0x79, 0x65, 0xcc, + 0x30, 0x29, 0x52, 0x45, 0x4d, 0xe7, 0x34, 0x9b, 0x49, 0xd8, 0xab, 0x65, + 0x3c, 0x2a, 0x55, 0xb3, 0x62, 0x10, 0xac, 0x9d, 0xed, 0x5a, 0x14, 0xbb, + 0x3a, 0xdb, 0x56, 0x3d, 0xb5, 0x1d, 0xd2, 0x78, 0xec, 0x68, 0x3b, 0xf4, + 0xcc, 0xb8, 0x61, 0x10, 0x03, 0x1e, 0x9b, 0x8d, 0xdd, 0xab, 0xac, 0x13, + 0xb9, 0xc7, 0x95, 0x79, 0x82, 0xf3, 0x99, 0xf3, 0xe8, 0xe0, 0xea, 0x3f, + 0x55, 0x05, 0x44, 0x5e, 0x8b, 0xec, 0xde, 0x97, 0x6a, 0x17, 0x54, 0x2b, + 0x55, 0xb1, 0xcb, 0xa4, 0x57, 0xc7, 0xe0, 0x48, 0x55, 0x8a, 0x67, 0xbb, + 0xb5, 0xc4, 0x14, 0x42, 0x31, 0xff, 0x77, 0x36, 0xcf, 0x4a, 0x40, 0xa9, + 0x98, 0x2b, 0x31, 0x9b, 0x4b, 0xd7, 0x69, 0x2c, 0xb5, 0x98, 0xe6, 0x32, + 0x91, 0x50, 0x7a, 0x28, 0x8a, 0x28, 0x24, 0xfa, 0xa8, 0x67, 0xe5, 0xd6, + 0x8b, 0xbb, 0xa2, 0x21, 0x90, 0xa4, 0xce, 0x51, 0x27, 0xbe, 0xc7, 0x36, + 0x28, 0xd1, 0x77, 0xaf, 0x7a, 0x77, 0x7e, 0x73, 0x2f, 0x61, 0xeb, 0xb5, + 0xd7, 0x10, 0xeb, 0x4f, 0x1a, 0xbe, 0x9d, 0x7b, 0xf1, 0xb4, 0x97, 0x4e, + 0xef, 0xbb, 0xfb, 0x2e, 0xbb, 0x6b, 0xbc, 0x0c, 0xe2, 0x52, 0xcb, 0x1c, + 0x47, 0xf5, 0xbe, 0x65, 0x93, 0x86, 0xb2, 0x24, 0x0d, 0x7c, 0x64, 0x03, + 0x29, 0xa2, 0xa5, 0xa8, 0x06, 0x7e, 0x27, 0xce, 0xbb, 0x4e, 0x4a, 0x43, + 0xe2, 0x79, 0x9f, 0xfa, 0x47, 0xbd, 0xb5, 0x71, 0x7c, 0x96, 0xa9, 0xeb, + 0xb9, 0x0e, 0xd3, 0xd3, 0x22, 0xc7, 0xb6, 0x23, 0x30, 0x4f, 0xf5, 0x56, + 0x93, 0x79, 0x97, 0x03, 0x3c, 0xac, 0xd2, 0xc0, 0x9d, 0x17, 0x42, 0xac, + 0x20, 0x1e, 0x1d, 0xb6, 0x4c, 0x9e, 0x5c, 0x8d, 0x73, 0xa3, 0x72, 0x94, + 0x79, 0x75, 0x8b, 0x81, 0x82, 0x82, 0x0a, 0xbc, 0x49, 0x9c, 0xf7, 0xb2, + 0x5e, 0x35, 0x02, 0x74, 0xf1, 0x30, 0x3d, 0x74, 0x17, 0x55, 0x84, 0xa3, + 0x45, 0x2b, 0x8a, 0x04, 0xff, 0x2d, 0x8c, 0x5d, 0x62, 0xc1, 0xe7, 0x1d, + 0x1b, 0x25, 0xb0, 0x31, 0x3b, 0xc8, 0xfa, 0x7e, 0x34, 0xb7, 0xcf, 0x46, + 0x99, 0x76, 0x78, 0x4d, 0x87, 0x4f, 0x22, 0x1f, 0xa6, 0x14, 0x46, 0xa5, + 0x7a, 0x51, 0x33, 0xca, 0x18, 0x60, 0xd5, 0x40, 0x94, 0x29, 0x87, 0x54, + 0x94, 0x0c, 0x12, 0x8d, 0x99, 0xc1, 0x85, 0x43, 0x31, 0xd9, 0xd9, 0x97, + 0x29, 0x4e, 0x85, 0xc7, 0x12, 0x76, 0x77, 0xf5, 0xa3, 0xb8, 0xbf, 0xcf, + 0xb5, 0xb1, 0xca, 0x5d, 0x5f, 0xee, 0x33, 0xd7, 0xc6, 0x8f, 0x12, 0x3d, + 0x17, 0xaf, 0xf4, 0xc7, 0x7d, 0x57, 0x67, 0xd9, 0xe3, 0x41, 0x1b, 0x8b, + 0x04, 0xc8, 0x9f, 0xec, 0x9b, 0xa2, 0x8d, 0x6e, 0xdf, 0x8a, 0x39, 0xae, + 0xd6, 0xc8, 0x71, 0x91, 0x90, 0xf7, 0xa8, 0x0d, 0x65, 0x5d, 0xf1, 0xf2, + 0x82, 0x92, 0x7e, 0x54, 0xb5, 0xde, 0x99, 0xa6, 0x98, 0x54, 0xb8, 0x07, + 0xe5, 0x3a, 0xb5, 0xb7, 0xd4, 0x63, 0x15, 0x0b, 0x68, 0xd6, 0x78, 0x8f, + 0xf0, 0xfb, 0xbe, 0x2c, 0x61, 0xaa, 0x59, 0xbf, 0x0d, 0xd6, 0x75, 0xd2, + 0x5d, 0x08, 0x6a, 0xc8, 0xa9, 0x46, 0x9b, 0x71, 0xa2, 0xdd, 0xfb, 0x47, + 0x67, 0x04, 0xb1, 0x31, 0x9d, 0xed, 0x92, 0xe9, 0xc4, 0xf6, 0x38, 0x8b, + 0xd6, 0xf8, 0xf0, 0x62, 0xf0, 0x56, 0x07, 0x0f, 0x97, 0x83, 0x3f, 0xd1, + 0xff, 0x6b, 0x76, 0x09, 0x24, 0x4f, 0xd2, 0xed, 0xd5, 0x58, 0xc0, 0xac, + 0x9a, 0xa3, 0x37, 0x4a, 0xdc, 0xe8, 0xf2, 0x47, 0xcd, 0xe5, 0x7e, 0x17, + 0xa7, 0x23, 0x34, 0xfe, 0x8d, 0x81, 0x8d, 0x89, 0xb4, 0xef, 0xc6, 0x01, + 0xdd, 0xc5, 0x1e, 0x9d, 0xfb, 0x1a, 0x86, 0x0b, 0xbc, 0x3e, 0x87, 0xe3, + 0xd7, 0x1a, 0x16, 0x6c, 0xc0, 0x3c, 0x2a, 0x0f, 0xb9, 0x23, 0x4b, 0x75, + 0xa4, 0x59, 0xc7, 0x79, 0xea, 0xe1, 0x12, 0x55, 0x3c, 0xc0, 0x98, 0x4d, + 0x12, 0x81, 0xd8, 0x34, 0xb0, 0xb9, 0x18, 0x48, 0xad, 0x96, 0x02, 0x3b, + 0xcd, 0xb7, 0xbf, 0xa8, 0x72, 0xd4, 0x20, 0xbc, 0xdd, 0xa1, 0x58, 0xc0, + 0x32, 0xf7, 0xb8, 0x9d, 0xcc, 0x26, 0x63, 0x67, 0xb6, 0x9b, 0x0c, 0x6b, + 0xb3, 0x1f, 0x78, 0x14, 0xa6, 0xeb, 0x6f, 0x5d, 0x2e, 0x6c, 0x2c, 0x08, + 0x7e, 0xdd, 0xbd, 0xb5, 0x7d, 0x5f, 0x2e, 0x01, 0x11, 0xec, 0xdd, 0x71, + 0x8f, 0x78, 0xbb, 0x0b, 0x84, 0xff, 0x0a, 0x80, 0x23, 0x3d, 0x3f, 0x45, + 0x31, 0x1a, 0x23, 0xfd, 0x35, 0x9e, 0xad, 0xe2, 0x39, 0x0c, 0xb3, 0xad, + 0xb0, 0x5e, 0x17, 0xc8, 0xeb, 0xd0, 0x60, 0xa7, 0x80, 0x58, 0x2d, 0x13, + 0x2c, 0xb4, 0x05, 0x87, 0x48, 0xa1, 0xa6, 0xcb, 0x50, 0x6e, 0xcb, 0xf1, + 0x66, 0x64, 0xba, 0x11, 0x5c, 0x38, 0x7a, 0x18, 0x2c, 0x98, 0x15, 0x92, + 0xd4, 0x36, 0x68, 0xcd, 0x7c, 0xe8, 0xcd, 0x64, 0xc0, 0x57, 0xf0, 0xec, + 0xcd, 0x66, 0xe8, 0x68, 0xc7, 0x4c, 0xa7, 0xaf, 0xfe, 0x4d, 0xbf, 0xfa, + 0xef, 0xff, 0x4d, 0xbe, 0x6a, 0xa7, 0xff, 0x50, 0x2f, 0xb2, 0xdd, 0x27, + 0xb6, 0xb6, 0x5b, 0xbf, 0x76, 0xfb, 0xb1, 0xb8, 0x48, 0x16, 0x73, 0x57, + 0xe1, 0x65, 0x0d, 0x9b, 0x68, 0xfc, 0x35, 0x2d, 0x72, 0x60, 0xfd, 0xb6, + 0x1f, 0x11, 0x9b, 0x1d, 0x4d, 0x1f, 0xec, 0xc4, 0x62, 0x92, 0xe8, 0xf8, + 0x59, 0xad, 0x87, 0x05, 0x24, 0xdd, 0xf7, 0x0e, 0x85, 0xe7, 0xe9, 0x66, + 0x36, 0xcc, 0xd4, 0x34, 0x74, 0x32, 0xcc, 0xc6, 0x81, 0x65, 0x14, 0x0a, + 0x2d, 0xfd, 0xc9, 0x0c, 0xae, 0xd1, 0x47, 0xed, 0x87, 0x92, 0x9e, 0xd7, + 0xef, 0x8b, 0x86, 0x05, 0x9e, 0x4a, 0x7f, 0x31, 0x21, 0xc4, 0x0c, 0x98, + 0xcd, 0xa3, 0x9a, 0x94, 0x7e, 0x5b, 0xab, 0x9b, 0xb9, 0x58, 0x76, 0x67, + 0x3f, 0xc0, 0xf5, 0x90, 0x8c, 0x16, 0x50, 0x48, 0xa2, 0x42, 0x72, 0xab, + 0x31, 0x04, 0xf9, 0xaf, 0xe9, 0x56, 0x3d, 0x0e, 0xbb, 0x9d, 0x53, 0x64, + 0x1a, 0x63, 0xf3, 0x9f, 0xfb, 0xe5, 0xbf, 0x34, 0xed, 0x68, 0x44, 0x5c, + 0x60, 0xad, 0xfd, 0xcd, 0xfe, 0x39, 0x5c, 0x6d, 0xcc, 0x1f, 0xf4, 0x99, + 0xff, 0x1a, 0xeb, 0x7d, 0xd4, 0x3f, 0x1f, 0xb5, 0xde, 0x76, 0x17, 0x17, + 0xfb, 0xb3, 0x73, 0x4f, 0x9c, 0xbb, 0xdd, 0x56, 0xc9, 0xc3, 0xce, 0x72, + 0x53, 0x5c, 0xa5, 0x66, 0x60, 0xb7, 0x71, 0x77, 0xdf, 0x54, 0xca, 0x32, + 0x26, 0xfd, 0x32, 0x8e, 0xed, 0xf4, 0xa8, 0x1b, 0x9d, 0x6e, 0xfb, 0xc8, + 0x71, 0xba, 0x68, 0xd3, 0x01, 0x50, 0x08, 0x48, 0xd7, 0xa8, 0xe8, 0x05, + 0x64, 0x8a, 0x72, 0xa5, 0x8c, 0x7d, 0x6d, 0xea, 0x82, 0x1e, 0xb3, 0x7c, + 0x8d, 0xb0, 0x6a, 0xda, 0x2a, 0x31, 0xbf, 0x01, 0xda, 0xf6, 0x3e, 0xda, + 0xf9, 0x8c, 0x36, 0xdc, 0x47, 0x7b, 0x26, 0xc2, 0xdf, 0x0d, 0xed, 0xf2, + 0x05, 0x68, 0xc7, 0x33, 0xda, 0xf2, 0x70, 0xb4, 0xc3, 0xdf, 0x0c, 0xed, + 0xf4, 0x70, 0xb4, 0x57, 0xfc, 0xd7, 0x5c, 0x67, 0xbb, 0xe5, 0xbc, 0x99, + 0x71, 0x2d, 0x80, 0x0b, 0x40, 0xaf, 0xdb, 0x21, 0x44, 0xa5, 0xd7, 0xb5, + 0xa6, 0xd0, 0xa2, 0xc9, 0xa8, 0x07, 0x65, 0x7a, 0x08, 0x1f, 0xb2, 0x71, + 0x33, 0xc2, 0x36, 0x2b, 0x06, 0xd4, 0x74, 0x1d, 0xd4, 0xf4, 0x8a, 0x01, + 0x5a, 0x01, 0xa5, 0x49, 0xbe, 0x62, 0x26, 0x82, 0xa4, 0xb6, 0x55, 0x7d, + 0x0c, 0x66, 0xca, 0x78, 0x6a, 0xf0, 0x9d, 0xde, 0xdf, 0x8e, 0x5b, 0xf5, + 0xe4, 0x9e, 0xc1, 0x27, 0x7d, 0x63, 0x7e, 0x9f, 0xf8, 0x91, 0x8b, 0x06, + 0xaa, 0x98, 0x6d, 0x7c, 0x5e, 0x0a, 0x33, 0x99, 0x53, 0xcd, 0x64, 0x46, + 0xac, 0xc4, 0x26, 0x72, 0x88, 0x47, 0x7d, 0xef, 0xf0, 0xbd, 0xc3, 0xad, + 0x0e, 0x09, 0x1d, 0x82, 0x72, 0x94, 0x66, 0x8f, 0xc9, 0x72, 0x1f, 0xc8, + 0x72, 0xa9, 0xf2, 0xe4, 0x04, 0x9e, 0xb4, 0x7c, 0x36, 0x12, 0x33, 0x52, + 0x97, 0x41, 0xae, 0xd6, 0xbc, 0xf6, 0x0f, 0x45, 0x43, 0x1a, 0x9a, 0xdb, + 0xd6, 0xfb, 0x50, 0x2e, 0x20, 0x40, 0xc5, 0x05, 0x27, 0xd8, 0x75, 0xa2, + 0x64, 0xe1, 0x68, 0x5d, 0x5c, 0xf4, 0xf0, 0x45, 0x95, 0xcd, 0x01, 0x2b, + 0x70, 0xe1, 0x8d, 0x58, 0x49, 0x8f, 0xab, 0xf6, 0x5c, 0x9a, 0x43, 0xc5, + 0x44, 0xaa, 0x45, 0x05, 0x0c, 0x50, 0x8f, 0xa6, 0x31, 0x8a, 0x99, 0x0d, + 0x13, 0x82, 0x9d, 0xfa, 0x3d, 0x62, 0x6e, 0xab, 0x05, 0x89, 0x54, 0x97, + 0xa8, 0xe1, 0xaa, 0x01, 0x22, 0x99, 0x96, 0xae, 0x4a, 0x0f, 0x70, 0xd8, + 0xcd, 0x8f, 0xf9, 0xa8, 0xad, 0xfd, 0x74, 0xff, 0x7f, 0x93, 0x3b, 0xcc, + 0x56, 0x11, 0x81, 0x3c, 0xfe, 0x8a, 0x70, 0x62, 0x90, 0x75, 0x0f, 0xd8, + 0x95, 0xc9, 0xe2, 0x02, 0xad, 0xcf, 0xf1, 0x7b, 0xc7, 0x7f, 0x52, 0x47, + 0xf5, 0x5a, 0x8d, 0x25, 0x3d, 0x6b, 0x51, 0xec, 0x8c, 0x33, 0xee, 0x0d, + 0xc6, 0xf6, 0xc6, 0xe6, 0x2b, 0x25, 0x99, 0x87, 0xd7, 0x06, 0x39, 0x28, + 0x19, 0x02, 0x72, 0xa8, 0xd0, 0x32, 0xc9, 0x59, 0x21, 0x52, 0x4c, 0x5c, + 0x96, 0x85, 0x66, 0xac, 0x9f, 0x5e, 0xc7, 0x53, 0x4b, 0x71, 0x45, 0x0b, + 0x79, 0x4a, 0x18, 0x59, 0xe3, 0x24, 0x3f, 0x01, 0xaf, 0x1c, 0x63, 0x7a, + 0x4a, 0x3e, 0x2d, 0x9a, 0x6a, 0xc0, 0xf1, 0x95, 0x50, 0x5e, 0x35, 0xa0, + 0xbc, 0xaa, 0x65, 0x85, 0x56, 0x40, 0x85, 0x56, 0xa7, 0x51, 0xd4, 0x5c, + 0xcf, 0xb1, 0x41, 0x4a, 0x74, 0xac, 0xbe, 0xe6, 0x23, 0xf5, 0x55, 0x42, + 0x18, 0x97, 0x25, 0x3a, 0x56, 0x78, 0x13, 0x44, 0x3f, 0x59, 0x30, 0xf7, + 0x57, 0x44, 0x7c, 0x37, 0x96, 0xe8, 0xbc, 0x12, 0xdc, 0x86, 0x42, 0xf5, + 0xc1, 0x86, 0x24, 0x6e, 0x79, 0x09, 0xdc, 0x1f, 0x88, 0xbb, 0x31, 0x7b, + 0xc1, 0xdc, 0xeb, 0x96, 0x01, 0xfa, 0xbb, 0x5f, 0xa4, 0xd3, 0x17, 0xdd, + 0xf9, 0x8b, 0x50, 0x2d, 0x8e, 0x05, 0xbb, 0x77, 0xa8, 0x52, 0x6a, 0x6a, + 0x82, 0xa0, 0xe5, 0x17, 0xc9, 0x6d, 0xf8, 0xc2, 0x22, 0xb2, 0xf5, 0x8c, + 0x32, 0x63, 0xe4, 0x6d, 0x94, 0x43, 0x45, 0xb9, 0x5e, 0xa0, 0x0c, 0xe7, + 0x59, 0x82, 0x83, 0xfe, 0x7e, 0x93, 0xe1, 0xc8, 0xab, 0xc9, 0x70, 0xe4, + 0xd5, 0x64, 0x2e, 0x96, 0xd1, 0xde, 0x5b, 0xc6, 0x43, 0xb6, 0xe9, 0x72, + 0x19, 0x87, 0xc9, 0x98, 0xd0, 0xe0, 0xea, 0x01, 0xff, 0x5f, 0x70, 0x45, + 0x9c, 0x44, 0xab, 0xc0, 0xf4, 0xc3, 0xdc, 0xc0, 0x06, 0xe9, 0x12, 0x91, + 0xf8, 0x6a, 0x28, 0x6a, 0xa5, 0x96, 0x4c, 0x6c, 0x6c, 0xea, 0x61, 0xa7, + 0x32, 0x3c, 0xe2, 0xba, 0x70, 0xe2, 0x87, 0xfc, 0xac, 0x61, 0xe4, 0x79, + 0x3a, 0x41, 0x6d, 0xf0, 0xc0, 0xf5, 0x7a, 0x64, 0xd7, 0x12, 0x5e, 0xc7, + 0x89, 0xeb, 0x84, 0xc9, 0x2c, 0x18, 0x20, 0xca, 0x41, 0xeb, 0xd6, 0x5e, + 0xa0, 0xae, 0x53, 0x4e, 0x46, 0x5a, 0x05, 0xaa, 0x24, 0x7e, 0xac, 0x66, + 0xc3, 0x7c, 0xb2, 0x2b, 0xb6, 0xaa, 0x02, 0x5c, 0xb0, 0x39, 0x51, 0xbd, + 0xcf, 0xc1, 0x74, 0x69, 0x31, 0x61, 0x9b, 0x39, 0xab, 0xf6, 0x56, 0x26, + 0x58, 0x1f, 0xd3, 0x21, 0x56, 0xf9, 0x9e, 0x40, 0xcc, 0x0c, 0xfe, 0xa0, + 0x66, 0xd8, 0xc8, 0x40, 0x23, 0xf8, 0x64, 0x02, 0x9f, 0x50, 0x5b, 0x93, + 0x5f, 0x2e, 0x59, 0x31, 0xa0, 0x63, 0x40, 0xc7, 0x68, 0x18, 0x2d, 0x7d, + 0x67, 0xbb, 0x82, 0x60, 0x59, 0xd5, 0xf2, 0x23, 0x3a, 0x4c, 0xe8, 0x90, + 0xd1, 0x21, 0xa3, 0x43, 0xc6, 0x54, 0x69, 0xcb, 0x30, 0xc3, 0x69, 0x80, + 0x08, 0x4c, 0x03, 0x46, 0x74, 0xb8, 0x15, 0x72, 0x48, 0xd6, 0x61, 0x46, + 0x07, 0x78, 0x5d, 0xc5, 0x33, 0xdb, 0x14, 0xff, 0xdb, 0xa2, 0xe8, 0x62, + 0x71, 0x26, 0xd8, 0x41, 0x53, 0x5e, 0x12, 0xea, 0x40, 0xc9, 0xdb, 0x23, + 0xbb, 0x33, 0x05, 0x03, 0x3a, 0x3c, 0x7e, 0x37, 0xef, 0x76, 0xb8, 0xc1, + 0x51, 0xaf, 0x70, 0x1c, 0xbd, 0xc1, 0x11, 0x6a, 0x3c, 0x19, 0xc1, 0x93, + 0x01, 0xfe, 0x57, 0x5f, 0x99, 0x76, 0xa1, 0x83, 0x16, 0x2d, 0xb9, 0x51, + 0x06, 0x84, 0x43, 0x7f, 0x2a, 0xc8, 0x50, 0x24, 0x3b, 0x17, 0x17, 0x9d, + 0x06, 0xec, 0x3a, 0x9d, 0x08, 0xae, 0x13, 0xbe, 0xc8, 0xa7, 0xf3, 0x97, + 0xb5, 0x9d, 0x9b, 0xbb, 0x99, 0xf8, 0x62, 0x82, 0x4c, 0xb2, 0x3c, 0x8a, + 0xd6, 0xfa, 0x52, 0xa5, 0xbb, 0xc1, 0x8a, 0x9c, 0xb1, 0x1a, 0x5e, 0x5d, + 0x49, 0x0e, 0xb4, 0x75, 0x3d, 0xe7, 0xec, 0xc5, 0xb6, 0xf4, 0x93, 0xa5, + 0xb3, 0x6f, 0x9f, 0x6f, 0x97, 0xab, 0x6d, 0x6a, 0x46, 0x3f, 0x0a, 0x5c, + 0xf5, 0x3b, 0xaf, 0x7b, 0x66, 0x9d, 0x85, 0x15, 0x8c, 0x59, 0x03, 0x0d, + 0x3d, 0x71, 0xed, 0x9b, 0xc9, 0xae, 0x13, 0x06, 0x21, 0x02, 0x0e, 0x34, + 0x2e, 0x1b, 0xbe, 0xa6, 0x40, 0x34, 0x89, 0x68, 0x0a, 0xd0, 0x78, 0x1d, + 0x7b, 0x2f, 0xca, 0x63, 0xd4, 0x41, 0x6f, 0x29, 0x51, 0xcf, 0x0c, 0x4d, + 0x2a, 0x51, 0xd1, 0xb4, 0xb2, 0xb9, 0x8b, 0xde, 0x60, 0x61, 0x5c, 0xe0, + 0x9d, 0xa1, 0x09, 0xa2, 0x83, 0xa7, 0xc6, 0xc2, 0xd0, 0x8a, 0x46, 0xf7, + 0xd7, 0x06, 0x05, 0x54, 0x71, 0x2c, 0x11, 0x94, 0x9e, 0x13, 0x0c, 0xb2, + 0x4c, 0x7f, 0xe3, 0x07, 0x43, 0xa3, 0x09, 0xde, 0x49, 0x6e, 0x20, 0x01, + 0x80, 0x51, 0x07, 0x19, 0x9a, 0x20, 0x19, 0x5f, 0xab, 0xb3, 0x9f, 0x0c, + 0x4d, 0x12, 0x07, 0xc9, 0xf4, 0x78, 0x83, 0xa0, 0xc4, 0x6a, 0x3a, 0x7d, + 0xd0, 0xcc, 0xc2, 0xa4, 0x83, 0x0c, 0x4d, 0x42, 0xd6, 0xba, 0x51, 0x87, + 0xc9, 0x02, 0x17, 0x4d, 0x34, 0x56, 0x47, 0xd1, 0xe8, 0xa0, 0xec, 0xa4, + 0x3c, 0x9e, 0x59, 0x2a, 0x32, 0x75, 0xf0, 0x93, 0xc6, 0xde, 0x14, 0xec, + 0x66, 0x09, 0xb9, 0x1e, 0xf9, 0x8c, 0x26, 0x14, 0x4b, 0x55, 0xc8, 0x20, + 0x43, 0xc3, 0x94, 0x47, 0xcb, 0x5c, 0x4a, 0xcf, 0xac, 0xd7, 0x50, 0x90, + 0x26, 0x94, 0x41, 0x43, 0x41, 0x5d, 0x49, 0xa8, 0xd9, 0x5f, 0xab, 0x4a, + 0x40, 0x06, 0x6f, 0x6b, 0x90, 0x26, 0x5b, 0x99, 0xe0, 0x9b, 0x1b, 0x24, + 0xf8, 0xb2, 0xa2, 0x69, 0x71, 0x26, 0xd8, 0x20, 0xcb, 0xae, 0xa8, 0x0d, + 0xbb, 0xea, 0x20, 0x45, 0x13, 0xed, 0xb8, 0x9a, 0xe9, 0x2c, 0xfe, 0xd5, + 0x3e, 0x60, 0xd6, 0x89, 0x22, 0x34, 0x42, 0xde, 0x50, 0x1b, 0xe2, 0x85, + 0x7e, 0xb1, 0x78, 0xd1, 0x87, 0xc9, 0x54, 0x43, 0xdd, 0xe7, 0xe5, 0xd6, + 0xff, 0x18, 0x34, 0xc1, 0x48, 0x43, 0x87, 0x1f, 0x05, 0x13, 0xee, 0xf8, + 0x6f, 0x67, 0x6b, 0x42, 0xa2, 0x04, 0xf2, 0xa1, 0x55, 0x91, 0xbf, 0x05, + 0x82, 0x11, 0xf3, 0x9d, 0xb0, 0x80, 0x8c, 0x15, 0xb1, 0xf2, 0x87, 0x8a, + 0x93, 0xa5, 0x60, 0x1b, 0xa9, 0x42, 0x32, 0x8d, 0xb8, 0x4e, 0x40, 0x94, + 0x81, 0x68, 0x06, 0xa2, 0x05, 0x88, 0x56, 0x20, 0xda, 0x48, 0x4d, 0x22, + 0x1a, 0x8f, 0x88, 0xbe, 0x6c, 0x26, 0xd9, 0x66, 0x12, 0x21, 0xda, 0x38, + 0x96, 0xe4, 0x14, 0x92, 0x63, 0x49, 0x6c, 0x49, 0x39, 0x96, 0xde, 0x97, + 0x83, 0xc7, 0x77, 0xb6, 0xe9, 0xfc, 0xa9, 0x02, 0x10, 0x73, 0x3b, 0x13, + 0x33, 0x83, 0x98, 0x7c, 0x2e, 0x61, 0x05, 0x82, 0x8d, 0x1e, 0xe2, 0x93, + 0x62, 0x31, 0x9c, 0x9f, 0x60, 0xf3, 0x65, 0x3a, 0x86, 0xc9, 0x44, 0x7f, + 0x8d, 0x66, 0x62, 0xee, 0xca, 0xd3, 0xb2, 0x85, 0x43, 0xf5, 0x0b, 0xd5, + 0xd4, 0xfb, 0x05, 0x9e, 0xd9, 0x3b, 0x1c, 0xd0, 0x0b, 0x4e, 0xf2, 0x4b, + 0x5b, 0x60, 0x38, 0x99, 0x5d, 0xb4, 0x2a, 0x0e, 0xe6, 0x17, 0xcd, 0x0f, + 0x1e, 0x5b, 0xdb, 0x1f, 0x8e, 0xa0, 0x54, 0xdb, 0x62, 0x84, 0x6d, 0x31, + 0xe1, 0x64, 0xce, 0x35, 0x1a, 0xa5, 0x94, 0x33, 0xf7, 0x94, 0x0e, 0xed, + 0x73, 0xd8, 0x1c, 0xaf, 0xb0, 0xf6, 0x37, 0x58, 0xfb, 0xfb, 0x6a, 0x83, + 0x58, 0x01, 0x32, 0x6c, 0x91, 0x15, 0xb6, 0x88, 0x39, 0xd5, 0x46, 0x44, + 0x8d, 0x7f, 0x3d, 0x35, 0xcd, 0x6c, 0x0f, 0x10, 0xf4, 0xba, 0x0b, 0xaa, + 0x9c, 0xc5, 0x04, 0x51, 0x57, 0x66, 0x1f, 0x90, 0x87, 0xba, 0x7d, 0x94, + 0x81, 0x85, 0xfb, 0xee, 0x96, 0x47, 0xc2, 0xfe, 0x07, 0x5c, 0x2f, 0x63, + 0x07, 0xdb, 0x29, 0xb8, 0xb0, 0xc1, 0x18, 0xdf, 0x6a, 0x08, 0x5d, 0x31, + 0x1c, 0x0d, 0x90, 0xb6, 0x80, 0xa4, 0xc1, 0xb9, 0x1a, 0x8a, 0xa2, 0x39, + 0x90, 0x10, 0x24, 0xbd, 0xf0, 0x58, 0x3a, 0x50, 0xec, 0x92, 0xc4, 0xf9, + 0x7e, 0x74, 0x9e, 0x93, 0xc5, 0xea, 0xce, 0x31, 0xc4, 0xed, 0x1c, 0x64, + 0x3c, 0x46, 0x21, 0x95, 0x04, 0x2d, 0xb9, 0x37, 0x99, 0xc7, 0xb2, 0x04, + 0x13, 0xa2, 0x6d, 0x40, 0x1c, 0xe5, 0x49, 0xb1, 0x73, 0x6b, 0xc6, 0xfd, + 0x57, 0xc6, 0x74, 0x02, 0xac, 0x43, 0x86, 0x39, 0xde, 0x31, 0x9a, 0x93, + 0x8e, 0xe1, 0x8e, 0x5e, 0x23, 0x10, 0xdc, 0x63, 0x3d, 0x9c, 0x57, 0x1a, + 0xa0, 0x11, 0xcc, 0xf0, 0xf2, 0x9e, 0x3d, 0x4c, 0x4e, 0x24, 0x07, 0xde, + 0xf5, 0x4d, 0xbe, 0x4e, 0x87, 0xde, 0xe4, 0x32, 0xe3, 0x44, 0x17, 0x52, + 0x68, 0x99, 0xcd, 0x00, 0x1a, 0x89, 0x4f, 0x9a, 0xcd, 0x60, 0x56, 0xe2, + 0x7d, 0x54, 0xca, 0x46, 0x7b, 0xa0, 0x94, 0x94, 0x75, 0x9b, 0x77, 0xc4, + 0x75, 0x8e, 0xf8, 0x82, 0xf9, 0xeb, 0x27, 0xfc, 0x02, 0xd7, 0x15, 0x1d, + 0x14, 0xff, 0x2f, 0xf8, 0x5f, 0xac, 0x1d, 0xf1, 0x92, 0x35, 0xa6, 0x1c, + 0x8b, 0xda, 0x19, 0x42, 0x28, 0x65, 0xfa, 0xf7, 0xc5, 0x0c, 0x15, 0xa3, + 0x64, 0x44, 0x58, 0xa8, 0xaf, 0x0f, 0x6a, 0x7d, 0xac, 0x11, 0xa6, 0xa5, + 0xc6, 0x8d, 0xfa, 0x1a, 0x50, 0xfa, 0x58, 0x05, 0x75, 0x39, 0x71, 0xb4, + 0x4b, 0xf4, 0xb7, 0xea, 0xe0, 0x61, 0xb0, 0xe7, 0xe0, 0x87, 0xb7, 0x60, + 0x18, 0x86, 0xc3, 0x36, 0x72, 0xd4, 0x33, 0x70, 0xd4, 0x1b, 0x18, 0x75, + 0xce, 0x72, 0xe4, 0x49, 0x6a, 0x87, 0x17, 0x60, 0xda, 0x8c, 0xa3, 0xc1, + 0xb9, 0xba, 0x37, 0x4b, 0xac, 0xa8, 0x37, 0x37, 0xd8, 0x91, 0x29, 0xa4, + 0x96, 0xdd, 0x13, 0x0d, 0xbf, 0xf8, 0x71, 0x90, 0xac, 0xe1, 0xe7, 0x06, + 0xb6, 0x35, 0xe3, 0x8b, 0x05, 0x5f, 0xac, 0x90, 0x51, 0x0f, 0x3e, 0xd2, + 0xf7, 0x3a, 0xe8, 0xc5, 0x80, 0xa5, 0x2a, 0x8a, 0xb9, 0xa9, 0x6e, 0x28, + 0xc5, 0xdf, 0xbd, 0xcf, 0x6a, 0x7e, 0xb8, 0x67, 0xfc, 0x0d, 0x3f, 0x78, + 0x30, 0x7a, 0x3b, 0xc8, 0xc5, 0xf9, 0x62, 0xc9, 0xe0, 0xdb, 0x97, 0xe5, + 0x70, 0x29, 0xb7, 0x2f, 0x96, 0xf5, 0xbc, 0x7d, 0xc9, 0xf5, 0xd2, 0xdd, + 0xb9, 0x4c, 0x8d, 0x9f, 0x42, 0x57, 0x97, 0xb1, 0x4a, 0xf9, 0xd5, 0xa5, + 0xa9, 0x97, 0x70, 0xfb, 0xf2, 0xd8, 0x35, 0x1f, 0x97, 0xfe, 0x88, 0x35, + 0x1f, 0x96, 0xfe, 0x98, 0x35, 0x4f, 0x9f, 0x58, 0xf3, 0x61, 0xe9, 0x8f, + 0x58, 0xf3, 0xf6, 0xfb, 0xd6, 0xbc, 0xfc, 0xbe, 0x35, 0xe7, 0xdf, 0xb7, + 0xe6, 0xf1, 0xfe, 0x9a, 0xcb, 0xe7, 0xd7, 0xec, 0x25, 0xef, 0x72, 0x27, + 0x3f, 0xa9, 0x3b, 0x88, 0xac, 0xca, 0x70, 0x53, 0x65, 0xb9, 0x85, 0xc4, + 0x5c, 0x06, 0x5a, 0x16, 0x9e, 0xd4, 0xe7, 0x94, 0xc4, 0x55, 0x1e, 0x3d, + 0x9f, 0xec, 0x90, 0x42, 0x83, 0x35, 0x53, 0x23, 0xf1, 0xa4, 0x6e, 0x60, + 0xa4, 0x4c, 0xc9, 0x5c, 0x46, 0xb1, 0x6c, 0xa8, 0xd3, 0x34, 0x4c, 0xf2, + 0xc3, 0x59, 0xe9, 0x45, 0xd3, 0x4a, 0x1b, 0x73, 0x2f, 0x2f, 0x70, 0x52, + 0xcf, 0x55, 0x6d, 0xae, 0xd5, 0x32, 0xb5, 0x0e, 0x13, 0x3a, 0xcc, 0x5f, + 0xdc, 0xe1, 0xde, 0x2d, 0xcc, 0x7e, 0x63, 0x07, 0x0d, 0x13, 0x74, 0x6a, + 0x6f, 0xb8, 0x66, 0xd6, 0x49, 0xf6, 0xf4, 0x86, 0x61, 0xd0, 0x4d, 0xd1, + 0x12, 0xab, 0x73, 0x6f, 0x87, 0xe0, 0xd2, 0x9a, 0x1b, 0xbc, 0xe9, 0x4e, + 0x05, 0xd3, 0xcc, 0xe2, 0xe9, 0x4c, 0x42, 0xfa, 0x58, 0x35, 0xf3, 0xfc, + 0x7b, 0x34, 0x73, 0xb2, 0xcd, 0x33, 0x54, 0x63, 0x35, 0x65, 0x89, 0x21, + 0xdc, 0xd9, 0xcd, 0xf6, 0xce, 0x6e, 0x8a, 0xdd, 0x25, 0x93, 0x94, 0xdd, + 0x94, 0xa0, 0x84, 0x04, 0x45, 0x66, 0x1c, 0x40, 0x0b, 0xec, 0x55, 0x6e, + 0x96, 0x31, 0xcb, 0x99, 0x92, 0x3c, 0xe3, 0x3c, 0xa1, 0xf3, 0xac, 0x86, + 0xed, 0xa6, 0x8b, 0x53, 0xf2, 0xe2, 0x9c, 0x75, 0xe3, 0x3c, 0x61, 0x89, + 0x01, 0x7c, 0xd9, 0xff, 0x71, 0x1d, 0x22, 0x3a, 0x5c, 0xc6, 0x16, 0xa7, + 0x6a, 0xd7, 0xc7, 0x8b, 0x55, 0xe8, 0x32, 0x7f, 0xb9, 0x60, 0x98, 0x82, + 0xf7, 0x1b, 0x44, 0x54, 0x87, 0x04, 0x18, 0xf6, 0x30, 0x50, 0x25, 0x88, + 0x23, 0x0c, 0x23, 0xc6, 0x7c, 0xcd, 0xa3, 0xe3, 0x3a, 0x05, 0xec, 0x01, + 0x3d, 0x3c, 0xdf, 0x0b, 0xfb, 0xc2, 0xb3, 0x81, 0x3c, 0x57, 0x47, 0xdf, + 0x4d, 0xcf, 0x23, 0x46, 0x1b, 0x68, 0x4b, 0x5c, 0x9a, 0x1b, 0xb1, 0xc5, + 0x54, 0x63, 0xcf, 0x66, 0xab, 0x76, 0x88, 0x0a, 0x48, 0xb0, 0x09, 0x8a, + 0xc3, 0x4c, 0xcd, 0x84, 0x11, 0xd1, 0x50, 0xdb, 0xcd, 0x9a, 0x8a, 0xfb, + 0xd2, 0x7d, 0x5a, 0xc8, 0x63, 0x23, 0x78, 0x8a, 0xcf, 0x7f, 0x5a, 0xa4, + 0xb5, 0x58, 0x28, 0xc9, 0xe6, 0x7f, 0x65, 0xe4, 0x1f, 0x56, 0x2e, 0x82, + 0xe6, 0x25, 0xdc, 0x62, 0xdd, 0x88, 0xa2, 0x59, 0x68, 0xf6, 0xa8, 0xab, + 0xdc, 0x99, 0x4d, 0xda, 0xb8, 0x3d, 0x24, 0x12, 0x76, 0x41, 0xfe, 0x00, + 0xff, 0xaa, 0xa5, 0xd9, 0x1f, 0xe1, 0xc5, 0x72, 0xc3, 0xdc, 0xf6, 0x0c, + 0xd8, 0xc9, 0xb6, 0x9a, 0xf7, 0x16, 0x3e, 0x3e, 0xd8, 0xfd, 0xbf, 0x55, + 0x23, 0xb4, 0x30, 0x79, 0x99, 0x2f, 0xf2, 0x9f, 0x05, 0x28, 0x4a, 0x95, + 0xb5, 0x8d, 0xd2, 0x7a, 0x50, 0xa9, 0x01, 0xdb, 0xe7, 0xdb, 0x95, 0xb0, + 0x3d, 0xc3, 0xcd, 0x14, 0xaa, 0x5b, 0xbc, 0x0f, 0x9c, 0xc5, 0x2b, 0x28, + 0x37, 0xc6, 0xc1, 0xcd, 0x3f, 0xe9, 0x6b, 0xfc, 0x7b, 0x2d, 0x48, 0x1a, + 0x3d, 0x03, 0x6f, 0xbe, 0x83, 0xeb, 0x25, 0xde, 0xa9, 0x48, 0xb7, 0x26, + 0xa7, 0x7e, 0x28, 0x1a, 0x03, 0xb0, 0x0f, 0x6d, 0xfe, 0x8c, 0xeb, 0x8a, + 0xad, 0xd2, 0x5e, 0xba, 0xde, 0x88, 0x4d, 0x24, 0x3b, 0x7b, 0x6e, 0xc9, + 0xf9, 0x5b, 0x70, 0xfd, 0x0a, 0xbe, 0x5e, 0x19, 0x2b, 0x0f, 0x38, 0x42, + 0x5e, 0x80, 0x31, 0x32, 0xa2, 0x0e, 0xf6, 0xdc, 0x5c, 0xa7, 0xd5, 0x14, + 0xf4, 0x70, 0x3c, 0xc0, 0x9d, 0xc1, 0xa0, 0x1e, 0x61, 0x18, 0x2f, 0x58, + 0x99, 0x67, 0xdf, 0x55, 0x66, 0xe3, 0x77, 0xa1, 0x60, 0x40, 0x68, 0x8b, + 0xd8, 0x91, 0x9f, 0x41, 0xe7, 0x85, 0x74, 0xbe, 0x62, 0xad, 0x74, 0x3c, + 0x55, 0x3a, 0x7b, 0x5a, 0xa8, 0x86, 0x54, 0x82, 0xba, 0x50, 0x2a, 0xb5, + 0x73, 0x82, 0x53, 0x3a, 0x40, 0x9c, 0x7f, 0x10, 0x9c, 0xce, 0xe0, 0xa6, + 0x25, 0x54, 0xdf, 0x4a, 0x58, 0x67, 0x84, 0xa1, 0x5f, 0x28, 0x23, 0xca, + 0x78, 0xd8, 0x91, 0xec, 0x81, 0x01, 0x6c, 0x00, 0xc5, 0xac, 0x5a, 0xfc, + 0xc6, 0x90, 0x9e, 0x37, 0xa4, 0xa4, 0x92, 0x11, 0x0f, 0xaa, 0xd1, 0xcf, + 0x8f, 0x0e, 0x1d, 0x94, 0x77, 0x19, 0xb6, 0x67, 0xa2, 0x69, 0x81, 0xa5, + 0x31, 0x36, 0x8f, 0x33, 0xf5, 0xe7, 0xe6, 0xeb, 0x7e, 0x86, 0xcf, 0xdf, + 0xf2, 0xd6, 0xe7, 0x41, 0x6f, 0x45, 0x3b, 0x03, 0xbf, 0xf2, 0xd4, 0xef, + 0x7f, 0xd2, 0xe3, 0x16, 0x55, 0xdf, 0x20, 0xb7, 0x34, 0xc9, 0xfe, 0xd1, + 0xed, 0xbc, 0xb2, 0x48, 0x51, 0xf4, 0xae, 0x35, 0x5f, 0x0b, 0x13, 0xcd, + 0xf2, 0xc6, 0xba, 0x59, 0x5e, 0x4a, 0x37, 0xcb, 0xdb, 0xe4, 0x46, 0xbb, + 0xe1, 0xbf, 0xe9, 0xdf, 0xff, 0xaa, 0x7f, 0xff, 0x53, 0xfe, 0x74, 0x96, + 0xf5, 0xc9, 0x3a, 0x9b, 0x49, 0x00, 0xb0, 0x50, 0x05, 0x80, 0xc4, 0x8a, + 0x88, 0x53, 0xc4, 0xa9, 0x30, 0x33, 0xe9, 0x98, 0x50, 0x2e, 0x83, 0x93, + 0x4a, 0x99, 0xc4, 0x42, 0x3d, 0xc6, 0xa4, 0x96, 0x3d, 0xb3, 0x92, 0x15, + 0x55, 0x1b, 0x16, 0x19, 0xb6, 0x48, 0x84, 0x99, 0x32, 0x96, 0x1c, 0xea, + 0x7c, 0xa0, 0x59, 0x2e, 0xca, 0x8e, 0x0b, 0x53, 0xff, 0x66, 0xd1, 0xeb, + 0x1d, 0x5b, 0xd8, 0xb6, 0x36, 0x29, 0x99, 0xe6, 0x40, 0x3b, 0x29, 0xd3, + 0x4b, 0x4d, 0x30, 0x2f, 0x0b, 0x1e, 0x64, 0x34, 0x1e, 0x37, 0x7b, 0x36, + 0xdb, 0x04, 0x74, 0x5e, 0x0b, 0xe2, 0x62, 0x51, 0x3b, 0x85, 0xc2, 0xd8, + 0xda, 0x0a, 0xb7, 0xd7, 0x2c, 0x6a, 0xe4, 0xd4, 0x0a, 0x15, 0xfd, 0xc8, + 0x8a, 0x05, 0xb3, 0xc7, 0x03, 0x4e, 0x5f, 0x0b, 0x92, 0x99, 0x01, 0x2e, + 0x84, 0xb4, 0xf7, 0xcf, 0x35, 0x38, 0x69, 0x95, 0xd6, 0xff, 0xa1, 0x7f, + 0xff, 0x87, 0xfe, 0xfd, 0xf7, 0xc7, 0x6d, 0xfa, 0xdd, 0xcf, 0xf2, 0x75, + 0x78, 0xf1, 0xe6, 0xe7, 0x91, 0x52, 0xf7, 0x99, 0xcf, 0xa4, 0x94, 0xc1, + 0x13, 0x1f, 0x4a, 0xab, 0x3e, 0xeb, 0xdd, 0x8c, 0x19, 0x2d, 0xea, 0x60, + 0x1e, 0xbb, 0xe5, 0x4c, 0xb7, 0xa6, 0x6e, 0x3f, 0x8a, 0x27, 0x26, 0x32, + 0x62, 0xe0, 0xa1, 0xba, 0xdc, 0xdd, 0x7e, 0x63, 0x42, 0xfb, 0x7b, 0xc1, + 0x0a, 0x49, 0xc9, 0xb7, 0x8f, 0x85, 0x21, 0xa6, 0xc6, 0xdc, 0xed, 0xed, + 0x8f, 0x7a, 0xe3, 0xa0, 0x93, 0x18, 0x0e, 0x82, 0x31, 0xc0, 0x14, 0x9b, + 0x79, 0x02, 0x45, 0x9c, 0x20, 0x77, 0x05, 0xc3, 0x66, 0x10, 0x74, 0x7e, + 0xba, 0x7f, 0x36, 0xb1, 0x54, 0x18, 0xe8, 0xb7, 0xf2, 0x65, 0x65, 0xda, + 0xf5, 0x2c, 0x18, 0x2b, 0x2b, 0x0d, 0x12, 0x12, 0x8e, 0x0b, 0x13, 0x1d, + 0x14, 0x0c, 0x2b, 0x21, 0x10, 0xda, 0x06, 0x33, 0xf1, 0x94, 0xb4, 0xc6, + 0x86, 0x4a, 0xd3, 0x56, 0x29, 0xfe, 0x6f, 0xdc, 0x62, 0x66, 0x39, 0x42, + 0x3d, 0x03, 0xb6, 0x06, 0x87, 0xc3, 0x08, 0x0e, 0xc8, 0xb0, 0x6f, 0x0e, + 0x39, 0x7a, 0xdd, 0x48, 0xbe, 0xf6, 0xa3, 0xb8, 0x3d, 0x88, 0x73, 0x69, + 0x82, 0x9d, 0x3b, 0x81, 0x81, 0x32, 0xae, 0x8c, 0x78, 0x7b, 0xe4, 0x3b, + 0x5e, 0x5f, 0x85, 0x34, 0x52, 0x6a, 0x23, 0xdb, 0xa6, 0xe5, 0xd6, 0x6d, + 0x41, 0xa8, 0xbd, 0xc0, 0x44, 0x2f, 0xc8, 0x14, 0x33, 0x86, 0xc8, 0x6b, + 0xbc, 0xba, 0x1e, 0xe6, 0x4c, 0x1b, 0xed, 0xf6, 0x35, 0x5d, 0x5d, 0xd7, + 0x7a, 0x55, 0xbb, 0x71, 0x32, 0x3b, 0xc1, 0x94, 0xe1, 0x16, 0x47, 0xbb, + 0xfe, 0xef, 0xa6, 0xa9, 0x71, 0x5a, 0x5e, 0x93, 0x5f, 0xd7, 0x8b, 0x6b, + 0xf1, 0xeb, 0x02, 0xca, 0x9f, 0xaf, 0x3c, 0x7d, 0xf5, 0xda, 0xd7, 0x6b, + 0x86, 0x30, 0xf2, 0x3a, 0x61, 0xc7, 0x26, 0xbb, 0xad, 0x97, 0x89, 0x8c, + 0x76, 0xbb, 0x82, 0xdb, 0x6c, 0x40, 0xcb, 0x2b, 0x93, 0xfe, 0xcb, 0x11, + 0xfd, 0x50, 0x53, 0xff, 0x19, 0x68, 0x26, 0xa0, 0x19, 0xc1, 0x10, 0x47, + 0x34, 0x5d, 0xb9, 0x2c, 0x60, 0xb8, 0x37, 0xbc, 0x5c, 0x0e, 0xe7, 0x6c, + 0x0e, 0x44, 0x98, 0xff, 0xd4, 0xeb, 0x1f, 0xfd, 0xf9, 0xb3, 0xd7, 0xf7, + 0x65, 0x74, 0x88, 0x30, 0x9e, 0xf1, 0x69, 0x0b, 0xb7, 0xc9, 0x3e, 0x5d, + 0xb9, 0x04, 0x98, 0x98, 0x2f, 0x17, 0xfd, 0xcb, 0x11, 0x63, 0x82, 0xfc, + 0x57, 0xfc, 0x50, 0x14, 0xf8, 0xf0, 0x60, 0x59, 0x2f, 0x10, 0x1e, 0x50, + 0xf0, 0xdf, 0xac, 0x8e, 0x09, 0xfc, 0x37, 0x42, 0x60, 0x7d, 0x39, 0x8d, + 0x28, 0xa7, 0x11, 0xd1, 0xff, 0xbd, 0xd6, 0x01, 0x8b, 0xdb, 0xbc, 0x36, + 0x60, 0xb4, 0x99, 0xfa, 0x80, 0xb1, 0xa9, 0x5e, 0x74, 0x5f, 0x2a, 0x27, + 0xf3, 0x9a, 0xb1, 0x58, 0xd6, 0x67, 0x2d, 0x44, 0x8d, 0xf0, 0xdb, 0xe6, + 0x93, 0x05, 0x11, 0x46, 0x4e, 0x6e, 0x69, 0xaa, 0xa8, 0xf0, 0x00, 0x6b, + 0xeb, 0xfa, 0xcd, 0x96, 0x08, 0x3e, 0xa0, 0x61, 0xe8, 0x87, 0xf4, 0xe8, + 0x15, 0xa5, 0x47, 0x00, 0x91, 0x84, 0x1e, 0xea, 0x80, 0x16, 0xfe, 0xba, + 0xef, 0x01, 0xde, 0x74, 0xe9, 0x03, 0x1a, 0x58, 0x6c, 0x13, 0xff, 0xdd, + 0x9b, 0x6e, 0xab, 0xdb, 0x1a, 0xcd, 0xdf, 0x16, 0x8c, 0x88, 0x76, 0x6e, + 0xcc, 0x9a, 0x6b, 0x50, 0x68, 0xc7, 0x96, 0x71, 0xf8, 0x45, 0xf1, 0x16, + 0xf5, 0x39, 0x4f, 0x3b, 0x7d, 0x7a, 0x71, 0x84, 0xec, 0x8d, 0xa1, 0xba, + 0x14, 0x8b, 0x7b, 0x49, 0xc8, 0x09, 0x47, 0x76, 0x14, 0x27, 0x5c, 0xce, + 0x41, 0xbc, 0x75, 0x44, 0x18, 0xe0, 0xb9, 0xfa, 0x2f, 0x63, 0x5f, 0xb3, + 0x89, 0xd6, 0x21, 0x79, 0xb8, 0x43, 0x23, 0x03, 0x13, 0xdf, 0x95, 0x52, + 0x2c, 0x50, 0x82, 0xd7, 0x8f, 0x90, 0xc3, 0x22, 0x5e, 0x4c, 0x12, 0xb0, + 0xd9, 0xad, 0xe6, 0xcd, 0xe9, 0xb3, 0x6b, 0xf8, 0x71, 0x0a, 0x35, 0x86, + 0x21, 0x1d, 0xb6, 0xb6, 0x46, 0xdc, 0xb4, 0x43, 0x8e, 0xb0, 0x1e, 0xcd, + 0x69, 0x53, 0x8f, 0xd7, 0x6a, 0xf9, 0x50, 0x08, 0x93, 0xa3, 0x85, 0x1c, + 0xec, 0x65, 0x4d, 0x8d, 0x04, 0x27, 0xcc, 0xdb, 0x9b, 0x5a, 0x4b, 0xeb, + 0xe4, 0x04, 0xaf, 0xaf, 0x76, 0x40, 0xe9, 0x78, 0xed, 0x30, 0x99, 0x2a, + 0xb7, 0x0e, 0x05, 0xaf, 0xd6, 0x58, 0xd9, 0x61, 0x46, 0x7e, 0x68, 0x34, + 0x02, 0xa5, 0x32, 0x58, 0x3f, 0xbc, 0xb6, 0xa8, 0xb7, 0x0e, 0xc1, 0x42, + 0x50, 0x3a, 0xdb, 0x60, 0x46, 0x4a, 0x66, 0x87, 0x77, 0x66, 0x24, 0xac, + 0x17, 0x1d, 0x3a, 0x6c, 0xbb, 0x77, 0x88, 0x46, 0x8b, 0x63, 0x87, 0x91, + 0x1d, 0xde, 0xdb, 0x8e, 0x2f, 0xa8, 0x75, 0x48, 0x1b, 0x58, 0x02, 0xb5, + 0x0d, 0xfd, 0xee, 0x06, 0x93, 0x25, 0x0e, 0x1d, 0x06, 0xef, 0x30, 0x58, + 0x07, 0x58, 0x28, 0xb9, 0xd1, 0x77, 0x3f, 0x83, 0x67, 0xee, 0x77, 0x98, + 0x6c, 0xd2, 0x78, 0x65, 0xc8, 0x1b, 0x23, 0xc9, 0x84, 0x30, 0xb7, 0x9d, + 0xbd, 0x9f, 0xea, 0xb0, 0x5e, 0x76, 0x08, 0xbb, 0x13, 0x7f, 0xec, 0xd0, + 0x5e, 0x74, 0x58, 0x0e, 0x1d, 0x46, 0xfe, 0x41, 0x07, 0xfd, 0x6e, 0xb1, + 0x2a, 0x33, 0xbc, 0x25, 0xa1, 0x35, 0xe2, 0x2c, 0x49, 0x19, 0xed, 0x5e, + 0x87, 0xfe, 0xdc, 0x41, 0x1e, 0x4a, 0xc5, 0x2b, 0x13, 0xc0, 0x6a, 0xb3, + 0xcf, 0x21, 0x68, 0xbc, 0x40, 0xf9, 0xa3, 0xf5, 0xbd, 0x01, 0x7f, 0xf8, + 0xe6, 0xb5, 0xe7, 0x0e, 0x41, 0x2c, 0xc4, 0xc9, 0xe4, 0x39, 0xe9, 0xae, + 0xad, 0xe8, 0x80, 0x0d, 0x8a, 0x12, 0x46, 0x40, 0x07, 0x61, 0x1a, 0x7d, + 0x20, 0x7b, 0xb5, 0x36, 0x80, 0x1a, 0x06, 0x1c, 0xcc, 0x4a, 0x93, 0x4d, + 0x99, 0xd1, 0x61, 0x51, 0xbd, 0xd2, 0x6a, 0x62, 0x31, 0x33, 0x74, 0xad, + 0x92, 0x07, 0x0d, 0x20, 0x11, 0xb7, 0xad, 0xe1, 0xeb, 0x7c, 0x7b, 0xea, + 0xdd, 0x82, 0xb7, 0xf8, 0x42, 0xd5, 0xea, 0xc4, 0xa3, 0x87, 0x2c, 0xd5, + 0xb0, 0xe5, 0xe4, 0x35, 0xc6, 0xf5, 0xc2, 0xb0, 0x9a, 0x65, 0x2d, 0x21, + 0x2d, 0x04, 0xc2, 0x11, 0xc2, 0x84, 0xf2, 0x0e, 0x10, 0x45, 0x2b, 0xb3, + 0x9e, 0x49, 0xe1, 0x62, 0xef, 0x69, 0xd9, 0x7c, 0x0b, 0x50, 0xed, 0xa2, + 0x4a, 0xcf, 0xa4, 0x3d, 0xc7, 0x02, 0x0f, 0xd5, 0x0a, 0x3a, 0x56, 0x1e, + 0x63, 0x6e, 0xc1, 0x0f, 0x05, 0x46, 0x2d, 0xbd, 0xca, 0x64, 0x3a, 0x3d, + 0x99, 0xb6, 0x5d, 0x68, 0x8b, 0x59, 0x4d, 0x91, 0xbc, 0xff, 0x12, 0xcf, + 0xd3, 0xc6, 0x93, 0xd5, 0xd0, 0x9d, 0xa3, 0x04, 0xf1, 0xe4, 0x87, 0xb5, + 0x07, 0x45, 0xab, 0x9f, 0x70, 0x71, 0xa0, 0xe2, 0xf6, 0xc7, 0xcf, 0x70, + 0x46, 0x99, 0x4e, 0x28, 0xfb, 0x33, 0xca, 0x78, 0x50, 0xee, 0xb8, 0xe7, + 0xe5, 0x2c, 0x0f, 0xe7, 0x09, 0xef, 0x79, 0x39, 0x89, 0xfe, 0x3c, 0x89, + 0x74, 0x1a, 0x51, 0xcf, 0xb4, 0x8a, 0xf2, 0xf2, 0x1e, 0x7e, 0x9e, 0x36, + 0xff, 0x97, 0x28, 0x81, 0xe2, 0xff, 0x70, 0x96, 0x58, 0x58, 0x9a, 0x38, + 0x4b, 0x03, 0x70, 0x2e, 0x83, 0x69, 0xe1, 0x7a, 0x34, 0x25, 0x9e, 0xdc, + 0x9b, 0x4f, 0x02, 0x9f, 0x7c, 0x18, 0x71, 0xe8, 0x50, 0x47, 0xa0, 0x43, + 0x7b, 0xee, 0xe0, 0x23, 0x38, 0xf9, 0x48, 0xc0, 0x61, 0x1d, 0x17, 0x1d, + 0xba, 0x73, 0x87, 0xfe, 0xd4, 0x81, 0x38, 0x2b, 0x39, 0xc2, 0x71, 0x0a, + 0x0e, 0x98, 0x0e, 0xc4, 0xea, 0xcb, 0x71, 0x93, 0x13, 0xbf, 0xec, 0x7d, + 0xa2, 0x89, 0xe3, 0x6f, 0x80, 0x06, 0x07, 0xf9, 0x5d, 0x07, 0xc7, 0x77, + 0x0d, 0x6a, 0x1d, 0x14, 0xb8, 0x25, 0x02, 0x5a, 0xee, 0x81, 0x22, 0x41, + 0x5d, 0xa9, 0x35, 0x2e, 0x87, 0x4c, 0xe3, 0x09, 0xd4, 0x3b, 0x28, 0x71, + 0xd1, 0xbd, 0x13, 0xfc, 0x06, 0xc8, 0x49, 0x33, 0x38, 0x51, 0x9d, 0x7a, + 0x37, 0x40, 0x81, 0x20, 0xdd, 0xca, 0xc5, 0x41, 0xa3, 0x81, 0x9e, 0x11, + 0xf4, 0xc1, 0x40, 0x9d, 0x83, 0xf4, 0xd7, 0x20, 0x26, 0x82, 0xd6, 0x33, + 0x68, 0x60, 0xaa, 0x64, 0xf6, 0xac, 0xed, 0xc0, 0xec, 0xea, 0x6c, 0x33, + 0xbc, 0x01, 0x1a, 0xe4, 0x75, 0x51, 0x28, 0x7b, 0xc8, 0x09, 0x3e, 0xf6, + 0xd6, 0x83, 0x8f, 0xaf, 0x40, 0xad, 0x83, 0x82, 0x9a, 0x12, 0xa3, 0xe9, + 0x03, 0xd3, 0x11, 0x41, 0x03, 0xbf, 0x17, 0xa0, 0x48, 0x50, 0x57, 0x58, + 0x7f, 0x1c, 0x55, 0x7b, 0x4d, 0x16, 0xf1, 0x08, 0x8c, 0xc1, 0xb9, 0xb1, + 0x05, 0x50, 0x52, 0x50, 0x26, 0xc8, 0xa2, 0xbb, 0x78, 0x99, 0x56, 0xaf, + 0x69, 0xc8, 0xad, 0xb1, 0x23, 0x40, 0x41, 0x83, 0x65, 0x26, 0x1d, 0x34, + 0xdf, 0x04, 0x05, 0x8b, 0x57, 0x20, 0x13, 0x9e, 0x68, 0x46, 0xf6, 0x50, + 0xb3, 0x67, 0x50, 0xe7, 0xa0, 0xc8, 0x33, 0xaf, 0x2b, 0x28, 0x39, 0xbe, + 0x05, 0x4a, 0x04, 0xf5, 0x06, 0xda, 0xdc, 0x0a, 0xf2, 0xa3, 0xc5, 0xc3, + 0xd8, 0x85, 0xd9, 0xcc, 0xb2, 0xb9, 0x84, 0xce, 0x2e, 0xcc, 0xb7, 0x40, + 0xad, 0x83, 0x02, 0x5a, 0xcc, 0x7f, 0x96, 0xe5, 0x16, 0x28, 0x12, 0x44, + 0x79, 0xf7, 0x18, 0xbb, 0xc6, 0x8a, 0xb2, 0x4d, 0x8e, 0xe9, 0x81, 0x89, + 0x31, 0x9e, 0xd5, 0x9c, 0x7a, 0xe9, 0xd1, 0xe3, 0x74, 0xd9, 0x6e, 0x81, + 0x0a, 0x8c, 0xe1, 0xec, 0xb1, 0x4a, 0x8f, 0xd9, 0xcc, 0x83, 0x87, 0x94, + 0x1c, 0x14, 0x08, 0x6a, 0x99, 0xe2, 0x5b, 0x02, 0x42, 0x4e, 0x96, 0x42, + 0xbf, 0x04, 0x3d, 0x71, 0x50, 0x24, 0xe8, 0x85, 0x81, 0x98, 0xdd, 0x96, + 0x49, 0xbf, 0xe0, 0x19, 0x67, 0x51, 0x14, 0x01, 0xfd, 0x4c, 0xd0, 0xe6, + 0xa0, 0x37, 0x06, 0x62, 0x1a, 0xb4, 0xd6, 0x3a, 0x59, 0x38, 0x05, 0xa0, + 0xb9, 0x50, 0xa0, 0x0b, 0xe5, 0x12, 0xf4, 0x63, 0x63, 0x66, 0x63, 0xf5, + 0xed, 0xf1, 0xc6, 0xc8, 0x46, 0xae, 0x3b, 0xe7, 0x3b, 0xe1, 0xf4, 0x3f, + 0x37, 0xbc, 0xfc, 0xb5, 0x5c, 0x37, 0xc6, 0x52, 0xac, 0x1c, 0x6a, 0x64, + 0x34, 0x6a, 0x22, 0xfb, 0x4c, 0x85, 0x26, 0x7a, 0x31, 0xcb, 0xd3, 0xaa, + 0x60, 0xf5, 0xcc, 0xd3, 0x46, 0x77, 0xd5, 0x68, 0x0f, 0x8d, 0xf9, 0x4e, + 0xa3, 0xf9, 0xe3, 0x1b, 0xc7, 0x9b, 0x5e, 0x4f, 0xec, 0xd4, 0xe8, 0xb8, + 0x40, 0x6f, 0xf4, 0x87, 0x25, 0x8f, 0x24, 0x82, 0x35, 0x22, 0x4a, 0x35, + 0x1a, 0xa3, 0xea, 0x66, 0x67, 0xca, 0x99, 0xe0, 0xbe, 0x17, 0xbe, 0x3b, + 0xd3, 0x79, 0x07, 0x7d, 0x73, 0x33, 0x1b, 0xcb, 0xf5, 0x76, 0x4f, 0x57, + 0x78, 0xae, 0x77, 0xf9, 0x62, 0x2b, 0x0f, 0x7b, 0xda, 0xfb, 0x56, 0x16, + 0xae, 0x34, 0xb2, 0x31, 0x90, 0x3e, 0x3d, 0x1b, 0x9d, 0x66, 0xec, 0x8e, + 0x0d, 0x4f, 0x89, 0x7a, 0xa3, 0x39, 0x34, 0xb6, 0x53, 0x23, 0x3d, 0xa8, + 0x51, 0x1e, 0xde, 0x88, 0xf7, 0x1b, 0x0f, 0xc3, 0x73, 0x77, 0x1a, 0xd7, + 0x93, 0x67, 0xe3, 0x7a, 0xc9, 0xdd, 0x55, 0xa3, 0x27, 0x7d, 0x06, 0x36, + 0x22, 0x1b, 0x05, 0xc4, 0x0c, 0xa5, 0xd4, 0xd7, 0xdf, 0xe4, 0xab, 0xc6, + 0x74, 0x6b, 0xbf, 0x2e, 0xb7, 0xf2, 0xf7, 0x71, 0x94, 0x2b, 0x10, 0xaa, + 0x14, 0x18, 0x58, 0x8c, 0x54, 0xc8, 0xfc, 0x9a, 0xa6, 0x46, 0xa8, 0x8d, + 0xb2, 0xe3, 0xa1, 0x7c, 0x33, 0xd8, 0x6c, 0x88, 0x87, 0x96, 0x9a, 0x9f, + 0x2a, 0xfe, 0xd6, 0x02, 0x16, 0x4a, 0x16, 0xe6, 0x51, 0xbd, 0x94, 0xd2, + 0x5c, 0x85, 0x42, 0xed, 0x52, 0x0e, 0x35, 0x1d, 0x2c, 0xa6, 0x55, 0x77, + 0x43, 0xd7, 0x23, 0xf9, 0x72, 0x0d, 0x0e, 0xf4, 0x48, 0x1e, 0x48, 0x26, + 0x7d, 0xaa, 0xdc, 0x39, 0xe0, 0x3d, 0x22, 0x32, 0xd3, 0x1f, 0x71, 0xa6, + 0xd8, 0x4b, 0x1b, 0x2c, 0xa3, 0xbf, 0xd6, 0x4c, 0xbe, 0xcc, 0xfd, 0x19, + 0xe2, 0x52, 0xa6, 0x9b, 0x5b, 0x96, 0x07, 0x78, 0x65, 0x01, 0x8b, 0x12, + 0x2a, 0x4b, 0x61, 0x31, 0x7f, 0x66, 0x17, 0xe3, 0xc3, 0x06, 0x1b, 0x63, + 0x16, 0x25, 0x03, 0xd6, 0x1e, 0x99, 0xfe, 0xde, 0xe5, 0xaf, 0xbe, 0x8d, + 0x09, 0xc5, 0x29, 0xf7, 0x18, 0x73, 0xba, 0xe0, 0x5d, 0x57, 0x25, 0xbf, + 0x90, 0xbd, 0x07, 0x9c, 0x08, 0x55, 0x02, 0x92, 0x45, 0xb4, 0xec, 0x39, + 0x4c, 0x11, 0x12, 0xb9, 0xa9, 0xaa, 0x1f, 0xb9, 0xe9, 0x8b, 0x52, 0xfc, + 0x69, 0x16, 0x7f, 0xeb, 0x88, 0xab, 0x8d, 0xec, 0xd2, 0xd8, 0xf3, 0x1b, + 0x17, 0x58, 0x0c, 0xaa, 0x32, 0x7d, 0x3e, 0x90, 0xe8, 0x05, 0xce, 0x07, + 0xcd, 0xd0, 0x61, 0xe1, 0x55, 0x79, 0x74, 0x85, 0x1b, 0x57, 0xe8, 0x4a, + 0x0d, 0xb0, 0xea, 0x8d, 0x56, 0x27, 0x1a, 0x2d, 0x35, 0x8e, 0xe8, 0x8a, + 0xcb, 0xab, 0x33, 0x30, 0x39, 0xb7, 0x2a, 0xab, 0xda, 0xe3, 0x42, 0x98, + 0x08, 0xf3, 0x52, 0x1a, 0xd7, 0x9e, 0x5e, 0x64, 0xc3, 0x87, 0x79, 0x3c, + 0x29, 0x56, 0x75, 0xef, 0x0b, 0xaa, 0xac, 0x01, 0x96, 0xb1, 0xfd, 0x84, + 0x89, 0xeb, 0xf2, 0xc0, 0x9d, 0x48, 0xa8, 0xc7, 0x97, 0xfb, 0x47, 0x18, + 0xe9, 0x7e, 0x00, 0xbc, 0x67, 0xd2, 0xeb, 0x2d, 0x74, 0x91, 0xbd, 0x59, + 0xa6, 0xe5, 0x39, 0xd2, 0xf1, 0x6d, 0x46, 0x03, 0xbb, 0x44, 0x74, 0x69, + 0x6e, 0x75, 0x29, 0xec, 0x52, 0xaa, 0xca, 0xde, 0x0e, 0x29, 0x82, 0xef, + 0x5d, 0xfe, 0x85, 0xba, 0x3c, 0x80, 0x5f, 0x2e, 0xbb, 0x38, 0xd7, 0x1d, + 0x19, 0xf3, 0x25, 0x19, 0x33, 0x5e, 0xf0, 0xee, 0xc0, 0x2e, 0x09, 0xe7, + 0x68, 0x7b, 0x25, 0x01, 0xa9, 0xbe, 0xb2, 0x4b, 0x85, 0x44, 0xc3, 0x1b, + 0x36, 0xe3, 0x83, 0x1c, 0x5d, 0x89, 0x5a, 0x55, 0x41, 0xb8, 0x2e, 0x57, + 0x02, 0x4b, 0x99, 0x76, 0x0b, 0x66, 0x3c, 0x8b, 0xfd, 0x74, 0xd6, 0x0c, + 0xe3, 0x49, 0x79, 0x64, 0xea, 0x95, 0x6f, 0xf5, 0x81, 0x6b, 0x37, 0xfb, + 0x69, 0x84, 0x8c, 0x13, 0xce, 0xa8, 0xc4, 0x92, 0x0a, 0x0b, 0x4b, 0xa8, + 0xb3, 0xf7, 0x0f, 0x18, 0xe4, 0xba, 0x14, 0x55, 0x06, 0xa6, 0x61, 0xf1, + 0xce, 0x37, 0xec, 0xb4, 0x1d, 0x0d, 0xf6, 0x33, 0x98, 0x56, 0x7b, 0xf1, + 0x98, 0x41, 0x7f, 0x71, 0x42, 0xfc, 0x49, 0x83, 0xbe, 0xd1, 0x27, 0x7f, + 0xbe, 0xcb, 0xd7, 0xfa, 0x6c, 0x75, 0xb1, 0xff, 0x1a, 0xff, 0x28, 0xf4, + 0xf3, 0xd6, 0x3f, 0xf6, 0x1f, 0x7f, 0xfe, 0x4a, 0xff, 0x15, 0xff, 0x21, + 0x1f, 0x7a, 0xd3, 0xb2, 0x23, 0x63, 0xd3, 0xd8, 0xb9, 0x24, 0x5d, 0x54, + 0xe3, 0x59, 0xe8, 0xba, 0x1c, 0x02, 0x7c, 0x2b, 0xcf, 0x79, 0xaf, 0x8a, + 0x5f, 0x1f, 0x60, 0x09, 0x7b, 0x30, 0x75, 0xa1, 0x6d, 0xef, 0xef, 0x2b, + 0x72, 0x93, 0xb8, 0xfa, 0xd3, 0x01, 0xfd, 0x72, 0xc2, 0x61, 0x2a, 0x8c, + 0xa2, 0x77, 0xf9, 0x81, 0xb7, 0x7b, 0xca, 0xf3, 0x7f, 0x80, 0x89, 0x20, + 0xb5, 0xff, 0x93, 0x2e, 0x2c, 0xc2, 0xe5, 0xf7, 0x18, 0xc8, 0x07, 0xce, + 0xda, 0x8c, 0x06, 0xf7, 0x64, 0x06, 0x96, 0xff, 0x07, 0xfa, 0xda, 0x36, + 0xc9, 0xe6, 0x1c, 0xa4, 0x59, 0xb9, 0xf8, 0xe5, 0xca, 0x97, 0xc8, 0x87, + 0x47, 0x00, 0xdc, 0x79, 0x0a, 0x5f, 0x80, 0xa6, 0xee, 0x89, 0x2b, 0xda, + 0x83, 0xcb, 0x98, 0x6a, 0x5a, 0xc7, 0x36, 0xcc, 0x4b, 0x45, 0xdc, 0xcf, + 0xec, 0xff, 0x24, 0x34, 0x67, 0x4f, 0xcc, 0xa3, 0xea, 0x33, 0xf7, 0xba, + 0x06, 0x53, 0xdc, 0x54, 0xe2, 0xa6, 0x93, 0x41, 0x1e, 0x89, 0x86, 0x44, + 0x07, 0xdf, 0xea, 0xa2, 0x02, 0x49, 0x1c, 0x41, 0x62, 0x1b, 0x36, 0x93, + 0xe8, 0xf9, 0xf0, 0xfc, 0x5b, 0xf7, 0x95, 0xd1, 0x0c, 0x44, 0x13, 0x88, + 0x26, 0x12, 0x4d, 0x22, 0x1a, 0x5a, 0xbe, 0xad, 0x96, 0x98, 0xcf, 0x0c, + 0xac, 0x1b, 0x03, 0xbd, 0x22, 0x1f, 0x3e, 0x27, 0x1f, 0xc6, 0x1a, 0xfc, + 0x29, 0x97, 0x91, 0xbc, 0x0f, 0xb4, 0x81, 0xf5, 0xc9, 0x0e, 0xe9, 0xfb, + 0x0b, 0x4d, 0xde, 0x9f, 0xa5, 0x21, 0xb3, 0x7a, 0x5a, 0xcc, 0xb7, 0xd6, + 0x67, 0x3b, 0xc4, 0xe3, 0xf6, 0x07, 0xbb, 0xfd, 0xe1, 0x8e, 0x85, 0x0d, + 0x3c, 0xdd, 0xf1, 0x8a, 0xd6, 0xcd, 0x73, 0xda, 0x41, 0x09, 0xef, 0x99, + 0x58, 0xe9, 0x91, 0xcf, 0x8c, 0x82, 0xfd, 0x54, 0xf0, 0xf0, 0xd8, 0x13, + 0x08, 0xf4, 0xd6, 0x63, 0xbf, 0x16, 0xfb, 0x65, 0xaf, 0x22, 0xa5, 0x36, + 0x93, 0x85, 0xe8, 0x22, 0xec, 0xf2, 0xbd, 0x63, 0x73, 0x48, 0xee, 0x86, + 0xa5, 0x32, 0x64, 0x22, 0xd9, 0xbe, 0x37, 0xfe, 0x59, 0x8d, 0xbf, 0xec, + 0xe7, 0x2f, 0x42, 0x9f, 0xef, 0x8d, 0x3f, 0xab, 0xf1, 0xfd, 0xf3, 0xcd, + 0x3e, 0xa3, 0xa9, 0xf9, 0x16, 0x86, 0xa1, 0xbd, 0x92, 0xda, 0xec, 0x8c, + 0x00, 0x03, 0x6f, 0x34, 0xdb, 0xe2, 0x7b, 0xb7, 0x7f, 0x70, 0xb7, 0x1e, + 0x41, 0x6b, 0x7b, 0x14, 0x54, 0xbe, 0x94, 0x9a, 0xd9, 0x62, 0x15, 0x4e, + 0x11, 0x31, 0x16, 0x7d, 0x5d, 0xcd, 0x1b, 0xf9, 0xf3, 0x52, 0xfe, 0x3c, + 0xdb, 0xff, 0xd7, 0xb7, 0xaa, 0xea, 0x3b, 0x57, 0x35, 0x1b, 0xf9, 0x5a, + 0xfe, 0x3c, 0x29, 0xc5, 0xca, 0xfe, 0x34, 0x08, 0x23, 0x0e, 0x83, 0xbe, + 0x1d, 0x59, 0xcd, 0x13, 0x0d, 0xcc, 0x79, 0xdd, 0xe7, 0xd6, 0x5c, 0xf8, + 0x02, 0xcb, 0x65, 0xa3, 0x3b, 0x24, 0x8e, 0x4f, 0xa9, 0x64, 0x2f, 0x0f, + 0x18, 0x0e, 0x8d, 0xe9, 0xb2, 0x11, 0x6e, 0x25, 0xd6, 0xbd, 0x61, 0x77, + 0x4f, 0xb5, 0x61, 0xa9, 0x76, 0xfd, 0x83, 0xc6, 0x7a, 0xd9, 0x68, 0x0f, + 0x8d, 0x45, 0x1b, 0xdd, 0xa1, 0x31, 0xd3, 0xee, 0xf6, 0x46, 0xd6, 0xc6, + 0x70, 0xd5, 0x08, 0x66, 0x2c, 0x5f, 0x36, 0xc6, 0x06, 0x84, 0xf6, 0x86, + 0x4d, 0x25, 0x5d, 0x35, 0xce, 0xb3, 0xdc, 0x5a, 0x38, 0x77, 0x17, 0x8d, + 0x45, 0x1b, 0xdd, 0x55, 0xa3, 0x47, 0xea, 0xb6, 0x36, 0xcc, 0xa5, 0x38, + 0x36, 0x02, 0x77, 0xc7, 0x1b, 0xf1, 0xd0, 0x18, 0x2f, 0x1b, 0x09, 0x0e, + 0x5d, 0x6d, 0x70, 0x5b, 0xbd, 0x51, 0x0e, 0x8d, 0xf5, 0x01, 0x8d, 0xeb, + 0x51, 0xde, 0x28, 0xd7, 0x8d, 0xf1, 0x7e, 0x63, 0xba, 0xd3, 0x78, 0x83, + 0x95, 0xd6, 0xc6, 0xcf, 0xcc, 0x68, 0xbf, 0x60, 0xe3, 0xc9, 0xa1, 0xa1, + 0xa4, 0x2b, 0xed, 0xa1, 0xb1, 0x72, 0x0b, 0xae, 0x1b, 0x1b, 0xf7, 0xeb, + 0xba, 0x81, 0x1d, 0x8c, 0x87, 0xc6, 0x69, 0xdf, 0xc3, 0xa1, 0x31, 0x91, + 0x6d, 0xae, 0x1b, 0xf9, 0x92, 0xc7, 0xfa, 0x2b, 0xf6, 0xeb, 0x0e, 0x8d, + 0xe5, 0x92, 0x69, 0xdb, 0x2b, 0x7e, 0x3e, 0xb2, 0xfa, 0x99, 0xbf, 0x52, + 0x6d, 0x9c, 0x45, 0x26, 0x5e, 0x89, 0xd5, 0x51, 0xe2, 0xa6, 0xcb, 0xc6, + 0x51, 0x3c, 0x4f, 0x92, 0xdb, 0x5f, 0x49, 0xf7, 0x51, 0xf0, 0x3f, 0x5b, + 0x31, 0x72, 0x2c, 0x1d, 0xb9, 0x5b, 0x7d, 0x91, 0xc8, 0x45, 0xde, 0xf8, + 0x4b, 0x06, 0x65, 0xbf, 0x0f, 0xfa, 0x3e, 0xe8, 0x6f, 0x14, 0xdb, 0x8f, + 0x36, 0xbf, 0xd6, 0x02, 0x17, 0x78, 0x30, 0x75, 0x1b, 0x10, 0xb5, 0x4a, + 0xfa, 0xeb, 0xad, 0x28, 0xe4, 0x15, 0x35, 0x2a, 0xab, 0x60, 0x91, 0xc1, + 0x68, 0x33, 0x15, 0xa3, 0x61, 0xb2, 0x85, 0xc8, 0x1b, 0x2f, 0xb2, 0xad, + 0x53, 0xde, 0x21, 0x32, 0x5b, 0x53, 0xce, 0xa0, 0xc5, 0x16, 0x27, 0xe1, + 0x92, 0xcd, 0x6e, 0x67, 0x21, 0x94, 0x86, 0x1a, 0x5b, 0xb1, 0x25, 0xb3, + 0x3e, 0x26, 0x6b, 0x0a, 0xe2, 0x6c, 0xb7, 0x13, 0xc4, 0x20, 0x8c, 0x20, + 0x06, 0xdd, 0x04, 0xf1, 0x66, 0x4d, 0x8d, 0xc3, 0xd8, 0xed, 0x14, 0xf1, + 0x68, 0xb7, 0x7b, 0x51, 0x3c, 0xa0, 0x24, 0x88, 0x11, 0x64, 0x12, 0xc4, + 0x88, 0xb7, 0x0a, 0x62, 0x44, 0x91, 0x04, 0xf1, 0x66, 0xb7, 0x13, 0xc4, + 0x8c, 0x33, 0x0e, 0xc5, 0x63, 0x8f, 0x4f, 0x0d, 0xdb, 0x00, 0xc4, 0xb3, + 0xdd, 0xee, 0x75, 0xf1, 0x12, 0x0f, 0x41, 0xbc, 0xa1, 0xd4, 0x3b, 0x15, + 0x56, 0x46, 0xe8, 0xab, 0xfd, 0x46, 0xbd, 0x9d, 0x22, 0x9e, 0x4c, 0x85, + 0x0d, 0x86, 0x2d, 0x02, 0xf1, 0x62, 0xb7, 0x13, 0xc4, 0x78, 0xc6, 0xea, + 0x75, 0xa1, 0x92, 0x35, 0xc3, 0xcc, 0x6e, 0xb7, 0x23, 0x7e, 0x85, 0x84, + 0xe7, 0x8e, 0xf8, 0x6d, 0x91, 0xe4, 0xec, 0x47, 0x41, 0xfc, 0xab, 0x3e, + 0xc2, 0xf1, 0x9b, 0x20, 0xfe, 0xa8, 0xa7, 0xdc, 0x26, 0x88, 0x7f, 0xb3, + 0x4a, 0x60, 0xf9, 0xfd, 0xe2, 0x2d, 0x69, 0x8c, 0xf7, 0xb9, 0x20, 0x8e, + 0x5a, 0x2b, 0xf2, 0x4c, 0x10, 0x3f, 0xd3, 0x38, 0xd2, 0x4b, 0x41, 0xfc, + 0xb2, 0x48, 0xb1, 0xc3, 0x1b, 0x41, 0xfc, 0x46, 0x9f, 0x6c, 0x79, 0x2f, + 0x88, 0xdf, 0xa9, 0x09, 0xaa, 0xbf, 0x64, 0xbc, 0xe8, 0x71, 0xb4, 0xe8, + 0xef, 0x4b, 0xa7, 0x62, 0x0f, 0xf4, 0xbc, 0x10, 0x4b, 0x62, 0xd4, 0xe7, + 0x8b, 0x5e, 0xcb, 0x6b, 0x3c, 0xa6, 0x46, 0x1e, 0x77, 0x7a, 0x57, 0xf4, + 0x87, 0x0c, 0xa3, 0x0e, 0xda, 0xfb, 0x48, 0xa9, 0x86, 0x0e, 0x7a, 0x5d, + 0xe4, 0x11, 0x70, 0x1d, 0x34, 0xeb, 0x49, 0xa8, 0x83, 0x96, 0x28, 0x76, + 0xea, 0x2c, 0xe9, 0xe6, 0x9d, 0x0f, 0x27, 0x7d, 0x8c, 0xe7, 0x8d, 0xf0, + 0x61, 0xee, 0xe4, 0xbb, 0xf7, 0xc2, 0x87, 0x73, 0x2b, 0xdf, 0x7d, 0xb0, + 0xa0, 0x98, 0xfe, 0x62, 0xb2, 0x1c, 0x48, 0x12, 0x28, 0xeb, 0x65, 0x6d, + 0x39, 0x49, 0x14, 0xad, 0x93, 0xb5, 0xc9, 0xaf, 0x26, 0x46, 0x89, 0x65, + 0x3f, 0x2b, 0x4b, 0xd0, 0x48, 0x99, 0x98, 0xbb, 0xeb, 0x20, 0x49, 0xe6, + 0xac, 0x88, 0x3b, 0x79, 0x26, 0x67, 0x56, 0xc4, 0xad, 0x18, 0xc6, 0xb3, + 0x15, 0xb8, 0xcb, 0x17, 0x8b, 0x21, 0x4e, 0xf6, 0x85, 0x20, 0x8e, 0xf6, + 0x85, 0x20, 0x1e, 0xec, 0x0b, 0x41, 0xac, 0x73, 0x6c, 0x14, 0xb1, 0xce, + 0x71, 0x54, 0xc4, 0x3a, 0xc7, 0x49, 0x11, 0xeb, 0x1c, 0xb3, 0x21, 0x16, + 0x3a, 0xcc, 0x86, 0x58, 0x9e, 0x8a, 0x9a, 0x3b, 0x45, 0x2c, 0x0f, 0x69, + 0x49, 0x9c, 0x3e, 0xca, 0x6c, 0xde, 0xe9, 0x09, 0xfe, 0x4c, 0x32, 0xe2, + 0x8b, 0x9a, 0x1a, 0x2f, 0x05, 0xb0, 0x69, 0x80, 0xef, 0x8d, 0x2e, 0x33, + 0xc9, 0x83, 0x81, 0xef, 0xd5, 0xd6, 0x78, 0x2e, 0x75, 0x73, 0x3b, 0xe2, + 0x1d, 0xf0, 0x4a, 0xc8, 0xb5, 0x6a, 0xe9, 0xce, 0x5b, 0x21, 0x97, 0x3c, + 0xc6, 0xa5, 0xcc, 0xa8, 0x88, 0x35, 0x31, 0x3f, 0x37, 0xf6, 0xf3, 0xdf, + 0xc5, 0xa2, 0x84, 0x2f, 0x2d, 0xa8, 0xb8, 0x02, 0x71, 0x30, 0x63, 0xe5, + 0x7d, 0x41, 0x95, 0xaa, 0x21, 0x16, 0x5e, 0x6b, 0x0c, 0xb1, 0x60, 0x1b, + 0x0d, 0xb1, 0x16, 0xfe, 0x1a, 0xe2, 0x26, 0x99, 0xed, 0xf2, 0xcc, 0x22, + 0x93, 0x19, 0x88, 0x83, 0x45, 0x96, 0xdf, 0x58, 0xc4, 0x79, 0x01, 0xe2, + 0xde, 0x22, 0xd0, 0x1f, 0x50, 0x9a, 0x04, 0xc4, 0x30, 0x6f, 0xf4, 0xc1, + 0xb3, 0x64, 0x5f, 0x45, 0x0b, 0x6f, 0x8e, 0x40, 0x1c, 0x8a, 0x7e, 0xf5, + 0xd2, 0x1c, 0xa0, 0x0c, 0xc4, 0x9d, 0xb9, 0x41, 0xef, 0xcd, 0x02, 0x9a, + 0x81, 0xb8, 0x31, 0x37, 0x69, 0x35, 0xcf, 0x48, 0x7a, 0x6d, 0x05, 0x0f, + 0x6a, 0xb5, 0x52, 0x6d, 0x25, 0xbc, 0xbd, 0x99, 0x15, 0xa9, 0xbf, 0xd5, + 0x62, 0x62, 0xa8, 0x75, 0x84, 0x5a, 0x38, 0x65, 0xf6, 0x9f, 0x05, 0x55, + 0xd5, 0x48, 0xca, 0x16, 0x77, 0x16, 0xa1, 0x41, 0xc5, 0xfc, 0xb8, 0x7f, + 0x39, 0x5b, 0x58, 0x7a, 0x6a, 0xe4, 0x19, 0x37, 0x0d, 0x55, 0xcb, 0x83, + 0x58, 0x2b, 0x11, 0xb7, 0x30, 0xb0, 0x4c, 0x04, 0x1b, 0x22, 0x4e, 0x78, + 0x3a, 0x4b, 0x21, 0x23, 0x11, 0x0f, 0x5a, 0xb0, 0xbf, 0xf1, 0x59, 0x06, + 0x57, 0x7d, 0x7e, 0x0a, 0xe0, 0xd4, 0xd0, 0x2f, 0xda, 0x02, 0x15, 0x96, + 0x0c, 0xa5, 0x1d, 0x13, 0x23, 0x3a, 0x40, 0xcd, 0xd5, 0x98, 0x38, 0x3b, + 0x64, 0x3e, 0x92, 0x8b, 0x0e, 0x6e, 0x42, 0x4d, 0xe8, 0x30, 0xd7, 0x57, + 0xd6, 0x58, 0xe4, 0x7d, 0x44, 0x87, 0xb9, 0x46, 0xeb, 0xb5, 0x83, 0x57, + 0x7a, 0x65, 0x74, 0x58, 0x6a, 0x2a, 0xc6, 0x3a, 0x4c, 0xe8, 0xb0, 0x54, + 0x2b, 0x2d, 0x00, 0xa5, 0x75, 0xc0, 0x23, 0x69, 0x46, 0x11, 0x52, 0xd4, + 0x50, 0xf6, 0xf8, 0x02, 0x29, 0x0c, 0x76, 0x98, 0xd0, 0xa1, 0xbe, 0x79, + 0x07, 0x1d, 0x26, 0x74, 0xe0, 0x03, 0x08, 0xf5, 0x67, 0x03, 0xd0, 0x21, + 0xa3, 0x43, 0x7d, 0xfd, 0x33, 0x3a, 0x64, 0x74, 0x60, 0xae, 0xcc, 0xdf, + 0x9d, 0x35, 0xa1, 0x03, 0x7f, 0xd7, 0xee, 0xfa, 0xcd, 0xb9, 0xda, 0xe1, + 0x2d, 0x5e, 0x18, 0xfd, 0x11, 0x71, 0x7b, 0x11, 0x65, 0xed, 0xf0, 0x0c, + 0xef, 0x98, 0x78, 0x83, 0x12, 0x86, 0x05, 0x25, 0x0f, 0xbb, 0xae, 0x5b, + 0xb4, 0xc3, 0x53, 0x3c, 0x4b, 0xf7, 0x1a, 0xef, 0x7e, 0xda, 0xd5, 0x86, + 0x3d, 0x6c, 0x37, 0x20, 0x6f, 0xf0, 0xc4, 0x0e, 0x03, 0xd1, 0x52, 0xea, + 0x87, 0xef, 0xc3, 0xd4, 0x33, 0x17, 0xf5, 0xa3, 0x1d, 0x46, 0xfc, 0xf8, + 0x49, 0x8e, 0xf6, 0xa3, 0x5b, 0x4b, 0x6f, 0xbf, 0x5d, 0x2b, 0x0e, 0xe3, + 0x2b, 0xed, 0x10, 0x25, 0xfd, 0x51, 0xbc, 0x9e, 0x86, 0x05, 0x36, 0x57, + 0x6b, 0x61, 0xcd, 0x1f, 0x6b, 0xe7, 0x36, 0xe6, 0x83, 0xc6, 0x2b, 0x32, + 0x45, 0x74, 0x00, 0xca, 0x33, 0x81, 0x17, 0xdf, 0x01, 0xa0, 0x1c, 0x1b, + 0x76, 0x08, 0xe8, 0x70, 0xda, 0xbb, 0x11, 0x28, 0x21, 0x61, 0x14, 0x24, + 0x8a, 0xce, 0x91, 0x2d, 0xbc, 0xe8, 0x89, 0x1d, 0x02, 0x3a, 0xf4, 0xe8, + 0xe0, 0x1c, 0x17, 0xd1, 0x81, 0x69, 0xc2, 0x2b, 0x5e, 0x65, 0x0e, 0xca, + 0xd3, 0x4a, 0xec, 0x10, 0xd0, 0x81, 0x62, 0xd0, 0xb0, 0x43, 0x44, 0x87, + 0x01, 0x1d, 0x2e, 0x05, 0x68, 0xac, 0xe5, 0x46, 0x5e, 0xef, 0xc8, 0x0e, + 0x01, 0x1d, 0x28, 0x9b, 0x0d, 0x3b, 0xc4, 0x52, 0x8d, 0x16, 0x98, 0x81, + 0x6e, 0x01, 0x6a, 0x87, 0x01, 0x1d, 0x3a, 0x64, 0xc9, 0x2c, 0x8f, 0x68, + 0x6a, 0x9c, 0xda, 0xa0, 0x39, 0x08, 0xb7, 0xcb, 0xe8, 0x29, 0xbb, 0x4a, + 0xe3, 0xa2, 0x4a, 0x23, 0xd5, 0xd1, 0x61, 0x03, 0x8a, 0xd7, 0x77, 0x7a, + 0x3a, 0xd0, 0xc5, 0xc5, 0xdf, 0x42, 0xe9, 0x2c, 0x53, 0x8b, 0xb4, 0x44, + 0x10, 0x54, 0x6c, 0xd6, 0x1e, 0x11, 0x2a, 0xe1, 0xd0, 0x95, 0xde, 0xf6, + 0x4a, 0x23, 0xf1, 0xec, 0x24, 0x26, 0x26, 0xdb, 0x12, 0x43, 0x0d, 0x91, + 0xfa, 0x26, 0x14, 0x3c, 0x3c, 0xfa, 0x23, 0x49, 0xfe, 0x86, 0xca, 0xcb, + 0xf3, 0x79, 0x9e, 0xe1, 0xfb, 0x8d, 0xb1, 0x86, 0xea, 0x1c, 0x26, 0x2e, + 0xfd, 0x39, 0x6d, 0xb7, 0x57, 0x87, 0xac, 0x31, 0xb5, 0x60, 0xcd, 0x2c, + 0x73, 0x3b, 0x2f, 0x83, 0x16, 0xe9, 0x92, 0x86, 0x23, 0xf9, 0xed, 0x3b, + 0xa2, 0x7f, 0x24, 0xa2, 0x2f, 0xe3, 0x23, 0x67, 0xc8, 0xf7, 0x34, 0xf9, + 0xbd, 0xf4, 0xa1, 0x86, 0x36, 0x12, 0x39, 0x3b, 0xd0, 0xd4, 0x67, 0x09, + 0x45, 0x31, 0xff, 0x2f, 0x1e, 0x44, 0x64, 0x2a, 0x28, 0xbc, 0x90, 0x73, + 0x42, 0xa3, 0xba, 0x72, 0x92, 0xe8, 0xc1, 0x34, 0x33, 0xb5, 0xec, 0x3f, + 0x47, 0xe7, 0x25, 0x1d, 0x5e, 0xe4, 0xb1, 0x56, 0xa1, 0xf5, 0x42, 0x10, + 0xd7, 0xf7, 0x7e, 0x88, 0x55, 0xe9, 0x0f, 0x6c, 0x50, 0x1f, 0x1f, 0xe2, + 0x76, 0x91, 0x0d, 0x5a, 0x0b, 0xb4, 0x68, 0xa0, 0x62, 0x72, 0x03, 0x50, + 0x63, 0x1f, 0xcb, 0x33, 0x93, 0xa8, 0x3c, 0xbd, 0xbd, 0x54, 0xc5, 0x0f, + 0x0f, 0x4c, 0xcc, 0x7f, 0xbd, 0x64, 0x62, 0x3d, 0x0a, 0xb5, 0x8a, 0x3f, + 0xc5, 0xc2, 0xd7, 0xf3, 0xd7, 0x2a, 0x51, 0x2e, 0xd8, 0xeb, 0x47, 0x67, + 0x20, 0xf1, 0xf7, 0xa7, 0x6f, 0x81, 0xdf, 0xf0, 0x04, 0xfb, 0x19, 0xe6, + 0x9d, 0x17, 0xac, 0x7e, 0xe0, 0xbc, 0x5e, 0x16, 0x44, 0x47, 0x53, 0x29, + 0x97, 0xa5, 0xb2, 0xaf, 0xb0, 0x55, 0x5e, 0x44, 0xfb, 0x2b, 0x0b, 0x58, + 0x9e, 0x57, 0x73, 0xc8, 0x2b, 0x73, 0x78, 0x62, 0x78, 0x00, 0x73, 0xa9, + 0xf6, 0x8f, 0x9f, 0x42, 0x5e, 0x80, 0xe2, 0x55, 0x2b, 0x7f, 0x2c, 0x12, + 0x3f, 0x06, 0xaf, 0x96, 0xe3, 0x0a, 0x34, 0x62, 0x39, 0xdd, 0x99, 0x26, + 0x3d, 0x69, 0x52, 0xea, 0x8f, 0xb0, 0xf9, 0xaf, 0xb2, 0xf9, 0x23, 0x4a, + 0x4f, 0x71, 0xf5, 0x32, 0xdf, 0x9f, 0x70, 0xf5, 0xdd, 0x51, 0x63, 0x47, + 0x00, 0xdc, 0xe2, 0x09, 0x48, 0xfc, 0x41, 0x03, 0x5a, 0x58, 0x5e, 0xb9, + 0x5f, 0x6d, 0x09, 0x2f, 0x76, 0x4a, 0x58, 0x7c, 0x87, 0xb5, 0x9b, 0x13, + 0x60, 0xcc, 0xa6, 0xf1, 0x07, 0xc6, 0x51, 0x6a, 0xe5, 0x8c, 0x97, 0xdb, + 0x78, 0x71, 0x4d, 0x02, 0x9f, 0x7a, 0xfd, 0xcd, 0x77, 0x24, 0x7f, 0x65, + 0x24, 0xd6, 0xfd, 0x6f, 0x6d, 0xea, 0xac, 0x97, 0xa6, 0x8e, 0xfd, 0x02, + 0xcc, 0xa7, 0x4c, 0x9d, 0xf0, 0xdd, 0xd4, 0xf9, 0x8e, 0xe8, 0xdb, 0x21, + 0xfa, 0x3a, 0xa6, 0x4e, 0xfb, 0x17, 0x34, 0x75, 0xd6, 0x07, 0x98, 0x3a, + 0x17, 0x29, 0xca, 0x08, 0xaa, 0x9c, 0x4c, 0x9d, 0xd5, 0x75, 0x4d, 0x43, + 0x10, 0x35, 0x1b, 0xf4, 0xd6, 0x8e, 0x1e, 0xfa, 0x2a, 0x40, 0xd9, 0x09, + 0x8a, 0x91, 0x6a, 0x0b, 0x9d, 0x5c, 0xa9, 0x45, 0x76, 0x1a, 0x88, 0xa9, + 0xa3, 0x6e, 0x6c, 0xa8, 0x13, 0x5d, 0xed, 0x35, 0xfc, 0x79, 0x2c, 0xe9, + 0xd4, 0xb0, 0xd3, 0xc8, 0x4e, 0x23, 0x3b, 0x4d, 0xec, 0x94, 0xd9, 0x89, + 0x8d, 0xc8, 0x37, 0xa4, 0x0c, 0x54, 0xaa, 0x1d, 0x50, 0x37, 0xcd, 0xff, + 0x63, 0xe3, 0x7f, 0xb2, 0xd1, 0x55, 0x67, 0xb5, 0xb9, 0x24, 0x51, 0xcd, + 0xb5, 0xd2, 0xed, 0x66, 0xdc, 0xac, 0x54, 0xdf, 0xfe, 0x41, 0x0f, 0x9d, + 0x7f, 0xe6, 0x8d, 0x02, 0x40, 0x74, 0x68, 0xe0, 0x05, 0xb5, 0xbc, 0x61, + 0xa6, 0x22, 0xcf, 0xdc, 0x53, 0x7f, 0xd0, 0xf7, 0x21, 0x0f, 0xa6, 0x7f, + 0xb6, 0xd1, 0x1c, 0x12, 0x8f, 0x7f, 0x5c, 0x63, 0xb9, 0xd3, 0x78, 0xc8, + 0x0c, 0x8f, 0xeb, 0x3a, 0x2d, 0xd9, 0x89, 0x10, 0xae, 0x08, 0x45, 0xd2, + 0xf5, 0x27, 0xf2, 0x9e, 0x28, 0x7f, 0xb5, 0x29, 0x5f, 0xf4, 0x40, 0x39, + 0x59, 0x62, 0xbd, 0x62, 0x92, 0x3b, 0x8d, 0xcb, 0xb4, 0xf9, 0x75, 0xc3, + 0xd3, 0xe6, 0x9f, 0x68, 0x4c, 0xda, 0xb8, 0x99, 0x51, 0xf7, 0x46, 0x6e, + 0xb0, 0xf6, 0xeb, 0xc6, 0x75, 0x6a, 0xfd, 0x21, 0xc9, 0xf6, 0x87, 0x64, + 0xdd, 0x1f, 0x93, 0x7e, 0xf7, 0x46, 0x7c, 0x54, 0x42, 0xde, 0x1b, 0x9f, + 0xc8, 0xcc, 0x7f, 0x22, 0x45, 0x7f, 0xdd, 0xb8, 0x16, 0xa2, 0x63, 0xe3, + 0x5e, 0xd2, 0xbe, 0x3d, 0xb0, 0xfa, 0x27, 0xde, 0x9d, 0x50, 0xfc, 0xd8, + 0x69, 0x2c, 0xce, 0x6e, 0x3a, 0xd3, 0x1f, 0x8a, 0x60, 0x96, 0x51, 0xd5, + 0x59, 0x34, 0x06, 0x33, 0x6d, 0xec, 0xee, 0x25, 0x63, 0x7a, 0x4b, 0x3d, + 0x06, 0x03, 0x98, 0x13, 0x07, 0x07, 0x4f, 0x82, 0xd9, 0x0f, 0x02, 0x1c, + 0x16, 0xd9, 0xcf, 0x8a, 0x04, 0x36, 0x0e, 0x60, 0xf0, 0x0e, 0xfc, 0x2f, + 0x52, 0x21, 0xa1, 0x4f, 0x79, 0x2e, 0x4d, 0x22, 0x9b, 0x1b, 0xcb, 0xae, + 0x72, 0xb2, 0xbd, 0x5a, 0x03, 0x1c, 0x4a, 0xd6, 0x95, 0x98, 0x89, 0x17, + 0xf0, 0x8a, 0xfa, 0x96, 0xaf, 0xa8, 0x8f, 0x78, 0x25, 0x4c, 0xaf, 0x2e, + 0xd3, 0x04, 0x79, 0x37, 0xcf, 0x2c, 0xc0, 0x55, 0xfb, 0x05, 0x36, 0xab, + 0xbd, 0xe5, 0xbe, 0xc7, 0x6f, 0x43, 0x37, 0xfc, 0xa5, 0x8f, 0x80, 0x17, + 0xcd, 0x74, 0x7c, 0x11, 0x4e, 0x82, 0x79, 0xfd, 0xef, 0xd5, 0xde, 0xee, + 0x78, 0x82, 0x44, 0x00, 0x7a, 0x9e, 0x3b, 0x24, 0x6a, 0x00, 0xc0, 0xa9, + 0x1c, 0x01, 0xe8, 0x09, 0x48, 0x00, 0x0c, 0xe7, 0x7d, 0x40, 0xd6, 0xd3, + 0xf2, 0x97, 0xc0, 0x75, 0x09, 0x88, 0x00, 0x74, 0xb7, 0x00, 0x1b, 0x01, + 0xf9, 0xcb, 0x00, 0x01, 0x80, 0xf6, 0xcb, 0x01, 0x0d, 0x01, 0xc3, 0x01, + 0x70, 0xb1, 0x96, 0x0b, 0x00, 0x57, 0x7b, 0xb5, 0x7c, 0x7f, 0x4f, 0x16, + 0x49, 0x99, 0x01, 0x58, 0x09, 0x98, 0x00, 0xb8, 0x22, 0xf6, 0x2c, 0xaf, + 0xda, 0xb6, 0xf4, 0x96, 0xa5, 0x4e, 0xa6, 0x00, 0xc9, 0x49, 0x1e, 0x86, + 0x5e, 0x2b, 0x2f, 0x9b, 0x70, 0xcc, 0xb0, 0xbb, 0x4c, 0x90, 0x3e, 0xd2, + 0xfd, 0x4f, 0x88, 0x07, 0xbc, 0x45, 0xcf, 0xdf, 0xd8, 0xf3, 0x79, 0xf1, + 0x08, 0xc5, 0xa4, 0xca, 0x25, 0xc1, 0xf3, 0x78, 0x09, 0x26, 0xe4, 0xaf, + 0xac, 0x1c, 0x7f, 0xb9, 0x22, 0xc3, 0x5c, 0x33, 0xc5, 0x32, 0xc0, 0xdd, + 0x7e, 0x0d, 0xd3, 0xc9, 0xde, 0x21, 0x90, 0x90, 0xe0, 0x0f, 0xc5, 0x7e, + 0xc2, 0x39, 0x14, 0x3e, 0x95, 0x59, 0xcf, 0x07, 0x4f, 0x2a, 0xf8, 0x19, + 0x00, 0x29, 0x9a, 0x5c, 0xee, 0x0c, 0x70, 0x48, 0x05, 0x44, 0x1b, 0x59, + 0x6d, 0xd1, 0x68, 0x1d, 0x6a, 0xb2, 0x49, 0x87, 0x4c, 0x4d, 0x8d, 0x37, + 0xf5, 0xdc, 0x98, 0x58, 0xa9, 0x0c, 0x93, 0x26, 0xe5, 0xc6, 0x4d, 0x91, + 0x3f, 0xf7, 0xb3, 0x55, 0x51, 0x8c, 0x55, 0x9c, 0x30, 0x5b, 0xe5, 0x44, + 0xfa, 0xbd, 0x5d, 0x15, 0x3b, 0x72, 0x57, 0x06, 0xcb, 0x7c, 0x1f, 0xfe, + 0x7d, 0xf8, 0xf7, 0xe1, 0x77, 0x87, 0x97, 0x9a, 0xce, 0xb6, 0x7f, 0x16, + 0x45, 0xe7, 0x99, 0x3d, 0xd3, 0x29, 0xd3, 0x41, 0xa7, 0x9a, 0x82, 0x6a, + 0xa0, 0x54, 0xb6, 0xe6, 0x10, 0xd7, 0xfe, 0x8e, 0xe6, 0x3b, 0x9a, 0x7f, + 0x2d, 0x34, 0x81, 0x68, 0x22, 0xd1, 0x24, 0x36, 0x0a, 0xf0, 0xb5, 0x8c, + 0x34, 0x7a, 0xd2, 0x83, 0x16, 0x03, 0x6c, 0x8a, 0x49, 0x47, 0x3f, 0x63, + 0x80, 0x27, 0xc2, 0x1e, 0xb1, 0xd8, 0x4f, 0xa7, 0x46, 0x70, 0x39, 0xb8, + 0x10, 0x41, 0xcd, 0x8a, 0x8d, 0xa1, 0x1f, 0x73, 0x00, 0x3b, 0x18, 0x04, + 0x5a, 0xfc, 0xa0, 0x66, 0x29, 0x0d, 0x8b, 0x8b, 0x67, 0x92, 0x6d, 0xda, + 0x6c, 0xd0, 0xb4, 0xf0, 0xc0, 0xcf, 0xe1, 0x81, 0x55, 0x34, 0x68, 0x5c, + 0x6c, 0x87, 0xf0, 0x6f, 0x24, 0x39, 0x22, 0xa9, 0xe0, 0xa4, 0x0b, 0xa0, + 0x98, 0xe7, 0xde, 0xab, 0xa1, 0xe1, 0x66, 0x5c, 0x63, 0xbf, 0xcb, 0xe3, + 0xe1, 0x21, 0x35, 0xcc, 0x84, 0x4a, 0xbf, 0x16, 0x94, 0x28, 0xfe, 0xe6, + 0x49, 0x89, 0xc4, 0x74, 0xd9, 0xcb, 0x82, 0xe2, 0xcd, 0x5f, 0x68, 0xc7, + 0x2c, 0x05, 0x95, 0x9e, 0xf6, 0x83, 0x57, 0x72, 0xc7, 0x1f, 0x0b, 0xaa, + 0x12, 0x5f, 0x9b, 0x2a, 0xd4, 0x64, 0xd6, 0xd2, 0xc0, 0xb3, 0xd8, 0x0c, + 0x65, 0xcf, 0xa2, 0x81, 0x9f, 0xb8, 0xb7, 0x73, 0xc4, 0xb6, 0xcb, 0x93, + 0x18, 0xda, 0x49, 0x1c, 0x93, 0x49, 0x51, 0x8e, 0x0c, 0xf2, 0xe5, 0x88, + 0x4d, 0x5d, 0x7a, 0x2c, 0x79, 0x6d, 0xb9, 0x87, 0x0d, 0x4d, 0xc9, 0x29, + 0xc0, 0xea, 0xb2, 0x5f, 0x53, 0x54, 0x38, 0xac, 0xca, 0xcd, 0xa2, 0x50, + 0x32, 0x78, 0x80, 0x87, 0x92, 0x3b, 0xfa, 0x2e, 0x0d, 0xbd, 0x1a, 0xed, + 0xa8, 0x28, 0x7b, 0xa4, 0x91, 0x26, 0xbb, 0x8b, 0xf9, 0x5b, 0xcb, 0xa1, + 0x53, 0xb0, 0x0d, 0x9f, 0xac, 0x27, 0xea, 0x7e, 0xb2, 0xdd, 0x45, 0x7a, + 0x0e, 0xe6, 0xbb, 0xd5, 0x4e, 0x06, 0xee, 0x58, 0xb0, 0xd5, 0xc0, 0xa7, + 0x1b, 0x75, 0x48, 0x0b, 0x83, 0xd5, 0x3b, 0x8d, 0x1a, 0x2c, 0x6b, 0x61, + 0x56, 0x4e, 0x76, 0x73, 0xb3, 0x3e, 0x37, 0x9b, 0x45, 0x0f, 0x5f, 0x4d, + 0x3a, 0x4d, 0xf6, 0x27, 0xc9, 0x32, 0xa4, 0xa7, 0xce, 0x22, 0x98, 0x6f, + 0x68, 0x8b, 0xee, 0xd0, 0x49, 0xe2, 0x04, 0xca, 0x3a, 0xf5, 0xe5, 0xf9, + 0x16, 0x96, 0xeb, 0x2c, 0x75, 0xa5, 0xac, 0x3f, 0xb7, 0x74, 0xde, 0x0c, + 0x65, 0xaf, 0xbc, 0x6a, 0xb2, 0x34, 0xe9, 0x52, 0xe4, 0x2e, 0x1d, 0x62, + 0xfc, 0x5b, 0x43, 0xa9, 0x1a, 0x23, 0xe4, 0x36, 0xf3, 0xd5, 0xfe, 0x4b, + 0x47, 0x69, 0x6f, 0x28, 0xc1, 0x53, 0x42, 0xa7, 0x39, 0x80, 0x67, 0x57, + 0x43, 0xd9, 0x6b, 0x36, 0x74, 0xb2, 0xbb, 0x24, 0xd4, 0x81, 0x2d, 0xec, + 0xb4, 0x19, 0xca, 0x4e, 0x85, 0xce, 0x18, 0x38, 0xd3, 0x93, 0x5e, 0xec, + 0xbe, 0x03, 0xdf, 0xb6, 0xdb, 0x69, 0x95, 0x69, 0xa3, 0x08, 0xec, 0x67, + 0xe7, 0xe5, 0x2e, 0x89, 0x96, 0xb7, 0xa1, 0x6c, 0x55, 0x0b, 0x58, 0x27, + 0xbc, 0xfa, 0x77, 0x10, 0x77, 0x25, 0x1b, 0x82, 0xe7, 0x05, 0x51, 0xd3, + 0xb7, 0xf4, 0xe4, 0x3e, 0xb2, 0x53, 0xa9, 0x8e, 0xa2, 0x1d, 0xfd, 0xa9, + 0x9c, 0x43, 0xab, 0xfd, 0x45, 0xa7, 0xdc, 0x40, 0x43, 0xb9, 0x2f, 0xb6, + 0x99, 0x20, 0x52, 0x26, 0x2f, 0x3a, 0x79, 0x66, 0x48, 0x51, 0xb6, 0x44, + 0xa9, 0x77, 0x99, 0x78, 0x17, 0xef, 0xe4, 0xc2, 0xad, 0x28, 0x15, 0xc1, + 0xc8, 0xbb, 0x64, 0xde, 0xc5, 0x3b, 0x99, 0x8b, 0x17, 0x88, 0xb2, 0x27, + 0x4a, 0xcf, 0x48, 0xc9, 0x5d, 0xbc, 0x93, 0xa1, 0xec, 0x5c, 0x73, 0x10, + 0x65, 0xf5, 0x5a, 0x86, 0x63, 0xa7, 0x86, 0x08, 0x26, 0x22, 0x98, 0x79, + 0x97, 0x9a, 0xf7, 0xf2, 0x4e, 0x23, 0x11, 0x64, 0x22, 0x00, 0xef, 0xc8, + 0xc8, 0x0c, 0x5a, 0x8e, 0xd2, 0x75, 0x84, 0x32, 0x17, 0x04, 0x1b, 0x36, + 0x47, 0x3d, 0xc2, 0x05, 0x5b, 0xa6, 0xc2, 0x9b, 0xe5, 0xcf, 0x07, 0x88, + 0xb4, 0xfe, 0x79, 0x03, 0x05, 0xae, 0x7f, 0x9e, 0xd9, 0x10, 0xfd, 0xb3, + 0x25, 0x1b, 0x32, 0xfb, 0x10, 0xca, 0xd0, 0xcf, 0xc5, 0xea, 0xae, 0xc0, + 0x27, 0x90, 0x73, 0xe1, 0x1e, 0x65, 0xb1, 0x64, 0x7f, 0x66, 0xf8, 0x9b, + 0xfa, 0xe7, 0x05, 0x86, 0xe8, 0x9f, 0xc1, 0xa4, 0xf3, 0x30, 0x04, 0x21, + 0xe6, 0x5c, 0xac, 0xac, 0x72, 0x06, 0x07, 0x4f, 0x16, 0xcf, 0xe3, 0x7b, + 0x9e, 0xa3, 0xfd, 0x31, 0x6e, 0x47, 0xc0, 0x31, 0xa7, 0xc3, 0x90, 0x1f, + 0x4c, 0x6f, 0x1c, 0x86, 0x24, 0xd3, 0x1d, 0x2a, 0x45, 0x2d, 0x82, 0x76, + 0x2a, 0x5b, 0x3d, 0xa2, 0x3a, 0x4a, 0xcb, 0x60, 0x7f, 0xec, 0xa5, 0xd0, + 0xc9, 0xfe, 0xcc, 0x03, 0x0e, 0x2b, 0x1d, 0x12, 0x4d, 0x59, 0xad, 0x3e, + 0x24, 0x9a, 0x9a, 0x31, 0xd1, 0x2d, 0xa6, 0x57, 0x32, 0x86, 0xd8, 0x6f, + 0x70, 0x04, 0x4b, 0x51, 0xac, 0xaa, 0x39, 0x92, 0x85, 0xa2, 0xe4, 0xec, + 0xd2, 0x97, 0x4a, 0xb7, 0x05, 0x5a, 0xa6, 0x37, 0x35, 0xaa, 0xba, 0x47, + 0x87, 0x04, 0x8b, 0x56, 0xad, 0x1c, 0x22, 0x2a, 0x68, 0x3e, 0x0e, 0x91, + 0xc2, 0x09, 0x0b, 0x55, 0xf4, 0x50, 0x4b, 0xb1, 0x40, 0xf5, 0x44, 0xd5, + 0xd2, 0x18, 0x6b, 0xba, 0xdb, 0xc6, 0x76, 0xaa, 0xd1, 0xdf, 0x9a, 0x5e, + 0xb2, 0x21, 0xcf, 0x65, 0x88, 0xe9, 0xb7, 0xb2, 0xea, 0x10, 0x89, 0x76, + 0x65, 0x29, 0xf6, 0x96, 0x19, 0x4a, 0x42, 0x7e, 0x94, 0x7a, 0x70, 0x5d, + 0x54, 0xaf, 0xe7, 0x47, 0x34, 0xdd, 0x15, 0xe4, 0x54, 0x59, 0xd3, 0xa6, + 0x74, 0x48, 0x56, 0xb3, 0xac, 0x74, 0x68, 0xf5, 0x70, 0x79, 0x2a, 0x43, + 0x40, 0xa8, 0x2d, 0xc8, 0x10, 0xa9, 0xcc, 0x5d, 0xf5, 0x47, 0x1d, 0x1a, + 0x4b, 0xf9, 0xcc, 0x52, 0xee, 0xbd, 0x76, 0x16, 0xf3, 0xdd, 0x69, 0xfb, + 0xc4, 0x36, 0x48, 0x70, 0x88, 0x0a, 0x53, 0xbd, 0x36, 0x68, 0x75, 0x76, + 0x34, 0x6d, 0xa7, 0x9b, 0x3e, 0x69, 0x21, 0x36, 0x36, 0xbd, 0x13, 0x88, + 0xd6, 0x0b, 0xfe, 0xa0, 0x15, 0xdd, 0x32, 0xfd, 0x5e, 0xdf, 0x8f, 0x10, + 0x84, 0x0d, 0x46, 0x59, 0xdc, 0x3e, 0x24, 0xd9, 0x0f, 0x87, 0xec, 0x08, + 0xd3, 0xd6, 0x9a, 0x0a, 0x54, 0x6e, 0xdc, 0x0f, 0x48, 0x55, 0x8c, 0xca, + 0x8d, 0x39, 0x98, 0x26, 0x54, 0x6e, 0x1c, 0x93, 0xbd, 0xb3, 0x3c, 0x4a, + 0x5d, 0x7a, 0x2b, 0x10, 0x0d, 0xf2, 0xbd, 0x90, 0x25, 0xea, 0x3b, 0xcc, + 0x3b, 0x19, 0x32, 0x0d, 0x36, 0x64, 0x90, 0x0e, 0x4d, 0xe4, 0x90, 0x0f, + 0x12, 0xff, 0x35, 0xc1, 0x11, 0xc9, 0x9a, 0x3b, 0x53, 0x9a, 0x2a, 0x59, + 0x93, 0x94, 0xd4, 0x6b, 0x3c, 0x50, 0x2a, 0xf0, 0x83, 0xbd, 0x3f, 0x5d, + 0xca, 0xc2, 0x97, 0x26, 0x49, 0xe9, 0xa8, 0xcc, 0xf2, 0x8d, 0x54, 0x5b, + 0x7e, 0x40, 0x04, 0xe7, 0xb9, 0xec, 0xf0, 0xaf, 0x18, 0xa2, 0x11, 0x99, + 0x57, 0x36, 0x44, 0x98, 0x02, 0x7a, 0x2e, 0x81, 0x29, 0xbc, 0x7e, 0x84, + 0x81, 0x38, 0xbb, 0x0c, 0xa6, 0xb1, 0xec, 0x12, 0x0b, 0xcd, 0x38, 0x04, + 0xef, 0x6f, 0x0e, 0xe9, 0xeb, 0x10, 0x15, 0x0c, 0x1a, 0x90, 0x2a, 0x18, + 0xb0, 0x1d, 0xad, 0xa8, 0x68, 0xc1, 0x23, 0x0e, 0x3a, 0x24, 0x37, 0xb8, + 0x5c, 0x0c, 0x89, 0x3e, 0x64, 0x65, 0x98, 0xcd, 0x94, 0x68, 0x67, 0x73, + 0xb1, 0x0b, 0xc2, 0x6c, 0x76, 0x89, 0x9e, 0x9b, 0x5b, 0x3d, 0x10, 0x57, + 0x60, 0x39, 0xd4, 0x21, 0xbd, 0x0f, 0x31, 0x63, 0x73, 0x75, 0x7d, 0x9b, + 0x4c, 0x57, 0x26, 0x28, 0x1e, 0xd6, 0x56, 0xaa, 0xe2, 0xd9, 0x4c, 0xcf, + 0x6e, 0x1e, 0xb7, 0x0b, 0xb4, 0x15, 0xb3, 0xeb, 0xe4, 0x3a, 0xa4, 0x31, + 0xf1, 0xa7, 0x6a, 0x1e, 0xdc, 0x68, 0x9c, 0x3d, 0xd8, 0x1a, 0xb9, 0x09, + 0x50, 0xfc, 0xee, 0xb9, 0x9a, 0xa2, 0xee, 0x7d, 0x48, 0x76, 0x97, 0x37, + 0x32, 0xfb, 0x6f, 0x43, 0xcc, 0x14, 0x5b, 0x1a, 0x5e, 0x3a, 0x9b, 0x4b, + 0x4f, 0xb3, 0x6d, 0xb4, 0x59, 0x36, 0x9e, 0x45, 0x4c, 0x48, 0xd0, 0x61, + 0xc8, 0x7c, 0x1c, 0x32, 0x9d, 0x86, 0x04, 0x86, 0x64, 0xcd, 0x74, 0xc4, + 0x63, 0x3d, 0xd9, 0x8c, 0x07, 0x9b, 0xe5, 0x68, 0x86, 0x0b, 0x7d, 0xec, + 0x9e, 0x21, 0x68, 0x0b, 0x63, 0x2f, 0x36, 0x3a, 0xd0, 0xf4, 0x78, 0x42, + 0x73, 0x14, 0x96, 0x66, 0x54, 0xb3, 0x24, 0xd3, 0x76, 0x31, 0xe3, 0x27, + 0x0f, 0x07, 0xe3, 0x13, 0xd6, 0x53, 0x38, 0x98, 0x93, 0x33, 0x8d, 0xa6, + 0x6a, 0x45, 0x66, 0x5a, 0x5d, 0xab, 0x59, 0x5d, 0xc3, 0xc1, 0x6c, 0x5b, + 0xec, 0xbb, 0x70, 0xb0, 0xfb, 0x66, 0x5a, 0x81, 0xa6, 0x9c, 0x0d, 0x03, + 0x8c, 0xbf, 0xd5, 0xbe, 0x1b, 0xa0, 0x8f, 0x0c, 0x03, 0xac, 0x52, 0xbc, + 0x7a, 0xbf, 0x83, 0x56, 0x34, 0x0c, 0x6e, 0x17, 0xf3, 0xe5, 0xfd, 0x6b, + 0x35, 0xac, 0x13, 0x38, 0x76, 0xa1, 0xa9, 0x56, 0x3a, 0x32, 0xa1, 0x5b, + 0xed, 0x1b, 0x7f, 0x9e, 0xd0, 0x7e, 0x11, 0x1d, 0x5e, 0x11, 0x7c, 0x83, + 0x67, 0xf4, 0x1b, 0x3e, 0xfa, 0xee, 0xdb, 0x77, 0x6a, 0xd6, 0x60, 0x43, + 0x4b, 0xf5, 0x42, 0xb8, 0x47, 0x87, 0xa8, 0xb6, 0x7e, 0x57, 0xcd, 0xa8, + 0x58, 0xae, 0x0c, 0xa3, 0xc4, 0xef, 0x86, 0x72, 0x88, 0x4f, 0x7b, 0x3c, + 0xda, 0xbe, 0xf3, 0x88, 0x7d, 0xb5, 0x7e, 0xba, 0x42, 0xbb, 0x22, 0xf1, + 0x3b, 0x0f, 0x81, 0x57, 0x33, 0x26, 0x1c, 0x0d, 0x93, 0x91, 0x18, 0xec, + 0xbb, 0x1b, 0x46, 0xcb, 0xed, 0xef, 0x7c, 0xdc, 0xcc, 0xdb, 0x6c, 0xfe, + 0x5d, 0xe6, 0x77, 0x75, 0x0a, 0x13, 0xbf, 0x5b, 0x2e, 0x67, 0x3c, 0x1c, + 0x57, 0xb3, 0x71, 0xea, 0x86, 0xc1, 0x17, 0xaf, 0xb4, 0x9a, 0xf8, 0x9d, + 0x5b, 0x93, 0x27, 0x3a, 0x26, 0x62, 0xd0, 0xef, 0x32, 0x09, 0xea, 0xe9, + 0x27, 0xc5, 0xa0, 0xdf, 0x2d, 0xf6, 0xdd, 0x2b, 0xda, 0xc1, 0xef, 0x29, + 0xd5, 0x78, 0x99, 0x7f, 0x77, 0x78, 0xbf, 0xff, 0x4c, 0x17, 0x73, 0x1b, + 0xc8, 0x28, 0x3f, 0x15, 0x30, 0xca, 0x4c, 0xa1, 0xdc, 0x3a, 0xca, 0xed, + 0x98, 0xc0, 0x73, 0x73, 0x0f, 0x1d, 0x81, 0x07, 0x90, 0xed, 0xc0, 0xae, + 0x8e, 0xc6, 0x62, 0xdf, 0x05, 0xb0, 0xef, 0xd8, 0x21, 0xf7, 0x34, 0x37, + 0x48, 0x3e, 0xad, 0x26, 0x04, 0xf0, 0xb3, 0x56, 0xfb, 0x6e, 0xb0, 0x83, + 0x70, 0x35, 0x21, 0x80, 0x1f, 0x55, 0xec, 0xbb, 0xce, 0x0c, 0x98, 0xd5, + 0x84, 0x80, 0xa6, 0xd9, 0xd4, 0x00, 0xf1, 0x6a, 0x5a, 0x63, 0x30, 0x33, + 0x63, 0x64, 0xfa, 0x33, 0x9b, 0x18, 0x45, 0xcc, 0x71, 0xee, 0xa1, 0xcd, + 0xcc, 0x18, 0x50, 0x0c, 0x11, 0x0a, 0x6a, 0xed, 0xe9, 0xd5, 0xfc, 0x40, + 0x9d, 0xb3, 0x44, 0x2a, 0xea, 0xfe, 0xe4, 0x05, 0x27, 0xf1, 0x90, 0x2f, + 0xdc, 0x68, 0x4b, 0x76, 0xe4, 0x06, 0xe5, 0x97, 0x70, 0x46, 0x16, 0xd7, + 0xdb, 0x85, 0x0e, 0xf5, 0x73, 0x2a, 0xa7, 0x97, 0xc4, 0xf9, 0x86, 0x5e, + 0xd4, 0x2f, 0xee, 0x3d, 0xd3, 0x6e, 0x5e, 0x48, 0x70, 0xfc, 0x82, 0x43, + 0xc2, 0xcf, 0x45, 0x8b, 0x76, 0xed, 0xe9, 0xaa, 0x75, 0x4c, 0xb2, 0x34, + 0xd4, 0x5f, 0x63, 0xa2, 0x5f, 0x1c, 0x61, 0xf6, 0x4e, 0x03, 0x0e, 0xf6, + 0xdc, 0xd3, 0x69, 0xec, 0x70, 0x4a, 0xcd, 0x34, 0x67, 0x17, 0x53, 0xd6, + 0xe6, 0x6b, 0x16, 0xd3, 0x7a, 0x83, 0x6d, 0xc2, 0x66, 0xc4, 0x0e, 0x76, + 0xfa, 0xc2, 0xab, 0x2d, 0xd4, 0x7c, 0xc9, 0xb6, 0x73, 0xd2, 0x8e, 0x1d, + 0x8e, 0xc8, 0xb1, 0xc5, 0x06, 0x4f, 0x2d, 0xf6, 0x2f, 0x73, 0xb3, 0x16, + 0xda, 0xdc, 0xf8, 0x99, 0x8c, 0x48, 0x4f, 0x3e, 0x14, 0xfc, 0x4c, 0x49, + 0x6f, 0x99, 0x4e, 0x3b, 0x2c, 0xe8, 0xdb, 0x2f, 0xdc, 0x3d, 0x68, 0xbc, + 0xc8, 0x98, 0x42, 0x50, 0x27, 0x13, 0xfb, 0xe9, 0x67, 0x8c, 0x13, 0xd3, + 0xe3, 0x15, 0xdc, 0xce, 0x8d, 0xfe, 0xa7, 0x79, 0x03, 0x75, 0x13, 0x3b, + 0xba, 0x8f, 0xc7, 0xc8, 0x49, 0x71, 0x55, 0x48, 0xaf, 0xb3, 0xf8, 0x09, + 0x56, 0xaa, 0x72, 0x9c, 0x29, 0xae, 0x0b, 0x45, 0xd2, 0xf3, 0x8b, 0xae, + 0x1b, 0xb7, 0x86, 0xc3, 0x9a, 0xe6, 0xa0, 0x11, 0xdb, 0x0b, 0xd5, 0x38, + 0x71, 0x58, 0xe6, 0xb0, 0xcc, 0x61, 0x33, 0x87, 0x2d, 0x97, 0x0a, 0xc7, + 0xf5, 0x61, 0xa4, 0x9a, 0x09, 0x6c, 0xf4, 0xd4, 0x74, 0xee, 0xf1, 0x55, + 0xfd, 0x96, 0xe8, 0xd5, 0xb9, 0x12, 0x0c, 0x07, 0x6d, 0x38, 0x73, 0x98, + 0xdd, 0xa3, 0x65, 0xa3, 0xe1, 0xcd, 0x4c, 0x96, 0x31, 0xbe, 0xe1, 0x78, + 0x43, 0x3d, 0x10, 0x75, 0x4f, 0x8c, 0x1d, 0x31, 0x7e, 0x47, 0xf4, 0xed, + 0x11, 0x29, 0xf3, 0xe2, 0xe5, 0x29, 0x5a, 0x1c, 0xa7, 0x67, 0x87, 0x5d, + 0xca, 0xd7, 0xfa, 0xf2, 0x17, 0xbb, 0x2c, 0xe8, 0x73, 0x98, 0x2b, 0x26, + 0x88, 0xe9, 0xb5, 0x2e, 0x23, 0xdb, 0x75, 0x9f, 0xce, 0x25, 0xe9, 0xd8, + 0x27, 0x78, 0x98, 0xe6, 0xd0, 0xa7, 0x79, 0x58, 0x9f, 0xf5, 0xba, 0x4f, + 0xef, 0x27, 0xe8, 0xb1, 0x4f, 0xbc, 0xd5, 0xa7, 0x3d, 0xf7, 0x29, 0x37, + 0xfb, 0x2c, 0xd7, 0x7d, 0xee, 0x2d, 0xfa, 0x46, 0x9f, 0xee, 0x26, 0x61, + 0xc2, 0xd7, 0x25, 0x4c, 0x7f, 0x93, 0x30, 0x37, 0x17, 0x7d, 0xd5, 0xe7, + 0xf6, 0xa2, 0x6f, 0xf4, 0x19, 0x6e, 0x12, 0xe6, 0xe6, 0xa2, 0x1f, 0xc8, + 0x0d, 0x0f, 0x25, 0xcc, 0x97, 0x2c, 0xfa, 0x5e, 0x9f, 0x6f, 0xfe, 0x39, + 0x6e, 0xc1, 0x21, 0x72, 0x57, 0x4d, 0x64, 0x9d, 0xd4, 0x82, 0xc9, 0x1e, + 0xcd, 0xbd, 0xd5, 0x67, 0xbc, 0x36, 0xcc, 0xbc, 0x78, 0xc6, 0xa5, 0xe6, + 0x4a, 0xe8, 0x28, 0x5b, 0xab, 0xf7, 0x56, 0xf0, 0x56, 0xb2, 0xd6, 0xe2, + 0xf5, 0x09, 0x8b, 0x53, 0xc2, 0xeb, 0x93, 0xd4, 0xf4, 0xd2, 0xd3, 0xda, + 0x42, 0x49, 0xd9, 0x3c, 0xc9, 0x04, 0x0b, 0x6d, 0x6a, 0x99, 0xcc, 0x18, + 0x3b, 0x7a, 0xa8, 0x38, 0x41, 0x52, 0x41, 0xfd, 0x9b, 0xfa, 0x8e, 0x91, + 0x69, 0x18, 0x73, 0x28, 0x61, 0xa4, 0xad, 0xee, 0xe2, 0x98, 0x67, 0x93, + 0x7b, 0xba, 0xf4, 0x93, 0xbb, 0xa2, 0x63, 0x60, 0x30, 0x60, 0x8c, 0xee, + 0x91, 0x26, 0x78, 0x82, 0xa8, 0x54, 0x13, 0x54, 0x2d, 0xbd, 0x3a, 0x31, + 0x16, 0xcc, 0x3f, 0x9d, 0x07, 0x86, 0xb2, 0x73, 0xa0, 0xf3, 0x37, 0x45, + 0x1a, 0x0e, 0x63, 0x62, 0xb6, 0xc5, 0x1c, 0x62, 0x99, 0x71, 0xeb, 0xe8, + 0xba, 0x13, 0x3a, 0xb0, 0x1d, 0x2a, 0x56, 0x03, 0xd5, 0x2d, 0xfd, 0xeb, + 0xe6, 0x10, 0x04, 0x05, 0x3a, 0x66, 0xba, 0xb6, 0x8b, 0xfd, 0xea, 0x49, + 0x73, 0xec, 0xdc, 0xcc, 0x0d, 0x56, 0x84, 0xb6, 0x87, 0x13, 0xb7, 0x1a, + 0x91, 0xf1, 0x52, 0x68, 0x9a, 0x94, 0xea, 0x52, 0xad, 0xce, 0x39, 0x8f, + 0x9c, 0x5d, 0xd3, 0xd2, 0x52, 0xf8, 0x72, 0x74, 0x9f, 0x9c, 0xdd, 0xef, + 0x59, 0xec, 0xef, 0x9f, 0xdd, 0x17, 0xef, 0xec, 0x3d, 0x46, 0x09, 0x8e, + 0x2e, 0x3a, 0xba, 0x13, 0xdf, 0xb5, 0xce, 0x77, 0x82, 0xa4, 0xfb, 0x24, + 0x1b, 0x0f, 0xee, 0x93, 0x04, 0xf7, 0xe8, 0x4f, 0x52, 0xd1, 0x99, 0x9b, + 0x54, 0xa0, 0x21, 0x19, 0x32, 0x3a, 0x08, 0x59, 0x8b, 0x0c, 0x25, 0x02, + 0xbe, 0x1d, 0xe3, 0x60, 0x16, 0x26, 0x92, 0xd6, 0x4f, 0x05, 0x56, 0x3a, + 0x88, 0xcb, 0x96, 0x0b, 0xfe, 0xe6, 0xad, 0xc5, 0x5b, 0xb3, 0xdb, 0x09, + 0x93, 0xb7, 0x7c, 0x83, 0x56, 0x6f, 0xcd, 0x17, 0x9b, 0x16, 0xd8, 0xed, + 0xa0, 0xa0, 0xdc, 0x45, 0xad, 0xb6, 0xee, 0x21, 0x63, 0x71, 0x60, 0xc2, + 0x8e, 0x8d, 0x1e, 0x7b, 0x9b, 0xaa, 0x65, 0x4c, 0xe4, 0x53, 0xe3, 0xa9, + 0x94, 0x0e, 0x57, 0xe2, 0x8f, 0x98, 0x04, 0x26, 0xd3, 0xe2, 0xda, 0x83, + 0x8f, 0x02, 0xae, 0x11, 0x53, 0xc4, 0x8a, 0xc8, 0xa2, 0x1d, 0xae, 0x03, + 0xd6, 0x17, 0xf0, 0xeb, 0x28, 0xc6, 0x30, 0xb3, 0x6e, 0x48, 0xd1, 0xc7, + 0xc9, 0x19, 0x9c, 0xce, 0x48, 0x95, 0x21, 0xf8, 0x6d, 0xbf, 0x5e, 0x6b, + 0xce, 0x91, 0xfd, 0xba, 0xdf, 0xa0, 0x43, 0x24, 0xe4, 0x6c, 0xbf, 0xbb, + 0x18, 0x95, 0x1d, 0xc4, 0x2f, 0xca, 0xbd, 0x39, 0x4a, 0x13, 0x3c, 0x27, + 0x75, 0x73, 0x5a, 0xe5, 0x25, 0x7d, 0xe5, 0x43, 0x63, 0x3b, 0xb7, 0xb6, + 0xe6, 0x07, 0x2f, 0x5d, 0x25, 0xe0, 0x04, 0x92, 0x8e, 0x20, 0x32, 0xb8, + 0x9c, 0xe1, 0x04, 0x06, 0x50, 0xe8, 0x12, 0xf0, 0xd8, 0xf8, 0xf3, 0x86, + 0xf7, 0x20, 0xe8, 0x00, 0xfe, 0x0a, 0x60, 0x1b, 0xdb, 0x87, 0x0c, 0x39, + 0x9f, 0xb0, 0x1f, 0x63, 0x8b, 0x7d, 0xe1, 0x7e, 0xfc, 0xce, 0xe1, 0x7f, + 0xee, 0xda, 0xff, 0xf2, 0xc3, 0x8d, 0x58, 0x76, 0xd9, 0x68, 0x0f, 0x00, + 0xfc, 0xa1, 0x7c, 0xa3, 0x8f, 0x9b, 0x2c, 0x88, 0x89, 0xbc, 0xba, 0x13, + 0x13, 0x69, 0x0b, 0xa2, 0x57, 0x37, 0x62, 0x22, 0xea, 0xcf, 0x23, 0xd0, + 0x7c, 0x15, 0x13, 0x29, 0x1d, 0x75, 0xef, 0x78, 0x15, 0x13, 0x99, 0xe2, + 0x21, 0xb0, 0xa0, 0x91, 0x22, 0x8f, 0x89, 0xcc, 0x46, 0xbf, 0xa2, 0xe2, + 0xbb, 0x58, 0x28, 0x04, 0x11, 0x0a, 0xfc, 0xc8, 0x67, 0x2c, 0x28, 0xec, + 0x0f, 0x66, 0xe2, 0xa8, 0x6a, 0xf5, 0x98, 0xc8, 0xd8, 0xc2, 0x68, 0x9a, + 0x20, 0xd7, 0x88, 0x89, 0x78, 0x44, 0xb8, 0x7e, 0xef, 0x31, 0x11, 0xb9, + 0x47, 0x6b, 0xfc, 0xed, 0xc1, 0x97, 0x95, 0x37, 0x32, 0x35, 0x5d, 0x43, + 0xc4, 0x3d, 0xa7, 0xde, 0x63, 0xea, 0xa6, 0x5f, 0x30, 0xe3, 0xcc, 0x28, + 0x76, 0xb6, 0x61, 0x4e, 0x83, 0x84, 0x13, 0x6e, 0x92, 0x60, 0xb5, 0x0e, + 0x63, 0xd4, 0x6b, 0x09, 0x0c, 0x8c, 0x5b, 0x58, 0x38, 0xf0, 0x37, 0x6a, + 0x07, 0x2d, 0x03, 0x1f, 0x99, 0x77, 0x1f, 0x19, 0x62, 0x9f, 0x1a, 0x14, + 0x8a, 0x64, 0xdb, 0x03, 0x1e, 0xe7, 0x96, 0x45, 0x92, 0x61, 0x7a, 0x0f, + 0x93, 0x62, 0x44, 0xc1, 0x70, 0x2c, 0x70, 0xc3, 0x4d, 0x82, 0xc1, 0x96, + 0x6e, 0xca, 0x1f, 0x82, 0xca, 0x13, 0x87, 0x4d, 0x1c, 0x96, 0x39, 0xcc, + 0x63, 0xca, 0x0b, 0x87, 0x2d, 0xe0, 0x77, 0xbb, 0x47, 0xc7, 0x7b, 0x34, + 0xde, 0x48, 0xe5, 0xc0, 0xfc, 0x23, 0x87, 0x8d, 0x1c, 0x36, 0x71, 0x58, + 0xe6, 0xb0, 0xcc, 0x61, 0x1e, 0xd2, 0xf1, 0xdc, 0x13, 0x4d, 0xcf, 0x72, + 0x38, 0x23, 0x33, 0x0f, 0xc1, 0xb9, 0x71, 0x9d, 0xe5, 0x4a, 0xac, 0xaa, + 0xa5, 0x07, 0x21, 0xfa, 0x03, 0xe3, 0x05, 0x8f, 0x9c, 0xd1, 0x23, 0x97, + 0xf6, 0xd7, 0x0d, 0x85, 0x38, 0xa2, 0xff, 0x0f, 0x02, 0xc7, 0x66, 0x05, + 0x3b, 0xdc, 0x00, 0x00 +}; + +const GFXglyph Oswald_Medium80pt7bGlyphs[] PROGMEM = { + { 0, 1, 1, 38, 0, 0 }, // 0x20 ' ' + { 1, 21, 127, 37, 8, -126 }, // 0x21 '!' + { 335, 43, 43, 49, 4, -126 }, // 0x22 '"' + { 567, 68, 127, 80, 6, -126 }, // 0x23 '#' + { 1647, 67, 156, 76, 5, 116 }, // 0x24 '$' + { 2954, 136, 127, 149, 6, -126 }, // 0x25 '%' + { 5113, 76, 130, 92, 8, -127 }, // 0x26 '&' + { 6348, 18, 43, 23, 4, -126 }, // 0x27 ''' + { 6445, 35, 157, 50, 10, -127 }, // 0x28 '(' + { 7132, 34, 157, 46, 5, -127 }, // 0x29 ')' + { 7800, 54, 55, 64, 7, -126 }, // 0x2A '*' + { 8172, 60, 64, 68, 4, -96 }, // 0x2B '+' + { 8652, 20, 42, 33, 7, -20 }, // 0x2C ',' + { 8757, 38, 14, 49, 6, -52 }, // 0x2D '-' + { 8824, 21, 21, 34, 7, -20 }, // 0x2E '.' + { 8880, 50, 127, 62, 6, -126 }, // 0x2F '/' + { 9674, 66, 130, 84, 9, -127 }, // 0x30 '0' + { 10747, 41, 127, 60, 4, -126 }, // 0x31 '1' + { 11398, 67, 128, 78, 6, -127 }, // 0x32 '2' + { 12470, 66, 130, 78, 6, -127 }, // 0x33 '3' + { 13543, 71, 127, 79, 5, -126 }, // 0x34 '4' + { 14671, 65, 129, 77, 7, -126 }, // 0x35 '5' + { 15720, 66, 130, 82, 9, -127 }, // 0x36 '6' + { 16793, 56, 127, 65, 3, -126 }, // 0x37 '7' + { 17682, 66, 130, 80, 7, -127 }, // 0x38 '8' + { 18755, 67, 130, 82, 6, -127 }, // 0x39 '9' + { 19844, 20, 74, 37, 10, -85 }, // 0x3A ':' + { 20029, 21, 102, 39, 10, -88 }, // 0x3B ';' + { 20297, 47, 67, 60, 6, -98 }, // 0x3C '<' + { 20691, 49, 42, 67, 9, -85 }, // 0x3D '=' + { 20949, 47, 67, 60, 9, -98 }, // 0x3E '>' + { 21343, 64, 128, 76, 6, -127 }, // 0x3F '?' + { 22367, 132, 147, 145, 8, -126 }, // 0x40 '@' + { 24793, 76, 127, 82, 3, -126 }, // 0x41 'A' + { 26000, 72, 127, 87, 10, -126 }, // 0x42 'B' + { 27143, 70, 130, 85, 8, -127 }, // 0x43 'C' + { 28281, 70, 127, 87, 10, -126 }, // 0x44 'D' + { 29393, 53, 127, 67, 10, -126 }, // 0x45 'E' + { 30235, 52, 127, 65, 10, -126 }, // 0x46 'F' + { 31061, 71, 130, 88, 8, -127 }, // 0x47 'G' + { 32215, 72, 127, 92, 10, -126 }, // 0x48 'H' + { 33358, 23, 127, 43, 10, -126 }, // 0x49 'I' + { 33724, 39, 130, 51, 2, -126 }, // 0x4A 'J' + { 34358, 72, 127, 83, 10, -126 }, // 0x4B 'K' + { 35501, 53, 127, 66, 10, -126 }, // 0x4C 'L' + { 36343, 89, 127, 107, 9, -126 }, // 0x4D 'M' + { 37756, 66, 127, 85, 10, -126 }, // 0x4E 'N' + { 38804, 73, 130, 88, 8, -127 }, // 0x4F 'O' + { 39991, 69, 127, 83, 10, -126 }, // 0x50 'P' + { 41087, 73, 153, 88, 8, -127 }, // 0x51 'Q' + { 42484, 72, 127, 88, 10, -126 }, // 0x52 'R' + { 43627, 67, 130, 77, 6, -127 }, // 0x53 'S' + { 44716, 63, 127, 67, 2, -126 }, // 0x54 'T' + { 45717, 71, 129, 89, 9, -126 }, // 0x55 'U' + { 46862, 74, 127, 81, 3, -126 }, // 0x56 'V' + { 48037, 102, 127, 112, 5, -126 }, // 0x57 'W' + { 49657, 75, 127, 78, 2, -126 }, // 0x58 'X' + { 50848, 73, 127, 76, 2, -126 }, // 0x59 'Y' + { 52007, 58, 127, 67, 5, -126 }, // 0x5A 'Z' + // Euro sign ([) - ASCII code 91 + { 52928, 79, 130, 83, 0, -127 }, // 0x5B '[' + // Backslash placeholder - ASCII code 92 + { 0, 0, 0, 0, 0, 0 }, // 0x5C '\' + // Pound sign (]) - ASCII code 93 + { 54212, 63, 128, 70, 2, -127 }, // 0x5D ']' + // Yen sign (^) - ASCII code 94 + { 55220, 73, 127, 73, 1, -126 } // 0x5E '^' + }; + + +// const GFXfont Oswald_Medium80pt7b PROGMEM = { +// (uint8_t *)Oswald_Medium80pt7bBitmaps, +// (GFXglyph *)Oswald_Medium80pt7bGlyphs, +// 0x20, 0x5A, 232 }; + +// Approx. 53348 bytes +// Font properties +static constexpr FontData Oswald_Medium80pt7b_Properties = { + Oswald_Medium80pt7bBitmaps_Gzip, + Oswald_Medium80pt7bGlyphs, + sizeof(Oswald_Medium80pt7bBitmaps_Gzip), + 56626, // Original size + 0x20, // First char + 0x5E, // Last char + 232 // yAdvance +}; diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 8849817..29d7eee 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -65,6 +65,7 @@ #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" #define DEFAULT_LED_FLASH_ON_ZAP true #define DEFAULT_FL_FLASH_ON_ZAP true +#define DEFAULT_FONT_NAME "antonio" #define DEFAULT_HTTP_AUTH_ENABLED false #define DEFAULT_HTTP_AUTH_USERNAME "btclock" @@ -93,3 +94,7 @@ enum DataSourceType { }; #define DEFAULT_DATA_SOURCE BTCLOCK_SOURCE + +#ifndef DEFAULT_BOOT_TEXT +#define DEFAULT_BOOT_TEXT "BTCLOCK" +#endif \ No newline at end of file diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 151b1c0..cca11a7 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -59,7 +59,7 @@ MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { MCP23X17_Pin(mcp2, 5), MCP23X17_Pin(mcp2, 7), }; -#else +#else Native_Pin EPD_CS[NUM_SCREENS] = { Native_Pin(2), Native_Pin(4), @@ -123,10 +123,20 @@ QueueHandle_t updateQueue; int fgColor = GxEPD_WHITE; int bgColor = GxEPD_BLACK; -#define FONT_SMALL Antonio_SemiBold20pt7b -#define FONT_BIG Antonio_SemiBold90pt7b -#define FONT_MEDIUM Antonio_SemiBold40pt7b -#define FONT_SATSYMBOL Satoshi_Symbol90pt7b +struct FontFamily { + GFXfont* big; + GFXfont* medium; + GFXfont* small; +}; + +FontFamily antonioFonts = {nullptr, nullptr, nullptr}; +FontFamily oswaldFonts = {nullptr, nullptr, nullptr}; + +const GFXfont *FONT_SMALL; +const GFXfont *FONT_BIG; +const GFXfont *FONT_MEDIUM; +const GFXfont *FONT_SATSYMBOL; + std::mutex epdUpdateMutex; std::mutex epdMutex[NUM_SCREENS]; @@ -149,8 +159,83 @@ void forceFullRefresh() } } +GFXfont font90; + void setupDisplays() { + String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); // Default to antonio + + if (fontName == "antonio") + { + // Load Antonio fonts + antonioFonts.big = FontLoader::loadCompressedFont( + Antonio_SemiBold90pt7b_Properties.compressedData, + Antonio_SemiBold90pt7b_Properties.glyphs, + Antonio_SemiBold90pt7b_Properties.compressedSize, + Antonio_SemiBold90pt7b_Properties.originalSize, + Antonio_SemiBold90pt7b_Properties.first, + Antonio_SemiBold90pt7b_Properties.last, + Antonio_SemiBold90pt7b_Properties.yAdvance); + + antonioFonts.medium = FontLoader::loadCompressedFont( + Antonio_SemiBold40pt7b_Properties.compressedData, + Antonio_SemiBold40pt7b_Properties.glyphs, + Antonio_SemiBold40pt7b_Properties.compressedSize, + Antonio_SemiBold40pt7b_Properties.originalSize, + Antonio_SemiBold40pt7b_Properties.first, + Antonio_SemiBold40pt7b_Properties.last, + Antonio_SemiBold40pt7b_Properties.yAdvance); + + antonioFonts.small = FontLoader::loadCompressedFont( + Antonio_SemiBold20pt7b_Properties.compressedData, + Antonio_SemiBold20pt7b_Properties.glyphs, + Antonio_SemiBold20pt7b_Properties.compressedSize, + Antonio_SemiBold20pt7b_Properties.originalSize, + Antonio_SemiBold20pt7b_Properties.first, + Antonio_SemiBold20pt7b_Properties.last, + Antonio_SemiBold20pt7b_Properties.yAdvance); + + FONT_BIG = antonioFonts.big; + FONT_MEDIUM = antonioFonts.medium; + FONT_SMALL = antonioFonts.small; + } + else if (fontName == "oswald") + { + // Load Oswald fonts + oswaldFonts.big = FontLoader::loadCompressedFont( + Oswald_Medium80pt7b_Properties.compressedData, + Oswald_Medium80pt7b_Properties.glyphs, + Oswald_Medium80pt7b_Properties.compressedSize, + Oswald_Medium80pt7b_Properties.originalSize, + Oswald_Medium80pt7b_Properties.first, + Oswald_Medium80pt7b_Properties.last, + Oswald_Medium80pt7b_Properties.yAdvance); + + oswaldFonts.medium = FontLoader::loadCompressedFont( + Oswald_Medium30pt7b_Properties.compressedData, + Oswald_Medium30pt7b_Properties.glyphs, + Oswald_Medium30pt7b_Properties.compressedSize, + Oswald_Medium30pt7b_Properties.originalSize, + Oswald_Medium30pt7b_Properties.first, + Oswald_Medium30pt7b_Properties.last, + Oswald_Medium30pt7b_Properties.yAdvance); + + oswaldFonts.small = FontLoader::loadCompressedFont( + Oswald_Medium20pt7b_Properties.compressedData, + Oswald_Medium20pt7b_Properties.glyphs, + Oswald_Medium20pt7b_Properties.compressedSize, + Oswald_Medium20pt7b_Properties.originalSize, + Oswald_Medium20pt7b_Properties.first, + Oswald_Medium20pt7b_Properties.last, + Oswald_Medium20pt7b_Properties.yAdvance); + + FONT_BIG = oswaldFonts.big; + FONT_MEDIUM = oswaldFonts.medium; + FONT_SMALL = oswaldFonts.small; + } + + FONT_SATSYMBOL = &Satoshi_Symbol90pt7b; + std::lock_guard lockMcp(mcpMutex); for (uint i = 0; i < NUM_SCREENS; i++) @@ -160,13 +245,10 @@ void setupDisplays() updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE*2, NULL, 11, NULL); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, NULL, 11, NULL); for (uint i = 0; i < NUM_SCREENS; i++) { - // epdUpdateSemaphore[i] = xSemaphoreCreateBinary(); - // xSemaphoreGive(epdUpdateSemaphore[i]); - int *taskParam = new int; *taskParam = i; @@ -184,239 +266,255 @@ void setupDisplays() } else { - #ifdef IS_BTCLOCK_V8 - epdContent = {"B", "T", "C", "L", "O", "C", "K", "v8"}; - #else - epdContent = {"B", "T", "C", "L", "O", "C", "K"}; - #endif + // Get custom text from preferences or use default "BTCLOCK" + String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); + + // Initialize array with spaces + std::array newContent; + newContent.fill(" "); + + // Fill in the custom text, truncating if longer than NUM_SCREENS + for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { + newContent[i] = String(customText[i]); + } + + epdContent = newContent; } - setEpdContent(epdContent); + setEpdContent(epdContent); } void setEpdContent(std::array newEpdContent) { - setEpdContent(newEpdContent, false); + setEpdContent(newEpdContent, false); } void setEpdContent(std::array newEpdContent) { - std::array conv; + std::array conv; - for (size_t i = 0; i < newEpdContent.size(); ++i) - { - conv[i] = String(newEpdContent[i].c_str()); - } + for (size_t i = 0; i < newEpdContent.size(); ++i) + { + conv[i] = String(newEpdContent[i].c_str()); + } - return setEpdContent(conv); + return setEpdContent(conv); } void setEpdContent(std::array newEpdContent, bool forceUpdate) { - std::lock_guard lock(epdUpdateMutex); + std::lock_guard lock(epdUpdateMutex); - waitUntilNoneBusy(); + waitUntilNoneBusy(); - for (uint i = 0; i < NUM_SCREENS; i++) - { - if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) + for (uint i = 0; i < NUM_SCREENS; i++) { - epdContent[i] = newEpdContent[i]; - UpdateDisplayTaskItem dispUpdate = {i}; - xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY); + if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) + { + epdContent[i] = newEpdContent[i]; + UpdateDisplayTaskItem dispUpdate = {i}; + xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY); + } } - } } void prepareDisplayUpdateTask(void *pvParameters) { - UpdateDisplayTaskItem receivedItem; + UpdateDisplayTaskItem receivedItem; - while (1) - { - // Wait for a work item to be available in the queue - if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) + while (1) { - uint epdIndex = receivedItem.dispNum; - std::lock_guard lock(epdMutex[epdIndex]); - // displays[epdIndex].init(0, false); // Little longer reset duration - // because of MCP - - bool updatePartial = true; - - if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL) - { - String top = epdContent[epdIndex].substring( - 0, epdContent[epdIndex].indexOf("/")); - String bottom = epdContent[epdIndex].substring( - epdContent[epdIndex].indexOf("/") + 1); - splitText(epdIndex, top, bottom, updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("qr"))) - { - renderQr(epdIndex, epdContent[epdIndex], updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("mdi"))) - { - bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial); - if (!updated) { - continue; - } - } - else if (epdContent[epdIndex].length() > 5) - { - renderText(epdIndex, epdContent[epdIndex], updatePartial); - } - else - { - if (epdContent[epdIndex].length() == 2) { - showChars(epdIndex, epdContent[epdIndex], updatePartial, &FONT_BIG); - } - else if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) - { - if (epdContent[epdIndex].equals("STS")) - { - showDigit(epdIndex, 'S', updatePartial, - &FONT_SATSYMBOL); - } - else - { - showChars(epdIndex, epdContent[epdIndex], updatePartial, - &FONT_MEDIUM); - } - } - else + // Wait for a work item to be available in the queue + if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) { + uint epdIndex = receivedItem.dispNum; + std::lock_guard lock(epdMutex[epdIndex]); + // displays[epdIndex].init(0, false); // Little longer reset duration + // because of MCP - showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, - &FONT_BIG); - } - } + bool updatePartial = true; - xTaskNotifyGive(tasks[epdIndex]); + if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL) + { + String top = epdContent[epdIndex].substring( + 0, epdContent[epdIndex].indexOf("/")); + String bottom = epdContent[epdIndex].substring( + epdContent[epdIndex].indexOf("/") + 1); + splitText(epdIndex, top, bottom, updatePartial); + } + else if (epdContent[epdIndex].startsWith(F("qr"))) + { + renderQr(epdIndex, epdContent[epdIndex], updatePartial); + } + else if (epdContent[epdIndex].startsWith(F("mdi"))) + { + bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial); + if (!updated) + { + continue; + } + } + else if (epdContent[epdIndex].length() > 5) + { + renderText(epdIndex, epdContent[epdIndex], updatePartial); + } + else + { + if (epdContent[epdIndex].length() == 2) + { + showChars(epdIndex, epdContent[epdIndex], updatePartial, FONT_BIG); + } + else if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) + { + if (epdContent[epdIndex].equals("STS")) + { + showDigit(epdIndex, 'S', updatePartial, + FONT_SATSYMBOL); + } + else + { + showChars(epdIndex, epdContent[epdIndex], updatePartial, + FONT_MEDIUM); + } + } + else + { + + showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, + FONT_BIG); + } + } + + xTaskNotifyGive(tasks[epdIndex]); + } } - } } extern "C" void updateDisplay(void *pvParameters) noexcept { - const int epdIndex = *(int *)pvParameters; - delete (int *)pvParameters; - - for (;;) - { - // Wait for the task notification - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - std::lock_guard lock(epdMutex[epdIndex]); + const int epdIndex = *(int *)pvParameters; + delete (int *)pvParameters; + for (;;) { - std::lock_guard lockMcp(mcpMutex); + // Wait for the task notification + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - displays[epdIndex].init(0, false, 40); + std::lock_guard lock(epdMutex[epdIndex]); + + { + std::lock_guard lockMcp(mcpMutex); + + displays[epdIndex].init(0, false, 40); + } + uint count = 0; + while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) + { + vTaskDelay(pdMS_TO_TICKS(100)); + count++; + } + + bool updatePartial = true; + + // Full Refresh every x minutes + if (!lastFullRefresh[epdIndex] || + (millis() - lastFullRefresh[epdIndex]) > + (preferences.getUInt("fullRefreshMin", + DEFAULT_MINUTES_FULL_REFRESH) * + 60 * 1000)) + { + updatePartial = false; + } + + char tries = 0; + while (tries < 3) + { + if (displays[epdIndex].displayWithReturn(updatePartial)) + { + displays[epdIndex].powerOff(); + currentEpdContent[epdIndex] = epdContent[epdIndex]; + if (!updatePartial) + lastFullRefresh[epdIndex] = millis(); + + if (eventSourceTaskHandle != NULL) + xTaskNotifyGive(eventSourceTaskHandle); + + break; + } + + vTaskDelay(pdMS_TO_TICKS(100)); + tries++; + } } - uint count = 0; - while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) - { - vTaskDelay(pdMS_TO_TICKS(100)); - count++; - } - - bool updatePartial = true; - - // Full Refresh every x minutes - if (!lastFullRefresh[epdIndex] || - (millis() - lastFullRefresh[epdIndex]) > - (preferences.getUInt("fullRefreshMin", - DEFAULT_MINUTES_FULL_REFRESH) * - 60 * 1000)) - { - updatePartial = false; - } - - char tries = 0; - while (tries < 3) - { - if (displays[epdIndex].displayWithReturn(updatePartial)) - { - displays[epdIndex].powerOff(); - currentEpdContent[epdIndex] = epdContent[epdIndex]; - if (!updatePartial) - lastFullRefresh[epdIndex] = millis(); - - if (eventSourceTaskHandle != NULL) - xTaskNotifyGive(eventSourceTaskHandle); - - break; - } - - vTaskDelay(pdMS_TO_TICKS(100)); - tries++; - } - } } void splitText(const uint dispNum, const String &top, const String &bottom, bool partial) { - if(preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { - displays[dispNum].setRotation(1); - } else { - displays[dispNum].setRotation(2); - } - displays[dispNum].setFont(&FONT_SMALL); - displays[dispNum].setTextColor(getFgColor()); + if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) + { + displays[dispNum].setRotation(1); + } + else + { + displays[dispNum].setRotation(2); + } + displays[dispNum].setFont(FONT_SMALL); + displays[dispNum].setTextColor(getFgColor()); - // Top text - int16_t ttbx, ttby; - uint16_t ttbw, ttbh; - displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); - uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx; - uint16_t ty = - ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; + // Top text + int16_t ttbx, ttby; + uint16_t ttbw, ttbh; + displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); + uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx; + uint16_t ty = + ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; - // Bottom text - int16_t tbbx, tbby; - uint16_t tbbw, tbbh; - displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); - uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx; - uint16_t by = - ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; + // Bottom text + int16_t tbbx, tbby; + uint16_t tbbw, tbbh; + displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); + uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx; + uint16_t by = + ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; - // Make separator as wide as the shortest text. - uint16_t lineWidth, lineX; - if (tbbw < ttbh) - lineWidth = tbbw; - else - lineWidth = ttbw; - lineX = round((displays[dispNum].width() - lineWidth) / 2); + // Make separator as wide as the shortest text. + uint16_t lineWidth, lineX; + if (tbbw < ttbh) + lineWidth = tbbw; + else + lineWidth = ttbw; + lineX = round((displays[dispNum].width() - lineWidth) / 2); - displays[dispNum].fillScreen(getBgColor()); - displays[dispNum].setCursor(tx, ty); - displays[dispNum].print(top); - displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3, - lineWidth, 6, 3, getFgColor()); - displays[dispNum].setCursor(bx, by); - displays[dispNum].print(bottom); + displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].setCursor(tx, ty); + displays[dispNum].print(top); + displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3, + lineWidth, 6, 3, getFgColor()); + displays[dispNum].setCursor(bx, by); + displays[dispNum].print(bottom); } // Consolidate common display setup code into a helper function -void setupDisplay(const uint dispNum, const GFXfont *font) { +void setupDisplay(const uint dispNum, const GFXfont *font) +{ displays[dispNum].setRotation(2); displays[dispNum].setFont(font); displays[dispNum].setTextColor(getFgColor()); displays[dispNum].fillScreen(getBgColor()); } -void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) { +void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) +{ String str(chr); - if (chr == '.') { + if (chr == '.') + { str = "!"; } - + setupDisplay(dispNum, font); - + int16_t tbx, tby; uint16_t tbw, tbh; displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); @@ -427,57 +525,64 @@ void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) displays[dispNum].setCursor(x, y); displays[dispNum].print(str); - if (chr == '.') { - displays[dispNum].fillRect(x, y, displays[dispNum].width(), - round(displays[dispNum].height() * 0.9), getBgColor()); + if (chr == '.') + { + displays[dispNum].fillRect(x, y, displays[dispNum].width(), + round(displays[dispNum].height() * 0.9), getBgColor()); } } -int16_t calculateDescent(const GFXfont *font) { - int16_t maxDescent = 0; - for (uint16_t i = font->first; i <= font->last; i++) { - GFXglyph *glyph = &font->glyph[i - font->first]; - int16_t descent = glyph->yOffset; - if (descent > maxDescent) { - maxDescent = descent; +int16_t calculateDescent(const GFXfont *font) +{ + int16_t maxDescent = 0; + for (uint16_t i = font->first; i <= font->last; i++) + { + GFXglyph *glyph = &font->glyph[i - font->first]; + int16_t descent = glyph->yOffset; + if (descent > maxDescent) + { + maxDescent = descent; + } } - } - return maxDescent; + return maxDescent; } void showChars(const uint dispNum, const String &chars, bool partial, const GFXfont *font) { - setupDisplay(dispNum, font); + setupDisplay(dispNum, font); + int16_t tbx, tby; + uint16_t tbw, tbh; + displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); - int16_t tbx, tby; - uint16_t tbw, tbh; - displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); + // center the bounding box by transposition of the origin: + uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; + uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; - // center the bounding box by transposition of the origin: - uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; - uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; + for (int i = 0; i < chars.length(); i++) + { + char c = chars[i]; + if (c == '.' || c == ',') + { + // For the dot, calculate its specific descent + GFXglyph *dotGlyph = &font->glyph[c - font->first]; + int16_t dotDescent = dotGlyph->yOffset; - for (int i = 0; i < chars.length(); i++) { - char c = chars[i]; - if (c == '.' || c == ',') { - // For the dot, calculate its specific descent - GFXglyph *dotGlyph = &font->glyph[c -font->first]; - int16_t dotDescent = dotGlyph->yOffset; - - // Draw the dot with adjusted y-position - displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8); - displays[dispNum].print(c); - } else { - // For other characters, use the original y-position - displays[dispNum].setCursor(x, y); - displays[dispNum].print(c); + // Draw the dot with adjusted y-position + displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8); + displays[dispNum].print(c); + } + else + { + // For other characters, use the original y-position + displays[dispNum].setCursor(x, y); + displays[dispNum].print(c); + } + + // Move x-position for the next character + x += font->glyph[c - font->first].xAdvance; } - - // Move x-position for the next character - x += font->glyph[c - font->first].xAdvance; - } } int getBgColor() { return bgColor; } @@ -490,142 +595,146 @@ void setFgColor(int color) { fgColor = color; } std::array getCurrentEpdContent() { - return currentEpdContent; + return currentEpdContent; } void renderText(const uint dispNum, const String &text, bool partial) { - displays[dispNum].setRotation(2); - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); - displays[dispNum].setTextColor(GxEPD_BLACK); - displays[dispNum].setCursor(0, 50); + displays[dispNum].setRotation(2); + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(GxEPD_WHITE); + displays[dispNum].setTextColor(GxEPD_BLACK); + displays[dispNum].setCursor(0, 50); - std::stringstream ss; - ss.str(text.c_str()); + std::stringstream ss; + ss.str(text.c_str()); - std::string line; + std::string line; - while (std::getline(ss, line, '\n')) - { - if (line.rfind("*", 0) == 0) + while (std::getline(ss, line, '\n')) { - line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); + if (line.rfind("*", 0) == 0) + { + line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); - displays[dispNum].setFont(&FreeSansBold9pt7b); - displays[dispNum].println(line.c_str()); + displays[dispNum].setFont(&FreeSansBold9pt7b); + displays[dispNum].println(line.c_str()); + } + else + { + displays[dispNum].setFont(&FreeSans9pt7b); + displays[dispNum].println(line.c_str()); + } } - else - { - displays[dispNum].setFont(&FreeSans9pt7b); - displays[dispNum].println(line.c_str()); - } - } } bool renderIcon(const uint dispNum, const String &text, bool partial) { - displays[dispNum].setRotation(2); + displays[dispNum].setRotation(2); - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); displays[dispNum].fillScreen(getBgColor()); displays[dispNum].setTextColor(getFgColor()); - uint iconIndex = 0; - uint width = 122; - uint height = 122; - if (text.endsWith("rocket")) { - iconIndex = 1; - } - else if (text.endsWith("lnbolt")) { - iconIndex = 2; - } - else if (text.endsWith("bitaxe")) { - width = 88; - height = 220; - iconIndex = 3; - } - else if (text.endsWith("miningpool")) { - LogoData logo = getMiningPoolLogo(); + uint iconIndex = 0; + uint width = 122; + uint height = 122; + if (text.endsWith("rocket")) + { + iconIndex = 1; + } + else if (text.endsWith("lnbolt")) + { + iconIndex = 2; + } + else if (text.endsWith("bitaxe")) + { + width = 88; + height = 220; + iconIndex = 3; + } + else if (text.endsWith("miningpool")) + { + LogoData logo = getMiningPoolLogo(); - if (logo.size == 0) { - Serial.println(F("No logo found")); - return false; - } + if (logo.size == 0) + { + Serial.println(F("No logo found")); + return false; + } - int x_offset = (displays[dispNum].width() - logo.width) / 2; - int y_offset = (displays[dispNum].height() - logo.height) / 2; - // Close the file + int x_offset = (displays[dispNum].width() - logo.width) / 2; + int y_offset = (displays[dispNum].height() - logo.height) / 2; + // Close the file + + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, logo.width, logo.height, getFgColor()); + return true; + } + + int x_offset = (displays[dispNum].width() - width) / 2; + int y_offset = (displays[dispNum].height() - height) / 2; + + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); - displays[dispNum].drawInvertedBitmap(x_offset,y_offset, logo.data, logo.width, logo.height, getFgColor()); return true; - } - - - int x_offset = (displays[dispNum].width() - width) / 2; - int y_offset = (displays[dispNum].height() - height) / 2; - - - displays[dispNum].drawInvertedBitmap(x_offset,y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); - - return true; -// displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); - - + // displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); } void renderQr(const uint dispNum, const String &text, bool partial) { #ifdef USE_QR - uint8_t tempBuffer[800]; - bool ok = qrcodegen_encodeText( - text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW, - qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); + uint8_t tempBuffer[800]; + bool ok = qrcodegen_encodeText( + text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); - const int size = qrcodegen_getSize(qrcode); + const int size = qrcodegen_getSize(qrcode); - const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); - const int paddingY = - floor(float(displays[dispNum].height() - (size * 4)) / 2); - displays[dispNum].setRotation(2); + const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); + const int paddingY = + floor(float(displays[dispNum].height() - (size * 4)) / 2); + displays[dispNum].setRotation(2); - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); - const int border = 0; + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(GxEPD_WHITE); + const int border = 0; - for (int y = -border; y < size * 4 + border; y++) - { - for (int x = -border; x < size * 4 + border; x++) + for (int y = -border; y < size * 4 + border; y++) { - displays[dispNum].drawPixel( - padding + x, paddingY + y, - qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) - ? GxEPD_BLACK - : GxEPD_WHITE); + for (int x = -border; x < size * 4 + border; x++) + { + displays[dispNum].drawPixel( + padding + x, paddingY + y, + qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) + ? GxEPD_BLACK + : GxEPD_WHITE); + } } - } #endif } void waitUntilNoneBusy() { - for (int i = 0; i < NUM_SCREENS; i++) - { - uint count = 0; - while (EPD_BUSY[i].digitalRead()) + for (int i = 0; i < NUM_SCREENS; i++) { - count++; - vTaskDelay(BUSY_RETRY_DELAY); - - if (count == BUSY_TIMEOUT_COUNT) { - vTaskDelay(pdMS_TO_TICKS(100)); - } else if (count > BUSY_TIMEOUT_COUNT + 5) { - log_e("Display %d busy timeout", i); - break; - } + uint count = 0; + while (EPD_BUSY[i].digitalRead()) + { + count++; + vTaskDelay(BUSY_RETRY_DELAY); + + if (count == BUSY_TIMEOUT_COUNT) + { + vTaskDelay(pdMS_TO_TICKS(100)); + } + else if (count > BUSY_TIMEOUT_COUNT + 5) + { + log_e("Display %d busy timeout", i); + break; + } + } } - } } \ No newline at end of file diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 35fcdd3..1825189 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -3,7 +3,7 @@ #include #include #include - +#include "gzip_decompressor.hpp" #include #include @@ -16,6 +16,18 @@ #include "icons/icons.h" #include "mining_pool_stats_fetch.hpp" +// Font includes +#include "../fonts/antonio-semibold20.h" +#include "../fonts/antonio-semibold40.h" +#include "../fonts/antonio-semibold90.h" + +// Oswald fonts +#include "../fonts/oswald-medium20.h" +#include "../fonts/oswald-medium30.h" +#include "../fonts/oswald-medium80.h" + +#include "../fonts/sats-symbol.h" + #ifdef USE_QR #include "qrcodegen.h" #endif diff --git a/src/lib/gzip_decompressor.hpp b/src/lib/gzip_decompressor.hpp new file mode 100644 index 0000000..a74b04e --- /dev/null +++ b/src/lib/gzip_decompressor.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "rom/miniz.h" +#include + +class GzipDecompressor { +public: + static bool decompressData(const uint8_t* input, size_t inputSize, + uint8_t* output, size_t* outputSize) { + if (!input || !output || !outputSize || inputSize < 18) { // Minimum gzip size + return false; + } + + tinfl_decompressor* decomp = new tinfl_decompressor; + if (!decomp) { + return false; + } + + tinfl_init(decomp); + + size_t inPos = 10; // Skip gzip header + size_t outPos = 0; + + while (inPos < inputSize - 8) { // -8 for footer + size_t inBytes = inputSize - inPos - 8; + size_t outBytes = *outputSize - outPos; + + tinfl_status status = tinfl_decompress(decomp, + &input[inPos], &inBytes, + output, &output[outPos], &outBytes, + TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + + inPos += inBytes; + outPos += outBytes; + + if (status == TINFL_STATUS_DONE) { + *outputSize = outPos; + delete decomp; + return true; + } else if (status < 0) { + delete decomp; + return false; + } + } + + delete decomp; + return false; + } +}; \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 66c6895..539fb17 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -3,7 +3,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; @@ -675,7 +675,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP); - + root["fontName"] = preferences.getString("fontName", DEFAULT_FONT_NAME); // Custom endpoint settings (only used for CUSTOM_SOURCE) root["customEndpoint"] = preferences.getString("customEndpoint", DEFAULT_CUSTOM_ENDPOINT); root["customEndpointDisableSSL"] = preferences.getBool("customEndpointDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); From 13c8e67b4c850aad5541dfc71217ce086a81d5b4 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 30 Dec 2024 00:53:50 +0100 Subject: [PATCH 151/188] More efficient handling of multiple fonts, restore data bugfix --- data | 2 +- src/fonts/fonts.hpp | 26 +++++++++ src/lib/epd.cpp | 114 ++++++++++--------------------------- src/lib/epd.hpp | 2 +- src/lib/price_notify.cpp | 31 ++++++++-- src/lib/price_notify.hpp | 3 +- src/lib/screen_handler.cpp | 1 + src/lib/shared.hpp | 10 ++++ src/lib/webserver.cpp | 1 + 9 files changed, 96 insertions(+), 94 deletions(-) diff --git a/data b/data index 48e585d..0041ec3 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 48e585d4ec12bbc441499936d7cbf53d4307b9ec +Subproject commit 0041ec3d9a174955383836bba02caf79f3961072 diff --git a/src/fonts/fonts.hpp b/src/fonts/fonts.hpp index a1f03bd..8f66032 100644 --- a/src/fonts/fonts.hpp +++ b/src/fonts/fonts.hpp @@ -15,9 +15,35 @@ struct FontData { const uint8_t yAdvance; }; +// Font name constants +namespace FontNames { + static const String ANTONIO = "antonio"; + static const String OSWALD = "oswald"; + + static const std::array AVAILABLE_FONTS = { + ANTONIO, + OSWALD + }; + + static const std::array& getAvailableFonts() { + return AVAILABLE_FONTS; + } +} class FontLoader { public: + static GFXfont* loadCompressedFont(const FontData& fontData) { + return loadCompressedFont( + fontData.compressedData, + fontData.glyphs, + fontData.compressedSize, + fontData.originalSize, + fontData.first, + fontData.last, + fontData.yAdvance + ); + } + static GFXfont* loadCompressedFont( const uint8_t* compressedData, const GFXglyph* glyphs, diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index cca11a7..31fbf86 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -161,73 +161,21 @@ void forceFullRefresh() GFXfont font90; -void setupDisplays() -{ - String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); // Default to antonio - - if (fontName == "antonio") - { +void loadFonts(const String& fontName) { + if (fontName == FontNames::ANTONIO) { // Load Antonio fonts - antonioFonts.big = FontLoader::loadCompressedFont( - Antonio_SemiBold90pt7b_Properties.compressedData, - Antonio_SemiBold90pt7b_Properties.glyphs, - Antonio_SemiBold90pt7b_Properties.compressedSize, - Antonio_SemiBold90pt7b_Properties.originalSize, - Antonio_SemiBold90pt7b_Properties.first, - Antonio_SemiBold90pt7b_Properties.last, - Antonio_SemiBold90pt7b_Properties.yAdvance); - - antonioFonts.medium = FontLoader::loadCompressedFont( - Antonio_SemiBold40pt7b_Properties.compressedData, - Antonio_SemiBold40pt7b_Properties.glyphs, - Antonio_SemiBold40pt7b_Properties.compressedSize, - Antonio_SemiBold40pt7b_Properties.originalSize, - Antonio_SemiBold40pt7b_Properties.first, - Antonio_SemiBold40pt7b_Properties.last, - Antonio_SemiBold40pt7b_Properties.yAdvance); - - antonioFonts.small = FontLoader::loadCompressedFont( - Antonio_SemiBold20pt7b_Properties.compressedData, - Antonio_SemiBold20pt7b_Properties.glyphs, - Antonio_SemiBold20pt7b_Properties.compressedSize, - Antonio_SemiBold20pt7b_Properties.originalSize, - Antonio_SemiBold20pt7b_Properties.first, - Antonio_SemiBold20pt7b_Properties.last, - Antonio_SemiBold20pt7b_Properties.yAdvance); + antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties); + antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties); + antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties); FONT_BIG = antonioFonts.big; FONT_MEDIUM = antonioFonts.medium; FONT_SMALL = antonioFonts.small; - } - else if (fontName == "oswald") - { + } else if (fontName == FontNames::OSWALD) { // Load Oswald fonts - oswaldFonts.big = FontLoader::loadCompressedFont( - Oswald_Medium80pt7b_Properties.compressedData, - Oswald_Medium80pt7b_Properties.glyphs, - Oswald_Medium80pt7b_Properties.compressedSize, - Oswald_Medium80pt7b_Properties.originalSize, - Oswald_Medium80pt7b_Properties.first, - Oswald_Medium80pt7b_Properties.last, - Oswald_Medium80pt7b_Properties.yAdvance); - - oswaldFonts.medium = FontLoader::loadCompressedFont( - Oswald_Medium30pt7b_Properties.compressedData, - Oswald_Medium30pt7b_Properties.glyphs, - Oswald_Medium30pt7b_Properties.compressedSize, - Oswald_Medium30pt7b_Properties.originalSize, - Oswald_Medium30pt7b_Properties.first, - Oswald_Medium30pt7b_Properties.last, - Oswald_Medium30pt7b_Properties.yAdvance); - - oswaldFonts.small = FontLoader::loadCompressedFont( - Oswald_Medium20pt7b_Properties.compressedData, - Oswald_Medium20pt7b_Properties.glyphs, - Oswald_Medium20pt7b_Properties.compressedSize, - Oswald_Medium20pt7b_Properties.originalSize, - Oswald_Medium20pt7b_Properties.first, - Oswald_Medium20pt7b_Properties.last, - Oswald_Medium20pt7b_Properties.yAdvance); + oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties); + oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties); + oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties); FONT_BIG = oswaldFonts.big; FONT_MEDIUM = oswaldFonts.medium; @@ -235,53 +183,49 @@ void setupDisplays() } FONT_SATSYMBOL = &Satoshi_Symbol90pt7b; +} +void setupDisplays() { + // Load fonts based on preference + String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); + loadFonts(fontName); + + // Initialize displays std::lock_guard lockMcp(mcpMutex); - - for (uint i = 0; i < NUM_SCREENS; i++) - { + for (uint i = 0; i < NUM_SCREENS; i++) { displays[i].init(0, true, 30); } + // Create update queue and task updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, NULL, 11, NULL); - for (uint i = 0; i < NUM_SCREENS; i++) - { + // Create display update tasks + for (uint i = 0; i < NUM_SCREENS; i++) { int *taskParam = new int; *taskParam = i; - - xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam, - 11, &tasks[i]); // create task + xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam, 11, &tasks[i]); } - // Hold lower button to enable "storage mode" (prevents burn-in of ePaper displays) - if (mcp1.read1(0) == LOW) - { + // Check for storage mode (prevents burn-in) + if (mcp1.read1(0) == LOW) { setFgColor(GxEPD_BLACK); setBgColor(GxEPD_WHITE); - epdContent.fill(""); - } - else - { - // Get custom text from preferences or use default "BTCLOCK" + } else { + // Initialize with custom text or default String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); - - // Initialize array with spaces std::array newContent; newContent.fill(" "); - // Fill in the custom text, truncating if longer than NUM_SCREENS for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { - newContent[i] = String(customText[i]); + newContent[i] = String(customText[i]); } epdContent = newContent; } - setEpdContent(epdContent); + setEpdContent(epdContent); } void setEpdContent(std::array newEpdContent) @@ -527,8 +471,8 @@ void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) if (chr == '.') { - displays[dispNum].fillRect(x, y, displays[dispNum].width(), - round(displays[dispNum].height() * 0.9), getBgColor()); + displays[dispNum].fillRect(0, 0, displays[dispNum].width(), + round(displays[dispNum].height() * 0.67), getBgColor()); } } diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 1825189..4796776 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -31,7 +31,6 @@ #ifdef USE_QR #include "qrcodegen.h" #endif -// extern TaskHandle_t epdTaskHandle; typedef struct { char dispNum; @@ -39,6 +38,7 @@ typedef struct { void forceFullRefresh(); void setupDisplays(); +void loadFonts(const String& fontName); void splitText(const uint dispNum, const String &top, const String &bottom, bool partial); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 0d56a90..2c6fe94 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -110,15 +110,17 @@ void processNewPrice(uint newPrice, char currency) if (lastUpdateMap.find(currency) == lastUpdateMap.end() || (currentTime - lastUpdateMap[currency]) > minSecPriceUpd) { - // const unsigned long oldPrice = currentPrice; currencyMap[currency] = newPrice; - if (currency == CURRENCY_USD && ( lastUpdateMap[currency] == 0 || - (currentTime - lastUpdateMap[currency]) > 120)) + + // Store price in preferences if enough time has passed + if (lastUpdateMap[currency] == 0 || (currentTime - lastUpdateMap[currency]) > 120) { - preferences.putUInt("lastPrice", currentPrice); + String prefKey = String("lastPrice_") + getCurrencyCode(currency).c_str(); + preferences.putUInt(prefKey.c_str(), newPrice); } + lastUpdateMap[currency] = currentTime; - // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER || ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP)) @@ -126,7 +128,24 @@ void processNewPrice(uint newPrice, char currency) WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } - //} + } +} + +void loadStoredPrices() +{ + // Load prices for all supported currencies + std::vector currencies = getAvailableCurrencies(); + + for (const std::string ¤cy : currencies) { + // Get first character as the currency identifier + String prefKey = String("lastPrice_") + currency.c_str(); + uint storedPrice = preferences.getUInt(prefKey.c_str(), 0); + + if (storedPrice > 0) { + currencyMap[getCurrencyChar(currency)] = storedPrice; + // Initialize lastUpdateMap to 0 so next update will store immediately + lastUpdateMap[getCurrencyChar(currency)] = 0; + } } } diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 4aad356..3591d40 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -27,4 +27,5 @@ void stopPriceNotify(); void restartPriceNotify(); bool getPriceNotifyInit(); -uint getLastPriceUpdate(char currency); \ No newline at end of file +uint getLastPriceUpdate(char currency); +void loadStoredPrices(); \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index f837e5e..4d8d1b9 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -321,6 +321,7 @@ void taskScreenRotate(void *pvParameters) { void setupTasks() { workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem)); + loadStoredPrices(); xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY, &workerTaskHandle); diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index adbbe5e..78fb9ea 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -97,6 +97,16 @@ namespace ArduinoJson { array.add(item); } }; + + template + struct Converter> { + static void toJson(const std::array& src, JsonVariant dst) { + JsonArray array = dst.to(); + for (const String& item : src) { + array.add(item); + } + } + }; } class HttpHelper { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 539fb17..c2bc86a 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -676,6 +676,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP); root["fontName"] = preferences.getString("fontName", DEFAULT_FONT_NAME); + root["availableFonts"] = FontNames::getAvailableFonts(); // Custom endpoint settings (only used for CUSTOM_SOURCE) root["customEndpoint"] = preferences.getString("customEndpoint", DEFAULT_CUSTOM_ENDPOINT); root["customEndpointDisableSSL"] = preferences.getBool("customEndpointDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); From 2e1b15e688c4a7cfb675c96c10d2cb01e2de5545 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 30 Dec 2024 02:04:18 +0100 Subject: [PATCH 152/188] Add Do Not Disturb feature --- data | 2 +- src/lib/led_handler.cpp | 108 +++++++++++++++++++++++++++++++++++++--- src/lib/led_handler.hpp | 21 +++++++- src/lib/webserver.cpp | 67 ++++++++++++++++++++++++- src/lib/webserver.hpp | 4 ++ 5 files changed, 189 insertions(+), 13 deletions(-) diff --git a/data b/data index 0041ec3..033fe09 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 0041ec3d9a174955383836bba02caf79f3961072 +Subproject commit 033fe098295ab6da6568d6298b4380e51bec0b98 diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 42ef5e5..16612ba 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -50,6 +50,9 @@ void frontlightFadeOut(uint num) void frontlightSetBrightness(uint brightness) { + if (isDNDActive()) { + return; // Don't change brightness during DND mode + } if (brightness > 4096) { return; @@ -183,6 +186,9 @@ bool frontlightIsOn() void frontlightFadeIn(uint num, int flDelayTime) { + if (isDNDActive()) { + return; // Don't change brightness during DND mode + } if (preferences.getBool("flDisable")) return; for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) @@ -194,6 +200,9 @@ void frontlightFadeIn(uint num, int flDelayTime) void frontlightFadeOut(uint num, int flDelayTime) { + if (isDNDActive()) { + return; // Don't change brightness during DND mode + } if (preferences.getBool("flDisable")) return; if (!frontlightIsOn()) @@ -207,6 +216,85 @@ void frontlightFadeOut(uint num, int flDelayTime) } #endif +// Do Not Disturb mode variables +bool dndEnabled = false; +bool dndTimeBasedEnabled = false; +DNDTimeRange dndTimeRange = {23, 0, 7, 0}; // Default: 23:00 to 07:00 + +void loadDNDSettings() { + dndEnabled = preferences.getBool("dndEnabled", false); + dndTimeBasedEnabled = preferences.getBool("dndTimeEnabled", false); + + dndTimeRange.startHour = preferences.getUChar("dndStartHour", 23); + dndTimeRange.startMinute = preferences.getUChar("dndStartMin", 0); + dndTimeRange.endHour = preferences.getUChar("dndEndHour", 7); + dndTimeRange.endMinute = preferences.getUChar("dndEndMin", 0); +} + +void setDNDEnabled(bool enabled) { + dndEnabled = enabled; + preferences.putBool("dndEnabled", enabled); + if (enabled && isDNDActive()) { + clearLeds(); + #ifdef HAS_FRONTLIGHT + frontlightFadeOutAll(); + #endif + } +} + +void setDNDTimeBasedEnabled(bool enabled) { + dndTimeBasedEnabled = enabled; + preferences.putBool("dndTimeEnabled", enabled); + if (enabled && isDNDActive()) { + clearLeds(); + #ifdef HAS_FRONTLIGHT + frontlightFadeOutAll(); + #endif + } +} + +void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) { + dndTimeRange.startHour = startHour; + dndTimeRange.startMinute = startMinute; + dndTimeRange.endHour = endHour; + dndTimeRange.endMinute = endMinute; + + preferences.putUChar("dndStartHour", startHour); + preferences.putUChar("dndStartMin", startMinute); + preferences.putUChar("dndEndHour", endHour); + preferences.putUChar("dndEndMin", endMinute); +} + +bool isTimeInDNDRange(uint8_t hour, uint8_t minute) { + uint16_t currentTime = hour * 60 + minute; + uint16_t startTime = dndTimeRange.startHour * 60 + dndTimeRange.startMinute; + uint16_t endTime = dndTimeRange.endHour * 60 + dndTimeRange.endMinute; + + if (startTime <= endTime) { + // Simple case: start time is before end time (e.g., 09:00 to 17:00) + return currentTime >= startTime && currentTime < endTime; + } else { + // Complex case: start time is after end time (e.g., 23:00 to 07:00) + return currentTime >= startTime || currentTime < endTime; + } +} + +bool isDNDActive() { + if (dndEnabled) { + return true; + } + + if (dndTimeBasedEnabled) { + time_t now; + struct tm timeinfo; + time(&now); + localtime_r(&now, &timeinfo); + return isTimeInDNDRange(timeinfo.tm_hour, timeinfo.tm_min); + } + + return false; +} + void ledTask(void *parameter) { while (1) @@ -450,6 +538,7 @@ void ledTask(void *parameter) void setupLeds() { + loadDNDSettings(); pixels.begin(); pixels.setBrightness(preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS)); pixels.clear(); @@ -595,15 +684,18 @@ void restoreLedState() QueueHandle_t getLedTaskQueue() { return ledTaskQueue; } -bool queueLedEffect(uint effect) -{ - if (ledTaskQueue == NULL) - { - return false; - } +bool queueLedEffect(uint effect) { + if (isDNDActive()) { + return false; // Don't queue any effects during DND mode + } + if (ledTaskQueue == NULL) + { + return false; + } - uint flashType = effect; - xQueueSend(ledTaskQueue, &flashType, portMAX_DELAY); + uint flashType = effect; + xQueueSend(ledTaskQueue, &flashType, portMAX_DELAY); + return true; } void ledRainbow(int wait) diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 98d8834..4fdbd28 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -28,7 +28,6 @@ const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103; const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104; - const int LED_PROGRESS_25 = 200; const int LED_PROGRESS_50 = 201; const int LED_PROGRESS_75 = 202; @@ -82,4 +81,22 @@ void frontlightFadeOutAll(int flDelayTime, bool staggered); void frontlightFadeIn(uint num, int flDelayTime); void frontlightFadeOut(uint num, int flDelayTime); -#endif \ No newline at end of file +#endif + +// Do Not Disturb mode settings +struct DNDTimeRange { + uint8_t startHour; + uint8_t startMinute; + uint8_t endHour; + uint8_t endMinute; +}; + +extern bool dndEnabled; +extern bool dndTimeBasedEnabled; +extern DNDTimeRange dndTimeRange; + +void setDNDEnabled(bool enabled); +void setDNDTimeBasedEnabled(bool enabled); +void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); +bool isDNDActive(); +bool isTimeInDNDRange(uint8_t hour, uint8_t minute); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index c2bc86a..2ba0a4d 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -16,7 +16,7 @@ static const char *const PROGMEM boolSettings[] = {"ledTestOnPower", "ledFlashOn "mempoolSecure", "bitaxeEnabled", "miningPoolStats", "verticalDesc", "nostrZapNotify", "httpAuthEnabled", - "enableDebugLog", "ceDisableSSL"}; + "enableDebugLog", "ceDisableSSL", "dndEnabled", "dndTimeBasedEnabled"}; AsyncWebServer server(80); AsyncEventSource events("/events"); @@ -116,6 +116,10 @@ void setupWebserver() server.addRewrite(new OneParamRewrite("/api/show/number/{number}", "/api/show/text?t={text}")); + server.on("/api/dnd/status", HTTP_GET, onApiDNDStatus); + server.on("/api/dnd/enable", HTTP_POST, onApiDNDEnable); + server.on("/api/dnd/disable", HTTP_POST, onApiDNDDisable); + server.onNotFound(onNotFound); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); @@ -265,6 +269,15 @@ JsonDocument getStatusObject() } #endif + // Add DND status + root["dnd"]["enabled"] = dndEnabled; + root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; + root["dnd"]["startTime"] = String(dndTimeRange.startHour) + ":" + + (dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); + root["dnd"]["endTime"] = String(dndTimeRange.endHour) + ":" + + (dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); + root["dnd"]["active"] = isDNDActive(); + return root; } @@ -605,6 +618,23 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settingsChanged = true; } + // Handle DND settings + if (settings.containsKey("dnd")) { + JsonObject dndObj = settings["dnd"]; + if (dndObj.containsKey("timeBasedEnabled")) { + setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); + } + if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && + dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { + setDNDTimeRange( + dndObj["startHour"].as(), + dndObj["startMinute"].as(), + dndObj["endHour"].as(), + dndObj["endMinute"].as() + ); + } + } + request->send(HTTP_OK); if (settingsChanged) { @@ -766,6 +796,14 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["ceEndpoint"] = preferences.getString("ceEndpoint", DEFAULT_CUSTOM_ENDPOINT); root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); + // Add DND settings + root["dnd"]["enabled"] = dndEnabled; + root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; + root["dnd"]["startHour"] = dndTimeRange.startHour; + root["dnd"]["startMinute"] = dndTimeRange.startMinute; + root["dnd"]["endHour"] = dndTimeRange.endHour; + root["dnd"]["endMinute"] = dndTimeRange.endMinute; + AsyncResponseStream *response = request->beginResponseStream(JSON_CONTENT); serializeJson(root, *response); @@ -1091,4 +1129,29 @@ void onApiFrontlightOff(AsyncWebServerRequest *request) request->send(HTTP_OK); } -#endif \ No newline at end of file +#endif + +void onApiDNDStatus(AsyncWebServerRequest *request) { + JsonDocument doc; + doc["enabled"] = dndEnabled; + doc["timeBasedEnabled"] = dndTimeBasedEnabled; + doc["startTime"] = String(dndTimeRange.startHour) + ":" + + (dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); + doc["endTime"] = String(dndTimeRange.endHour) + ":" + + (dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); + doc["active"] = isDNDActive(); + + String response; + serializeJson(doc, response); + request->send(200, "application/json", response); +} + +void onApiDNDEnable(AsyncWebServerRequest *request) { + setDNDEnabled(true); + request->send(200); +} + +void onApiDNDDisable(AsyncWebServerRequest *request) { + setDNDEnabled(false); + request->send(200); +} \ No newline at end of file diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 45fa854..ddd6b73 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -67,6 +67,10 @@ void eventSourceTask(void *pvParameters); void onApiStopDataSources(AsyncWebServerRequest *request); void onApiRestartDataSources(AsyncWebServerRequest *request); +void onApiDNDStatus(AsyncWebServerRequest *request); +void onApiDNDEnable(AsyncWebServerRequest *request); +void onApiDNDDisable(AsyncWebServerRequest *request); + #ifdef HAS_FRONTLIGHT void onApiFrontlightOn(AsyncWebServerRequest *request); void onApiFrontlightFlash(AsyncWebServerRequest *request); From aea69d54b66d91bdb083a62654711ec50da617b6 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 31 Dec 2024 11:33:50 +0100 Subject: [PATCH 153/188] Add tag and HW revision to setup screen, change setup SSID same as hostname --- src/lib/config.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 5870ec4..5ef77dc 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -157,8 +157,7 @@ void setupWifi() byte mac[6]; WiFi.macAddress(mac); - String softAP_SSID = - String("BTClock" + String(mac[5], 16) + String(mac[1], 16)); + String softAP_SSID = getMyHostname(); WiFi.setHostname(softAP_SSID.c_str()); String softAP_password = replaceAmbiguousChars( base64::encode(String(mac[2], 16) + String(mac[4], 16) + @@ -183,7 +182,8 @@ void setupWifi() ";T:WPA;P:" + softAP_password.c_str() + ";;"; const String explainText = "*SSID: *\r\n" + wifiManager->getConfigPortalSSID() + - "\r\n\r\n*Password:*\r\n" + softAP_password; + "\r\n\r\n*Password:*\r\n" + softAP_password + + "\r\n\r\n*Hostname*:\r\n" + getMyHostname(); // Set the UNIX timestamp time_t timestamp = LAST_BUILD_TIME; // Example timestamp: March 7, 2021 00:00:00 UTC @@ -193,14 +193,19 @@ void setupWifi() // Format the date char formattedDate[20]; strftime(formattedDate, sizeof(formattedDate), "%y-%m-%d\r\n%H:%M:%S", timeinfo); - + String hwStr = String(HW_REV); + hwStr.replace("_EPD_", "\r\nEPD_"); std::array epdContent = { "Welcome!", "Bienvenidos!", "To setup\r\nscan QR or\r\nconnect\r\nmanually", "Para\r\nconfigurar\r\nescanear QR\r\no conectar\r\nmanualmente", explainText, - "*Hostname*:\r\n" + getMyHostname() + "\r\n\r\n" + "*FW build date:*\r\n" + formattedDate, + "*HW version:*\r\n" + hwStr + +#ifdef GIT_TAG + "\r\n\r\n*SW Version:*\r\n" + GIT_TAG + +#endif + "\r\n\r\n*FW build date:*\r\n" + formattedDate, qrText}; setEpdContent(epdContent); }); From b8428e1650027943c43698d7a285b1f31ef97ed5 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 31 Dec 2024 11:51:34 +0100 Subject: [PATCH 154/188] Add LED effect when retrying time synchronization --- src/lib/config.cpp | 1 + src/lib/led_handler.cpp | 20 ++++++++++++++++++++ src/lib/led_handler.hpp | 11 +++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 5ef77dc..2269cc6 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -279,6 +279,7 @@ void syncTime() while (!getLocalTime(&timeinfo)) { + queueLedEffect(LED_EFFECT_CONFIGURING); configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, NTP_SERVER); delay(500); diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 16612ba..806ee9c 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -333,6 +333,26 @@ void ledTask(void *parameter) blinkDelayTwoColor(100, 3, pixels.Color(8, 161, 236), pixels.Color(255, 0, 0)); break; + case LED_EFFECT_CONFIGURING: + for (int i = NEOPIXEL_COUNT; i--; i > 0) + { + for (int j = NEOPIXEL_COUNT; j--; j > 0) + { + uint32_t c = pixels.Color(0, 0, 0); + if (i == j) + c = pixels.Color(0, 0, 255); + + pixels.setPixelColor(j, c); + } + + pixels.show(); + + delay(100); + } + + pixels.clear(); + pixels.show(); + break; case LED_FLASH_ERROR: blinkDelayColor(250, 3, 255, 0, 0); break; diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 4fdbd28..fe72ffa 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -18,10 +18,13 @@ const int LED_FLASH_ERROR = 0; const int LED_FLASH_SUCCESS = 1; const int LED_FLASH_UPDATE = 2; -const int LED_FLASH_BLOCK_NOTIFY = 3; -const int LED_EFFECT_START_TIMER = 4; -const int LED_EFFECT_PAUSE_TIMER = 5; -const int LED_EFFECT_HEARTBEAT = 6; + +const int LED_EFFECT_CONFIGURING = 10; + +const int LED_FLASH_BLOCK_NOTIFY = 4; +const int LED_EFFECT_START_TIMER = 5; +const int LED_EFFECT_PAUSE_TIMER = 6; +const int LED_EFFECT_HEARTBEAT = 7; const int LED_EFFECT_WIFI_WAIT_FOR_CONFIG = 100; const int LED_EFFECT_WIFI_CONNECTING = 101; const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; From b4864b1db6e6fd9d0940fde06edf73c521cd47db Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 18:08:21 +0100 Subject: [PATCH 155/188] Improve bitaxe handling code --- lib/btclock/bitaxe_handler.cpp | 72 ++++++++++++++++++------- lib/btclock/bitaxe_handler.hpp | 6 ++- lib/btclock/utils.cpp | 11 ++++ lib/btclock/utils.hpp | 3 +- src/lib/bitaxe_fetch.cpp | 24 ++++++--- src/lib/bitaxe_fetch.hpp | 5 +- src/lib/block_notify.cpp | 16 +++--- src/lib/block_notify.hpp | 16 +++--- test/test_bitaxehandler/test_main.cpp | 75 +++++++++++++++++++++++++++ 9 files changed, 183 insertions(+), 45 deletions(-) create mode 100644 test/test_bitaxehandler/test_main.cpp diff --git a/lib/btclock/bitaxe_handler.cpp b/lib/btclock/bitaxe_handler.cpp index c18b7de..c51ca7c 100644 --- a/lib/btclock/bitaxe_handler.cpp +++ b/lib/btclock/bitaxe_handler.cpp @@ -1,14 +1,20 @@ #include "bitaxe_handler.hpp" -std::array parseBitaxeHashRate(std::string text) +std::array parseBitaxeHashRate(uint64_t hashrate) { std::array ret; ret.fill(""); // Initialize all elements to empty strings - std::size_t textLength = text.length(); + // Convert hashrate to GH/s and round to nearest integer + double hashRateGH = static_cast(hashrate) / std::pow(10, getHashrateMultiplier('G')); + std::string hashRateStr = std::to_string(static_cast(std::round(hashRateGH))); + + // Place the icons + ret[0] = "mdi:bitaxe"; + ret[NUM_SCREENS - 1] = "GH/S"; // Calculate the position where the digits should start - // Account for the position of the "mdi:pickaxe" and the "GH/S" label + std::size_t textLength = hashRateStr.length(); std::size_t startIndex = NUM_SCREENS - 1 - textLength; // Insert the "mdi:pickaxe" icon just before the digits @@ -17,34 +23,64 @@ std::array parseBitaxeHashRate(std::string text) ret[startIndex - 1] = "mdi:pickaxe"; } - // Place the digits + // Place each digit for (std::size_t i = 0; i < textLength; ++i) { - ret[startIndex + i] = text.substr(i, 1); + ret[startIndex + i] = std::string(1, hashRateStr[i]); } - ret[NUM_SCREENS - 1] = "GH/S"; - ret[0] = "mdi:bitaxe"; - return ret; } -std::array parseBitaxeBestDiff(std::string text) +std::array parseBitaxeBestDiff(uint64_t difficulty) { std::array ret; - std::uint32_t firstIndex = 0; + ret.fill(""); - if (text.length() < NUM_SCREENS) - { - text.insert(text.begin(), NUM_SCREENS - text.length(), ' '); - ret[0] = "mdi:bitaxe"; - ret[1] = "mdi:rocket"; - firstIndex = 2; + // Add icons at the start + ret[0] = "mdi:bitaxe"; + ret[1] = "mdi:rocket"; + + if (difficulty == 0) { + ret[NUM_SCREENS - 1] = "0"; + return ret; } - for (std::uint8_t i = firstIndex; i < NUM_SCREENS; i++) + // Find the appropriate suffix and format the number + const std::pair suffixes[] = { + {'Q', 15}, {'T', 12}, {'G', 9}, {'M', 6}, {'K', 3} + }; + + std::string text; + for (const auto& suffix : suffixes) { + if (difficulty >= std::pow(10, suffix.second)) { + double value = difficulty / std::pow(10, suffix.second); + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.1f", value); + text = buffer; + // Remove trailing zeros and decimal point if not needed + if (text.find('.') != std::string::npos) { + text = text.substr(0, text.find_last_not_of('0') + 1); + if (text.back() == '.') { + text.pop_back(); + } + } + text += suffix.first; + break; + } + } + + if (text.empty()) { + text = std::to_string(difficulty); + } + + // Calculate start position to right-align the text + std::size_t startIndex = NUM_SCREENS - text.length(); + + // Place the formatted difficulty string + for (std::size_t i = 0; i < text.length() && (startIndex + i) < NUM_SCREENS; ++i) { - ret[i] = text[i]; + ret[startIndex + i] = std::string(1, text[i]); } return ret; diff --git a/lib/btclock/bitaxe_handler.hpp b/lib/btclock/bitaxe_handler.hpp index c78c8f7..f1484ef 100644 --- a/lib/btclock/bitaxe_handler.hpp +++ b/lib/btclock/bitaxe_handler.hpp @@ -1,5 +1,7 @@ #include #include +#include +#include "utils.hpp" -std::array parseBitaxeHashRate(std::string text); -std::array parseBitaxeBestDiff(std::string text); +std::array parseBitaxeHashRate(uint64_t hashrate); +std::array parseBitaxeBestDiff(uint64_t difficulty); diff --git a/lib/btclock/utils.cpp b/lib/btclock/utils.cpp index d5c8027..443eb1e 100644 --- a/lib/btclock/utils.cpp +++ b/lib/btclock/utils.cpp @@ -243,3 +243,14 @@ int getHashrateMultiplier(char unit) { }; return multipliers.at(unit); } + +int getDifficultyMultiplier(char unit) { + if (unit == '0') + return 0; + + static const std::unordered_map multipliers = { + {'Q', 15}, {'T', 12}, {'B', 9}, {'M', 6}, {'K', 3}, {'G', 9}, + {'q', 15}, {'t', 12}, {'b', 9}, {'m', 6}, {'k', 3}, {'g', 9} + }; + return multipliers.at(unit); +} diff --git a/lib/btclock/utils.hpp b/lib/btclock/utils.hpp index 44df032..05540d4 100644 --- a/lib/btclock/utils.hpp +++ b/lib/btclock/utils.hpp @@ -16,4 +16,5 @@ std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4); std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mowMode); int64_t getAmountInSatoshis(std::string bolt11); void parseHashrateString(const std::string& hashrate, std::string& label, std::string& output, unsigned int maxCharacters); -int getHashrateMultiplier(char unit); \ No newline at end of file +int getHashrateMultiplier(char unit); +int getDifficultyMultiplier(char unit); \ No newline at end of file diff --git a/src/lib/bitaxe_fetch.cpp b/src/lib/bitaxe_fetch.cpp index f439cb4..d2dae13 100644 --- a/src/lib/bitaxe_fetch.cpp +++ b/src/lib/bitaxe_fetch.cpp @@ -2,15 +2,15 @@ TaskHandle_t bitaxeFetchTaskHandle; -std::string bitaxeHashrate; -std::string bitaxeBestDiff; +uint64_t bitaxeHashrate; +uint64_t bitaxeBestDiff; -std::string getBitAxeHashRate() +uint64_t getBitAxeHashRate() { return bitaxeHashrate; } -std::string getBitaxeBestDiff() +uint64_t getBitaxeBestDiff() { return bitaxeBestDiff; } @@ -33,8 +33,20 @@ void taskBitaxeFetch(void *pvParameters) String payload = http.getString(); JsonDocument doc; deserializeJson(doc, payload); - bitaxeHashrate = std::to_string(static_cast(std::round(doc["hashRate"].as()))); - bitaxeBestDiff = doc["bestDiff"].as(); + + // Convert GH/s to H/s (multiply by 10^9) + float hashRateGH = doc["hashRate"].as(); + bitaxeHashrate = static_cast(std::round(hashRateGH * std::pow(10, getHashrateMultiplier('G')))); + + // Parse difficulty string and convert to uint64_t + std::string diffStr = doc["bestDiff"].as(); + char diffUnit = diffStr[diffStr.length() - 1]; + if (std::isalpha(diffUnit)) { + float diffValue = std::stof(diffStr.substr(0, diffStr.length() - 1)); + bitaxeBestDiff = static_cast(std::round(diffValue * std::pow(10, getDifficultyMultiplier(diffUnit)))); + } else { + bitaxeBestDiff = std::stoull(diffStr); + } if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) { diff --git a/src/lib/bitaxe_fetch.hpp b/src/lib/bitaxe_fetch.hpp index 8e4f7f3..8e1da37 100644 --- a/src/lib/bitaxe_fetch.hpp +++ b/src/lib/bitaxe_fetch.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "lib/config.hpp" #include "lib/shared.hpp" @@ -11,5 +12,5 @@ extern TaskHandle_t bitaxeFetchTaskHandle; void setupBitaxeFetchTask(); void taskBitaxeFetch(void *pvParameters); -std::string getBitAxeHashRate(); -std::string getBitaxeBestDiff(); \ No newline at end of file +uint64_t getBitAxeHashRate(); +uint64_t getBitaxeBestDiff(); \ No newline at end of file diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index a1ce1fc..ec72138 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -2,8 +2,8 @@ char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; -uint currentBlockHeight = 873400; -uint blockMedianFee = 1; +uint32_t currentBlockHeight = 873400; +uint16_t blockMedianFee = 1; bool blockNotifyInit = false; unsigned long int lastBlockUpdate; @@ -179,7 +179,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) doc.clear(); } -void processNewBlock(uint newBlockHeight) { +void processNewBlock(uint32_t newBlockHeight) { if (newBlockHeight < currentBlockHeight) return; @@ -222,7 +222,7 @@ void processNewBlock(uint newBlockHeight) { } } -void processNewBlockFee(uint newBlockFee) { +void processNewBlockFee(uint16_t newBlockFee) { if (blockMedianFee == newBlockFee) { return; @@ -238,16 +238,16 @@ void processNewBlockFee(uint newBlockFee) { } } -uint getBlockHeight() { return currentBlockHeight; } +uint32_t getBlockHeight() { return currentBlockHeight; } -void setBlockHeight(uint newBlockHeight) +void setBlockHeight(uint32_t newBlockHeight) { currentBlockHeight = newBlockHeight; } -uint getBlockMedianFee() { return blockMedianFee; } +uint16_t getBlockMedianFee() { return blockMedianFee; } -void setBlockMedianFee(uint newBlockMedianFee) +void setBlockMedianFee(uint16_t newBlockMedianFee) { blockMedianFee = newBlockMedianFee; } diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index d5565eb..9c41bf0 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -22,20 +22,20 @@ void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data); -void setBlockHeight(uint newBlockHeight); -uint getBlockHeight(); +void setBlockHeight(uint32_t newBlockHeight); +uint32_t getBlockHeight(); -void setBlockMedianFee(uint blockMedianFee); -uint getBlockMedianFee(); +void setBlockMedianFee(uint16_t blockMedianFee); +uint16_t getBlockMedianFee(); bool isBlockNotifyConnected(); void stopBlockNotify(); void restartBlockNotify(); -void processNewBlock(uint newBlockHeight); -void processNewBlockFee(uint newBlockFee); +void processNewBlock(uint32_t newBlockHeight); +void processNewBlockFee(uint16_t newBlockFee); bool getBlockNotifyInit(); -uint getLastBlockUpdate(); +uint32_t getLastBlockUpdate(); int getBlockFetch(); -void setLastBlockUpdate(uint lastUpdate); \ No newline at end of file +void setLastBlockUpdate(uint32_t lastUpdate); diff --git a/test/test_bitaxehandler/test_main.cpp b/test/test_bitaxehandler/test_main.cpp new file mode 100644 index 0000000..4336107 --- /dev/null +++ b/test/test_bitaxehandler/test_main.cpp @@ -0,0 +1,75 @@ +#include +#include + +template +std::string joinArrayWithBrackets(const std::array& arr, const std::string& separator = " ") { + std::ostringstream result; + for (size_t i = 0; i < N; ++i) { + if (i > 0) { + result << separator; + } + result << '[' << arr[i] << ']'; + } + return result.str(); +} + +void setUp(void) +{ + // set stuff up here +} + +void tearDown(void) +{ + // clean stuff up here +} + +void test_BitaxeParseHashrate(void) +{ + std::array output = parseBitaxeHashRate(656130000000); + + std::string joined = joinArrayWithBrackets(output); + + + TEST_ASSERT_EQUAL_STRING_MESSAGE("mdi:bitaxe", output[0].c_str(), joined.c_str()); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("6", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("5", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("6", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("GH/S", output[NUM_SCREENS - 1].c_str(), joined.c_str()); +} + +void test_BitaxeParseBestDiff(void) +{ + std::array output = parseBitaxeBestDiff(15800000000); + + std::string joined = joinArrayWithBrackets(output); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("mdi:bitaxe", output[0].c_str(), joined.c_str()); + + + TEST_ASSERT_EQUAL_STRING_MESSAGE("1", output[NUM_SCREENS - 5].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("5", output[NUM_SCREENS - 4].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE(".", output[NUM_SCREENS - 3].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("8", output[NUM_SCREENS - 2].c_str(), joined.c_str()); + TEST_ASSERT_EQUAL_STRING_MESSAGE("G", output[NUM_SCREENS - 1].c_str(), joined.c_str()); +} + +// not needed when using generate_test_runner.rb +int runUnityTests(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_BitaxeParseHashrate); + RUN_TEST(test_BitaxeParseBestDiff); + + return UNITY_END(); +} + +int main(void) +{ + return runUnityTests(); +} + +extern "C" void app_main() +{ + runUnityTests(); +} From cd4dea9d340078817596413664150dbb2e4fd7fb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 18:50:19 +0100 Subject: [PATCH 156/188] Add explicit LED frequency, compress sats symbol font --- src/lib/config.cpp | 1 + src/lib/epd.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 2269cc6..765bedd 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -674,6 +674,7 @@ void setupFrontlight() Serial.println(F("FL driver error")); return; } + flArray.setFrequency(200); Serial.println(F("FL driver active")); if (!preferences.isKey("flMaxBrightness")) diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 31fbf86..d9b5303 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -182,7 +182,7 @@ void loadFonts(const String& fontName) { FONT_SMALL = oswaldFonts.small; } - FONT_SATSYMBOL = &Satoshi_Symbol90pt7b; + FONT_SATSYMBOL = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties); } void setupDisplays() { From fa15e46d349a5a4ffeb028085b1250c776a1c88c Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 20:07:56 +0100 Subject: [PATCH 157/188] Add compressed sat symbol font file --- src/fonts/sats-symbol.h | 424 ++++++++++++++++++++++------------------ 1 file changed, 229 insertions(+), 195 deletions(-) diff --git a/src/fonts/sats-symbol.h b/src/fonts/sats-symbol.h index d21a9a5..182c6ea 100644 --- a/src/fonts/sats-symbol.h +++ b/src/fonts/sats-symbol.h @@ -1,201 +1,235 @@ -const uint8_t Satoshi_Symbol90pt7bBitmaps[] PROGMEM = { - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, - 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, - 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, - 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, - 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, - 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, - 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, - 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, - 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, - 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xE0 }; +#pragma once + +#include +#include +#include "fonts.hpp" + +const uint8_t Satoshi_Symbol90pt7bBitmaps_Gzip[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x63, 0x60, + 0x60, 0x60, 0xe0, 0xff, 0xc7, 0x00, 0x05, 0xcc, 0xff, 0x1b, 0x60, 0xcc, + 0xff, 0x0f, 0x60, 0x2c, 0xfb, 0x1f, 0x30, 0xd6, 0x60, 0x53, 0x38, 0x28, + 0x81, 0xfd, 0x7f, 0x3a, 0x83, 0x81, 0xf6, 0x30, 0x1a, 0xe0, 0xa7, 0xb7, + 0xff, 0x0f, 0x0c, 0xb4, 0x8f, 0x51, 0x01, 0x33, 0xbd, 0xfd, 0xff, 0x61, + 0xa0, 0x7d, 0x8c, 0x17, 0x0c, 0x8e, 0x92, 0x82, 0x44, 0x85, 0x54, 0x8f, + 0x23, 0x88, 0xe1, 0xec, 0xff, 0xa1, 0x56, 0xf2, 0xff, 0xff, 0x03, 0x61, + 0xc8, 0xff, 0x87, 0x3a, 0xc7, 0x1e, 0x16, 0x8d, 0xf5, 0x30, 0xa7, 0xc2, + 0x12, 0x36, 0xe3, 0x7f, 0xa8, 0xeb, 0x98, 0x47, 0x8d, 0x19, 0x35, 0x66, + 0x78, 0x1b, 0x43, 0x65, 0xf0, 0x00, 0x00, 0xc7, 0x63, 0x9f, 0x4b, 0xde, + 0x08, 0x00, 0x00 +}; +// unsigned int satoshi_bin_gz_len = 123; + + +// const uint8_t Satoshi_Symbol90pt7bBitmaps[] PROGMEM = { +// 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, +// 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +// 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +// 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, +// 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, +// 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, +// 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +// 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, +// 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, +// 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, +// 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, +// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, +// 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +// 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, +// 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, +// 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, +// 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, +// 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, +// 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, +// 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +// 0xFF, 0xE0 }; const GFXglyph Satoshi_Symbol90pt7bGlyphs[] PROGMEM = { { 0, 82, 127, 99, 8, -126 }, { 1302, 71, 109, 93, 0, -117 } }; // 0x53 'S' -const GFXfont Satoshi_Symbol90pt7b PROGMEM = { - (uint8_t *)Satoshi_Symbol90pt7bBitmaps, - (GFXglyph *)Satoshi_Symbol90pt7bGlyphs, - 0x53, 0x53, 192 }; +// const GFXfont Satoshi_Symbol90pt7b PROGMEM = { +// (uint8_t *)Satoshi_Symbol90pt7bBitmaps, +// (GFXglyph *)Satoshi_Symbol90pt7bGlyphs, +// 0x53, 0x53, 192 }; + + // Font properties +static constexpr FontData Satoshi_Symbol90pt7b_Properties = { + Satoshi_Symbol90pt7bBitmaps_Gzip, + Satoshi_Symbol90pt7bGlyphs, + sizeof(Satoshi_Symbol90pt7bBitmaps_Gzip), + 2270, // Original size + 0x53, // First char + 0x53, // Last char + 192 // yAdvance +}; + // Approx. 2284 bytes From c91428dd5f872dc0613ce55c5e2ebcfe031d051b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 20:14:55 +0100 Subject: [PATCH 158/188] Refactor BitAxeFetch to a class --- src/lib/bitaxe_fetch.cpp | 53 ++++++++++++++------------------------ src/lib/bitaxe_fetch.hpp | 28 ++++++++++++++++---- src/lib/config.cpp | 4 +-- src/lib/screen_handler.cpp | 4 +-- src/lib/timers.cpp | 5 ++-- 5 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/lib/bitaxe_fetch.cpp b/src/lib/bitaxe_fetch.cpp index d2dae13..8fa1db0 100644 --- a/src/lib/bitaxe_fetch.cpp +++ b/src/lib/bitaxe_fetch.cpp @@ -1,24 +1,19 @@ #include "bitaxe_fetch.hpp" -TaskHandle_t bitaxeFetchTaskHandle; - -uint64_t bitaxeHashrate; -uint64_t bitaxeBestDiff; - -uint64_t getBitAxeHashRate() -{ - return bitaxeHashrate; +void BitAxeFetch::taskWrapper(void* pvParameters) { + BitAxeFetch::getInstance().task(); } -uint64_t getBitaxeBestDiff() -{ - return bitaxeBestDiff; +uint64_t BitAxeFetch::getHashRate() const { + return hashrate; } -void taskBitaxeFetch(void *pvParameters) -{ - for (;;) - { +uint64_t BitAxeFetch::getBestDiff() const { + return bestDiff; +} + +void BitAxeFetch::task() { + for (;;) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); HTTPClient http; @@ -28,46 +23,38 @@ void taskBitaxeFetch(void *pvParameters) int httpCode = http.GET(); - if (httpCode == 200) - { + if (httpCode == 200) { String payload = http.getString(); JsonDocument doc; deserializeJson(doc, payload); // Convert GH/s to H/s (multiply by 10^9) float hashRateGH = doc["hashRate"].as(); - bitaxeHashrate = static_cast(std::round(hashRateGH * std::pow(10, getHashrateMultiplier('G')))); + hashrate = static_cast(std::round(hashRateGH * std::pow(10, getHashrateMultiplier('G')))); // Parse difficulty string and convert to uint64_t std::string diffStr = doc["bestDiff"].as(); char diffUnit = diffStr[diffStr.length() - 1]; if (std::isalpha(diffUnit)) { float diffValue = std::stof(diffStr.substr(0, diffStr.length() - 1)); - bitaxeBestDiff = static_cast(std::round(diffValue * std::pow(10, getDifficultyMultiplier(diffUnit)))); + bestDiff = static_cast(std::round(diffValue * std::pow(10, getDifficultyMultiplier(diffUnit)))); } else { - bitaxeBestDiff = std::stoull(diffStr); + bestDiff = std::stoull(diffStr); } - if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) - { + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) { WorkItem priceUpdate = {TASK_BITAXE_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } - } - else - { - Serial.print( - F("Error retrieving BitAxe data. HTTP status code: ")); + } else { + Serial.print(F("Error retrieving BitAxe data. HTTP status code: ")); Serial.println(httpCode); Serial.println(bitaxeApiUrl); } } } -void setupBitaxeFetchTask() -{ - xTaskCreate(taskBitaxeFetch, "bitaxeFetch", (3 * 1024), NULL, tskIDLE_PRIORITY, - &bitaxeFetchTaskHandle); - - xTaskNotifyGive(bitaxeFetchTaskHandle); +void BitAxeFetch::setup() { + xTaskCreate(taskWrapper, "bitaxeFetch", (3 * 1024), NULL, tskIDLE_PRIORITY, &taskHandle); + xTaskNotifyGive(taskHandle); } \ No newline at end of file diff --git a/src/lib/bitaxe_fetch.hpp b/src/lib/bitaxe_fetch.hpp index 8e1da37..0dd98d9 100644 --- a/src/lib/bitaxe_fetch.hpp +++ b/src/lib/bitaxe_fetch.hpp @@ -7,10 +7,28 @@ #include "lib/config.hpp" #include "lib/shared.hpp" -extern TaskHandle_t bitaxeFetchTaskHandle; +class BitAxeFetch { +public: + static BitAxeFetch& getInstance() { + static BitAxeFetch instance; + return instance; + } -void setupBitaxeFetchTask(); -void taskBitaxeFetch(void *pvParameters); + void setup(); + uint64_t getHashRate() const; + uint64_t getBestDiff() const; + static void taskWrapper(void* pvParameters); + TaskHandle_t getTaskHandle() const { return taskHandle; } -uint64_t getBitAxeHashRate(); -uint64_t getBitaxeBestDiff(); \ No newline at end of file +private: + BitAxeFetch() = default; + ~BitAxeFetch() = default; + BitAxeFetch(const BitAxeFetch&) = delete; + BitAxeFetch& operator=(const BitAxeFetch&) = delete; + + void task(); + + TaskHandle_t taskHandle = nullptr; + uint64_t hashrate = 0; + uint64_t bestDiff = 0; +}; \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 765bedd..2dce75e 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -100,7 +100,7 @@ void setup() if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { - setupBitaxeFetchTask(); + BitAxeFetch::getInstance().setup(); } if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) @@ -221,7 +221,7 @@ void setupWifi() // waitUntilNoneBusy(); // std::array epdContent = {"Welcome!", - // "Bienvenidos!", "Use\r\nweb-interface\r\nto configure", "Use\r\nla + // "Bienvenidos!", "Use\r\nweb-interface\r\npara configurar", "Use\r\nla // interfaz web\r\npara configurar", "Or // restart\r\nwhile\r\nholding\r\n2nd button\r\r\nto start\r\n QR-config", // "O reinicie\r\nmientras\r\n mantiene presionado\r\nel segundo diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 4d8d1b9..27d83b9 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -220,8 +220,8 @@ void workerTask(void *pvParameters) { currentScreenValue != SCREEN_BITAXE_BESTDIFF) break; taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ? - parseBitaxeHashRate(getBitAxeHashRate()) : - parseBitaxeBestDiff(getBitaxeBestDiff()); + parseBitaxeHashRate(BitAxeFetch::getInstance().getHashRate()) : + parseBitaxeBestDiff(BitAxeFetch::getInstance().getBestDiff()); setEpdContent(taskEpdContent); break; } diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp index 2fdb71c..3779c2d 100644 --- a/src/lib/timers.cpp +++ b/src/lib/timers.cpp @@ -68,8 +68,9 @@ void IRAM_ATTR minuteTimerISR(void *arg) { WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken); - if (bitaxeFetchTaskHandle != NULL) { - vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); + TaskHandle_t bitaxeHandle = BitAxeFetch::getInstance().getTaskHandle(); + if (bitaxeHandle != NULL) { + vTaskNotifyGiveFromISR(bitaxeHandle, &xHigherPriorityTaskWoken); } if (miningPoolStatsFetchTaskHandle != NULL) { From ac130988240d41c02b663608b6df13b9445309e7 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 20:24:13 +0100 Subject: [PATCH 159/188] Refactor mining pool stats fetch to a class --- src/lib/config.cpp | 4 +- src/lib/epd.cpp | 2 +- src/lib/mining_pool_stats_fetch.cpp | 128 ++++++++++++---------------- src/lib/mining_pool_stats_fetch.hpp | 42 +++++++-- src/lib/screen_handler.cpp | 7 +- src/lib/timers.cpp | 5 +- 6 files changed, 100 insertions(+), 88 deletions(-) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 2dce75e..f0db973 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -105,7 +105,7 @@ void setup() if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { - setupMiningPoolStatsFetchTask(); + MiningPoolStatsFetch::getInstance().setup(); } ButtonHandler::setup(); @@ -374,7 +374,7 @@ void setupPreferences() if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate"); - if (getMiningPool()->supportsDailyEarnings()) { + if (MiningPoolStatsFetch::getInstance().getPool()->supportsDailyEarnings()) { addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings"); } } diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index d9b5303..04d3c5c 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -600,7 +600,7 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) } else if (text.endsWith("miningpool")) { - LogoData logo = getMiningPoolLogo(); + LogoData logo = MiningPoolStatsFetch::getInstance().getLogo(); if (logo.size == 0) { diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index a35a00e..e04f208 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -1,95 +1,98 @@ #include "mining_pool_stats_fetch.hpp" -TaskHandle_t miningPoolStatsFetchTaskHandle; - -std::string miningPoolName; -std::string miningPoolStatsHashrate; -int miningPoolStatsDailyEarnings; - -std::string getMiningPoolStatsHashRate() -{ - return miningPoolStatsHashrate; +void MiningPoolStatsFetch::taskWrapper(void* pvParameters) { + MiningPoolStatsFetch::getInstance().task(); } -int getMiningPoolStatsDailyEarnings() -{ - return miningPoolStatsDailyEarnings; +void MiningPoolStatsFetch::downloadLogoTaskWrapper(void* pvParameters) { + MiningPoolStatsFetch::getInstance().downloadLogoTask(); } -void taskMiningPoolStatsFetch(void *pvParameters) -{ +std::string MiningPoolStatsFetch::getHashRate() const { + return hashrate; +} + +int MiningPoolStatsFetch::getDailyEarnings() const { + return dailyEarnings; +} + +MiningPoolInterface* MiningPoolStatsFetch::getPool() { + if (!currentPool) { + std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); + currentPool = PoolFactory::createPool(poolName); + } + return currentPool.get(); +} + +const MiningPoolInterface* MiningPoolStatsFetch::getPool() const { + return currentPool.get(); +} + +LogoData MiningPoolStatsFetch::getLogo() const { + if (const auto* pool = getPool()) { + return pool->getLogo(); + } + return LogoData{}; +} + +void MiningPoolStatsFetch::task() { std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); - auto poolInterface = PoolFactory::createPool(poolName); + auto* poolInterface = getPool(); + if (!poolInterface) return; std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str(); // Main stats fetching loop - for (;;) - { + for (;;) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); HTTPClient http; http.setUserAgent(USER_AGENT); - - - + poolInterface->setPoolUser(poolUser); std::string apiUrl = poolInterface->getApiUrl(); http.begin(apiUrl.c_str()); - if (debugLogEnabled()) - { + if (debugLogEnabled()) { Serial.printf("Fetching mining pool stats from %s\r\n", apiUrl.c_str()); } poolInterface->prepareRequest(http); int httpCode = http.GET(); - if (httpCode == 200) - { + if (httpCode == 200) { String payload = http.getString(); JsonDocument doc; deserializeJson(doc, payload); - if (debugLogEnabled()) - { + if (debugLogEnabled()) { Serial.printf("Mining pool stats response: %s\r\n", payload.c_str()); } PoolStats stats = poolInterface->parseResponse(doc); + hashrate = stats.hashrate; - miningPoolStatsHashrate = stats.hashrate; - - if (debugLogEnabled()) - { + if (debugLogEnabled()) { Serial.printf("Mining pool stats parsed hashrate: %s\r\n", stats.hashrate.c_str()); } - if (stats.dailyEarnings) - { - miningPoolStatsDailyEarnings = *stats.dailyEarnings; - } - else - { - miningPoolStatsDailyEarnings = 0; // or any other default value - } + dailyEarnings = stats.dailyEarnings ? *stats.dailyEarnings : 0; - if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) - { + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || + ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) { WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } - } - else - { - Serial.print( - F("Error retrieving mining pool data. HTTP status code: ")); + } else { + Serial.print(F("Error retrieving mining pool data. HTTP status code: ")); Serial.println(httpCode); } } } -void downloadMiningPoolLogoTask(void *pvParameters) { +void MiningPoolStatsFetch::downloadLogoTask() { std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); - auto poolInterface = PoolFactory::createPool(poolName); - PoolFactory::downloadPoolLogo(poolName, poolInterface.get()); + auto* poolInterface = getPool(); + if (!poolInterface) return; + + PoolFactory::downloadPoolLogo(poolName, poolInterface); // If we're on the mining pool stats screen, trigger a display update if (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { @@ -97,41 +100,22 @@ void downloadMiningPoolLogoTask(void *pvParameters) { xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } - xTaskNotifyGive(miningPoolStatsFetchTaskHandle); + xTaskNotifyGive(taskHandle); vTaskDelete(NULL); } -void setupMiningPoolStatsFetchTask() -{ - xTaskCreate(downloadMiningPoolLogoTask, +void MiningPoolStatsFetch::setup() { + xTaskCreate(downloadLogoTaskWrapper, "logoDownload", (6 * 1024), NULL, tskIDLE_PRIORITY, NULL); - xTaskCreate(taskMiningPoolStatsFetch, + xTaskCreate(taskWrapper, "miningPoolStatsFetch", (6 * 1024), NULL, tskIDLE_PRIORITY, - &miningPoolStatsFetchTaskHandle); -} - -std::unique_ptr& getMiningPool() -{ - static std::unique_ptr currentMiningPool; - - if (!currentMiningPool) { - std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str(); - currentMiningPool = PoolFactory::createPool(poolName); - } - - return currentMiningPool; -} - -LogoData getMiningPoolLogo() -{ - LogoData logo = getMiningPool()->getLogo(); - return logo; + &taskHandle); } diff --git a/src/lib/mining_pool_stats_fetch.hpp b/src/lib/mining_pool_stats_fetch.hpp index 7a84454..9ea351a 100644 --- a/src/lib/mining_pool_stats_fetch.hpp +++ b/src/lib/mining_pool_stats_fetch.hpp @@ -2,18 +2,44 @@ #include #include -#include "mining_pool/pool_factory.hpp" +#include +#include #include "lib/config.hpp" #include "lib/shared.hpp" +#include "lib/mining_pool/mining_pool_interface.hpp" +#include "mining_pool/pool_factory.hpp" -extern TaskHandle_t miningPoolStatsFetchTaskHandle; +class MiningPoolStatsFetch { +public: + static MiningPoolStatsFetch& getInstance() { + static MiningPoolStatsFetch instance; + return instance; + } -void setupMiningPoolStatsFetchTask(); -void taskMiningPoolStatsFetch(void *pvParameters); + void setup(); + std::string getHashRate() const; + int getDailyEarnings() const; + TaskHandle_t getTaskHandle() const { return taskHandle; } + static void taskWrapper(void* pvParameters); + static void downloadLogoTaskWrapper(void* pvParameters); + + // Pool interface methods + MiningPoolInterface* getPool(); + const MiningPoolInterface* getPool() const; + LogoData getLogo() const; -std::string getMiningPoolStatsHashRate(); -int getMiningPoolStatsDailyEarnings(); +private: + MiningPoolStatsFetch() = default; + ~MiningPoolStatsFetch() = default; + MiningPoolStatsFetch(const MiningPoolStatsFetch&) = delete; + MiningPoolStatsFetch& operator=(const MiningPoolStatsFetch&) = delete; -std::unique_ptr& getMiningPool(); -LogoData getMiningPoolLogo(); \ No newline at end of file + void task(); + void downloadLogoTask(); + + TaskHandle_t taskHandle = nullptr; + std::string hashrate; + int dailyEarnings = 0; + std::unique_ptr currentPool; +}; \ No newline at end of file diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 27d83b9..0692c1d 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -231,9 +231,10 @@ void workerTask(void *pvParameters) { currentScreenValue != SCREEN_MINING_POOL_STATS_EARNINGS) break; taskEpdContent = (currentScreenValue == SCREEN_MINING_POOL_STATS_HASHRATE) ? - parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()) : - parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), - getMiningPool()->getDailyEarningsLabel(), *getMiningPool()); + parseMiningPoolStatsHashRate(MiningPoolStatsFetch::getInstance().getHashRate(), *MiningPoolStatsFetch::getInstance().getPool()) : + parseMiningPoolStatsDailyEarnings(MiningPoolStatsFetch::getInstance().getDailyEarnings(), + MiningPoolStatsFetch::getInstance().getPool()->getDailyEarningsLabel(), + *MiningPoolStatsFetch::getInstance().getPool()); setEpdContent(taskEpdContent); break; } diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp index 3779c2d..4e67951 100644 --- a/src/lib/timers.cpp +++ b/src/lib/timers.cpp @@ -73,8 +73,9 @@ void IRAM_ATTR minuteTimerISR(void *arg) { vTaskNotifyGiveFromISR(bitaxeHandle, &xHigherPriorityTaskWoken); } - if (miningPoolStatsFetchTaskHandle != NULL) { - vTaskNotifyGiveFromISR(miningPoolStatsFetchTaskHandle, &xHigherPriorityTaskWoken); + TaskHandle_t miningPoolHandle = MiningPoolStatsFetch::getInstance().getTaskHandle(); + if (miningPoolHandle != NULL) { + vTaskNotifyGiveFromISR(miningPoolHandle, &xHigherPriorityTaskWoken); } if (xHigherPriorityTaskWoken == pdTRUE) { From d023643090370e32408b129ba4167fa971431e5b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 21:19:28 +0100 Subject: [PATCH 160/188] Refactor LedHandler to a class --- src/lib/block_notify.cpp | 3 +- src/lib/config.cpp | 78 +-- src/lib/config.hpp | 8 +- src/lib/epd.cpp | 50 +- src/lib/led_handler.cpp | 1324 +++++++++++++++++--------------------- src/lib/led_handler.hpp | 142 ++-- src/lib/nostr_notify.cpp | 3 +- src/lib/ota.cpp | 10 +- src/lib/timers.cpp | 5 +- src/lib/webserver.cpp | 228 +++++-- src/main.cpp | 15 +- 11 files changed, 931 insertions(+), 935 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index ec72138..111a7a7 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,4 +1,5 @@ #include "block_notify.hpp" +#include "led_handler.hpp" char *wsServer; esp_websocket_client_handle_t blockNotifyClient = NULL; @@ -217,7 +218,7 @@ void processNewBlock(uint32_t newBlockHeight) { if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) { vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated - queueLedEffect(LED_FLASH_BLOCK_NOTIFY); + getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); } } } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index f0db973..5a3fa7d 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -1,4 +1,5 @@ #include "config.hpp" +#include "led_handler.hpp" #define MAX_ATTEMPTS_WIFI_CONNECTION 20 @@ -50,7 +51,8 @@ void setup() setupDisplays(); if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { - queueLedEffect(LED_POWER_TEST); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_POWER_TEST); } { std::lock_guard lockMcp(mcpMutex); @@ -60,7 +62,8 @@ void setup() preferences.remove("txPower"); WiFi.eraseAP(); - queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); } } @@ -76,7 +79,8 @@ void setup() else if (mcp1.read1(1) == LOW) { preferences.clear(); - queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); nvs_flash_erase(); delay(1000); @@ -116,13 +120,13 @@ void setup() #ifdef HAS_FRONTLIGHT if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON)) { - frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); flArray.allOFF(); } #endif forceFullRefresh(); - } void setupWifi() @@ -144,7 +148,8 @@ void setupWifi() // if (!preferences.getBool("wifiConfigured", DEFAULT_WIFI_CONFIGURED) { - queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); bool buttonPress = false; { @@ -279,7 +284,8 @@ void syncTime() while (!getLocalTime(&timeinfo)) { - queueLedEffect(LED_EFFECT_CONFIGURING); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_EFFECT_CONFIGURING); configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, NTP_SERVER); delay(500); @@ -420,13 +426,14 @@ void setupTimers() void finishSetup() { + auto& ledHandler = getLedHandler(); if (preferences.getBool("ledStatus", DEFAULT_LED_STATUS)) { - restoreLedState(); + ledHandler.restoreLedState(); } else { - clearLeds(); + ledHandler.clear(); } } @@ -475,22 +482,9 @@ void setupHardware() Serial.println(F("Error loading WebUI")); } - // { - // File f = LittleFS.open("/qr.txt", "w"); - - // if(f) { - // if (f.print("Hello")) { - // Serial.println(F("Written QR to FS")); - // Serial.printf("\nLittleFS free: %zu\n", LittleFS.totalBytes() - LittleFS.usedBytes()); - // } - // } else { - // Serial.println(F("Can't write QR to FS")); - // } - - // f.close(); - // } - - setupLeds(); + // Initialize LED handler + auto& ledHandler = getLedHandler(); + ledHandler.setup(); WiFi.setHostname(getMyHostname().c_str()); if (!psramInit()) @@ -548,7 +542,8 @@ void setupHardware() #endif #ifdef HAS_FRONTLIGHT - setupFrontlight(); + // Initialize frontlight through LedHandler + ledHandler.initializeFrontlight(); Wire.beginTransmission(0x5C); byte error = Wire.endTransmission(); @@ -570,6 +565,7 @@ void setupHardware() void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { static bool first_connect = true; + auto& ledHandler = getLedHandler(); // Get ledHandler reference once at the start Serial.printf("[WiFi-event] event: %d\n", event); @@ -595,7 +591,7 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) if (!first_connect) { Serial.println(F("Disconnected from WiFi access point")); - queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR); uint8_t reason = info.wifi_sta_disconnected.reason; if (reason) Serial.printf("Disconnect reason: %s, ", @@ -611,13 +607,13 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) Serial.print("Obtained IP address: "); Serial.println(WiFi.localIP()); if (!first_connect) - queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); + ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); first_connect = false; break; } case ARDUINO_EVENT_WIFI_STA_LOST_IP: Serial.println(F("Lost IP address and IP address is reset to 0")); - queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); + ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR); WiFi.reconnect(); break; case ARDUINO_EVENT_WIFI_AP_START: @@ -667,30 +663,6 @@ uint getLastTimeSync() } #ifdef HAS_FRONTLIGHT -void setupFrontlight() -{ - if (!flArray.begin(PCA9685_MODE1_AUTOINCR | PCA9685_MODE1_ALLCALL, PCA9685_MODE2_TOTEMPOLE)) - { - Serial.println(F("FL driver error")); - return; - } - flArray.setFrequency(200); - Serial.println(F("FL driver active")); - - if (!preferences.isKey("flMaxBrightness")) - { - preferences.putUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); - } - if (!preferences.isKey("flEffectDelay")) - { - preferences.putUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); - } - - if (!preferences.isKey("flFlashOnUpd")) - { - preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); - } -} float getLightLevel() { diff --git a/src/lib/config.hpp b/src/lib/config.hpp index d8e27b6..95c877d 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -52,10 +52,10 @@ void setupTimers(); void finishSetup(); void setupMcp(); #ifdef HAS_FRONTLIGHT -void setupFrontlight(); +extern BH1750 bh1750; +extern bool hasLuxSensor; float getLightLevel(); bool hasLightLevel(); -extern PCA9685 flArray; #endif String getMyHostname(); @@ -98,6 +98,10 @@ extern MCP23017 mcp1; extern MCP23017 mcp2; #endif +#ifdef HAS_FRONTLIGHT +extern PCA9685 flArray; +#endif + // Expose DataSourceType enum extern DataSourceType getDataSource(); extern void setDataSource(DataSourceType source); \ No newline at end of file diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 04d3c5c..9808aa7 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -140,8 +140,6 @@ const GFXfont *FONT_SATSYMBOL; std::mutex epdUpdateMutex; std::mutex epdMutex[NUM_SCREENS]; -uint8_t qrcode[800]; - #ifdef IS_BTCLOCK_V8 #define EPD_TASK_STACK_SIZE 4096 #else @@ -159,8 +157,6 @@ void forceFullRefresh() } } -GFXfont font90; - void loadFonts(const String& fontName) { if (fontName == FontNames::ANTONIO) { // Load Antonio fonts @@ -628,35 +624,45 @@ bool renderIcon(const uint dispNum, const String &text, bool partial) void renderQr(const uint dispNum, const String &text, bool partial) { #ifdef USE_QR + // Dynamically allocate QR buffer + uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX); + if (!qrcode) { + log_e("Failed to allocate QR buffer"); + return; + } uint8_t tempBuffer[800]; bool ok = qrcodegen_encodeText( text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); - const int size = qrcodegen_getSize(qrcode); + if (ok) { + const int size = qrcodegen_getSize(qrcode); + const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); + const int paddingY = + floor(float(displays[dispNum].height() - (size * 4)) / 2); + displays[dispNum].setRotation(2); - const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); - const int paddingY = - floor(float(displays[dispNum].height() - (size * 4)) / 2); - displays[dispNum].setRotation(2); + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(GxEPD_WHITE); + const int border = 0; - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); - const int border = 0; - - for (int y = -border; y < size * 4 + border; y++) - { - for (int x = -border; x < size * 4 + border; x++) + for (int y = -border; y < size * 4 + border; y++) { - displays[dispNum].drawPixel( - padding + x, paddingY + y, - qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) - ? GxEPD_BLACK - : GxEPD_WHITE); + for (int x = -border; x < size * 4 + border; x++) + { + displays[dispNum].drawPixel( + padding + x, paddingY + y, + qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) + ? GxEPD_BLACK + : GxEPD_WHITE); + } } } + + // Free the buffer after we're done + free(qrcode); #endif } diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 806ee9c..3cdb953 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -1,227 +1,431 @@ #include "led_handler.hpp" -TaskHandle_t ledTaskHandle = NULL; -QueueHandle_t ledTaskQueue = NULL; -Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); -uint ledTaskParams; +// Singleton instance +LedHandler& LedHandler::getInstance() { + static LedHandler instance; + return instance; +} + +LedHandler::LedHandler() + : pixels(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800) + , ledTaskHandle(nullptr) + , ledTaskQueue(nullptr) + , ledTaskParams(0) + , dndEnabled(false) + , dndTimeBasedEnabled(false) + , dndTimeRange{23, 0, 7, 0} // Default: 23:00 to 07:00 +#ifdef HAS_FRONTLIGHT + , frontlightOn(false) + , flInTransition(false) +#endif +{ +} + +void LedHandler::setup() { + loadDNDSettings(); + pixels.begin(); + pixels.setBrightness(preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS)); + pixels.clear(); + pixels.show(); + setupTask(); + + if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { + while (!ledTaskQueue) { + delay(1); + } + queueEffect(LED_POWER_TEST); + } +} + +void LedHandler::setupTask() { + ledTaskQueue = xQueueCreate(5, sizeof(uint)); + xTaskCreate(ledTask, "LedTask", 2048, this, 10, &ledTaskHandle); +} + +void LedHandler::ledTask(void* pvParameters) { + auto* handler = static_cast(pvParameters); + while (true) { + if (handler->ledTaskQueue != nullptr) { + if (xQueueReceive(handler->ledTaskQueue, &handler->ledTaskParams, portMAX_DELAY) == pdPASS) { + if (preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS)) { + continue; + } + + std::array oldLights; + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + oldLights[i] = handler->pixels.getPixelColor(i); + } #ifdef HAS_FRONTLIGHT -constexpr uint16_t FL_FADE_STEP = 25; - -bool frontlightOn = false; -bool flInTransition = false; - -void frontlightFlash(int flDelayTime) -{ - if (preferences.getBool("flDisable")) - return; - - if (frontlightOn) - { - frontlightFadeOutAll(flDelayTime, true); - frontlightFadeInAll(flDelayTime, true); - } - else - { - frontlightFadeInAll(flDelayTime, true); - frontlightFadeOutAll(flDelayTime, true); - } -} - -void frontlightFadeInAll() -{ - frontlightFadeInAll(preferences.getUInt("flEffectDelay")); -} - -void frontlightFadeOutAll() -{ - frontlightFadeOutAll(preferences.getUInt("flEffectDelay")); -} - -void frontlightFadeIn(uint num) -{ - frontlightFadeIn(num, preferences.getUInt("flEffectDelay")); -} - -void frontlightFadeOut(uint num) -{ - frontlightFadeOut(num, preferences.getUInt("flEffectDelay")); -} - -void frontlightSetBrightness(uint brightness) -{ - if (isDNDActive()) { - return; // Don't change brightness during DND mode - } - if (brightness > 4096) - { - return; - } - - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) - { - flArray.setPWM(ledPin, 0, brightness); - } -} - -void frontlightFadeInAll(int flDelayTime) -{ - frontlightFadeInAll(flDelayTime, false); -} - -void frontlightFadeInAll(int flDelayTime, bool staggered) -{ - if (preferences.getBool("flDisable") || frontlightIsOn() || flInTransition) - return; - - flInTransition = true; - - const int maxBrightness = preferences.getUInt("flMaxBrightness"); - - if (staggered) - { - int step = FL_FADE_STEP; - int staggerDelay = flDelayTime / NUM_SCREENS; - - for (int dutyCycle = 0; dutyCycle <= maxBrightness + (NUM_SCREENS - 1) * maxBrightness / NUM_SCREENS; dutyCycle += step) - { - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) - { - int ledBrightness = dutyCycle - ledPin * maxBrightness / NUM_SCREENS; - if (ledBrightness < 0) - ledBrightness = 0; - else if (ledBrightness > maxBrightness) - ledBrightness = maxBrightness; - - flArray.setPWM(ledPin + 1, 0, ledBrightness); - } - vTaskDelay(pdMS_TO_TICKS(staggerDelay)); - } - } - else - { - for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) - { - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) - { - flArray.setPWM(ledPin, 0, dutyCycle); - } - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } - } - frontlightOn = true; - flInTransition = false; -} - -void frontlightFadeOutAll(int flDelayTime) -{ - frontlightFadeOutAll(flDelayTime, false); -} - -void frontlightFadeOutAll(int flDelayTime, bool staggered) -{ - if (preferences.getBool("flDisable")) - return; - if (!frontlightIsOn()) - return; - if (flInTransition) - return; - flInTransition = true; - - if (staggered) - { - int maxBrightness = preferences.getUInt("flMaxBrightness"); - int step = FL_FADE_STEP; - int staggerDelay = flDelayTime / NUM_SCREENS; - - for (int dutyCycle = maxBrightness; dutyCycle >= 0; dutyCycle -= step) - { - for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) - { - int ledBrightness = dutyCycle - (NUM_SCREENS - 1 - ledPin) * maxBrightness / NUM_SCREENS; - if (ledBrightness < 0) - ledBrightness = 0; - else if (ledBrightness > maxBrightness) - ledBrightness = maxBrightness; - - flArray.setPWM(ledPin + 1, 0, ledBrightness); - } - vTaskDelay(pdMS_TO_TICKS(staggerDelay)); - } - } - else - { - for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) - { - for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) - { - flArray.setPWM(ledPin, 0, dutyCycle); - } - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } - } - - flArray.allOFF(); - frontlightOn = false; - flInTransition = false; -} - -std::vector frontlightGetStatus() -{ - std::vector statuses; - for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) - { - uint16_t a = 0, b = 0; - flArray.getPWM(ledPin, &a, &b); - statuses.push_back(round(b - a / 4096)); - } - - return statuses; -} - -bool frontlightIsOn() -{ - return frontlightOn; -} - -void frontlightFadeIn(uint num, int flDelayTime) -{ - if (isDNDActive()) { - return; // Don't change brightness during DND mode - } - if (preferences.getBool("flDisable")) - return; - for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) - { - flArray.setPWM(num, 0, dutyCycle); - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } -} - -void frontlightFadeOut(uint num, int flDelayTime) -{ - if (isDNDActive()) { - return; // Don't change brightness during DND mode - } - if (preferences.getBool("flDisable")) - return; - if (!frontlightIsOn()) - return; - - for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) - { - flArray.setPWM(num, 0, dutyCycle); - vTaskDelay(pdMS_TO_TICKS(flDelayTime)); - } -} + uint flDelayTime = preferences.getUInt("flEffectDelay"); #endif -// Do Not Disturb mode variables -bool dndEnabled = false; -bool dndTimeBasedEnabled = false; -DNDTimeRange dndTimeRange = {23, 0, 7, 0}; // Default: 23:00 to 07:00 + switch (handler->ledTaskParams) { + case LED_POWER_TEST: +#ifdef HAS_FRONTLIGHT + handler->frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); +#endif + handler->rainbow(20); + handler->pixels.clear(); + break; -void loadDNDSettings() { + case LED_EFFECT_WIFI_CONNECT_ERROR: + handler->blinkDelayTwoColor(100, 3, handler->pixels.Color(8, 161, 236), + handler->pixels.Color(255, 0, 0)); + break; + + case LED_EFFECT_CONFIGURING: + for (int i = NEOPIXEL_COUNT; i--; i > 0) { + for (int j = NEOPIXEL_COUNT; j--; j > 0) { + uint32_t c = handler->pixels.Color(0, 0, 0); + if (i == j) + c = handler->pixels.Color(0, 0, 255); + handler->pixels.setPixelColor(j, c); + } + handler->pixels.show(); + delay(100); + } + handler->pixels.clear(); + handler->pixels.show(); + break; + + case LED_FLASH_ERROR: + handler->blinkDelayColor(250, 3, 255, 0, 0); + break; + + case LED_EFFECT_HEARTBEAT: + handler->blinkDelayColor(150, 2, 0, 0, 255); + break; + + case LED_DATA_BLOCK_ERROR: + handler->blinkDelayColor(150, 2, 128, 0, 128); + break; + + case LED_DATA_PRICE_ERROR: + handler->blinkDelayColor(150, 2, 177, 90, 31); + break; + + case LED_FLASH_IDENTIFY: + handler->blinkDelayTwoColor(100, 2, handler->pixels.Color(255, 0, 0), + handler->pixels.Color(0, 255, 255)); + handler->blinkDelayTwoColor(100, 2, handler->pixels.Color(0, 255, 0), + handler->pixels.Color(0, 0, 255)); + break; + + case LED_EFFECT_WIFI_CONNECT_SUCCESS: + case LED_FLASH_SUCCESS: + handler->blinkDelayColor(150, 3, 0, 255, 0); + break; + + case LED_PROGRESS_100: + handler->pixels.setPixelColor(0, handler->pixels.Color(0, 255, 0)); + [[fallthrough]]; + case LED_PROGRESS_75: + handler->pixels.setPixelColor(1, handler->pixels.Color(0, 255, 0)); + [[fallthrough]]; + case LED_PROGRESS_50: + handler->pixels.setPixelColor(2, handler->pixels.Color(0, 255, 0)); + [[fallthrough]]; + case LED_PROGRESS_25: + handler->pixels.setPixelColor(3, handler->pixels.Color(0, 255, 0)); + handler->pixels.show(); + break; + + case LED_EFFECT_NOSTR_ZAP: + { +#ifdef HAS_FRONTLIGHT + bool frontlightWasOn = false; + if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) { + if (handler->frontlightOn) { + frontlightWasOn = true; + handler->frontlightFadeOutAll(flDelayTime, true); + } else { + handler->frontlightFadeInAll(flDelayTime, true); + } + } +#endif + for (int flash = 0; flash < random(7, 10); flash++) { + handler->lightningStrike(); + delay(random(50, 150)); + } +#ifdef HAS_FRONTLIGHT + if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) { + vTaskDelay(pdMS_TO_TICKS(10)); + if (frontlightWasOn) { + handler->frontlightFadeInAll(flDelayTime, true); + } else { + handler->frontlightFadeOutAll(flDelayTime, true); + } + } +#endif + break; + } + + case LED_FLASH_UPDATE: + handler->blinkDelayTwoColor(250, 3, handler->pixels.Color(0, 230, 0), + handler->pixels.Color(230, 230, 0)); + break; + + case LED_FLASH_BLOCK_NOTIFY: + { +#ifdef HAS_FRONTLIGHT + bool frontlightWasOn = false; + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) { + if (handler->frontlightOn) { + frontlightWasOn = true; + handler->frontlightFadeOutAll(flDelayTime, true); + } else { + handler->frontlightFadeInAll(flDelayTime, true); + } + } +#endif + handler->blinkDelayTwoColor(250, 3, handler->pixels.Color(224, 67, 0), + handler->pixels.Color(8, 2, 0)); +#ifdef HAS_FRONTLIGHT + if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) { + vTaskDelay(pdMS_TO_TICKS(10)); + if (frontlightWasOn) { + handler->frontlightFadeInAll(flDelayTime, true); + } else { + handler->frontlightFadeOutAll(flDelayTime, true); + } + } +#endif + break; + } + + case LED_EFFECT_WIFI_WAIT_FOR_CONFIG: + handler->blinkDelayTwoColor(100, 1, handler->pixels.Color(8, 161, 236), + handler->pixels.Color(156, 225, 240)); + break; + + case LED_EFFECT_WIFI_ERASE_SETTINGS: + handler->blinkDelay(100, 3); + break; + + case LED_EFFECT_WIFI_CONNECTING: + for (int i = NEOPIXEL_COUNT; i >= 0; i--) { + for (int j = NEOPIXEL_COUNT; j >= 0; j--) { + if (j == i) { + handler->pixels.setPixelColor(i, handler->pixels.Color(16, 197, 236)); + } else { + handler->pixels.setPixelColor(j, handler->pixels.Color(0, 0, 0)); + } + } + handler->pixels.show(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + break; + + case LED_EFFECT_PAUSE_TIMER: + for (int i = NEOPIXEL_COUNT; i >= 0; i--) { + for (int j = NEOPIXEL_COUNT; j >= 0; j--) { + uint32_t c = handler->pixels.Color(0, 0, 0); + if (i == j) + c = handler->pixels.Color(0, 255, 0); + handler->pixels.setPixelColor(j, c); + } + handler->pixels.show(); + delay(100); + } + handler->pixels.setPixelColor(0, handler->pixels.Color(255, 0, 0)); + handler->pixels.show(); + delay(900); + handler->pixels.clear(); + handler->pixels.show(); + break; + + case LED_EFFECT_START_TIMER: + handler->pixels.clear(); + handler->pixels.setPixelColor((NEOPIXEL_COUNT - 1), handler->pixels.Color(255, 0, 0)); + handler->pixels.show(); + delay(900); + for (int i = NEOPIXEL_COUNT; i--; i > 0) { + for (int j = NEOPIXEL_COUNT; j--; j > 0) { + uint32_t c = handler->pixels.Color(0, 0, 0); + if (i == j) + c = handler->pixels.Color(0, 255, 0); + handler->pixels.setPixelColor(j, c); + } + handler->pixels.show(); + delay(100); + } + handler->pixels.clear(); + handler->pixels.show(); + break; + } + + // Restore previous state unless power test + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + handler->pixels.setPixelColor(i, oldLights[i]); + } + handler->pixels.show(); + } + } + } +} + +bool LedHandler::queueEffect(uint effect) { + if (isDNDActive()) { + return false; + } + if (ledTaskQueue == nullptr) { + return false; + } + xQueueSend(ledTaskQueue, &effect, portMAX_DELAY); + return true; +} + +void LedHandler::clear() { + preferences.putBool("ledStatus", false); + pixels.clear(); + pixels.show(); +} + +void LedHandler::setLights(int r, int g, int b) { + setLights(pixels.Color(r, g, b)); +} + +void LedHandler::setLights(uint32_t color) { + bool ledStatus = true; + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, color); + } + pixels.show(); + + if (color == pixels.Color(0, 0, 0)) { + ledStatus = false; + } else { + saveLedState(); + } + preferences.putBool("ledStatus", ledStatus); +} + +void LedHandler::saveLedState() { + for (int i = 0; i < pixels.numPixels(); i++) { + int pixelColor = pixels.getPixelColor(i); + char key[12]; + snprintf(key, 12, "%s%d", "ledColor_", i); + preferences.putUInt(key, pixelColor); + } + xTaskNotifyGive(eventSourceTaskHandle); +} + +void LedHandler::restoreLedState() { + for (int i = 0; i < pixels.numPixels(); i++) { + char key[12]; + snprintf(key, 12, "%s%d", "ledColor_", i); + uint pixelColor = preferences.getUInt(key, pixels.Color(0, 0, 0)); + pixels.setPixelColor(i, pixelColor); + } + pixels.show(); +} + +void LedHandler::rainbow(int wait) { + for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) { + pixels.rainbow(firstPixelHue); + pixels.show(); + delayMicroseconds(wait); + } +} + +void LedHandler::theaterChase(uint32_t color, int wait) { + for (int a = 0; a < 10; a++) { + for (int b = 0; b < 3; b++) { + pixels.clear(); + for (int c = b; c < pixels.numPixels(); c += 3) { + pixels.setPixelColor(c, color); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(wait)); + } + } +} + +void LedHandler::theaterChaseRainbow(int wait) { + int firstPixelHue = 0; + for (int a = 0; a < 30; a++) { + for (int b = 0; b < 3; b++) { + pixels.clear(); + for (int c = b; c < pixels.numPixels(); c += 3) { + int hue = firstPixelHue + c * 65536L / pixels.numPixels(); + uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); + pixels.setPixelColor(c, color); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(wait)); + firstPixelHue += 65536 / 90; + } + } +} + +void LedHandler::lightningStrike() { + uint32_t PURPLE = pixels.Color(128, 0, 128); + uint32_t YELLOW = pixels.Color(255, 226, 41); + + for (int i = 0; i < pixels.numPixels(); i++) { + pixels.setPixelColor(i, random(2) == 0 ? YELLOW : PURPLE); + } + pixels.show(); + delay(random(10, 50)); +} + +void LedHandler::blinkDelay(int d, int times) { + for (int j = 0; j < times; j++) { + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.setPixelColor(1, pixels.Color(0, 255, 0)); + pixels.setPixelColor(2, pixels.Color(255, 0, 0)); + pixels.setPixelColor(3, pixels.Color(0, 255, 0)); + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + + pixels.setPixelColor(0, pixels.Color(255, 255, 0)); + pixels.setPixelColor(1, pixels.Color(0, 255, 255)); + pixels.setPixelColor(2, pixels.Color(255, 255, 0)); + pixels.setPixelColor(3, pixels.Color(0, 255, 255)); + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + } + pixels.clear(); + pixels.show(); +} + +void LedHandler::blinkDelayColor(int d, int times, uint r, uint g, uint b) { + for (int j = 0; j < times; j++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, pixels.Color(r, g, b)); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + + pixels.clear(); + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + } + pixels.clear(); + pixels.show(); +} + +void LedHandler::blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2) { + for (int j = 0; j < times; j++) { + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, c1); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + + for (int i = 0; i < NEOPIXEL_COUNT; i++) { + pixels.setPixelColor(i, c2); + } + pixels.show(); + vTaskDelay(pdMS_TO_TICKS(d)); + } + pixels.clear(); + pixels.show(); +} + +// DND Implementation +void LedHandler::loadDNDSettings() { dndEnabled = preferences.getBool("dndEnabled", false); dndTimeBasedEnabled = preferences.getBool("dndTimeEnabled", false); @@ -231,29 +435,29 @@ void loadDNDSettings() { dndTimeRange.endMinute = preferences.getUChar("dndEndMin", 0); } -void setDNDEnabled(bool enabled) { +void LedHandler::setDNDEnabled(bool enabled) { dndEnabled = enabled; preferences.putBool("dndEnabled", enabled); if (enabled && isDNDActive()) { - clearLeds(); - #ifdef HAS_FRONTLIGHT + clear(); +#ifdef HAS_FRONTLIGHT frontlightFadeOutAll(); - #endif +#endif } } -void setDNDTimeBasedEnabled(bool enabled) { +void LedHandler::setDNDTimeBasedEnabled(bool enabled) { dndTimeBasedEnabled = enabled; preferences.putBool("dndTimeEnabled", enabled); if (enabled && isDNDActive()) { - clearLeds(); - #ifdef HAS_FRONTLIGHT + clear(); +#ifdef HAS_FRONTLIGHT frontlightFadeOutAll(); - #endif +#endif } } -void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) { +void LedHandler::setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) { dndTimeRange.startHour = startHour; dndTimeRange.startMinute = startMinute; dndTimeRange.endHour = endHour; @@ -265,21 +469,19 @@ void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, ui preferences.putUChar("dndEndMin", endMinute); } -bool isTimeInDNDRange(uint8_t hour, uint8_t minute) { +bool LedHandler::isTimeInDNDRange(uint8_t hour, uint8_t minute) const { uint16_t currentTime = hour * 60 + minute; uint16_t startTime = dndTimeRange.startHour * 60 + dndTimeRange.startMinute; uint16_t endTime = dndTimeRange.endHour * 60 + dndTimeRange.endMinute; if (startTime <= endTime) { - // Simple case: start time is before end time (e.g., 09:00 to 17:00) return currentTime >= startTime && currentTime < endTime; } else { - // Complex case: start time is after end time (e.g., 23:00 to 07:00) return currentTime >= startTime || currentTime < endTime; } } -bool isDNDActive() { +bool LedHandler::isDNDActive() const { if (dndEnabled) { return true; } @@ -295,518 +497,174 @@ bool isDNDActive() { return false; } -void ledTask(void *parameter) -{ - while (1) - { - if (ledTaskQueue != NULL) - { - if (xQueueReceive(ledTaskQueue, &ledTaskParams, portMAX_DELAY) == - pdPASS) - { - - if (preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS)) - { - continue; - } - - std::array oldLights; - - // get current state - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - oldLights[i] = pixels.getPixelColor(i); - } #ifdef HAS_FRONTLIGHT - uint flDelayTime = preferences.getUInt("flEffectDelay"); -#endif - switch (ledTaskParams) - { - case LED_POWER_TEST: -#ifdef HAS_FRONTLIGHT - frontlightFadeInAll(preferences.getUInt("flEffectDelay"), true); -#endif - ledRainbow(20); - pixels.clear(); - break; - case LED_EFFECT_WIFI_CONNECT_ERROR: - blinkDelayTwoColor(100, 3, pixels.Color(8, 161, 236), - pixels.Color(255, 0, 0)); - break; - case LED_EFFECT_CONFIGURING: - for (int i = NEOPIXEL_COUNT; i--; i > 0) - { - for (int j = NEOPIXEL_COUNT; j--; j > 0) - { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) - c = pixels.Color(0, 0, 255); +// Frontlight implementation +void LedHandler::frontlightFlash(int flDelayTime) { + if (preferences.getBool("flDisable")) { + return; + } - pixels.setPixelColor(j, c); - } + if (frontlightOn) { + frontlightFadeOutAll(flDelayTime, true); + frontlightFadeInAll(flDelayTime, true); + } else { + frontlightFadeInAll(flDelayTime, true); + frontlightFadeOutAll(flDelayTime, true); + } +} - pixels.show(); +void LedHandler::frontlightFadeInAll() { + frontlightFadeInAll(preferences.getUInt("flEffectDelay")); +} - delay(100); - } +void LedHandler::frontlightFadeOutAll() { + frontlightFadeOutAll(preferences.getUInt("flEffectDelay")); +} - pixels.clear(); - pixels.show(); - break; - case LED_FLASH_ERROR: - blinkDelayColor(250, 3, 255, 0, 0); - break; - case LED_EFFECT_HEARTBEAT: - blinkDelayColor(150, 2, 0, 0, 255); - break; - case LED_DATA_BLOCK_ERROR: - blinkDelayColor(150, 2, 128, 0, 128); - break; - case LED_DATA_PRICE_ERROR: - blinkDelayColor(150, 2, 177, 90, 31); - break; - case LED_FLASH_IDENTIFY: - blinkDelayTwoColor(100, 2, pixels.Color(255, 0, 0), - pixels.Color(0, 255, 255)); - blinkDelayTwoColor(100, 2, pixels.Color(0, 255, 0), - pixels.Color(0, 0, 255)); - break; - case LED_EFFECT_WIFI_CONNECT_SUCCESS: - case LED_FLASH_SUCCESS: - blinkDelayColor(150, 3, 0, 255, 0); - break; - case LED_PROGRESS_100: - pixels.setPixelColor(0, pixels.Color(0, 255, 0)); - case LED_PROGRESS_75: - pixels.setPixelColor(1, pixels.Color(0, 255, 0)); - case LED_PROGRESS_50: - pixels.setPixelColor(2, pixels.Color(0, 255, 0)); - case LED_PROGRESS_25: - pixels.setPixelColor(3, pixels.Color(0, 255, 0)); - pixels.show(); - break; - case LED_EFFECT_NOSTR_ZAP: - { -#ifdef HAS_FRONTLIGHT - bool frontlightWasOn = false; +void LedHandler::frontlightFadeIn(uint num) { + frontlightFadeIn(num, preferences.getUInt("flEffectDelay")); +} - if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) - { - if (frontlightOn) - { - frontlightWasOn = true; - frontlightFadeOutAll(flDelayTime, true); +void LedHandler::frontlightFadeOut(uint num) { + frontlightFadeOut(num, preferences.getUInt("flEffectDelay")); +} + +void LedHandler::frontlightSetBrightness(uint brightness) { + if (isDNDActive() || brightness > 4096) { + return; + } + + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, brightness); + } +} + +std::vector LedHandler::frontlightGetStatus() { + std::vector statuses; + for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) { + uint16_t a = 0, b = 0; + flArray.getPWM(ledPin, &a, &b); + statuses.push_back(round(b - a / 4096)); + } + return statuses; +} + +void LedHandler::frontlightFadeInAll(int flDelayTime, bool staggered) { + if (preferences.getBool("flDisable") || frontlightIsOn() || flInTransition) { + return; + } + + flInTransition = true; + const int maxBrightness = preferences.getUInt("flMaxBrightness"); + + if (staggered) { + int step = FL_FADE_STEP; + int staggerDelay = flDelayTime / NUM_SCREENS; + + for (int dutyCycle = 0; dutyCycle <= maxBrightness + (NUM_SCREENS - 1) * maxBrightness / NUM_SCREENS; dutyCycle += step) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + int ledBrightness = dutyCycle - ledPin * maxBrightness / NUM_SCREENS; + if (ledBrightness < 0) { + ledBrightness = 0; + } else if (ledBrightness > maxBrightness) { + ledBrightness = maxBrightness; + } + flArray.setPWM(ledPin + 1, 0, ledBrightness); } - else - { - frontlightFadeInAll(flDelayTime, true); - } - } -#endif - for (int flash = 0; flash < random(7, 10); flash++) - { - lightningStrike(); - delay(random(50, 150)); - } - // blinkDelayColor(250, 3, 142, 48, 235); - // blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235), - // pixels.Color(169, 21, 255)); -#ifdef HAS_FRONTLIGHT - if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP)) - { - vTaskDelay(pdMS_TO_TICKS(10)); - if (frontlightWasOn) - { - frontlightFadeInAll(flDelayTime, true); - } - else - { - frontlightFadeOutAll(flDelayTime, true); - } - } -#endif - break; + vTaskDelay(pdMS_TO_TICKS(staggerDelay)); } - case LED_FLASH_UPDATE: - blinkDelayTwoColor(250, 3, pixels.Color(0, 230, 0), - pixels.Color(230, 230, 0)); - break; - case LED_FLASH_BLOCK_NOTIFY: - { -#ifdef HAS_FRONTLIGHT - bool frontlightWasOn = false; + } else { + for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); + } + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } + } + frontlightOn = true; + flInTransition = false; +} - if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) - { - if (frontlightOn) - { - frontlightWasOn = true; - frontlightFadeOutAll(flDelayTime, true); +void LedHandler::frontlightFadeOutAll(int flDelayTime, bool staggered) { + if (preferences.getBool("flDisable") || !frontlightIsOn() || flInTransition) { + return; + } + + flInTransition = true; + if (staggered) { + int maxBrightness = preferences.getUInt("flMaxBrightness"); + int step = FL_FADE_STEP; + int staggerDelay = flDelayTime / NUM_SCREENS; + + for (int dutyCycle = maxBrightness; dutyCycle >= 0; dutyCycle -= step) { + for (int ledPin = 0; ledPin < NUM_SCREENS; ledPin++) { + int ledBrightness = dutyCycle - (NUM_SCREENS - 1 - ledPin) * maxBrightness / NUM_SCREENS; + if (ledBrightness < 0) { + ledBrightness = 0; + } else if (ledBrightness > maxBrightness) { + ledBrightness = maxBrightness; + } + flArray.setPWM(ledPin + 1, 0, ledBrightness); } - else - { - frontlightFadeInAll(flDelayTime, true); + vTaskDelay(pdMS_TO_TICKS(staggerDelay)); + } + } else { + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) { + for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { + flArray.setPWM(ledPin, 0, dutyCycle); } - } + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } + } + + flArray.allOFF(); + frontlightOn = false; + flInTransition = false; +} + +void LedHandler::frontlightFadeIn(uint num, int flDelayTime) { + if (isDNDActive() || preferences.getBool("flDisable")) { + return; + } + + for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { + flArray.setPWM(num, 0, dutyCycle); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } +} + +void LedHandler::frontlightFadeOut(uint num, int flDelayTime) { + if (isDNDActive() || preferences.getBool("flDisable") || !frontlightIsOn()) { + return; + } + + for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { + flArray.setPWM(num, 0, dutyCycle); + vTaskDelay(pdMS_TO_TICKS(flDelayTime)); + } +} + +void LedHandler::initializeFrontlight() { + if (!flArray.begin(PCA9685_MODE1_AUTOINCR | PCA9685_MODE1_ALLCALL, PCA9685_MODE2_TOTEMPOLE)) + { + Serial.println(F("FL driver error")); + return; + } + flArray.setFrequency(200); + Serial.println(F("FL driver active")); + + if (!preferences.isKey("flMaxBrightness")) + { + preferences.putUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); + } + if (!preferences.isKey("flEffectDelay")) + { + preferences.putUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY); + } + + if (!preferences.isKey("flFlashOnUpd")) + { + preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); + } +} #endif - blinkDelayTwoColor(250, 3, pixels.Color(224, 67, 0), - pixels.Color(8, 2, 0)); -#ifdef HAS_FRONTLIGHT - if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE)) - { - vTaskDelay(pdMS_TO_TICKS(10)); - if (frontlightWasOn) - { - frontlightFadeInAll(flDelayTime, true); - } - else - { - frontlightFadeOutAll(flDelayTime, true); - } - } -#endif - break; - } - case LED_EFFECT_WIFI_WAIT_FOR_CONFIG: - blinkDelayTwoColor(100, 1, pixels.Color(8, 161, 236), - pixels.Color(156, 225, 240)); - break; - case LED_EFFECT_WIFI_ERASE_SETTINGS: - blinkDelay(100, 3); - break; - case LED_EFFECT_WIFI_CONNECTING: - for (int i = NEOPIXEL_COUNT; i >= 0; i--) - { - for (int j = NEOPIXEL_COUNT; j >= 0; j--) - { - if (j == i) - { - pixels.setPixelColor(i, pixels.Color(16, 197, 236)); - } - else - { - pixels.setPixelColor(j, pixels.Color(0, 0, 0)); - } - } - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(100)); - } - break; - case LED_EFFECT_PAUSE_TIMER: - for (int i = NEOPIXEL_COUNT; i >= 0; i--) - { - for (int j = NEOPIXEL_COUNT; j >= 0; j--) - { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) - c = pixels.Color(0, 255, 0); - pixels.setPixelColor(j, c); - } - - pixels.show(); - - delay(100); - } - pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - pixels.show(); - - delay(900); - - pixels.clear(); - pixels.show(); - break; - case LED_EFFECT_START_TIMER: - pixels.clear(); - pixels.setPixelColor((NEOPIXEL_COUNT - 1), pixels.Color(255, 0, 0)); - pixels.show(); - - delay(900); - - for (int i = NEOPIXEL_COUNT; i--; i > 0) - { - for (int j = NEOPIXEL_COUNT; j--; j > 0) - { - uint32_t c = pixels.Color(0, 0, 0); - if (i == j) - c = pixels.Color(0, 255, 0); - - pixels.setPixelColor(j, c); - } - - pixels.show(); - - delay(100); - } - - pixels.clear(); - pixels.show(); - break; - } - - // revert to previous state unless power test - - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, oldLights[i]); - } - - pixels.show(); - } - } - } -} - -void setupLeds() -{ - loadDNDSettings(); - pixels.begin(); - pixels.setBrightness(preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS)); - pixels.clear(); - pixels.show(); - setupLedTask(); - if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) - { - while (!ledTaskQueue) - { - delay(1); - // wait until queue is available - } - queueLedEffect(LED_POWER_TEST); - } -} - -void setupLedTask() -{ - ledTaskQueue = xQueueCreate(5, sizeof(uint)); - - xTaskCreate(ledTask, "LedTask", 2048, NULL, 10, &ledTaskHandle); -} - -void blinkDelay(int d, int times) -{ - for (int j = 0; j < times; j++) - { - pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - pixels.setPixelColor(1, pixels.Color(0, 255, 0)); - pixels.setPixelColor(2, pixels.Color(255, 0, 0)); - pixels.setPixelColor(3, pixels.Color(0, 255, 0)); - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - - pixels.setPixelColor(0, pixels.Color(255, 255, 0)); - pixels.setPixelColor(1, pixels.Color(0, 255, 255)); - pixels.setPixelColor(2, pixels.Color(255, 255, 0)); - pixels.setPixelColor(3, pixels.Color(0, 255, 255)); - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - } - pixels.clear(); - pixels.show(); -} - -void blinkDelayColor(int d, int times, uint r, uint g, uint b) -{ - for (int j = 0; j < times; j++) - { - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, pixels.Color(r, g, b)); - } - - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - - pixels.clear(); - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - } - pixels.clear(); - pixels.show(); -} - -void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2) -{ - for (int j = 0; j < times; j++) - { - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, c1); - } - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, c2); - } - pixels.show(); - vTaskDelay(pdMS_TO_TICKS(d)); - } - pixels.clear(); - pixels.show(); -} - -void clearLeds() -{ - preferences.putBool("ledStatus", false); - pixels.clear(); - pixels.show(); -} - -void setLights(int r, int g, int b) { setLights(pixels.Color(r, g, b)); } - -void setLights(uint32_t color) -{ - bool ledStatus = true; - - for (int i = 0; i < NEOPIXEL_COUNT; i++) - { - pixels.setPixelColor(i, color); - } - pixels.show(); - - if (color == pixels.Color(0, 0, 0)) - { - ledStatus = false; - } - else - { - saveLedState(); - } - preferences.putBool("ledStatus", ledStatus); -} - -void saveLedState() -{ - for (int i = 0; i < pixels.numPixels(); i++) - { - int pixelColor = pixels.getPixelColor(i); - char key[12]; - snprintf(key, 12, "%s%d", "ledColor_", i); - preferences.putUInt(key, pixelColor); - } - - xTaskNotifyGive(eventSourceTaskHandle); -} - -void restoreLedState() -{ - for (int i = 0; i < pixels.numPixels(); i++) - { - char key[12]; - snprintf(key, 12, "%s%d", "ledColor_", i); - uint pixelColor = preferences.getUInt(key, pixels.Color(0, 0, 0)); - pixels.setPixelColor(i, pixelColor); - } - - pixels.show(); -} - -QueueHandle_t getLedTaskQueue() { return ledTaskQueue; } - -bool queueLedEffect(uint effect) { - if (isDNDActive()) { - return false; // Don't queue any effects during DND mode - } - if (ledTaskQueue == NULL) - { - return false; - } - - uint flashType = effect; - xQueueSend(ledTaskQueue, &flashType, portMAX_DELAY); - return true; -} - -void ledRainbow(int wait) -{ - // Hue of first pixel runs 5 complete loops through the color wheel. - // Color wheel has a range of 65536 but it's OK if we roll over, so - // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time - // means we'll make 5*65536/256 = 1280 passes through this loop: - for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; - firstPixelHue += 256) - { - // strip.rainbow() can take a single argument (first pixel hue) or - // optionally a few extras: number of rainbow repetitions (default 1), - // saturation and value (brightness) (both 0-255, similar to the - // ColorHSV() function, default 255), and a true/false flag for whether - // to apply gamma correction to provide 'truer' colors (default true). - pixels.rainbow(firstPixelHue); - // Above line is equivalent to: - // strip.rainbow(firstPixelHue, 1, 255, 255, true); - pixels.show(); // Update strip with new contents - delayMicroseconds(wait); - // vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment - } -} - -void ledTheaterChase(uint32_t color, int wait) -{ - for (int a = 0; a < 10; a++) - { // Repeat 10 times... - for (int b = 0; b < 3; b++) - { // 'b' counts from 0 to 2... - pixels.clear(); // Set all pixels in RAM to 0 (off) - // 'c' counts up from 'b' to end of strip in steps of 3... - for (int c = b; c < pixels.numPixels(); c += 3) - { - pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' - } - pixels.show(); // Update strip with new contents - vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment - } - } -} - -void ledTheaterChaseRainbow(int wait) -{ - int firstPixelHue = 0; // First pixel starts at red (hue 0) - for (int a = 0; a < 30; a++) - { // Repeat 30 times... - for (int b = 0; b < 3; b++) - { // 'b' counts from 0 to 2... - pixels.clear(); // Set all pixels in RAM to 0 (off) - // 'c' counts up from 'b' to end of strip in increments of 3... - for (int c = b; c < pixels.numPixels(); c += 3) - { - // hue of pixel 'c' is offset by an amount to make one full - // revolution of the color wheel (range 65536) along the length - // of the strip (strip.numPixels() steps): - int hue = firstPixelHue + c * 65536L / pixels.numPixels(); - uint32_t color = pixels.gamma32(pixels.ColorHSV(hue)); // hue -> RGB - pixels.setPixelColor(c, color); // Set pixel 'c' to value 'color' - } - pixels.show(); // Update strip with new contents - vTaskDelay(pdMS_TO_TICKS(wait)); // Pause for a moment - firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames - } - } -} - -void lightningStrike() -{ - uint32_t PURPLE = pixels.Color(128, 0, 128); - uint32_t YELLOW = pixels.Color(255, 226, 41); - - // Randomly choose which LEDs to light up - for (int i = 0; i < pixels.numPixels(); i++) - { - if (random(2) == 0) - { // 50% chance for each LED - pixels.setPixelColor(i, YELLOW); - } - else - { - pixels.setPixelColor(i, PURPLE); - } - } - pixels.show(); - - delay(random(10, 50)); // Flash duration - - // Return to purple background - // setAllPixels(PURPLE); -} - -Adafruit_NeoPixel getPixels() { return pixels; } diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index fe72ffa..e599e2a 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "lib/shared.hpp" #include "lib/webserver.hpp" @@ -15,12 +16,11 @@ #define NEOPIXEL_COUNT 4 #endif +// LED effect constants const int LED_FLASH_ERROR = 0; const int LED_FLASH_SUCCESS = 1; const int LED_FLASH_UPDATE = 2; - const int LED_EFFECT_CONFIGURING = 10; - const int LED_FLASH_BLOCK_NOTIFY = 4; const int LED_EFFECT_START_TIMER = 5; const int LED_EFFECT_PAUSE_TIMER = 6; @@ -30,61 +30,15 @@ const int LED_EFFECT_WIFI_CONNECTING = 101; const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103; const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104; - const int LED_PROGRESS_25 = 200; const int LED_PROGRESS_50 = 201; const int LED_PROGRESS_75 = 202; const int LED_PROGRESS_100 = 203; - const int LED_DATA_PRICE_ERROR = 300; const int LED_DATA_BLOCK_ERROR = 301; - const int LED_EFFECT_NOSTR_ZAP = 400; - const int LED_FLASH_IDENTIFY = 990; const int LED_POWER_TEST = 999; -extern TaskHandle_t ledTaskHandle; -extern Adafruit_NeoPixel pixels; - -void ledTask(void *pvParameters); -void setupLeds(); -void setupLedTask(); -void blinkDelay(int d, int times); -void blinkDelayColor(int d, int times, uint r, uint g, uint b); -void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2); -void clearLeds(); -void saveLedState(); -void restoreLedState(); -QueueHandle_t getLedTaskQueue(); -bool queueLedEffect(uint effect); -void setLights(int r, int g, int b); -void setLights(uint32_t color); -void ledRainbow(int wait); -void ledTheaterChaseRainbow(int wait); -void ledTheaterChase(uint32_t color, int wait); -Adafruit_NeoPixel getPixels(); -void lightningStrike(); - -#ifdef HAS_FRONTLIGHT -void frontlightFlash(int flDelayTime); -void frontlightFadeInAll(); -void frontlightFadeOutAll(); -void frontlightFadeIn(uint num); -void frontlightFadeOut(uint num); - -std::vector frontlightGetStatus(); - -void frontlightSetBrightness(uint brightness); -bool frontlightIsOn(); - -void frontlightFadeInAll(int flDelayTime); -void frontlightFadeInAll(int flDelayTime, bool staggered); -void frontlightFadeOutAll(int flDelayTime); -void frontlightFadeOutAll(int flDelayTime, bool staggered); - -void frontlightFadeIn(uint num, int flDelayTime); -void frontlightFadeOut(uint num, int flDelayTime); -#endif // Do Not Disturb mode settings struct DNDTimeRange { @@ -94,12 +48,88 @@ struct DNDTimeRange { uint8_t endMinute; }; -extern bool dndEnabled; -extern bool dndTimeBasedEnabled; -extern DNDTimeRange dndTimeRange; +class LedHandler { +public: + static LedHandler& getInstance(); + + // Delete copy constructor and assignment operator + LedHandler(const LedHandler&) = delete; + LedHandler& operator=(const LedHandler&) = delete; -void setDNDEnabled(bool enabled); -void setDNDTimeBasedEnabled(bool enabled); -void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); -bool isDNDActive(); -bool isTimeInDNDRange(uint8_t hour, uint8_t minute); \ No newline at end of file + void setup(); + void setupTask(); + bool queueEffect(uint effect); + void clear(); + void setLights(int r, int g, int b); + void setLights(uint32_t color); + void saveLedState(); + void restoreLedState(); + QueueHandle_t getTaskQueue() const { return ledTaskQueue; } + Adafruit_NeoPixel& getPixels() { return pixels; } + + // DND methods + void setDNDEnabled(bool enabled); + void setDNDTimeBasedEnabled(bool enabled); + void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); + bool isDNDActive() const; + bool isTimeInDNDRange(uint8_t hour, uint8_t minute) const; + + // DND getters + bool isDNDEnabled() const { return dndEnabled; } + bool isDNDTimeBasedEnabled() const { return dndTimeBasedEnabled; } + uint8_t getDNDStartHour() const { return dndTimeRange.startHour; } + uint8_t getDNDStartMinute() const { return dndTimeRange.startMinute; } + uint8_t getDNDEndHour() const { return dndTimeRange.endHour; } + uint8_t getDNDEndMinute() const { return dndTimeRange.endMinute; } + + // Effect methods + void rainbow(int wait); + void theaterChase(uint32_t color, int wait); + void theaterChaseRainbow(int wait); + void lightningStrike(); + void blinkDelay(int d, int times); + void blinkDelayColor(int d, int times, uint r, uint g, uint b); + void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2); + +#ifdef HAS_FRONTLIGHT + void frontlightFlash(int flDelayTime); + void frontlightFadeInAll(); + void frontlightFadeOutAll(); + void frontlightFadeIn(uint num); + void frontlightFadeOut(uint num); + std::vector frontlightGetStatus(); + void frontlightSetBrightness(uint brightness); + bool frontlightIsOn() const { return frontlightOn; } + void frontlightFadeInAll(int flDelayTime, bool staggered = false); + void frontlightFadeOutAll(int flDelayTime, bool staggered = false); + void frontlightFadeIn(uint num, int flDelayTime); + void frontlightFadeOut(uint num, int flDelayTime); + void initializeFrontlight(); +#endif + +private: + LedHandler(); // Private constructor for singleton + void loadDNDSettings(); + static void ledTask(void* pvParameters); + + Adafruit_NeoPixel pixels; + TaskHandle_t ledTaskHandle; + QueueHandle_t ledTaskQueue; + uint ledTaskParams; + + // DND members + bool dndEnabled; + bool dndTimeBasedEnabled; + DNDTimeRange dndTimeRange; + +#ifdef HAS_FRONTLIGHT + static constexpr uint16_t FL_FADE_STEP = 25; + bool frontlightOn; + bool flInTransition; +#endif +}; + +// Global accessor function +inline LedHandler& getLedHandler() { + return LedHandler::getInstance(); +} \ No newline at end of file diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index b9a7a46..3c996a2 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -1,4 +1,5 @@ #include "nostr_notify.hpp" +#include "led_handler.hpp" std::vector pools; nostr::Transport *transport; @@ -286,7 +287,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) { - queueLedEffect(LED_EFFECT_NOSTR_ZAP); + getLedHandler().queueEffect(LED_EFFECT_NOSTR_ZAP); } if (timerPeriod > 0) { diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index c481b72..b42a505 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -1,4 +1,5 @@ #include "ota.hpp" +#include "led_handler.hpp" TaskHandle_t taskOtaHandle = NULL; bool isOtaUpdating = false; @@ -31,6 +32,9 @@ void setupOTA() void onOTAProgress(unsigned int progress, unsigned int total) { uint percentage = progress / (total / 100); + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + pixels.fill(pixels.Color(0, 255, 0)); if (percentage < 100) { @@ -84,15 +88,15 @@ void handleOTATask(void *parameter) { if (msg.updateType == UPDATE_ALL) { isOtaUpdating = true; - queueLedEffect(LED_FLASH_UPDATE); + getLedHandler().queueEffect(LED_FLASH_UPDATE); int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI); - queueLedEffect(LED_FLASH_UPDATE); + getLedHandler().queueEffect(LED_FLASH_UPDATE); int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE); if (resultWebUi == 0 && resultFw == 0) { ESP.restart(); } else { - queueLedEffect(LED_FLASH_ERROR); + getLedHandler().queueEffect(LED_FLASH_ERROR); vTaskDelay(pdMS_TO_TICKS(3000)); ESP.restart(); } diff --git a/src/lib/timers.cpp b/src/lib/timers.cpp index 4e67951..850ead3 100644 --- a/src/lib/timers.cpp +++ b/src/lib/timers.cpp @@ -1,4 +1,5 @@ #include "timers.hpp" +#include "led_handler.hpp" esp_timer_handle_t screenRotateTimer; esp_timer_handle_t minuteTimer; @@ -49,11 +50,11 @@ void setTimerActive(bool status) { if (status) { esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond); - queueLedEffect(LED_EFFECT_START_TIMER); + getLedHandler().queueEffect(LED_EFFECT_START_TIMER); preferences.putBool("timerActive", true); } else { esp_timer_stop(screenRotateTimer); - queueLedEffect(LED_EFFECT_PAUSE_TIMER); + getLedHandler().queueEffect(LED_EFFECT_PAUSE_TIMER); preferences.putBool("timerActive", false); } diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 2ba0a4d..4897811 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -1,4 +1,6 @@ #include "webserver.hpp" +#include "lib/led_handler.hpp" +#include "lib/shared.hpp" static const char* JSON_CONTENT = "application/json"; @@ -231,6 +233,7 @@ void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, JsonDocument getStatusObject() { + auto& ledHandler = getLedHandler(); JsonDocument root; root["currentScreen"] = ScreenHandler::getCurrentScreen(); @@ -238,25 +241,21 @@ JsonDocument getStatusObject() root["timerRunning"] = isTimerActive(); root["isOTAUpdating"] = getIsOTAUpdating(); root["espUptime"] = esp_timer_get_time() / 1000000; - // root["currentPrice"] = getPrice(); - // root["currentBlockHeight"] = getBlockHeight(); root["espFreeHeap"] = ESP.getFreeHeap(); root["espHeapSize"] = ESP.getHeapSize(); - // root["espFreePsram"] = ESP.getFreePsram(); - // root["espPsramSize"] = ESP.getPsramSize(); JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected(); - conStatus["nostr"] = nostrConnected(); root["rssi"] = WiFi.RSSI(); root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency()); + #ifdef HAS_FRONTLIGHT - std::vector statuses = frontlightGetStatus(); + std::vector statuses = ledHandler.frontlightGetStatus(); uint16_t arr[NUM_SCREENS]; std::copy(statuses.begin(), statuses.end(), arr); @@ -270,22 +269,24 @@ JsonDocument getStatusObject() #endif // Add DND status - root["dnd"]["enabled"] = dndEnabled; - root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; - root["dnd"]["startTime"] = String(dndTimeRange.startHour) + ":" + - (dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); - root["dnd"]["endTime"] = String(dndTimeRange.endHour) + ":" + - (dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); - root["dnd"]["active"] = isDNDActive(); + root["dnd"]["enabled"] = ledHandler.isDNDEnabled(); + root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled(); + root["dnd"]["startTime"] = String(ledHandler.getDNDStartHour()) + ":" + + (ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute()); + root["dnd"]["endTime"] = String(ledHandler.getDNDEndHour()) + ":" + + (ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute()); + root["dnd"]["active"] = ledHandler.isDNDActive(); return root; } JsonDocument getLedStatusObject() { + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + JsonDocument root; JsonArray colors = root["data"].to(); - // Adafruit_NeoPixel pix = getPixels(); for (uint i = 0; i < pixels.numPixels(); i++) { @@ -295,13 +296,7 @@ JsonDocument getLedStatusObject() uint blue = pixColor & 0xFF; char hexColor[8]; snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue); - - - JsonObject object = colors.add(); - object["red"] = red; - object["green"] = green; - object["blue"] = blue; - object["hex"] = hexColor; + colors.add(hexColor); } return root; @@ -621,24 +616,26 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) // Handle DND settings if (settings.containsKey("dnd")) { JsonObject dndObj = settings["dnd"]; + auto& ledHandler = getLedHandler(); + if (dndObj.containsKey("timeBasedEnabled")) { - setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); + ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); } if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { - setDNDTimeRange( - dndObj["startHour"].as(), - dndObj["startMinute"].as(), - dndObj["endHour"].as(), - dndObj["endMinute"].as() - ); + ledHandler.setDNDTimeRange( + dndObj["startHour"].as(), + dndObj["startMinute"].as(), + dndObj["endHour"].as(), + dndObj["endMinute"].as()); } } request->send(HTTP_OK); if (settingsChanged) { - queueLedEffect(LED_FLASH_SUCCESS); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_FLASH_SUCCESS); } } @@ -659,7 +656,8 @@ void onApiRestart(AsyncWebServerRequest *request) void onApiIdentify(AsyncWebServerRequest *request) { - queueLedEffect(LED_FLASH_IDENTIFY); + auto& ledHandler = getLedHandler(); + ledHandler.queueEffect(LED_FLASH_IDENTIFY); request->send(HTTP_OK); } @@ -797,12 +795,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); // Add DND settings - root["dnd"]["enabled"] = dndEnabled; - root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; - root["dnd"]["startHour"] = dndTimeRange.startHour; - root["dnd"]["startMinute"] = dndTimeRange.startMinute; - root["dnd"]["endHour"] = dndTimeRange.endHour; - root["dnd"]["endMinute"] = dndTimeRange.endMinute; + auto& ledHandler = getLedHandler(); + root["dnd"]["enabled"] = ledHandler.isDNDEnabled(); + root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled(); + root["dnd"]["startHour"] = ledHandler.getDNDStartHour(); + root["dnd"]["startMinute"] = ledHandler.getDNDStartMinute(); + root["dnd"]["endHour"] = ledHandler.getDNDEndHour(); + root["dnd"]["endMinute"] = ledHandler.getDNDEndMinute(); AsyncResponseStream *response = request->beginResponseStream(JSON_CONTENT); @@ -929,7 +928,8 @@ void onApiRestartDataSources(AsyncWebServerRequest *request) void onApiLightsOff(AsyncWebServerRequest *request) { - setLights(0, 0, 0); + auto& ledHandler = getLedHandler(); + ledHandler.setLights(0, 0, 0); request->send(HTTP_OK); } @@ -944,13 +944,15 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) if (rgbColor.compareTo("off") == 0) { - setLights(0, 0, 0); + auto& ledHandler = getLedHandler(); + ledHandler.setLights(0, 0, 0); } else { uint r, g, b; sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b); - setLights(r, g, b); + auto& ledHandler = getLedHandler(); + ledHandler.setLights(r, g, b); } JsonDocument doc; @@ -968,6 +970,9 @@ void onApiLightsSetColor(AsyncWebServerRequest *request) void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) { + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + JsonArray lights = json.as(); if (lights.size() != pixels.numPixels()) @@ -1016,7 +1021,7 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) } pixels.show(); - saveLedState(); + ledHandler.saveLedState(); request->send(HTTP_OK); } @@ -1080,19 +1085,21 @@ void onApiShowCurrency(AsyncWebServerRequest *request) #ifdef HAS_FRONTLIGHT void onApiFrontlightOn(AsyncWebServerRequest *request) { - frontlightFadeInAll(); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFadeInAll(); request->send(HTTP_OK); } void onApiFrontlightStatus(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); AsyncResponseStream *response = request->beginResponseStream(JSON_CONTENT); JsonDocument root; - std::vector statuses = frontlightGetStatus(); + std::vector statuses = ledHandler.frontlightGetStatus(); uint16_t arr[NUM_SCREENS]; std::copy(statuses.begin(), statuses.end(), arr); @@ -1105,7 +1112,8 @@ void onApiFrontlightStatus(AsyncWebServerRequest *request) void onApiFrontlightFlash(AsyncWebServerRequest *request) { - frontlightFlash(preferences.getUInt("flEffectDelay")); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFlash(preferences.getUInt("flEffectDelay")); request->send(HTTP_OK); } @@ -1114,7 +1122,8 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request) { if (request->hasParam("b")) { - frontlightSetBrightness(request->getParam("b")->value().toInt()); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightSetBrightness(request->getParam("b")->value().toInt()); request->send(HTTP_OK); } else @@ -1125,21 +1134,51 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request) void onApiFrontlightOff(AsyncWebServerRequest *request) { - frontlightFadeOutAll(); + auto& ledHandler = getLedHandler(); + ledHandler.frontlightFadeOutAll(); request->send(HTTP_OK); } #endif +void onApiDNDTimeBasedEnable(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); + ledHandler.setDNDTimeBasedEnabled(true); + request->send(200); +} + +void onApiDNDTimeBasedDisable(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); + ledHandler.setDNDTimeBasedEnabled(false); + request->send(200); +} + +void onApiDNDSetTimeRange(AsyncWebServerRequest *request) { + if (request->hasParam("startHour") && request->hasParam("startMinute") && + request->hasParam("endHour") && request->hasParam("endMinute")) { + auto& ledHandler = getLedHandler(); + uint8_t startHour = request->getParam("startHour")->value().toInt(); + uint8_t startMinute = request->getParam("startMinute")->value().toInt(); + uint8_t endHour = request->getParam("endHour")->value().toInt(); + uint8_t endMinute = request->getParam("endMinute")->value().toInt(); + + ledHandler.setDNDTimeRange(startHour, startMinute, endHour, endMinute); + request->send(200); + } else { + request->send(400); + } +} + void onApiDNDStatus(AsyncWebServerRequest *request) { + auto& ledHandler = getLedHandler(); JsonDocument doc; - doc["enabled"] = dndEnabled; - doc["timeBasedEnabled"] = dndTimeBasedEnabled; - doc["startTime"] = String(dndTimeRange.startHour) + ":" + - (dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); - doc["endTime"] = String(dndTimeRange.endHour) + ":" + - (dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); - doc["active"] = isDNDActive(); + doc["enabled"] = ledHandler.isDNDEnabled(); + doc["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled(); + doc["startTime"] = String(ledHandler.getDNDStartHour()) + ":" + + (ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute()); + doc["endTime"] = String(ledHandler.getDNDEndHour()) + ":" + + (ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute()); + doc["active"] = ledHandler.isDNDActive(); String response; serializeJson(doc, response); @@ -1147,11 +1186,92 @@ void onApiDNDStatus(AsyncWebServerRequest *request) { } void onApiDNDEnable(AsyncWebServerRequest *request) { - setDNDEnabled(true); + auto& ledHandler = getLedHandler(); + ledHandler.setDNDEnabled(true); request->send(200); } void onApiDNDDisable(AsyncWebServerRequest *request) { - setDNDEnabled(false); + auto& ledHandler = getLedHandler(); + ledHandler.setDNDEnabled(false); request->send(200); +} + +void onApiLightsGet(AsyncWebServerRequest *request) +{ + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + + DynamicJsonDocument doc(1024); + JsonArray lights = doc.createNestedArray("lights"); + + for (uint i = 0; i < pixels.numPixels(); i++) + { + uint32_t pixColor = pixels.getPixelColor(pixels.numPixels() - i - 1); + JsonObject light = lights.createNestedObject(); + light["r"] = (uint8_t)(pixColor >> 16); + light["g"] = (uint8_t)(pixColor >> 8); + light["b"] = (uint8_t)pixColor; + } + + String output; + serializeJson(doc, output); + request->send(200, "application/json", output); +} + +void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, + size_t index, size_t total) +{ + auto& ledHandler = getLedHandler(); + auto& pixels = ledHandler.getPixels(); + + DynamicJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, data); + if (error) + { + request->send(400); + return; + } + + JsonArray lights = doc["lights"]; + if (lights.size() != pixels.numPixels()) + { + request->send(400); + return; + } + + for (uint i = 0; i < pixels.numPixels(); i++) + { + JsonObject light = lights[i]; + uint8_t red = light["r"]; + uint8_t green = light["g"]; + uint8_t blue = light["b"]; + + pixels.setPixelColor((pixels.numPixels() - i - 1), + pixels.Color(red, green, blue)); + } + pixels.show(); + + request->send(200); +} + +void onApiSettings(AsyncWebServerRequest *request, JsonVariant &json) +{ + JsonObject settings = json.as(); + auto& ledHandler = getLedHandler(); + + if (settings.containsKey("dnd")) { + JsonObject dndObj = settings["dnd"]; + if (dndObj.containsKey("timeBasedEnabled")) { + ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); + } + if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && + dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { + ledHandler.setDNDTimeRange( + dndObj["startHour"].as(), + dndObj["startMinute"].as(), + dndObj["endHour"].as(), + dndObj["endMinute"].as()); + } + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4943a51..c892a03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #define WEBSERVER_H #include "ESPAsyncWebServer.h" #include "lib/config.hpp" +#include "lib/led_handler.hpp" uint wifiLostConnection; uint priceNotifyLostConnection = 0; @@ -58,13 +59,14 @@ void handleFrontlight() { if (hasLightLevel() && preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { uint lightLevel = getLightLevel(); uint luxThreshold = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); + auto& ledHandler = getLedHandler(); if (lightLevel <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) { - if (frontlightIsOn()) frontlightFadeOutAll(); - } else if (lightLevel < luxThreshold && !frontlightIsOn()) { - frontlightFadeInAll(); - } else if (frontlightIsOn() && lightLevel > luxThreshold) { - frontlightFadeOutAll(); + if (ledHandler.frontlightIsOn()) ledHandler.frontlightFadeOutAll(); + } else if (lightLevel < luxThreshold && !ledHandler.frontlightIsOn()) { + ledHandler.frontlightFadeInAll(); + } else if (ledHandler.frontlightIsOn() && lightLevel > luxThreshold) { + ledHandler.frontlightFadeOutAll(); } } #endif @@ -100,9 +102,7 @@ void checkMissedBlocks() { } } - void monitorDataConnections() { - // Price notification monitoring if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) { handlePriceNotifyDisconnection(); @@ -137,7 +137,6 @@ extern "C" void app_main() { bool thirdPartySource = getDataSource() == THIRD_PARTY_SOURCE; - while (true) { if (eventSourceTaskHandle != NULL) { xTaskNotifyGive(eventSourceTaskHandle); From a6a8b5a0710a4555e18201f7c7fc3a3f1e561370 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:11:53 +0100 Subject: [PATCH 161/188] Refactor EPD code to EPDManager class --- src/lib/block_notify.cpp | 37 +- src/lib/config.cpp | 60 +-- src/lib/epd.cpp | 867 +++++++++++++++---------------------- src/lib/epd.hpp | 119 +++-- src/lib/nostr_notify.cpp | 20 +- src/lib/ota.cpp | 4 +- src/lib/screen_handler.cpp | 16 +- src/lib/shared.cpp | 3 +- src/lib/shared.hpp | 5 +- src/lib/v2_notify.cpp | 5 + src/lib/webserver.cpp | 34 +- 11 files changed, 532 insertions(+), 638 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 111a7a7..37b5b2f 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -181,45 +181,42 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (newBlockHeight < currentBlockHeight) - return; + if (currentBlockHeight == newBlockHeight) + { + return; + } - currentBlockHeight = newBlockHeight; - - // Serial.printf("New block found: %d\r\n", block["height"].as()); - preferences.putUInt("blockHeight", currentBlockHeight); + currentBlockHeight = newBlockHeight; lastBlockUpdate = esp_timer_get_time() / 1000000; if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - // xTaskNotifyGive(blockUpdateTaskHandle); + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } - if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && - preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) - { + if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && + preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) + { uint64_t timerPeriod = 0; if (isTimerActive()) { - // store timer periode before making inactive to prevent artifacts - timerPeriod = getTimerSeconds(); - esp_timer_stop(screenRotateTimer); + timerPeriod = getTimerSeconds(); + esp_timer_stop(screenRotateTimer); } ScreenHandler::setCurrentScreen(SCREEN_BLOCK_HEIGHT); if (timerPeriod > 0) { - esp_timer_start_periodic(screenRotateTimer, + esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } vTaskDelay(pdMS_TO_TICKS(315*NUM_SCREENS)); // Extra delay because of screen switching - } + } - if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) - { + if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) + { vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); - } } } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 5a3fa7d..6354a7a 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -48,7 +48,7 @@ void setup() setupPreferences(); setupHardware(); - setupDisplays(); + EPDManager::getInstance().initialize(); if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { auto& ledHandler = getLedHandler(); @@ -115,7 +115,7 @@ void setup() ButtonHandler::setup(); setupOTA(); - waitUntilNoneBusy(); + EPDManager::getInstance().waitUntilNoneBusy(); #ifdef HAS_FRONTLIGHT if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON)) @@ -126,7 +126,7 @@ void setup() } #endif - forceFullRefresh(); + EPDManager::getInstance().forceFullRefresh(); } void setupWifi() @@ -181,8 +181,8 @@ void setupWifi() wifiManager->getConfigPortalSSID().c_str(), softAP_password.c_str()); // delay(6000); - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); + EPDManager::getInstance().setForegroundColor(GxEPD_BLACK); + EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE); const String qrText = "qrWIFI:S:" + wifiManager->getConfigPortalSSID() + ";T:WPA;P:" + softAP_password.c_str() + ";;"; const String explainText = "*SSID: *\r\n" + @@ -212,58 +212,22 @@ void setupWifi() #endif "\r\n\r\n*FW build date:*\r\n" + formattedDate, qrText}; - setEpdContent(epdContent); }); + + EPDManager::getInstance().setContent(epdContent); }); wm.setSaveConfigCallback([]() { preferences.putBool("wifiConfigured", true); delay(1000); - // just restart after succes + // just restart after success ESP.restart(); }); bool ac = wm.autoConnect(softAP_SSID.c_str(), softAP_password.c_str()); - - // waitUntilNoneBusy(); - // std::array epdContent = {"Welcome!", - // "Bienvenidos!", "Use\r\nweb-interface\r\npara configurar", "Use\r\nla - // interfaz web\r\npara configurar", "Or - // restart\r\nwhile\r\nholding\r\n2nd button\r\r\nto start\r\n QR-config", - // "O reinicie\r\nmientras\r\n mantiene presionado\r\nel segundo - // botón\r\r\npara iniciar\r\nQR-config", ""}; setEpdContent(epdContent); - // esp_task_wdt_init(30, false); - // uint count = 0; - // while (WiFi.status() != WL_CONNECTED) - // { - // if (Serial.available() > 0) - // { - // uint8_t b = Serial.read(); - - // if (parse_improv_serial_byte(x_position, b, x_buffer, - // onImprovCommandCallback, onImprovErrorCallback)) - // { - // x_buffer[x_position++] = b; - // } - // else - // { - // x_position = 0; - // } - // } - // count++; - - // if (count > 2000000) { - // queueLedEffect(LED_EFFECT_HEARTBEAT); - // count = 0; - // } - // } - // esp_task_wdt_deinit(); - // esp_task_wdt_reset(); } - - - setFgColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); - setBgColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); + EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); + EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); } // else // { @@ -299,8 +263,8 @@ void setupPreferences() { preferences.begin("btclock", false); - setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); - setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); + EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); + EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 9808aa7..fa389c6 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -1,455 +1,278 @@ #include "epd.hpp" +// Initialize static members #ifdef IS_BTCLOCK_REV_B -Native_Pin EPD_CS[NUM_SCREENS] = { - Native_Pin(2), - Native_Pin(4), - Native_Pin(6), - Native_Pin(10), - Native_Pin(38), - Native_Pin(21), - Native_Pin(17), +Native_Pin EPDManager::EPD_DC(14); +std::array EPDManager::EPD_CS = { + Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10), + Native_Pin(38), Native_Pin(21), Native_Pin(17) }; -Native_Pin EPD_BUSY[NUM_SCREENS] = { - Native_Pin(3), - Native_Pin(5), - Native_Pin(7), - Native_Pin(9), - Native_Pin(37), - Native_Pin(18), - Native_Pin(16), +std::array EPDManager::EPD_BUSY = { + Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9), + Native_Pin(37), Native_Pin(18), Native_Pin(16) }; -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14) }; - -Native_Pin EPD_DC = Native_Pin(14); -#elif IS_BTCLOCK_V8 -Native_Pin EPD_DC = Native_Pin(38); - -MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), - MCP23X17_Pin(mcp1, 4), +#elif defined(IS_BTCLOCK_V8) +Native_Pin EPDManager::EPD_DC(38); +std::array EPDManager::EPD_BUSY = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), MCP23X17_Pin(mcp1, 4) }; - -MCP23X17_Pin EPD_CS[NUM_SCREENS] = { +std::array EPDManager::EPD_CS = { MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), - MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; - -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 9), - MCP23X17_Pin(mcp2, 11), - MCP23X17_Pin(mcp2, 13), - MCP23X17_Pin(mcp2, 15), - MCP23X17_Pin(mcp2, 1), - MCP23X17_Pin(mcp2, 3), - MCP23X17_Pin(mcp2, 5), - MCP23X17_Pin(mcp2, 7), + MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6) +}; +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp2, 9), MCP23X17_Pin(mcp2, 11), MCP23X17_Pin(mcp2, 13), + MCP23X17_Pin(mcp2, 15), MCP23X17_Pin(mcp2, 1), MCP23X17_Pin(mcp2, 3), + MCP23X17_Pin(mcp2, 5), MCP23X17_Pin(mcp2, 7) }; #else -Native_Pin EPD_CS[NUM_SCREENS] = { - Native_Pin(2), - Native_Pin(4), - Native_Pin(6), - Native_Pin(10), - Native_Pin(33), - Native_Pin(21), - Native_Pin(17), -#if NUM_SCREENS == 9 - // MCP23X17_Pin(mcp2, 7), - Native_Pin(-1), - Native_Pin(-1), -#endif +Native_Pin EPDManager::EPD_DC(14); +std::array EPDManager::EPD_CS = { + Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10), + Native_Pin(33), Native_Pin(21), Native_Pin(17) }; -Native_Pin EPD_BUSY[NUM_SCREENS] = { - Native_Pin(3), - Native_Pin(5), - Native_Pin(7), - Native_Pin(9), - Native_Pin(37), - Native_Pin(18), - Native_Pin(16), +std::array EPDManager::EPD_BUSY = { + Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9), + Native_Pin(37), Native_Pin(18), Native_Pin(16) }; -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14) }; - -Native_Pin EPD_DC = Native_Pin(14); #endif -GxEPD2_BW displays[NUM_SCREENS] = { - EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET_MPD[0], &EPD_BUSY[0]), - EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET_MPD[1], &EPD_BUSY[1]), - EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET_MPD[2], &EPD_BUSY[2]), - EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET_MPD[3], &EPD_BUSY[3]), - EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]), - EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]), - EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]), -#ifdef IS_BTCLOCK_V8 - EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[7], &EPD_BUSY[7]), -#endif -}; - -std::array currentEpdContent; -std::array epdContent; -uint32_t lastFullRefresh[NUM_SCREENS]; -TaskHandle_t tasks[NUM_SCREENS]; -// TaskHandle_t epdTaskHandle = NULL; - -#define UPDATE_QUEUE_SIZE 14 -QueueHandle_t updateQueue; - -// SemaphoreHandle_t epdUpdateSemaphore[NUM_SCREENS]; - -int fgColor = GxEPD_WHITE; -int bgColor = GxEPD_BLACK; - -struct FontFamily { - GFXfont* big; - GFXfont* medium; - GFXfont* small; -}; - -FontFamily antonioFonts = {nullptr, nullptr, nullptr}; -FontFamily oswaldFonts = {nullptr, nullptr, nullptr}; - -const GFXfont *FONT_SMALL; -const GFXfont *FONT_BIG; -const GFXfont *FONT_MEDIUM; -const GFXfont *FONT_SATSYMBOL; - -std::mutex epdUpdateMutex; -std::mutex epdMutex[NUM_SCREENS]; - -#ifdef IS_BTCLOCK_V8 -#define EPD_TASK_STACK_SIZE 4096 -#else -#define EPD_TASK_STACK_SIZE 2048 -#endif - -#define BUSY_TIMEOUT_COUNT 200 -#define BUSY_RETRY_DELAY pdMS_TO_TICKS(10) - -void forceFullRefresh() -{ - for (uint i = 0; i < NUM_SCREENS; i++) - { - lastFullRefresh[i] = NULL; - } +EPDManager& EPDManager::getInstance() { + static EPDManager instance; + return instance; } -void loadFonts(const String& fontName) { - if (fontName == FontNames::ANTONIO) { - // Load Antonio fonts - antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties); - antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties); - antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties); - - FONT_BIG = antonioFonts.big; - FONT_MEDIUM = antonioFonts.medium; - FONT_SMALL = antonioFonts.small; - } else if (fontName == FontNames::OSWALD) { - // Load Oswald fonts - oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties); - oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties); - oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties); - - FONT_BIG = oswaldFonts.big; - FONT_MEDIUM = oswaldFonts.medium; - FONT_SMALL = oswaldFonts.small; - } - - FONT_SATSYMBOL = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties); -} - -void setupDisplays() { - // Load fonts based on preference - String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); - loadFonts(fontName); - - // Initialize displays - std::lock_guard lockMcp(mcpMutex); - for (uint i = 0; i < NUM_SCREENS; i++) { - displays[i].init(0, true, 30); - } - - // Create update queue and task - updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, NULL, 11, NULL); - - // Create display update tasks - for (uint i = 0; i < NUM_SCREENS; i++) { - int *taskParam = new int; - *taskParam = i; - xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam, 11, &tasks[i]); - } - - // Check for storage mode (prevents burn-in) - if (mcp1.read1(0) == LOW) { - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); - epdContent.fill(""); - } else { - // Initialize with custom text or default - String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); - std::array newContent; - newContent.fill(" "); - - for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { - newContent[i] = String(customText[i]); +EPDManager::EPDManager() + : currentContent{} + , content{} + , lastFullRefresh{} + , tasks{} + , updateQueue{nullptr} + , antonioFonts{nullptr, nullptr, nullptr} + , oswaldFonts{nullptr, nullptr, nullptr} + , fontSmall{nullptr} + , fontBig{nullptr} + , fontMedium{nullptr} + , fontSatsymbol{nullptr} + , bgColor{GxEPD_BLACK} + , fgColor{GxEPD_WHITE} + , displays{ + #ifdef IS_BTCLOCK_V8 + EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]), + EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]), + EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]), + EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]), + EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]), + EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]), + EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6]), + EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET[7], &EPD_BUSY[7]) + #else + EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]), + EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]), + EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]), + EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]), + EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]), + EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]), + EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6]) + #endif } - - epdContent = newContent; - } - - setEpdContent(epdContent); +{ } -void setEpdContent(std::array newEpdContent) -{ - setEpdContent(newEpdContent, false); -} - -void setEpdContent(std::array newEpdContent) -{ - std::array conv; - - for (size_t i = 0; i < newEpdContent.size(); ++i) - { - conv[i] = String(newEpdContent[i].c_str()); +EPDManager::~EPDManager() { + // Clean up tasks + for (auto& task : tasks) { + if (task != nullptr) { + vTaskDelete(task); + } } - return setEpdContent(conv); + // Clean up queue + if (updateQueue != nullptr) { + vQueueDelete(updateQueue); + } + + // Clean up fonts + delete antonioFonts.big; + delete antonioFonts.medium; + delete antonioFonts.small; + delete oswaldFonts.big; + delete oswaldFonts.medium; + delete oswaldFonts.small; } -void setEpdContent(std::array newEpdContent, - bool forceUpdate) -{ - std::lock_guard lock(epdUpdateMutex); +void EPDManager::initialize() { + // Load fonts based on preference + String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); + loadFonts(fontName); + // Initialize displays + std::lock_guard lockMcp(mcpMutex); + for (auto& display : displays) { + display.init(0, true, 30); + } + + // Create update queue and task + updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, nullptr, 11, nullptr); + + // Create display update tasks + for (size_t i = 0; i < NUM_SCREENS; i++) { + auto* taskParam = new int(i); + xTaskCreate(updateDisplayTask, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, + taskParam, 11, &tasks[i]); + } + + // Check for storage mode (prevents burn-in) + if (mcp1.read1(0) == LOW) { + setForegroundColor(GxEPD_BLACK); + setBackgroundColor(GxEPD_WHITE); + content.fill(""); + } else { + // Initialize with custom text or default + String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); + std::array newContent; + newContent.fill(" "); + + for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { + newContent[i] = String(customText[i]); + } + + content = newContent; + } + + setContent(content); +} + +void EPDManager::loadFonts(const String& fontName) { + if (fontName == FontNames::ANTONIO) { + // Load Antonio fonts + antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties); + antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties); + antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties); + + fontBig = antonioFonts.big; + fontMedium = antonioFonts.medium; + fontSmall = antonioFonts.small; + } else if (fontName == FontNames::OSWALD) { + // Load Oswald fonts + oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties); + oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties); + oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties); + + fontBig = oswaldFonts.big; + fontMedium = oswaldFonts.medium; + fontSmall = oswaldFonts.small; + } + + fontSatsymbol = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties); +} + +void EPDManager::forceFullRefresh() { + std::fill(lastFullRefresh.begin(), lastFullRefresh.end(), 0); +} + +void EPDManager::setContent(const std::array& newContent, bool forceUpdate) { + std::lock_guard lock(updateMutex); waitUntilNoneBusy(); - for (uint i = 0; i < NUM_SCREENS; i++) - { - if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) - { - epdContent[i] = newEpdContent[i]; - UpdateDisplayTaskItem dispUpdate = {i}; + for (size_t i = 0; i < NUM_SCREENS; i++) { + if (newContent[i].compareTo(currentContent[i]) != 0 || forceUpdate) { + content[i] = newContent[i]; + UpdateDisplayTaskItem dispUpdate{static_cast(i)}; xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY); } } } -void prepareDisplayUpdateTask(void *pvParameters) -{ - UpdateDisplayTaskItem receivedItem; - - while (1) - { - // Wait for a work item to be available in the queue - if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) - { - uint epdIndex = receivedItem.dispNum; - std::lock_guard lock(epdMutex[epdIndex]); - // displays[epdIndex].init(0, false); // Little longer reset duration - // because of MCP - - bool updatePartial = true; - - if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL) - { - String top = epdContent[epdIndex].substring( - 0, epdContent[epdIndex].indexOf("/")); - String bottom = epdContent[epdIndex].substring( - epdContent[epdIndex].indexOf("/") + 1); - splitText(epdIndex, top, bottom, updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("qr"))) - { - renderQr(epdIndex, epdContent[epdIndex], updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("mdi"))) - { - bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial); - if (!updated) - { - continue; - } - } - else if (epdContent[epdIndex].length() > 5) - { - renderText(epdIndex, epdContent[epdIndex], updatePartial); - } - else - { - if (epdContent[epdIndex].length() == 2) - { - showChars(epdIndex, epdContent[epdIndex], updatePartial, FONT_BIG); - } - else if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) - { - if (epdContent[epdIndex].equals("STS")) - { - showDigit(epdIndex, 'S', updatePartial, - FONT_SATSYMBOL); - } - else - { - showChars(epdIndex, epdContent[epdIndex], updatePartial, - FONT_MEDIUM); - } - } - else - { - - showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, - FONT_BIG); - } - } - - xTaskNotifyGive(tasks[epdIndex]); - } +void EPDManager::setContent(const std::array& newContent) { + std::array conv; + for (size_t i = 0; i < newContent.size(); ++i) { + conv[i] = String(newContent[i].c_str()); } + setContent(conv); } -extern "C" void updateDisplay(void *pvParameters) noexcept -{ - const int epdIndex = *(int *)pvParameters; - delete (int *)pvParameters; +std::array EPDManager::getCurrentContent() const { + return currentContent; +} - for (;;) - { - // Wait for the task notification - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - std::lock_guard lock(epdMutex[epdIndex]); - - { - std::lock_guard lockMcp(mcpMutex); - - displays[epdIndex].init(0, false, 40); - } - uint count = 0; - while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) - { - vTaskDelay(pdMS_TO_TICKS(100)); +void EPDManager::waitUntilNoneBusy() { + for (size_t i = 0; i < NUM_SCREENS; i++) { + uint32_t count = 0; + while (EPD_BUSY[i].digitalRead()) { count++; - } - - bool updatePartial = true; - - // Full Refresh every x minutes - if (!lastFullRefresh[epdIndex] || - (millis() - lastFullRefresh[epdIndex]) > - (preferences.getUInt("fullRefreshMin", - DEFAULT_MINUTES_FULL_REFRESH) * - 60 * 1000)) - { - updatePartial = false; - } - - char tries = 0; - while (tries < 3) - { - if (displays[epdIndex].displayWithReturn(updatePartial)) - { - displays[epdIndex].powerOff(); - currentEpdContent[epdIndex] = epdContent[epdIndex]; - if (!updatePartial) - lastFullRefresh[epdIndex] = millis(); - - if (eventSourceTaskHandle != NULL) - xTaskNotifyGive(eventSourceTaskHandle); + vTaskDelay(BUSY_RETRY_DELAY); + if (count == BUSY_TIMEOUT_COUNT) { + vTaskDelay(pdMS_TO_TICKS(100)); + } else if (count > BUSY_TIMEOUT_COUNT + 5) { + log_e("Display %d busy timeout", i); break; } - - vTaskDelay(pdMS_TO_TICKS(100)); - tries++; } } } -void splitText(const uint dispNum, const String &top, const String &bottom, - bool partial) -{ - if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) - { +void EPDManager::setupDisplay(uint dispNum, const GFXfont* font) { + displays[dispNum].setRotation(2); + displays[dispNum].setFont(font); + displays[dispNum].setTextColor(fgColor); + displays[dispNum].fillScreen(bgColor); +} + +void EPDManager::splitText(uint dispNum, const String& top, const String& bottom, bool partial) { + if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { displays[dispNum].setRotation(1); - } - else - { + } else { displays[dispNum].setRotation(2); } - displays[dispNum].setFont(FONT_SMALL); - displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].setFont(fontSmall); + displays[dispNum].setTextColor(fgColor); // Top text int16_t ttbx, ttby; uint16_t ttbw, ttbh; displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx; - uint16_t ty = - ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; + uint16_t ty = ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; // Bottom text int16_t tbbx, tbby; uint16_t tbbw, tbbh; displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx; - uint16_t by = - ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; + uint16_t by = ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; - // Make separator as wide as the shortest text. - uint16_t lineWidth, lineX; - if (tbbw < ttbh) - lineWidth = tbbw; - else - lineWidth = ttbw; - lineX = round((displays[dispNum].width() - lineWidth) / 2); + // Make separator as wide as the shortest text + uint16_t lineWidth = (tbbw < ttbh) ? tbbw : ttbw; + uint16_t lineX = round((displays[dispNum].width() - lineWidth) / 2); - displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].fillScreen(bgColor); displays[dispNum].setCursor(tx, ty); displays[dispNum].print(top); displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3, - lineWidth, 6, 3, getFgColor()); + lineWidth, 6, 3, fgColor); displays[dispNum].setCursor(bx, by); displays[dispNum].print(bottom); } -// Consolidate common display setup code into a helper function -void setupDisplay(const uint dispNum, const GFXfont *font) -{ - displays[dispNum].setRotation(2); - displays[dispNum].setFont(font); - displays[dispNum].setTextColor(getFgColor()); - displays[dispNum].fillScreen(getBgColor()); -} - -void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) -{ +void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* font) { String str(chr); - if (chr == '.') - { + if (chr == '.') { str = "!"; } @@ -465,56 +288,34 @@ void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) displays[dispNum].setCursor(x, y); displays[dispNum].print(str); - if (chr == '.') - { + if (chr == '.') { displays[dispNum].fillRect(0, 0, displays[dispNum].width(), - round(displays[dispNum].height() * 0.67), getBgColor()); + round(displays[dispNum].height() * 0.67), bgColor); } } -int16_t calculateDescent(const GFXfont *font) -{ - int16_t maxDescent = 0; - for (uint16_t i = font->first; i <= font->last; i++) - { - GFXglyph *glyph = &font->glyph[i - font->first]; - int16_t descent = glyph->yOffset; - if (descent > maxDescent) - { - maxDescent = descent; - } - } - return maxDescent; -} - -void showChars(const uint dispNum, const String &chars, bool partial, - const GFXfont *font) -{ +void EPDManager::showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font) { setupDisplay(dispNum, font); int16_t tbx, tby; uint16_t tbw, tbh; displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); - // center the bounding box by transposition of the origin: + // Center the bounding box by transposition of the origin uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; - for (int i = 0; i < chars.length(); i++) - { + for (size_t i = 0; i < chars.length(); i++) { char c = chars[i]; - if (c == '.' || c == ',') - { + if (c == '.' || c == ',') { // For the dot, calculate its specific descent - GFXglyph *dotGlyph = &font->glyph[c - font->first]; + GFXglyph* dotGlyph = &font->glyph[c - font->first]; int16_t dotDescent = dotGlyph->yOffset; // Draw the dot with adjusted y-position displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8); displays[dispNum].print(c); - } - else - { + } else { // For other characters, use the original y-position displays[dispNum].setCursor(x, y); displays[dispNum].print(c); @@ -525,104 +326,70 @@ void showChars(const uint dispNum, const String &chars, bool partial, } } -int getBgColor() { return bgColor; } - -int getFgColor() { return fgColor; } - -void setBgColor(int color) { bgColor = color; } - -void setFgColor(int color) { fgColor = color; } - -std::array getCurrentEpdContent() -{ - return currentEpdContent; -} -void renderText(const uint dispNum, const String &text, bool partial) -{ +bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) { displays[dispNum].setRotation(2); displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); - displays[dispNum].setTextColor(GxEPD_BLACK); - displays[dispNum].setCursor(0, 50); - - std::stringstream ss; - ss.str(text.c_str()); - - std::string line; - - while (std::getline(ss, line, '\n')) - { - if (line.rfind("*", 0) == 0) - { - line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); - - displays[dispNum].setFont(&FreeSansBold9pt7b); - displays[dispNum].println(line.c_str()); - } - else - { - displays[dispNum].setFont(&FreeSans9pt7b); - displays[dispNum].println(line.c_str()); - } - } -} - -bool renderIcon(const uint dispNum, const String &text, bool partial) -{ - displays[dispNum].setRotation(2); - - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(getBgColor()); - displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].height()); + displays[dispNum].fillScreen(bgColor); + displays[dispNum].setTextColor(fgColor); uint iconIndex = 0; uint width = 122; uint height = 122; - if (text.endsWith("rocket")) - { + + if (text.endsWith("rocket")) { iconIndex = 1; - } - else if (text.endsWith("lnbolt")) - { + } else if (text.endsWith("lnbolt")) { iconIndex = 2; - } - else if (text.endsWith("bitaxe")) - { + } else if (text.endsWith("bitaxe")) { width = 88; height = 220; iconIndex = 3; - } - else if (text.endsWith("miningpool")) - { + } else if (text.endsWith("miningpool")) { LogoData logo = MiningPoolStatsFetch::getInstance().getLogo(); - - if (logo.size == 0) - { + if (logo.size == 0) { Serial.println(F("No logo found")); return false; } int x_offset = (displays[dispNum].width() - logo.width) / 2; int y_offset = (displays[dispNum].height() - logo.height) / 2; - // Close the file - - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, logo.width, logo.height, getFgColor()); + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, + logo.width, logo.height, fgColor); return true; } int x_offset = (displays[dispNum].width() - width) / 2; int y_offset = (displays[dispNum].height() - height) / 2; - - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); - + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], + width, height, fgColor); return true; - // displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); } -void renderQr(const uint dispNum, const String &text, bool partial) -{ +void EPDManager::renderText(uint dispNum, const String& text, bool partial) { + displays[dispNum].setRotation(2); + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(GxEPD_WHITE); + displays[dispNum].setTextColor(GxEPD_BLACK); + displays[dispNum].setCursor(0, 50); + + std::stringstream ss; + ss.str(text.c_str()); + std::string line; + + while (std::getline(ss, line, '\n')) { + if (line.rfind("*", 0) == 0) { + line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); + displays[dispNum].setFont(&FreeSansBold9pt7b); + } else { + displays[dispNum].setFont(&FreeSans9pt7b); + } + displays[dispNum].println(line.c_str()); + } +} + +void EPDManager::renderQr(uint dispNum, const String& text, bool partial) { #ifdef USE_QR // Dynamically allocate QR buffer uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX); @@ -639,19 +406,15 @@ void renderQr(const uint dispNum, const String &text, bool partial) if (ok) { const int size = qrcodegen_getSize(qrcode); const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); - const int paddingY = - floor(float(displays[dispNum].height() - (size * 4)) / 2); + const int paddingY = floor(float(displays[dispNum].height() - (size * 4)) / 2); + displays[dispNum].setRotation(2); - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); + displays[dispNum].height()); displays[dispNum].fillScreen(GxEPD_WHITE); - const int border = 0; - for (int y = -border; y < size * 4 + border; y++) - { - for (int x = -border; x < size * 4 + border; x++) - { + for (int y = 0; y < size * 4; y++) { + for (int x = 0; x < size * 4; x++) { displays[dispNum].drawPixel( padding + x, paddingY + y, qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) @@ -661,30 +424,114 @@ void renderQr(const uint dispNum, const String &text, bool partial) } } - // Free the buffer after we're done free(qrcode); #endif } -void waitUntilNoneBusy() -{ - for (int i = 0; i < NUM_SCREENS; i++) - { - uint count = 0; - while (EPD_BUSY[i].digitalRead()) - { - count++; - vTaskDelay(BUSY_RETRY_DELAY); +int16_t EPDManager::calculateDescent(const GFXfont* font) { + int16_t maxDescent = 0; + for (uint16_t i = font->first; i <= font->last; i++) { + GFXglyph* glyph = &font->glyph[i - font->first]; + int16_t descent = glyph->yOffset; + if (descent > maxDescent) { + maxDescent = descent; + } + } + return maxDescent; +} - if (count == BUSY_TIMEOUT_COUNT) - { - vTaskDelay(pdMS_TO_TICKS(100)); - } - else if (count > BUSY_TIMEOUT_COUNT + 5) - { - log_e("Display %d busy timeout", i); +void EPDManager::updateDisplayTask(void* pvParameters) noexcept { + auto& instance = EPDManager::getInstance(); + const int epdIndex = *(int*)pvParameters; + delete (int*)pvParameters; + + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + std::lock_guard lock(instance.displayMutexes[epdIndex]); + { + std::lock_guard lockMcp(mcpMutex); + instance.displays[epdIndex].init(0, false, 40); + } + + uint32_t count = 0; + while (instance.EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) { + vTaskDelay(pdMS_TO_TICKS(100)); + count++; + } + + bool updatePartial = true; + if (!instance.lastFullRefresh[epdIndex] || + (millis() - instance.lastFullRefresh[epdIndex]) > + (preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH) * 60 * 1000)) { + updatePartial = false; + } + + char tries = 0; + while (tries < 3) { + if (instance.displays[epdIndex].displayWithReturn(updatePartial)) { + instance.displays[epdIndex].powerOff(); + instance.currentContent[epdIndex] = instance.content[epdIndex]; + if (!updatePartial) { + instance.lastFullRefresh[epdIndex] = millis(); + } + + if (eventSourceTaskHandle != nullptr) { + xTaskNotifyGive(eventSourceTaskHandle); + } break; } + + vTaskDelay(pdMS_TO_TICKS(100)); + tries++; + } + } +} + +void EPDManager::prepareDisplayUpdateTask(void* pvParameters) { + auto& instance = EPDManager::getInstance(); + UpdateDisplayTaskItem receivedItem; + + for (;;) { + if (xQueueReceive(instance.updateQueue, &receivedItem, portMAX_DELAY)) { + uint epdIndex = receivedItem.dispNum; + std::lock_guard lock(instance.displayMutexes[epdIndex]); + + bool updatePartial = true; + + if (instance.content[epdIndex].length() > 1 && + strstr(instance.content[epdIndex].c_str(), "/") != nullptr) { + String top = instance.content[epdIndex].substring( + 0, instance.content[epdIndex].indexOf("/")); + String bottom = instance.content[epdIndex].substring( + instance.content[epdIndex].indexOf("/") + 1); + instance.splitText(epdIndex, top, bottom, updatePartial); + } else if (instance.content[epdIndex].startsWith(F("qr"))) { + instance.renderQr(epdIndex, instance.content[epdIndex], updatePartial); + } else if (instance.content[epdIndex].startsWith(F("mdi"))) { + if (!instance.renderIcon(epdIndex, instance.content[epdIndex], updatePartial)) { + continue; + } + } else if (instance.content[epdIndex].length() > 5) { + instance.renderText(epdIndex, instance.content[epdIndex], updatePartial); + } else { + if (instance.content[epdIndex].length() == 2) { + instance.showChars(epdIndex, instance.content[epdIndex], updatePartial, instance.fontBig); + } else if (instance.content[epdIndex].length() > 1 && + instance.content[epdIndex].indexOf(".") == -1) { + if (instance.content[epdIndex].equals("STS")) { + instance.showDigit(epdIndex, 'S', updatePartial, instance.fontSatsymbol); + } else { + instance.showChars(epdIndex, instance.content[epdIndex], updatePartial, + instance.fontMedium); + } + } else { + instance.showDigit(epdIndex, instance.content[epdIndex].c_str()[0], + updatePartial, instance.fontBig); + } + } + + xTaskNotifyGive(instance.tasks[epdIndex]); } } } \ No newline at end of file diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 4796776..80bed83 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "fonts/fonts.hpp" #include "lib/config.hpp" @@ -32,39 +34,102 @@ #include "qrcodegen.h" #endif -typedef struct { - char dispNum; -} UpdateDisplayTaskItem; +struct UpdateDisplayTaskItem { + char dispNum; +}; -void forceFullRefresh(); -void setupDisplays(); -void loadFonts(const String& fontName); +struct FontFamily { + GFXfont* big; + GFXfont* medium; + GFXfont* small; +}; -void splitText(const uint dispNum, const String &top, const String &bottom, - bool partial); +class EPDManager { +public: + static EPDManager& getInstance(); -void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font); -void showChars(const uint dispNum, const String &chars, bool partial, - const GFXfont *font); + // Delete copy constructor and assignment operator + EPDManager(const EPDManager&) = delete; + EPDManager& operator=(const EPDManager&) = delete; -extern "C" void updateDisplay(void *pvParameters) noexcept; -void updateDisplayAlt(int epdIndex); -void prepareDisplayUpdateTask(void *pvParameters); + void initialize(); + void forceFullRefresh(); + void loadFonts(const String& fontName); + void setContent(const std::array& newContent, bool forceUpdate = false); + void setContent(const std::array& newContent); + std::array getCurrentContent() const; -int getBgColor(); -int getFgColor(); -void setBgColor(int color); -void setFgColor(int color); + int getBackgroundColor() const { return bgColor; } + int getForegroundColor() const { return fgColor; } + void setBackgroundColor(int color) { bgColor = color; } + void setForegroundColor(int color) { fgColor = color; } + void waitUntilNoneBusy(); -bool renderIcon(const uint dispNum, const String &text, bool partial); -void renderText(const uint dispNum, const String &text, bool partial); -void renderQr(const uint dispNum, const String &text, bool partial); +private: + EPDManager(); // Private constructor for singleton + ~EPDManager(); // Private destructor -void setEpdContent(std::array newEpdContent, - bool forceUpdate); -void setEpdContent(std::array newEpdContent); + void setupDisplay(uint dispNum, const GFXfont* font); + void splitText(uint dispNum, const String& top, const String& bottom, bool partial); + void showDigit(uint dispNum, char chr, bool partial, const GFXfont* font); + void showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font); + bool renderIcon(uint dispNum, const String& text, bool partial); + void renderText(uint dispNum, const String& text, bool partial); + void renderQr(uint dispNum, const String& text, bool partial); + int16_t calculateDescent(const GFXfont* font); -void setEpdContent(std::array newEpdContent); + static void updateDisplayTask(void* pvParameters) noexcept; + static void prepareDisplayUpdateTask(void* pvParameters); -std::array getCurrentEpdContent(); -void waitUntilNoneBusy(); \ No newline at end of file + // Member variables + std::array currentContent; + std::array content; + std::array lastFullRefresh; + std::array tasks; + QueueHandle_t updateQueue; + + FontFamily antonioFonts; + FontFamily oswaldFonts; + const GFXfont* fontSmall; + const GFXfont* fontBig; + const GFXfont* fontMedium; + const GFXfont* fontSatsymbol; + + int bgColor; + int fgColor; + + std::mutex updateMutex; + std::array displayMutexes; + + // Pin configurations based on board version + #ifdef IS_BTCLOCK_REV_B + static Native_Pin EPD_DC; + static std::array EPD_CS; + static std::array EPD_BUSY; + static std::array EPD_RESET; + #elif defined(IS_BTCLOCK_V8) + static Native_Pin EPD_DC; + static std::array EPD_BUSY; + static std::array EPD_CS; + static std::array EPD_RESET; + #else + static Native_Pin EPD_DC; + static std::array EPD_CS; + static std::array EPD_BUSY; + static std::array EPD_RESET; + #endif + + // Display array + std::array, NUM_SCREENS> displays; + + static constexpr size_t UPDATE_QUEUE_SIZE = 14; + static constexpr uint32_t BUSY_TIMEOUT_COUNT = 200; + static constexpr TickType_t BUSY_RETRY_DELAY = pdMS_TO_TICKS(10); + static constexpr size_t EPD_TASK_STACK_SIZE = + #ifdef IS_BTCLOCK_V8 + 4096 + #else + 2048 + #endif + ; +}; \ No newline at end of file diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 3c996a2..6d9cce9 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -283,7 +283,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) } ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(textEpdContent); + EPDManager::getInstance().setContent(textEpdContent); vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) { @@ -294,4 +294,20 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } -} \ No newline at end of file +} + +// void onNostrEvent(const String &subId, const nostr::Event &event) { +// // This is the callback that will be called when a new event is received +// if (event.kind == 9735) { +// // Parse the zap amount from the event +// uint16_t amount = parseZapAmount(event); +// if (amount > 0) { +// std::array zapContent = parseZapNotify(amount, true); +// EPDManager::getInstance().setContent(zapContent); + +// if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) { +// getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); +// } +// } +// } +// } \ No newline at end of file diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index b42a505..59497c7 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -57,10 +57,10 @@ void onOTAProgress(unsigned int progress, unsigned int total) void onOTAStart() { - forceFullRefresh(); + EPDManager::getInstance().forceFullRefresh(); std::array epdContent = {"U", "P", "D", "A", "T", "E", "!"}; - setEpdContent(epdContent); + EPDManager::getInstance().setContent(epdContent); // Stop all timers esp_timer_stop(screenRotateTimer); esp_timer_stop(minuteTimer); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 0692c1d..6b3241e 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -203,7 +203,7 @@ void ScreenHandler::showSystemStatusScreen() { String((int)round(ESP.getFreeHeap() / 1024)) + "/" + (int)round(ESP.getHeapSize() / 1024); setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(sysStatusEpdContent); + EPDManager::getInstance().setContent(sysStatusEpdContent); } // Keep these as free functions @@ -222,7 +222,7 @@ void workerTask(void *pvParameters) { taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ? parseBitaxeHashRate(BitAxeFetch::getInstance().getHashRate()) : parseBitaxeBestDiff(BitAxeFetch::getInstance().getBestDiff()); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } @@ -235,7 +235,7 @@ void workerTask(void *pvParameters) { parseMiningPoolStatsDailyEarnings(MiningPoolStatsFetch::getInstance().getDailyEarnings(), MiningPoolStatsFetch::getInstance().getPool()->getDailyEarningsLabel(), *MiningPoolStatsFetch::getInstance().getPool()); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } @@ -256,13 +256,13 @@ void workerTask(void *pvParameters) { preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } case TASK_FEE_UPDATE: { if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; } @@ -275,7 +275,7 @@ void workerTask(void *pvParameters) { if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || currentScreenValue == SCREEN_BLOCK_HEIGHT) { - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; } @@ -302,7 +302,7 @@ void workerTask(void *pvParameters) { for (uint i = 1; i < NUM_SCREENS; i++) { taskEpdContent[i] = timeString[i]; } - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; @@ -330,8 +330,6 @@ void setupTasks() { xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle); - waitUntilNoneBusy(); - if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) ScreenHandler::setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index a776750..aa67768 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -179,4 +179,5 @@ void HttpHelper::end(HTTPClient* http) { http->end(); delete http; } -} \ No newline at end of file +} + diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 78fb9ea..479a52f 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include "defaults.hpp" @@ -118,4 +120,5 @@ private: static WiFiClientSecure secureClient; static bool certBundleSet; static WiFiClient insecureClient; -}; \ No newline at end of file +}; + diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 469e6e1..c0007dd 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -106,6 +106,11 @@ namespace V2Notify JsonDocument doc; DeserializationError error = deserializeMsgPack(doc, payload, length); + if (error) { + Serial.println(F("Error deserializing message")); + break; + } + V2Notify::handleV2Message(doc); break; } diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 4897811..382faa9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -309,7 +309,7 @@ void eventSourceUpdate() { doc["leds"] = getLedStatusObject()["data"]; // Get current EPD content directly as array - std::array epdContent = getCurrentEpdContent(); + std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays JsonArray data = doc["data"].to(); @@ -336,7 +336,7 @@ void onApiStatus(AsyncWebServerRequest *request) JsonDocument root = getStatusObject(); // Get current EPD content directly as array - std::array epdContent = getCurrentEpdContent(); + std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays JsonArray data = root["data"].to(); @@ -378,11 +378,9 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request) */ void onApiFullRefresh(AsyncWebServerRequest *request) { - forceFullRefresh(); - std::array newEpdContent = getCurrentEpdContent(); - - setEpdContent(newEpdContent, true); - + EPDManager::getInstance().forceFullRefresh(); + std::array newEpdContent = EPDManager::getInstance().getCurrentContent(); + EPDManager::getInstance().setContent(newEpdContent, true); request->send(HTTP_OK); } @@ -429,7 +427,7 @@ void onApiShowText(AsyncWebServerRequest *request) textEpdContent[i] = t[i]; } - setEpdContent(textEpdContent); + EPDManager::getInstance().setContent(textEpdContent); } ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); @@ -447,7 +445,7 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) i++; } - setEpdContent(epdContent); + EPDManager::getInstance().setContent(epdContent); ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); @@ -475,13 +473,13 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) if (inverted) { preferences.putUInt("fgColor", GxEPD_WHITE); preferences.putUInt("bgColor", GxEPD_BLACK); - setFgColor(GxEPD_WHITE); - setBgColor(GxEPD_BLACK); + EPDManager::getInstance().setForegroundColor(GxEPD_WHITE); + EPDManager::getInstance().setBackgroundColor(GxEPD_BLACK); } else { preferences.putUInt("fgColor", GxEPD_BLACK); preferences.putUInt("bgColor", GxEPD_WHITE); - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); + EPDManager::getInstance().setForegroundColor(GxEPD_BLACK); + EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE); } Serial.printf("Setting invertedColor to %d\r\n", inverted); settingsChanged = true; @@ -680,7 +678,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) JsonDocument root; root["numScreens"] = NUM_SCREENS; - root["invertedColor"] = preferences.getBool("invertedColor", getFgColor() == GxEPD_WHITE); + root["invertedColor"] = preferences.getBool("invertedColor", EPDManager::getInstance().getForegroundColor() == GxEPD_WHITE); root["timerSeconds"] = getTimerSeconds(); root["timerRunning"] = isTimerActive(); root["minSecPriceUpd"] = preferences.getUInt( @@ -818,7 +816,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) const AsyncWebParameter *fgColor = request->getParam("fgColor", true); uint32_t color = strtol(fgColor->value().c_str(), NULL, 16); preferences.putUInt("fgColor", color); - setFgColor(color); + EPDManager::getInstance().setForegroundColor(color); // Serial.print(F("Setting foreground color to ")); // Serial.println(fgColor->value().c_str()); settingsChanged = true; @@ -829,7 +827,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) uint32_t color = strtol(bgColor->value().c_str(), NULL, 16); preferences.putUInt("bgColor", color); - setBgColor(color); + EPDManager::getInstance().setBackgroundColor(color); // Serial.print(F("Setting background color to ")); // Serial.println(bgColor->value().c_str()); settingsChanged = true; @@ -1202,7 +1200,7 @@ void onApiLightsGet(AsyncWebServerRequest *request) auto& ledHandler = getLedHandler(); auto& pixels = ledHandler.getPixels(); - DynamicJsonDocument doc(1024); + JsonDocument doc; JsonArray lights = doc.createNestedArray("lights"); for (uint i = 0; i < pixels.numPixels(); i++) @@ -1225,7 +1223,7 @@ void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, auto& ledHandler = getLedHandler(); auto& pixels = ledHandler.getPixels(); - DynamicJsonDocument doc(1024); + JsonDocument doc; DeserializationError error = deserializeJson(doc, data); if (error) { From b435552c92685ad4525c7538df36138bffb199fb Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:12:39 +0100 Subject: [PATCH 162/188] fix: verify block update --- src/lib/block_notify.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 37b5b2f..0dab659 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -181,7 +181,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (currentBlockHeight == newBlockHeight) + if (currentBlockHeight <= newBlockHeight) { return; } From 178748b94d5019a6702497749d032e3ebac537ca Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:47:13 +0100 Subject: [PATCH 163/188] WebUI update --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 033fe09..732dd26 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 033fe098295ab6da6568d6298b4380e51bec0b98 +Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475 From 0999dd08ad30bc91efe9cacebcc79f4c2f0893fe Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:13:05 +0100 Subject: [PATCH 164/188] Remove deprecated ArduinoJson methods --- src/lib/block_notify.cpp | 6 +++--- src/lib/v2_notify.cpp | 6 +++--- src/lib/webserver.cpp | 29 ++++------------------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 0dab659..60b3034 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -158,7 +158,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) // return; // } - if (doc.containsKey("block")) + if (doc["block"].is()) { JsonObject block = doc["block"]; @@ -168,7 +168,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) processNewBlock(block["height"].as()); } - else if (doc.containsKey("mempool-blocks")) + else if (doc["mempool-blocks"].is()) { JsonArray blockInfo = doc["mempool-blocks"].as(); @@ -181,7 +181,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (currentBlockHeight <= newBlockHeight) + if (newBlockHeight <= currentBlockHeight) { return; } diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index c0007dd..560b39d 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -127,7 +127,7 @@ namespace V2Notify void handleV2Message(JsonDocument doc) { - if (doc.containsKey("blockheight")) + if (doc["blockheight"].is()) { uint newBlockHeight = doc["blockheight"].as(); @@ -138,13 +138,13 @@ namespace V2Notify processNewBlock(newBlockHeight); } - else if (doc.containsKey("blockfee")) + else if (doc["blockfee"].is()) { uint medianFee = doc["blockfee"].as(); processNewBlockFee(medianFee); } - else if (doc.containsKey("price")) + else if (doc["price"].is()) { // Iterate through the key-value pairs of the "price" object diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 382faa9..1d6a878 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -612,15 +612,15 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } // Handle DND settings - if (settings.containsKey("dnd")) { + if (settings["dnd"].is()) { JsonObject dndObj = settings["dnd"]; auto& ledHandler = getLedHandler(); - if (dndObj.containsKey("timeBasedEnabled")) { + if (dndObj["timeBasedEnabled"].is()) { ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); } - if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && - dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { + if (dndObj["startHour"].is() && dndObj["startMinute"].is() && + dndObj["endHour"].is() && dndObj["endMinute"].is()) { ledHandler.setDNDTimeRange( dndObj["startHour"].as(), dndObj["startMinute"].as(), @@ -1251,25 +1251,4 @@ void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, pixels.show(); request->send(200); -} - -void onApiSettings(AsyncWebServerRequest *request, JsonVariant &json) -{ - JsonObject settings = json.as(); - auto& ledHandler = getLedHandler(); - - if (settings.containsKey("dnd")) { - JsonObject dndObj = settings["dnd"]; - if (dndObj.containsKey("timeBasedEnabled")) { - ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); - } - if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && - dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { - ledHandler.setDNDTimeRange( - dndObj["startHour"].as(), - dndObj["startMinute"].as(), - dndObj["endHour"].as(), - dndObj["endMinute"].as()); - } - } } \ No newline at end of file From 7195b7d3432bf844798ffd58f7764b13afbe73dd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:13:25 +0100 Subject: [PATCH 165/188] Rewrite price notify to websocketsclient --- src/lib/price_notify.cpp | 165 +++++++++++++++------------------------ src/lib/price_notify.hpp | 14 ++-- 2 files changed, 71 insertions(+), 108 deletions(-) diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 2c6fe94..6ac2f3b 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -2,103 +2,64 @@ const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; -// WebsocketsClient client; -esp_websocket_client_handle_t clientPrice = NULL; -esp_websocket_client_config_t config; +WebSocketsClient webSocket; uint currentPrice = 90000; unsigned long int lastPriceUpdate; bool priceNotifyInit = false; std::map currencyMap; std::map lastUpdateMap; -WebSocketsClient priceNotifyWs; +TaskHandle_t priceNotifyTaskHandle; + +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); void setupPriceNotify() { - config = {.uri = wsServerPrice, - .user_agent = USER_AGENT}; - config.cert_pem = isrg_root_x1cert; + webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); + webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) { + onWebsocketPriceEvent(type, payload, length); + }); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); - config.task_stack = (6*1024); - - - clientPrice = esp_websocket_client_init(&config); - esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, - onWebsocketPriceEvent, clientPrice); - esp_websocket_client_start(clientPrice); - - // priceNotifyWs.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); - // priceNotifyWs.onEvent(onWebsocketPriceEvent); - // priceNotifyWs.setReconnectInterval(5000); - // priceNotifyWs.enableHeartbeat(15000, 3000, 2); + setupPriceNotifyTask(); } +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + Serial.println(F("Price WS Connection Closed")); + break; + case WStype_CONNECTED: + { + Serial.println("Connected to " + String(wsServerPrice)); + priceNotifyInit = true; + break; + } + case WStype_TEXT: + { + JsonDocument doc; + deserializeJson(doc, (char *)payload); -// void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { -// switch(type) { -// case WStype_DISCONNECTED: -// Serial.printf("[WSc] Disconnected!\n"); -// break; -// case WStype_CONNECTED: -// { -// Serial.printf("[WSc] Connected to url: %s\n", payload); - - -// break; -// } -// case WStype_TEXT: -// String message = String((char*)payload); -// onWebsocketPriceMessage(message); -// break; -// case WStype_BIN: -// break; -// case WStype_ERROR: -// case WStype_FRAGMENT_TEXT_START: -// case WStype_FRAGMENT_BIN_START: -// case WStype_FRAGMENT: -// case WStype_PING: -// case WStype_PONG: -// case WStype_FRAGMENT_FIN: -// break; -// } -// } - -void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) -{ - esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - - switch (event_id) - { - case WEBSOCKET_EVENT_CONNECTED: - Serial.println("Connected to " + String(config.uri) + " WebSocket"); - priceNotifyInit = true; - - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketPriceMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Price WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Price WS Connnection Closed")); - break; - } -} - -void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) -{ - JsonDocument doc; - - deserializeJson(doc, (char *)event_data->data_ptr); - - if (doc.containsKey("bitcoin")) - { - if (currentPrice != doc["bitcoin"].as()) - { - processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + if (doc["bitcoin"].is()) + { + if (currentPrice != doc["bitcoin"].as()) + { + processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + } + } + break; + } + case WStype_BIN: + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_PING: + case WStype_PONG: + case WStype_FRAGMENT_FIN: + break; } - } } void processNewPrice(uint newPrice, char currency) @@ -175,9 +136,7 @@ void setPrice(uint newPrice, char currency) bool isPriceNotifyConnected() { - if (clientPrice == NULL) - return false; - return esp_websocket_client_is_connected(clientPrice); + return webSocket.isConnected(); } bool getPriceNotifyInit() @@ -187,24 +146,30 @@ bool getPriceNotifyInit() void stopPriceNotify() { - if (clientPrice == NULL) - return; - esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); - esp_websocket_client_stop(clientPrice); - esp_websocket_client_destroy(clientPrice); - - clientPrice = NULL; + webSocket.disconnect(); + if (priceNotifyTaskHandle != NULL) { + vTaskDelete(priceNotifyTaskHandle); + priceNotifyTaskHandle = NULL; + } } void restartPriceNotify() { stopPriceNotify(); - if (clientPrice == NULL) + setupPriceNotify(); +} + +void taskPriceNotify(void *pvParameters) +{ + for (;;) { - setupPriceNotify(); - return; + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); } - // esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); - // esp_websocket_client_stop(clientPrice); - // esp_websocket_client_start(clientPrice); +} + +void setupPriceNotifyTask() +{ + xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &priceNotifyTaskHandle); } \ No newline at end of file diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 3591d40..6c8c6df 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -2,24 +2,22 @@ #include #include -#include -#include "block_notify.hpp" +#include #include #include "lib/screen_handler.hpp" +extern TaskHandle_t priceNotifyTaskHandle; + void setupPriceNotify(); +void setupPriceNotifyTask(); +void taskPriceNotify(void *pvParameters); -void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data); -//void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); - -void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); uint getPrice(char currency); void setPrice(uint newPrice, char currency); -//void processNewPrice(uint newPrice); void processNewPrice(uint newPrice, char currency); bool isPriceNotifyConnected(); From e19cad05bced9337c727e23a33d35fba65bc257e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:15:34 +0100 Subject: [PATCH 166/188] Update ESPAsyncWebserver --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b65defd..d46c153 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,7 +38,7 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.2.1 - mathieucarbou/ESPAsyncWebServer @ 3.3.23 + mathieucarbou/ESPAsyncWebServer @ 3.4.5 robtillaart/MCP23017@^0.8.0 adafruit/Adafruit NeoPixel@^1.12.3 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt From ebbec75e6bc9e51a973d13ad78f277f7be2c2865 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 00:01:34 +0100 Subject: [PATCH 167/188] Fix V2 message parsing --- src/lib/v2_notify.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 560b39d..6d9fc61 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -127,7 +127,7 @@ namespace V2Notify void handleV2Message(JsonDocument doc) { - if (doc["blockheight"].is()) + if (doc["blockheight"].is()) { uint newBlockHeight = doc["blockheight"].as(); @@ -136,16 +136,29 @@ namespace V2Notify return; } + if (debugLogEnabled()) { + Serial.print(F("processNewBlock ")); + Serial.println(newBlockHeight); + } processNewBlock(newBlockHeight); } - else if (doc["blockfee"].is()) + else if (doc["blockfee"].is()) { uint medianFee = doc["blockfee"].as(); + if (debugLogEnabled()) { + Serial.print(F("processNewBlockFee ")); + Serial.println(medianFee); + } + processNewBlockFee(medianFee); } else if (doc["price"].is()) { + if (debugLogEnabled()) { + Serial.print(F("processNewPrice ")); + Serial.println(doc["price"].as().size()); + } // Iterate through the key-value pairs of the "price" object for (JsonPair kv : doc["price"].as()) From e330984ba23504f1a214c93d48101d4dc3630bf9 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 00:43:31 +0100 Subject: [PATCH 168/188] Refactor BlockNotify to a class, use websocketsClient --- src/lib/block_notify.cpp | 363 ++++++++++++++++--------------------- src/lib/block_notify.hpp | 65 +++++-- src/lib/config.cpp | 4 +- src/lib/nostr_notify.cpp | 11 +- src/lib/ota.cpp | 4 +- src/lib/screen_handler.cpp | 14 +- src/lib/v2_notify.cpp | 6 +- src/lib/webserver.cpp | 9 +- src/main.cpp | 20 +- 9 files changed, 239 insertions(+), 257 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 60b3034..4c32a2a 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,186 +1,146 @@ #include "block_notify.hpp" -#include "led_handler.hpp" -char *wsServer; -esp_websocket_client_handle_t blockNotifyClient = NULL; -uint32_t currentBlockHeight = 873400; -uint16_t blockMedianFee = 1; -bool blockNotifyInit = false; -unsigned long int lastBlockUpdate; +// Initialize static members +WebSocketsClient BlockNotify::webSocket; +uint32_t BlockNotify::currentBlockHeight = 878000; +uint16_t BlockNotify::blockMedianFee = 1; +bool BlockNotify::notifyInit = false; +unsigned long int BlockNotify::lastBlockUpdate = 0; +TaskHandle_t BlockNotify::taskHandle = nullptr; -const char *mempoolWsCert = R"EOF( ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- -)EOF"; - -void setupBlockNotify() +void BlockNotify::taskNotify(void* pvParameters) { - IPAddress result; - - int dnsErr = -1; - String mempoolInstance = - preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - - while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) - { - dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); - - if (dnsErr != 1) + for (;;) { - Serial.print(mempoolInstance); - Serial.println(F("mempool DNS could not be resolved")); - WiFi.reconnect(); - vTaskDelay(pdMS_TO_TICKS(1000)); + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); } - } - - // Get current block height through regular API - int blockFetch = getBlockFetch(); - - if (blockFetch > currentBlockHeight) - currentBlockHeight = blockFetch; - - if (currentBlockHeight != -1) - { - lastBlockUpdate = esp_timer_get_time() / 1000000; - } - - if (workQueue != nullptr) - { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - } - - // std::strcpy(wsServer, String("wss://" + mempoolInstance + - // "/api/v1/ws").c_str()); - - const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "wss" : "ws"; - - String mempoolUri = protocol + "://" + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE) + "/api/v1/ws"; - - esp_websocket_client_config_t config = { - // .uri = "wss://mempool.space/api/v1/ws", - .task_stack = (6*1024), - .user_agent = USER_AGENT - }; - - if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { - config.cert_pem = mempoolWsCert; - } - - config.uri = mempoolUri.c_str(); - - Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); - - blockNotifyClient = esp_websocket_client_init(&config); - esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY, - onWebsocketBlockEvent, blockNotifyClient); - esp_websocket_client_start(blockNotifyClient); } -void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) +void BlockNotify::setupTask() { - esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}"; - switch (event_id) - { - case WEBSOCKET_EVENT_CONNECTED: - blockNotifyInit = true; + xTaskCreate(taskNotify, "blockNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &taskHandle); +} - Serial.println(F("Connected to Mempool.space WebSocket")); +void BlockNotify::onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: { + Serial.println(F("Mempool.space WS Connection Closed")); + break; + } + case WStype_CONNECTED: { + notifyInit = true; + Serial.print(F("Connected to ")); + Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); - Serial.println(sub); - if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(), - sub.length(), portMAX_DELAY) == -1) + JsonDocument doc; + doc["action"] = "want"; + JsonArray data = doc.createNestedArray("data"); + data.add("blocks"); + data.add("mempool-blocks"); + + String sub; + serializeJson(doc, sub); + Serial.println(sub); + webSocket.sendTXT(sub.c_str()); + break; + } + case WStype_TEXT: { + JsonDocument doc; + JsonDocument filter; + filter["block"]["height"] = true; + filter["mempool-blocks"][0]["medianFee"] = true; + + deserializeJson(doc, (char*)payload, DeserializationOption::Filter(filter)); + + if (debugLogEnabled()) { + Serial.println(doc.as()); + } + + if (doc["block"].is()) + { + JsonObject block = doc["block"]; + if (block["height"].as() != currentBlockHeight) { + BlockNotify::getInstance().processNewBlock(block["height"].as()); + } + } + else if (doc["mempool-blocks"].is()) + { + JsonArray blockInfo = doc["mempool-blocks"].as(); + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + BlockNotify::getInstance().processNewBlockFee(medianFee); + } + break; + } + case WStype_BIN: + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_PING: + case WStype_PONG: + case WStype_FRAGMENT_FIN: { + break; + } + } +} + +void BlockNotify::setup() +{ + IPAddress result; + int dnsErr = -1; + String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + + while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) { - Serial.println(F("Mempool.space WS Block Subscribe Error")); + dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); + + if (dnsErr != 1) + { + Serial.print(mempoolInstance); + Serial.println(F("mempool DNS could not be resolved")); + WiFi.reconnect(); + vTaskDelay(pdMS_TO_TICKS(1000)); + } } - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketBlockMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Mempool.space WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Mempool.space WS Connnection Closed")); - break; - } -} + // Get current block height through regular API + int blockFetch = fetchLatestBlock(); -void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) -{ - JsonDocument doc; + if (blockFetch > currentBlockHeight) + currentBlockHeight = blockFetch; - JsonDocument filter; - filter["block"]["height"] = true; - filter["mempool-blocks"][0]["medianFee"] = true; - - deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter)); - - // if (error) { - // Serial.print("deserializeJson() failed: "); - // Serial.println(error.c_str()); - // return; - // } - - if (doc["block"].is()) - { - JsonObject block = doc["block"]; - - if (block["height"].as() == currentBlockHeight) { - return; + if (currentBlockHeight != -1) + { + lastBlockUpdate = esp_timer_get_time() / 1000000; } - processNewBlock(block["height"].as()); - } - else if (doc["mempool-blocks"].is()) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); + if (workQueue != nullptr) + { + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + const int port = useSSL ? 443 : 80; - processNewBlockFee(medianFee); - } + if (useSSL) { + webSocket.beginSSL(mempoolInstance.c_str(), port, "/api/v1/ws"); +// webSocket.beginSSL("ws.btclock.dev", port, "/api/v1/ws"); - doc.clear(); + } else { + webSocket.begin(mempoolInstance.c_str(), port, "/api/v1/ws"); + } + + webSocket.onEvent(onWebsocketEvent); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); + + setupTask(); } -void processNewBlock(uint32_t newBlockHeight) { +void BlockNotify::processNewBlock(uint32_t newBlockHeight) { if (newBlockHeight <= currentBlockHeight) { return; @@ -220,76 +180,65 @@ void processNewBlock(uint32_t newBlockHeight) { } } -void processNewBlockFee(uint16_t newBlockFee) { - if (blockMedianFee == newBlockFee) +void BlockNotify::processNewBlockFee(uint16_t newBlockFee) { + if (blockMedianFee == newBlockFee) { - return; + return; } - // Serial.printf("New median fee: %d\r\n", medianFee); blockMedianFee = newBlockFee; if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } } -uint32_t getBlockHeight() { return currentBlockHeight; } - -void setBlockHeight(uint32_t newBlockHeight) -{ - currentBlockHeight = newBlockHeight; +uint32_t BlockNotify::getBlockHeight() const { + return currentBlockHeight; } -uint16_t getBlockMedianFee() { return blockMedianFee; } - -void setBlockMedianFee(uint16_t newBlockMedianFee) +void BlockNotify::setBlockHeight(uint32_t newBlockHeight) { - blockMedianFee = newBlockMedianFee; + currentBlockHeight = newBlockHeight; } -bool isBlockNotifyConnected() -{ - if (blockNotifyClient == NULL) - return false; - return esp_websocket_client_is_connected(blockNotifyClient); +uint16_t BlockNotify::getBlockMedianFee() const { + return blockMedianFee; } -bool getBlockNotifyInit() +void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee) { - return blockNotifyInit; + blockMedianFee = newBlockMedianFee; } -void stopBlockNotify() +bool BlockNotify::isConnected() const { - if (blockNotifyClient == NULL) - return; - - esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); - esp_websocket_client_stop(blockNotifyClient); - esp_websocket_client_destroy(blockNotifyClient); - - blockNotifyClient = NULL; + return webSocket.isConnected(); } -void restartBlockNotify() +bool BlockNotify::isInitialized() const { - stopBlockNotify(); - - if (blockNotifyClient == NULL) { - setupBlockNotify(); - return; - } - - // esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); - // esp_websocket_client_stop(blockNotifyClient); - // esp_websocket_client_start(blockNotifyClient); + return notifyInit; } +void BlockNotify::stop() +{ + webSocket.disconnect(); + if (taskHandle != NULL) { + vTaskDelete(taskHandle); + taskHandle = NULL; + } +} -int getBlockFetch() { +void BlockNotify::restart() +{ + stop(); + setup(); +} + +int BlockNotify::fetchLatestBlock() { try { String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http"; @@ -312,12 +261,12 @@ int getBlockFetch() { return 2203; // B-T-C } -uint getLastBlockUpdate() +uint BlockNotify::getLastBlockUpdate() const { - return lastBlockUpdate; + return lastBlockUpdate; } -void setLastBlockUpdate(uint lastUpdate) +void BlockNotify::setLastBlockUpdate(uint lastUpdate) { - lastBlockUpdate = lastUpdate; + lastBlockUpdate = lastUpdate; } \ No newline at end of file diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index 9c41bf0..aa99f2b 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -4,8 +4,7 @@ #include #include #include -#include - +#include #include #include @@ -14,28 +13,54 @@ #include "lib/timers.hpp" #include "lib/shared.hpp" -// using namespace websockets; +class BlockNotify { +public: + static BlockNotify& getInstance() { + static BlockNotify instance; + return instance; + } -void setupBlockNotify(); + // Delete copy constructor and assignment operator + BlockNotify(const BlockNotify&) = delete; + void operator=(const BlockNotify&) = delete; -void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data); -void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data); + // Block notification setup and control + void setup(); + void stop(); + void restart(); + bool isConnected() const; + bool isInitialized() const; -void setBlockHeight(uint32_t newBlockHeight); -uint32_t getBlockHeight(); + // Block height management + void setBlockHeight(uint32_t newBlockHeight); + uint32_t getBlockHeight() const; -void setBlockMedianFee(uint16_t blockMedianFee); -uint16_t getBlockMedianFee(); + // Block fee management + void setBlockMedianFee(uint16_t blockMedianFee); + uint16_t getBlockMedianFee() const; -bool isBlockNotifyConnected(); -void stopBlockNotify(); -void restartBlockNotify(); + // Block processing + void processNewBlock(uint32_t newBlockHeight); + void processNewBlockFee(uint16_t newBlockFee); -void processNewBlock(uint32_t newBlockHeight); -void processNewBlockFee(uint16_t newBlockFee); + // Block fetch and update tracking + int fetchLatestBlock(); + uint getLastBlockUpdate() const; + void setLastBlockUpdate(uint lastUpdate); -bool getBlockNotifyInit(); -uint32_t getLastBlockUpdate(); -int getBlockFetch(); -void setLastBlockUpdate(uint32_t lastUpdate); + // Task handling + static void taskNotify(void* pvParameters); + +private: + BlockNotify() = default; // Private constructor for singleton + + void setupTask(); + static void onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length); + + static WebSocketsClient webSocket; + static uint32_t currentBlockHeight; + static uint16_t blockMedianFee; + static bool notifyInit; + static unsigned long int lastBlockUpdate; + static TaskHandle_t taskHandle; +}; \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 6354a7a..def33ea 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -265,7 +265,7 @@ void setupPreferences() EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); - setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); + BlockNotify::getInstance().setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); if (!preferences.isKey("enableDebugLog")) { @@ -373,7 +373,7 @@ void setupWebsocketClients(void *pvParameters) } else if (dataSource == THIRD_PARTY_SOURCE) { - setupBlockNotify(); + BlockNotify::getInstance().setup(); setupPriceNotify(); } diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 6d9cce9..7df5ea1 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -79,8 +79,9 @@ void nostrTask(void *pvParameters) { DataSourceType dataSource = getDataSource(); if(dataSource == NOSTR_SOURCE) { - int blockFetch = getBlockFetch(); - processNewBlock(blockFetch); + auto& blockNotify = BlockNotify::getInstance(); + int blockFetch = blockNotify.fetchLatestBlock(); + blockNotify.processNewBlock(blockFetch); } while (1) @@ -174,11 +175,13 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even processNewPrice(obj["content"].as(), CURRENCY_USD); } else if (typeValue == "blockHeight") { - processNewBlock(obj["content"].as()); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlock(obj["content"].as()); } if (medianFee != 0) { - processNewBlockFee(medianFee); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlockFee(medianFee); } } } diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 59497c7..2d94d48 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -74,8 +74,8 @@ void onOTAStart() ButtonHandler::suspendTask(); // stopWebServer(); - stopBlockNotify(); - stopPriceNotify(); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.stop(); } void handleOTATask(void *parameter) diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 6b3241e..75e59aa 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -251,9 +251,8 @@ void workerTask(void *pvParameters) { } else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) { taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); } else { - taskEpdContent = - parseMarketCap(getBlockHeight(), price, currency, - preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseMarketCap(blockNotify.getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } EPDManager::getInstance().setContent(taskEpdContent); @@ -261,16 +260,19 @@ void workerTask(void *pvParameters) { } case TASK_FEE_UPDATE: { if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { - taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseBlockFees(static_cast(blockNotify.getBlockMedianFee())); EPDManager::getInstance().setContent(taskEpdContent); } break; } case TASK_BLOCK_UPDATE: { if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) { - taskEpdContent = parseBlockHeight(getBlockHeight()); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseBlockHeight(blockNotify.getBlockHeight()); } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseHalvingCountdown(blockNotify.getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); } if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 6d9fc61..a8174a1 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -131,7 +131,7 @@ namespace V2Notify { uint newBlockHeight = doc["blockheight"].as(); - if (newBlockHeight == getBlockHeight()) + if (newBlockHeight == BlockNotify::getInstance().getBlockHeight()) { return; } @@ -140,7 +140,7 @@ namespace V2Notify Serial.print(F("processNewBlock ")); Serial.println(newBlockHeight); } - processNewBlock(newBlockHeight); + BlockNotify::getInstance().processNewBlock(newBlockHeight); } else if (doc["blockfee"].is()) { @@ -151,7 +151,7 @@ namespace V2Notify Serial.println(medianFee); } - processNewBlockFee(medianFee); + BlockNotify::getInstance().processNewBlockFee(medianFee); } else if (doc["price"].is()) { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 1d6a878..430828d 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -247,7 +247,8 @@ JsonDocument getStatusObject() JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); - conStatus["blocks"] = isBlockNotifyConnected(); + auto& blockNotify = BlockNotify::getInstance(); + conStatus["blocks"] = blockNotify.isConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected(); conStatus["nostr"] = nostrConnected(); @@ -906,7 +907,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request) request->beginResponseStream(JSON_CONTENT); stopPriceNotify(); - stopBlockNotify(); + BlockNotify::getInstance().stop(); request->send(response); } @@ -917,9 +918,7 @@ void onApiRestartDataSources(AsyncWebServerRequest *request) request->beginResponseStream(JSON_CONTENT); restartPriceNotify(); - restartBlockNotify(); - // setupPriceNotify(); - // setupBlockNotify(); + BlockNotify::getInstance().restart(); request->send(response); } diff --git a/src/main.cpp b/src/main.cpp index c892a03..08d38ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "ESPAsyncWebServer.h" #include "lib/config.hpp" #include "lib/led_handler.hpp" +#include "lib/block_notify.hpp" uint wifiLostConnection; uint priceNotifyLostConnection = 0; @@ -49,7 +50,8 @@ void handleBlockNotifyDisconnection() { if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout Serial.println(F("Block notification connection lost for 5 minutes, restarting handler...")); - restartBlockNotify(); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.restart(); blockNotifyLostConnection = 0; } } @@ -92,13 +94,14 @@ void checkWiFiConnection() { void checkMissedBlocks() { Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); - int currentBlock = getBlockFetch(); + auto& blockNotify = BlockNotify::getInstance(); + int currentBlock = blockNotify.fetchLatestBlock(); if (currentBlock != -1) { - if (currentBlock != getBlockHeight()) { + if (currentBlock != blockNotify.getBlockHeight()) { Serial.println(F("Detected stuck block height... restarting block handler.")); - restartBlockNotify(); + blockNotify.restart(); } - setLastBlockUpdate(getUptime()); + blockNotify.setLastBlockUpdate(getUptime()); } } @@ -111,9 +114,10 @@ void monitorDataConnections() { } // Block notification monitoring - if (getBlockNotifyInit() && !isBlockNotifyConnected()) { + auto& blockNotify = BlockNotify::getInstance(); + if (blockNotify.isInitialized() && !blockNotify.isConnected()) { handleBlockNotifyDisconnection(); - } else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) { + } else if (blockNotifyLostConnection > 0 && blockNotify.isConnected()) { blockNotifyLostConnection = 0; } @@ -125,7 +129,7 @@ void monitorDataConnections() { } // Check for missed blocks - if ((getLastBlockUpdate() - getUptime()) > 45 * 60) { + if ((blockNotify.getLastBlockUpdate() - getUptime()) > 45 * 60) { checkMissedBlocks(); } } From 1d61453563f62430d441630e642737bb8aad5406 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:13:09 +0100 Subject: [PATCH 169/188] Revert to esp websocket client because websocketsClient does not work --- src/lib/block_notify.cpp | 195 ++++++++++++++++++++++----------------- src/lib/block_notify.hpp | 11 +-- src/lib/v2_notify.cpp | 2 +- 3 files changed, 114 insertions(+), 94 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 4c32a2a..8502e3d 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,103 +1,116 @@ #include "block_notify.hpp" // Initialize static members -WebSocketsClient BlockNotify::webSocket; +esp_websocket_client_handle_t BlockNotify::wsClient = nullptr; uint32_t BlockNotify::currentBlockHeight = 878000; uint16_t BlockNotify::blockMedianFee = 1; bool BlockNotify::notifyInit = false; unsigned long int BlockNotify::lastBlockUpdate = 0; TaskHandle_t BlockNotify::taskHandle = nullptr; -void BlockNotify::taskNotify(void* pvParameters) -{ - for (;;) - { - webSocket.loop(); - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} +const char* BlockNotify::mempoolWsCert = R"EOF( +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- +)EOF"; -void BlockNotify::setupTask() -{ - xTaskCreate(taskNotify, "blockNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, - &taskHandle); -} +void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { + esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; + BlockNotify& instance = BlockNotify::getInstance(); -void BlockNotify::onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length) { - switch(type) { - case WStype_DISCONNECTED: { - Serial.println(F("Mempool.space WS Connection Closed")); - break; - } - case WStype_CONNECTED: { + switch (event_id) { + case WEBSOCKET_EVENT_CONNECTED: + { notifyInit = true; Serial.print(F("Connected to ")); Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); JsonDocument doc; doc["action"] = "want"; - JsonArray data = doc.createNestedArray("data"); - data.add("blocks"); - data.add("mempool-blocks"); + JsonArray dataArray = doc.createNestedArray("data"); + dataArray.add("blocks"); + dataArray.add("mempool-blocks"); String sub; serializeJson(doc, sub); - Serial.println(sub); - webSocket.sendTXT(sub.c_str()); + esp_websocket_client_send_text(wsClient, sub.c_str(), sub.length(), portMAX_DELAY); break; } - case WStype_TEXT: { - JsonDocument doc; - JsonDocument filter; - filter["block"]["height"] = true; - filter["mempool-blocks"][0]["medianFee"] = true; - - deserializeJson(doc, (char*)payload, DeserializationOption::Filter(filter)); - - if (debugLogEnabled()) { - Serial.println(doc.as()); - } - - if (doc["block"].is()) - { - JsonObject block = doc["block"]; - if (block["height"].as() != currentBlockHeight) { - BlockNotify::getInstance().processNewBlock(block["height"].as()); - } - } - else if (doc["mempool-blocks"].is()) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); - BlockNotify::getInstance().processNewBlockFee(medianFee); - } + case WEBSOCKET_EVENT_DATA: + instance.onWebsocketMessage(data); break; - } - case WStype_BIN: - case WStype_ERROR: - case WStype_FRAGMENT_TEXT_START: - case WStype_FRAGMENT_BIN_START: - case WStype_FRAGMENT: - case WStype_PING: - case WStype_PONG: - case WStype_FRAGMENT_FIN: { + + case WEBSOCKET_EVENT_DISCONNECTED: + Serial.println(F("Mempool.space WS Connection Closed")); + break; + + case WEBSOCKET_EVENT_ERROR: + Serial.println(F("Mempool.space WS Connection Error")); break; - } } } -void BlockNotify::setup() -{ +void BlockNotify::onWebsocketMessage(esp_websocket_event_data_t *data) { + JsonDocument doc; + JsonDocument filter; + filter["block"]["height"] = true; + filter["mempool-blocks"][0]["medianFee"] = true; + + deserializeJson(doc, (char*)data->data_ptr, DeserializationOption::Filter(filter)); + + if (doc["block"].is()) { + JsonObject block = doc["block"]; + if (block["height"].as() != currentBlockHeight) { + processNewBlock(block["height"].as()); + } + } + else if (doc["mempool-blocks"].is()) { + JsonArray blockInfo = doc["mempool-blocks"].as(); + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + processNewBlockFee(medianFee); + } +} + +void BlockNotify::setup() { IPAddress result; int dnsErr = -1; String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) - { + while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) { dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); - if (dnsErr != 1) - { + if (dnsErr != 1) { Serial.print(mempoolInstance); Serial.println(F("mempool DNS could not be resolved")); WiFi.reconnect(); @@ -111,35 +124,39 @@ void BlockNotify::setup() if (blockFetch > currentBlockHeight) currentBlockHeight = blockFetch; - if (currentBlockHeight != -1) - { + if (currentBlockHeight != -1) { lastBlockUpdate = esp_timer_get_time() / 1000000; } - if (workQueue != nullptr) - { + if (workQueue != nullptr) { WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); - const int port = useSSL ? 443 : 80; + const String protocol = useSSL ? "wss" : "ws"; + String wsUri = protocol + "://" + mempoolInstance + "/api/v1/ws"; + + esp_websocket_client_config_t config = { + .task_stack = (6*1024), + .user_agent = USER_AGENT + }; if (useSSL) { - webSocket.beginSSL(mempoolInstance.c_str(), port, "/api/v1/ws"); -// webSocket.beginSSL("ws.btclock.dev", port, "/api/v1/ws"); - - } else { - webSocket.begin(mempoolInstance.c_str(), port, "/api/v1/ws"); + config.cert_pem = mempoolWsCert; } - webSocket.onEvent(onWebsocketEvent); - webSocket.setReconnectInterval(5000); - webSocket.enableHeartbeat(15000, 3000, 2); + config.uri = wsUri.c_str(); - setupTask(); + Serial.printf("Connecting to %s\r\n", mempoolInstance.c_str()); + + wsClient = esp_websocket_client_init(&config); + esp_websocket_register_events(wsClient, WEBSOCKET_EVENT_ANY, onWebsocketEvent, wsClient); + esp_websocket_client_start(wsClient); } + + void BlockNotify::processNewBlock(uint32_t newBlockHeight) { if (newBlockHeight <= currentBlockHeight) { @@ -215,7 +232,9 @@ void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee) bool BlockNotify::isConnected() const { - return webSocket.isConnected(); + if (wsClient == NULL) + return false; + return esp_websocket_client_is_connected(wsClient); } bool BlockNotify::isInitialized() const @@ -225,11 +244,13 @@ bool BlockNotify::isInitialized() const void BlockNotify::stop() { - webSocket.disconnect(); - if (taskHandle != NULL) { - vTaskDelete(taskHandle); - taskHandle = NULL; - } + if (wsClient == NULL) + return; + + esp_websocket_client_close(wsClient, portMAX_DELAY); + esp_websocket_client_stop(wsClient); + esp_websocket_client_destroy(wsClient); + wsClient = NULL; } void BlockNotify::restart() diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index aa99f2b..15aabee 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -48,16 +48,15 @@ public: uint getLastBlockUpdate() const; void setLastBlockUpdate(uint lastUpdate); - // Task handling - static void taskNotify(void* pvParameters); - private: BlockNotify() = default; // Private constructor for singleton void setupTask(); - static void onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length); + static void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); + void onWebsocketMessage(esp_websocket_event_data_t *data); - static WebSocketsClient webSocket; + static const char* mempoolWsCert; + static esp_websocket_client_handle_t wsClient; static uint32_t currentBlockHeight; static uint16_t blockMedianFee; static bool notifyInit; diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index a8174a1..4d23f3a 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -176,7 +176,7 @@ namespace V2Notify for (;;) { webSocket.loop(); - vTaskDelay(10 / portTICK_PERIOD_MS); + vTaskDelay(pdMS_TO_TICKS(10)); } } From bf64b2f64f26ca189ade22a40c0928d0b36b3b9e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:27:13 +0100 Subject: [PATCH 170/188] Merge root certificates --- src/lib/block_notify.cpp | 32 +++++++++++++++++++ src/lib/shared.cpp | 66 ++++++++++++++++++++-------------------- src/lib/shared.hpp | 2 +- 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 8502e3d..20e6267 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -43,6 +43,38 @@ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG jjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- )EOF"; void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index aa67768..b8efc0c 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -40,39 +40,39 @@ // "MrY=\n" // "-----END CERTIFICATE-----\n"; -const char* isrg_root_x1cert = R"EOF( ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)EOF"; +// const char* isrg_root_x1cert = R"EOF( +// -----BEGIN CERTIFICATE----- +// MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +// TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +// cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +// WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +// ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +// MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +// h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +// 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +// A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +// T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +// B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +// B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +// KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +// OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +// jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +// qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +// rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +// HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +// hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +// ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +// 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +// NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +// ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +// TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +// jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +// oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +// 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +// mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +// emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +// -----END CERTIFICATE----- +// )EOF"; #ifdef TEST_SCREENS diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 479a52f..54d48aa 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -68,7 +68,7 @@ const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; // extern const char *github_root_ca; -extern const char *isrg_root_x1cert; +// extern const char *isrg_root_x1cert; extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start"); // extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start"); From 963f3b10b7a42ebfb50aba1e71b20d6fe8557dcc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:30:46 +0100 Subject: [PATCH 171/188] Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 732dd26..91e60d2 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475 +Subproject commit 91e60d2f4cd7437075a6e10ea0f395b2650ed531 From 1083a3222bf71ab96d27196efa6666e4093bbb3b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 8 Jan 2025 02:14:33 +0100 Subject: [PATCH 172/188] Add local public pool support --- data | 2 +- dependencies.lock | 2 +- src/lib/defaults.hpp | 1 + src/lib/mining_pool/pool_factory.cpp | 2 ++ src/lib/mining_pool/pool_factory.hpp | 3 +++ src/lib/mining_pool/public_pool/local_public_pool.cpp | 11 +++++++++++ src/lib/mining_pool/public_pool/local_public_pool.hpp | 11 +++++++++++ src/lib/v2_notify.cpp | 4 ---- src/lib/webserver.cpp | 5 ++++- 9 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/lib/mining_pool/public_pool/local_public_pool.cpp create mode 100644 src/lib/mining_pool/public_pool/local_public_pool.hpp diff --git a/data b/data index 91e60d2..e0d539a 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 91e60d2f4cd7437075a6e10ea0f395b2650ed531 +Subproject commit e0d539a8a3d29669ccc116311870f317b0d9dfcc diff --git a/dependencies.lock b/dependencies.lock index c338e6c..f52d5f2 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff +manifest_hash: 04e75badb795f8a851d8b088baff06d145ecf7a66457d960c2f4ede17b45ef05 target: esp32s3 version: 1.0.0 diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 29d7eee..a662344 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -60,6 +60,7 @@ #define DEFAULT_MINING_POOL_STATS_ENABLED false #define DEFAULT_MINING_POOL_NAME "ocean" #define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher +#define DEFAULT_LOCAL_POOL_ENDPOINT "umbrel.local:2019" #define DEFAULT_ZAP_NOTIFY_ENABLED false #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" diff --git a/src/lib/mining_pool/pool_factory.cpp b/src/lib/mining_pool/pool_factory.cpp index c353996..45bc6ce 100644 --- a/src/lib/mining_pool/pool_factory.cpp +++ b/src/lib/mining_pool/pool_factory.cpp @@ -5,6 +5,7 @@ const char* PoolFactory::MINING_POOL_NAME_NODERUNNERS = "noderunners"; const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins"; const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio"; const char* PoolFactory::MINING_POOL_NAME_PUBLIC_POOL = "public_pool"; +const char* PoolFactory::MINING_POOL_NAME_LOCAL_PUBLIC_POOL = "local_public_pool"; const char* PoolFactory::MINING_POOL_NAME_GOBRRR_POOL = "gobrrr_pool"; const char* PoolFactory::MINING_POOL_NAME_CKPOOL = "ckpool"; const char* PoolFactory::MINING_POOL_NAME_EU_CKPOOL = "eu_ckpool"; @@ -17,6 +18,7 @@ std::unique_ptr PoolFactory::createPool(const std::string& {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique(); }}, {MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique(); }}, {MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_LOCAL_PUBLIC_POOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_CKPOOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique(); }} diff --git a/src/lib/mining_pool/pool_factory.hpp b/src/lib/mining_pool/pool_factory.hpp index 951dbe5..9885d74 100644 --- a/src/lib/mining_pool/pool_factory.hpp +++ b/src/lib/mining_pool/pool_factory.hpp @@ -10,6 +10,7 @@ #include "ocean/ocean_pool.hpp" #include "satoshi_radio/satoshi_radio_pool.hpp" #include "public_pool/public_pool.hpp" +#include "public_pool/local_public_pool.hpp" #include "gobrrr_pool/gobrrr_pool.hpp" #include "ckpool/ckpool.hpp" #include "ckpool/eu_ckpool.hpp" @@ -28,6 +29,7 @@ class PoolFactory { MINING_POOL_NAME_SATOSHI_RADIO, MINING_POOL_NAME_BRAIINS, MINING_POOL_NAME_PUBLIC_POOL, + MINING_POOL_NAME_LOCAL_PUBLIC_POOL, MINING_POOL_NAME_GOBRRR_POOL, MINING_POOL_NAME_CKPOOL, MINING_POOL_NAME_EU_CKPOOL @@ -55,6 +57,7 @@ class PoolFactory { static const char* MINING_POOL_NAME_BRAIINS; static const char* MINING_POOL_NAME_SATOSHI_RADIO; static const char* MINING_POOL_NAME_PUBLIC_POOL; + static const char* MINING_POOL_NAME_LOCAL_PUBLIC_POOL; static const char* MINING_POOL_NAME_GOBRRR_POOL; static const char* MINING_POOL_NAME_CKPOOL; static const char* MINING_POOL_NAME_EU_CKPOOL; diff --git a/src/lib/mining_pool/public_pool/local_public_pool.cpp b/src/lib/mining_pool/public_pool/local_public_pool.cpp new file mode 100644 index 0000000..048197c --- /dev/null +++ b/src/lib/mining_pool/public_pool/local_public_pool.cpp @@ -0,0 +1,11 @@ +#include "local_public_pool.hpp" +#include "lib/shared.hpp" +#include "lib/defaults.hpp" + +std::string LocalPublicPool::getEndpoint() const { + return preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT).c_str(); +} + +std::string LocalPublicPool::getApiUrl() const { + return "http://" + getEndpoint() + "/api/client/" + poolUser; +} \ No newline at end of file diff --git a/src/lib/mining_pool/public_pool/local_public_pool.hpp b/src/lib/mining_pool/public_pool/local_public_pool.hpp new file mode 100644 index 0000000..a9e37ad --- /dev/null +++ b/src/lib/mining_pool/public_pool/local_public_pool.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "public_pool.hpp" + +class LocalPublicPool : public PublicPool { +public: + std::string getApiUrl() const override; + std::string getDisplayLabel() const override { return "LOCAL/POOL"; } +private: + std::string getEndpoint() const; +}; \ No newline at end of file diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 4d23f3a..b915518 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -155,10 +155,6 @@ namespace V2Notify } else if (doc["price"].is()) { - if (debugLogEnabled()) { - Serial.print(F("processNewPrice ")); - Serial.println(doc["price"].as().size()); - } // Iterate through the key-value pairs of the "price" object for (JsonPair kv : doc["price"].as()) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 430828d..fef1634 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -5,7 +5,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; @@ -696,6 +696,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + // Local pool settings + root["localPoolEndpoint"] = preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT); + // Nostr settings (used for NOSTR_SOURCE or when zapNotify is enabled) root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); From b01003f07549cbd26e89b871bba4004a0356813b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 15 Jan 2025 22:09:05 +0100 Subject: [PATCH 173/188] fix: Never write to LED0 --- src/lib/led_handler.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 3cdb953..453b067 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -535,7 +535,7 @@ void LedHandler::frontlightSetBrightness(uint brightness) { } for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, brightness); + flArray.setPWM(ledPin + 1, 0, brightness); } } @@ -543,7 +543,7 @@ std::vector LedHandler::frontlightGetStatus() { std::vector statuses; for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) { uint16_t a = 0, b = 0; - flArray.getPWM(ledPin, &a, &b); + flArray.getPWM(ledPin + 1, &a, &b); statuses.push_back(round(b - a / 4096)); } return statuses; @@ -576,7 +576,7 @@ void LedHandler::frontlightFadeInAll(int flDelayTime, bool staggered) { } else { for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); + flArray.setPWM(ledPin + 1, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } @@ -611,7 +611,7 @@ void LedHandler::frontlightFadeOutAll(int flDelayTime, bool staggered) { } else { for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); + flArray.setPWM(ledPin + 1, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } @@ -628,7 +628,7 @@ void LedHandler::frontlightFadeIn(uint num, int flDelayTime) { } for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { - flArray.setPWM(num, 0, dutyCycle); + flArray.setPWM(num + 1, 0, dutyCycle); vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } @@ -639,7 +639,7 @@ void LedHandler::frontlightFadeOut(uint num, int flDelayTime) { } for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { - flArray.setPWM(num, 0, dutyCycle); + flArray.setPWM(num + 1, 0, dutyCycle); vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } From 9ea021086490c53845508e2a6eac27b6f016b63e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 16 Jan 2025 00:30:40 +0100 Subject: [PATCH 174/188] fix: set better defaults for frontlight enabled devices --- data | 2 +- src/lib/defaults.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data b/data index e0d539a..68207a7 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e0d539a8a3d29669ccc116311870f317b0d9dfcc +Subproject commit 68207a7d95b6cb16df5c991b84ff40c25c23755b diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index a662344..55da6e3 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -46,8 +46,8 @@ #define DEFAULT_LUX_LIGHT_TOGGLE 128 #define DEFAULT_FL_OFF_WHEN_DARK true -#define DEFAULT_FL_ALWAYS_ON false -#define DEFAULT_FL_FLASH_ON_UPDATE false +#define DEFAULT_FL_ALWAYS_ON true +#define DEFAULT_FL_FLASH_ON_UPDATE true #define DEFAULT_LED_STATUS false #define DEFAULT_TIMER_ACTIVE true From 678a4ba0998121d78fa582c2353d44a92866bafc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 19 Jan 2025 22:32:04 +0100 Subject: [PATCH 175/188] fix: Set WiFi country to NL for scanning --- src/lib/config.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index def33ea..16cb80a 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -132,9 +132,25 @@ void setup() void setupWifi() { WiFi.onEvent(WiFiEvent); + + // wifi_country_t country = { + // .cc = "NL", + // .schan = 1, + // .nchan = 13, + // .policy = WIFI_COUNTRY_POLICY_MANUAL + // }; + + // esp_err_t err = esp_wifi_set_country(&country); + // if (err != ESP_OK) { + // Serial.printf("Failed to set country: %d\n", err); + // } + WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); WiFi.begin(); + + + if (preferences.getInt("txPower", DEFAULT_TX_POWER)) { if (WiFi.setTxPower( @@ -172,6 +188,7 @@ void setupWifi() wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT)); wm.setWiFiAutoReconnect(false); wm.setDebugOutput(false); + wm.setCountry("NL"); wm.setConfigPortalBlocking(true); wm.setAPCallback([&](WiFiManager *wifiManager) From 3265eec30886a6ff3b6b4f54eb96b36773133cbc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 20 Jan 2025 12:05:48 +0100 Subject: [PATCH 176/188] chore: update dependencies and make eventsource use static jsondocument --- platformio.ini | 13 ++++++++----- src/lib/webserver.cpp | 15 ++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index d46c153..1eb178a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,17 +30,17 @@ build_flags = -DLAST_BUILD_TIME=$UNIX_TIME -DARDUINO_USB_CDC_ON_BOOT -DCORE_DEBUG_LEVEL=0 - -D DEFAULT_BOOT_TEXT=\"BTCLOCK\" + -D CONFIG_ASYNC_TCP_STACK_SIZE=16384 -fexceptions build_unflags = -Werror=all -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.2.1 - mathieucarbou/ESPAsyncWebServer @ 3.4.5 + bblanchon/ArduinoJson@^7.3.0 + mathieucarbou/ESPAsyncWebServer @ 3.6.0 robtillaart/MCP23017@^0.8.0 - adafruit/Adafruit NeoPixel@^1.12.3 + adafruit/Adafruit NeoPixel@^1.12.4 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 @@ -79,9 +79,10 @@ build_flags = -D I2C_SDA_PIN=35 -D I2C_SCK_PIN=36 -D HAS_FRONTLIGHT - -D PCA_OE_PIN=45 + -D PCA_OE_PIN=48 -D PCA_I2C_ADDR=0x42 -D IS_HW_REV_B + lib_deps = ${btclock_base.lib_deps} robtillaart/PCA9685@^0.7.1 @@ -100,6 +101,7 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 -D HW_REV=\"REV_A_EPD_2_13\" + -D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384 platform_packages = platformio/tool-mklittlefs@^1.203.210628 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 @@ -112,6 +114,7 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 -D HW_REV=\"REV_B_EPD_2_13\" + -D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384 platform_packages = platformio/tool-mklittlefs@^1.203.210628 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index fef1634..cfb5d62 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -30,7 +30,8 @@ TaskHandle_t eventSourceTaskHandle; void setupWebserver() { events.onConnect([](AsyncEventSourceClient *client) - { client->send("welcome", NULL, millis(), 1000); }); + { client->send("welcome", NULL, millis(), 1000); + }); server.addHandler(&events); AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); @@ -306,14 +307,18 @@ JsonDocument getLedStatusObject() void eventSourceUpdate() { if (!events.count()) return; - JsonDocument doc = getStatusObject(); - doc["leds"] = getLedStatusObject()["data"]; + static JsonDocument doc; + doc.clear(); + + JsonDocument root = getStatusObject(); + + root["leds"] = getLedStatusObject()["data"]; // Get current EPD content directly as array std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays - JsonArray data = doc["data"].to(); + JsonArray data = root["data"].to(); // Copy array elements directly for(const auto& content : epdContent) { @@ -321,7 +326,7 @@ void eventSourceUpdate() { } String buffer; - serializeJson(doc, buffer); + serializeJson(root, buffer); events.send(buffer.c_str(), "status"); } From 0b1a362b53d44aa9c31b1bfd13a1781f92a585c4 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 14:12:16 +0100 Subject: [PATCH 177/188] chore: dependency updates --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1eb178a..f9dc11e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd, [env] [btclock_base] -platform = espressif32 @ ^6.9.0 +platform = espressif32 @ ^6.10.0 framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize @@ -38,8 +38,8 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.3.0 - mathieucarbou/ESPAsyncWebServer @ 3.6.0 - robtillaart/MCP23017@^0.8.0 + esp32async/ESPAsyncWebServer @ 3.7.0 + robtillaart/MCP23017@^0.9.0 adafruit/Adafruit NeoPixel@^1.12.4 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin From e4ac3c5c948372f6cc58048d24656b9ec0f0b187 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 15:15:53 +0100 Subject: [PATCH 178/188] feat: switch to replaceable events for nostr source --- data | 2 +- dependencies.lock | 2 +- src/lib/nostr_notify.cpp | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/data b/data index 68207a7..0116cd6 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 68207a7d95b6cb16df5c991b84ff40c25c23755b +Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500 diff --git a/dependencies.lock b/dependencies.lock index f52d5f2..c338e6c 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: 04e75badb795f8a851d8b088baff06d145ecf7a66457d960c2f4ede17b45ef05 +manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff target: esp32s3 version: 1.0.0 diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 7df5ea1..dda24e1 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -41,7 +41,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) {relay}, {// First filter { - {"kinds", {"1"}}, + {"kinds", {"12203"}}, {"since", {String(getMinutesAgo(60))}}, {"authors", {pubKey}}, }}, @@ -146,6 +146,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even // Use direct value access instead of multiple comparisons String typeValue; uint medianFee = 0; + uint blockHeight = 0; for (JsonArray tag : tags) { if (tag.size() != 2) continue; @@ -166,6 +167,11 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even medianFee = tag[1].as(); } break; + case 'b': // blockHeight + if (strcmp(key, "block") == 0) { + blockHeight = tag[1].as(); + } + break; } } @@ -173,6 +179,10 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even if (!typeValue.isEmpty()) { if (typeValue == "priceUsd") { processNewPrice(obj["content"].as(), CURRENCY_USD); + if (blockHeight != 0) { + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlock(blockHeight); + } } else if (typeValue == "blockHeight") { auto& blockNotify = BlockNotify::getInstance(); From dc8e348aa349e9fcf3c0f7b12733b4f2306a995f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 15:38:54 +0100 Subject: [PATCH 179/188] fix: Set explicit littlefs version tag --- dependencies.lock | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index c338e6c..2274b85 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff +manifest_hash: 1d4ef353a86901733b106a1897b186dbf9fc091a4981f0560ea2f6899b7a3d44 target: esp32s3 version: 1.0.0 diff --git a/platformio.ini b/platformio.ini index f9dc11e..37e99da 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,7 +36,7 @@ build_unflags = -Werror=all -fno-exceptions lib_deps = - https://github.com/joltwallet/esp_littlefs.git + https://github.com/joltwallet/esp_littlefs.git#v1.16.4 bblanchon/ArduinoJson@^7.3.0 esp32async/ESPAsyncWebServer @ 3.7.0 robtillaart/MCP23017@^0.9.0 From 7266a51a92c776350bd7cb3b5ff409695478313f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:35:40 +0200 Subject: [PATCH 180/188] chore: update dependencies --- platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 37e99da..140946d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,10 +37,10 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git#v1.16.4 - bblanchon/ArduinoJson@^7.3.0 - esp32async/ESPAsyncWebServer @ 3.7.0 - robtillaart/MCP23017@^0.9.0 - adafruit/Adafruit NeoPixel@^1.12.4 + bblanchon/ArduinoJson@^7.3.1 + esp32async/ESPAsyncWebServer @ 3.7.4 + robtillaart/MCP23017@^0.9.1 + adafruit/Adafruit NeoPixel@^1.12.5 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 From c62563c3c2a9886e09a617aa136c94901acebb50 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:40:12 +0200 Subject: [PATCH 181/188] feat: Replace timezone offset with timezone selector --- include/timezone_data.hpp | 721 ++++++++++++++++++++++++++++++++++++++ src/lib/config.cpp | 13 +- src/lib/config.hpp | 3 +- src/lib/defaults.hpp | 1 + src/lib/webserver.cpp | 7 +- 5 files changed, 739 insertions(+), 6 deletions(-) create mode 100644 include/timezone_data.hpp diff --git a/include/timezone_data.hpp b/include/timezone_data.hpp new file mode 100644 index 0000000..1ebb5db --- /dev/null +++ b/include/timezone_data.hpp @@ -0,0 +1,721 @@ +#pragma once + +#include +#include +#include + +namespace timezone_data { + +// Enum for unique timezone strings +enum class TimezoneValue : uint8_t { + plus000plus02_2M350_1M1050_3, + plus01_1, + plus02_2, + plus0330_330, + plus03_3, + plus0430_430, + plus04_4, + plus0530_530, + plus0545_545, + plus05_5, + plus0630_630, + plus06_6, + plus07_7, + plus0845_845, + plus08_8, + plus09_9, + plus1030_1030plus11_11M1010M410, + plus10_10, + plus11_11, + plus11_11plus12M1010M410_3, + plus1245_1245plus1345M950_245M410_345, + plus12_12, + plus13_13, + plus14_14, + _011, + _011plus00M350_0M1050_1, + _022, + _022_01M350__1M1050_0, + _033, + _033_02M320M1110, + _044, + _044_03M1010_0M340_0, + _044_03M916_24M416_24, + _055, + _066, + _066_05M916_22M416_22, + _077, + _088, + _0930930, + _099, + _1010, + _1111, + _1212, + ACST_930, + ACST_930ACDTM1010M410_3, + AEST_10, + AEST_10AEDTM1010M410_3, + AKST9AKDTM320M1110, + AST4, + AST4ADTM320M1110, + AWST_8, + CAT_2, + CET_1, + CET_1CESTM350M1050_3, + CST_8, + CST5CDTM320_0M1110_1, + CST6, + CST6CDTM320M1110, + ChST_10, + EAT_3, + EET_2, + EET_2EESTM344_50M1044_50, + EET_2EESTM350M1050_3, + EET_2EESTM350_0M1050_0, + EET_2EESTM350_3M1050_4, + EET_2EESTM455_0M1054_24, + EST5, + EST5EDTM320M1110, + GMT0, + GMT0BSTM350_1M1050, + HKT_8, + HST10, + HST10HDTM320M1110, + IST_1GMT0M1050M350_1, + IST_2IDTM344_26M1050, + IST_530, + JST_9, + KST_9, + MSK_3, + MST7, + MST7MDTM320M1110, + NST330NDTM320M1110, + NZST_12NZDTM950M410_3, + PKT_5, + PST_8, + PST8PDTM320M1110, + SAST_2, + SST11, + UTC0, + WAT_1, + WET0WESTM350_1M1050, + WIB_7, + WIT_9, + WITA_8, +}; + +// Key-value pair type +using TimezoneEntry = std::pair; + +// Lookup table +constexpr std::array TIMEZONE_DATA = {{ + {"Africa/Abidjan", TimezoneValue::GMT0}, + {"Africa/Accra", TimezoneValue::GMT0}, + {"Africa/Addis_Ababa", TimezoneValue::EAT_3}, + {"Africa/Algiers", TimezoneValue::CET_1}, + {"Africa/Asmara", TimezoneValue::EAT_3}, + {"Africa/Bamako", TimezoneValue::GMT0}, + {"Africa/Bangui", TimezoneValue::WAT_1}, + {"Africa/Banjul", TimezoneValue::GMT0}, + {"Africa/Bissau", TimezoneValue::GMT0}, + {"Africa/Blantyre", TimezoneValue::CAT_2}, + {"Africa/Brazzaville", TimezoneValue::WAT_1}, + {"Africa/Bujumbura", TimezoneValue::CAT_2}, + {"Africa/Cairo", TimezoneValue::EET_2EESTM455_0M1054_24}, + {"Africa/Casablanca", TimezoneValue::plus01_1}, + {"Africa/Ceuta", TimezoneValue::CET_1CESTM350M1050_3}, + {"Africa/Conakry", TimezoneValue::GMT0}, + {"Africa/Dakar", TimezoneValue::GMT0}, + {"Africa/Dar_es_Salaam", TimezoneValue::EAT_3}, + {"Africa/Djibouti", TimezoneValue::EAT_3}, + {"Africa/Douala", TimezoneValue::WAT_1}, + {"Africa/El_Aaiun", TimezoneValue::plus01_1}, + {"Africa/Freetown", TimezoneValue::GMT0}, + {"Africa/Gaborone", TimezoneValue::CAT_2}, + {"Africa/Harare", TimezoneValue::CAT_2}, + {"Africa/Johannesburg", TimezoneValue::SAST_2}, + {"Africa/Juba", TimezoneValue::CAT_2}, + {"Africa/Kampala", TimezoneValue::EAT_3}, + {"Africa/Khartoum", TimezoneValue::CAT_2}, + {"Africa/Kigali", TimezoneValue::CAT_2}, + {"Africa/Kinshasa", TimezoneValue::WAT_1}, + {"Africa/Lagos", TimezoneValue::WAT_1}, + {"Africa/Libreville", TimezoneValue::WAT_1}, + {"Africa/Lome", TimezoneValue::GMT0}, + {"Africa/Luanda", TimezoneValue::WAT_1}, + {"Africa/Lubumbashi", TimezoneValue::CAT_2}, + {"Africa/Lusaka", TimezoneValue::CAT_2}, + {"Africa/Malabo", TimezoneValue::WAT_1}, + {"Africa/Maputo", TimezoneValue::CAT_2}, + {"Africa/Maseru", TimezoneValue::SAST_2}, + {"Africa/Mbabane", TimezoneValue::SAST_2}, + {"Africa/Mogadishu", TimezoneValue::EAT_3}, + {"Africa/Monrovia", TimezoneValue::GMT0}, + {"Africa/Nairobi", TimezoneValue::EAT_3}, + {"Africa/Ndjamena", TimezoneValue::WAT_1}, + {"Africa/Niamey", TimezoneValue::WAT_1}, + {"Africa/Nouakchott", TimezoneValue::GMT0}, + {"Africa/Ouagadougou", TimezoneValue::GMT0}, + {"Africa/Porto-Novo", TimezoneValue::WAT_1}, + {"Africa/Sao_Tome", TimezoneValue::GMT0}, + {"Africa/Tripoli", TimezoneValue::EET_2}, + {"Africa/Tunis", TimezoneValue::CET_1}, + {"Africa/Windhoek", TimezoneValue::CAT_2}, + {"America/Adak", TimezoneValue::HST10HDTM320M1110}, + {"America/Anchorage", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Anguilla", TimezoneValue::AST4}, + {"America/Antigua", TimezoneValue::AST4}, + {"America/Araguaina", TimezoneValue::_033}, + {"America/Argentina/Buenos_Aires", TimezoneValue::_033}, + {"America/Argentina/Catamarca", TimezoneValue::_033}, + {"America/Argentina/Cordoba", TimezoneValue::_033}, + {"America/Argentina/Jujuy", TimezoneValue::_033}, + {"America/Argentina/La_Rioja", TimezoneValue::_033}, + {"America/Argentina/Mendoza", TimezoneValue::_033}, + {"America/Argentina/Rio_Gallegos", TimezoneValue::_033}, + {"America/Argentina/Salta", TimezoneValue::_033}, + {"America/Argentina/San_Juan", TimezoneValue::_033}, + {"America/Argentina/San_Luis", TimezoneValue::_033}, + {"America/Argentina/Tucuman", TimezoneValue::_033}, + {"America/Argentina/Ushuaia", TimezoneValue::_033}, + {"America/Aruba", TimezoneValue::AST4}, + {"America/Asuncion", TimezoneValue::_044_03M1010_0M340_0}, + {"America/Atikokan", TimezoneValue::EST5}, + {"America/Bahia", TimezoneValue::_033}, + {"America/Bahia_Banderas", TimezoneValue::CST6}, + {"America/Barbados", TimezoneValue::AST4}, + {"America/Belem", TimezoneValue::_033}, + {"America/Belize", TimezoneValue::CST6}, + {"America/Blanc-Sablon", TimezoneValue::AST4}, + {"America/Boa_Vista", TimezoneValue::_044}, + {"America/Bogota", TimezoneValue::_055}, + {"America/Boise", TimezoneValue::MST7MDTM320M1110}, + {"America/Cambridge_Bay", TimezoneValue::MST7MDTM320M1110}, + {"America/Campo_Grande", TimezoneValue::_044}, + {"America/Cancun", TimezoneValue::EST5}, + {"America/Caracas", TimezoneValue::_044}, + {"America/Cayenne", TimezoneValue::_033}, + {"America/Cayman", TimezoneValue::EST5}, + {"America/Chicago", TimezoneValue::CST6CDTM320M1110}, + {"America/Chihuahua", TimezoneValue::CST6}, + {"America/Costa_Rica", TimezoneValue::CST6}, + {"America/Creston", TimezoneValue::MST7}, + {"America/Cuiaba", TimezoneValue::_044}, + {"America/Curacao", TimezoneValue::AST4}, + {"America/Danmarkshavn", TimezoneValue::GMT0}, + {"America/Dawson", TimezoneValue::MST7}, + {"America/Dawson_Creek", TimezoneValue::MST7}, + {"America/Denver", TimezoneValue::MST7MDTM320M1110}, + {"America/Detroit", TimezoneValue::EST5EDTM320M1110}, + {"America/Dominica", TimezoneValue::AST4}, + {"America/Edmonton", TimezoneValue::MST7MDTM320M1110}, + {"America/Eirunepe", TimezoneValue::_055}, + {"America/El_Salvador", TimezoneValue::CST6}, + {"America/Fort_Nelson", TimezoneValue::MST7}, + {"America/Fortaleza", TimezoneValue::_033}, + {"America/Glace_Bay", TimezoneValue::AST4ADTM320M1110}, + {"America/Godthab", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Goose_Bay", TimezoneValue::AST4ADTM320M1110}, + {"America/Grand_Turk", TimezoneValue::EST5EDTM320M1110}, + {"America/Grenada", TimezoneValue::AST4}, + {"America/Guadeloupe", TimezoneValue::AST4}, + {"America/Guatemala", TimezoneValue::CST6}, + {"America/Guayaquil", TimezoneValue::_055}, + {"America/Guyana", TimezoneValue::_044}, + {"America/Halifax", TimezoneValue::AST4ADTM320M1110}, + {"America/Havana", TimezoneValue::CST5CDTM320_0M1110_1}, + {"America/Hermosillo", TimezoneValue::MST7}, + {"America/Indiana/Indianapolis", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Knox", TimezoneValue::CST6CDTM320M1110}, + {"America/Indiana/Marengo", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Petersburg", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Tell_City", TimezoneValue::CST6CDTM320M1110}, + {"America/Indiana/Vevay", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Vincennes", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Winamac", TimezoneValue::EST5EDTM320M1110}, + {"America/Inuvik", TimezoneValue::MST7MDTM320M1110}, + {"America/Iqaluit", TimezoneValue::EST5EDTM320M1110}, + {"America/Jamaica", TimezoneValue::EST5}, + {"America/Juneau", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Kentucky/Louisville", TimezoneValue::EST5EDTM320M1110}, + {"America/Kentucky/Monticello", TimezoneValue::EST5EDTM320M1110}, + {"America/Kralendijk", TimezoneValue::AST4}, + {"America/La_Paz", TimezoneValue::_044}, + {"America/Lima", TimezoneValue::_055}, + {"America/Los_Angeles", TimezoneValue::PST8PDTM320M1110}, + {"America/Lower_Princes", TimezoneValue::AST4}, + {"America/Maceio", TimezoneValue::_033}, + {"America/Managua", TimezoneValue::CST6}, + {"America/Manaus", TimezoneValue::_044}, + {"America/Marigot", TimezoneValue::AST4}, + {"America/Martinique", TimezoneValue::AST4}, + {"America/Matamoros", TimezoneValue::CST6CDTM320M1110}, + {"America/Mazatlan", TimezoneValue::MST7}, + {"America/Menominee", TimezoneValue::CST6CDTM320M1110}, + {"America/Merida", TimezoneValue::CST6}, + {"America/Metlakatla", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Mexico_City", TimezoneValue::CST6}, + {"America/Miquelon", TimezoneValue::_033_02M320M1110}, + {"America/Moncton", TimezoneValue::AST4ADTM320M1110}, + {"America/Monterrey", TimezoneValue::CST6}, + {"America/Montevideo", TimezoneValue::_033}, + {"America/Montreal", TimezoneValue::EST5EDTM320M1110}, + {"America/Montserrat", TimezoneValue::AST4}, + {"America/Nassau", TimezoneValue::EST5EDTM320M1110}, + {"America/New_York", TimezoneValue::EST5EDTM320M1110}, + {"America/Nipigon", TimezoneValue::EST5EDTM320M1110}, + {"America/Nome", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Noronha", TimezoneValue::_022}, + {"America/North_Dakota/Beulah", TimezoneValue::CST6CDTM320M1110}, + {"America/North_Dakota/Center", TimezoneValue::CST6CDTM320M1110}, + {"America/North_Dakota/New_Salem", TimezoneValue::CST6CDTM320M1110}, + {"America/Nuuk", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Ojinaga", TimezoneValue::CST6CDTM320M1110}, + {"America/Panama", TimezoneValue::EST5}, + {"America/Pangnirtung", TimezoneValue::EST5EDTM320M1110}, + {"America/Paramaribo", TimezoneValue::_033}, + {"America/Phoenix", TimezoneValue::MST7}, + {"America/Port-au-Prince", TimezoneValue::EST5EDTM320M1110}, + {"America/Port_of_Spain", TimezoneValue::AST4}, + {"America/Porto_Velho", TimezoneValue::_044}, + {"America/Puerto_Rico", TimezoneValue::AST4}, + {"America/Punta_Arenas", TimezoneValue::_033}, + {"America/Rainy_River", TimezoneValue::CST6CDTM320M1110}, + {"America/Rankin_Inlet", TimezoneValue::CST6CDTM320M1110}, + {"America/Recife", TimezoneValue::_033}, + {"America/Regina", TimezoneValue::CST6}, + {"America/Resolute", TimezoneValue::CST6CDTM320M1110}, + {"America/Rio_Branco", TimezoneValue::_055}, + {"America/Santarem", TimezoneValue::_033}, + {"America/Santiago", TimezoneValue::_044_03M916_24M416_24}, + {"America/Santo_Domingo", TimezoneValue::AST4}, + {"America/Sao_Paulo", TimezoneValue::_033}, + {"America/Scoresbysund", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Sitka", TimezoneValue::AKST9AKDTM320M1110}, + {"America/St_Barthelemy", TimezoneValue::AST4}, + {"America/St_Johns", TimezoneValue::NST330NDTM320M1110}, + {"America/St_Kitts", TimezoneValue::AST4}, + {"America/St_Lucia", TimezoneValue::AST4}, + {"America/St_Thomas", TimezoneValue::AST4}, + {"America/St_Vincent", TimezoneValue::AST4}, + {"America/Swift_Current", TimezoneValue::CST6}, + {"America/Tegucigalpa", TimezoneValue::CST6}, + {"America/Thule", TimezoneValue::AST4ADTM320M1110}, + {"America/Thunder_Bay", TimezoneValue::EST5EDTM320M1110}, + {"America/Tijuana", TimezoneValue::PST8PDTM320M1110}, + {"America/Toronto", TimezoneValue::EST5EDTM320M1110}, + {"America/Tortola", TimezoneValue::AST4}, + {"America/Vancouver", TimezoneValue::PST8PDTM320M1110}, + {"America/Whitehorse", TimezoneValue::MST7}, + {"America/Winnipeg", TimezoneValue::CST6CDTM320M1110}, + {"America/Yakutat", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Yellowknife", TimezoneValue::MST7MDTM320M1110}, + {"Antarctica/Casey", TimezoneValue::plus08_8}, + {"Antarctica/Davis", TimezoneValue::plus07_7}, + {"Antarctica/DumontDUrville", TimezoneValue::plus10_10}, + {"Antarctica/Macquarie", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Antarctica/Mawson", TimezoneValue::plus05_5}, + {"Antarctica/McMurdo", TimezoneValue::NZST_12NZDTM950M410_3}, + {"Antarctica/Palmer", TimezoneValue::_033}, + {"Antarctica/Rothera", TimezoneValue::_033}, + {"Antarctica/Syowa", TimezoneValue::plus03_3}, + {"Antarctica/Troll", TimezoneValue::plus000plus02_2M350_1M1050_3}, + {"Antarctica/Vostok", TimezoneValue::plus05_5}, + {"Arctic/Longyearbyen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Asia/Aden", TimezoneValue::plus03_3}, + {"Asia/Almaty", TimezoneValue::plus05_5}, + {"Asia/Amman", TimezoneValue::plus03_3}, + {"Asia/Anadyr", TimezoneValue::plus12_12}, + {"Asia/Aqtau", TimezoneValue::plus05_5}, + {"Asia/Aqtobe", TimezoneValue::plus05_5}, + {"Asia/Ashgabat", TimezoneValue::plus05_5}, + {"Asia/Atyrau", TimezoneValue::plus05_5}, + {"Asia/Baghdad", TimezoneValue::plus03_3}, + {"Asia/Bahrain", TimezoneValue::plus03_3}, + {"Asia/Baku", TimezoneValue::plus04_4}, + {"Asia/Bangkok", TimezoneValue::plus07_7}, + {"Asia/Barnaul", TimezoneValue::plus07_7}, + {"Asia/Beirut", TimezoneValue::EET_2EESTM350_0M1050_0}, + {"Asia/Bishkek", TimezoneValue::plus06_6}, + {"Asia/Brunei", TimezoneValue::plus08_8}, + {"Asia/Chita", TimezoneValue::plus09_9}, + {"Asia/Choibalsan", TimezoneValue::plus08_8}, + {"Asia/Colombo", TimezoneValue::plus0530_530}, + {"Asia/Damascus", TimezoneValue::plus03_3}, + {"Asia/Dhaka", TimezoneValue::plus06_6}, + {"Asia/Dili", TimezoneValue::plus09_9}, + {"Asia/Dubai", TimezoneValue::plus04_4}, + {"Asia/Dushanbe", TimezoneValue::plus05_5}, + {"Asia/Famagusta", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Asia/Gaza", TimezoneValue::EET_2EESTM344_50M1044_50}, + {"Asia/Hebron", TimezoneValue::EET_2EESTM344_50M1044_50}, + {"Asia/Ho_Chi_Minh", TimezoneValue::plus07_7}, + {"Asia/Hong_Kong", TimezoneValue::HKT_8}, + {"Asia/Hovd", TimezoneValue::plus07_7}, + {"Asia/Irkutsk", TimezoneValue::plus08_8}, + {"Asia/Jakarta", TimezoneValue::WIB_7}, + {"Asia/Jayapura", TimezoneValue::WIT_9}, + {"Asia/Jerusalem", TimezoneValue::IST_2IDTM344_26M1050}, + {"Asia/Kabul", TimezoneValue::plus0430_430}, + {"Asia/Kamchatka", TimezoneValue::plus12_12}, + {"Asia/Karachi", TimezoneValue::PKT_5}, + {"Asia/Kathmandu", TimezoneValue::plus0545_545}, + {"Asia/Khandyga", TimezoneValue::plus09_9}, + {"Asia/Kolkata", TimezoneValue::IST_530}, + {"Asia/Krasnoyarsk", TimezoneValue::plus07_7}, + {"Asia/Kuala_Lumpur", TimezoneValue::plus08_8}, + {"Asia/Kuching", TimezoneValue::plus08_8}, + {"Asia/Kuwait", TimezoneValue::plus03_3}, + {"Asia/Macau", TimezoneValue::CST_8}, + {"Asia/Magadan", TimezoneValue::plus11_11}, + {"Asia/Makassar", TimezoneValue::WITA_8}, + {"Asia/Manila", TimezoneValue::PST_8}, + {"Asia/Muscat", TimezoneValue::plus04_4}, + {"Asia/Nicosia", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Asia/Novokuznetsk", TimezoneValue::plus07_7}, + {"Asia/Novosibirsk", TimezoneValue::plus07_7}, + {"Asia/Omsk", TimezoneValue::plus06_6}, + {"Asia/Oral", TimezoneValue::plus05_5}, + {"Asia/Phnom_Penh", TimezoneValue::plus07_7}, + {"Asia/Pontianak", TimezoneValue::WIB_7}, + {"Asia/Pyongyang", TimezoneValue::KST_9}, + {"Asia/Qatar", TimezoneValue::plus03_3}, + {"Asia/Qyzylorda", TimezoneValue::plus05_5}, + {"Asia/Riyadh", TimezoneValue::plus03_3}, + {"Asia/Sakhalin", TimezoneValue::plus11_11}, + {"Asia/Samarkand", TimezoneValue::plus05_5}, + {"Asia/Seoul", TimezoneValue::KST_9}, + {"Asia/Shanghai", TimezoneValue::CST_8}, + {"Asia/Singapore", TimezoneValue::plus08_8}, + {"Asia/Srednekolymsk", TimezoneValue::plus11_11}, + {"Asia/Taipei", TimezoneValue::CST_8}, + {"Asia/Tashkent", TimezoneValue::plus05_5}, + {"Asia/Tbilisi", TimezoneValue::plus04_4}, + {"Asia/Tehran", TimezoneValue::plus0330_330}, + {"Asia/Thimphu", TimezoneValue::plus06_6}, + {"Asia/Tokyo", TimezoneValue::JST_9}, + {"Asia/Tomsk", TimezoneValue::plus07_7}, + {"Asia/Ulaanbaatar", TimezoneValue::plus08_8}, + {"Asia/Urumqi", TimezoneValue::plus06_6}, + {"Asia/Ust-Nera", TimezoneValue::plus10_10}, + {"Asia/Vientiane", TimezoneValue::plus07_7}, + {"Asia/Vladivostok", TimezoneValue::plus10_10}, + {"Asia/Yakutsk", TimezoneValue::plus09_9}, + {"Asia/Yangon", TimezoneValue::plus0630_630}, + {"Asia/Yekaterinburg", TimezoneValue::plus05_5}, + {"Asia/Yerevan", TimezoneValue::plus04_4}, + {"Atlantic/Azores", TimezoneValue::_011plus00M350_0M1050_1}, + {"Atlantic/Bermuda", TimezoneValue::AST4ADTM320M1110}, + {"Atlantic/Canary", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Cape_Verde", TimezoneValue::_011}, + {"Atlantic/Faroe", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Madeira", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Reykjavik", TimezoneValue::GMT0}, + {"Atlantic/South_Georgia", TimezoneValue::_022}, + {"Atlantic/St_Helena", TimezoneValue::GMT0}, + {"Atlantic/Stanley", TimezoneValue::_033}, + {"Australia/Adelaide", TimezoneValue::ACST_930ACDTM1010M410_3}, + {"Australia/Brisbane", TimezoneValue::AEST_10}, + {"Australia/Broken_Hill", TimezoneValue::ACST_930ACDTM1010M410_3}, + {"Australia/Currie", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Darwin", TimezoneValue::ACST_930}, + {"Australia/Eucla", TimezoneValue::plus0845_845}, + {"Australia/Hobart", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Lindeman", TimezoneValue::AEST_10}, + {"Australia/Lord_Howe", TimezoneValue::plus1030_1030plus11_11M1010M410}, + {"Australia/Melbourne", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Perth", TimezoneValue::AWST_8}, + {"Australia/Sydney", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Etc/GMT", TimezoneValue::GMT0}, + {"Etc/GMT+0", TimezoneValue::GMT0}, + {"Etc/GMT+1", TimezoneValue::_011}, + {"Etc/GMT+10", TimezoneValue::_1010}, + {"Etc/GMT+11", TimezoneValue::_1111}, + {"Etc/GMT+12", TimezoneValue::_1212}, + {"Etc/GMT+2", TimezoneValue::_022}, + {"Etc/GMT+3", TimezoneValue::_033}, + {"Etc/GMT+4", TimezoneValue::_044}, + {"Etc/GMT+5", TimezoneValue::_055}, + {"Etc/GMT+6", TimezoneValue::_066}, + {"Etc/GMT+7", TimezoneValue::_077}, + {"Etc/GMT+8", TimezoneValue::_088}, + {"Etc/GMT+9", TimezoneValue::_099}, + {"Etc/GMT-0", TimezoneValue::GMT0}, + {"Etc/GMT-1", TimezoneValue::plus01_1}, + {"Etc/GMT-10", TimezoneValue::plus10_10}, + {"Etc/GMT-11", TimezoneValue::plus11_11}, + {"Etc/GMT-12", TimezoneValue::plus12_12}, + {"Etc/GMT-13", TimezoneValue::plus13_13}, + {"Etc/GMT-14", TimezoneValue::plus14_14}, + {"Etc/GMT-2", TimezoneValue::plus02_2}, + {"Etc/GMT-3", TimezoneValue::plus03_3}, + {"Etc/GMT-4", TimezoneValue::plus04_4}, + {"Etc/GMT-5", TimezoneValue::plus05_5}, + {"Etc/GMT-6", TimezoneValue::plus06_6}, + {"Etc/GMT-7", TimezoneValue::plus07_7}, + {"Etc/GMT-8", TimezoneValue::plus08_8}, + {"Etc/GMT-9", TimezoneValue::plus09_9}, + {"Etc/GMT0", TimezoneValue::GMT0}, + {"Etc/Greenwich", TimezoneValue::GMT0}, + {"Etc/UCT", TimezoneValue::UTC0}, + {"Etc/UTC", TimezoneValue::UTC0}, + {"Etc/Universal", TimezoneValue::UTC0}, + {"Etc/Zulu", TimezoneValue::UTC0}, + {"Europe/Amsterdam", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Andorra", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Astrakhan", TimezoneValue::plus04_4}, + {"Europe/Athens", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Belgrade", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Berlin", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Bratislava", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Brussels", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Bucharest", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Budapest", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Busingen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Chisinau", TimezoneValue::EET_2EESTM350M1050_3}, + {"Europe/Copenhagen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Dublin", TimezoneValue::IST_1GMT0M1050M350_1}, + {"Europe/Gibraltar", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Guernsey", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Helsinki", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Isle_of_Man", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Istanbul", TimezoneValue::plus03_3}, + {"Europe/Jersey", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Kaliningrad", TimezoneValue::EET_2}, + {"Europe/Kiev", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Kirov", TimezoneValue::MSK_3}, + {"Europe/Lisbon", TimezoneValue::WET0WESTM350_1M1050}, + {"Europe/Ljubljana", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/London", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Luxembourg", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Madrid", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Malta", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Mariehamn", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Minsk", TimezoneValue::plus03_3}, + {"Europe/Monaco", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Moscow", TimezoneValue::MSK_3}, + {"Europe/Oslo", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Paris", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Podgorica", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Prague", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Riga", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Rome", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Samara", TimezoneValue::plus04_4}, + {"Europe/San_Marino", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Sarajevo", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Saratov", TimezoneValue::plus04_4}, + {"Europe/Simferopol", TimezoneValue::MSK_3}, + {"Europe/Skopje", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Sofia", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Stockholm", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Tallinn", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Tirane", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Ulyanovsk", TimezoneValue::plus04_4}, + {"Europe/Uzhgorod", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Vaduz", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vatican", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vienna", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vilnius", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Volgograd", TimezoneValue::MSK_3}, + {"Europe/Warsaw", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Zagreb", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Zaporozhye", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Zurich", TimezoneValue::CET_1CESTM350M1050_3}, + {"Indian/Antananarivo", TimezoneValue::EAT_3}, + {"Indian/Chagos", TimezoneValue::plus06_6}, + {"Indian/Christmas", TimezoneValue::plus07_7}, + {"Indian/Cocos", TimezoneValue::plus0630_630}, + {"Indian/Comoro", TimezoneValue::EAT_3}, + {"Indian/Kerguelen", TimezoneValue::plus05_5}, + {"Indian/Mahe", TimezoneValue::plus04_4}, + {"Indian/Maldives", TimezoneValue::plus05_5}, + {"Indian/Mauritius", TimezoneValue::plus04_4}, + {"Indian/Mayotte", TimezoneValue::EAT_3}, + {"Indian/Reunion", TimezoneValue::plus04_4}, + {"Pacific/Apia", TimezoneValue::plus13_13}, + {"Pacific/Auckland", TimezoneValue::NZST_12NZDTM950M410_3}, + {"Pacific/Bougainville", TimezoneValue::plus11_11}, + {"Pacific/Chatham", TimezoneValue::plus1245_1245plus1345M950_245M410_345}, + {"Pacific/Chuuk", TimezoneValue::plus10_10}, + {"Pacific/Easter", TimezoneValue::_066_05M916_22M416_22}, + {"Pacific/Efate", TimezoneValue::plus11_11}, + {"Pacific/Enderbury", TimezoneValue::plus13_13}, + {"Pacific/Fakaofo", TimezoneValue::plus13_13}, + {"Pacific/Fiji", TimezoneValue::plus12_12}, + {"Pacific/Funafuti", TimezoneValue::plus12_12}, + {"Pacific/Galapagos", TimezoneValue::_066}, + {"Pacific/Gambier", TimezoneValue::_099}, + {"Pacific/Guadalcanal", TimezoneValue::plus11_11}, + {"Pacific/Guam", TimezoneValue::ChST_10}, + {"Pacific/Honolulu", TimezoneValue::HST10}, + {"Pacific/Kiritimati", TimezoneValue::plus14_14}, + {"Pacific/Kosrae", TimezoneValue::plus11_11}, + {"Pacific/Kwajalein", TimezoneValue::plus12_12}, + {"Pacific/Majuro", TimezoneValue::plus12_12}, + {"Pacific/Marquesas", TimezoneValue::_0930930}, + {"Pacific/Midway", TimezoneValue::SST11}, + {"Pacific/Nauru", TimezoneValue::plus12_12}, + {"Pacific/Niue", TimezoneValue::_1111}, + {"Pacific/Norfolk", TimezoneValue::plus11_11plus12M1010M410_3}, + {"Pacific/Noumea", TimezoneValue::plus11_11}, + {"Pacific/Pago_Pago", TimezoneValue::SST11}, + {"Pacific/Palau", TimezoneValue::plus09_9}, + {"Pacific/Pitcairn", TimezoneValue::_088}, + {"Pacific/Pohnpei", TimezoneValue::plus11_11}, + {"Pacific/Port_Moresby", TimezoneValue::plus10_10}, + {"Pacific/Rarotonga", TimezoneValue::_1010}, + {"Pacific/Saipan", TimezoneValue::ChST_10}, + {"Pacific/Tahiti", TimezoneValue::_1010}, + {"Pacific/Tarawa", TimezoneValue::plus12_12}, + {"Pacific/Tongatapu", TimezoneValue::plus13_13}, + {"Pacific/Wake", TimezoneValue::plus12_12}, + {"Pacific/Wallis", TimezoneValue::plus12_12}, +}}; + +// Helper function to find timezone value +inline TimezoneValue find_timezone_value(std::string_view key) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.first == key) { + return entry.second; + } + } + return TimezoneValue::plus000plus02_2M350_1M1050_3; // Default fallback +} + +// Overload for String +inline TimezoneValue find_timezone_value(const String& key) { + return find_timezone_value(std::string_view(key.c_str())); +} + +// Overload for std::string +inline TimezoneValue find_timezone_value(const std::string& key) { + return find_timezone_value(std::string_view(key)); +} + +// Helper function to convert TimezoneValue to string representation +inline String get_timezone_string(TimezoneValue value) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.second == value) { + return String(entry.first.data(), entry.first.length()); + } + } + return String("GMT0"); // Default fallback +} + +// Overload for std::string +inline std::string get_timezone_string_std(TimezoneValue value) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.second == value) { + return std::string(entry.first.data(), entry.first.length()); + } + } + return "GMT0"; // Default fallback +} + +// Helper function to convert TimezoneValue enum to its string representation +inline String get_timezone_value_string(TimezoneValue value) { + switch (value) { + case TimezoneValue::plus000plus02_2M350_1M1050_3: return String("+00:00+02:00,M3.5.0/1,M10.5.0/3"); + case TimezoneValue::plus01_1: return String("+01:00"); + case TimezoneValue::plus02_2: return String("+02:00"); + case TimezoneValue::plus0330_330: return String("+03:30"); + case TimezoneValue::plus03_3: return String("+03:00"); + case TimezoneValue::plus0430_430: return String("+04:30"); + case TimezoneValue::plus04_4: return String("+04:00"); + case TimezoneValue::plus0530_530: return String("+05:30"); + case TimezoneValue::plus0545_545: return String("+05:45"); + case TimezoneValue::plus05_5: return String("+05:00"); + case TimezoneValue::plus0630_630: return String("+06:30"); + case TimezoneValue::plus06_6: return String("+06:00"); + case TimezoneValue::plus07_7: return String("+07:00"); + case TimezoneValue::plus0845_845: return String("+08:45"); + case TimezoneValue::plus08_8: return String("+08:00"); + case TimezoneValue::plus09_9: return String("+09:00"); + case TimezoneValue::plus1030_1030plus11_11M1010M410: return String("+10:30+11:00,M10.1.0,M4.1.0"); + case TimezoneValue::plus10_10: return String("+10:00"); + case TimezoneValue::plus11_11: return String("+11:00"); + case TimezoneValue::plus11_11plus12M1010M410_3: return String("+11:00+12:00,M10.1.0,M4.1.0/3"); + case TimezoneValue::plus1245_1245plus1345M950_245M410_345: return String("+12:45+13:45,M9.5.0/2:45,M4.1.0/3:45"); + case TimezoneValue::plus12_12: return String("+12:00"); + case TimezoneValue::plus13_13: return String("+13:00"); + case TimezoneValue::plus14_14: return String("+14:00"); + case TimezoneValue::_011: return String("-01:00"); + case TimezoneValue::_011plus00M350_0M1050_1: return String("-01:00+00:00,M3.5.0/0,M10.5.0/1"); + case TimezoneValue::_022: return String("-02:00"); + case TimezoneValue::_022_01M350__1M1050_0: return String("-02:00-01:00,M3.5.0/-1,M10.5.0/0"); + case TimezoneValue::_033: return String("-03:00"); + case TimezoneValue::_033_02M320M1110: return String("-03:00-02:00,M3.2.0,M11.1.0"); + case TimezoneValue::_044: return String("-04:00"); + case TimezoneValue::_044_03M1010_0M340_0: return String("-04:00-03:00,M10.1.0/0,M3.4.0/0"); + case TimezoneValue::_044_03M916_24M416_24: return String("-04:00-03:00,M9.1.6/24,M4.1.6/24"); + case TimezoneValue::_055: return String("-05:00"); + case TimezoneValue::_066: return String("-06:00"); + case TimezoneValue::_066_05M916_22M416_22: return String("-06:00-05:00,M9.1.6/22,M4.1.6/22"); + case TimezoneValue::_077: return String("-07:00"); + case TimezoneValue::_088: return String("-08:00"); + case TimezoneValue::_0930930: return String("-09:30"); + case TimezoneValue::_099: return String("-09:00"); + case TimezoneValue::_1010: return String("-10:00"); + case TimezoneValue::_1111: return String("-11:00"); + case TimezoneValue::_1212: return String("-12:00"); + case TimezoneValue::ACST_930: return String("ACST-9:30"); + case TimezoneValue::ACST_930ACDTM1010M410_3: return String("ACST-9:30ACDT,M10.1.0,M4.1.0/3"); + case TimezoneValue::AEST_10: return String("AEST-10"); + case TimezoneValue::AEST_10AEDTM1010M410_3: return String("AEST-10AEDT,M10.1.0,M4.1.0/3"); + case TimezoneValue::AKST9AKDTM320M1110: return String("AKST9AKDT,M3.2.0,M11.1.0"); + case TimezoneValue::AST4: return String("AST4"); + case TimezoneValue::AST4ADTM320M1110: return String("AST4ADT,M3.2.0,M11.1.0"); + case TimezoneValue::AWST_8: return String("AWST-8"); + case TimezoneValue::CAT_2: return String("CAT-2"); + case TimezoneValue::CET_1: return String("CET-1"); + case TimezoneValue::CET_1CESTM350M1050_3: return String("CET-1CEST,M3.5.0,M10.5.0/3"); + case TimezoneValue::CST_8: return String("CST-8"); + case TimezoneValue::CST5CDTM320_0M1110_1: return String("CST5CDT,M3.2.0/0,M11.1.0/1"); + case TimezoneValue::CST6: return String("CST6"); + case TimezoneValue::CST6CDTM320M1110: return String("CST6CDT,M3.2.0,M11.1.0"); + case TimezoneValue::ChST_10: return String("ChST-10"); + case TimezoneValue::EAT_3: return String("EAT-3"); + case TimezoneValue::EET_2: return String("EET-2"); + case TimezoneValue::EET_2EESTM344_50M1044_50: return String("EET-2EEST,M3.4.4/50,M10.4.4/50"); + case TimezoneValue::EET_2EESTM350M1050_3: return String("EET-2EEST,M3.5.0,M10.5.0/3"); + case TimezoneValue::EET_2EESTM350_0M1050_0: return String("EET-2EEST,M3.5.0/0,M10.5.0/0"); + case TimezoneValue::EET_2EESTM350_3M1050_4: return String("EET-2EEST,M3.5.0/3,M10.5.0/4"); + case TimezoneValue::EET_2EESTM455_0M1054_24: return String("EET-2EEST,M4.5.5/0,M10.5.4/24"); + case TimezoneValue::EST5: return String("EST5"); + case TimezoneValue::EST5EDTM320M1110: return String("EST5EDT,M3.2.0,M11.1.0"); + case TimezoneValue::GMT0: return String("GMT0"); + case TimezoneValue::GMT0BSTM350_1M1050: return String("GMT0BST,M3.5.0/1,M10.5.0"); + case TimezoneValue::HKT_8: return String("HKT-8"); + case TimezoneValue::HST10: return String("HST10"); + case TimezoneValue::HST10HDTM320M1110: return String("HST10HDT,M3.2.0,M11.1.0"); + case TimezoneValue::IST_1GMT0M1050M350_1: return String("IST-1GMT0,M10.5.0,M3.5.0/1"); + case TimezoneValue::IST_2IDTM344_26M1050: return String("IST-2IDT,M3.4.4/26,M10.5.0"); + case TimezoneValue::IST_530: return String("IST-5:30"); + case TimezoneValue::JST_9: return String("JST-9"); + case TimezoneValue::KST_9: return String("KST-9"); + case TimezoneValue::MSK_3: return String("MSK-3"); + case TimezoneValue::MST7: return String("MST7"); + case TimezoneValue::MST7MDTM320M1110: return String("MST7MDT,M3.2.0,M11.1.0"); + case TimezoneValue::NST330NDTM320M1110: return String("NST3:30NDT,M3.2.0,M11.1.0"); + case TimezoneValue::NZST_12NZDTM950M410_3: return String("NZST-12NZDT,M9.5.0,M4.1.0/3"); + case TimezoneValue::PKT_5: return String("PKT-5"); + case TimezoneValue::PST_8: return String("PST-8"); + case TimezoneValue::PST8PDTM320M1110: return String("PST8PDT,M3.2.0,M11.1.0"); + case TimezoneValue::SAST_2: return String("SAST-2"); + case TimezoneValue::SST11: return String("SST11"); + case TimezoneValue::UTC0: return String("UTC0"); + case TimezoneValue::WAT_1: return String("WAT-1"); + case TimezoneValue::WET0WESTM350_1M1050: return String("WET0WEST,M3.5.0/1,M10.5.0"); + case TimezoneValue::WIB_7: return String("WIB-7"); + case TimezoneValue::WIT_9: return String("WIT-9"); + case TimezoneValue::WITA_8: return String("WITA-8"); + default: return String("GMT0"); // Default fallback + } +} + +// Overload for std::string +inline std::string get_timezone_value_string_std(TimezoneValue value) { + return std::string(get_timezone_value_string(value).c_str()); +} + +} // namespace timezone_data diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 16cb80a..908a5a6 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -259,7 +259,7 @@ void setupWifi() void syncTime() { - configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, + configTime(0, 0, NTP_SERVER); struct tm timeinfo; @@ -267,15 +267,24 @@ void syncTime() { auto& ledHandler = getLedHandler(); ledHandler.queueEffect(LED_EFFECT_CONFIGURING); - configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, + configTime(0, 0, NTP_SERVER); delay(500); Serial.println(F("Retry set time")); } + setTimezone(get_timezone_value_string(timezone_data::find_timezone_value(preferences.getString("tzString", DEFAULT_TZ_STRING)))); + lastTimeSync = esp_timer_get_time() / 1000000; } +void setTimezone(String timezone) { + Serial.printf(" Setting Timezone to %s\n",timezone.c_str()); + setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time + tzset(); +} + + void setupPreferences() { preferences.begin("btclock", false); diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 95c877d..c8d5336 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -33,7 +33,7 @@ #include "shared.hpp" #include "defaults.hpp" - +#include "timezone_data.hpp" #define NTP_SERVER "pool.ntp.org" #define DEFAULT_TIME_OFFSET_SECONDS 3600 #ifndef MCP_DEV_ADDR @@ -43,6 +43,7 @@ void setup(); void syncTime(); +void setTimezone(String timezone); uint getLastTimeSync(); void setupPreferences(); void setupWebsocketClients(void *pvParameters); diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 55da6e3..00d8bc0 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -24,6 +24,7 @@ #define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD #define DEFAULT_TIME_OFFSET_SECONDS 3600 +#define DEFAULT_TZ_STRING "Europe/Amsterdam" #define DEFAULT_HOSTNAME_PREFIX "btclock" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index cfb5d62..8313b76 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -5,7 +5,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint", "tzString"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; @@ -692,8 +692,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["fullRefreshMin"] = preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH); root["wpTimeout"] = preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT); - root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; - + //root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; + root["tzString"] = preferences.getString("tzString", DEFAULT_TZ_STRING); + // Add data source settings root["dataSource"] = preferences.getUChar("dataSource", DEFAULT_DATA_SOURCE); From 3e54343da8b995aae663ce972631561b6a950e7b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:40:52 +0200 Subject: [PATCH 182/188] chore: Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 0116cd6..0e278d1 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500 +Subproject commit 0e278d1be4160ab382213e80ec0716f8aad14a65 From d648551835d3db3ad80921ee652f4c518aae53dd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 2 May 2025 22:28:46 +0200 Subject: [PATCH 183/188] fix: Fix LED status object --- src/lib/webserver.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 8313b76..6ee62f9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -298,7 +298,13 @@ JsonDocument getLedStatusObject() uint blue = pixColor & 0xFF; char hexColor[8]; snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue); - colors.add(hexColor); + // colors.add(hexColor); + + JsonObject object = colors.add(); + object["red"] = red; + object["green"] = green; + object["blue"] = blue; + object["hex"] = hexColor; } return root; From 064fe8fe6ce83b04bfdc28529bf6685ffda095ca Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 3 May 2025 00:03:01 +0200 Subject: [PATCH 184/188] chore: Update WebUI and dependencies --- data | 2 +- platformio.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data b/data index 0e278d1..8389ed8 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 0e278d1be4160ab382213e80ec0716f8aad14a65 +Subproject commit 8389ed8e36a9a1a7a39ca31f53324a0949aedb1d diff --git a/platformio.ini b/platformio.ini index 140946d..98192fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,8 +37,8 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git#v1.16.4 - bblanchon/ArduinoJson@^7.3.1 - esp32async/ESPAsyncWebServer @ 3.7.4 + bblanchon/ArduinoJson@^7.4.1 + esp32async/ESPAsyncWebServer @ 3.7.7 robtillaart/MCP23017@^0.9.1 adafruit/Adafruit NeoPixel@^1.12.5 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt From 2ac2a62c617ca0469cba1027c70c0ddd9ac3ae76 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 3 May 2025 00:03:19 +0200 Subject: [PATCH 185/188] feat: change 3rd party price source to kraken --- src/lib/price_notify.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 6ac2f3b..c851e85 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -1,6 +1,6 @@ #include "price_notify.hpp" -const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; +const char *wsServerPrice = "wss://ws.kraken.com/v2"; WebSocketsClient webSocket; uint currentPrice = 90000; @@ -14,7 +14,7 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); void setupPriceNotify() { - webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); + webSocket.beginSSL("ws.kraken.com", 443, "/v2"); webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) { onWebsocketPriceEvent(type, payload, length); }); @@ -32,7 +32,14 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { case WStype_CONNECTED: { Serial.println("Connected to " + String(wsServerPrice)); - priceNotifyInit = true; + + JsonDocument doc; + doc["method"] = "subscribe"; + JsonObject params = doc["params"].to(); + params["channel"] = "ticker"; + params["symbol"][0] = "BTC/USD"; + + webSocket.sendTXT(doc.as().c_str()); break; } case WStype_TEXT: @@ -40,13 +47,15 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { JsonDocument doc; deserializeJson(doc, (char *)payload); - if (doc["bitcoin"].is()) + if (doc["data"][0].is()) { - if (currentPrice != doc["bitcoin"].as()) + float price = doc["data"][0]["last"].as(); + uint roundedPrice = round(price); + if (currentPrice != roundedPrice) { - processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + processNewPrice(roundedPrice, CURRENCY_USD); } - } + } break; } case WStype_BIN: From 7853bf3e069ed8b3daafb950bb8006569a0c519b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 12 Jun 2025 16:10:17 +0200 Subject: [PATCH 186/188] feat: Add restore screen after zap setting --- src/lib/defaults.hpp | 1 + src/lib/nostr_notify.cpp | 15 ++++++++++++++- src/lib/shared.hpp | 1 + src/lib/webserver.cpp | 3 ++- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 00d8bc0..a785561 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -86,6 +86,7 @@ #define DEFAULT_CUSTOM_ENDPOINT "ws-staging.btclock.dev" #define DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL false #define DEFAULT_MOW_MODE false +#define DEFAULT_SCREEN_RESTORE_AFTER_ZAP true // Define data source types enum DataSourceType { diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index dda24e1..5bab443 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -10,6 +10,14 @@ boolean nostrIsSubscribing = true; String subIdZap; +void screenRestoreAfterZapCallback(TimerHandle_t xTimer) +{ + Serial.println("Restoring screen after zap"); + int screenBeforeZap = (int)(uintptr_t)pvTimerGetTimerID(xTimer); + ScreenHandler::setCurrentScreen(screenBeforeZap); + xTimerDelete(xTimer, 0); +} + void setupNostrNotify(bool asDatasource, bool zapNotify) { nostr::esp32::ESP32Platform::initNostr(false); @@ -288,12 +296,13 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) } uint64_t timerPeriod = 0; + int screenBeforeZap = ScreenHandler::getCurrentScreen(); if (isTimerActive()) { // store timer periode before making inactive to prevent artifacts timerPeriod = getTimerSeconds(); esp_timer_stop(screenRotateTimer); - } + } ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); EPDManager::getInstance().setContent(textEpdContent); @@ -306,6 +315,10 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) { esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); + } else if (preferences.getBool("scrnRestoreZap", DEFAULT_SCREEN_RESTORE_AFTER_ZAP)) { + TimerHandle_t screenRestoreAfterZapTimer = xTimerCreate("screenRestoreAfterZap", pdMS_TO_TICKS(getTimerSeconds() * msPerSecond), pdFALSE, (void*)(uintptr_t)screenBeforeZap, screenRestoreAfterZapCallback); + Serial.println("Starting screen restore after zap"); + xTimerStart(screenRestoreAfterZapTimer, 0); } } diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 54d48aa..97af754 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -66,6 +66,7 @@ const PROGMEM int screens[SCREEN_COUNT] = { SCREEN_BLOCK_FEE_RATE}; const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; +const int msPerSecond = 1000; // extern const char *github_root_ca; // extern const char *isrg_root_x1cert; diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 6ee62f9..2d447d6 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -18,7 +18,7 @@ static const char *const PROGMEM boolSettings[] = {"ledTestOnPower", "ledFlashOn "mempoolSecure", "bitaxeEnabled", "miningPoolStats", "verticalDesc", "nostrZapNotify", "httpAuthEnabled", - "enableDebugLog", "ceDisableSSL", "dndEnabled", "dndTimeBasedEnabled"}; + "enableDebugLog", "ceDisableSSL", "dndEnabled", "dndTimeBasedEnabled", "scrnRestoreZap"}; AsyncWebServer server(80); AsyncEventSource events("/events"); @@ -717,6 +717,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["nostrZapNotify"] = preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED); root["nostrZapPubkey"] = preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY); root["ledFlashOnZap"] = preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP); + root["scrnRestoreZap"] = preferences.getBool("scrnRestoreZap", DEFAULT_SCREEN_RESTORE_AFTER_ZAP); root["fontName"] = preferences.getString("fontName", DEFAULT_FONT_NAME); root["availableFonts"] = FontNames::getAvailableFonts(); // Custom endpoint settings (only used for CUSTOM_SOURCE) From b185d861862d2e9bfb2e87344413cdffc9cd5d05 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 12 Jun 2025 16:10:33 +0200 Subject: [PATCH 187/188] chore: dependency update --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 98192fe..be356f0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -40,7 +40,7 @@ lib_deps = bblanchon/ArduinoJson@^7.4.1 esp32async/ESPAsyncWebServer @ 3.7.7 robtillaart/MCP23017@^0.9.1 - adafruit/Adafruit NeoPixel@^1.12.5 + adafruit/Adafruit NeoPixel@^1.15.1 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 From 3149259309255f2c6de2d57fd48983ef10512f60 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 12 Jun 2025 16:28:12 +0200 Subject: [PATCH 188/188] chore: update webui --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 8389ed8..f5a9133 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 8389ed8e36a9a1a7a39ca31f53324a0949aedb1d +Subproject commit f5a9133cabfbab82325275575545c12f7aacf8f2