pump-mqtt-bot: wip; relayctl: somewhat stable
This commit is contained in:
parent
022ec129bb
commit
0a065f48be
@ -10,5 +10,6 @@
|
||||
"html-minifier-terser": "^7.1.0",
|
||||
"minimist": "^1.2.7",
|
||||
"terser": "^5.16.1"
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
@ -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};"
|
||||
|
@ -15,6 +15,9 @@ 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
|
||||
https://github.com/bertmelis/espMqttClient#unordered-acks
|
||||
;build_flags =
|
||||
; -DDEBUG
|
||||
; -DDEBUG_ESP_SSL
|
||||
; -DDEBUG_ESP_PORT=Serial
|
||||
build_type = release
|
@ -1,7 +1,6 @@
|
||||
#include <EEPROM.h>
|
||||
#include <strings.h>
|
||||
#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<uint8_t*>(&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 == '"')
|
||||
|
@ -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])
|
@ -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);
|
||||
|
||||
|
@ -1,21 +1,26 @@
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 JSON_SCAN_FIRST_LIST[] PROGMEM = "{\"list\":[";
|
||||
|
||||
static const char FIELD_NODE_ID[] = "nid";
|
||||
static const char FIELD_SSID[] = "ssid";
|
||||
static const char FIELD_PSK[] = "psk";
|
||||
static const char MSG_IS_INVALID[] PROGMEM = " is invalid";
|
||||
static const char MSG_IS_MISSING[] PROGMEM = " is missing";
|
||||
|
||||
static const char MSG_IS_INVALID[] = " is invalid";
|
||||
static const char MSG_IS_MISSING[] = " is missing";
|
||||
static const char GZIP[] PROGMEM = "gzip";
|
||||
static const char CONTENT_ENCODING[] PROGMEM = "Content-Encoding";
|
||||
static const char NOT_FOUND[] PROGMEM = "Not Found";
|
||||
|
||||
static const char GZIP[] = "gzip";
|
||||
static const char CONTENT_ENCODING[] = "Content-Encoding";
|
||||
static const char NOT_FOUND[] = "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<uint8_t*>(buf1k), ARRAY_SIZE(buf1k));
|
||||
char* cur = buf1k;
|
||||
bzero(reinterpret_cast<uint8_t*>(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(json_buf, "{\"result\":%d}", should_reboot ? 1 : 0);
|
||||
sprintf_P(json_buf, JSON_UPDATE_FMT, should_reboot ? 1 : 0);
|
||||
|
||||
auto resp = req->beginResponse(200, CONTENT_TYPE_JSON, json_buf);
|
||||
req->send(resp);
|
||||
server.send(200, FPSTR(CONTENT_TYPE_JSON), json_buf);
|
||||
|
||||
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))
|
||||
Update.printError(Serial);
|
||||
}
|
||||
if (should_reboot)
|
||||
restartTimer.once(1, restart);
|
||||
}, [&]() {
|
||||
HTTPUpload& upload = server.upload();
|
||||
|
||||
if (!Update.hasError() && len) {
|
||||
if (Update.write(data, len) != len) {
|
||||
Update.printError(Serial);
|
||||
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 (final) { // if the final flag is set then this is the last frame of data
|
||||
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;
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
|
@ -1,37 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <Ticker.h>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <Ticker.h>
|
||||
#include <utility>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#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<std::list<wifi::ScanResult>> _scanResults;
|
||||
char buf1k[1024];
|
||||
std::shared_ptr<std::list<wifi::ScanResult>> 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<std::list<wifi::ScanResult>> 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();
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#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
|
@ -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
|
||||
|
@ -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<int>(wifi_state) >= 1
|
||||
&& mqtt != nullptr) {
|
||||
if (!mqtt->loop()) {
|
||||
PRINTLN("mqtt::loop() returned false");
|
||||
// FIXME do something here
|
||||
}
|
||||
if (static_cast<int>(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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,13 @@
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#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");
|
||||
|
||||
sendInitialStat();
|
||||
|
||||
subscribe(TOPIC_RELAY_POWER, 1);
|
||||
subscribe(TOPIC_ADMIN_OTA);
|
||||
});
|
||||
|
||||
client.onDisconnect([&](DisconnectReason reason) {
|
||||
PRINTF("mqtt: disconnected, reason=%d\n", static_cast<int>(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<unsigned int>(*(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.setCallback([&](char* topic, byte* payload, unsigned int length) {
|
||||
this->callback(topic, payload, length);
|
||||
});
|
||||
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<uint8_t>(relay::getState() ? 1 : 0),
|
||||
.config_changed_value_present = 1,
|
||||
.config_changed = static_cast<uint8_t>(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<uint8_t>(relay::getState() ? 1 : 0),
|
||||
.config_changed_value_present = 1,
|
||||
.config_changed = static_cast<uint8_t>(cfg.flags.node_configured ||
|
||||
cfg.flags.wifi_configured ? 1 : 0)
|
||||
}
|
||||
};
|
||||
publish(TOPIC_STAT1, reinterpret_cast<uint8_t*>(&stat), sizeof(stat));
|
||||
publish(TOPIC_INITIAL_STAT, reinterpret_cast<uint8_t*>(&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<uint8_t>(relay::getState() ? 1 : 0),
|
||||
.config_changed_value_present = 0,
|
||||
.config_changed = 0
|
||||
}
|
||||
};
|
||||
|
||||
PRINTF("free heap: %d\n", ESP.getFreeHeap());
|
||||
|
||||
publish(TOPIC_STAT, reinterpret_cast<uint8_t*>(&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<uint8_t*>(&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<struct PowerPayload*>(payload);
|
||||
auto pd = reinterpret_cast<const struct PowerPayload*>(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<uint8_t*>(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<uint8_t*>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,30 +1,69 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <espMqttClient.h>
|
||||
#include <Ticker.h>
|
||||
#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 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);
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
}
|
||||
struct OTAResponse {
|
||||
OTAResult status;
|
||||
uint8_t error_code;
|
||||
} __attribute__((packed));
|
||||
|
||||
} }
|
@ -3,14 +3,14 @@
|
||||
#include <Arduino.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
} }
|
@ -7,201 +7,375 @@
|
||||
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,
|
||||
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};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
13
platformio/relayctl/src/util.h
Normal file
13
platformio/relayctl/src/util.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace homekit {
|
||||
|
||||
inline size_t otaGetMaxUpdateSize() {
|
||||
return (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
||||
}
|
||||
|
||||
inline void restart() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
#include <pgmspace.h>
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,12 +14,9 @@ struct ScanResult {
|
||||
String ssid;
|
||||
};
|
||||
|
||||
void getConfig(ConfigData &cfg, char **ssid_dst, char **psk_dst, char **hostname_dst);
|
||||
std::shared_ptr<std::list<ScanResult>> scan();
|
||||
void getConfig(ConfigData& cfg, const char** ssid, const char** psk, const char** hostname);
|
||||
|
||||
inline int8_t getRSSI() {
|
||||
return WiFi.RSSI();
|
||||
}
|
||||
std::shared_ptr<std::list<ScanResult>> 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[];
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
<title>Configuration</title>
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||
<script src="/md5.js"></script>
|
||||
<script src="/app.js"></script>
|
||||
</head>
|
||||
<body onload="initApp()">
|
||||
@ -22,14 +23,14 @@
|
||||
<div class="form_label">WiFi Password</div>
|
||||
<div class="form_input">
|
||||
<input type="password" value="" name="psk" class="full-width" id="fld_psk" maxlength="63" disabled>
|
||||
<div class="form_checkbox">
|
||||
<div class="form_sublabel">
|
||||
<label for="show_psk"><input type="checkbox" name="show_psk" id="show_psk"> show password</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form_label">Node ID</div>
|
||||
<div class="form_label">Home ID</div>
|
||||
<div class="form_input">
|
||||
<input type="text" value="" maxlength="16" name="nid" id="fld_nid" class="full-width" disabled>
|
||||
<input type="text" value="" maxlength="16" name="hid" id="fld_hid" class="full-width" disabled>
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled="disabled" name="submit">Save and Reboot</button>
|
||||
@ -42,7 +43,7 @@
|
||||
<div class="form_input">
|
||||
<input type="file" accept=".bin,.bin.gz" name="file">
|
||||
</div>
|
||||
<button type="submit" name="submit">Upload</button>
|
||||
<button type="submit" name="submit" disabled="disabled">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
615
platformio/relayctl/static/md5.js
Normal file
615
platformio/relayctl/static/md5.js
Normal file
@ -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();
|
||||
})();
|
@ -30,7 +30,7 @@ body, button, input[type="text"], input[type="password"] {
|
||||
.form_input {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form_checkbox {
|
||||
.form_sublabel {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ class BotType(Enum):
|
||||
ADMIN = auto()
|
||||
SOUND = auto()
|
||||
POLARIS_KETTLE = auto()
|
||||
PUMP_MQTT = auto()
|
||||
|
||||
|
||||
class TemperatureSensorLocation(Enum):
|
||||
|
@ -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:
|
||||
|
@ -1,2 +1,3 @@
|
||||
from .mqtt import MQTTBase
|
||||
from .util import poll_tick
|
||||
from .relay import MQTTRelay
|
@ -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}')
|
@ -0,0 +1 @@
|
||||
from .base_payload import MQTTPayload
|
@ -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)
|
||||
|
93
src/home/mqtt/relay.py
Normal file
93
src/home/mqtt/relay.py
Normal file
@ -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()
|
@ -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):
|
||||
|
191
src/pump_mqtt_bot.py
Executable file
191
src/pump_mqtt_bot.py
Executable file
@ -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='Пользователь <a href="tg://user?id=%d">%s</a> <b>%s</b> насос.',
|
||||
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 <a href="tg://user?id=%d">%s</a> turned the pump <b>%s</b>.',
|
||||
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()
|
98
tools/mcuota.py
Executable file
98
tools/mcuota.py
Executable file
@ -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)
|
14
tools/mcuota.sh
Executable file
14
tools/mcuota.sh
Executable file
@ -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" "$@"
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user