From 0a065f48be99d4ebae49de622a335f23e50c6ca0 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Sun, 18 Dec 2022 06:31:24 +0300 Subject: pump-mqtt-bot: wip; relayctl: somewhat stable --- package.json | 3 +- platformio/relayctl/make_static.sh | 3 +- platformio/relayctl/platformio.ini | 11 +- platformio/relayctl/src/config.cpp | 30 +- platformio/relayctl/src/config.def.h.example | 8 +- platformio/relayctl/src/config.h | 10 +- platformio/relayctl/src/http_server.cpp | 236 ++++++---- platformio/relayctl/src/http_server.h | 45 +- platformio/relayctl/src/led.cpp | 17 +- platformio/relayctl/src/led.h | 19 +- platformio/relayctl/src/logging.cpp | 37 -- platformio/relayctl/src/logging.h | 8 +- platformio/relayctl/src/main.cpp | 45 +- platformio/relayctl/src/mqtt.cpp | 341 ++++++++++---- platformio/relayctl/src/mqtt.h | 64 ++- platformio/relayctl/src/relay.h | 6 +- platformio/relayctl/src/static.cpp | 676 +++++++++++++++++---------- platformio/relayctl/src/static.h | 1 + platformio/relayctl/src/util.h | 13 + platformio/relayctl/src/wifi.cpp | 25 +- platformio/relayctl/src/wifi.h | 19 +- platformio/relayctl/static/app.js | 48 +- platformio/relayctl/static/index.html | 9 +- platformio/relayctl/static/md5.js | 615 ++++++++++++++++++++++++ platformio/relayctl/static/style.css | 2 +- src/home/api/types/types.py | 1 + src/home/api/web_api_client.py | 6 +- src/home/mqtt/__init__.py | 1 + src/home/mqtt/mqtt.py | 21 +- src/home/mqtt/payload/__init__.py | 1 + src/home/mqtt/payload/relay.py | 45 +- src/home/mqtt/relay.py | 93 ++++ src/home/telegram/bot.py | 2 + src/pump_mqtt_bot.py | 191 ++++++++ tools/mcuota.py | 98 ++++ tools/mcuota.sh | 14 + tools/minify.js | 16 +- 37 files changed, 2154 insertions(+), 626 deletions(-) delete mode 100644 platformio/relayctl/src/logging.cpp create mode 100644 platformio/relayctl/src/util.h create mode 100644 platformio/relayctl/static/md5.js create mode 100644 src/home/mqtt/relay.py create mode 100755 src/pump_mqtt_bot.py create mode 100755 tools/mcuota.py create mode 100755 tools/mcuota.sh diff --git a/package.json b/package.json index 16706ba..c38bb94 100644 --- a/package.json +++ b/package.json @@ -10,5 +10,6 @@ "html-minifier-terser": "^7.1.0", "minimist": "^1.2.7", "terser": "^5.16.1" - } + }, + "dependencies": {} } diff --git a/platformio/relayctl/make_static.sh b/platformio/relayctl/make_static.sh index 079d26b..879beb4 100755 --- a/platformio/relayctl/make_static.sh +++ b/platformio/relayctl/make_static.sh @@ -73,7 +73,8 @@ for ext in html js css ico; do gzip | xxd -ps -c 16 | sed 's/.\{2\}/0x&, /g' | - sed 's/^/ /' + sed 's/^/ /' | + sed 's/[ \t]*$//' echo "};" echo "const StaticFile $filename PROGMEM = {(sizeof(${filename}_content)/sizeof(${filename}_content[0])), ${filename}_content};" diff --git a/platformio/relayctl/platformio.ini b/platformio/relayctl/platformio.ini index 592eba9..662049e 100644 --- a/platformio/relayctl/platformio.ini +++ b/platformio/relayctl/platformio.ini @@ -14,7 +14,10 @@ board = esp12e framework = arduino upload_port = /dev/ttyUSB0 monitor_speed = 115200 -lib_deps = - ESP Async WebServer - knolleary/PubSubClient@^2.8 - me-no-dev/ESPAsyncTCP@^1.2.2 +lib_deps = + https://github.com/bertmelis/espMqttClient#unordered-acks +;build_flags = +; -DDEBUG +; -DDEBUG_ESP_SSL +; -DDEBUG_ESP_PORT=Serial +build_type = release \ No newline at end of file diff --git a/platformio/relayctl/src/config.cpp b/platformio/relayctl/src/config.cpp index d143a7f..796adf0 100644 --- a/platformio/relayctl/src/config.cpp +++ b/platformio/relayctl/src/config.cpp @@ -1,7 +1,6 @@ #include #include #include "config.h" -#include "config.def.h" #include "logging.h" #define GET_DATA_CRC(data) \ @@ -10,7 +9,7 @@ namespace homekit::config { static const uint32_t magic = 0xdeadbeef; -static const uint32_t crc_table[16] = { +static const uint32_t crc_table[16] PROGMEM = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, @@ -20,15 +19,15 @@ static const uint32_t crc_table[16] = { static uint32_t eeprom_crc(const uint8_t* data, size_t len) { uint32_t crc = ~0L; for (size_t index = 0; index < len; index++) { - crc = crc_table[(crc ^ data[index]) & 0x0f] ^ (crc >> 4); - crc = crc_table[(crc ^ (data[index] >> 4)) & 0x0f] ^ (crc >> 4); + crc = pgm_read_word(&crc_table[(crc ^ data[index]) & 0x0f]) ^ (crc >> 4); + crc = pgm_read_word(&crc_table[(crc ^ (data[index] >> 4)) & 0x0f]) ^ (crc >> 4); crc = ~crc; } return crc; } ConfigData read() { - ConfigData data {0}; + ConfigData data; EEPROM.begin(sizeof(ConfigData)); EEPROM.get(0, data); EEPROM.end(); @@ -40,25 +39,22 @@ ConfigData read() { return data; } -bool write(ConfigData& data) { +void write(ConfigData& data) { EEPROM.begin(sizeof(ConfigData)); data.magic = magic; data.crc = GET_DATA_CRC(data); EEPROM.put(0, data); - return EEPROM.end(); + EEPROM.end(); } -bool erase() { +void erase() { ConfigData data; - return erase(data); + erase(data); } -bool erase(ConfigData& data) { +void erase(ConfigData& data) { bzero(reinterpret_cast(&data), sizeof(data)); - data.magic = magic; - EEPROM.begin(sizeof(data)); - EEPROM.put(0, data); - return EEPROM.end(); + write(data); } bool isValid(ConfigData& data) { @@ -69,11 +65,11 @@ bool isDirty(ConfigData& data) { return data.magic != magic; } -char* ConfigData::escapeNodeId(char* buf, size_t len) { +char* ConfigData::escapeHomeId(char* buf, size_t len) { if (len < 32) return nullptr; - size_t id_len = strlen(node_id); - char* c = node_id; + size_t id_len = strlen(home_id); + char* c = home_id; char* dst = buf; for (size_t i = 0; i < id_len; i++) { if (*c == '"') diff --git a/platformio/relayctl/src/config.def.h.example b/platformio/relayctl/src/config.def.h.example index 13ddd46..dd3d89b 100644 --- a/platformio/relayctl/src/config.def.h.example +++ b/platformio/relayctl/src/config.def.h.example @@ -18,15 +18,15 @@ 0x8a, 0xb0, 0xc9, 0x9c, 0xbb \ }; -#define DEFAULT_NODE_ID "relay-node" +#define DEFAULT_HOME_ID "relay-node" #define FLASH_BUTTON_PIN 0 #define ESP_LED_PIN 2 #define BOARD_LED_PIN 16 +#define RELAY_PIN 5 // 12 bytes string -#define SECRET "" - -#define DEBUG +#define HOME_SECRET_SIZE 12 +#define HOME_SECRET "" #define ARRAY_SIZE(X) sizeof((X))/sizeof((X)[0]) \ No newline at end of file diff --git a/platformio/relayctl/src/config.h b/platformio/relayctl/src/config.h index 58a5f25..562b61a 100644 --- a/platformio/relayctl/src/config.h +++ b/platformio/relayctl/src/config.h @@ -14,20 +14,20 @@ struct ConfigData { // helpers uint32_t crc = 0; uint32_t magic = 0; - char node_id[16] = {0}; + char home_id[16] = {0}; char wifi_ssid[32] = {0}; char wifi_psk[63] = {0}; ConfigFlags flags {0}; // helper methods - char* escapeNodeId(char* buf, size_t len); + char* escapeHomeId(char* buf, size_t len); } __attribute__((packed)); ConfigData read(); -bool write(ConfigData& data); -bool erase(); -bool erase(ConfigData& data); +void write(ConfigData& data); +void erase(); +void erase(ConfigData& data); bool isValid(ConfigData& data); bool isDirty(ConfigData& data); diff --git a/platformio/relayctl/src/http_server.cpp b/platformio/relayctl/src/http_server.cpp index 0899231..e1e9c10 100644 --- a/platformio/relayctl/src/http_server.cpp +++ b/platformio/relayctl/src/http_server.cpp @@ -1,21 +1,26 @@ #include #include +#include "static.h" #include "http_server.h" #include "config.h" -#include "wifi.h" #include "config.def.h" #include "logging.h" +#include "util.h" +#include "led.h" namespace homekit { -static const char CONTENT_TYPE_HTML[] = "text/html; charset=utf-8"; -static const char CONTENT_TYPE_CSS[] = "text/css"; -static const char CONTENT_TYPE_JS[] = "application/javascript"; -static const char CONTENT_TYPE_JSON[] = "application/json"; -static const char CONTENT_TYPE_FAVICON[] = "image/x-icon"; +using files::StaticFile; -static const char JSON_STATUS_FMT[] = "{\"node_id\":\"%s\"" +static const char CONTENT_TYPE_HTML[] PROGMEM = "text/html; charset=utf-8"; +static const char CONTENT_TYPE_CSS[] PROGMEM = "text/css"; +static const char CONTENT_TYPE_JS[] PROGMEM = "application/javascript"; +static const char CONTENT_TYPE_JSON[] PROGMEM = "application/json"; +static const char CONTENT_TYPE_FAVICON[] PROGMEM = "image/x-icon"; + +static const char JSON_UPDATE_FMT[] PROGMEM = "{\"result\":%d}"; +static const char JSON_STATUS_FMT[] PROGMEM = "{\"home_id\":\"%s\"" #ifdef DEBUG ",\"configured\":%d" ",\"crc\":%u" @@ -25,38 +30,41 @@ static const char JSON_STATUS_FMT[] = "{\"node_id\":\"%s\"" "}"; static const size_t JSON_BUF_SIZE = 192; -static const char NODE_ID_ERROR[] = "?"; - -static const char FIELD_NODE_ID[] = "nid"; -static const char FIELD_SSID[] = "ssid"; -static const char FIELD_PSK[] = "psk"; +static const char JSON_SCAN_FIRST_LIST[] PROGMEM = "{\"list\":["; -static const char MSG_IS_INVALID[] = " is invalid"; -static const char MSG_IS_MISSING[] = " is missing"; +static const char MSG_IS_INVALID[] PROGMEM = " is invalid"; +static const char MSG_IS_MISSING[] PROGMEM = " is missing"; -static const char GZIP[] = "gzip"; -static const char CONTENT_ENCODING[] = "Content-Encoding"; -static const char NOT_FOUND[] = "Not Found"; +static const char GZIP[] PROGMEM = "gzip"; +static const char CONTENT_ENCODING[] PROGMEM = "Content-Encoding"; +static const char NOT_FOUND[] PROGMEM = "Not Found"; -static void do_restart() { - ESP.restart(); -} +static const char ROUTE_STYLE_CSS[] PROGMEM = "/style.css"; +static const char ROUTE_APP_JS[] PROGMEM = "/app.js"; +static const char ROUTE_MD5_JS[] PROGMEM = "/md5.js"; +static const char ROUTE_FAVICON_ICO[] PROGMEM = "/favicon.ico"; +static const char ROUTE_STATUS[] PROGMEM = "/status"; +static const char ROUTE_SCAN[] PROGMEM = "/scan"; +static const char ROUTE_RESET[] PROGMEM = "/reset"; +// #ifdef DEBUG +static const char ROUTE_HEAP[] PROGMEM = "/heap"; +// #endif +static const char ROUTE_UPDATE[] PROGMEM = "/update"; void HttpServer::start() { - _server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest* req) { sendGzip(req, files::style_css, CONTENT_TYPE_CSS); }); - _server.on("/app.js", HTTP_GET, [](AsyncWebServerRequest* req) { sendGzip(req, files::app_js, CONTENT_TYPE_JS); }); - _server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest* req) { sendGzip(req, files::favicon_ico, CONTENT_TYPE_FAVICON); }); - _server.on("/", HTTP_GET, [&](AsyncWebServerRequest* req) { sendGzip(req, files::index_html, CONTENT_TYPE_HTML); }); + server.on(FPSTR(ROUTE_STYLE_CSS), HTTP_GET, [&]() { sendGzip(files::style_css, CONTENT_TYPE_CSS); }); + server.on(FPSTR(ROUTE_APP_JS), HTTP_GET, [&]() { sendGzip(files::app_js, CONTENT_TYPE_JS); }); + server.on(FPSTR(ROUTE_MD5_JS), HTTP_GET, [&]() { sendGzip(files::md5_js, CONTENT_TYPE_JS); }); + server.on(FPSTR(ROUTE_FAVICON_ICO), HTTP_GET, [&]() { sendGzip(files::favicon_ico, CONTENT_TYPE_FAVICON); }); - _server.on("/status", HTTP_GET, [](AsyncWebServerRequest* req) { + server.on("/", HTTP_GET, [&]() { sendGzip(files::index_html, CONTENT_TYPE_HTML); }); + server.on(FPSTR(ROUTE_STATUS), HTTP_GET, [&]() { char json_buf[JSON_BUF_SIZE]; auto cfg = config::read(); - char *ssid, *psk; - wifi::getConfig(cfg, &ssid, &psk, nullptr); if (!isValid(cfg) || !cfg.flags.node_configured) { - sprintf(json_buf, JSON_STATUS_FMT - , DEFAULT_NODE_ID + sprintf_P(json_buf, JSON_STATUS_FMT + , DEFAULT_HOME_ID #ifdef DEBUG , 0 , cfg.crc @@ -65,10 +73,10 @@ void HttpServer::start() { #endif ); } else { - char escaped_node_id[32]; - char *escaped_node_id_res = cfg.escapeNodeId(escaped_node_id, 32); - sprintf(json_buf, JSON_STATUS_FMT - , escaped_node_id_res == nullptr ? NODE_ID_ERROR : escaped_node_id + char escaped_home_id[32]; + char *escaped_home_id_res = cfg.escapeHomeId(escaped_home_id, 32); + sprintf_P(json_buf, JSON_STATUS_FMT + , escaped_home_id_res == nullptr ? "?" : escaped_home_id #ifdef DEBUG , 1 , cfg.crc @@ -77,58 +85,54 @@ void HttpServer::start() { #endif ); } - req->send(200, CONTENT_TYPE_JSON, json_buf); + server.send(200, FPSTR(CONTENT_TYPE_JSON), json_buf); }); - - _server.on("/status", HTTP_POST, [&](AsyncWebServerRequest* req) { + server.on(FPSTR(ROUTE_STATUS), HTTP_POST, [&]() { auto cfg = config::read(); - char *s; + String s; - if (!handleInputStr(req, FIELD_SSID, 32, &s)) return; - strncpy(cfg.wifi_ssid, s, 32); + if (!getInputParam("ssid", 32, s)) return; + strncpy(cfg.wifi_ssid, s.c_str(), 32); PRINTF("saving ssid: %s\n", cfg.wifi_ssid); - if (!handleInputStr(req, FIELD_PSK, 63, &s)) return; - strncpy(cfg.wifi_psk, s, 63); + if (!getInputParam("psk", 63, s)) return; + strncpy(cfg.wifi_psk, s.c_str(), 63); PRINTF("saving psk: %s\n", cfg.wifi_psk); - if (!handleInputStr(req, FIELD_NODE_ID, 16, &s)) return; - strcpy(cfg.node_id, s); - PRINTF("saving node id: %s\n", cfg.node_id); + if (!getInputParam("hid", 16, s)) return; + strcpy(cfg.home_id, s.c_str()); + PRINTF("saving home id: %s\n", cfg.home_id); cfg.flags.node_configured = 1; cfg.flags.wifi_configured = 1; - if (!config::write(cfg)) { - PRINTLN("eeprom write error"); - return sendError(req, "eeprom error"); - } + config::write(cfg); - restartTimer.once(0, do_restart); + restartTimer.once(0, restart); }); - _server.on("/reset", HTTP_POST, [&](AsyncWebServerRequest* req) { + server.on(FPSTR(ROUTE_RESET), HTTP_POST, [&]() { config::erase(); - restartTimer.once(0, do_restart); + restartTimer.once(1, restart); }); - _server.on("/heap", HTTP_GET, [](AsyncWebServerRequest* req) { - req->send(200, CONTENT_TYPE_HTML, String(ESP.getFreeHeap())); + server.on(FPSTR(ROUTE_HEAP), HTTP_GET, [&]() { + server.send(200, FPSTR(CONTENT_TYPE_HTML), String(ESP.getFreeHeap())); }); - _server.on("/scan", HTTP_GET, [&](AsyncWebServerRequest* req) { - int i = 0; + server.on(FPSTR(ROUTE_SCAN), HTTP_GET, [&]() { + size_t i = 0; size_t len; const char* ssid; bool enough = false; - bzero(reinterpret_cast(buf1k), ARRAY_SIZE(buf1k)); - char* cur = buf1k; + bzero(reinterpret_cast(scanBuf), scanBufSize); + char* cur = scanBuf; - strncpy(cur, "{\"list\":[", ARRAY_SIZE(buf1k)); + strncpy_P(cur, JSON_SCAN_FIRST_LIST, scanBufSize); cur += 9; - for (auto& res: *_scanResults) { + for (auto& res: *scanResults) { ssid = res.ssid.c_str(); len = res.ssid.length(); @@ -151,10 +155,10 @@ void HttpServer::start() { // close array *cur++ = ']'; - if (cur - buf1k >= ARRAY_SIZE(buf1k)-40) + if ((size_t)(cur - scanBuf) >= (size_t) ARRAY_SIZE(scanBuf) - 40) enough = true; - if (i < _scanResults->size()-1 || enough) + if (i < scanResults->size() - 1 || enough) *cur++ = ','; if (enough) @@ -167,82 +171,108 @@ void HttpServer::start() { *cur++ = '}'; *cur++ = '\0'; - req->send(200, CONTENT_TYPE_JSON, buf1k); + server.send(200, FPSTR(CONTENT_TYPE_JSON), scanBuf); }); - _server.on("/update", HTTP_POST, [&](AsyncWebServerRequest* req) { + server.on(FPSTR(ROUTE_UPDATE), HTTP_POST, [&]() { char json_buf[16]; - bool should_reboot = !Update.hasError(); + bool should_reboot = !Update.hasError() && !ota.invalidMd5; + Update.clearError(); + + sprintf_P(json_buf, JSON_UPDATE_FMT, should_reboot ? 1 : 0); - sprintf(json_buf, "{\"result\":%d}", should_reboot ? 1 : 0); + server.send(200, FPSTR(CONTENT_TYPE_JSON), json_buf); - auto resp = req->beginResponse(200, CONTENT_TYPE_JSON, json_buf); - req->send(resp); + if (should_reboot) + restartTimer.once(1, restart); + }, [&]() { + HTTPUpload& upload = server.upload(); - if (should_reboot) restartTimer.once(1, do_restart); - }, [&](AsyncWebServerRequest *req, const String& filename, size_t index, uint8_t *data, size_t len, bool final) { - if (!index) { - PRINTF("update start: %s\n", filename.c_str()); - Update.runAsync(true); - if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) + if (upload.status == UPLOAD_FILE_START) { + ota.clean(); + + String s; + if (!getInputParam("md5", 0, s)) { + ota.invalidMd5 = true; + PRINTLN("http/ota: md5 not found"); + return; + } + + if (!Update.setMD5(s.c_str())) { + ota.invalidMd5 = true; + PRINTLN("http/ota: setMD5() failed"); + return; + } + + Serial.printf("http/ota: starting, filename=%s\n", upload.filename.c_str()); + if (!Update.begin(otaGetMaxUpdateSize())) { +#ifdef DEBUG Update.printError(Serial); - } +#endif + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (!Update.isRunning()) + return; - if (!Update.hasError() && len) { - if (Update.write(data, len) != len) { + PRINTF("http/ota: writing %ul\n", upload.currentSize); + esp_led.blink(1, 1); + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { +#ifdef DEBUG Update.printError(Serial); +#endif } - } + } else if (upload.status == UPLOAD_FILE_END) { + if (!Update.isRunning()) + return; - if (final) { // if the final flag is set then this is the last frame of data if (Update.end(true)) { - PRINTF("update success: %uB\n", index+len); + PRINTF("http/ota: ok, total size %ul\n", upload.totalSize); } else { +#ifdef DEBUG Update.printError(Serial); +#endif } } }); - _server.onNotFound([](AsyncWebServerRequest* req) { - req->send(404, CONTENT_TYPE_HTML, NOT_FOUND); + server.onNotFound([&]() { + server.send(404, FPSTR(CONTENT_TYPE_HTML), NOT_FOUND); }); - _server.begin(); + server.begin(); } -void HttpServer::sendGzip(AsyncWebServerRequest* req, StaticFile file, const char* content_type) { - auto resp = req->beginResponse_P(200, content_type, file.content, file.size); - resp->addHeader(CONTENT_ENCODING, GZIP); - req->send(resp); +void HttpServer::loop() { + server.handleClient(); } -void HttpServer::sendError(AsyncWebServerRequest* req, const String& message) { +void HttpServer::sendGzip(const StaticFile& file, PGM_P content_type) { + server.sendHeader(FPSTR(CONTENT_ENCODING), FPSTR(GZIP)); + server.send_P(200, content_type, (const char*)file.content, file.size); +} + +void HttpServer::sendError(const String& message) { char buf[32]; - if (snprintf(buf, 32, "error: %s", message.c_str()) == 32) + if (snprintf_P(buf, 32, PSTR("error: %s"), message.c_str()) == 32) buf[31] = '\0'; - req->send(400, CONTENT_TYPE_HTML, buf); + server.send(400, FPSTR(CONTENT_TYPE_HTML), buf); } -bool HttpServer::handleInputStr(AsyncWebServerRequest *req, - const char *field_name, - size_t max_len, - char **dst) { - const char* s; - size_t len; - - if (!req->hasParam(field_name, true)) { - sendError(req, String(field_name) + String(MSG_IS_MISSING)); +bool HttpServer::getInputParam(const char *field_name, + size_t max_len, + String& dst) { + if (!server.hasArg(field_name)) { + sendError(String(field_name) + String(MSG_IS_MISSING)); return false; } - s = req->getParam(field_name, true)->value().c_str(); - len = strlen(s); - if (!len || len > max_len) { - sendError(req, String(FIELD_NODE_ID) + String(MSG_IS_INVALID)); + String field = server.arg(field_name); + if (!field.length() || (max_len != 0 && field.length() > max_len)) { + sendError(String(field_name) + String(MSG_IS_INVALID)); return false; } - *dst = (char*)s; + dst = field; return true; } diff --git a/platformio/relayctl/src/http_server.h b/platformio/relayctl/src/http_server.h index c7e3d9c..35f7c08 100644 --- a/platformio/relayctl/src/http_server.h +++ b/platformio/relayctl/src/http_server.h @@ -1,37 +1,56 @@ #pragma once - +#include +#include #include #include -#include #include -#include -#include "static.h" #include "config.h" #include "wifi.h" +#include "static.h" namespace homekit { +struct OTAStatus { + bool invalidMd5; + + OTAStatus() : invalidMd5(false) {} + + inline void clean() { + invalidMd5 = false; + } +}; + using files::StaticFile; class HttpServer { private: - AsyncWebServer _server; + ESP8266WebServer server; Ticker restartTimer; - std::shared_ptr> _scanResults; - char buf1k[1024]; + std::shared_ptr> scanResults; + OTAStatus ota; - static void sendGzip(AsyncWebServerRequest* req, StaticFile file, const char* content_type); - static void sendError(AsyncWebServerRequest* req, const String& message); + char* scanBuf; + size_t scanBufSize; - static bool handleInputStr(AsyncWebServerRequest* req, const char* field_name, size_t max_len, char** dst); - // static bool handle_input_addr(AsyncWebServerRequest* req, const char* field_name, ConfigIPv4Addr* addr_dst); + void sendGzip(const StaticFile& file, PGM_P content_type); + void sendError(const String& message); + + bool getInputParam(const char* field_name, size_t max_len, String& dst); public: explicit HttpServer(std::shared_ptr> scanResults) - : _server(80) - , _scanResults(std::move(scanResults)) {}; + : server(80) + , scanResults(std::move(scanResults)) + , scanBufSize(512) { + scanBuf = new char[scanBufSize]; + }; + + ~HttpServer() { + delete[] scanBuf; + } void start(); + void loop(); }; } \ No newline at end of file diff --git a/platformio/relayctl/src/led.cpp b/platformio/relayctl/src/led.cpp index eafc662..b6b0d81 100644 --- a/platformio/relayctl/src/led.cpp +++ b/platformio/relayctl/src/led.cpp @@ -1,20 +1,9 @@ #include "led.h" +#include "config.def.h" namespace homekit { -void Led::blink(uint8_t count, uint16_t delay_ms) const { - for (uint8_t i = 0; i < count; i++) { - on_off(delay_ms, i < count-1); - } -} - -void Led::on_off(uint16_t delay_ms, bool last_delay) const { - on(); - delay(delay_ms); - - off(); - if (last_delay) - delay(delay_ms); -} +Led board_led(BOARD_LED_PIN); +Led esp_led(ESP_LED_PIN); } \ No newline at end of file diff --git a/platformio/relayctl/src/led.h b/platformio/relayctl/src/led.h index ccb3f11..315523a 100644 --- a/platformio/relayctl/src/led.h +++ b/platformio/relayctl/src/led.h @@ -17,8 +17,23 @@ public: inline void off() const { digitalWrite(_pin, HIGH); } inline void on() const { digitalWrite(_pin, LOW); } - void on_off(uint16_t delay_ms, bool last_delay = false) const; - void blink(uint8_t count, uint16_t delay_ms) const; + void on_off(uint16_t delay_ms, bool last_delay = false) const { + on(); + delay(delay_ms); + + off(); + if (last_delay) + delay(delay_ms); + } + + void blink(uint8_t count, uint16_t delay_ms) const { + for (uint8_t i = 0; i < count; i++) { + on_off(delay_ms, i < count-1); + } + } }; +extern Led board_led; +extern Led esp_led; + } \ No newline at end of file diff --git a/platformio/relayctl/src/logging.cpp b/platformio/relayctl/src/logging.cpp deleted file mode 100644 index a929d81..0000000 --- a/platformio/relayctl/src/logging.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include "logging.h" - -#ifdef DEBUG -namespace homekit { - -void hexdump(const void* data, size_t size) { - char ascii[17]; - size_t i, j; - ascii[16] = '\0'; - for (i = 0; i < size; ++i) { - printf("%02X ", ((unsigned char*)data)[i]); - if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') { - ascii[i % 16] = ((unsigned char*)data)[i]; - } else { - ascii[i % 16] = '.'; - } - if ((i+1) % 8 == 0 || i+1 == size) { - printf(" "); - if ((i+1) % 16 == 0) { - printf("| %s \n", ascii); - } else if (i+1 == size) { - ascii[(i+1) % 16] = '\0'; - if ((i+1) % 16 <= 8) { - printf(" "); - } - for (j = (i+1) % 16; j < 16; ++j) { - printf(" "); - } - printf("| %s \n", ascii); - } - } - } -} - -} -#endif \ No newline at end of file diff --git a/platformio/relayctl/src/logging.h b/platformio/relayctl/src/logging.h index c31e98f..070f367 100644 --- a/platformio/relayctl/src/logging.h +++ b/platformio/relayctl/src/logging.h @@ -5,20 +5,14 @@ #ifdef DEBUG -namespace homekit { -void hexdump(const void* data, size_t size); -} - #define PRINTLN(s) Serial.println(s) #define PRINT(s) Serial.print(s) #define PRINTF(fmt, ...) Serial.printf(fmt, ##__VA_ARGS__) -#define HEXDUMP(data, size) homekit::hexdump((data), (size)); #else #define PRINTLN(s) #define PRINT(s) -#define PRINTF(a) -#define HEXDUMP(data, size) +#define PRINTF(...) #endif diff --git a/platformio/relayctl/src/main.cpp b/platformio/relayctl/src/main.cpp index 4a634d6..0c4d469 100644 --- a/platformio/relayctl/src/main.cpp +++ b/platformio/relayctl/src/main.cpp @@ -15,9 +15,6 @@ using namespace homekit; -static Led board_led(BOARD_LED_PIN); -static Led esp_led(ESP_LED_PIN); - enum class WorkingMode { RECOVERY, // AP mode, http server with configuration NORMAL, // MQTT client @@ -39,13 +36,13 @@ static WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler; static Ticker wifiTimer; static StopWatch blinkStopWatch; -static DNSServer* dnsServer; +static DNSServer* dnsServer = nullptr; static void onWifiConnected(const WiFiEventStationModeGotIP& event); static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event); static void wifiConnect() { - char* ssid, *psk, *hostname; + const char *ssid, *psk, *hostname; auto cfg = config::read(); wifi::getConfig(cfg, &ssid, &psk, &hostname); @@ -54,16 +51,10 @@ static void wifiConnect() { wifi_state = WiFiConnectionState::WAITING; WiFi.mode(WIFI_STA); - WiFi.setHostname(hostname); + WiFi.hostname(hostname); WiFi.begin(ssid, psk); PRINT("connecting to wifi.."); - while (WiFi.status() != WL_CONNECTED) { - esp_led.blink(2, 50); - delay(1000); - PRINT('.'); - } - PRINT(' '); } static void wifiHotspot() { @@ -72,7 +63,7 @@ static void wifiHotspot() { auto scanResults = wifi::scan(); WiFi.mode(WIFI_AP); - WiFi.softAP(wifi::WIFI_AP_SSID); + WiFi.softAP(wifi::AP_SSID); dnsServer = new DNSServer(); dnsServer->start(53, "*", WiFi.softAPIP()); @@ -82,6 +73,8 @@ static void wifiHotspot() { } void setup() { + WiFi.disconnect(); + #ifdef DEBUG Serial.begin(115200); #endif @@ -119,6 +112,13 @@ void setup() { void loop() { if (working_mode == WorkingMode::NORMAL) { + if (wifi_state == WiFiConnectionState::WAITING) { + PRINT("."); + esp_led.blink(2, 50); + delay(1000); + return; + } + if (wifi_state == WiFiConnectionState::JUST_CONNECTED) { board_led.blink(3, 300); wifi_state = WiFiConnectionState::CONNECTED; @@ -131,28 +131,29 @@ void loop() { } auto mqtt = (mqtt::MQTT*)service; - if (static_cast(wifi_state) >= 1 - && mqtt != nullptr) { - if (!mqtt->loop()) { - PRINTLN("mqtt::loop() returned false"); - // FIXME do something here - } + if (static_cast(wifi_state) >= 1 && mqtt != nullptr) { + mqtt->loop(); - if (mqtt->statStopWatch.elapsed(10000)) { + if (mqtt->ota.readyToRestart) { + mqtt->disconnect(); + } else if (mqtt->statStopWatch.elapsed(10000)) { mqtt->sendStat(); } // periodically blink board led if (blinkStopWatch.elapsed(5000)) { + // PRINTF("free heap: %d\n", ESP.getFreeHeap()); board_led.blink(1, 10); blinkStopWatch.save(); } } - - delay(500); } else { if (dnsServer != nullptr) dnsServer->processNextRequest(); + + auto httpServer = (HttpServer*)service; + if (httpServer != nullptr) + httpServer->loop(); } } diff --git a/platformio/relayctl/src/mqtt.cpp b/platformio/relayctl/src/mqtt.cpp index cca215b..e1f70c3 100644 --- a/platformio/relayctl/src/mqtt.cpp +++ b/platformio/relayctl/src/mqtt.cpp @@ -1,9 +1,13 @@ +#include #include "mqtt.h" #include "logging.h" #include "wifi.h" #include "config.def.h" #include "relay.h" #include "config.h" +#include "static.h" +#include "util.h" +#include "led.h" namespace homekit::mqtt { @@ -13,26 +17,124 @@ static const uint16_t MQTT_PORT = DEFAULT_MQTT_PORT; static const char MQTT_USERNAME[] = DEFAULT_MQTT_USERNAME; static const char MQTT_PASSWORD[] = DEFAULT_MQTT_PASSWORD; static const char MQTT_CLIENT_ID[] = DEFAULT_MQTT_CLIENT_ID; +static const char MQTT_SECRET[HOME_SECRET_SIZE+1] = HOME_SECRET; -static const char MQTT_SECRET[] = SECRET; -static const char TOPIC_RELAY_POWER[] = "relay/power"; static const char TOPIC_STAT[] = "stat"; -static const char TOPIC_STAT1[] = "stat1"; -static const char TOPIC_ADMIN[] = "admin"; -static const char TOPIC_RELAY[] = "relay"; +static const char TOPIC_INITIAL_STAT[] = "stat1"; +static const char TOPIC_OTA_RESPONSE[] = "otares"; +static const char TOPIC_RELAY_POWER[] = "power"; +static const char TOPIC_ADMIN_OTA[] = "admin/ota"; +static const uint16_t MQTT_KEEPALIVE = 30; +enum class IncomingMessage { + UNKNOWN, + RELAY_POWER, + OTA +}; -using namespace homekit; +using namespace espMqttClientTypes; + +#define MD5_SIZE 16 + +MQTT::MQTT() { + auto cfg = config::read(); + homeId = String(cfg.flags.node_configured ? cfg.home_id : wifi::HOME_ID); -MQTT::MQTT() : client(wifiClient) { randomSeed(micros()); - wifiClient.setFingerprint(MQTT_CA_FINGERPRINT); + client.onConnect([&](bool sessionPresent) { + PRINTLN("mqtt: connected"); - client.setServer(MQTT_SERVER, MQTT_PORT); - client.setCallback([&](char* topic, byte* payload, unsigned int length) { - this->callback(topic, payload, length); + sendInitialStat(); + + subscribe(TOPIC_RELAY_POWER, 1); + subscribe(TOPIC_ADMIN_OTA); + }); + + client.onDisconnect([&](DisconnectReason reason) { + PRINTF("mqtt: disconnected, reason=%d\n", static_cast(reason)); +#ifdef DEBUG + if (reason == DisconnectReason::TLS_BAD_FINGERPRINT) + PRINTLN("reason: bad fingerprint"); +#endif + + if (ota.started()) { + PRINTLN("mqtt: update was in progress, canceling.."); + ota.clean(); + Update.end(); + Update.clearError(); + } + + if (ota.readyToRestart) { + restartTimer.once(1, restart); + } else { + reconnectTimer.once(2, [&]() { + reconnect(); + }); + } + }); + + client.onSubscribe([&](uint16_t packetId, const SubscribeReturncode* returncodes, size_t len) { + PRINTF("mqtt: subscribe ack, packet_id=%d\n", packetId); + for (size_t i = 0; i < len; i++) { + PRINTF(" return code: %u\n", static_cast(*(returncodes+i))); + } + }); + + client.onUnsubscribe([&](uint16_t packetId) { + PRINTF("mqtt: unsubscribe ack, packet_id=%d\n", packetId); + }); + + client.onMessage([&](const MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { + PRINTF("mqtt: message received, topic=%s, qos=%d, dup=%d, retain=%d, len=%ul, index=%ul, total=%ul\n", + topic, properties.qos, (int)properties.dup, (int)properties.retain, len, index, total); + + IncomingMessage msgType = IncomingMessage::UNKNOWN; + + const char *ptr = topic + homeId.length() + 10; + String relevantTopic(ptr); + + if (relevantTopic == TOPIC_RELAY_POWER) + msgType = IncomingMessage::RELAY_POWER; + else if (relevantTopic == TOPIC_ADMIN_OTA) + msgType = IncomingMessage::OTA; + + if (len != total && msgType != IncomingMessage::OTA) { + PRINTLN("mqtt: received partial message, not supported"); + return; + } + + switch (msgType) { + case IncomingMessage::RELAY_POWER: + handleRelayPowerPayload(payload, total); + break; + + case IncomingMessage::OTA: + if (ota.finished) + break; + handleAdminOtaPayload(properties.packetId, payload, len, index, total); + break; + + case IncomingMessage::UNKNOWN: + PRINTF("error: invalid topic %s\n", topic); + break; + } }); + + client.onPublish([&](uint16_t packetId) { + PRINTF("mqtt: publish ack, packet_id=%d\n", packetId); + + if (ota.finished && packetId == ota.publishResultPacketId) { + ota.readyToRestart = true; + } + }); + + client.setServer(MQTT_SERVER, MQTT_PORT); + client.setClientId(MQTT_CLIENT_ID); + client.setCredentials(MQTT_USERNAME, MQTT_PASSWORD); + client.setCleanSession(true); + client.setFingerprint(MQTT_CA_FINGERPRINT); + client.setKeepAlive(MQTT_KEEPALIVE); } void MQTT::connect() { @@ -40,127 +142,96 @@ void MQTT::connect() { } void MQTT::reconnect() { - char buf[128] {0}; - if (client.connected()) { PRINTLN("warning: already connected"); return; } - - // Attempt to connect - if (client.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) { - PRINTLN("mqtt: connected"); - - sendInitialStat(); - - subscribe(TOPIC_RELAY); - subscribe(TOPIC_ADMIN); - } else { - PRINTF("mqtt: failed to connect, rc=%d\n", client.state()); - wifiClient.getLastSSLError(buf, sizeof(buf)); - PRINTF("SSL error: %s\n", buf); - - reconnectTimer.once(2, [&]() { - reconnect(); - }); - } + client.connect(); } void MQTT::disconnect() { // TODO test how this works??? reconnectTimer.detach(); client.disconnect(); - wifiClient.stop(); } -bool MQTT::loop() { - return client.loop(); +uint16_t MQTT::publish(const String &topic, uint8_t *payload, size_t length) { + String fullTopic = "hk/" + homeId + "/relay/" + topic; + return client.publish(fullTopic.c_str(), 1, false, payload, length); } -bool MQTT::publish(const char* topic, uint8_t *payload, size_t length) { - char full_topic[40] {0}; - strcpy(full_topic, "/hk/"); - strcat(full_topic, wifi::NODE_ID); - strcat(full_topic, "/"); - strcat(full_topic, topic); - return client.publish(full_topic, payload, length); +void MQTT::loop() { + client.loop(); } -bool MQTT::subscribe(const char *topic) { - char full_topic[40] {0}; - strcpy(full_topic, "/hk/"); - strcat(full_topic, wifi::NODE_ID); - strcat(full_topic, "/"); - strcat(full_topic, topic); - strcat(full_topic, "/#"); - bool res = client.subscribe(full_topic, 1); - if (!res) - PRINTF("error: failed to subscribe to %s\n", full_topic); - return res; +uint16_t MQTT::subscribe(const String &topic, uint8_t qos) { + String fullTopic = "hk/" + homeId + "/relay/" + topic; + PRINTF("mqtt: subscribing to %s...\n", fullTopic.c_str()); + + uint16_t packetId = client.subscribe(fullTopic.c_str(), qos); + if (!packetId) + PRINTF("error: failed to subscribe to %s\n", fullTopic.c_str()); + return packetId; } void MQTT::sendInitialStat() { auto cfg = config::read(); - InitialStatPayload stat { - .ip = wifi::getIPAsInteger(), - .fw_version = FW_VERSION, - .rssi = wifi::getRSSI(), - .free_heap = ESP.getFreeHeap(), - .flags = StatFlags { - .state = static_cast(relay::getState() ? 1 : 0), - .config_changed_value_present = 1, - .config_changed = static_cast(cfg.flags.node_configured || cfg.flags.wifi_configured ? 1 : 0) - } + InitialStatPayload stat{ + .ip = wifi::getIPAsInteger(), + .fw_version = FW_VERSION, + .rssi = wifi::getRSSI(), + .free_heap = ESP.getFreeHeap(), + .flags = StatFlags{ + .state = static_cast(relay::getState() ? 1 : 0), + .config_changed_value_present = 1, + .config_changed = static_cast(cfg.flags.node_configured || + cfg.flags.wifi_configured ? 1 : 0) + } }; - publish(TOPIC_STAT1, reinterpret_cast(&stat), sizeof(stat)); + publish(TOPIC_INITIAL_STAT, reinterpret_cast(&stat), sizeof(stat)); statStopWatch.save(); } void MQTT::sendStat() { - StatPayload stat { + StatPayload stat{ .rssi = wifi::getRSSI(), .free_heap = ESP.getFreeHeap(), - .flags = StatFlags { + .flags = StatFlags{ .state = static_cast(relay::getState() ? 1 : 0), .config_changed_value_present = 0, .config_changed = 0 } }; - - PRINTF("free heap: %d\n", ESP.getFreeHeap()); - publish(TOPIC_STAT, reinterpret_cast(&stat), sizeof(stat)); statStopWatch.save(); } -void MQTT::callback(char* topic, uint8_t* payload, uint32_t length) { - const size_t bufsize = 16; - char relevant_topic[bufsize]; - strncpy(relevant_topic, topic+strlen(wifi::NODE_ID)+5, bufsize); - - if (strncmp(TOPIC_RELAY_POWER, relevant_topic, bufsize) == 0) { - handleRelayPowerPayload(payload, length); - } else { - PRINTF("error: invalid topic %s\n", topic); - } +uint16_t MQTT::sendOtaResponse(OTAResult status, uint8_t error_code) { + OTAResponse resp{ + .status = status, + .error_code = error_code + }; + return publish(TOPIC_OTA_RESPONSE, reinterpret_cast(&resp), sizeof(resp)); } -void MQTT::handleRelayPowerPayload(uint8_t *payload, uint32_t length) { +void MQTT::handleRelayPowerPayload(const uint8_t *payload, uint32_t length) { if (length != sizeof(PowerPayload)) { PRINTF("error: size of payload (%ul) does not match expected (%ul)\n", length, sizeof(PowerPayload)); return; } - auto pd = reinterpret_cast(payload); + auto pd = reinterpret_cast(payload); if (strncmp(pd->secret, MQTT_SECRET, sizeof(pd->secret)) != 0) { PRINTLN("error: invalid secret"); return; } if (pd->state == 1) { + PRINTLN("mqtt: turning relay on"); relay::setOn(); } else if (pd->state == 0) { + PRINTLN("mqtt: turning relay off"); relay::setOff(); } else { PRINTLN("error: unexpected state value"); @@ -169,4 +240,114 @@ void MQTT::handleRelayPowerPayload(uint8_t *payload, uint32_t length) { sendStat(); } +void MQTT::handleAdminOtaPayload(uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) { + char md5[33]; + char* md5Ptr = md5; + + if (index != 0 && ota.dataPacketId != packetId) { + PRINTLN("mqtt/ota: non-matching packet id"); + return; + } + + Update.runAsync(true); + + if (index == 0) { + if (length < HOME_SECRET_SIZE + MD5_SIZE) { + PRINTLN("mqtt/ota: failed to check secret, first packet size is too small"); + return; + } + + if (memcmp((const char*)payload, HOME_SECRET, HOME_SECRET_SIZE) != 0) { + PRINTLN("mqtt/ota: invalid secret"); + return; + } + + PRINTF("mqtt/ota: starting update, total=%ul\n", total-HOME_SECRET_SIZE); + for (int i = 0; i < MD5_SIZE; i++) { + md5Ptr += sprintf(md5Ptr, "%02x", *((unsigned char*)(payload+HOME_SECRET_SIZE+i))); + } + md5[32] = '\0'; + PRINTF("mqtt/ota: md5 is %s\n", md5); + PRINTF("mqtt/ota: first packet is %ul bytes length\n", length); + + md5[32] = '\0'; + + if (Update.isRunning()) { + Update.end(); + Update.clearError(); + } + + if (!Update.setMD5(md5)) { + PRINTLN("mqtt/ota: setMD5 failed"); + return; + } + + ota.dataPacketId = packetId; + + if (!Update.begin(total - HOME_SECRET_SIZE - MD5_SIZE)) { + ota.clean(); +#ifdef DEBUG + Update.printError(Serial); +#endif + sendOtaResponse(OTAResult::UPDATE_ERROR, Update.getError()); + } + + ota.written = Update.write(const_cast(payload)+HOME_SECRET_SIZE + MD5_SIZE, length-HOME_SECRET_SIZE - MD5_SIZE); + ota.written += HOME_SECRET_SIZE + MD5_SIZE; + + esp_led.blink(1, 1); + PRINTF("mqtt/ota: updating %u/%u\n", ota.written, Update.size()); + + } else { + if (!Update.isRunning()) { + PRINTLN("mqtt/ota: update is not running"); + return; + } + + if (index == ota.written) { + size_t written; + if ((written = Update.write(const_cast(payload), length)) != length) { + PRINTF("mqtt/ota: error: tried to write %ul bytes, write() returned %ul\n", + length, written); + ota.clean(); + Update.end(); + Update.clearError(); + sendOtaResponse(OTAResult::WRITE_ERROR); + return; + } + ota.written += length; + + esp_led.blink(1, 1); + PRINTF("mqtt/ota: updating %u/%u\n", + ota.written - HOME_SECRET_SIZE - MD5_SIZE, + Update.size()); + } else { + PRINTF("mqtt/ota: position is invalid, expected %ul, got %ul\n", ota.written, index); + ota.clean(); + Update.end(); + Update.clearError(); + } + } + + if (Update.isFinished()) { + ota.dataPacketId = 0; + + if (Update.end()) { + ota.finished = true; + ota.publishResultPacketId = sendOtaResponse(OTAResult::OK); + PRINTF("mqtt/ota: ok, otares packet_id=%d\n", ota.publishResultPacketId); + } else { + ota.clean(); + + PRINTF("mqtt/ota: error: %u\n", Update.getError()); +#ifdef DEBUG + Update.printError(Serial); +#endif + Update.clearError(); + + sendOtaResponse(OTAResult::UPDATE_ERROR, Update.getError()); + } + } +} + } \ No newline at end of file diff --git a/platformio/relayctl/src/mqtt.h b/platformio/relayctl/src/mqtt.h index a769750..f5ffdab 100644 --- a/platformio/relayctl/src/mqtt.h +++ b/platformio/relayctl/src/mqtt.h @@ -1,30 +1,69 @@ #include -#include +#include #include #include "stopwatch.h" -namespace homekit::mqtt { +namespace homekit { namespace mqtt { + +enum class OTAResult: uint8_t { + OK = 0, + UPDATE_ERROR = 1, + WRITE_ERROR = 2, +}; + +struct OTAStatus { + uint16_t dataPacketId; + uint16_t publishResultPacketId; + bool finished; + bool readyToRestart; + size_t written; + + OTAStatus() + : dataPacketId(0) + , publishResultPacketId(0) + , finished(false) + , readyToRestart(false) + , written(0) + {} + + inline void clean() { + dataPacketId = 0; + publishResultPacketId = 0; + finished = false; + readyToRestart = false; + written = 0; + } + + inline bool started() const { + return dataPacketId != 0; + } +}; class MQTT { private: - WiFiClientSecure wifiClient; - PubSubClient client; + String homeId; + WiFiClientSecure httpsSecureClient; + espMqttClientSecure client; Ticker reconnectTimer; + Ticker restartTimer; + + void handleRelayPowerPayload(const uint8_t* payload, uint32_t length); + void handleAdminOtaPayload(uint16_t packetId, const uint8_t* payload, size_t length, size_t index, size_t total); - void callback(char* topic, uint8_t* payload, size_t length); - void handleRelayPowerPayload(uint8_t* payload, uint32_t length); - bool publish(const char* topic, uint8_t* payload, size_t length); - bool subscribe(const char* topic); + uint16_t publish(const String& topic, uint8_t* payload, size_t length); + uint16_t subscribe(const String& topic, uint8_t qos = 0); void sendInitialStat(); + uint16_t sendOtaResponse(OTAResult status, uint8_t error_code = 0); public: StopWatch statStopWatch; + OTAStatus ota; MQTT(); void connect(); void disconnect(); void reconnect(); - bool loop(); + void loop(); void sendStat(); }; @@ -54,4 +93,9 @@ struct PowerPayload { uint8_t state; } __attribute__((packed)); -} \ No newline at end of file +struct OTAResponse { + OTAResult status; + uint8_t error_code; +} __attribute__((packed)); + +} } \ No newline at end of file diff --git a/platformio/relayctl/src/relay.h b/platformio/relayctl/src/relay.h index a3519ac..ba7f5a6 100644 --- a/platformio/relayctl/src/relay.h +++ b/platformio/relayctl/src/relay.h @@ -3,14 +3,14 @@ #include #include "config.def.h" -namespace homekit::relay { +namespace homekit { namespace relay { inline void init() { pinMode(RELAY_PIN, OUTPUT); } inline bool getState() { - return digitalRead(RELAY_PIN) == 1; + return digitalRead(RELAY_PIN) == HIGH; } inline void setOn() { @@ -21,4 +21,4 @@ inline void setOff() { digitalWrite(RELAY_PIN, LOW); } -} \ No newline at end of file +} } \ No newline at end of file diff --git a/platformio/relayctl/src/static.cpp b/platformio/relayctl/src/static.cpp index 8e2c56d..0e60044 100644 --- a/platformio/relayctl/src/static.cpp +++ b/platformio/relayctl/src/static.cpp @@ -7,269 +7,443 @@ namespace homekit::files { static const uint8_t index_html_content[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x56, 0xdb, 0x8a, 0xdc, 0x38, - 0x10, 0x7d, 0x9f, 0xaf, 0x50, 0xf4, 0xb0, 0xcc, 0xc0, 0xb4, 0xbd, 0xd3, 0x81, 0x21, 0x6c, 0x6c, - 0xc3, 0x92, 0x0b, 0x04, 0xc2, 0x32, 0x4c, 0x13, 0x02, 0x79, 0x69, 0x64, 0xb9, 0xdc, 0x56, 0x5a, - 0x96, 0xb4, 0x56, 0xd9, 0x3d, 0xbd, 0x5f, 0x9f, 0x92, 0x6c, 0xf7, 0x65, 0xd3, 0x4c, 0x2e, 0x2f, - 0x6d, 0x57, 0xa9, 0xea, 0xd4, 0xa9, 0xa3, 0x92, 0xd5, 0xd9, 0x8b, 0xca, 0x4a, 0xdc, 0x3b, 0x60, - 0x0d, 0xb6, 0xba, 0xb8, 0xca, 0xc2, 0x83, 0x69, 0x61, 0x36, 0x39, 0x07, 0xc3, 0x83, 0x03, 0x44, - 0x45, 0x8f, 0x16, 0x50, 0x50, 0x0c, 0xba, 0x05, 0xfc, 0xdb, 0xab, 0x21, 0xe7, 0xd2, 0x1a, 0x04, - 0x83, 0x8b, 0x90, 0xcc, 0xd9, 0x64, 0xe5, 0x1c, 0xe1, 0x09, 0xd3, 0x00, 0xf2, 0x9a, 0xc9, 0x46, - 0x74, 0x1e, 0x30, 0xef, 0xb1, 0x5e, 0xbc, 0xe2, 0x33, 0x86, 0x11, 0x2d, 0xe4, 0x7c, 0x50, 0xb0, - 0x73, 0xb6, 0xc3, 0x93, 0xcc, 0x9d, 0xaa, 0xb0, 0xc9, 0x2b, 0x18, 0x94, 0x84, 0x45, 0x34, 0x6e, - 0x95, 0x51, 0xa8, 0x84, 0x5e, 0x78, 0x29, 0x34, 0xe4, 0x77, 0xb7, 0x2d, 0x39, 0xda, 0xbe, 0x3d, - 0xda, 0xe2, 0xe9, 0xcc, 0xee, 0x3d, 0x74, 0xd1, 0x10, 0x25, 0xd9, 0xc6, 0x86, 0xa2, 0xa8, 0x50, - 0x43, 0xf1, 0xc6, 0x9a, 0x5a, 0x6d, 0xfa, 0x4e, 0xa0, 0xb2, 0x26, 0x4b, 0x47, 0xe7, 0x55, 0xa6, - 0x95, 0xd9, 0xb2, 0x0e, 0x74, 0xce, 0x7d, 0x43, 0x6c, 0x64, 0x8f, 0x4c, 0x11, 0x21, 0xce, 0x9a, - 0x0e, 0xea, 0x9c, 0xa7, 0xb5, 0x18, 0x82, 0x9d, 0xd0, 0x0f, 0x67, 0xa1, 0xd3, 0x9c, 0xab, 0x56, - 0x6c, 0x20, 0x7d, 0x5a, 0xc4, 0xb8, 0x73, 0x08, 0xdc, 0x6b, 0xf0, 0x0d, 0x00, 0xce, 0xb1, 0x51, - 0x0c, 0xe9, 0xfd, 0x01, 0x2f, 0x86, 0x24, 0xc1, 0x43, 0x99, 0x5e, 0x76, 0xca, 0x21, 0xf3, 0x9d, - 0xa4, 0x15, 0xe1, 0x5c, 0xf2, 0x95, 0xdc, 0x59, 0x3a, 0xba, 0x69, 0x3d, 0x9d, 0xa4, 0x2f, 0x6d, - 0xb5, 0x67, 0xd6, 0x68, 0x2b, 0x2a, 0x2a, 0x4f, 0x92, 0xfc, 0xed, 0xdc, 0xf5, 0x4d, 0x40, 0xa8, - 0xd4, 0xc0, 0xa4, 0x16, 0xde, 0x53, 0xa9, 0xd0, 0x11, 0x2f, 0x56, 0x80, 0xa8, 0xcc, 0xc6, 0xb3, - 0xcc, 0x3b, 0x61, 0x98, 0xa2, 0x8c, 0x90, 0x47, 0xae, 0x35, 0x89, 0x02, 0x9a, 0x17, 0xd7, 0x93, - 0x9d, 0x24, 0xc9, 0x0d, 0x15, 0xa3, 0x28, 0xaa, 0x49, 0x40, 0xe7, 0x70, 0xa5, 0xb6, 0x72, 0x1b, - 0x4a, 0xd4, 0xb6, 0x6b, 0x19, 0x6d, 0x5c, 0x63, 0x09, 0xca, 0x59, 0x4f, 0xbd, 0x09, 0x19, 0x44, - 0x8c, 0xdd, 0x08, 0xec, 0xa9, 0xb9, 0x71, 0x4b, 0x0d, 0xe0, 0xce, 0x76, 0xdb, 0xb5, 0x9f, 0x28, - 0xfc, 0x8f, 0x60, 0x00, 0x9a, 0x39, 0x7c, 0x56, 0xef, 0x15, 0x5b, 0xad, 0x3e, 0xbc, 0xbd, 0x50, - 0x39, 0xc6, 0x29, 0xe3, 0x7a, 0x8c, 0x1a, 0x81, 0x06, 0x89, 0xb1, 0x0f, 0xef, 0x55, 0xb5, 0x1e, - 0xed, 0xb9, 0x64, 0x70, 0xf1, 0x43, 0x62, 0xaf, 0xf5, 0x38, 0x37, 0x21, 0xd1, 0xba, 0x40, 0x92, - 0x0d, 0x42, 0xf7, 0x14, 0xc8, 0x8b, 0x8f, 0x87, 0xae, 0xb3, 0x74, 0x5c, 0x0b, 0x0a, 0x8f, 0x70, - 0xe1, 0xed, 0x32, 0x8f, 0x53, 0xbe, 0x0f, 0xe4, 0xa6, 0x06, 0xab, 0x1f, 0x72, 0x8e, 0x2f, 0xd3, - 0x04, 0xb8, 0x29, 0x89, 0x1f, 0x98, 0x4c, 0xd4, 0x9d, 0xdf, 0x5e, 0x62, 0x1e, 0x3b, 0xad, 0x75, - 0xb5, 0x8e, 0xeb, 0x34, 0xdf, 0x1a, 0xcc, 0x86, 0x8e, 0x05, 0xbf, 0x7f, 0xc9, 0x59, 0xa5, 0x7c, - 0x18, 0xec, 0xea, 0x42, 0x71, 0xd9, 0x80, 0xdc, 0x96, 0xf6, 0x29, 0x4e, 0x64, 0x20, 0xcd, 0xc8, - 0x1d, 0xa7, 0x7a, 0x17, 0xa1, 0x8a, 0x33, 0x56, 0x87, 0xe8, 0x59, 0xc7, 0x39, 0x6c, 0x14, 0xfa, - 0x90, 0xc4, 0xc2, 0x2b, 0x73, 0x87, 0xc6, 0x23, 0xf2, 0x51, 0xad, 0xe7, 0x45, 0xfb, 0xc7, 0x56, - 0xc0, 0x7e, 0x62, 0x8b, 0x4f, 0x89, 0x85, 0x03, 0x73, 0x22, 0xd5, 0x49, 0xff, 0x77, 0xf7, 0x87, - 0x39, 0x0b, 0x7b, 0x3e, 0xcb, 0x64, 0x2e, 0x0f, 0xc0, 0xa9, 0x54, 0x53, 0xfd, 0xb2, 0x47, 0xa4, - 0x81, 0x18, 0xeb, 0xf8, 0xbe, 0x6c, 0x15, 0x1e, 0xc3, 0x66, 0x1d, 0x46, 0x77, 0xb1, 0x12, 0x03, - 0x30, 0x61, 0x2a, 0xf6, 0x08, 0xa5, 0xb5, 0x98, 0xa5, 0x63, 0x72, 0x00, 0x0b, 0xdc, 0x2f, 0xb6, - 0x3e, 0x1d, 0xc0, 0x4f, 0xae, 0x12, 0x08, 0xac, 0x56, 0x5d, 0xbb, 0x13, 0x1d, 0xb0, 0xeb, 0xa4, - 0x54, 0xe6, 0xe6, 0x77, 0x4f, 0x58, 0x1f, 0xd1, 0x38, 0x03, 0x23, 0x47, 0xe2, 0x6d, 0xaf, 0x51, - 0x39, 0xd1, 0x61, 0x24, 0xb2, 0xa0, 0x55, 0x31, 0xeb, 0x32, 0xc6, 0x3e, 0x7b, 0xfc, 0x2e, 0x6a, - 0x5e, 0x2b, 0xe2, 0x4d, 0x25, 0x25, 0x38, 0xfa, 0x0a, 0x07, 0xba, 0xb7, 0xe1, 0x27, 0xd9, 0xfc, - 0x37, 0x23, 0xc7, 0x88, 0x1f, 0x28, 0x79, 0x2e, 0xe0, 0x27, 0x17, 0xbe, 0x32, 0xbf, 0xa2, 0xdb, - 0x23, 0x10, 0x71, 0x36, 0x93, 0xff, 0x5d, 0xbd, 0xba, 0x80, 0xc2, 0x7f, 0x8a, 0xe3, 0x8c, 0xab, - 0xfc, 0x7a, 0xca, 0x8a, 0x14, 0x7e, 0x85, 0xf3, 0x07, 0x53, 0xdb, 0x67, 0x98, 0xbe, 0x5b, 0x3d, - 0xbc, 0x5a, 0xde, 0xdf, 0x2f, 0x4a, 0xe1, 0x69, 0xc2, 0xb2, 0xb2, 0xa0, 0x5b, 0x42, 0xec, 0x25, - 0x6a, 0xaa, 0x51, 0xdc, 0x1e, 0x47, 0x64, 0x58, 0x66, 0x65, 0x57, 0x5c, 0x3d, 0xd0, 0xae, 0x32, - 0x5b, 0xb3, 0x4c, 0x4c, 0xb7, 0x45, 0xb8, 0x6d, 0xfd, 0x5f, 0x69, 0xba, 0x51, 0x98, 0xc8, 0xe6, - 0xce, 0x25, 0xca, 0xa6, 0x8d, 0x6d, 0x61, 0x4b, 0x36, 0xf9, 0x52, 0x5e, 0x4c, 0x56, 0x96, 0x8a, - 0x82, 0x95, 0xfb, 0xef, 0x33, 0xa7, 0x2c, 0x5e, 0xbc, 0x1b, 0x36, 0x60, 0xf6, 0xec, 0x8b, 0x32, - 0x96, 0x6e, 0xde, 0x21, 0x26, 0xfc, 0x21, 0xad, 0xdb, 0xbf, 0x66, 0xcb, 0x3f, 0x97, 0xcb, 0xe3, - 0x89, 0x0e, 0x77, 0x4d, 0xbc, 0x7a, 0xe2, 0xbf, 0x81, 0x6f, 0xa3, 0xf2, 0xc7, 0xe5, 0x1e, 0x08, - 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x56, 0x4d, 0x8f, 0xdb, 0x36, + 0x10, 0xbd, 0xef, 0xaf, 0x60, 0x78, 0x28, 0x76, 0x81, 0xb5, 0xd4, 0xdd, 0xa0, 0x46, 0xd0, 0x48, + 0x02, 0x82, 0x34, 0x45, 0x02, 0xe4, 0xb0, 0x58, 0x23, 0x28, 0xd0, 0x8b, 0x41, 0x51, 0x23, 0x8b, + 0x35, 0x45, 0xb2, 0xe2, 0x48, 0x5e, 0xe7, 0xd7, 0x77, 0x48, 0x49, 0xfe, 0xd8, 0x1a, 0x9b, 0x8f, + 0x8b, 0xa5, 0x19, 0xce, 0xbc, 0x79, 0xf3, 0x38, 0x14, 0x9d, 0xbd, 0xaa, 0xac, 0xc4, 0xbd, 0x03, + 0xd6, 0x60, 0xab, 0x8b, 0xab, 0x2c, 0x3c, 0x98, 0x16, 0x66, 0x93, 0x73, 0x30, 0x3c, 0x38, 0x40, + 0x54, 0xf4, 0x68, 0x01, 0x05, 0xc5, 0xa0, 0x5b, 0xc0, 0xbf, 0xbd, 0x1a, 0x72, 0x2e, 0xad, 0x41, + 0x30, 0xb8, 0x08, 0xc9, 0x9c, 0x4d, 0x56, 0xce, 0x11, 0x9e, 0x30, 0x0d, 0x20, 0x6f, 0x99, 0x6c, + 0x44, 0xe7, 0x01, 0xf3, 0x1e, 0xeb, 0xc5, 0x1b, 0x3e, 0x63, 0x18, 0xd1, 0x42, 0xce, 0x07, 0x05, + 0x3b, 0x67, 0x3b, 0x3c, 0xc9, 0xdc, 0xa9, 0x0a, 0x9b, 0xbc, 0x82, 0x41, 0x49, 0x58, 0x44, 0xe3, + 0x56, 0x19, 0x85, 0x4a, 0xe8, 0x85, 0x97, 0x42, 0x43, 0x7e, 0x77, 0xdb, 0x92, 0xa3, 0xed, 0xdb, + 0xa3, 0x2d, 0x9e, 0xce, 0xec, 0xde, 0x43, 0x17, 0x0d, 0x51, 0x92, 0x6d, 0x6c, 0x28, 0x8a, 0x0a, + 0x35, 0x14, 0xef, 0xad, 0xa9, 0xd5, 0xa6, 0xef, 0x04, 0x2a, 0x6b, 0xb2, 0x74, 0x74, 0x5e, 0x65, + 0x5a, 0x99, 0x2d, 0xeb, 0x40, 0xe7, 0xdc, 0x37, 0xc4, 0x46, 0xf6, 0xc8, 0x14, 0x11, 0xe2, 0xac, + 0xe9, 0xa0, 0xce, 0x79, 0x5a, 0x8b, 0x21, 0xd8, 0x09, 0xfd, 0x70, 0x16, 0x3a, 0xcd, 0xb9, 0x6a, + 0xc5, 0x06, 0xd2, 0xa7, 0x45, 0x8c, 0x3b, 0x87, 0xc0, 0xbd, 0x06, 0xdf, 0x00, 0xe0, 0x1c, 0x1b, + 0xc5, 0x90, 0xde, 0x1f, 0xf0, 0x62, 0x48, 0x12, 0x3c, 0x94, 0xe9, 0x65, 0xa7, 0x1c, 0x32, 0xdf, + 0x49, 0x5a, 0x69, 0xab, 0xdf, 0x92, 0x7f, 0xc8, 0x9d, 0xa5, 0xa3, 0xfb, 0xf9, 0xba, 0x70, 0xee, + 0xf9, 0x7a, 0x3a, 0x6d, 0x4d, 0x69, 0xab, 0x3d, 0xb3, 0x46, 0x5b, 0x51, 0x11, 0x3d, 0x92, 0xec, + 0x9d, 0x73, 0xd7, 0x37, 0xa1, 0x42, 0xa5, 0x06, 0x26, 0xb5, 0xf0, 0x9e, 0xa8, 0x84, 0x8e, 0x79, + 0xb1, 0x02, 0x44, 0x65, 0x36, 0x9e, 0x65, 0xde, 0x09, 0xc3, 0x14, 0x65, 0x84, 0x3c, 0x72, 0xad, + 0x49, 0x34, 0xd0, 0xbc, 0xb8, 0x9e, 0xec, 0x24, 0x49, 0x6e, 0xa8, 0x18, 0x45, 0x51, 0x4d, 0x02, + 0x3a, 0x87, 0x2b, 0xb5, 0x95, 0xdb, 0x50, 0xa2, 0xb6, 0x5d, 0xcb, 0x68, 0x63, 0x1b, 0x4b, 0x50, + 0xce, 0x7a, 0xea, 0x5d, 0xc8, 0x20, 0x72, 0xec, 0x56, 0x60, 0x4f, 0xcd, 0x8f, 0x5b, 0x6e, 0x00, + 0x77, 0xb6, 0xdb, 0xae, 0xfd, 0x44, 0xe1, 0x19, 0xc1, 0x00, 0x34, 0x73, 0xf8, 0x4b, 0xfd, 0xa9, + 0xd8, 0x6a, 0xf5, 0xe9, 0x8f, 0x0b, 0x95, 0x63, 0x9c, 0x32, 0xae, 0xc7, 0xa8, 0x21, 0x68, 0x90, + 0x18, 0xfb, 0xf0, 0x5e, 0x55, 0xeb, 0xd1, 0x9e, 0x4b, 0x06, 0x17, 0x3f, 0x24, 0xf6, 0x5a, 0x8f, + 0x73, 0x15, 0x12, 0xad, 0x0b, 0x24, 0xd9, 0x20, 0x74, 0x4f, 0x81, 0xbc, 0xf8, 0x7c, 0xe8, 0x3a, + 0x4b, 0xc7, 0xb5, 0xa0, 0xf0, 0x08, 0x17, 0xde, 0x2e, 0xf3, 0x38, 0xe5, 0xfb, 0x40, 0x6e, 0x6a, + 0xb0, 0xfa, 0x26, 0xe7, 0xf8, 0x32, 0x4d, 0x88, 0x9b, 0x92, 0xf8, 0x81, 0xc9, 0x44, 0xdd, 0xf9, + 0xed, 0x25, 0xe6, 0xb1, 0xd3, 0x5a, 0x57, 0xeb, 0xb8, 0x4e, 0xf3, 0xaf, 0xc1, 0x6c, 0xe8, 0xd8, + 0xf0, 0xe5, 0x6b, 0xce, 0x2a, 0xe5, 0xc3, 0xe0, 0x57, 0x17, 0x8a, 0xfb, 0xbe, 0x9c, 0xb8, 0xd2, + 0xc4, 0x86, 0x17, 0x46, 0xee, 0x38, 0xf5, 0xbb, 0x08, 0x55, 0x9c, 0xb1, 0x92, 0x0d, 0xc8, 0x6d, + 0x69, 0x9f, 0x0e, 0x3a, 0xce, 0x61, 0xa3, 0xd0, 0x87, 0x24, 0x16, 0x5e, 0x99, 0x3b, 0x34, 0x1e, + 0x91, 0x8f, 0x6a, 0xbd, 0x2c, 0xda, 0x47, 0xdb, 0x02, 0xfb, 0x8e, 0x2d, 0x3e, 0x25, 0x16, 0x0e, + 0xd4, 0x89, 0x54, 0x27, 0xfd, 0xdf, 0x2d, 0x67, 0xb2, 0x4d, 0xd8, 0xf3, 0x59, 0xa6, 0xe6, 0xf2, + 0x00, 0x9c, 0x4a, 0x35, 0xd5, 0x2f, 0x7b, 0x44, 0x1a, 0x88, 0xb1, 0x0e, 0xc9, 0xd5, 0x2a, 0x3c, + 0x86, 0xcd, 0x3a, 0x8c, 0xee, 0x62, 0x25, 0x06, 0x60, 0xc2, 0x54, 0xec, 0x11, 0x4a, 0x6b, 0x31, + 0x4b, 0xc7, 0xe4, 0x00, 0x16, 0xb8, 0x5f, 0x6c, 0x7d, 0x3a, 0x80, 0x5f, 0x5c, 0x25, 0x10, 0x58, + 0xad, 0xba, 0x76, 0x27, 0x3a, 0x60, 0xd7, 0x49, 0xa9, 0xcc, 0xcd, 0xcf, 0x9e, 0xb0, 0x3e, 0xa2, + 0x71, 0x06, 0x46, 0x8e, 0xc4, 0xdb, 0x5e, 0xa3, 0x72, 0xa2, 0xc3, 0x48, 0x64, 0x41, 0xab, 0x62, + 0xd6, 0x65, 0x8c, 0x7d, 0xf1, 0xf8, 0x5d, 0xd4, 0xbc, 0x56, 0xc4, 0x9b, 0x4a, 0x4a, 0x70, 0xf4, + 0x95, 0x0e, 0x74, 0x6f, 0xc3, 0x4f, 0xb2, 0xf9, 0x3a, 0x23, 0xc7, 0x88, 0x6f, 0x28, 0x79, 0x26, + 0xe0, 0x51, 0xfe, 0x2f, 0x2e, 0x7c, 0x6e, 0x7e, 0x44, 0xc0, 0x47, 0xa0, 0x0e, 0xd8, 0xdc, 0xc5, + 0xcf, 0x0a, 0xd7, 0x05, 0x14, 0xfe, 0x7d, 0x64, 0x27, 0x5c, 0xe5, 0xd7, 0x53, 0x56, 0xa4, 0xf0, + 0x23, 0x9c, 0x3f, 0x99, 0xda, 0xbe, 0xc0, 0xf4, 0xc3, 0xea, 0xe1, 0xcd, 0xfd, 0x72, 0xb9, 0x28, + 0x85, 0xa7, 0x51, 0xcb, 0xca, 0x82, 0xae, 0x13, 0xb1, 0x97, 0xa8, 0xa9, 0x46, 0x71, 0x7b, 0x9c, + 0x95, 0x61, 0x99, 0x95, 0x5d, 0x71, 0xf5, 0x40, 0xdb, 0xcb, 0x6c, 0xcd, 0x32, 0x31, 0x5d, 0x2b, + 0xe1, 0x5a, 0xf6, 0xbf, 0xa7, 0xe9, 0x46, 0x61, 0x22, 0x9b, 0x3b, 0x97, 0x28, 0x9b, 0x36, 0x74, + 0xba, 0xb6, 0x64, 0x93, 0x2f, 0xe5, 0xc5, 0x64, 0x65, 0xa9, 0x28, 0x58, 0xb9, 0xff, 0x7f, 0xe6, + 0x94, 0xc5, 0x8b, 0x0f, 0xc3, 0x06, 0xcc, 0x9e, 0xfd, 0xad, 0x8c, 0xa5, 0x2b, 0x7a, 0x88, 0x09, + 0xbf, 0x48, 0xeb, 0xf6, 0x6f, 0xd9, 0xfd, 0xaf, 0xf7, 0xf7, 0xc7, 0xa3, 0x1d, 0x2e, 0x9d, 0x78, + 0x07, 0xc5, 0xbf, 0x0d, 0xff, 0x01, 0x5f, 0x1c, 0x62, 0xab, 0x47, 0x08, 0x00, 0x00, }; const StaticFile index_html PROGMEM = {(sizeof(index_html_content)/sizeof(index_html_content[0])), index_html_content}; static const uint8_t app_js_content[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x56, 0x5b, 0x6f, 0xdc, 0x44, - 0x14, 0x7e, 0xe7, 0x57, 0x78, 0x47, 0x22, 0xf2, 0xb0, 0x8e, 0x73, 0x81, 0x87, 0x6a, 0x5d, 0x6b, - 0xd5, 0x2b, 0x2d, 0x6a, 0x9b, 0xaa, 0x49, 0x11, 0x52, 0x14, 0xa2, 0x59, 0xfb, 0x6c, 0xd6, 0x8d, - 0x77, 0xc6, 0x8c, 0xc7, 0xd9, 0x84, 0x6d, 0x24, 0xda, 0x3e, 0x80, 0x04, 0x52, 0x25, 0xde, 0xe1, - 0x89, 0x1f, 0x90, 0x06, 0x02, 0x2d, 0x6d, 0xc3, 0x5f, 0xf0, 0xfe, 0x23, 0xce, 0x19, 0x7b, 0xaf, - 0x89, 0x44, 0xc5, 0x43, 0x36, 0xf6, 0x78, 0xce, 0xed, 0x3b, 0xe7, 0xfb, 0x66, 0xba, 0x85, 0x8c, - 0x4c, 0xa2, 0xa4, 0x93, 0xe4, 0x1b, 0x9d, 0x27, 0x10, 0x19, 0x17, 0xf8, 0x50, 0x83, 0x29, 0xb4, - 0x64, 0xdb, 0xca, 0xae, 0x38, 0xd5, 0x87, 0x1d, 0x16, 0x86, 0x61, 0xf5, 0xe8, 0x67, 0x5a, 0x19, - 0x65, 0x8e, 0x32, 0xf0, 0x8d, 0xda, 0x34, 0x3a, 0x91, 0x7b, 0x7e, 0x24, 0xd2, 0x14, 0x6d, 0x8f, - 0xbb, 0x63, 0x87, 0x7b, 0x30, 0x75, 0xe5, 0xc4, 0x2a, 0x2a, 0xfa, 0x20, 0x8d, 0xbf, 0x07, 0xe6, - 0x56, 0x0a, 0xf4, 0x78, 0xfd, 0xe8, 0x6e, 0x3c, 0x67, 0x10, 0x09, 0x19, 0x41, 0x7a, 0xeb, 0x00, - 0xbf, 0xcd, 0x58, 0x02, 0x06, 0x03, 0x5a, 0xbb, 0x09, 0x5d, 0x51, 0xa4, 0x66, 0x69, 0x69, 0x71, - 0xc5, 0xe5, 0x1e, 0xf8, 0xb9, 0x51, 0xd9, 0x43, 0xad, 0x32, 0xb1, 0x27, 0xc8, 0x19, 0xed, 0x5a, - 0x58, 0xb2, 0xdb, 0xaa, 0x18, 0xd7, 0x8b, 0x4e, 0x27, 0x85, 0xb0, 0xb1, 0x8a, 0x2b, 0x55, 0x98, - 0x2f, 0x45, 0x5a, 0xe0, 0xc2, 0x9a, 0xd7, 0x58, 0x9b, 0x26, 0x04, 0x5a, 0x2b, 0xbd, 0x05, 0x87, - 0x73, 0xe9, 0x38, 0x89, 0xcc, 0x0d, 0x79, 0x51, 0x5d, 0xe7, 0x16, 0x6d, 0x68, 0x83, 0xdf, 0x87, - 0x3c, 0x17, 0x7b, 0xd0, 0x82, 0x26, 0x63, 0x53, 0xf3, 0x54, 0x45, 0xfb, 0x64, 0x89, 0x99, 0x80, - 0xb9, 0x66, 0x10, 0xa6, 0x4e, 0x61, 0xc0, 0x65, 0x71, 0x92, 0x0b, 0x0c, 0x1f, 0x33, 0x6f, 0xfa, - 0x38, 0x03, 0x43, 0x21, 0xa7, 0x86, 0x1a, 0xfa, 0xea, 0x00, 0x2e, 0xb3, 0x9d, 0x31, 0x48, 0x64, - 0x62, 0x1e, 0x80, 0x19, 0x28, 0xbd, 0xbf, 0x09, 0xc6, 0x60, 0x33, 0x72, 0x97, 0x0f, 0xa7, 0x55, - 0xb8, 0xe0, 0x19, 0x3e, 0x94, 0x45, 0x9a, 0x36, 0xc2, 0x10, 0xf1, 0x73, 0xc1, 0x3f, 0xb0, 0xe5, - 0x1a, 0xee, 0x4d, 0x82, 0x1d, 0x1f, 0x08, 0xed, 0x98, 0x70, 0x35, 0x98, 0xd8, 0x21, 0x60, 0xc3, - 0x75, 0x6c, 0x79, 0xb3, 0x49, 0x36, 0xd8, 0x4d, 0x96, 0x2a, 0x11, 0xa3, 0xf7, 0xdd, 0x54, 0x74, - 0x20, 0x65, 0x1c, 0x01, 0x3e, 0x4a, 0xc1, 0xc7, 0x94, 0xb2, 0x54, 0x1c, 0x85, 0x4c, 0x2a, 0x09, - 0xac, 0x72, 0xa4, 0xc3, 0x49, 0xc3, 0xbb, 0x4a, 0xf7, 0x73, 0x5f, 0x56, 0xf9, 0xed, 0xe6, 0x75, - 0x82, 0x81, 0xf6, 0x45, 0x1c, 0xdb, 0x5e, 0xdf, 0x4b, 0x72, 0x03, 0x12, 0xb4, 0xcb, 0xf2, 0xa2, - 0xd3, 0x4f, 0x0c, 0xf3, 0xdc, 0x71, 0x0e, 0x33, 0xb8, 0x6b, 0x5f, 0x26, 0x71, 0x95, 0xb7, 0x8f, - 0x68, 0xf4, 0x5d, 0xde, 0xd6, 0x7e, 0x96, 0xef, 0xd7, 0x4b, 0x29, 0xc8, 0x3d, 0xd3, 0xbb, 0x7a, - 0xa5, 0xed, 0x8a, 0x14, 0xb4, 0x71, 0x59, 0xf9, 0x4b, 0x79, 0x56, 0x9e, 0x96, 0x67, 0xa3, 0xef, - 0xca, 0xf7, 0xa3, 0x1f, 0xcb, 0x37, 0x4e, 0xf9, 0x4f, 0x79, 0x82, 0x2f, 0xe7, 0xe5, 0xdb, 0xd1, - 0x4f, 0x8e, 0x5b, 0xbe, 0x2b, 0x5f, 0x97, 0xef, 0xf1, 0xef, 0x5d, 0x79, 0x42, 0x2b, 0xf8, 0x7c, - 0x32, 0x7a, 0xe9, 0x94, 0x7f, 0x94, 0x6f, 0xed, 0x87, 0x13, 0x67, 0xd9, 0xb9, 0xe2, 0x8c, 0x9e, - 0xd9, 0x1d, 0xa7, 0x64, 0x85, 0x7f, 0xa7, 0x9c, 0x71, 0x6f, 0x7e, 0x4a, 0x79, 0x6b, 0x79, 0x2d, - 0x0c, 0xb5, 0x9f, 0xe7, 0x98, 0x5d, 0x0e, 0x29, 0x92, 0x03, 0xe2, 0xbb, 0x32, 0x86, 0xc3, 0xb9, - 0x4c, 0x9c, 0xf2, 0x14, 0x93, 0x78, 0x85, 0xf1, 0x4f, 0xac, 0xf3, 0xd1, 0xf3, 0xf2, 0x7c, 0xf4, - 0x7d, 0xf9, 0x37, 0x3e, 0x62, 0xc8, 0xf3, 0xd1, 0xb3, 0xd1, 0xf3, 0xd1, 0x0b, 0xca, 0xf0, 0x92, - 0x00, 0x07, 0x2a, 0x89, 0xab, 0x59, 0xc2, 0x30, 0x16, 0x21, 0xde, 0x9a, 0xf8, 0xfe, 0x99, 0x6a, - 0x44, 0x17, 0xaf, 0xd1, 0xe3, 0x99, 0x23, 0x55, 0x8c, 0xf3, 0x19, 0x5f, 0x74, 0x72, 0xcc, 0xb9, - 0x87, 0xd6, 0x3d, 0x35, 0xd8, 0x25, 0xcc, 0x2e, 0x42, 0x1f, 0xf5, 0x84, 0xdc, 0x83, 0x45, 0xe8, - 0x2d, 0xc0, 0xf3, 0xc3, 0x4b, 0xa4, 0x67, 0x48, 0x1b, 0x23, 0x34, 0x52, 0xd9, 0x8f, 0x7a, 0x10, - 0xed, 0x43, 0xdc, 0x66, 0x06, 0x69, 0xc2, 0x5a, 0x2c, 0x13, 0x79, 0x8e, 0x8d, 0xa6, 0xf9, 0xac, - 0x42, 0x12, 0x2e, 0x1f, 0x18, 0xae, 0x9a, 0xc0, 0x89, 0xeb, 0x39, 0x34, 0x83, 0xe5, 0xb5, 0xc9, - 0xf0, 0xd6, 0xdf, 0x55, 0x46, 0x86, 0xf9, 0xb6, 0xd9, 0xc1, 0xd5, 0x99, 0x59, 0x08, 0x19, 0xab, - 0x0a, 0x16, 0x4f, 0xc4, 0x21, 0xe9, 0x8d, 0xcb, 0x56, 0x90, 0xb1, 0xa6, 0xc8, 0x99, 0x37, 0x3c, - 0x9e, 0x09, 0x69, 0x3c, 0xc5, 0x87, 0x46, 0x1f, 0x0d, 0x93, 0xae, 0x6b, 0xb8, 0xe9, 0x69, 0x35, - 0x70, 0x4c, 0x00, 0xae, 0x1d, 0x35, 0x4f, 0xf9, 0x84, 0xe6, 0x6e, 0x12, 0x3f, 0x7d, 0x4a, 0xd4, - 0x41, 0xf1, 0xa8, 0x82, 0x78, 0xd3, 0xb7, 0xaa, 0x1b, 0xf5, 0x02, 0xd2, 0xe5, 0x38, 0x12, 0x26, - 0xea, 0xa1, 0xaf, 0x61, 0xd5, 0x9e, 0xa9, 0x7e, 0x18, 0xcc, 0x68, 0x31, 0x25, 0x6c, 0xd1, 0x42, - 0x42, 0x96, 0xa7, 0x75, 0x42, 0x50, 0x27, 0x04, 0x41, 0x0d, 0x62, 0x22, 0x11, 0xb9, 0x3b, 0x5b, - 0xf7, 0xef, 0x61, 0x7d, 0x01, 0x92, 0xca, 0x25, 0xb8, 0x14, 0x12, 0x56, 0x5d, 0x35, 0x7e, 0x8a, - 0xc8, 0xd6, 0x1c, 0x08, 0x54, 0xb3, 0x59, 0x61, 0x99, 0x84, 0xd5, 0x87, 0x6d, 0xb5, 0xb3, 0xbd, - 0xba, 0xe3, 0x89, 0x99, 0xd7, 0xb5, 0x9d, 0xb1, 0x5b, 0x91, 0x65, 0x20, 0x63, 0x57, 0xc2, 0xc0, - 0xd9, 0xb0, 0x80, 0xba, 0x49, 0x93, 0x39, 0x2e, 0x6b, 0x0a, 0xfc, 0x17, 0x5f, 0xef, 0x73, 0xe6, - 0x25, 0x98, 0x7c, 0xad, 0x13, 0x95, 0xd1, 0x6c, 0xb1, 0x70, 0xb1, 0x58, 0xa8, 0x8a, 0x9d, 0x17, - 0xa8, 0xc7, 0x59, 0x2c, 0x0c, 0xdc, 0x46, 0x31, 0x70, 0xab, 0xec, 0x60, 0x51, 0x22, 0x0a, 0xbb, - 0x63, 0xaa, 0x10, 0xf0, 0x41, 0x0a, 0x81, 0x88, 0x21, 0x5a, 0xb3, 0xe3, 0x8e, 0xaa, 0xd6, 0x00, - 0xbf, 0x9b, 0xa4, 0xd5, 0x4f, 0x5e, 0xe3, 0xc2, 0x6b, 0x21, 0x19, 0x33, 0xe7, 0x37, 0xa4, 0xe2, - 0x9b, 0xf2, 0xad, 0x83, 0xbc, 0x7e, 0x85, 0x9c, 0x44, 0x76, 0x23, 0xc7, 0xcf, 0x48, 0x13, 0x48, - 0x07, 0xde, 0x2f, 0x70, 0x16, 0x29, 0xd5, 0x58, 0x0b, 0x2a, 0xad, 0x1c, 0xb3, 0x30, 0xa0, 0x32, - 0x64, 0x48, 0xd0, 0x7d, 0x75, 0xff, 0xde, 0x1d, 0x63, 0xb2, 0x47, 0xf0, 0x4d, 0x01, 0xb9, 0xf1, - 0xb4, 0x5d, 0xa4, 0x62, 0x6f, 0x0a, 0x23, 0x82, 0x89, 0x84, 0xd5, 0x60, 0x33, 0xca, 0x8b, 0x48, - 0x34, 0x4d, 0x12, 0x1b, 0x84, 0xa8, 0x22, 0x06, 0xa4, 0xb1, 0x97, 0x14, 0x8e, 0x07, 0xee, 0x9e, - 0xc6, 0x53, 0x66, 0xa1, 0x74, 0x9b, 0x01, 0x86, 0x5b, 0x70, 0xe5, 0xe7, 0xc9, 0xb7, 0x10, 0x48, - 0x6a, 0x38, 0xba, 0x83, 0xf8, 0xaa, 0x6e, 0xdf, 0x17, 0xa6, 0xe7, 0x6b, 0x55, 0x60, 0xf8, 0xf1, - 0xea, 0x8a, 0xfe, 0x64, 0x6d, 0x75, 0x95, 0xe3, 0x11, 0x7e, 0x3b, 0x39, 0x84, 0xd8, 0x5d, 0xe7, - 0x2d, 0x7c, 0xf7, 0xc6, 0xf5, 0xcd, 0x4c, 0x9c, 0x6c, 0xb2, 0x8f, 0x19, 0xcd, 0xaf, 0xf4, 0x95, - 0xd4, 0x20, 0xe2, 0x23, 0x22, 0x14, 0x54, 0x2c, 0x0e, 0x27, 0x09, 0xf1, 0xe1, 0x67, 0x78, 0x5e, - 0x48, 0xdf, 0xee, 0xd8, 0xa4, 0x1d, 0xc8, 0x4c, 0x94, 0xc7, 0xf0, 0x8b, 0xcd, 0x8d, 0x07, 0x7e, - 0x26, 0x74, 0x0e, 0x2e, 0x7d, 0xcd, 0x33, 0xe4, 0x2d, 0xd0, 0xac, 0x70, 0x7a, 0xc3, 0x23, 0xbc, - 0x3d, 0xee, 0xca, 0xaf, 0x8b, 0xcd, 0xa0, 0x3e, 0xfc, 0x85, 0x1d, 0xb0, 0x52, 0x3e, 0xfa, 0xc1, - 0x2e, 0x9e, 0x7b, 0xce, 0xe8, 0x85, 0xd5, 0x4c, 0xd2, 0xf3, 0x37, 0xf4, 0x44, 0x1a, 0x4d, 0x12, - 0x4f, 0x7a, 0x7f, 0x66, 0x0d, 0x7e, 0xc7, 0xed, 0x2f, 0xca, 0x3f, 0xf1, 0xe9, 0x0c, 0x37, 0x3e, - 0x1b, 0xbd, 0x64, 0xbc, 0x35, 0x89, 0x82, 0x8e, 0x5e, 0x63, 0x24, 0xab, 0xbf, 0x97, 0xf4, 0x9f, - 0x04, 0xc4, 0xd6, 0x6a, 0x87, 0x3a, 0x9c, 0x15, 0xa9, 0xcb, 0x86, 0x9d, 0xb6, 0x62, 0x63, 0x5d, - 0xf6, 0x70, 0x63, 0x73, 0x8b, 0x79, 0x66, 0xac, 0x50, 0xc2, 0x5a, 0x11, 0x6a, 0x39, 0xb5, 0x5d, - 0xd3, 0x0c, 0x5d, 0x20, 0xc6, 0xb5, 0x2c, 0x43, 0xe0, 0x2e, 0x3d, 0xc3, 0xbd, 0x45, 0xe2, 0x1c, - 0x37, 0x66, 0xb0, 0x9e, 0x3f, 0xe0, 0x3d, 0x1c, 0x03, 0x4b, 0x05, 0x1c, 0xc8, 0x4a, 0xb1, 0x3c, - 0x36, 0xde, 0xc1, 0x1a, 0x21, 0xc9, 0x36, 0x5e, 0x58, 0x74, 0xad, 0x2a, 0x34, 0x9e, 0xf6, 0xf2, - 0x82, 0x52, 0x8c, 0xf7, 0xb6, 0x8e, 0x88, 0xf6, 0x9d, 0x7e, 0x91, 0x1b, 0xa7, 0x03, 0x8e, 0x70, - 0x26, 0x76, 0x3c, 0x40, 0x87, 0x0d, 0x73, 0xd1, 0x48, 0x2a, 0xa7, 0xd0, 0xa9, 0x93, 0x67, 0x10, - 0x25, 0xdd, 0x84, 0xae, 0x23, 0x41, 0x3e, 0x48, 0x6a, 0x3d, 0x88, 0x44, 0x0e, 0xec, 0xf3, 0x5b, - 0x5b, 0xac, 0x85, 0xd6, 0x93, 0x6b, 0xa5, 0xe4, 0x7c, 0xa2, 0x5a, 0x58, 0xb8, 0x23, 0xb9, 0xf4, - 0x7b, 0x22, 0xdf, 0x18, 0x48, 0xba, 0xa3, 0x21, 0xa8, 0x47, 0xae, 0xe2, 0x38, 0x2f, 0xa6, 0x19, - 0xba, 0x74, 0xa6, 0x86, 0x34, 0x80, 0xa8, 0xfc, 0x1b, 0x5d, 0x97, 0xb5, 0x19, 0x6f, 0xe3, 0x4f, - 0x8b, 0x2d, 0x31, 0xde, 0x04, 0x19, 0xa1, 0x30, 0x3f, 0x7e, 0x74, 0xf7, 0x86, 0xea, 0xe3, 0x20, - 0x11, 0xe5, 0x15, 0x6f, 0xb2, 0x90, 0x5d, 0xf6, 0x45, 0xa2, 0xda, 0x71, 0x1e, 0x74, 0x70, 0x24, - 0xf7, 0x03, 0x9b, 0x98, 0xed, 0xd1, 0x62, 0x66, 0xb5, 0x5e, 0x6e, 0xef, 0x04, 0x1f, 0x92, 0x63, - 0xe2, 0x67, 0x45, 0x8e, 0xa5, 0xfe, 0x8f, 0x44, 0x64, 0x98, 0xf8, 0x4f, 0x54, 0x82, 0xb3, 0x82, - 0xa5, 0x1c, 0xdb, 0xbb, 0x92, 0xb8, 0x44, 0x41, 0xc6, 0x92, 0x21, 0xaa, 0xc1, 0xa2, 0x83, 0xc1, - 0xab, 0x32, 0x47, 0x60, 0x90, 0x55, 0x82, 0x4e, 0xe5, 0x7a, 0xef, 0x1d, 0x64, 0x1b, 0x49, 0xc4, - 0x0d, 0x25, 0x51, 0x2c, 0xcc, 0x72, 0x75, 0x42, 0x33, 0xd4, 0x9a, 0x34, 0x89, 0xec, 0xcd, 0x77, - 0xe5, 0x70, 0x79, 0x30, 0x18, 0x2c, 0x93, 0xcc, 0x2e, 0x63, 0xdb, 0xaa, 0xec, 0xe8, 0x96, 0x20, - 0xfe, 0x83, 0xce, 0x88, 0x12, 0x31, 0x5a, 0xcc, 0x30, 0xda, 0x2e, 0xb2, 0xfa, 0x3c, 0x45, 0x88, - 0xc4, 0xd2, 0x52, 0x63, 0xe5, 0xeb, 0xf5, 0xa7, 0x6b, 0xeb, 0xeb, 0x9f, 0xae, 0xf8, 0x06, 0xf3, - 0x71, 0x31, 0x39, 0xfb, 0x99, 0x5f, 0x9c, 0x9b, 0x1e, 0x96, 0xe8, 0x50, 0x74, 0x07, 0x0f, 0x98, - 0xf1, 0xb6, 0x40, 0xbb, 0x76, 0x5e, 0x67, 0x54, 0x42, 0xcc, 0xab, 0x04, 0x22, 0x65, 0x73, 0xbd, - 0x48, 0x47, 0x8d, 0xd8, 0xd8, 0xf3, 0x97, 0x36, 0x58, 0x96, 0xd9, 0xc9, 0x23, 0x94, 0xda, 0xb4, - 0xde, 0x42, 0xfa, 0x89, 0xe3, 0x01, 0x4e, 0x92, 0x1a, 0xf8, 0x74, 0xfa, 0x86, 0x43, 0x64, 0x66, - 0x0b, 0xfc, 0x0e, 0x2e, 0xa1, 0xad, 0xdd, 0xcd, 0xbd, 0x4c, 0xe5, 0xb3, 0x8b, 0x16, 0x69, 0x0c, - 0xea, 0xf2, 0xe0, 0xa3, 0x7f, 0x01, 0x1a, 0x08, 0xa7, 0x18, 0x21, 0x0d, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x57, 0x6d, 0x6f, 0xdb, 0x46, + 0x12, 0xfe, 0xde, 0x5f, 0x41, 0x2d, 0x70, 0x06, 0xf7, 0x44, 0xd3, 0x2f, 0xd7, 0x02, 0x85, 0x18, + 0x42, 0x48, 0xda, 0xe4, 0x92, 0x22, 0xa9, 0x8b, 0x24, 0x57, 0x1c, 0x60, 0xf8, 0x82, 0x95, 0x38, + 0xb2, 0x18, 0x53, 0xbb, 0xec, 0xee, 0xd2, 0xb2, 0x4f, 0x11, 0x70, 0x69, 0x0a, 0xb4, 0x40, 0x0b, + 0x04, 0xe8, 0xf7, 0xcb, 0xa7, 0xfe, 0x00, 0x37, 0x77, 0xbe, 0x4b, 0x2e, 0x4d, 0xfa, 0x17, 0xa8, + 0x7f, 0xd4, 0x99, 0x25, 0x29, 0xd1, 0xb2, 0x81, 0x06, 0xf7, 0xc1, 0x12, 0xb5, 0x9c, 0x9d, 0x9d, + 0x79, 0x66, 0x9e, 0x67, 0xc7, 0xa3, 0x42, 0x0e, 0x6d, 0xaa, 0xa4, 0x07, 0x3e, 0xf0, 0x99, 0x06, + 0x5b, 0x68, 0xc9, 0xf6, 0xd5, 0xe0, 0x31, 0x0c, 0xad, 0xb7, 0xe7, 0xbe, 0x0e, 0x58, 0x1c, 0xc7, + 0xd5, 0x63, 0x98, 0x6b, 0x65, 0x95, 0x3d, 0xcd, 0x21, 0xb4, 0xea, 0x81, 0xd5, 0xa9, 0x3c, 0x0c, + 0x87, 0x22, 0xcb, 0x70, 0xef, 0x7c, 0xd4, 0x78, 0xb2, 0x2b, 0x4f, 0x5e, 0xa2, 0x86, 0xc5, 0x04, + 0xa4, 0x0d, 0x0f, 0xc1, 0xde, 0xcc, 0x80, 0x1e, 0x6f, 0x9c, 0xde, 0x49, 0x2e, 0xd8, 0x4b, 0xb2, + 0x87, 0xd0, 0xd8, 0xd3, 0x0c, 0xc2, 0x24, 0x35, 0x79, 0x26, 0x4e, 0x63, 0x26, 0x95, 0x04, 0xb6, + 0x32, 0xd2, 0x2d, 0xa7, 0x80, 0x61, 0xc0, 0x31, 0xba, 0xfa, 0x14, 0x46, 0xa2, 0xc8, 0xec, 0xc6, + 0xc6, 0xfa, 0x8a, 0xcf, 0x03, 0x72, 0xa8, 0xf2, 0x2f, 0xb4, 0xca, 0xc5, 0xa1, 0x20, 0x17, 0x64, + 0xb5, 0xb6, 0xe4, 0xcc, 0x86, 0x42, 0x0e, 0x21, 0xbb, 0x51, 0x0c, 0x06, 0x19, 0xc4, 0x9d, 0x6d, + 0x5c, 0xa9, 0x8e, 0xf9, 0x52, 0x64, 0x05, 0x2e, 0xec, 0x04, 0x9d, 0x9d, 0x55, 0x18, 0x69, 0x3b, + 0x0c, 0x2f, 0x95, 0xc6, 0xd2, 0x6e, 0x35, 0xf2, 0x6e, 0x6a, 0xad, 0x74, 0x1f, 0xc2, 0x09, 0x18, + 0x23, 0x0e, 0xa1, 0x07, 0x5d, 0xd6, 0x8a, 0x5e, 0xd5, 0x29, 0x82, 0xbd, 0x6e, 0x11, 0xb5, 0x41, + 0x61, 0xc1, 0x67, 0x98, 0xaa, 0xc0, 0x33, 0x13, 0x16, 0xac, 0x1e, 0x5b, 0xb0, 0x88, 0x6a, 0x8f, + 0x86, 0x89, 0x3a, 0x86, 0xab, 0xb6, 0xb5, 0x6c, 0x8d, 0xcf, 0x67, 0xa3, 0x56, 0x25, 0x03, 0xcb, + 0x67, 0xb2, 0xc8, 0xb2, 0x4e, 0x1c, 0x23, 0x3a, 0x3e, 0x84, 0xc7, 0x2e, 0x19, 0xcb, 0x03, 0xf2, + 0x3a, 0x3f, 0x16, 0xda, 0x33, 0xf1, 0x76, 0xb4, 0xdc, 0x52, 0xe0, 0xfe, 0x5d, 0xac, 0x72, 0xb7, + 0x6b, 0x36, 0x36, 0xa4, 0x6f, 0x7d, 0x96, 0x29, 0x91, 0x60, 0x75, 0x1f, 0x65, 0x62, 0x00, 0x19, + 0xe3, 0xd5, 0x9e, 0x24, 0x5e, 0xd6, 0x73, 0xa4, 0xf4, 0xc4, 0x84, 0x12, 0xec, 0x54, 0xe9, 0xa3, + 0x47, 0x98, 0x98, 0x45, 0x6b, 0x13, 0x25, 0xa1, 0x48, 0x92, 0x9b, 0x54, 0x89, 0xbb, 0xa9, 0xb1, + 0x20, 0x41, 0xfb, 0xcc, 0x14, 0x83, 0x49, 0x6a, 0x59, 0xe0, 0x37, 0xc7, 0xb5, 0xfb, 0x23, 0x1c, + 0xa7, 0x49, 0x15, 0x5d, 0x88, 0x19, 0x4e, 0x7c, 0xde, 0x4f, 0xc2, 0xdc, 0x1c, 0xd5, 0x4b, 0x19, + 0xc8, 0x43, 0x3b, 0xbe, 0xf6, 0x71, 0xdf, 0x17, 0x19, 0x68, 0x0c, 0xab, 0xfc, 0x67, 0x79, 0x5e, + 0xbe, 0x2c, 0xcf, 0x17, 0xff, 0x28, 0xdf, 0x2e, 0xbe, 0x2f, 0x5f, 0x7b, 0xe5, 0xaf, 0xe5, 0x19, + 0xfe, 0x78, 0x57, 0xbe, 0x59, 0xfc, 0xe0, 0xf9, 0xe5, 0x2f, 0xe5, 0xab, 0xf2, 0x2d, 0xfe, 0xfd, + 0x52, 0x9e, 0xd1, 0x0a, 0x3e, 0x9f, 0x2d, 0x9e, 0x7b, 0xe5, 0xbf, 0xcb, 0x37, 0xee, 0xc5, 0x99, + 0xb7, 0xe9, 0x7d, 0xec, 0x2d, 0x9e, 0x3a, 0x8b, 0x97, 0xb4, 0x0b, 0xff, 0x5e, 0x72, 0xc6, 0x03, + 0xea, 0x2f, 0xde, 0xdb, 0xdc, 0x41, 0x10, 0x92, 0xd0, 0x18, 0x0c, 0xca, 0x40, 0x86, 0x1d, 0x0f, + 0xc9, 0x1d, 0x99, 0xc0, 0xc9, 0x85, 0x00, 0xbc, 0xf2, 0x25, 0x9e, 0xfd, 0x33, 0x1e, 0x7b, 0xe6, + 0x7c, 0x2e, 0xbe, 0x2e, 0xdf, 0x2d, 0xbe, 0x2d, 0xff, 0x87, 0x8f, 0x78, 0xd2, 0xbb, 0xc5, 0xd3, + 0xc5, 0xd7, 0x8b, 0x67, 0x14, 0xd8, 0xd2, 0xef, 0xb1, 0x4a, 0x13, 0x6c, 0x02, 0x74, 0xea, 0xb0, + 0xe0, 0xbd, 0xa5, 0xbb, 0x1f, 0x29, 0x1b, 0xdc, 0xf5, 0x0a, 0x9d, 0x9c, 0x7b, 0x63, 0x35, 0xc1, + 0xae, 0x4a, 0x9a, 0x7d, 0x73, 0xce, 0x03, 0xdc, 0x33, 0x56, 0xd3, 0x47, 0x84, 0xc9, 0x65, 0x68, + 0x87, 0x63, 0x21, 0x0f, 0x61, 0x0d, 0xda, 0x0a, 0xc0, 0x8b, 0xbd, 0x46, 0x94, 0x65, 0xd8, 0xda, + 0x56, 0x68, 0x64, 0x62, 0x38, 0x1c, 0xc3, 0xf0, 0x08, 0x92, 0x3e, 0xb3, 0x70, 0x62, 0x59, 0x8f, + 0xe5, 0xc2, 0x18, 0x2c, 0x24, 0xf5, 0x54, 0x75, 0x24, 0x01, 0xf0, 0x9e, 0xc7, 0x51, 0x63, 0xd8, + 0x78, 0xe9, 0xfa, 0x02, 0x6c, 0xd1, 0xe6, 0xce, 0xb2, 0x05, 0xeb, 0xf7, 0x2a, 0xa7, 0x8d, 0x66, + 0xdf, 0x1e, 0xe0, 0x6a, 0xab, 0xd6, 0x31, 0x63, 0x55, 0xc2, 0xe2, 0xb1, 0x38, 0x21, 0xb9, 0xf0, + 0xd9, 0x16, 0xb2, 0xcb, 0x16, 0x86, 0x05, 0xb3, 0x79, 0xeb, 0x48, 0x1b, 0x48, 0x3e, 0xb3, 0xfa, + 0x74, 0x96, 0x8e, 0x7c, 0xcb, 0xed, 0x58, 0xab, 0xa9, 0x67, 0x23, 0xf0, 0x5d, 0x2b, 0x05, 0x32, + 0x24, 0x0c, 0x1f, 0xa5, 0xc9, 0x93, 0x27, 0x44, 0x00, 0x24, 0x78, 0x75, 0x48, 0xb0, 0xfa, 0x55, + 0xd5, 0xa0, 0x5e, 0xc0, 0xce, 0x9f, 0x0f, 0x85, 0x1d, 0x8e, 0xd1, 0xd7, 0xac, 0x2a, 0x4a, 0x8a, + 0x8f, 0x7c, 0xbe, 0x1e, 0x0a, 0xca, 0xc4, 0x5a, 0x20, 0x8e, 0x65, 0x75, 0x20, 0x50, 0x07, 0x02, + 0x51, 0x0d, 0x5e, 0x2a, 0x11, 0xb1, 0xdb, 0x0f, 0xef, 0xdd, 0xc5, 0xbc, 0x22, 0x24, 0x8b, 0x4f, + 0x30, 0x49, 0xe4, 0x9c, 0xbc, 0x66, 0xc3, 0x0c, 0x11, 0xad, 0x7b, 0x3b, 0x92, 0xdd, 0x6e, 0x85, + 0xa1, 0x8e, 0xab, 0x17, 0xfb, 0xf2, 0x60, 0x7f, 0xfb, 0x20, 0x50, 0xad, 0x9f, 0x3b, 0x07, 0x8d, + 0x5b, 0x91, 0xe7, 0x20, 0x13, 0x5f, 0xc2, 0xd4, 0xdb, 0x73, 0x40, 0xfa, 0xba, 0xcb, 0x3c, 0x9f, + 0x75, 0x15, 0x7e, 0x25, 0x37, 0x26, 0x9c, 0x05, 0x1a, 0x83, 0x17, 0x7e, 0x65, 0xdf, 0xce, 0x0f, + 0x56, 0xf9, 0x41, 0x95, 0xdf, 0xfc, 0x02, 0xff, 0x29, 0x06, 0x58, 0x27, 0x78, 0x91, 0x27, 0xc2, + 0xc2, 0x8a, 0xdf, 0xf0, 0x5e, 0xfc, 0x46, 0x5c, 0x10, 0x13, 0x8d, 0xdf, 0x41, 0x07, 0xc2, 0x51, + 0x9a, 0x55, 0x1f, 0xa6, 0xce, 0x99, 0xd7, 0xe4, 0x6f, 0x38, 0xf0, 0x13, 0xf2, 0xe8, 0x75, 0xf9, + 0xc6, 0x43, 0x2e, 0xfe, 0x8c, 0x84, 0x42, 0x46, 0x22, 0x2f, 0xcf, 0x89, 0xc7, 0xc4, 0xdd, 0xb7, + 0x6b, 0x84, 0x43, 0x72, 0x74, 0x76, 0x22, 0x54, 0xd4, 0x86, 0x4c, 0x51, 0x05, 0x2d, 0x61, 0xf2, + 0xd7, 0x7b, 0x77, 0x6f, 0x5b, 0x9b, 0xdf, 0x87, 0xaf, 0x0a, 0x30, 0x36, 0x10, 0x6e, 0xf1, 0x16, + 0x66, 0xf2, 0xa9, 0xb0, 0x22, 0x6a, 0x8e, 0x6d, 0x50, 0x64, 0x14, 0x14, 0xb1, 0x62, 0x15, 0x21, + 0x22, 0xcf, 0xb1, 0x87, 0x8a, 0x9c, 0xf4, 0xef, 0x8a, 0x5c, 0xf1, 0xfe, 0x3b, 0xd4, 0x28, 0xf1, + 0x6b, 0xd9, 0xba, 0x08, 0x02, 0x1d, 0xaf, 0xb9, 0x0a, 0x4d, 0xfa, 0x77, 0x88, 0x24, 0x55, 0x12, + 0xdd, 0x41, 0x72, 0x4d, 0xf7, 0xef, 0x09, 0x3b, 0x0e, 0xb5, 0x2a, 0xf0, 0xf8, 0x66, 0x75, 0x4b, + 0xff, 0x71, 0x67, 0x7b, 0x9b, 0xe3, 0x8d, 0x7a, 0x2b, 0x3d, 0x81, 0xc4, 0xdf, 0xe5, 0x3d, 0xfc, + 0x1d, 0x34, 0xf9, 0xb5, 0x5a, 0x49, 0x76, 0xd9, 0x1f, 0x18, 0x35, 0xa6, 0x0c, 0x95, 0xd4, 0x20, + 0x92, 0x53, 0x62, 0x08, 0x54, 0xb4, 0x8c, 0x97, 0x01, 0x35, 0xb5, 0x64, 0xe5, 0x8b, 0x75, 0x40, + 0x09, 0xcb, 0xff, 0x22, 0x8a, 0x4e, 0x42, 0x17, 0xdf, 0xb9, 0xc5, 0x77, 0x81, 0xb7, 0x78, 0xe6, + 0x44, 0x8b, 0x74, 0xf4, 0x35, 0x3d, 0x91, 0x36, 0x92, 0xb4, 0x92, 0xce, 0x9e, 0xbb, 0x0d, 0xff, + 0x42, 0xf3, 0x67, 0xe5, 0x7f, 0xf0, 0xe9, 0x1c, 0x0d, 0x9f, 0x2e, 0x9e, 0xb3, 0x08, 0x4b, 0xfc, + 0x21, 0xca, 0xa5, 0x0c, 0x5d, 0x24, 0x0f, 0x28, 0x12, 0x4e, 0x74, 0x20, 0x0d, 0xfd, 0xec, 0xc1, + 0xde, 0xe7, 0x61, 0x2e, 0xb4, 0x01, 0x9f, 0xde, 0x9b, 0x1c, 0x29, 0x0f, 0x0f, 0x51, 0x6c, 0x38, + 0xfd, 0xc2, 0x1b, 0xba, 0x5f, 0x15, 0x1f, 0x78, 0xaf, 0xe9, 0x82, 0x17, 0x18, 0xce, 0x2b, 0x8c, + 0xd7, 0xc9, 0xe8, 0x15, 0x9d, 0xc0, 0x2e, 0x11, 0x15, 0x6f, 0xb2, 0xb9, 0x83, 0x02, 0xe8, 0xf2, + 0x8d, 0xdb, 0xa2, 0xd4, 0xee, 0x74, 0x32, 0xc1, 0x7a, 0xfb, 0xec, 0x8b, 0xbd, 0x07, 0x0f, 0x59, + 0x60, 0x1b, 0x25, 0x12, 0xce, 0x9a, 0xc0, 0x34, 0xd4, 0x0d, 0x82, 0xfa, 0x8a, 0xc0, 0xad, 0x8b, + 0xf8, 0x3e, 0xf2, 0x57, 0x35, 0xfa, 0xd2, 0xe3, 0x85, 0x26, 0x9f, 0xad, 0xda, 0xf2, 0x16, 0xae, + 0xdf, 0x47, 0x90, 0x40, 0x47, 0x14, 0x2e, 0x95, 0x7d, 0xbd, 0x5a, 0x36, 0x9e, 0xa6, 0x32, 0x51, + 0xd3, 0x70, 0x92, 0x7c, 0x54, 0x41, 0x86, 0x20, 0xf1, 0x68, 0x7d, 0x66, 0xa8, 0x62, 0xc6, 0x89, + 0x61, 0xab, 0x62, 0x66, 0x1f, 0xcd, 0x63, 0xd6, 0xad, 0x2e, 0xf6, 0x86, 0x10, 0x57, 0x82, 0xd2, + 0x60, 0x72, 0x11, 0xe8, 0xc5, 0xb7, 0x74, 0xef, 0xd4, 0x54, 0x5b, 0x7c, 0x53, 0x11, 0x91, 0x2e, + 0x2e, 0xf2, 0x41, 0x75, 0xbd, 0x6e, 0x6e, 0xa4, 0x52, 0xe8, 0xd3, 0x6a, 0xd8, 0x5b, 0x4b, 0x95, + 0xd8, 0xe2, 0x84, 0xa4, 0xd3, 0x3a, 0xa6, 0x35, 0x07, 0xa2, 0xc8, 0x06, 0x3a, 0x48, 0x2b, 0x35, + 0x88, 0x75, 0x25, 0xc9, 0x01, 0x6b, 0x2c, 0x58, 0x27, 0xa6, 0x7b, 0x09, 0xa7, 0xa7, 0xb4, 0x96, + 0x4f, 0x02, 0xcb, 0x4d, 0x52, 0x08, 0x36, 0x8e, 0x95, 0x03, 0x31, 0x3c, 0xf2, 0x26, 0x85, 0xb1, + 0xde, 0x00, 0x3c, 0xe1, 0x2d, 0xf7, 0x71, 0xea, 0xbd, 0x8e, 0xbc, 0xbc, 0x49, 0x2a, 0xaf, 0xd0, + 0x99, 0x67, 0x72, 0x18, 0xa6, 0xa3, 0x94, 0x66, 0xa4, 0xc8, 0x4c, 0xd3, 0xba, 0x69, 0x86, 0xc2, + 0x00, 0xfb, 0xf3, 0xcd, 0x87, 0xac, 0x47, 0x82, 0xed, 0xa3, 0x4e, 0x36, 0xba, 0xac, 0x70, 0x92, + 0xf3, 0x34, 0xd7, 0xe1, 0x58, 0x98, 0xbd, 0xa9, 0xa4, 0x09, 0x11, 0xa1, 0x3a, 0xf5, 0x15, 0xc7, + 0x3b, 0x4a, 0x76, 0x63, 0xdf, 0xcd, 0x05, 0x12, 0x99, 0x88, 0x77, 0xda, 0xde, 0xc8, 0x67, 0x7d, + 0xc6, 0xfb, 0xf8, 0xd1, 0x63, 0x1b, 0x8c, 0x77, 0x41, 0x0e, 0x55, 0x02, 0x7f, 0xb9, 0x7f, 0xe7, + 0x13, 0x35, 0xc1, 0x3e, 0xc7, 0x96, 0xc1, 0x8d, 0x5d, 0x86, 0x65, 0xb9, 0xe2, 0x8d, 0xde, 0x57, + 0x07, 0x9c, 0x47, 0x03, 0xc4, 0xf6, 0x28, 0x72, 0x11, 0xb9, 0xae, 0x5c, 0x86, 0xe4, 0x9a, 0x41, + 0xc4, 0xfb, 0x07, 0xd1, 0xfb, 0x04, 0x27, 0xc2, 0xbc, 0x30, 0x28, 0xed, 0xff, 0x47, 0x04, 0x3a, + 0x16, 0xe1, 0x63, 0x95, 0x22, 0x2d, 0x30, 0x87, 0x79, 0x3d, 0x12, 0x5e, 0xd6, 0xd0, 0x46, 0x34, + 0x4d, 0xc5, 0x21, 0xba, 0x7c, 0x83, 0x2a, 0xe4, 0xd8, 0x5d, 0xec, 0x86, 0x1a, 0xb4, 0xb6, 0xbd, + 0xed, 0x5a, 0xdc, 0x67, 0x9f, 0x28, 0x89, 0x8c, 0xb1, 0x9b, 0xd5, 0xd0, 0xc1, 0x50, 0x6d, 0xb3, + 0x74, 0xe8, 0x06, 0xee, 0xad, 0x93, 0xcd, 0xe9, 0x74, 0xba, 0x49, 0x77, 0xcb, 0x26, 0x16, 0xaa, + 0x8a, 0x8e, 0xc6, 0x1d, 0xf3, 0x3b, 0x82, 0x56, 0x6b, 0x8d, 0x69, 0x6b, 0x0d, 0x2d, 0xb2, 0x7a, + 0x44, 0x40, 0x88, 0x70, 0x72, 0xed, 0x6c, 0xfd, 0x6d, 0xf7, 0xc9, 0xce, 0xee, 0xee, 0x9f, 0xb6, + 0x42, 0x8b, 0xf1, 0xf8, 0x18, 0x9c, 0x7b, 0xcd, 0x2f, 0x77, 0xca, 0x18, 0x53, 0xf4, 0xe8, 0x74, + 0x8f, 0x75, 0x97, 0x66, 0x51, 0xea, 0xbb, 0x0e, 0x6d, 0xa9, 0x97, 0xb9, 0xa8, 0x5e, 0xa4, 0x38, + 0xe6, 0x4a, 0xc5, 0x41, 0xad, 0xa9, 0x46, 0x0a, 0x32, 0x70, 0x82, 0xe2, 0x7a, 0x8d, 0x50, 0xea, + 0xd3, 0x7a, 0x4f, 0x63, 0x96, 0xf3, 0x9a, 0xe4, 0x34, 0x58, 0xc4, 0x33, 0xe4, 0x51, 0xcf, 0x86, + 0x83, 0x94, 0xee, 0x82, 0xc0, 0x59, 0xf3, 0x20, 0x57, 0xa6, 0xbd, 0xe8, 0x90, 0xc6, 0x43, 0xf1, + 0x3f, 0x95, 0x7a, 0x6b, 0x2a, 0x53, 0x7b, 0x3d, 0xcf, 0xdb, 0xe0, 0xe0, 0xf4, 0xef, 0xee, 0xf9, + 0xe8, 0x83, 0xdf, 0x00, 0xbe, 0x18, 0xb5, 0xbd, 0xce, 0x0d, 0x00, 0x00, }; const StaticFile app_js PROGMEM = {(sizeof(app_js_content)/sizeof(app_js_content[0])), app_js_content}; +static const uint8_t md5_js_content[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xad, 0x59, 0x79, 0x73, 0x1b, 0xb7, + 0x15, 0xff, 0xbf, 0x9f, 0x42, 0xe2, 0x4c, 0x39, 0xbb, 0xb3, 0x2b, 0x05, 0xf7, 0x61, 0x72, 0xe5, + 0x89, 0x93, 0x1e, 0xe9, 0x95, 0xb6, 0x69, 0xd2, 0x83, 0x43, 0xcd, 0xd0, 0xd2, 0xd2, 0xbb, 0x89, + 0x42, 0xaa, 0x58, 0xd0, 0xb2, 0x62, 0xd2, 0x9f, 0xbd, 0x0f, 0xd8, 0x0b, 0x4b, 0x89, 0x3a, 0xac, + 0x8e, 0x2d, 0x2c, 0x08, 0xbc, 0xf7, 0x80, 0xdf, 0xbb, 0x70, 0x1d, 0x2f, 0x37, 0xab, 0x0b, 0x5b, + 0xae, 0x57, 0x51, 0xfc, 0x71, 0xb4, 0xa9, 0xf2, 0xa3, 0xca, 0x9a, 0xf2, 0xc2, 0x8e, 0x26, 0xef, + 0x17, 0xe6, 0xc8, 0xa6, 0x26, 0x1b, 0x95, 0xab, 0xeb, 0x8d, 0x3d, 0x2a, 0xab, 0xa3, 0x72, 0xf5, + 0x7e, 0x71, 0x55, 0x5e, 0x1e, 0xd9, 0xdb, 0xeb, 0x7c, 0x94, 0x96, 0xd9, 0xfb, 0x35, 0xfc, 0x40, + 0xc7, 0x59, 0x76, 0x53, 0xae, 0x2e, 0xd7, 0x37, 0xa7, 0x5f, 0x1a, 0xb3, 0xb8, 0x7d, 0xb3, 0x59, + 0x2e, 0x73, 0x93, 0x16, 0xd9, 0x08, 0x61, 0x42, 0x19, 0x17, 0x52, 0xe9, 0xc5, 0xdb, 0x8b, 0xcb, + 0x7c, 0x39, 0x3a, 0xad, 0xae, 0xaf, 0x4a, 0x1b, 0x8d, 0x46, 0x71, 0x5a, 0x65, 0x33, 0x4c, 0x54, + 0x4a, 0x89, 0x14, 0x2a, 0x55, 0x54, 0x29, 0x81, 0x54, 0x7a, 0x42, 0x30, 0x93, 0x4c, 0x51, 0xc1, + 0xd4, 0x3c, 0xcd, 0xb3, 0x19, 0x4a, 0x55, 0x8a, 0x45, 0x4a, 0xd8, 0x3c, 0x5d, 0x64, 0xb3, 0x51, + 0x91, 0x7f, 0x18, 0xa5, 0xa3, 0x85, 0x1b, 0x04, 0xbe, 0x97, 0xe5, 0xbb, 0xbc, 0xb2, 0x50, 0x79, + 0xeb, 0x07, 0x6c, 0x7b, 0xde, 0xb4, 0xbf, 0xde, 0x2e, 0xaa, 0x5c, 0xb0, 0xd1, 0x3c, 0x5d, 0x66, + 0xa3, 0x2f, 0xdf, 0x7c, 0xf5, 0xf5, 0x6f, 0x7e, 0xfb, 0xbb, 0xdf, 0x7f, 0xf3, 0x87, 0x3f, 0xfe, + 0xe9, 0xcf, 0x7f, 0xf9, 0xf6, 0xaf, 0x7f, 0xfb, 0xfb, 0x77, 0xff, 0xf8, 0xfe, 0x87, 0x7f, 0xfe, + 0xeb, 0xdf, 0xff, 0xa9, 0x67, 0xf6, 0xae, 0x28, 0x7f, 0xfc, 0xe9, 0xea, 0xe7, 0xd5, 0xfa, 0xfa, + 0xbf, 0xa6, 0xb2, 0x9b, 0xf7, 0x37, 0x1f, 0x6e, 0x7f, 0xe9, 0x67, 0x9f, 0x7c, 0x11, 0xce, 0x7c, + 0x95, 0xcd, 0xe6, 0x93, 0x72, 0x19, 0x95, 0xf1, 0x47, 0xa7, 0xa1, 0x75, 0xb6, 0xca, 0x6f, 0x8e, + 0x02, 0xe4, 0x91, 0x50, 0xf1, 0xc4, 0xfa, 0xd6, 0xef, 0xcb, 0x95, 0x55, 0xbe, 0x2b, 0x5a, 0x3b, + 0xc6, 0xb6, 0x8d, 0x92, 0xb6, 0x71, 0xe7, 0x2b, 0xa7, 0x65, 0xe5, 0xbf, 0xdb, 0x6d, 0x34, 0xf8, + 0x9d, 0x75, 0xa6, 0xb1, 0xf1, 0x47, 0x93, 0xdb, 0x8d, 0x59, 0x8d, 0x66, 0xeb, 0xb7, 0x3f, 0xe6, + 0x17, 0xb6, 0x1e, 0x71, 0x3e, 0xca, 0xb2, 0xec, 0x5b, 0xdf, 0x70, 0x7a, 0x6d, 0xd6, 0x76, 0xed, + 0x2c, 0x73, 0x6a, 0xd7, 0xdf, 0x81, 0x09, 0x57, 0xef, 0x4e, 0x2f, 0x16, 0x57, 0x57, 0xc0, 0xba, + 0x8b, 0xd3, 0x72, 0x3c, 0x3e, 0x0e, 0xe6, 0x08, 0x03, 0xfc, 0x50, 0xe6, 0x37, 0xe3, 0x71, 0x74, + 0xb7, 0xf1, 0xbe, 0x41, 0xeb, 0x31, 0x61, 0x30, 0x27, 0x7f, 0xbd, 0x3c, 0xb2, 0xe3, 0xb1, 0x3d, + 0xad, 0xf5, 0xde, 0xd7, 0x4e, 0x2f, 0xd6, 0x2b, 0xf0, 0x9d, 0xcd, 0x85, 0x5d, 0x1b, 0x98, 0x56, + 0x20, 0x79, 0x17, 0x7b, 0x67, 0xba, 0x0f, 0xcf, 0x51, 0xd7, 0x64, 0xba, 0x26, 0xa7, 0xa6, 0x4d, + 0x74, 0x8c, 0xe2, 0xd3, 0xcd, 0xf5, 0xe5, 0xc2, 0xe6, 0xd0, 0x35, 0xb3, 0xf3, 0x28, 0xde, 0xed, + 0x26, 0x2d, 0x35, 0xf4, 0x03, 0x3d, 0x58, 0xc1, 0xc4, 0xab, 0x19, 0x9a, 0x67, 0xab, 0x19, 0x16, + 0xbe, 0x74, 0x05, 0x71, 0x05, 0x75, 0x05, 0x73, 0x05, 0x77, 0x85, 0xef, 0x95, 0xae, 0x50, 0xae, + 0xd0, 0x9e, 0xb8, 0x66, 0xf4, 0x3c, 0xd8, 0x33, 0x61, 0xcf, 0x85, 0x3d, 0x1b, 0x06, 0x3e, 0x94, + 0xda, 0xa2, 0xac, 0x4e, 0xdf, 0x5e, 0xad, 0x2f, 0x7e, 0xaa, 0xb2, 0x55, 0xf3, 0xcb, 0x63, 0x52, + 0x99, 0x9d, 0xe4, 0x57, 0x10, 0x2e, 0xbd, 0x2b, 0x14, 0xf7, 0xbb, 0x42, 0xc8, 0xb3, 0xe7, 0x15, + 0x45, 0x3c, 0x1c, 0x60, 0xcf, 0x3f, 0x8a, 0x78, 0xe7, 0x87, 0x08, 0x69, 0x20, 0x28, 0x1e, 0xf9, + 0x37, 0xaf, 0x87, 0x2c, 0x50, 0x56, 0x7f, 0x71, 0xf3, 0x25, 0xcd, 0x97, 0xd6, 0xdf, 0xca, 0x2e, + 0x8c, 0xad, 0xab, 0x6f, 0x6f, 0x6d, 0x5e, 0x35, 0xbd, 0x6f, 0x7c, 0xbd, 0x01, 0xbe, 0x2c, 0x57, + 0x10, 0xf0, 0xbf, 0xe4, 0x97, 0x4d, 0xe7, 0xa2, 0x2a, 0xa0, 0x7e, 0x8c, 0xdb, 0x5e, 0x08, 0x96, + 0xec, 0x18, 0xed, 0x36, 0x81, 0xf3, 0xd5, 0x26, 0x1b, 0x58, 0x1a, 0x34, 0x74, 0x3c, 0x14, 0xd7, + 0xe8, 0x0b, 0xb2, 0x40, 0xeb, 0x50, 0x2e, 0xa2, 0x46, 0x95, 0x77, 0xdb, 0x11, 0x24, 0x94, 0xca, + 0x73, 0xb5, 0x7e, 0xe7, 0x1b, 0x6c, 0x61, 0xd6, 0x37, 0x47, 0xc6, 0x11, 0xae, 0x36, 0x57, 0x57, + 0xe0, 0x61, 0x36, 0x6c, 0x2b, 0x9d, 0x23, 0x1e, 0xf4, 0xc0, 0xf8, 0x4e, 0x40, 0xda, 0xb8, 0x33, + 0xdf, 0xf1, 0x30, 0xea, 0xa0, 0x6b, 0xbb, 0x05, 0x71, 0x77, 0x43, 0x03, 0x7a, 0xe2, 0x6e, 0xcc, + 0xc2, 0x21, 0x5f, 0xae, 0x4d, 0xe4, 0xa0, 0x2c, 0xd2, 0x25, 0xc4, 0x37, 0x4a, 0xd7, 0x99, 0x3d, + 0xbd, 0xca, 0x57, 0xef, 0x6c, 0x91, 0xde, 0x66, 0x81, 0xd9, 0xd2, 0x4d, 0x16, 0xfa, 0xc1, 0x64, + 0x35, 0x5d, 0x4f, 0x3c, 0xc4, 0x40, 0xaf, 0x10, 0x8d, 0x7b, 0x5a, 0xbe, 0x75, 0xae, 0x7d, 0xeb, + 0x5c, 0x3b, 0xf5, 0xa5, 0xab, 0xbb, 0x82, 0xb8, 0x82, 0xba, 0x82, 0xb9, 0x82, 0xbb, 0xc2, 0xf7, + 0x4a, 0x57, 0x28, 0x57, 0x68, 0x4f, 0x5c, 0xb3, 0x7b, 0x1e, 0xec, 0x99, 0xb0, 0xe7, 0xc2, 0x9e, + 0xcd, 0x3b, 0x78, 0x9c, 0x16, 0xb1, 0xf7, 0x60, 0x87, 0x64, 0x19, 0x78, 0x86, 0x9b, 0xe2, 0x78, + 0xbc, 0x9c, 0x0a, 0x36, 0x49, 0x92, 0x55, 0xbc, 0x99, 0x2d, 0x93, 0x64, 0x9e, 0xd9, 0xd9, 0x6a, + 0x5e, 0xeb, 0xed, 0x11, 0xfa, 0xdb, 0xd9, 0xf2, 0xec, 0x8c, 0xcc, 0xb7, 0x9e, 0x63, 0x3a, 0xcd, + 0x67, 0x74, 0xec, 0x04, 0x04, 0x21, 0xf3, 0x88, 0x80, 0x68, 0x01, 0xaa, 0xbc, 0x28, 0x16, 0xe6, + 0xab, 0xf5, 0x65, 0xfe, 0xa5, 0x8d, 0x56, 0x71, 0x3c, 0x85, 0x15, 0xe3, 0x75, 0x33, 0x91, 0xc5, + 0xab, 0xc5, 0x94, 0x20, 0xa6, 0x5e, 0x47, 0x4d, 0x03, 0xd6, 0x64, 0xbb, 0x38, 0x3b, 0x13, 0x69, + 0xfb, 0x9b, 0xa8, 0xad, 0xa0, 0xe3, 0x45, 0x0c, 0x84, 0x9c, 0x13, 0x2d, 0xb6, 0xd0, 0x9d, 0x71, + 0x49, 0x19, 0xeb, 0x78, 0x08, 0x61, 0x8e, 0x07, 0x93, 0x90, 0xc9, 0x09, 0x19, 0x0b, 0x7a, 0x57, + 0x0e, 0xcc, 0x48, 0x70, 0x4e, 0x45, 0x12, 0x45, 0x18, 0x11, 0xd7, 0x34, 0x9d, 0x62, 0xb4, 0xf5, + 0xf5, 0xc1, 0x54, 0xdd, 0xf4, 0xe3, 0x96, 0x9f, 0x30, 0xe4, 0xc7, 0x50, 0x7b, 0x63, 0x60, 0xb2, + 0x37, 0xc8, 0xa1, 0x71, 0x9f, 0xa4, 0xee, 0x43, 0xda, 0xea, 0xcc, 0xb0, 0xe8, 0x6d, 0xd0, 0x69, + 0xae, 0xeb, 0x8c, 0x5a, 0xe5, 0xc5, 0x3d, 0x55, 0x1a, 0xf4, 0xb6, 0x53, 0xe9, 0x7b, 0xef, 0xd5, + 0x6a, 0xcf, 0xd1, 0x29, 0xf6, 0xb0, 0xc0, 0x06, 0xee, 0x33, 0x46, 0x7c, 0xa6, 0xfe, 0x83, 0xd9, + 0xb4, 0x26, 0x78, 0x70, 0x36, 0xde, 0x20, 0xff, 0xc7, 0xf9, 0xd6, 0x59, 0xf8, 0x6a, 0x51, 0x59, + 0x97, 0x52, 0xbf, 0x59, 0x5d, 0xe6, 0x1f, 0xb2, 0x65, 0xda, 0xe7, 0xdb, 0x24, 0x5b, 0x9e, 0xf4, + 0xf6, 0x4c, 0x97, 0x67, 0x99, 0x00, 0x1d, 0x06, 0xa9, 0x79, 0x79, 0x22, 0x58, 0xda, 0x65, 0x84, + 0x28, 0x4e, 0x07, 0xd9, 0x01, 0xc5, 0xaf, 0x42, 0xda, 0x5d, 0xb3, 0x76, 0xf6, 0xf2, 0xcf, 0x18, + 0xd1, 0x4c, 0x0b, 0x49, 0x34, 0xef, 0x32, 0xcb, 0x9b, 0x7a, 0xe0, 0x9e, 0xe8, 0x8b, 0x8e, 0x48, + 0x4c, 0xa7, 0x28, 0xdd, 0x5f, 0x0d, 0x7c, 0xf5, 0xd7, 0x3d, 0x4d, 0x3d, 0x87, 0xdd, 0x2e, 0x0d, + 0x33, 0x7e, 0x9b, 0xd5, 0xb3, 0x60, 0x23, 0x79, 0x5f, 0xca, 0xdf, 0x5b, 0x51, 0x8e, 0x51, 0xbd, + 0xc3, 0x1c, 0xa4, 0x49, 0x93, 0xdd, 0xd5, 0xda, 0xc4, 0xce, 0x4c, 0xad, 0xe7, 0x0a, 0x74, 0x6b, + 0xe6, 0xa9, 0x01, 0x8f, 0x13, 0xc3, 0x6c, 0xb9, 0xdd, 0x0e, 0x14, 0xe5, 0xf2, 0xa6, 0xf5, 0x79, + 0xd3, 0x97, 0xae, 0xee, 0x0a, 0xe2, 0x0a, 0xea, 0x0a, 0xe6, 0x0a, 0xee, 0x0a, 0xdf, 0x2b, 0x5d, + 0xa1, 0x5c, 0xa1, 0x3d, 0x71, 0xcd, 0xee, 0x79, 0xb0, 0x67, 0xc2, 0x9e, 0x0b, 0x7b, 0xb6, 0x26, + 0x6f, 0x36, 0x3f, 0x3b, 0x35, 0x4d, 0xa7, 0x34, 0xad, 0x3b, 0x03, 0x5d, 0x43, 0xe3, 0x36, 0x30, + 0x09, 0xc0, 0xd0, 0xa1, 0x49, 0xf7, 0x34, 0xe9, 0x1a, 0x43, 0x2d, 0x36, 0x3b, 0xf0, 0xb4, 0x4c, + 0x61, 0xa9, 0x4c, 0x73, 0xd8, 0x04, 0x07, 0xba, 0x9a, 0xf4, 0x6b, 0xf0, 0x6b, 0x93, 0x45, 0x91, + 0xfb, 0xb3, 0xfe, 0x6f, 0x01, 0xf0, 0x4f, 0x84, 0x42, 0x4a, 0x0a, 0x4d, 0x25, 0xf8, 0xa4, 0xdc, + 0x5a, 0x37, 0x34, 0x8f, 0x4f, 0x88, 0xc4, 0x92, 0x52, 0x25, 0x35, 0x58, 0x3b, 0x3e, 0x8f, 0x4a, + 0xa0, 0x87, 0xbf, 0xbe, 0xf9, 0x3c, 0x2a, 0xa0, 0x09, 0xfe, 0x4e, 0xa0, 0x81, 0x70, 0xc5, 0xb0, + 0x66, 0xe7, 0x04, 0x21, 0x46, 0xb1, 0x42, 0x12, 0x8f, 0x6d, 0x9c, 0x2c, 0x40, 0x97, 0x27, 0x18, + 0x4b, 0x45, 0x91, 0x44, 0x2e, 0xa4, 0x30, 0xd9, 0x16, 0x4e, 0x3c, 0x8a, 0x13, 0xeb, 0xc4, 0x8e, + 0x43, 0x79, 0xb0, 0x62, 0x02, 0x07, 0x71, 0x1c, 0x44, 0x30, 0x60, 0x92, 0xdc, 0xb1, 0xc8, 0x6d, + 0x09, 0x2c, 0x98, 0xc7, 0x49, 0x51, 0xb3, 0x14, 0x0d, 0x25, 0x05, 0x4a, 0x8a, 0x05, 0xe1, 0x9a, + 0x20, 0x0d, 0x94, 0x84, 0x6c, 0x8d, 0xa3, 0x04, 0xe1, 0x25, 0x50, 0xbe, 0x8a, 0x1a, 0x7f, 0x29, + 0x50, 0xeb, 0x2b, 0x05, 0x86, 0x93, 0x48, 0xb3, 0xc3, 0x49, 0xbd, 0x22, 0x92, 0x56, 0x13, 0x89, + 0xc7, 0xd2, 0xec, 0x7a, 0xe2, 0x73, 0x33, 0x8e, 0xca, 0xf3, 0xc2, 0x0f, 0x13, 0x28, 0x48, 0x84, + 0x0a, 0x4a, 0x4c, 0xa0, 0x18, 0x60, 0x37, 0x8d, 0x42, 0xa0, 0x5a, 0x9e, 0xdb, 0x31, 0xfc, 0x2e, + 0xe3, 0x46, 0x05, 0x54, 0x69, 0x2e, 0x18, 0x57, 0xe2, 0x5e, 0x15, 0xd8, 0x73, 0xd3, 0x20, 0x4f, + 0x04, 0x12, 0x18, 0x71, 0x85, 0xf5, 0xa3, 0xc0, 0x11, 0x63, 0x9c, 0x70, 0x4a, 0xd1, 0x5d, 0xe0, + 0xf1, 0x5d, 0x68, 0x45, 0x08, 0x88, 0x01, 0xbb, 0x14, 0x0c, 0x2b, 0xa5, 0xe5, 0xe7, 0x00, 0xe2, + 0xf3, 0x04, 0x83, 0x9d, 0x91, 0x42, 0x8c, 0x3c, 0x8c, 0x48, 0xc0, 0x48, 0x0c, 0x9c, 0x83, 0x62, + 0xca, 0xf0, 0x83, 0x90, 0xe4, 0xfc, 0x84, 0x71, 0x89, 0xb8, 0x56, 0xf4, 0x2e, 0xa0, 0x47, 0xf0, + 0x28, 0x98, 0x8f, 0x94, 0x08, 0x51, 0xce, 0xf0, 0x67, 0x59, 0x48, 0xc3, 0x34, 0xb5, 0x73, 0x60, + 0xc6, 0xb0, 0x7c, 0x10, 0x10, 0x44, 0xfb, 0x09, 0x23, 0xc8, 0xe7, 0xf6, 0xc3, 0x60, 0xb0, 0xf3, + 0x7a, 0xad, 0x11, 0x43, 0x30, 0x21, 0xf2, 0x6c, 0x3c, 0x90, 0x46, 0x12, 0x08, 0x21, 0x26, 0x10, + 0x15, 0x8a, 0x7c, 0x96, 0xcb, 0x81, 0x87, 0x30, 0x04, 0x3a, 0xc7, 0x08, 0x3f, 0x8c, 0xc7, 0xf9, + 0x02, 0x47, 0x60, 0x4d, 0x42, 0x34, 0x7a, 0x18, 0x94, 0x37, 0x3b, 0x15, 0x9c, 0x72, 0x4a, 0xee, + 0x09, 0xb7, 0x1e, 0x54, 0x3b, 0x25, 0x98, 0xcd, 0xb8, 0x87, 0x08, 0xe8, 0x06, 0x31, 0x81, 0x05, + 0x97, 0x5a, 0x70, 0xec, 0x46, 0xe5, 0x35, 0x40, 0xd9, 0x02, 0xec, 0xbd, 0x07, 0x09, 0xcd, 0x11, + 0x16, 0xd4, 0xa9, 0x41, 0xd7, 0x28, 0x68, 0x83, 0xc2, 0x01, 0xee, 0x34, 0x61, 0x9d, 0x02, 0x03, + 0xfd, 0x27, 0x82, 0x51, 0x48, 0x2c, 0x12, 0x7b, 0x4b, 0xb1, 0x1a, 0x94, 0x6a, 0x40, 0xf5, 0x81, + 0x4d, 0x25, 0x84, 0xa6, 0xa4, 0xc8, 0x1b, 0x09, 0xd5, 0x78, 0xc8, 0x67, 0xe0, 0xe1, 0xf3, 0x13, + 0x89, 0x30, 0x87, 0x08, 0xd7, 0xf8, 0x20, 0x1e, 0x70, 0x9e, 0x84, 0x2a, 0x40, 0x83, 0xbc, 0x93, + 0x3f, 0x1d, 0x0e, 0x48, 0x17, 0x02, 0xb9, 0x8c, 0x48, 0xf9, 0x61, 0x38, 0xcc, 0xd9, 0x1c, 0x36, + 0x3d, 0x52, 0x31, 0xf5, 0x42, 0x38, 0x7a, 0x9e, 0x70, 0xa1, 0x18, 0x03, 0x25, 0xaa, 0xc3, 0x70, + 0x9c, 0xef, 0x20, 0xac, 0x15, 0xb8, 0xa9, 0xf7, 0x9d, 0x27, 0x03, 0x72, 0xf9, 0x4b, 0x49, 0x2a, + 0xa8, 0x16, 0xf8, 0x30, 0x1e, 0x17, 0xd6, 0x60, 0x79, 0x4e, 0xc1, 0x41, 0xf1, 0x0b, 0x01, 0xb9, + 0x80, 0x80, 0xe8, 0x66, 0x42, 0x61, 0x26, 0xe4, 0x41, 0x48, 0xb0, 0xf6, 0x70, 0x0c, 0x81, 0x03, + 0x2a, 0x7c, 0x0e, 0x1e, 0xe9, 0x12, 0x90, 0x0b, 0x0b, 0x05, 0xc9, 0xee, 0x30, 0x20, 0xec, 0x96, + 0x36, 0x4d, 0xc0, 0x94, 0x12, 0x36, 0xc1, 0x0f, 0x21, 0xca, 0xb3, 0x0e, 0x54, 0x54, 0x65, 0x0e, + 0xc4, 0x79, 0x87, 0xab, 0x02, 0x69, 0xb5, 0xc7, 0xc1, 0x34, 0xc1, 0xe3, 0x40, 0x0e, 0xab, 0xc1, + 0xa8, 0x06, 0x4c, 0xad, 0xbc, 0x13, 0x02, 0x31, 0xcd, 0x25, 0x60, 0xf6, 0x53, 0xc2, 0x35, 0x18, + 0xdc, 0x81, 0x09, 0xd2, 0x48, 0x0e, 0xf0, 0x9b, 0xb0, 0xc1, 0x8a, 0x6a, 0x44, 0x11, 0xf7, 0x69, + 0x0b, 0x8b, 0x1a, 0x87, 0x68, 0x70, 0xb4, 0x56, 0x07, 0xa8, 0x48, 0x73, 0xee, 0x72, 0x2d, 0xa1, + 0x1e, 0x82, 0xfe, 0x0c, 0x04, 0x2e, 0x07, 0x38, 0x41, 0xb0, 0x7a, 0x0b, 0x74, 0x3f, 0x0a, 0xe6, + 0x52, 0x8e, 0x24, 0x4a, 0x83, 0xe3, 0x3f, 0x15, 0x85, 0x74, 0x62, 0x39, 0xd3, 0xb2, 0x4e, 0x1a, + 0xf7, 0x62, 0x40, 0xce, 0x73, 0x35, 0x18, 0x0b, 0x09, 0x86, 0x5e, 0x88, 0x82, 0x42, 0xae, 0x51, + 0x30, 0x4b, 0x8d, 0x25, 0xbb, 0x1f, 0x85, 0xcb, 0x33, 0x5c, 0x41, 0x60, 0x12, 0x42, 0x9e, 0x08, + 0x02, 0xdc, 0x15, 0xa8, 0x39, 0xc1, 0x5a, 0xea, 0x03, 0x20, 0xc4, 0x3c, 0x91, 0x02, 0x11, 0x8d, + 0x95, 0x7e, 0x19, 0x02, 0x58, 0xfd, 0x40, 0x0b, 0x54, 0x30, 0xa6, 0xe4, 0xfd, 0x00, 0x9c, 0xe3, + 0x32, 0x82, 0x15, 0xe6, 0xaa, 0x4e, 0x40, 0x4f, 0x71, 0x26, 0x58, 0x2e, 0xc0, 0xba, 0x92, 0x01, + 0x0a, 0x74, 0x00, 0x02, 0x88, 0xd5, 0x1a, 0xf6, 0x30, 0x0a, 0x96, 0x81, 0x07, 0x30, 0xf4, 0xf1, + 0x1d, 0x46, 0x77, 0x64, 0xb6, 0x9f, 0xba, 0x3c, 0x0e, 0x59, 0x48, 0x50, 0x04, 0x19, 0x0b, 0xc4, + 0x88, 0x1a, 0x80, 0x68, 0x00, 0x6c, 0x3f, 0x95, 0x5d, 0x90, 0xc2, 0xbe, 0x52, 0x69, 0xcc, 0x30, + 0xaf, 0x8f, 0x83, 0x1e, 0x04, 0x69, 0x41, 0x84, 0xc1, 0x1d, 0x15, 0xdb, 0x4f, 0x41, 0x96, 0x63, + 0x2e, 0x0f, 0x31, 0x8d, 0x3c, 0x1f, 0xaf, 0x81, 0xc8, 0x06, 0xc8, 0xf6, 0x93, 0x6d, 0x97, 0x00, + 0x88, 0x36, 0xea, 0x52, 0xb0, 0xc3, 0x82, 0xeb, 0xd0, 0xc6, 0xcf, 0x05, 0xe3, 0x17, 0x7f, 0xd8, + 0xcc, 0x30, 0x88, 0x6d, 0x89, 0x0f, 0xc3, 0xf1, 0x39, 0x14, 0xce, 0x5a, 0x0a, 0xd2, 0x89, 0x78, + 0x06, 0x1c, 0xef, 0xfa, 0x1c, 0x73, 0x42, 0x1f, 0xc0, 0x82, 0x5d, 0xf6, 0x00, 0xc0, 0x04, 0xdc, + 0x5a, 0xbf, 0x04, 0x8d, 0xcb, 0xe1, 0x90, 0xe9, 0x61, 0xff, 0x47, 0xb9, 0x3e, 0x0c, 0xc6, 0xad, + 0x70, 0x10, 0x8b, 0x70, 0x52, 0xf0, 0x16, 0x7c, 0x22, 0x16, 0xb7, 0x3f, 0xe0, 0xc2, 0xad, 0x40, + 0xb0, 0xaa, 0x3e, 0x84, 0x06, 0x42, 0x14, 0x43, 0xa2, 0xc1, 0x1c, 0x0b, 0xf6, 0x22, 0x38, 0xde, + 0x17, 0x38, 0xe8, 0x0e, 0x8e, 0x33, 0x0f, 0xa0, 0xf1, 0x87, 0x1e, 0x48, 0xbf, 0x18, 0x56, 0x12, + 0xfd, 0x74, 0x3c, 0x60, 0x79, 0x09, 0xda, 0x52, 0x92, 0x78, 0x5d, 0x1d, 0x82, 0x03, 0xf1, 0x4a, + 0xc1, 0xcf, 0xc0, 0x3f, 0xea, 0xa8, 0xd9, 0x03, 0x13, 0x1c, 0xf5, 0xa2, 0xee, 0x72, 0x37, 0xe9, + 0x8e, 0x68, 0xb4, 0xa3, 0x29, 0x70, 0x66, 0x06, 0x87, 0xbc, 0xb4, 0xbd, 0xfb, 0x2d, 0x83, 0x13, + 0x5d, 0xdf, 0x4e, 0xb3, 0x22, 0x69, 0xc9, 0xd5, 0x70, 0xa4, 0xec, 0x18, 0xc7, 0xaf, 0xfa, 0xd1, + 0xea, 0xaf, 0xc7, 0xda, 0x0d, 0xd5, 0x7c, 0xbd, 0xa6, 0xba, 0x81, 0x9a, 0x6f, 0x30, 0xf1, 0xf6, + 0xc6, 0xb9, 0xa0, 0x35, 0xe8, 0xbd, 0x43, 0xaf, 0xbb, 0x17, 0xe9, 0xcf, 0xbc, 0x83, 0x5b, 0x82, + 0x28, 0x0e, 0xef, 0x08, 0x0e, 0x9c, 0xf9, 0xda, 0x9b, 0x6b, 0x3a, 0x69, 0xee, 0x40, 0x8a, 0x19, + 0x98, 0x90, 0x8d, 0x5d, 0xb6, 0x2a, 0xc0, 0x0d, 0xc7, 0xd6, 0x7d, 0xad, 0xbf, 0xe1, 0xa9, 0xdb, + 0xa0, 0xae, 0xfa, 0x2a, 0x41, 0x7d, 0x1d, 0x8b, 0xa0, 0x3d, 0xa4, 0x69, 0xc5, 0x99, 0x81, 0x64, + 0xd3, 0x34, 0x75, 0x92, 0x4d, 0x2f, 0xd9, 0x04, 0x92, 0x4d, 0x20, 0xd9, 0x04, 0x92, 0x4d, 0x20, + 0xb9, 0x1c, 0x48, 0x2e, 0x9b, 0xa6, 0x4e, 0x72, 0xd9, 0x4b, 0x2e, 0x03, 0xc9, 0x65, 0x20, 0xb9, + 0x0c, 0x24, 0x97, 0x81, 0xe4, 0x6a, 0x20, 0xb9, 0x6a, 0x9a, 0x3a, 0xc9, 0x55, 0x2f, 0xb9, 0x0a, + 0x24, 0x57, 0x81, 0xe4, 0x2a, 0x90, 0x5c, 0xb5, 0x92, 0x87, 0x66, 0x6c, 0x1f, 0x9d, 0xb2, 0x3d, + 0xdb, 0x0e, 0x88, 0xea, 0x37, 0xbb, 0x97, 0x99, 0xbb, 0xd8, 0x33, 0xf7, 0x8c, 0x70, 0x30, 0x71, + 0xea, 0x6d, 0x0a, 0xd5, 0xb4, 0xb6, 0x62, 0x53, 0x83, 0x99, 0xba, 0x9a, 0x23, 0x31, 0xa9, 0x69, + 0x49, 0x4c, 0x47, 0x62, 0x06, 0x24, 0x65, 0x5a, 0xb6, 0x24, 0x65, 0x47, 0x52, 0x0e, 0x48, 0x8a, + 0xb4, 0x68, 0x49, 0x8a, 0x8e, 0xa4, 0x68, 0x49, 0xf6, 0x74, 0xe2, 0x1f, 0x24, 0xb3, 0xbb, 0x0a, + 0xb8, 0x4b, 0x54, 0x3f, 0x33, 0x3c, 0xae, 0x98, 0xfd, 0x07, 0x26, 0x58, 0x7a, 0x41, 0x49, 0xfb, + 0xef, 0x46, 0x36, 0x6e, 0x43, 0xc1, 0xf8, 0x9b, 0xb0, 0x56, 0x9b, 0xfe, 0x16, 0xac, 0x51, 0xa8, + 0xf1, 0xb7, 0x61, 0xed, 0xb5, 0x89, 0xbf, 0x15, 0xab, 0xd5, 0x9a, 0xda, 0x21, 0x88, 0xfa, 0xc9, + 0x22, 0x3b, 0x30, 0xe5, 0x21, 0xa9, 0x7f, 0x72, 0x0d, 0x51, 0xb4, 0xef, 0x23, 0xcd, 0x2d, 0x56, + 0x36, 0x1a, 0xb5, 0xe1, 0xea, 0x65, 0x44, 0x71, 0x9a, 0x67, 0x68, 0x92, 0x43, 0x5e, 0x9c, 0xc4, + 0x36, 0xab, 0x66, 0xb9, 0xbb, 0x57, 0x35, 0x6d, 0xa5, 0x6c, 0x2b, 0x90, 0xbf, 0x97, 0x33, 0x9f, + 0x97, 0xe7, 0xc9, 0x72, 0x26, 0x28, 0x1c, 0x76, 0xdd, 0xa6, 0xc6, 0xe5, 0x48, 0x16, 0xb7, 0x4d, + 0x90, 0x85, 0x88, 0xcf, 0xae, 0xa2, 0x6d, 0x2a, 0xe7, 0xad, 0x1e, 0xbc, 0xec, 0x7d, 0x39, 0x4e, + 0xc6, 0x58, 0xc0, 0x42, 0x32, 0xca, 0xb2, 0xd1, 0x2e, 0x6d, 0x1e, 0xb1, 0x7f, 0xbe, 0xe4, 0x77, + 0xee, 0xe0, 0xb2, 0xdb, 0xc8, 0xbf, 0x3e, 0xc7, 0x13, 0x7b, 0x7a, 0x61, 0xf2, 0xc1, 0x03, 0xd7, + 0xf0, 0xd9, 0x72, 0x97, 0xda, 0x3b, 0x4f, 0x60, 0xfd, 0xcb, 0x66, 0xcb, 0x1d, 0x05, 0x4f, 0x9b, + 0xbb, 0x49, 0xab, 0x24, 0x03, 0xaa, 0x30, 0xd3, 0x45, 0xf3, 0x80, 0x34, 0x49, 0x12, 0x53, 0x0f, + 0x5f, 0x66, 0x8b, 0x99, 0x99, 0x4f, 0xec, 0xac, 0x9c, 0xc3, 0x44, 0xca, 0xb8, 0xbb, 0xeb, 0xdd, + 0x45, 0x31, 0xfc, 0x9f, 0xfc, 0xea, 0x7f, 0xf1, 0xc2, 0x99, 0x50, 0xc3, 0x1f, 0x00, 0x00, +}; +const StaticFile md5_js PROGMEM = {(sizeof(md5_js_content)/sizeof(md5_js_content[0])), md5_js_content}; + static const uint8_t style_css_content[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x53, 0x5d, 0x6b, 0xdb, 0x30, - 0x14, 0x7d, 0xdf, 0xaf, 0x08, 0x94, 0x41, 0x0b, 0x76, 0xb0, 0x9b, 0x26, 0x59, 0x64, 0xf6, 0xb0, - 0x3d, 0x8c, 0xed, 0x61, 0x4f, 0x65, 0x4f, 0xa3, 0x14, 0x7d, 0x5c, 0xd9, 0x22, 0xb2, 0x25, 0xa4, - 0xeb, 0x26, 0x99, 0xf1, 0x7f, 0x9f, 0xfc, 0x95, 0x26, 0x4d, 0x06, 0x65, 0x18, 0x04, 0xf7, 0x43, - 0x3a, 0xe7, 0x9e, 0x7b, 0xcc, 0x8c, 0x38, 0x44, 0x05, 0x96, 0xba, 0xb1, 0x54, 0x08, 0x55, 0xe5, - 0x24, 0xc9, 0x4a, 0xea, 0x72, 0x55, 0x91, 0xa4, 0x65, 0x5d, 0x91, 0xd5, 0x88, 0xa6, 0x8a, 0x54, - 0x65, 0x6b, 0xfc, 0x8d, 0x07, 0x0b, 0x9f, 0x2d, 0xf5, 0x7e, 0x67, 0x9c, 0x78, 0x3a, 0x4d, 0x22, - 0xec, 0xf1, 0xa9, 0x91, 0xa6, 0xc2, 0xd8, 0xab, 0x3f, 0x40, 0xd2, 0x95, 0xdd, 0x67, 0x7d, 0x28, - 0x69, 0xa9, 0xf4, 0x81, 0xc4, 0xd4, 0x5a, 0x0d, 0xb1, 0x3f, 0x78, 0x84, 0x32, 0xfa, 0xaa, 0x55, - 0xb5, 0xfd, 0x49, 0xf9, 0x63, 0x1f, 0x7e, 0x0b, 0x7d, 0xd1, 0x23, 0xe4, 0x06, 0x66, 0xbf, 0x7e, - 0x44, 0xdf, 0x41, 0xbf, 0x00, 0x2a, 0x4e, 0xa3, 0x2f, 0x4e, 0x51, 0x1d, 0x79, 0x5a, 0xf9, 0xd8, - 0x83, 0x53, 0xb2, 0x9d, 0xa3, 0x42, 0x0d, 0x47, 0xae, 0x69, 0x62, 0xf7, 0xb3, 0xfe, 0x38, 0xa2, - 0xed, 0x40, 0xe5, 0x05, 0x92, 0x55, 0x92, 0x64, 0x8c, 0xf2, 0x6d, 0xee, 0x4c, 0x5d, 0x89, 0x98, - 0x1b, 0x6d, 0x1c, 0xb9, 0x01, 0x29, 0xef, 0xe5, 0x32, 0x63, 0x81, 0x3c, 0xb8, 0x98, 0x99, 0x30, - 0x59, 0x49, 0xd2, 0x70, 0xdd, 0x1b, 0xad, 0xc4, 0xec, 0x46, 0x6c, 0x20, 0x81, 0x75, 0x36, 0x76, - 0xdf, 0xaf, 0x57, 0xc0, 0x1e, 0xb2, 0x93, 0x99, 0x96, 0x76, 0xdf, 0xce, 0x99, 0x36, 0x7c, 0x7b, - 0x46, 0xa1, 0x9d, 0xcb, 0x5a, 0xeb, 0x78, 0xa7, 0x04, 0x16, 0x4d, 0x7f, 0x86, 0x74, 0xf2, 0x31, - 0xe0, 0xec, 0xbb, 0x8b, 0x5d, 0xdb, 0x11, 0xb2, 0x6b, 0x36, 0xae, 0x7c, 0xd6, 0x94, 0xc1, 0x89, - 0xe8, 0xb3, 0x64, 0xb6, 0xb8, 0x1c, 0x61, 0xec, 0xed, 0x65, 0x6e, 0x86, 0xb5, 0x1c, 0x59, 0xf7, - 0x5c, 0xfa, 0x32, 0x2f, 0x80, 0x6f, 0xc3, 0xcb, 0xd3, 0x6b, 0x31, 0x1a, 0x4b, 0xc2, 0x6b, 0xed, - 0xbb, 0x76, 0x16, 0x79, 0xd0, 0xc0, 0xb1, 0x19, 0x19, 0x3a, 0x2a, 0x54, 0xed, 0xc9, 0x43, 0x20, - 0x33, 0x64, 0x4e, 0xf5, 0xe1, 0x1b, 0xce, 0xb9, 0xcc, 0x26, 0xd6, 0xeb, 0x50, 0xd9, 0x84, 0x46, - 0x53, 0x63, 0x58, 0x27, 0x04, 0xc7, 0x5c, 0x43, 0x24, 0xd2, 0xf0, 0xda, 0x5f, 0xe0, 0x8e, 0xe9, - 0x01, 0x7d, 0x08, 0x9a, 0x5e, 0xb0, 0x82, 0x0a, 0xb3, 0xeb, 0x15, 0xe9, 0x37, 0xeb, 0x72, 0x46, - 0x6f, 0x93, 0xa8, 0xfb, 0xe6, 0xe9, 0xf2, 0xae, 0x1d, 0xfc, 0x48, 0x84, 0xf2, 0x94, 0x69, 0x10, - 0x57, 0x8d, 0x79, 0xb5, 0x3a, 0xa0, 0x1e, 0x2b, 0x23, 0xf0, 0x14, 0x37, 0x97, 0x6e, 0x91, 0x69, - 0x70, 0xcb, 0x62, 0x72, 0xcb, 0x59, 0x72, 0x64, 0xf1, 0xbf, 0xa2, 0x75, 0xcb, 0x7b, 0x55, 0xed, - 0xc4, 0xa8, 0xe1, 0x7d, 0x29, 0x27, 0xff, 0x25, 0xc1, 0xc3, 0xd6, 0x78, 0x85, 0x2a, 0xcc, 0xeb, - 0x40, 0x53, 0x54, 0x2f, 0x90, 0x75, 0x77, 0xe2, 0x62, 0xb0, 0x48, 0xfa, 0xe9, 0x8a, 0x67, 0x46, - 0x81, 0x2a, 0x83, 0xb7, 0xc7, 0xe9, 0xee, 0x48, 0x61, 0x5e, 0xc0, 0xbd, 0x47, 0xe0, 0x8c, 0xd7, - 0xce, 0x07, 0x78, 0x6b, 0x54, 0x85, 0xe0, 0xde, 0x8c, 0xcf, 0x96, 0x9c, 0xc3, 0xe2, 0xfc, 0x0f, - 0xf9, 0x07, 0x22, 0xe5, 0x1d, 0xdf, 0xa6, 0x33, 0x63, 0x50, 0x63, 0x6c, 0x9a, 0x2b, 0xff, 0xec, - 0xc0, 0x03, 0x46, 0x6f, 0xe2, 0xeb, 0x7c, 0xa7, 0xff, 0x76, 0xb5, 0xd8, 0xa4, 0xeb, 0xf6, 0xc3, - 0x5f, 0xa4, 0x85, 0xb2, 0x78, 0xae, 0x04, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x53, 0x5d, 0x6b, 0xdb, 0x30, + 0x14, 0x7d, 0xdf, 0xaf, 0x08, 0x94, 0x41, 0x0b, 0x76, 0xb0, 0x9b, 0x26, 0x59, 0x64, 0xf6, 0xb0, + 0x3d, 0x8c, 0xed, 0x61, 0x4f, 0x65, 0x4f, 0xa3, 0x14, 0x7d, 0x5c, 0xd9, 0x22, 0xb2, 0x25, 0xa4, + 0xeb, 0x26, 0x99, 0xf1, 0x7f, 0x9f, 0xfc, 0x11, 0x37, 0x69, 0x32, 0x28, 0xc3, 0x20, 0xb8, 0x1f, + 0xd2, 0x39, 0xf7, 0xdc, 0x63, 0x66, 0xc4, 0x21, 0x2a, 0xb0, 0xd4, 0x8d, 0xa5, 0x42, 0xa8, 0x2a, + 0x27, 0x49, 0x56, 0x52, 0x97, 0xab, 0x8a, 0x24, 0x2d, 0xeb, 0x8a, 0xac, 0x46, 0x34, 0x55, 0xa4, + 0x2a, 0x5b, 0xe3, 0x6f, 0x3c, 0x58, 0xf8, 0x6c, 0xa9, 0xf7, 0x3b, 0xe3, 0xc4, 0xd3, 0x69, 0x12, + 0x61, 0x8f, 0x4f, 0x8d, 0x34, 0x15, 0xc6, 0x5e, 0xfd, 0x01, 0x92, 0xae, 0xec, 0x3e, 0xeb, 0x43, + 0x49, 0x4b, 0xa5, 0x0f, 0x24, 0xa6, 0xd6, 0x6a, 0x88, 0xfd, 0xc1, 0x23, 0x94, 0xd1, 0x57, 0xad, + 0xaa, 0xed, 0x4f, 0xca, 0x1f, 0xfb, 0xf0, 0x5b, 0xe8, 0x8b, 0x1e, 0x21, 0x37, 0x30, 0xfb, 0xf5, + 0x23, 0xfa, 0x0e, 0xfa, 0x05, 0x50, 0x71, 0x1a, 0x7d, 0x71, 0x8a, 0xea, 0xc8, 0xd3, 0xca, 0xc7, + 0x1e, 0x9c, 0x92, 0xed, 0x1c, 0x15, 0x6a, 0x98, 0xb8, 0xa6, 0x89, 0xdd, 0xcf, 0xfa, 0x63, 0x42, + 0xdb, 0x81, 0xca, 0x0b, 0x24, 0xab, 0x24, 0xc9, 0x18, 0xe5, 0xdb, 0xdc, 0x99, 0xba, 0x12, 0x31, + 0x37, 0xda, 0x38, 0x72, 0x03, 0x52, 0xde, 0xcb, 0x65, 0xc6, 0x02, 0x79, 0x70, 0x31, 0x33, 0x61, + 0xb2, 0x92, 0xa4, 0xe1, 0xba, 0x37, 0x5a, 0x89, 0xd9, 0x8d, 0xd8, 0x40, 0x02, 0xeb, 0x6c, 0xec, + 0xbe, 0x5f, 0xaf, 0x80, 0x3d, 0x64, 0x27, 0x33, 0x2d, 0xed, 0xbe, 0x9d, 0x33, 0x6d, 0xf8, 0xf6, + 0x8c, 0x42, 0x3b, 0x97, 0xb5, 0xd6, 0xf1, 0x4e, 0x09, 0x2c, 0x9a, 0xfe, 0x0c, 0xe9, 0xe4, 0x63, + 0xc0, 0xd9, 0x77, 0x17, 0xbb, 0xb6, 0x09, 0xb2, 0x6b, 0x36, 0xae, 0x7c, 0xd6, 0x94, 0xc1, 0x89, + 0xe8, 0xb3, 0x64, 0xb6, 0xb8, 0x1c, 0x61, 0xec, 0xed, 0x65, 0x6e, 0x86, 0xb5, 0x4c, 0xac, 0x7b, + 0x2e, 0x7d, 0xd9, 0xd7, 0xec, 0xec, 0xb5, 0x18, 0x8d, 0x25, 0xe1, 0xb5, 0xf6, 0x5d, 0x3b, 0x8b, + 0x3c, 0x68, 0xe0, 0xd8, 0x8c, 0x0c, 0x1d, 0x15, 0xaa, 0xf6, 0xe4, 0x21, 0x90, 0x19, 0x32, 0xa7, + 0xfa, 0xf0, 0x0d, 0xe7, 0x5c, 0x66, 0x47, 0xd6, 0xeb, 0x50, 0xd9, 0x84, 0x46, 0x53, 0x63, 0x58, + 0x27, 0x04, 0xc7, 0x5c, 0x43, 0x24, 0xd2, 0xf0, 0xda, 0x5f, 0xe0, 0x8e, 0xe9, 0x01, 0x7d, 0x08, + 0x9a, 0x5e, 0xb0, 0x82, 0x0a, 0xb3, 0xeb, 0x15, 0xe9, 0x37, 0xeb, 0x72, 0x46, 0x6f, 0x93, 0xa8, + 0xfb, 0xe6, 0xe9, 0xf2, 0xae, 0x1d, 0xfc, 0x48, 0x84, 0xf2, 0x94, 0x69, 0x10, 0x57, 0x8d, 0x79, + 0xb5, 0x3a, 0xa0, 0x4e, 0x95, 0x11, 0xf8, 0x18, 0x37, 0x97, 0x6e, 0x91, 0x69, 0x70, 0xcb, 0xe2, + 0xe8, 0x96, 0xb3, 0xe4, 0xc8, 0xe2, 0x7f, 0x45, 0xeb, 0x96, 0xf7, 0xaa, 0xda, 0x89, 0x51, 0xc3, + 0xfb, 0x52, 0x1e, 0xfd, 0x97, 0x04, 0x0f, 0x5b, 0xe3, 0x15, 0xaa, 0x30, 0xaf, 0x03, 0x4d, 0x51, + 0xbd, 0x40, 0xd6, 0xdd, 0x89, 0x8b, 0xc1, 0x22, 0xe9, 0xa7, 0x2b, 0x9e, 0x19, 0x05, 0xaa, 0x0c, + 0xde, 0x4e, 0xd3, 0xdd, 0x91, 0xc2, 0xbc, 0x80, 0x7b, 0x8f, 0xc0, 0x19, 0xaf, 0x9d, 0x0f, 0xf0, + 0xd6, 0xa8, 0x0a, 0xc1, 0xbd, 0x19, 0x9f, 0x2d, 0x39, 0x87, 0xc5, 0xf9, 0x1f, 0xf2, 0x0f, 0x44, + 0xca, 0x3b, 0xbe, 0x4d, 0x67, 0xc6, 0xa0, 0xc6, 0xd8, 0x34, 0x57, 0xfe, 0xd9, 0x81, 0x07, 0x8c, + 0xde, 0xc4, 0xd7, 0xf9, 0x1e, 0xff, 0xdb, 0xd5, 0x62, 0x93, 0xae, 0xdb, 0x0f, 0x7f, 0x01, 0x37, + 0xdb, 0x6e, 0xf6, 0xae, 0x04, 0x00, 0x00, }; const StaticFile style_css PROGMEM = {(sizeof(style_css_content)/sizeof(style_css_content[0])), style_css_content}; static const uint8_t favicon_ico_content[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0x99, 0x4b, 0x48, 0x15, 0x61, - 0x14, 0xc7, 0xcf, 0xc5, 0x17, 0x2e, 0x4a, 0x57, 0xe5, 0x63, 0xe1, 0x85, 0x42, 0x23, 0x8c, 0x8c, - 0x20, 0x4d, 0x45, 0xdb, 0x59, 0x14, 0x2e, 0x7a, 0xab, 0x68, 0x1b, 0x17, 0xae, 0x24, 0x41, 0xf1, - 0x41, 0xa0, 0x41, 0xa1, 0x11, 0x28, 0x1a, 0x2e, 0x12, 0x72, 0xe7, 0x03, 0x09, 0x74, 0x15, 0x54, - 0x1b, 0x97, 0xd9, 0x53, 0x23, 0x8a, 0x5a, 0x94, 0x25, 0x59, 0x91, 0x82, 0xa0, 0x81, 0x99, 0x39, - 0xfd, 0x8f, 0x73, 0x46, 0xbf, 0xc6, 0xb9, 0x73, 0x67, 0xee, 0x9d, 0xab, 0x41, 0x1e, 0xf8, 0x71, - 0xe7, 0x7e, 0xe7, 0xf1, 0x9f, 0xb9, 0xdf, 0x7c, 0x8f, 0x99, 0x4b, 0xe4, 0xa3, 0x28, 0x4a, 0x4c, - 0x24, 0x7c, 0xfa, 0xa9, 0x2a, 0x9a, 0xe8, 0x08, 0x11, 0x25, 0x25, 0xe9, 0xdf, 0xdb, 0xe2, 0x89, - 0x7a, 0xd1, 0xe6, 0xf7, 0xeb, 0xdf, 0x07, 0x11, 0x97, 0xbe, 0x93, 0x68, 0x1f, 0x62, 0x70, 0x88, - 0x16, 0xbd, 0x7d, 0xd5, 0x10, 0x37, 0x43, 0x3a, 0x0e, 0x2c, 0x06, 0xdc, 0x16, 0x62, 0x9c, 0xa5, - 0xfc, 0x65, 0xb1, 0x60, 0x58, 0x88, 0x75, 0x99, 0x9b, 0x06, 0x2a, 0xc1, 0x1b, 0xa1, 0x52, 0xda, - 0x82, 0x19, 0xae, 0x90, 0xca, 0xc0, 0x04, 0x58, 0x02, 0x9a, 0xb0, 0x24, 0x6d, 0xa5, 0x12, 0x13, - 0xc8, 0xd8, 0x3f, 0xab, 0xe4, 0x99, 0x99, 0x95, 0xfa, 0x56, 0xe6, 0x17, 0x8d, 0x40, 0xb9, 0x06, - 0x13, 0x12, 0x6b, 0xb6, 0x4b, 0xa6, 0x73, 0x0e, 0xc4, 0x92, 0xc4, 0x9a, 0xed, 0xba, 0x83, 0x5c, - 0x83, 0x6b, 0x16, 0xf9, 0x1d, 0x2e, 0xf2, 0x3b, 0x2c, 0xf2, 0xdb, 0x5d, 0xe4, 0xb7, 0x9b, 0x72, - 0xf9, 0xfe, 0xea, 0x76, 0x91, 0xcf, 0xb1, 0x6a, 0x3f, 0x9e, 0x03, 0x5f, 0x5c, 0xe4, 0x73, 0xec, - 0x59, 0x25, 0xbf, 0x00, 0xdc, 0x02, 0x5d, 0xe0, 0x2e, 0x58, 0x00, 0xf7, 0xe5, 0x7b, 0x97, 0x1c, - 0x2f, 0x88, 0xaf, 0x4b, 0x62, 0x0b, 0x4c, 0xd7, 0xe0, 0x93, 0xcf, 0x7c, 0xf0, 0x99, 0xf4, 0x7b, - 0xc9, 0xb0, 0x52, 0x69, 0xcb, 0x37, 0xc5, 0x5a, 0x59, 0x1e, 0x98, 0x06, 0x25, 0x4a, 0x5b, 0x89, - 0xb4, 0xe5, 0xd9, 0xe4, 0x19, 0x96, 0x09, 0x9e, 0x83, 0x22, 0xa5, 0xad, 0x48, 0xda, 0x32, 0xcd, - 0xc1, 0x8b, 0x18, 0x55, 0x93, 0x71, 0x44, 0xa3, 0x51, 0x44, 0x2d, 0x3e, 0x1d, 0xa7, 0xc6, 0x79, - 0x3c, 0xcf, 0x60, 0x2a, 0xa2, 0x2c, 0x52, 0xe6, 0x99, 0x78, 0x57, 0xf3, 0x8c, 0xd9, 0x52, 0xc1, - 0x43, 0xf0, 0x40, 0x8e, 0xbd, 0xb6, 0x1c, 0xf0, 0x5e, 0xc8, 0x89, 0x40, 0xfd, 0x6c, 0xf0, 0x41, - 0xc8, 0xf6, 0xb0, 0x2e, 0x8f, 0x91, 0xbd, 0xa0, 0x0a, 0x7c, 0x15, 0xf8, 0x78, 0x0f, 0xd9, 0xcf, - 0x69, 0xc1, 0x8c, 0x7b, 0x9c, 0xbb, 0xaf, 0x0d, 0x3c, 0x05, 0xf3, 0xb4, 0x3e, 0x4e, 0xe6, 0xa5, - 0xad, 0x15, 0x1c, 0x24, 0xfb, 0xfb, 0xd6, 0xca, 0xf8, 0xbc, 0x78, 0x6c, 0x3a, 0x99, 0x17, 0xc7, - 0x25, 0xd6, 0xe9, 0xb5, 0xf0, 0xb9, 0x9c, 0x27, 0x7d, 0x5c, 0x38, 0x1d, 0xf7, 0xd3, 0x92, 0xe3, - 0xe4, 0x3a, 0xf8, 0x37, 0x79, 0xe1, 0xa2, 0xb6, 0x7a, 0x1d, 0x59, 0x41, 0x6a, 0x73, 0x5f, 0xb6, - 0x85, 0x50, 0xdb, 0xe0, 0x06, 0xd9, 0xaf, 0xc9, 0xe9, 0xa4, 0xf7, 0x5b, 0xa8, 0xf5, 0x9f, 0x48, - 0x8d, 0x40, 0x76, 0x0c, 0xfc, 0x08, 0xa3, 0x3e, 0xcf, 0xa5, 0x85, 0x36, 0xf5, 0xcf, 0x84, 0x51, - 0xdb, 0xe0, 0xb4, 0x4d, 0xfd, 0x72, 0x0f, 0xea, 0x97, 0xdb, 0xd4, 0xaf, 0xf0, 0xa0, 0x7e, 0xc5, - 0x16, 0xd6, 0x2f, 0xf3, 0xa0, 0x7e, 0xa0, 0x7d, 0x12, 0xcf, 0x57, 0x9d, 0x1e, 0xd4, 0xef, 0x94, - 0x5a, 0xaa, 0x61, 0x15, 0xa3, 0x5e, 0xf0, 0xd3, 0x83, 0xfa, 0x5c, 0xe3, 0x8e, 0xd4, 0x34, 0x8c, - 0xe7, 0x8d, 0x26, 0xf0, 0x58, 0x18, 0x03, 0xcf, 0x48, 0x5f, 0xea, 0x38, 0x67, 0x4a, 0xf1, 0x19, - 0x4c, 0x89, 0x6f, 0x46, 0x62, 0xc7, 0x14, 0x5f, 0x13, 0x6d, 0x9c, 0x8b, 0x78, 0x5c, 0xef, 0x06, - 0x29, 0xa4, 0x2f, 0xad, 0xbc, 0xc4, 0x8e, 0x80, 0x15, 0xd2, 0xe7, 0xe1, 0x64, 0xf1, 0xa5, 0xc8, - 0x71, 0xab, 0xf8, 0x46, 0x68, 0x7d, 0x39, 0x4e, 0x91, 0x1a, 0x4e, 0xf6, 0xed, 0x09, 0xa0, 0x5f, - 0x6a, 0x34, 0x58, 0xf8, 0x1b, 0xc4, 0xd7, 0x2f, 0xb1, 0x6e, 0x8d, 0x1f, 0x43, 0x06, 0xa4, 0x46, - 0xa3, 0x85, 0xbf, 0x51, 0x7c, 0x03, 0x12, 0xeb, 0xd6, 0xf8, 0x9c, 0xfa, 0xa4, 0x46, 0xbd, 0x85, - 0xbf, 0x5e, 0x7c, 0x7d, 0x14, 0xda, 0xf9, 0x73, 0xff, 0x5f, 0x01, 0x9f, 0xc0, 0x49, 0x0b, 0x3f, - 0xb7, 0x4d, 0x49, 0x4c, 0x94, 0x85, 0x7f, 0xd5, 0xb4, 0xab, 0xda, 0x2a, 0x2b, 0xd4, 0x4c, 0x8b, - 0x78, 0x94, 0x99, 0xc3, 0xa9, 0x4c, 0x52, 0xdc, 0x2a, 0xa3, 0x48, 0x63, 0x5a, 0x70, 0x2b, 0x04, - 0xc2, 0x6c, 0xdc, 0xb6, 0x98, 0x94, 0xb6, 0x06, 0x77, 0xbc, 0x9f, 0xf4, 0x89, 0x79, 0x6d, 0x1f, - 0x96, 0xb8, 0x71, 0x1f, 0x76, 0xea, 0x72, 0x51, 0x48, 0x48, 0xf9, 0x77, 0xe0, 0x2d, 0x48, 0x0b, - 0xb5, 0x4e, 0x18, 0xfa, 0xd5, 0xb4, 0x3e, 0x36, 0xab, 0xb7, 0x40, 0xbf, 0x56, 0xd1, 0xaf, 0xfd, - 0x0f, 0xf5, 0xeb, 0x14, 0xfd, 0xba, 0x4d, 0xd2, 0xe4, 0xdb, 0x9a, 0x9f, 0x35, 0x5f, 0x82, 0xdf, - 0x8a, 0x3e, 0x1f, 0xf3, 0x9e, 0x92, 0x9f, 0x23, 0x33, 0x22, 0xa0, 0xcb, 0x73, 0xe7, 0x10, 0xe9, - 0xf3, 0x46, 0xb0, 0x75, 0x82, 0x63, 0x06, 0xc1, 0x2e, 0x8f, 0xb4, 0xf3, 0x68, 0x7d, 0x3d, 0x71, - 0xc3, 0x77, 0x90, 0x1b, 0xa6, 0x76, 0x21, 0xe9, 0x7b, 0xb3, 0x50, 0xd7, 0x4c, 0xce, 0x2d, 0x08, - 0xe3, 0x37, 0x77, 0xb3, 0x97, 0x0f, 0xc4, 0x37, 0x90, 0x1c, 0x82, 0xfe, 0x90, 0x07, 0xda, 0x06, - 0x83, 0x2e, 0xb5, 0xf7, 0x9b, 0xee, 0xef, 0x70, 0xe1, 0x5a, 0x07, 0x5c, 0xe8, 0xbb, 0x79, 0x77, - 0xe3, 0x94, 0x9b, 0x2e, 0xf4, 0x5f, 0x47, 0x40, 0xff, 0x95, 0x43, 0x6d, 0xde, 0x23, 0x39, 0x19, - 0xe7, 0xa1, 0xf4, 0x41, 0xb4, 0x03, 0xfd, 0xd4, 0x08, 0x68, 0x1b, 0xa4, 0x38, 0xd0, 0xcf, 0x88, - 0xa0, 0x7e, 0xd0, 0xb9, 0x79, 0x5b, 0x7f, 0x5b, 0x7f, 0xab, 0xf4, 0x61, 0x17, 0xc1, 0xbd, 0x08, - 0xea, 0x73, 0xed, 0x0b, 0x01, 0xb4, 0xe3, 0xc1, 0xaf, 0x08, 0x6a, 0x1b, 0xf0, 0xff, 0x05, 0xf1, - 0x16, 0xfa, 0xfc, 0x50, 0x31, 0xbe, 0x09, 0xfa, 0xfc, 0x0e, 0xcd, 0x67, 0xf3, 0x1b, 0x1c, 0x02, - 0x87, 0x4d, 0xd4, 0x28, 0xf9, 0xc3, 0x16, 0x7e, 0x83, 0x61, 0x25, 0xae, 0xc6, 0xc2, 0x7f, 0xc8, - 0xea, 0xda, 0x1d, 0xdc, 0x93, 0xc5, 0x4a, 0xdd, 0x1e, 0x9b, 0xb8, 0x1e, 0x25, 0xae, 0xd8, 0xad, - 0xce, 0xb6, 0xfe, 0x3f, 0xab, 0x7f, 0x42, 0xa9, 0xdb, 0x6d, 0x13, 0xa7, 0xfe, 0x67, 0x75, 0xdc, - 0x43, 0xfd, 0x1d, 0xa4, 0xbf, 0x27, 0xe2, 0x77, 0xfc, 0x47, 0x6d, 0xe2, 0x72, 0x49, 0xdf, 0xef, - 0x3e, 0xe2, 0x1c, 0x27, 0xb5, 0xb5, 0xe5, 0x42, 0x6d, 0x03, 0x93, 0x71, 0x9a, 0xd6, 0xe2, 0xd3, - 0x70, 0x11, 0x1a, 0x36, 0x42, 0xcd, 0xcb, 0x78, 0x0e, 0x60, 0x16, 0xf1, 0x9c, 0x6f, 0x30, 0x47, - 0x94, 0x60, 0xc7, 0x24, 0x51, 0xdc, 0x28, 0x51, 0x14, 0xc3, 0xc7, 0x5a, 0xeb, 0x47, 0xcd, 0x8a, - 0x3f, 0x05, 0x2f, 0x43, 0xb9, 0xce, 0x1e, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0x99, 0x4b, 0x48, 0x15, 0x61, + 0x14, 0xc7, 0xcf, 0xc5, 0x17, 0x2e, 0x4a, 0x57, 0xe5, 0x63, 0xe1, 0x85, 0x42, 0x23, 0x8c, 0x8c, + 0x20, 0x4d, 0x45, 0xdb, 0x59, 0x14, 0x2e, 0x7a, 0xab, 0x68, 0x1b, 0x17, 0xae, 0x24, 0x41, 0xf1, + 0x41, 0xa0, 0x41, 0xa1, 0x11, 0x28, 0x1a, 0x2e, 0x12, 0x72, 0xe7, 0x03, 0x09, 0x74, 0x15, 0x54, + 0x1b, 0x97, 0xd9, 0x53, 0x23, 0x8a, 0x5a, 0x94, 0x25, 0x59, 0x91, 0x82, 0xa0, 0x81, 0x99, 0x39, + 0xfd, 0x8f, 0x73, 0x46, 0xbf, 0xc6, 0xb9, 0x73, 0x67, 0xee, 0x9d, 0xab, 0x41, 0x1e, 0xf8, 0x71, + 0xe7, 0x7e, 0xe7, 0xf1, 0x9f, 0xb9, 0xdf, 0x7c, 0x8f, 0x99, 0x4b, 0xe4, 0xa3, 0x28, 0x4a, 0x4c, + 0x24, 0x7c, 0xfa, 0xa9, 0x2a, 0x9a, 0xe8, 0x08, 0x11, 0x25, 0x25, 0xe9, 0xdf, 0xdb, 0xe2, 0x89, + 0x7a, 0xd1, 0xe6, 0xf7, 0xeb, 0xdf, 0x07, 0x11, 0x97, 0xbe, 0x93, 0x68, 0x1f, 0x62, 0x70, 0x88, + 0x16, 0xbd, 0x7d, 0xd5, 0x10, 0x37, 0x43, 0x3a, 0x0e, 0x2c, 0x06, 0xdc, 0x16, 0x62, 0x9c, 0xa5, + 0xfc, 0x65, 0xb1, 0x60, 0x58, 0x88, 0x75, 0x99, 0x9b, 0x06, 0x2a, 0xc1, 0x1b, 0xa1, 0x52, 0xda, + 0x82, 0x19, 0xae, 0x90, 0xca, 0xc0, 0x04, 0x58, 0x02, 0x9a, 0xb0, 0x24, 0x6d, 0xa5, 0x12, 0x13, + 0xc8, 0xd8, 0x3f, 0xab, 0xe4, 0x99, 0x99, 0x95, 0xfa, 0x56, 0xe6, 0x17, 0x8d, 0x40, 0xb9, 0x06, + 0x13, 0x12, 0x6b, 0xb6, 0x4b, 0xa6, 0x73, 0x0e, 0xc4, 0x92, 0xc4, 0x9a, 0xed, 0xba, 0x83, 0x5c, + 0x83, 0x6b, 0x16, 0xf9, 0x1d, 0x2e, 0xf2, 0x3b, 0x2c, 0xf2, 0xdb, 0x5d, 0xe4, 0xb7, 0x9b, 0x72, + 0xf9, 0xfe, 0xea, 0x76, 0x91, 0xcf, 0xb1, 0x6a, 0x3f, 0x9e, 0x03, 0x5f, 0x5c, 0xe4, 0x73, 0xec, + 0x59, 0x25, 0xbf, 0x00, 0xdc, 0x02, 0x5d, 0xe0, 0x2e, 0x58, 0x00, 0xf7, 0xe5, 0x7b, 0x97, 0x1c, + 0x2f, 0x88, 0xaf, 0x4b, 0x62, 0x0b, 0x4c, 0xd7, 0xe0, 0x93, 0xcf, 0x7c, 0xf0, 0x99, 0xf4, 0x7b, + 0xc9, 0xb0, 0x52, 0x69, 0xcb, 0x37, 0xc5, 0x5a, 0x59, 0x1e, 0x98, 0x06, 0x25, 0x4a, 0x5b, 0x89, + 0xb4, 0xe5, 0xd9, 0xe4, 0x19, 0x96, 0x09, 0x9e, 0x83, 0x22, 0xa5, 0xad, 0x48, 0xda, 0x32, 0xcd, + 0xc1, 0x8b, 0x18, 0x55, 0x93, 0x71, 0x44, 0xa3, 0x51, 0x44, 0x2d, 0x3e, 0x1d, 0xa7, 0xc6, 0x79, + 0x3c, 0xcf, 0x60, 0x2a, 0xa2, 0x2c, 0x52, 0xe6, 0x99, 0x78, 0x57, 0xf3, 0x8c, 0xd9, 0x52, 0xc1, + 0x43, 0xf0, 0x40, 0x8e, 0xbd, 0xb6, 0x1c, 0xf0, 0x5e, 0xc8, 0x89, 0x40, 0xfd, 0x6c, 0xf0, 0x41, + 0xc8, 0xf6, 0xb0, 0x2e, 0x8f, 0x91, 0xbd, 0xa0, 0x0a, 0x7c, 0x15, 0xf8, 0x78, 0x0f, 0xd9, 0xcf, + 0x69, 0xc1, 0x8c, 0x7b, 0x9c, 0xbb, 0xaf, 0x0d, 0x3c, 0x05, 0xf3, 0xb4, 0x3e, 0x4e, 0xe6, 0xa5, + 0xad, 0x15, 0x1c, 0x24, 0xfb, 0xfb, 0xd6, 0xca, 0xf8, 0xbc, 0x78, 0x6c, 0x3a, 0x99, 0x17, 0xc7, + 0x25, 0xd6, 0xe9, 0xb5, 0xf0, 0xb9, 0x9c, 0x27, 0x7d, 0x5c, 0x38, 0x1d, 0xf7, 0xd3, 0x92, 0xe3, + 0xe4, 0x3a, 0xf8, 0x37, 0x79, 0xe1, 0xa2, 0xb6, 0x7a, 0x1d, 0x59, 0x41, 0x6a, 0x73, 0x5f, 0xb6, + 0x85, 0x50, 0xdb, 0xe0, 0x06, 0xd9, 0xaf, 0xc9, 0xe9, 0xa4, 0xf7, 0x5b, 0xa8, 0xf5, 0x9f, 0x48, + 0x8d, 0x40, 0x76, 0x0c, 0xfc, 0x08, 0xa3, 0x3e, 0xcf, 0xa5, 0x85, 0x36, 0xf5, 0xcf, 0x84, 0x51, + 0xdb, 0xe0, 0xb4, 0x4d, 0xfd, 0x72, 0x0f, 0xea, 0x97, 0xdb, 0xd4, 0xaf, 0xf0, 0xa0, 0x7e, 0xc5, + 0x16, 0xd6, 0x2f, 0xf3, 0xa0, 0x7e, 0xa0, 0x7d, 0x12, 0xcf, 0x57, 0x9d, 0x1e, 0xd4, 0xef, 0x94, + 0x5a, 0xaa, 0x61, 0x15, 0xa3, 0x5e, 0xf0, 0xd3, 0x83, 0xfa, 0x5c, 0xe3, 0x8e, 0xd4, 0x34, 0x8c, + 0xe7, 0x8d, 0x26, 0xf0, 0x58, 0x18, 0x03, 0xcf, 0x48, 0x5f, 0xea, 0x38, 0x67, 0x4a, 0xf1, 0x19, + 0x4c, 0x89, 0x6f, 0x46, 0x62, 0xc7, 0x14, 0x5f, 0x13, 0x6d, 0x9c, 0x8b, 0x78, 0x5c, 0xef, 0x06, + 0x29, 0xa4, 0x2f, 0xad, 0xbc, 0xc4, 0x8e, 0x80, 0x15, 0xd2, 0xe7, 0xe1, 0x64, 0xf1, 0xa5, 0xc8, + 0x71, 0xab, 0xf8, 0x46, 0x68, 0x7d, 0x39, 0x4e, 0x91, 0x1a, 0x4e, 0xf6, 0xed, 0x09, 0xa0, 0x5f, + 0x6a, 0x34, 0x58, 0xf8, 0x1b, 0xc4, 0xd7, 0x2f, 0xb1, 0x6e, 0x8d, 0x1f, 0x43, 0x06, 0xa4, 0x46, + 0xa3, 0x85, 0xbf, 0x51, 0x7c, 0x03, 0x12, 0xeb, 0xd6, 0xf8, 0x9c, 0xfa, 0xa4, 0x46, 0xbd, 0x85, + 0xbf, 0x5e, 0x7c, 0x7d, 0x14, 0xda, 0xf9, 0x73, 0xff, 0x5f, 0x01, 0x9f, 0xc0, 0x49, 0x0b, 0x3f, + 0xb7, 0x4d, 0x49, 0x4c, 0x94, 0x85, 0x7f, 0xd5, 0xb4, 0xab, 0xda, 0x2a, 0x2b, 0xd4, 0x4c, 0x8b, + 0x78, 0x94, 0x99, 0xc3, 0xa9, 0x4c, 0x52, 0xdc, 0x2a, 0xa3, 0x48, 0x63, 0x5a, 0x70, 0x2b, 0x04, + 0xc2, 0x6c, 0xdc, 0xb6, 0x98, 0x94, 0xb6, 0x06, 0x77, 0xbc, 0x9f, 0xf4, 0x89, 0x79, 0x6d, 0x1f, + 0x96, 0xb8, 0x71, 0x1f, 0x76, 0xea, 0x72, 0x51, 0x48, 0x48, 0xf9, 0x77, 0xe0, 0x2d, 0x48, 0x0b, + 0xb5, 0x4e, 0x18, 0xfa, 0xd5, 0xb4, 0x3e, 0x36, 0xab, 0xb7, 0x40, 0xbf, 0x56, 0xd1, 0xaf, 0xfd, + 0x0f, 0xf5, 0xeb, 0x14, 0xfd, 0xba, 0x4d, 0xd2, 0xe4, 0xdb, 0x9a, 0x9f, 0x35, 0x5f, 0x82, 0xdf, + 0x8a, 0x3e, 0x1f, 0xf3, 0x9e, 0x92, 0x9f, 0x23, 0x33, 0x22, 0xa0, 0xcb, 0x73, 0xe7, 0x10, 0xe9, + 0xf3, 0x46, 0xb0, 0x75, 0x82, 0x63, 0x06, 0xc1, 0x2e, 0x8f, 0xb4, 0xf3, 0x68, 0x7d, 0x3d, 0x71, + 0xc3, 0x77, 0x90, 0x1b, 0xa6, 0x76, 0x21, 0xe9, 0x7b, 0xb3, 0x50, 0xd7, 0x4c, 0xce, 0x2d, 0x08, + 0xe3, 0x37, 0x77, 0xb3, 0x97, 0x0f, 0xc4, 0x37, 0x90, 0x1c, 0x82, 0xfe, 0x90, 0x07, 0xda, 0x06, + 0x83, 0x2e, 0xb5, 0xf7, 0x9b, 0xee, 0xef, 0x70, 0xe1, 0x5a, 0x07, 0x5c, 0xe8, 0xbb, 0x79, 0x77, + 0xe3, 0x94, 0x9b, 0x2e, 0xf4, 0x5f, 0x47, 0x40, 0xff, 0x95, 0x43, 0x6d, 0xde, 0x23, 0x39, 0x19, + 0xe7, 0xa1, 0xf4, 0x41, 0xb4, 0x03, 0xfd, 0xd4, 0x08, 0x68, 0x1b, 0xa4, 0x38, 0xd0, 0xcf, 0x88, + 0xa0, 0x7e, 0xd0, 0xb9, 0x79, 0x5b, 0x7f, 0x5b, 0x7f, 0xab, 0xf4, 0x61, 0x17, 0xc1, 0xbd, 0x08, + 0xea, 0x73, 0xed, 0x0b, 0x01, 0xb4, 0xe3, 0xc1, 0xaf, 0x08, 0x6a, 0x1b, 0xf0, 0xff, 0x05, 0xf1, + 0x16, 0xfa, 0xfc, 0x50, 0x31, 0xbe, 0x09, 0xfa, 0xfc, 0x0e, 0xcd, 0x67, 0xf3, 0x1b, 0x1c, 0x02, + 0x87, 0x4d, 0xd4, 0x28, 0xf9, 0xc3, 0x16, 0x7e, 0x83, 0x61, 0x25, 0xae, 0xc6, 0xc2, 0x7f, 0xc8, + 0xea, 0xda, 0x1d, 0xdc, 0x93, 0xc5, 0x4a, 0xdd, 0x1e, 0x9b, 0xb8, 0x1e, 0x25, 0xae, 0xd8, 0xad, + 0xce, 0xb6, 0xfe, 0x3f, 0xab, 0x7f, 0x42, 0xa9, 0xdb, 0x6d, 0x13, 0xa7, 0xfe, 0x67, 0x75, 0xdc, + 0x43, 0xfd, 0x1d, 0xa4, 0xbf, 0x27, 0xe2, 0x77, 0xfc, 0x47, 0x6d, 0xe2, 0x72, 0x49, 0xdf, 0xef, + 0x3e, 0xe2, 0x1c, 0x27, 0xb5, 0xb5, 0xe5, 0x42, 0x6d, 0x03, 0x93, 0x71, 0x9a, 0xd6, 0xe2, 0xd3, + 0x70, 0x11, 0x1a, 0x36, 0x42, 0xcd, 0xcb, 0x78, 0x0e, 0x60, 0x16, 0xf1, 0x9c, 0x6f, 0x30, 0x47, + 0x94, 0x60, 0xc7, 0x24, 0x51, 0xdc, 0x28, 0x51, 0x14, 0xc3, 0xc7, 0x5a, 0xeb, 0x47, 0xcd, 0x8a, + 0x3f, 0x05, 0x2f, 0x43, 0xb9, 0xce, 0x1e, 0x00, 0x00, }; const StaticFile favicon_ico PROGMEM = {(sizeof(favicon_ico_content)/sizeof(favicon_ico_content[0])), favicon_ico_content}; diff --git a/platformio/relayctl/src/static.h b/platformio/relayctl/src/static.h index 3273e68..90560bf 100644 --- a/platformio/relayctl/src/static.h +++ b/platformio/relayctl/src/static.h @@ -15,6 +15,7 @@ typedef struct { extern const StaticFile index_html; extern const StaticFile app_js; +extern const StaticFile md5_js; extern const StaticFile style_css; extern const StaticFile favicon_ico; diff --git a/platformio/relayctl/src/util.h b/platformio/relayctl/src/util.h new file mode 100644 index 0000000..e0780d8 --- /dev/null +++ b/platformio/relayctl/src/util.h @@ -0,0 +1,13 @@ +#pragma once + +namespace homekit { + +inline size_t otaGetMaxUpdateSize() { + return (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; +} + +inline void restart() { + ESP.restart(); +} + +} \ No newline at end of file diff --git a/platformio/relayctl/src/wifi.cpp b/platformio/relayctl/src/wifi.cpp index b85d1dc..daff7bf 100644 --- a/platformio/relayctl/src/wifi.cpp +++ b/platformio/relayctl/src/wifi.cpp @@ -1,3 +1,4 @@ +#include #include "config.def.h" #include "wifi.h" #include "config.h" @@ -8,22 +9,20 @@ namespace homekit::wifi { using namespace homekit; using homekit::config::ConfigData; -const char NODE_ID[] = DEFAULT_NODE_ID; -const char WIFI_AP_SSID[] = DEFAULT_WIFI_AP_SSID; -const char WIFI_STA_SSID[] = DEFAULT_WIFI_STA_SSID; -const char WIFI_STA_PSK[] = DEFAULT_WIFI_STA_PSK; +const char HOME_ID[] = DEFAULT_HOME_ID; +const char AP_SSID[] = DEFAULT_WIFI_AP_SSID; +const char STA_SSID[] = DEFAULT_WIFI_STA_SSID; +const char STA_PSK[] = DEFAULT_WIFI_STA_PSK; -void getConfig(ConfigData& cfg, char** ssid_dst, char** psk_dst, char** hostname_dst) { +void getConfig(ConfigData &cfg, const char** ssid, const char** psk, const char** hostname) { if (cfg.flags.wifi_configured) { - *ssid_dst = cfg.wifi_ssid; - *psk_dst = cfg.wifi_psk; - if (hostname_dst != nullptr) - *hostname_dst = cfg.node_id; + *ssid = cfg.wifi_ssid; + *psk = cfg.wifi_psk; + *hostname = cfg.home_id; } else { - *ssid_dst = (char*)WIFI_STA_SSID; - *psk_dst = (char*)WIFI_STA_PSK; - if (hostname_dst != nullptr) - *hostname_dst = (char*)NODE_ID; + *ssid = STA_SSID; + *psk = STA_PSK; + *hostname = HOME_ID; } } diff --git a/platformio/relayctl/src/wifi.h b/platformio/relayctl/src/wifi.h index 65dbd53..37d8c02 100644 --- a/platformio/relayctl/src/wifi.h +++ b/platformio/relayctl/src/wifi.h @@ -14,12 +14,9 @@ struct ScanResult { String ssid; }; -void getConfig(ConfigData &cfg, char **ssid_dst, char **psk_dst, char **hostname_dst); -std::shared_ptr> scan(); +void getConfig(ConfigData& cfg, const char** ssid, const char** psk, const char** hostname); -inline int8_t getRSSI() { - return WiFi.RSSI(); -} +std::shared_ptr> scan(); inline uint32_t getIPAsInteger() { if (!WiFi.isConnected()) @@ -27,9 +24,13 @@ inline uint32_t getIPAsInteger() { return WiFi.localIP().v4(); } -extern const char WIFI_AP_SSID[]; -extern const char WIFI_STA_SSID[]; -extern const char WIFI_STA_PSK[]; -extern const char NODE_ID[]; +inline int8_t getRSSI() { + return WiFi.RSSI(); +} + +extern const char AP_SSID[]; +extern const char STA_SSID[]; +extern const char STA_PSK[]; +extern const char HOME_ID[]; } \ No newline at end of file diff --git a/platformio/relayctl/static/app.js b/platformio/relayctl/static/app.js index e345957..2446541 100644 --- a/platformio/relayctl/static/app.js +++ b/platformio/relayctl/static/app.js @@ -6,6 +6,10 @@ function ge(id) { return document.getElementById(id) } +function hide(el) { + el.style.display = 'none' +} + function cancelEvent(evt) { if (evt.preventDefault) evt.preventDefault(); if (evt.stopPropagation) evt.stopPropagation(); @@ -100,14 +104,14 @@ function initNetworkSettings() { function onRequestDone() { doneRequestsCount++; if (doneRequestsCount === 2) { - ge('loading_label').style.display = 'none'; + hide(ge('loading_label')) } } var form = document.forms.network_settings; form.addEventListener('submit', function(e) { - if (!form.nid.value.trim()) { - alert('Введите node id'); + if (!form.hid.value.trim()) { + alert('Введите home id'); return cancelEvent(e); } @@ -116,7 +120,7 @@ function initNetworkSettings() { return cancelEvent(e); } - if (form.ssid.selectedIndex == -1) { + if (form.ssid.selectedIndex === -1) { alert('Не выбрана точка доступа'); return cancelEvent(e); } @@ -140,7 +144,7 @@ function initNetworkSettings() { if (error) throw error; - setupField(form.nid, response.node_id || null); + setupField(form.hid, response.home_id || null); setupField(form.psk, null); setupField(form.submit, null); @@ -196,17 +200,23 @@ function initUpdateForm() { form.submit.innerHTML = progress + '%'; }); xhr.onreadystatechange = function() { + var errorMessage = 'Ошибка обновления'; + var successMessage = 'Обновление завершено, устройство перезагружается'; if (xhr.readyState === 4) { - var response = JSON.parse(xhr.responseText); - if (response.result === 1) { - alert('Обновление завершено, устройство перезагружается'); - } else { - alert('Ошибка обновления'); + try { + var response = JSON.parse(xhr.responseText); + if (response.result === 1) { + alert(successMessage); + } else { + alert(errorMessage); + } + } catch (e) { + alert(successMessage); } } }; xhr.onerror = function(e) { - alert(errorText(e)) + alert(errorText(e)); }; xhr.open('POST', e.target.action); @@ -214,9 +224,23 @@ function initUpdateForm() { return false; }); + form.file.addEventListener('change', function(e) { + if (e.target.files.length) { + var reader = new FileReader(); + reader.onload = function() { + var hash = window.md5(reader.result); + form.setAttribute('action', '/update?md5='+hash); + unlock(form.submit); + }; + reader.onerror = function() { + alert('Ошибка чтения файла'); + }; + reader.readAsBinaryString(e.target.files[0]); + } + }); } -function initApp() { +window.initApp = function() { initNetworkSettings(); initUpdateForm(); } \ No newline at end of file diff --git a/platformio/relayctl/static/index.html b/platformio/relayctl/static/index.html index d89967d..d4a8040 100644 --- a/platformio/relayctl/static/index.html +++ b/platformio/relayctl/static/index.html @@ -6,6 +6,7 @@ Configuration + @@ -22,14 +23,14 @@
WiFi Password
-
+
-
Node ID
+
Home ID
- +
@@ -42,7 +43,7 @@
- +
diff --git a/platformio/relayctl/static/md5.js b/platformio/relayctl/static/md5.js new file mode 100644 index 0000000..b707a4e --- /dev/null +++ b/platformio/relayctl/static/md5.js @@ -0,0 +1,615 @@ +/** + * [js-md5]{@link https://github.com/emn178/js-md5} + * + * @namespace md5 + * @version 0.7.3 + * @author Chen, Yi-Cyuan [emn178@gmail.com] + * @copyright Chen, Yi-Cyuan 2014-2017 + * @license MIT + */ +(function () { + 'use strict'; + + var ERROR = 'input is invalid type'; + var ARRAY_BUFFER = typeof window.ArrayBuffer !== 'undefined'; + var HEX_CHARS = '0123456789abcdef'.split(''); + var EXTRA = [128, 32768, 8388608, -2147483648]; + var SHIFT = [0, 8, 16, 24]; + var OUTPUT_TYPES = ['hex', 'array', 'digest', 'buffer', 'arrayBuffer', 'base64']; + var BASE64_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); + + var blocks = [], buffer8; + if (ARRAY_BUFFER) { + var buffer = new ArrayBuffer(68); + buffer8 = new Uint8Array(buffer); + blocks = new Uint32Array(buffer); + } + + if (!Array.isArray) { + Array.isArray = function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + } + + if (ARRAY_BUFFER && !ArrayBuffer.isView) { + ArrayBuffer.isView = function (obj) { + return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; + }; + } + + /** + * @method hex + * @memberof md5 + * @description Output hash as hex string + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {String} Hex string + * @example + * md5.hex('The quick brown fox jumps over the lazy dog'); + * // equal to + * md5('The quick brown fox jumps over the lazy dog'); + */ + /** + * @method digest + * @memberof md5 + * @description Output hash as bytes array + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {Array} Bytes array + * @example + * md5.digest('The quick brown fox jumps over the lazy dog'); + */ + /** + * @method array + * @memberof md5 + * @description Output hash as bytes array + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {Array} Bytes array + * @example + * md5.array('The quick brown fox jumps over the lazy dog'); + */ + /** + * @method arrayBuffer + * @memberof md5 + * @description Output hash as ArrayBuffer + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {ArrayBuffer} ArrayBuffer + * @example + * md5.arrayBuffer('The quick brown fox jumps over the lazy dog'); + */ + /** + * @method buffer + * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead. + * @memberof md5 + * @description Output hash as ArrayBuffer + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {ArrayBuffer} ArrayBuffer + * @example + * md5.buffer('The quick brown fox jumps over the lazy dog'); + */ + /** + * @method base64 + * @memberof md5 + * @description Output hash as base64 string + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {String} base64 string + * @example + * md5.base64('The quick brown fox jumps over the lazy dog'); + */ + var createOutputMethod = function (outputType) { + return function (message) { + return new Md5(true).update(message)[outputType](); + }; + }; + + /** + * @method create + * @memberof md5 + * @description Create Md5 object + * @returns {Md5} Md5 object. + * @example + * var hash = md5.create(); + */ + /** + * @method update + * @memberof md5 + * @description Create and update Md5 object + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {Md5} Md5 object. + * @example + * var hash = md5.update('The quick brown fox jumps over the lazy dog'); + * // equal to + * var hash = md5.create(); + * hash.update('The quick brown fox jumps over the lazy dog'); + */ + var createMethod = function () { + var method = createOutputMethod('hex'); + method.create = function () { + return new Md5(); + }; + method.update = function (message) { + return method.create().update(message); + }; + for (var i = 0; i < OUTPUT_TYPES.length; ++i) { + var type = OUTPUT_TYPES[i]; + method[type] = createOutputMethod(type); + } + return method; + }; + + /** + * Md5 class + * @class Md5 + * @description This is internal class. + * @see {@link md5.create} + */ + function Md5(sharedMemory) { + if (sharedMemory) { + blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = + blocks[4] = blocks[5] = blocks[6] = blocks[7] = + blocks[8] = blocks[9] = blocks[10] = blocks[11] = + blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + this.blocks = blocks; + this.buffer8 = buffer8; + } else { + if (ARRAY_BUFFER) { + var buffer = new ArrayBuffer(68); + this.buffer8 = new Uint8Array(buffer); + this.blocks = new Uint32Array(buffer); + } else { + this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + } + } + this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0; + this.finalized = this.hashed = false; + this.first = true; + } + + /** + * @method update + * @memberof Md5 + * @instance + * @description Update hash + * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash + * @returns {Md5} Md5 object. + * @see {@link md5.update} + */ + Md5.prototype.update = function (message) { + if (this.finalized) { + return; + } + + var notString, type = typeof message; + if (type !== 'string') { + if (type === 'object') { + if (message === null) { + throw ERROR; + } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { + message = new Uint8Array(message); + } else if (!Array.isArray(message)) { + if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { + throw ERROR; + } + } + } else { + throw ERROR; + } + notString = true; + } + var code, index = 0, i, length = message.length, blocks = this.blocks; + var buffer8 = this.buffer8; + + while (index < length) { + if (this.hashed) { + this.hashed = false; + blocks[0] = blocks[16]; + blocks[16] = blocks[1] = blocks[2] = blocks[3] = + blocks[4] = blocks[5] = blocks[6] = blocks[7] = + blocks[8] = blocks[9] = blocks[10] = blocks[11] = + blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + } + + if (notString) { + if (ARRAY_BUFFER) { + for (i = this.start; index < length && i < 64; ++index) { + buffer8[i++] = message[index]; + } + } else { + for (i = this.start; index < length && i < 64; ++index) { + blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; + } + } + } else { + if (ARRAY_BUFFER) { + for (i = this.start; index < length && i < 64; ++index) { + code = message.charCodeAt(index); + if (code < 0x80) { + buffer8[i++] = code; + } else if (code < 0x800) { + buffer8[i++] = 0xc0 | (code >> 6); + buffer8[i++] = 0x80 | (code & 0x3f); + } else if (code < 0xd800 || code >= 0xe000) { + buffer8[i++] = 0xe0 | (code >> 12); + buffer8[i++] = 0x80 | ((code >> 6) & 0x3f); + buffer8[i++] = 0x80 | (code & 0x3f); + } else { + code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); + buffer8[i++] = 0xf0 | (code >> 18); + buffer8[i++] = 0x80 | ((code >> 12) & 0x3f); + buffer8[i++] = 0x80 | ((code >> 6) & 0x3f); + buffer8[i++] = 0x80 | (code & 0x3f); + } + } + } else { + for (i = this.start; index < length && i < 64; ++index) { + code = message.charCodeAt(index); + if (code < 0x80) { + blocks[i >> 2] |= code << SHIFT[i++ & 3]; + } else if (code < 0x800) { + blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } else if (code < 0xd800 || code >= 0xe000) { + blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } else { + code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); + blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } + } + } + } + this.lastByteIndex = i; + this.bytes += i - this.start; + if (i >= 64) { + this.start = i - 64; + this.hash(); + this.hashed = true; + } else { + this.start = i; + } + } + if (this.bytes > 4294967295) { + this.hBytes += this.bytes / 4294967296 << 0; + this.bytes = this.bytes % 4294967296; + } + return this; + }; + + Md5.prototype.finalize = function () { + if (this.finalized) { + return; + } + this.finalized = true; + var blocks = this.blocks, i = this.lastByteIndex; + blocks[i >> 2] |= EXTRA[i & 3]; + if (i >= 56) { + if (!this.hashed) { + this.hash(); + } + blocks[0] = blocks[16]; + blocks[16] = blocks[1] = blocks[2] = blocks[3] = + blocks[4] = blocks[5] = blocks[6] = blocks[7] = + blocks[8] = blocks[9] = blocks[10] = blocks[11] = + blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + } + blocks[14] = this.bytes << 3; + blocks[15] = this.hBytes << 3 | this.bytes >>> 29; + this.hash(); + }; + + Md5.prototype.hash = function () { + var a, b, c, d, bc, da, blocks = this.blocks; + + if (this.first) { + a = blocks[0] - 680876937; + a = (a << 7 | a >>> 25) - 271733879 << 0; + d = (-1732584194 ^ a & 2004318071) + blocks[1] - 117830708; + d = (d << 12 | d >>> 20) + a << 0; + c = (-271733879 ^ (d & (a ^ -271733879))) + blocks[2] - 1126478375; + c = (c << 17 | c >>> 15) + d << 0; + b = (a ^ (c & (d ^ a))) + blocks[3] - 1316259209; + b = (b << 22 | b >>> 10) + c << 0; + } else { + a = this.h0; + b = this.h1; + c = this.h2; + d = this.h3; + a += (d ^ (b & (c ^ d))) + blocks[0] - 680876936; + a = (a << 7 | a >>> 25) + b << 0; + d += (c ^ (a & (b ^ c))) + blocks[1] - 389564586; + d = (d << 12 | d >>> 20) + a << 0; + c += (b ^ (d & (a ^ b))) + blocks[2] + 606105819; + c = (c << 17 | c >>> 15) + d << 0; + b += (a ^ (c & (d ^ a))) + blocks[3] - 1044525330; + b = (b << 22 | b >>> 10) + c << 0; + } + + a += (d ^ (b & (c ^ d))) + blocks[4] - 176418897; + a = (a << 7 | a >>> 25) + b << 0; + d += (c ^ (a & (b ^ c))) + blocks[5] + 1200080426; + d = (d << 12 | d >>> 20) + a << 0; + c += (b ^ (d & (a ^ b))) + blocks[6] - 1473231341; + c = (c << 17 | c >>> 15) + d << 0; + b += (a ^ (c & (d ^ a))) + blocks[7] - 45705983; + b = (b << 22 | b >>> 10) + c << 0; + a += (d ^ (b & (c ^ d))) + blocks[8] + 1770035416; + a = (a << 7 | a >>> 25) + b << 0; + d += (c ^ (a & (b ^ c))) + blocks[9] - 1958414417; + d = (d << 12 | d >>> 20) + a << 0; + c += (b ^ (d & (a ^ b))) + blocks[10] - 42063; + c = (c << 17 | c >>> 15) + d << 0; + b += (a ^ (c & (d ^ a))) + blocks[11] - 1990404162; + b = (b << 22 | b >>> 10) + c << 0; + a += (d ^ (b & (c ^ d))) + blocks[12] + 1804603682; + a = (a << 7 | a >>> 25) + b << 0; + d += (c ^ (a & (b ^ c))) + blocks[13] - 40341101; + d = (d << 12 | d >>> 20) + a << 0; + c += (b ^ (d & (a ^ b))) + blocks[14] - 1502002290; + c = (c << 17 | c >>> 15) + d << 0; + b += (a ^ (c & (d ^ a))) + blocks[15] + 1236535329; + b = (b << 22 | b >>> 10) + c << 0; + a += (c ^ (d & (b ^ c))) + blocks[1] - 165796510; + a = (a << 5 | a >>> 27) + b << 0; + d += (b ^ (c & (a ^ b))) + blocks[6] - 1069501632; + d = (d << 9 | d >>> 23) + a << 0; + c += (a ^ (b & (d ^ a))) + blocks[11] + 643717713; + c = (c << 14 | c >>> 18) + d << 0; + b += (d ^ (a & (c ^ d))) + blocks[0] - 373897302; + b = (b << 20 | b >>> 12) + c << 0; + a += (c ^ (d & (b ^ c))) + blocks[5] - 701558691; + a = (a << 5 | a >>> 27) + b << 0; + d += (b ^ (c & (a ^ b))) + blocks[10] + 38016083; + d = (d << 9 | d >>> 23) + a << 0; + c += (a ^ (b & (d ^ a))) + blocks[15] - 660478335; + c = (c << 14 | c >>> 18) + d << 0; + b += (d ^ (a & (c ^ d))) + blocks[4] - 405537848; + b = (b << 20 | b >>> 12) + c << 0; + a += (c ^ (d & (b ^ c))) + blocks[9] + 568446438; + a = (a << 5 | a >>> 27) + b << 0; + d += (b ^ (c & (a ^ b))) + blocks[14] - 1019803690; + d = (d << 9 | d >>> 23) + a << 0; + c += (a ^ (b & (d ^ a))) + blocks[3] - 187363961; + c = (c << 14 | c >>> 18) + d << 0; + b += (d ^ (a & (c ^ d))) + blocks[8] + 1163531501; + b = (b << 20 | b >>> 12) + c << 0; + a += (c ^ (d & (b ^ c))) + blocks[13] - 1444681467; + a = (a << 5 | a >>> 27) + b << 0; + d += (b ^ (c & (a ^ b))) + blocks[2] - 51403784; + d = (d << 9 | d >>> 23) + a << 0; + c += (a ^ (b & (d ^ a))) + blocks[7] + 1735328473; + c = (c << 14 | c >>> 18) + d << 0; + b += (d ^ (a & (c ^ d))) + blocks[12] - 1926607734; + b = (b << 20 | b >>> 12) + c << 0; + bc = b ^ c; + a += (bc ^ d) + blocks[5] - 378558; + a = (a << 4 | a >>> 28) + b << 0; + d += (bc ^ a) + blocks[8] - 2022574463; + d = (d << 11 | d >>> 21) + a << 0; + da = d ^ a; + c += (da ^ b) + blocks[11] + 1839030562; + c = (c << 16 | c >>> 16) + d << 0; + b += (da ^ c) + blocks[14] - 35309556; + b = (b << 23 | b >>> 9) + c << 0; + bc = b ^ c; + a += (bc ^ d) + blocks[1] - 1530992060; + a = (a << 4 | a >>> 28) + b << 0; + d += (bc ^ a) + blocks[4] + 1272893353; + d = (d << 11 | d >>> 21) + a << 0; + da = d ^ a; + c += (da ^ b) + blocks[7] - 155497632; + c = (c << 16 | c >>> 16) + d << 0; + b += (da ^ c) + blocks[10] - 1094730640; + b = (b << 23 | b >>> 9) + c << 0; + bc = b ^ c; + a += (bc ^ d) + blocks[13] + 681279174; + a = (a << 4 | a >>> 28) + b << 0; + d += (bc ^ a) + blocks[0] - 358537222; + d = (d << 11 | d >>> 21) + a << 0; + da = d ^ a; + c += (da ^ b) + blocks[3] - 722521979; + c = (c << 16 | c >>> 16) + d << 0; + b += (da ^ c) + blocks[6] + 76029189; + b = (b << 23 | b >>> 9) + c << 0; + bc = b ^ c; + a += (bc ^ d) + blocks[9] - 640364487; + a = (a << 4 | a >>> 28) + b << 0; + d += (bc ^ a) + blocks[12] - 421815835; + d = (d << 11 | d >>> 21) + a << 0; + da = d ^ a; + c += (da ^ b) + blocks[15] + 530742520; + c = (c << 16 | c >>> 16) + d << 0; + b += (da ^ c) + blocks[2] - 995338651; + b = (b << 23 | b >>> 9) + c << 0; + a += (c ^ (b | ~d)) + blocks[0] - 198630844; + a = (a << 6 | a >>> 26) + b << 0; + d += (b ^ (a | ~c)) + blocks[7] + 1126891415; + d = (d << 10 | d >>> 22) + a << 0; + c += (a ^ (d | ~b)) + blocks[14] - 1416354905; + c = (c << 15 | c >>> 17) + d << 0; + b += (d ^ (c | ~a)) + blocks[5] - 57434055; + b = (b << 21 | b >>> 11) + c << 0; + a += (c ^ (b | ~d)) + blocks[12] + 1700485571; + a = (a << 6 | a >>> 26) + b << 0; + d += (b ^ (a | ~c)) + blocks[3] - 1894986606; + d = (d << 10 | d >>> 22) + a << 0; + c += (a ^ (d | ~b)) + blocks[10] - 1051523; + c = (c << 15 | c >>> 17) + d << 0; + b += (d ^ (c | ~a)) + blocks[1] - 2054922799; + b = (b << 21 | b >>> 11) + c << 0; + a += (c ^ (b | ~d)) + blocks[8] + 1873313359; + a = (a << 6 | a >>> 26) + b << 0; + d += (b ^ (a | ~c)) + blocks[15] - 30611744; + d = (d << 10 | d >>> 22) + a << 0; + c += (a ^ (d | ~b)) + blocks[6] - 1560198380; + c = (c << 15 | c >>> 17) + d << 0; + b += (d ^ (c | ~a)) + blocks[13] + 1309151649; + b = (b << 21 | b >>> 11) + c << 0; + a += (c ^ (b | ~d)) + blocks[4] - 145523070; + a = (a << 6 | a >>> 26) + b << 0; + d += (b ^ (a | ~c)) + blocks[11] - 1120210379; + d = (d << 10 | d >>> 22) + a << 0; + c += (a ^ (d | ~b)) + blocks[2] + 718787259; + c = (c << 15 | c >>> 17) + d << 0; + b += (d ^ (c | ~a)) + blocks[9] - 343485551; + b = (b << 21 | b >>> 11) + c << 0; + + if (this.first) { + this.h0 = a + 1732584193 << 0; + this.h1 = b - 271733879 << 0; + this.h2 = c - 1732584194 << 0; + this.h3 = d + 271733878 << 0; + this.first = false; + } else { + this.h0 = this.h0 + a << 0; + this.h1 = this.h1 + b << 0; + this.h2 = this.h2 + c << 0; + this.h3 = this.h3 + d << 0; + } + }; + + /** + * @method hex + * @memberof Md5 + * @instance + * @description Output hash as hex string + * @returns {String} Hex string + * @see {@link md5.hex} + * @example + * hash.hex(); + */ + Md5.prototype.hex = function () { + this.finalize(); + + var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3; + + return HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + + HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + + HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + + HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + + HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + + HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + + HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + + HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + + HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + + HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + + HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + + HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + + HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + + HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + + HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + + HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F]; + }; + + /** + * @method toString + * @memberof Md5 + * @instance + * @description Output hash as hex string + * @returns {String} Hex string + * @see {@link md5.hex} + * @example + * hash.toString(); + */ + Md5.prototype.toString = Md5.prototype.hex; + + /** + * @method digest + * @memberof Md5 + * @instance + * @description Output hash as bytes array + * @returns {Array} Bytes array + * @see {@link md5.digest} + * @example + * hash.digest(); + */ + Md5.prototype.digest = function () { + this.finalize(); + + var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3; + return [ + h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 24) & 0xFF, + h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 24) & 0xFF, + h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 24) & 0xFF, + h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 24) & 0xFF + ]; + }; + + /** + * @method array + * @memberof Md5 + * @instance + * @description Output hash as bytes array + * @returns {Array} Bytes array + * @see {@link md5.array} + * @example + * hash.array(); + */ + Md5.prototype.array = Md5.prototype.digest; + + /** + * @method arrayBuffer + * @memberof Md5 + * @instance + * @description Output hash as ArrayBuffer + * @returns {ArrayBuffer} ArrayBuffer + * @see {@link md5.arrayBuffer} + * @example + * hash.arrayBuffer(); + */ + Md5.prototype.arrayBuffer = function () { + this.finalize(); + + var buffer = new ArrayBuffer(16); + var blocks = new Uint32Array(buffer); + blocks[0] = this.h0; + blocks[1] = this.h1; + blocks[2] = this.h2; + blocks[3] = this.h3; + return buffer; + }; + + /** + * @method buffer + * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead. + * @memberof Md5 + * @instance + * @description Output hash as ArrayBuffer + * @returns {ArrayBuffer} ArrayBuffer + * @see {@link md5.buffer} + * @example + * hash.buffer(); + */ + Md5.prototype.buffer = Md5.prototype.arrayBuffer; + + /** + * @method base64 + * @memberof Md5 + * @instance + * @description Output hash as base64 string + * @returns {String} base64 string + * @see {@link md5.base64} + * @example + * hash.base64(); + */ + Md5.prototype.base64 = function () { + var v1, v2, v3, base64Str = '', bytes = this.array(); + for (var i = 0; i < 15;) { + v1 = bytes[i++]; + v2 = bytes[i++]; + v3 = bytes[i++]; + base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + + BASE64_ENCODE_CHAR[(v1 << 4 | v2 >>> 4) & 63] + + BASE64_ENCODE_CHAR[(v2 << 2 | v3 >>> 6) & 63] + + BASE64_ENCODE_CHAR[v3 & 63]; + } + v1 = bytes[i]; + base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] + + BASE64_ENCODE_CHAR[(v1 << 4) & 63] + + '=='; + return base64Str; + }; + + window.md5 = createMethod(); +})(); diff --git a/platformio/relayctl/static/style.css b/platformio/relayctl/static/style.css index 9a950a5..32bd02c 100644 --- a/platformio/relayctl/static/style.css +++ b/platformio/relayctl/static/style.css @@ -30,7 +30,7 @@ body, button, input[type="text"], input[type="password"] { .form_input { margin-bottom: 15px; } -.form_checkbox { +.form_sublabel { padding-top: 3px; } diff --git a/src/home/api/types/types.py b/src/home/api/types/types.py index 1d5596f..3f77b19 100644 --- a/src/home/api/types/types.py +++ b/src/home/api/types/types.py @@ -8,6 +8,7 @@ class BotType(Enum): ADMIN = auto() SOUND = auto() POLARIS_KETTLE = auto() + PUMP_MQTT = auto() class TemperatureSensorLocation(Enum): diff --git a/src/home/api/web_api_client.py b/src/home/api/web_api_client.py index f74b5a1..ca9a9ee 100644 --- a/src/home/api/web_api_client.py +++ b/src/home/api/web_api_client.py @@ -183,9 +183,9 @@ class WebAPIClient: data = json.loads(r.text) if r.status_code != 200: raise ApiResponseError(r.status_code, - data['error']['type'], - data['error']['message'], - data['error']['stacktrace'] if 'stacktrace' in data['error'] else None) + data['error'], + data['message'], + data['stacktrace'] if 'stacktrace' in data['error'] else None) return data['response'] if 'response' in data else True finally: diff --git a/src/home/mqtt/__init__.py b/src/home/mqtt/__init__.py index c0ef9ba..a6f5f5e 100644 --- a/src/home/mqtt/__init__.py +++ b/src/home/mqtt/__init__.py @@ -1,2 +1,3 @@ from .mqtt import MQTTBase from .util import poll_tick +from .relay import MQTTRelay \ No newline at end of file diff --git a/src/home/mqtt/mqtt.py b/src/home/mqtt/mqtt.py index b3334b5..9dd973b 100644 --- a/src/home/mqtt/mqtt.py +++ b/src/home/mqtt/mqtt.py @@ -6,8 +6,6 @@ import logging from typing import Tuple from ..config import config -logger = logging.getLogger(__name__) - def username_and_password() -> Tuple[str, str]: username = config['mqtt']['username'] if 'username' in config['mqtt'] else None @@ -23,11 +21,15 @@ class MQTTBase: self._client.on_connect = self.on_connect self._client.on_disconnect = self.on_disconnect self._client.on_message = self.on_message + self._client.on_log = self.on_log + self._client.on_publish = self.on_publish + self._loop_started = False self._logger = logging.getLogger(self.__class__.__name__) username, password = username_and_password() if username and password: + self._logger.debug(f'username={username} password={password}') self._client.username_pw_set(username, password) def configure_tls(self): @@ -50,6 +52,12 @@ class MQTTBase: self._client.loop_forever() else: self._client.loop_start() + self._loop_started = True + + def disconnect(self): + self._client.disconnect() + self._client.loop_write() + self._client.loop_stop() def on_connect(self, client: mqtt.Client, userdata, flags, rc): self._logger.info("Connected with result code " + str(rc)) @@ -57,5 +65,12 @@ class MQTTBase: def on_disconnect(self, client: mqtt.Client, userdata, rc): self._logger.info("Disconnected with result code " + str(rc)) + def on_log(self, client: mqtt.Client, userdata, level, buf): + level = mqtt.LOGGING_LEVEL[level] if level in mqtt.LOGGING_LEVEL else logging.INFO + self._logger.log(level, f'MQTT: {buf}') + def on_message(self, client: mqtt.Client, userdata, msg): - self._logger.info(msg.topic + ": " + str(msg.payload)) + self._logger.debug(msg.topic + ": " + str(msg.payload)) + + def on_publish(self, client: mqtt.Client, userdata, mid): + self._logger.debug(f'publish done, mid={mid}') \ No newline at end of file diff --git a/src/home/mqtt/payload/__init__.py b/src/home/mqtt/payload/__init__.py index e69de29..9fcaf3e 100644 --- a/src/home/mqtt/payload/__init__.py +++ b/src/home/mqtt/payload/__init__.py @@ -0,0 +1 @@ +from .base_payload import MQTTPayload \ No newline at end of file diff --git a/src/home/mqtt/payload/relay.py b/src/home/mqtt/payload/relay.py index 2a327ba..debc2c8 100644 --- a/src/home/mqtt/payload/relay.py +++ b/src/home/mqtt/payload/relay.py @@ -1,6 +1,10 @@ +import hashlib + from .base_payload import MQTTPayload, MQTTPayloadCustomField +# _logger = logging.getLogger(__name__) + class StatFlags(MQTTPayloadCustomField): state: bool config_changed_value_present: bool @@ -8,9 +12,11 @@ class StatFlags(MQTTPayloadCustomField): @staticmethod def unpack(flags: int): + # _logger.debug(f'StatFlags.unpack: flags={flags}') state = flags & 0x1 ccvp = (flags >> 1) & 0x1 cc = (flags >> 2) & 0x1 + # _logger.debug(f'StatFlags.unpack: state={state}') return StatFlags(state=(state == 1), config_changed_value_present=(ccvp == 1), config_changed=(cc == 1)) @@ -24,7 +30,7 @@ class StatFlags(MQTTPayloadCustomField): class InitialStatPayload(MQTTPayload): - FORMAT = 'IBbIB' + FORMAT = '=IBbIB' ip: int fw_version: int @@ -34,7 +40,7 @@ class InitialStatPayload(MQTTPayload): class StatPayload(MQTTPayload): - FORMAT = 'bIB' + FORMAT = '=bIB' rssi: int free_heap: int @@ -42,13 +48,42 @@ class StatPayload(MQTTPayload): class PowerPayload(MQTTPayload): - FORMAT = '12sB' + FORMAT = '=12sB' PACKER = { - 'state': lambda n: int(n) + 'state': lambda n: int(n), + 'secret': lambda s: s.encode('utf-8') } UNPACKER = { - 'state': lambda n: bool(n) + 'state': lambda n: bool(n), + 'secret': lambda s: s.decode('utf-8') } secret: str state: bool + + +class OTAPayload(MQTTPayload): + secret: str + filename: str + + # structure of returned data: + # + # uint8_t[len(secret)] secret; + # uint8_t[16] md5; + # *uint8_t data + + def pack(self): + buf = bytearray(self.secret.encode()) + m = hashlib.md5() + with open(self.filename, 'rb') as fd: + content = fd.read() + m.update(content) + buf.extend(m.digest()) + buf.extend(content) + return buf + + def unpack(cls, buf: bytes): + raise RuntimeError(f'{cls.__class__.__name__}.unpack: not implemented') + # secret = buf[:12].decode() + # filename = buf[12:].decode() + # return OTAPayload(secret=secret, filename=filename) diff --git a/src/home/mqtt/relay.py b/src/home/mqtt/relay.py new file mode 100644 index 0000000..0f97b5b --- /dev/null +++ b/src/home/mqtt/relay.py @@ -0,0 +1,93 @@ +import paho.mqtt.client as mqtt +import re + +from .mqtt import MQTTBase +from typing import Optional, Union +from .payload.relay import ( + InitialStatPayload, + StatPayload, + PowerPayload, + OTAPayload +) + + +class MQTTRelay(MQTTBase): + _home_id: Union[str, int] + _secret: str + _message_callback: Optional[callable] + _ota_publish_callback: Optional[callable] + + def __init__(self, + home_id: Union[str, int], + secret: str, + subscribe_to_updates=True): + super().__init__(clean_session=True) + self._home_id = home_id + self._secret = secret + self._message_callback = None + self._ota_publish_callback = None + self._subscribe_to_updates = subscribe_to_updates + self._ota_mid = None + + def on_connect(self, client: mqtt.Client, userdata, flags, rc): + super().on_connect(client, userdata, flags, rc) + + if self._subscribe_to_updates: + topic = f'hk/{self._home_id}/relay/#' + self._logger.info(f"subscribing to {topic}") + + client.subscribe(topic, qos=1) + + def on_publish(self, client: mqtt.Client, userdata, mid): + if self._ota_mid is not None and mid == self._ota_mid and self._ota_publish_callback: + self._ota_publish_callback() + + def set_message_callback(self, callback: callable): + self._message_callback = callback + + def on_message(self, client: mqtt.Client, userdata, msg): + try: + match = re.match(r'^hk/(.*?)/relay/(stat|stat1|power|otares)$', msg.topic) + self._logger.debug(f'topic: {msg.topic}') + if not match: + return + + name = match.group(1) + subtopic = match.group(2) + + if name != self._home_id: + return + + message = None + if subtopic == 'stat': + message = StatPayload.unpack(msg.payload) + elif subtopic == 'stat1': + message = InitialStatPayload.unpack(msg.payload) + elif subtopic == 'power': + message = PowerPayload.unpack(msg.payload) + + if message and self._message_callback: + self._message_callback(message) + + except Exception as e: + self._logger.exception(str(e)) + + def set_power(self, enable: bool): + payload = PowerPayload(secret=self._secret, + state=enable) + self._client.publish(f'hk/{self._home_id}/relay/power', + payload=payload.pack(), + qos=1) + self._client.loop_write() + + def push_ota(self, + filename: str, + publish_callback: callable, + qos: int): + self._ota_publish_callback = publish_callback + payload = OTAPayload(secret=self._secret, filename=filename) + publish_result = self._client.publish(f'hk/{self._home_id}/relay/admin/ota', + payload=payload.pack(), + qos=qos) + self._ota_mid = publish_result.mid + self._client.loop_write() diff --git a/src/home/telegram/bot.py b/src/home/telegram/bot.py index 3c82587..bd09f42 100644 --- a/src/home/telegram/bot.py +++ b/src/home/telegram/bot.py @@ -110,6 +110,8 @@ def _handler_of_handler(*args, **kwargs): ctx.reply_exc(e) else: notify_user(ctx.user_id, exc2text(e)) + else: + _logger.exception(e) def handler(**kwargs): diff --git a/src/pump_mqtt_bot.py b/src/pump_mqtt_bot.py new file mode 100755 index 0000000..d87234b --- /dev/null +++ b/src/pump_mqtt_bot.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +import datetime + +from enum import Enum +from typing import Optional +from telegram import ReplyKeyboardMarkup, User + +from home.config import config +from home.telegram import bot +from home.telegram._botutil import user_any_name +from home.api.types import BotType +from home.mqtt import MQTTRelay +from home.mqtt.payload import MQTTPayload +from home.mqtt.payload.relay import InitialStatPayload, StatPayload + + +config.load('pump_mqtt_bot') + +bot.initialize() +bot.lang.ru( + start_message="Выберите команду на клавиатуре", + start_message_no_access="Доступ запрещён. Вы можете отправить заявку на получение доступа.", + unknown_command="Неизвестная команда", + send_access_request="Отправить заявку", + management="Админка", + + enable="Включить", + enabled="Включен ✅", + + disable="Выключить", + disabled="Выключен ❌", + + status="Статус", + status_updated=' (обновлено %s)', + + done="Готово 👌", + user_action_notification='Пользователь %s %s насос.', + user_action_on="включил", + user_action_off="выключил", + date_yday="вчера", + date_yyday="позавчера", + date_at="в" +) +bot.lang.en( + start_message="Select command on the keyboard", + start_message_no_access="You have no access.", + unknown_command="Unknown command", + send_access_request="Send request", + management="Admin options", + + enable="Turn ON", + enable_silently="Turn ON silently", + enabled="Turned ON ✅", + + disable="Turn OFF", + disable_silently="Turn OFF silently", + disabled="Turned OFF ❌", + + status="Status", + status_updated=' (updated %s)', + + done="Done 👌", + user_action_notification='User %s turned the pump %s.', + user_action_on="ON", + user_action_off="OFF", + + date_yday="yesterday", + date_yyday="the day before yesterday", + date_at="at" +) + + +class RelayState: + enabled: bool + update_time: datetime.datetime + rssi: int + fw_version: int + ever_updated: bool + + def __init__(self): + self.ever_updated = False + + def update(self, + enabled: bool, + rssi: int, + fw_version=None): + self.ever_updated = True + self.enabled = enabled + self.rssi = rssi + self.update_time = datetime.datetime.now() + if fw_version: + self.fw_version = fw_version + + +mqtt_relay: Optional[MQTTRelay] = None +relay_state = RelayState() + + +class UserAction(Enum): + ON = 'on' + OFF = 'off' + + +def on_mqtt_message(message: MQTTPayload): + if isinstance(message, InitialStatPayload) or isinstance(message, StatPayload): + kwargs = dict(rssi=message.rssi, enabled=message.flags.state) + if isinstance(message, InitialStatPayload): + kwargs['fw_version'] = message.fw_version + relay_state.update(**kwargs) + + +def notify(user: User, action: UserAction) -> None: + def text_getter(lang: str): + action_name = bot.lang.get(f'user_action_{action.value}', lang) + user_name = user_any_name(user) + return 'ℹ ' + bot.lang.get('user_action_notification', lang, + user.id, user_name, action_name) + + bot.notify_all(text_getter, exclude=(user.id,)) + + +@bot.handler(message='enable') +def enable_handler(ctx: bot.Context) -> None: + mqtt_relay.set_power(True) + ctx.reply(ctx.lang('done')) + notify(ctx.user, UserAction.ON) + + +@bot.handler(message='disable') +def disable_handler(ctx: bot.Context) -> None: + mqtt_relay.set_power(False) + ctx.reply(ctx.lang('done')) + notify(ctx.user, UserAction.OFF) + + +@bot.handler(message='status') +def status(ctx: bot.Context) -> None: + label = ctx.lang('enabled') if relay_state.enabled else ctx.lang('disabled') + if relay_state.ever_updated: + date_label = '' + today = datetime.date.today() + if today != relay_state.update_time.date(): + yday = today - datetime.timedelta(days=1) + yyday = today - datetime.timedelta(days=2) + if yday == relay_state.update_time.date(): + date_label = ctx.lang('date_yday') + elif yyday == relay_state.update_time.date(): + date_label = ctx.lang('date_yyday') + else: + date_label = relay_state.update_time.strftime('%d.%m.%Y') + date_label += ' ' + date_label += ctx.lang('date_at') + ' ' + date_label += relay_state.update_time.strftime('%H:%M') + label += ctx.lang('status_updated', date_label) + ctx.reply(label) + + +def start(ctx: bot.Context) -> None: + if ctx.user_id in config['bot']['users'] or ctx.user_id in config['bot']['admin_users']: + ctx.reply(ctx.lang('start_message')) + else: + buttons = [ + [ctx.lang('send_access_request')] + ] + ctx.reply(ctx.lang('start_message_no_access'), markup=ReplyKeyboardMarkup(buttons, one_time_keyboard=False)) + + +@bot.exceptionhandler +def exception_handler(e: Exception, ctx: bot.Context) -> bool: + return False + + +@bot.defaultreplymarkup +def markup(ctx: Optional[bot.Context]) -> Optional[ReplyKeyboardMarkup]: + buttons = [[ctx.lang('enable'), ctx.lang('disable')], [ctx.lang('status')]] + if ctx.user_id in config['bot']['admin_users']: + buttons.append([ctx.lang('management')]) + return ReplyKeyboardMarkup(buttons, one_time_keyboard=False) + + +if __name__ == '__main__': + mqtt_relay = MQTTRelay(home_id=config['mqtt']['home_id'], + secret=config['mqtt']['relay']['secret']) + mqtt_relay.set_message_callback(on_mqtt_message) + mqtt_relay.configure_tls() + mqtt_relay.connect_and_loop(loop_forever=False) + + # bot.enable_logging(BotType.PUMP_MQTT) + bot.run(start_handler=start) + + mqtt_relay.disconnect() diff --git a/tools/mcuota.py b/tools/mcuota.py new file mode 100755 index 0000000..f7f2968 --- /dev/null +++ b/tools/mcuota.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +import sys +import os.path +sys.path.extend([ + os.path.realpath( + os.path.join(os.path.dirname(os.path.join(__file__)), '..') + ) +]) + +from time import sleep +from argparse import ArgumentParser +from src.home.config import config +from src.home.mqtt import MQTTRelay + + +def guess_filename(product: str, build_target: str): + return os.path.join( + products_dir, + product, + '.pio', + 'build', + build_target, + 'firmware.bin' + ) + + +def relayctl_publish_ota(filename: str, + home_id: str, + home_secret: str, + qos: int): + global stop + + def published(): + global stop + stop = True + + mqtt_relay = MQTTRelay(home_id=home_id, + secret=home_secret) + mqtt_relay.configure_tls() + mqtt_relay.connect_and_loop(loop_forever=False) + mqtt_relay.push_ota(filename, published, qos) + while not stop: + sleep(0.1) + mqtt_relay.disconnect() + + +stop = False +products = { + 'relayctl': { + 'build_target': 'esp12e', + 'callback': relayctl_publish_ota + } +} + +products_dir = os.path.join( + os.path.dirname(__file__), + '..', + 'platformio' +) + + +def main(): + parser = ArgumentParser() + parser.add_argument('--filename', type=str) + parser.add_argument('--home-id', type=str, required=True) + parser.add_argument('--product', type=str, required=True) + parser.add_argument('--qos', type=int, default=1) + + config.load('mcuota_push', parser=parser) + arg = parser.parse_args() + + if arg.product not in products: + raise ValueError(f'invalid product: \'{arg.product}\' not found') + + if arg.home_id not in config['mqtt']['home_secrets']: + raise ValueError(f'home_secret for home {arg.home_id} not found in config!') + + filename = arg.filename if arg.filename else guess_filename(arg.product, products[arg.product]['build_target']) + if not os.path.exists(filename): + raise OSError(f'file \'{filename}\' does not exists') + + print('Please confirm following OTA params.') + print('') + print(f' Home ID: {arg.home_id}') + print(f' Product: {arg.product}') + print(f'Firmware file: {filename}') + print('') + input('Press any key to continue or Ctrl+C to abort.') + + products[arg.product]['callback'](filename, arg.home_id, config['mqtt']['home_secrets'][arg.home_id], qos=arg.qos) + + +if __name__ == '__main__': + try: + main() + except Exception as e: + print(str(e), file=sys.stderr) + sys.exit(1) diff --git a/tools/mcuota.sh b/tools/mcuota.sh new file mode 100755 index 0000000..b2e7910 --- /dev/null +++ b/tools/mcuota.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +. "$DIR/lib.bash" + +if [ -d "$DIR/../venv" ]; then + echoinfo "activating python venv" + . "$DIR/../venv/bin/activate" +else + echowarn "python venv not found" +fi + +"$DIR/mcuota.py" "$@" \ No newline at end of file diff --git a/tools/minify.js b/tools/minify.js index 105c4eb..159e34b 100755 --- a/tools/minify.js +++ b/tools/minify.js @@ -52,12 +52,24 @@ const args = parseArgs(argv, { break case 'css': - console.log(new CleanCSS({level:2}).minify(content).styles) + console.log(new CleanCSS({ + level: 2 + }).minify(content).styles) break case 'js': console.log((await minifyJs(content, { - ecma: 5 + mangle: { + eval: true, + keep_classnames: false, + keep_fnames: false, + module: false, + toplevel: true, + }, + ecma: 5, + format: { + comments: false + } })).code) break } -- cgit v1.2.3