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",
|
"html-minifier-terser": "^7.1.0",
|
||||||
"minimist": "^1.2.7",
|
"minimist": "^1.2.7",
|
||||||
"terser": "^5.16.1"
|
"terser": "^5.16.1"
|
||||||
}
|
},
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,8 @@ for ext in html js css ico; do
|
|||||||
gzip |
|
gzip |
|
||||||
xxd -ps -c 16 |
|
xxd -ps -c 16 |
|
||||||
sed 's/.\{2\}/0x&, /g' |
|
sed 's/.\{2\}/0x&, /g' |
|
||||||
sed 's/^/ /'
|
sed 's/^/ /' |
|
||||||
|
sed 's/[ \t]*$//'
|
||||||
|
|
||||||
echo "};"
|
echo "};"
|
||||||
echo "const StaticFile $filename PROGMEM = {(sizeof(${filename}_content)/sizeof(${filename}_content[0])), ${filename}_content};"
|
echo "const StaticFile $filename PROGMEM = {(sizeof(${filename}_content)/sizeof(${filename}_content[0])), ${filename}_content};"
|
||||||
|
@ -14,7 +14,10 @@ board = esp12e
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
upload_port = /dev/ttyUSB0
|
upload_port = /dev/ttyUSB0
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
lib_deps =
|
lib_deps =
|
||||||
ESP Async WebServer
|
https://github.com/bertmelis/espMqttClient#unordered-acks
|
||||||
knolleary/PubSubClient@^2.8
|
;build_flags =
|
||||||
me-no-dev/ESPAsyncTCP@^1.2.2
|
; -DDEBUG
|
||||||
|
; -DDEBUG_ESP_SSL
|
||||||
|
; -DDEBUG_ESP_PORT=Serial
|
||||||
|
build_type = release
|
@ -1,7 +1,6 @@
|
|||||||
#include <EEPROM.h>
|
#include <EEPROM.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "config.def.h"
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
|
||||||
#define GET_DATA_CRC(data) \
|
#define GET_DATA_CRC(data) \
|
||||||
@ -10,7 +9,7 @@
|
|||||||
namespace homekit::config {
|
namespace homekit::config {
|
||||||
|
|
||||||
static const uint32_t magic = 0xdeadbeef;
|
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,
|
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
|
||||||
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
|
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
|
||||||
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
|
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) {
|
static uint32_t eeprom_crc(const uint8_t* data, size_t len) {
|
||||||
uint32_t crc = ~0L;
|
uint32_t crc = ~0L;
|
||||||
for (size_t index = 0; index < len; index++) {
|
for (size_t index = 0; index < len; index++) {
|
||||||
crc = crc_table[(crc ^ data[index]) & 0x0f] ^ (crc >> 4);
|
crc = pgm_read_word(&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] >> 4)) & 0x0f]) ^ (crc >> 4);
|
||||||
crc = ~crc;
|
crc = ~crc;
|
||||||
}
|
}
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigData read() {
|
ConfigData read() {
|
||||||
ConfigData data {0};
|
ConfigData data;
|
||||||
EEPROM.begin(sizeof(ConfigData));
|
EEPROM.begin(sizeof(ConfigData));
|
||||||
EEPROM.get(0, data);
|
EEPROM.get(0, data);
|
||||||
EEPROM.end();
|
EEPROM.end();
|
||||||
@ -40,25 +39,22 @@ ConfigData read() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool write(ConfigData& data) {
|
void write(ConfigData& data) {
|
||||||
EEPROM.begin(sizeof(ConfigData));
|
EEPROM.begin(sizeof(ConfigData));
|
||||||
data.magic = magic;
|
data.magic = magic;
|
||||||
data.crc = GET_DATA_CRC(data);
|
data.crc = GET_DATA_CRC(data);
|
||||||
EEPROM.put(0, data);
|
EEPROM.put(0, data);
|
||||||
return EEPROM.end();
|
EEPROM.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool erase() {
|
void erase() {
|
||||||
ConfigData data;
|
ConfigData data;
|
||||||
return erase(data);
|
erase(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool erase(ConfigData& data) {
|
void erase(ConfigData& data) {
|
||||||
bzero(reinterpret_cast<uint8_t*>(&data), sizeof(data));
|
bzero(reinterpret_cast<uint8_t*>(&data), sizeof(data));
|
||||||
data.magic = magic;
|
write(data);
|
||||||
EEPROM.begin(sizeof(data));
|
|
||||||
EEPROM.put(0, data);
|
|
||||||
return EEPROM.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isValid(ConfigData& data) {
|
bool isValid(ConfigData& data) {
|
||||||
@ -69,11 +65,11 @@ bool isDirty(ConfigData& data) {
|
|||||||
return data.magic != magic;
|
return data.magic != magic;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* ConfigData::escapeNodeId(char* buf, size_t len) {
|
char* ConfigData::escapeHomeId(char* buf, size_t len) {
|
||||||
if (len < 32)
|
if (len < 32)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
size_t id_len = strlen(node_id);
|
size_t id_len = strlen(home_id);
|
||||||
char* c = node_id;
|
char* c = home_id;
|
||||||
char* dst = buf;
|
char* dst = buf;
|
||||||
for (size_t i = 0; i < id_len; i++) {
|
for (size_t i = 0; i < id_len; i++) {
|
||||||
if (*c == '"')
|
if (*c == '"')
|
||||||
|
@ -18,15 +18,15 @@
|
|||||||
0x8a, 0xb0, 0xc9, 0x9c, 0xbb \
|
0x8a, 0xb0, 0xc9, 0x9c, 0xbb \
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DEFAULT_NODE_ID "relay-node"
|
#define DEFAULT_HOME_ID "relay-node"
|
||||||
|
|
||||||
#define FLASH_BUTTON_PIN 0
|
#define FLASH_BUTTON_PIN 0
|
||||||
#define ESP_LED_PIN 2
|
#define ESP_LED_PIN 2
|
||||||
#define BOARD_LED_PIN 16
|
#define BOARD_LED_PIN 16
|
||||||
|
#define RELAY_PIN 5
|
||||||
|
|
||||||
// 12 bytes string
|
// 12 bytes string
|
||||||
#define SECRET ""
|
#define HOME_SECRET_SIZE 12
|
||||||
|
#define HOME_SECRET ""
|
||||||
#define DEBUG
|
|
||||||
|
|
||||||
#define ARRAY_SIZE(X) sizeof((X))/sizeof((X)[0])
|
#define ARRAY_SIZE(X) sizeof((X))/sizeof((X)[0])
|
@ -14,20 +14,20 @@ struct ConfigData {
|
|||||||
// helpers
|
// helpers
|
||||||
uint32_t crc = 0;
|
uint32_t crc = 0;
|
||||||
uint32_t magic = 0;
|
uint32_t magic = 0;
|
||||||
char node_id[16] = {0};
|
char home_id[16] = {0};
|
||||||
char wifi_ssid[32] = {0};
|
char wifi_ssid[32] = {0};
|
||||||
char wifi_psk[63] = {0};
|
char wifi_psk[63] = {0};
|
||||||
ConfigFlags flags {0};
|
ConfigFlags flags {0};
|
||||||
|
|
||||||
// helper methods
|
// helper methods
|
||||||
char* escapeNodeId(char* buf, size_t len);
|
char* escapeHomeId(char* buf, size_t len);
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
|
||||||
ConfigData read();
|
ConfigData read();
|
||||||
bool write(ConfigData& data);
|
void write(ConfigData& data);
|
||||||
bool erase();
|
void erase();
|
||||||
bool erase(ConfigData& data);
|
void erase(ConfigData& data);
|
||||||
bool isValid(ConfigData& data);
|
bool isValid(ConfigData& data);
|
||||||
bool isDirty(ConfigData& data);
|
bool isDirty(ConfigData& data);
|
||||||
|
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "static.h"
|
||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "wifi.h"
|
|
||||||
#include "config.def.h"
|
#include "config.def.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "led.h"
|
||||||
|
|
||||||
namespace homekit {
|
namespace homekit {
|
||||||
|
|
||||||
static const char CONTENT_TYPE_HTML[] = "text/html; charset=utf-8";
|
using files::StaticFile;
|
||||||
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";
|
|
||||||
|
|
||||||
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
|
#ifdef DEBUG
|
||||||
",\"configured\":%d"
|
",\"configured\":%d"
|
||||||
",\"crc\":%u"
|
",\"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 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 MSG_IS_INVALID[] PROGMEM = " is invalid";
|
||||||
static const char FIELD_SSID[] = "ssid";
|
static const char MSG_IS_MISSING[] PROGMEM = " is missing";
|
||||||
static const char FIELD_PSK[] = "psk";
|
|
||||||
|
|
||||||
static const char MSG_IS_INVALID[] = " is invalid";
|
static const char GZIP[] PROGMEM = "gzip";
|
||||||
static const char MSG_IS_MISSING[] = " is missing";
|
static const char CONTENT_ENCODING[] PROGMEM = "Content-Encoding";
|
||||||
|
static const char NOT_FOUND[] PROGMEM = "Not Found";
|
||||||
|
|
||||||
static const char GZIP[] = "gzip";
|
static const char ROUTE_STYLE_CSS[] PROGMEM = "/style.css";
|
||||||
static const char CONTENT_ENCODING[] = "Content-Encoding";
|
static const char ROUTE_APP_JS[] PROGMEM = "/app.js";
|
||||||
static const char NOT_FOUND[] = "Not Found";
|
static const char ROUTE_MD5_JS[] PROGMEM = "/md5.js";
|
||||||
|
static const char ROUTE_FAVICON_ICO[] PROGMEM = "/favicon.ico";
|
||||||
static void do_restart() {
|
static const char ROUTE_STATUS[] PROGMEM = "/status";
|
||||||
ESP.restart();
|
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() {
|
void HttpServer::start() {
|
||||||
_server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest* req) { sendGzip(req, files::style_css, CONTENT_TYPE_CSS); });
|
server.on(FPSTR(ROUTE_STYLE_CSS), HTTP_GET, [&]() { sendGzip(files::style_css, CONTENT_TYPE_CSS); });
|
||||||
_server.on("/app.js", HTTP_GET, [](AsyncWebServerRequest* req) { sendGzip(req, files::app_js, CONTENT_TYPE_JS); });
|
server.on(FPSTR(ROUTE_APP_JS), HTTP_GET, [&]() { sendGzip(files::app_js, CONTENT_TYPE_JS); });
|
||||||
_server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest* req) { sendGzip(req, files::favicon_ico, CONTENT_TYPE_FAVICON); });
|
server.on(FPSTR(ROUTE_MD5_JS), HTTP_GET, [&]() { sendGzip(files::md5_js, CONTENT_TYPE_JS); });
|
||||||
_server.on("/", HTTP_GET, [&](AsyncWebServerRequest* req) { sendGzip(req, files::index_html, CONTENT_TYPE_HTML); });
|
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];
|
char json_buf[JSON_BUF_SIZE];
|
||||||
auto cfg = config::read();
|
auto cfg = config::read();
|
||||||
char *ssid, *psk;
|
|
||||||
wifi::getConfig(cfg, &ssid, &psk, nullptr);
|
|
||||||
|
|
||||||
if (!isValid(cfg) || !cfg.flags.node_configured) {
|
if (!isValid(cfg) || !cfg.flags.node_configured) {
|
||||||
sprintf(json_buf, JSON_STATUS_FMT
|
sprintf_P(json_buf, JSON_STATUS_FMT
|
||||||
, DEFAULT_NODE_ID
|
, DEFAULT_HOME_ID
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
, 0
|
, 0
|
||||||
, cfg.crc
|
, cfg.crc
|
||||||
@ -65,10 +73,10 @@ void HttpServer::start() {
|
|||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
char escaped_node_id[32];
|
char escaped_home_id[32];
|
||||||
char *escaped_node_id_res = cfg.escapeNodeId(escaped_node_id, 32);
|
char *escaped_home_id_res = cfg.escapeHomeId(escaped_home_id, 32);
|
||||||
sprintf(json_buf, JSON_STATUS_FMT
|
sprintf_P(json_buf, JSON_STATUS_FMT
|
||||||
, escaped_node_id_res == nullptr ? NODE_ID_ERROR : escaped_node_id
|
, escaped_home_id_res == nullptr ? "?" : escaped_home_id
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
, 1
|
, 1
|
||||||
, cfg.crc
|
, cfg.crc
|
||||||
@ -77,58 +85,54 @@ void HttpServer::start() {
|
|||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
req->send(200, CONTENT_TYPE_JSON, json_buf);
|
server.send(200, FPSTR(CONTENT_TYPE_JSON), json_buf);
|
||||||
});
|
});
|
||||||
|
server.on(FPSTR(ROUTE_STATUS), HTTP_POST, [&]() {
|
||||||
_server.on("/status", HTTP_POST, [&](AsyncWebServerRequest* req) {
|
|
||||||
auto cfg = config::read();
|
auto cfg = config::read();
|
||||||
char *s;
|
String s;
|
||||||
|
|
||||||
if (!handleInputStr(req, FIELD_SSID, 32, &s)) return;
|
if (!getInputParam("ssid", 32, s)) return;
|
||||||
strncpy(cfg.wifi_ssid, s, 32);
|
strncpy(cfg.wifi_ssid, s.c_str(), 32);
|
||||||
PRINTF("saving ssid: %s\n", cfg.wifi_ssid);
|
PRINTF("saving ssid: %s\n", cfg.wifi_ssid);
|
||||||
|
|
||||||
if (!handleInputStr(req, FIELD_PSK, 63, &s)) return;
|
if (!getInputParam("psk", 63, s)) return;
|
||||||
strncpy(cfg.wifi_psk, s, 63);
|
strncpy(cfg.wifi_psk, s.c_str(), 63);
|
||||||
PRINTF("saving psk: %s\n", cfg.wifi_psk);
|
PRINTF("saving psk: %s\n", cfg.wifi_psk);
|
||||||
|
|
||||||
if (!handleInputStr(req, FIELD_NODE_ID, 16, &s)) return;
|
if (!getInputParam("hid", 16, s)) return;
|
||||||
strcpy(cfg.node_id, s);
|
strcpy(cfg.home_id, s.c_str());
|
||||||
PRINTF("saving node id: %s\n", cfg.node_id);
|
PRINTF("saving home id: %s\n", cfg.home_id);
|
||||||
|
|
||||||
cfg.flags.node_configured = 1;
|
cfg.flags.node_configured = 1;
|
||||||
cfg.flags.wifi_configured = 1;
|
cfg.flags.wifi_configured = 1;
|
||||||
|
|
||||||
if (!config::write(cfg)) {
|
config::write(cfg);
|
||||||
PRINTLN("eeprom write error");
|
|
||||||
return sendError(req, "eeprom error");
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
config::erase();
|
||||||
restartTimer.once(0, do_restart);
|
restartTimer.once(1, restart);
|
||||||
});
|
});
|
||||||
|
|
||||||
_server.on("/heap", HTTP_GET, [](AsyncWebServerRequest* req) {
|
server.on(FPSTR(ROUTE_HEAP), HTTP_GET, [&]() {
|
||||||
req->send(200, CONTENT_TYPE_HTML, String(ESP.getFreeHeap()));
|
server.send(200, FPSTR(CONTENT_TYPE_HTML), String(ESP.getFreeHeap()));
|
||||||
});
|
});
|
||||||
|
|
||||||
_server.on("/scan", HTTP_GET, [&](AsyncWebServerRequest* req) {
|
server.on(FPSTR(ROUTE_SCAN), HTTP_GET, [&]() {
|
||||||
int i = 0;
|
size_t i = 0;
|
||||||
size_t len;
|
size_t len;
|
||||||
const char* ssid;
|
const char* ssid;
|
||||||
bool enough = false;
|
bool enough = false;
|
||||||
|
|
||||||
bzero(reinterpret_cast<uint8_t*>(buf1k), ARRAY_SIZE(buf1k));
|
bzero(reinterpret_cast<uint8_t*>(scanBuf), scanBufSize);
|
||||||
char* cur = buf1k;
|
char* cur = scanBuf;
|
||||||
|
|
||||||
strncpy(cur, "{\"list\":[", ARRAY_SIZE(buf1k));
|
strncpy_P(cur, JSON_SCAN_FIRST_LIST, scanBufSize);
|
||||||
cur += 9;
|
cur += 9;
|
||||||
|
|
||||||
for (auto& res: *_scanResults) {
|
for (auto& res: *scanResults) {
|
||||||
ssid = res.ssid.c_str();
|
ssid = res.ssid.c_str();
|
||||||
len = res.ssid.length();
|
len = res.ssid.length();
|
||||||
|
|
||||||
@ -151,10 +155,10 @@ void HttpServer::start() {
|
|||||||
// close array
|
// close array
|
||||||
*cur++ = ']';
|
*cur++ = ']';
|
||||||
|
|
||||||
if (cur - buf1k >= ARRAY_SIZE(buf1k)-40)
|
if ((size_t)(cur - scanBuf) >= (size_t) ARRAY_SIZE(scanBuf) - 40)
|
||||||
enough = true;
|
enough = true;
|
||||||
|
|
||||||
if (i < _scanResults->size()-1 || enough)
|
if (i < scanResults->size() - 1 || enough)
|
||||||
*cur++ = ',';
|
*cur++ = ',';
|
||||||
|
|
||||||
if (enough)
|
if (enough)
|
||||||
@ -167,82 +171,108 @@ void HttpServer::start() {
|
|||||||
*cur++ = '}';
|
*cur++ = '}';
|
||||||
*cur++ = '\0';
|
*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];
|
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);
|
server.send(200, FPSTR(CONTENT_TYPE_JSON), json_buf);
|
||||||
req->send(resp);
|
|
||||||
|
|
||||||
if (should_reboot) restartTimer.once(1, do_restart);
|
if (should_reboot)
|
||||||
}, [&](AsyncWebServerRequest *req, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
|
restartTimer.once(1, restart);
|
||||||
if (!index) {
|
}, [&]() {
|
||||||
PRINTF("update start: %s\n", filename.c_str());
|
HTTPUpload& upload = server.upload();
|
||||||
Update.runAsync(true);
|
|
||||||
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000))
|
|
||||||
Update.printError(Serial);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Update.hasError() && len) {
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
if (Update.write(data, len) != len) {
|
ota.clean();
|
||||||
Update.printError(Serial);
|
|
||||||
|
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)) {
|
if (Update.end(true)) {
|
||||||
PRINTF("update success: %uB\n", index+len);
|
PRINTF("http/ota: ok, total size %ul\n", upload.totalSize);
|
||||||
} else {
|
} else {
|
||||||
|
#ifdef DEBUG
|
||||||
Update.printError(Serial);
|
Update.printError(Serial);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_server.onNotFound([](AsyncWebServerRequest* req) {
|
server.onNotFound([&]() {
|
||||||
req->send(404, CONTENT_TYPE_HTML, NOT_FOUND);
|
server.send(404, FPSTR(CONTENT_TYPE_HTML), NOT_FOUND);
|
||||||
});
|
});
|
||||||
|
|
||||||
_server.begin();
|
server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpServer::sendGzip(AsyncWebServerRequest* req, StaticFile file, const char* content_type) {
|
void HttpServer::loop() {
|
||||||
auto resp = req->beginResponse_P(200, content_type, file.content, file.size);
|
server.handleClient();
|
||||||
resp->addHeader(CONTENT_ENCODING, GZIP);
|
|
||||||
req->send(resp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
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';
|
buf[31] = '\0';
|
||||||
req->send(400, CONTENT_TYPE_HTML, buf);
|
server.send(400, FPSTR(CONTENT_TYPE_HTML), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpServer::handleInputStr(AsyncWebServerRequest *req,
|
bool HttpServer::getInputParam(const char *field_name,
|
||||||
const char *field_name,
|
size_t max_len,
|
||||||
size_t max_len,
|
String& dst) {
|
||||||
char **dst) {
|
if (!server.hasArg(field_name)) {
|
||||||
const char* s;
|
sendError(String(field_name) + String(MSG_IS_MISSING));
|
||||||
size_t len;
|
|
||||||
|
|
||||||
if (!req->hasParam(field_name, true)) {
|
|
||||||
sendError(req, String(field_name) + String(MSG_IS_MISSING));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
s = req->getParam(field_name, true)->value().c_str();
|
String field = server.arg(field_name);
|
||||||
len = strlen(s);
|
if (!field.length() || (max_len != 0 && field.length() > max_len)) {
|
||||||
if (!len || len > max_len) {
|
sendError(String(field_name) + String(MSG_IS_INVALID));
|
||||||
sendError(req, String(FIELD_NODE_ID) + String(MSG_IS_INVALID));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*dst = (char*)s;
|
dst = field;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +1,56 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
#include <Ticker.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <Ticker.h>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
#include "static.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
|
#include "static.h"
|
||||||
|
|
||||||
namespace homekit {
|
namespace homekit {
|
||||||
|
|
||||||
|
struct OTAStatus {
|
||||||
|
bool invalidMd5;
|
||||||
|
|
||||||
|
OTAStatus() : invalidMd5(false) {}
|
||||||
|
|
||||||
|
inline void clean() {
|
||||||
|
invalidMd5 = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
using files::StaticFile;
|
using files::StaticFile;
|
||||||
|
|
||||||
class HttpServer {
|
class HttpServer {
|
||||||
private:
|
private:
|
||||||
AsyncWebServer _server;
|
ESP8266WebServer server;
|
||||||
Ticker restartTimer;
|
Ticker restartTimer;
|
||||||
std::shared_ptr<std::list<wifi::ScanResult>> _scanResults;
|
std::shared_ptr<std::list<wifi::ScanResult>> scanResults;
|
||||||
char buf1k[1024];
|
OTAStatus ota;
|
||||||
|
|
||||||
static void sendGzip(AsyncWebServerRequest* req, StaticFile file, const char* content_type);
|
char* scanBuf;
|
||||||
static void sendError(AsyncWebServerRequest* req, const String& message);
|
size_t scanBufSize;
|
||||||
|
|
||||||
static bool handleInputStr(AsyncWebServerRequest* req, const char* field_name, size_t max_len, char** dst);
|
void sendGzip(const StaticFile& file, PGM_P content_type);
|
||||||
// static bool handle_input_addr(AsyncWebServerRequest* req, const char* field_name, ConfigIPv4Addr* addr_dst);
|
void sendError(const String& message);
|
||||||
|
|
||||||
|
bool getInputParam(const char* field_name, size_t max_len, String& dst);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HttpServer(std::shared_ptr<std::list<wifi::ScanResult>> scanResults)
|
explicit HttpServer(std::shared_ptr<std::list<wifi::ScanResult>> scanResults)
|
||||||
: _server(80)
|
: server(80)
|
||||||
, _scanResults(std::move(scanResults)) {};
|
, scanResults(std::move(scanResults))
|
||||||
|
, scanBufSize(512) {
|
||||||
|
scanBuf = new char[scanBufSize];
|
||||||
|
};
|
||||||
|
|
||||||
|
~HttpServer() {
|
||||||
|
delete[] scanBuf;
|
||||||
|
}
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
|
void loop();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
@ -1,20 +1,9 @@
|
|||||||
#include "led.h"
|
#include "led.h"
|
||||||
|
#include "config.def.h"
|
||||||
|
|
||||||
namespace homekit {
|
namespace homekit {
|
||||||
|
|
||||||
void Led::blink(uint8_t count, uint16_t delay_ms) const {
|
Led board_led(BOARD_LED_PIN);
|
||||||
for (uint8_t i = 0; i < count; i++) {
|
Led esp_led(ESP_LED_PIN);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -17,8 +17,23 @@ public:
|
|||||||
inline void off() const { digitalWrite(_pin, HIGH); }
|
inline void off() const { digitalWrite(_pin, HIGH); }
|
||||||
inline void on() const { digitalWrite(_pin, LOW); }
|
inline void on() const { digitalWrite(_pin, LOW); }
|
||||||
|
|
||||||
void on_off(uint16_t delay_ms, bool last_delay = false) const;
|
void on_off(uint16_t delay_ms, bool last_delay = false) const {
|
||||||
void blink(uint8_t count, uint16_t delay_ms) 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
|
#ifdef DEBUG
|
||||||
|
|
||||||
namespace homekit {
|
|
||||||
void hexdump(const void* data, size_t size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define PRINTLN(s) Serial.println(s)
|
#define PRINTLN(s) Serial.println(s)
|
||||||
#define PRINT(s) Serial.print(s)
|
#define PRINT(s) Serial.print(s)
|
||||||
#define PRINTF(fmt, ...) Serial.printf(fmt, ##__VA_ARGS__)
|
#define PRINTF(fmt, ...) Serial.printf(fmt, ##__VA_ARGS__)
|
||||||
#define HEXDUMP(data, size) homekit::hexdump((data), (size));
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#define PRINTLN(s)
|
#define PRINTLN(s)
|
||||||
#define PRINT(s)
|
#define PRINT(s)
|
||||||
#define PRINTF(a)
|
#define PRINTF(...)
|
||||||
#define HEXDUMP(data, size)
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -15,9 +15,6 @@
|
|||||||
|
|
||||||
using namespace homekit;
|
using namespace homekit;
|
||||||
|
|
||||||
static Led board_led(BOARD_LED_PIN);
|
|
||||||
static Led esp_led(ESP_LED_PIN);
|
|
||||||
|
|
||||||
enum class WorkingMode {
|
enum class WorkingMode {
|
||||||
RECOVERY, // AP mode, http server with configuration
|
RECOVERY, // AP mode, http server with configuration
|
||||||
NORMAL, // MQTT client
|
NORMAL, // MQTT client
|
||||||
@ -39,13 +36,13 @@ static WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler;
|
|||||||
static Ticker wifiTimer;
|
static Ticker wifiTimer;
|
||||||
static StopWatch blinkStopWatch;
|
static StopWatch blinkStopWatch;
|
||||||
|
|
||||||
static DNSServer* dnsServer;
|
static DNSServer* dnsServer = nullptr;
|
||||||
|
|
||||||
static void onWifiConnected(const WiFiEventStationModeGotIP& event);
|
static void onWifiConnected(const WiFiEventStationModeGotIP& event);
|
||||||
static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event);
|
static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event);
|
||||||
|
|
||||||
static void wifiConnect() {
|
static void wifiConnect() {
|
||||||
char* ssid, *psk, *hostname;
|
const char *ssid, *psk, *hostname;
|
||||||
auto cfg = config::read();
|
auto cfg = config::read();
|
||||||
wifi::getConfig(cfg, &ssid, &psk, &hostname);
|
wifi::getConfig(cfg, &ssid, &psk, &hostname);
|
||||||
|
|
||||||
@ -54,16 +51,10 @@ static void wifiConnect() {
|
|||||||
wifi_state = WiFiConnectionState::WAITING;
|
wifi_state = WiFiConnectionState::WAITING;
|
||||||
|
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.setHostname(hostname);
|
WiFi.hostname(hostname);
|
||||||
WiFi.begin(ssid, psk);
|
WiFi.begin(ssid, psk);
|
||||||
|
|
||||||
PRINT("connecting to wifi..");
|
PRINT("connecting to wifi..");
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
|
||||||
esp_led.blink(2, 50);
|
|
||||||
delay(1000);
|
|
||||||
PRINT('.');
|
|
||||||
}
|
|
||||||
PRINT(' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wifiHotspot() {
|
static void wifiHotspot() {
|
||||||
@ -72,7 +63,7 @@ static void wifiHotspot() {
|
|||||||
auto scanResults = wifi::scan();
|
auto scanResults = wifi::scan();
|
||||||
|
|
||||||
WiFi.mode(WIFI_AP);
|
WiFi.mode(WIFI_AP);
|
||||||
WiFi.softAP(wifi::WIFI_AP_SSID);
|
WiFi.softAP(wifi::AP_SSID);
|
||||||
|
|
||||||
dnsServer = new DNSServer();
|
dnsServer = new DNSServer();
|
||||||
dnsServer->start(53, "*", WiFi.softAPIP());
|
dnsServer->start(53, "*", WiFi.softAPIP());
|
||||||
@ -82,6 +73,8 @@ static void wifiHotspot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
WiFi.disconnect();
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
#endif
|
#endif
|
||||||
@ -119,6 +112,13 @@ void setup() {
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if (working_mode == WorkingMode::NORMAL) {
|
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) {
|
if (wifi_state == WiFiConnectionState::JUST_CONNECTED) {
|
||||||
board_led.blink(3, 300);
|
board_led.blink(3, 300);
|
||||||
wifi_state = WiFiConnectionState::CONNECTED;
|
wifi_state = WiFiConnectionState::CONNECTED;
|
||||||
@ -131,28 +131,29 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto mqtt = (mqtt::MQTT*)service;
|
auto mqtt = (mqtt::MQTT*)service;
|
||||||
if (static_cast<int>(wifi_state) >= 1
|
if (static_cast<int>(wifi_state) >= 1 && mqtt != nullptr) {
|
||||||
&& mqtt != nullptr) {
|
mqtt->loop();
|
||||||
if (!mqtt->loop()) {
|
|
||||||
PRINTLN("mqtt::loop() returned false");
|
|
||||||
// FIXME do something here
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mqtt->statStopWatch.elapsed(10000)) {
|
if (mqtt->ota.readyToRestart) {
|
||||||
|
mqtt->disconnect();
|
||||||
|
} else if (mqtt->statStopWatch.elapsed(10000)) {
|
||||||
mqtt->sendStat();
|
mqtt->sendStat();
|
||||||
}
|
}
|
||||||
|
|
||||||
// periodically blink board led
|
// periodically blink board led
|
||||||
if (blinkStopWatch.elapsed(5000)) {
|
if (blinkStopWatch.elapsed(5000)) {
|
||||||
|
// PRINTF("free heap: %d\n", ESP.getFreeHeap());
|
||||||
board_led.blink(1, 10);
|
board_led.blink(1, 10);
|
||||||
blinkStopWatch.save();
|
blinkStopWatch.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(500);
|
|
||||||
} else {
|
} else {
|
||||||
if (dnsServer != nullptr)
|
if (dnsServer != nullptr)
|
||||||
dnsServer->processNextRequest();
|
dnsServer->processNextRequest();
|
||||||
|
|
||||||
|
auto httpServer = (HttpServer*)service;
|
||||||
|
if (httpServer != nullptr)
|
||||||
|
httpServer->loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
#include <ESP8266httpUpdate.h>
|
||||||
#include "mqtt.h"
|
#include "mqtt.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
#include "config.def.h"
|
#include "config.def.h"
|
||||||
#include "relay.h"
|
#include "relay.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "static.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "led.h"
|
||||||
|
|
||||||
namespace homekit::mqtt {
|
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_USERNAME[] = DEFAULT_MQTT_USERNAME;
|
||||||
static const char MQTT_PASSWORD[] = DEFAULT_MQTT_PASSWORD;
|
static const char MQTT_PASSWORD[] = DEFAULT_MQTT_PASSWORD;
|
||||||
static const char MQTT_CLIENT_ID[] = DEFAULT_MQTT_CLIENT_ID;
|
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_STAT[] = "stat";
|
||||||
static const char TOPIC_STAT1[] = "stat1";
|
static const char TOPIC_INITIAL_STAT[] = "stat1";
|
||||||
static const char TOPIC_ADMIN[] = "admin";
|
static const char TOPIC_OTA_RESPONSE[] = "otares";
|
||||||
static const char TOPIC_RELAY[] = "relay";
|
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());
|
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.setServer(MQTT_SERVER, MQTT_PORT);
|
||||||
client.setCallback([&](char* topic, byte* payload, unsigned int length) {
|
client.setClientId(MQTT_CLIENT_ID);
|
||||||
this->callback(topic, payload, length);
|
client.setCredentials(MQTT_USERNAME, MQTT_PASSWORD);
|
||||||
});
|
client.setCleanSession(true);
|
||||||
|
client.setFingerprint(MQTT_CA_FINGERPRINT);
|
||||||
|
client.setKeepAlive(MQTT_KEEPALIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTT::connect() {
|
void MQTT::connect() {
|
||||||
@ -40,127 +142,96 @@ void MQTT::connect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MQTT::reconnect() {
|
void MQTT::reconnect() {
|
||||||
char buf[128] {0};
|
|
||||||
|
|
||||||
if (client.connected()) {
|
if (client.connected()) {
|
||||||
PRINTLN("warning: already connected");
|
PRINTLN("warning: already connected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
client.connect();
|
||||||
// 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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTT::disconnect() {
|
void MQTT::disconnect() {
|
||||||
// TODO test how this works???
|
// TODO test how this works???
|
||||||
reconnectTimer.detach();
|
reconnectTimer.detach();
|
||||||
client.disconnect();
|
client.disconnect();
|
||||||
wifiClient.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MQTT::loop() {
|
uint16_t MQTT::publish(const String &topic, uint8_t *payload, size_t length) {
|
||||||
return client.loop();
|
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) {
|
void MQTT::loop() {
|
||||||
char full_topic[40] {0};
|
client.loop();
|
||||||
strcpy(full_topic, "/hk/");
|
|
||||||
strcat(full_topic, wifi::NODE_ID);
|
|
||||||
strcat(full_topic, "/");
|
|
||||||
strcat(full_topic, topic);
|
|
||||||
return client.publish(full_topic, payload, length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MQTT::subscribe(const char *topic) {
|
uint16_t MQTT::subscribe(const String &topic, uint8_t qos) {
|
||||||
char full_topic[40] {0};
|
String fullTopic = "hk/" + homeId + "/relay/" + topic;
|
||||||
strcpy(full_topic, "/hk/");
|
PRINTF("mqtt: subscribing to %s...\n", fullTopic.c_str());
|
||||||
strcat(full_topic, wifi::NODE_ID);
|
|
||||||
strcat(full_topic, "/");
|
uint16_t packetId = client.subscribe(fullTopic.c_str(), qos);
|
||||||
strcat(full_topic, topic);
|
if (!packetId)
|
||||||
strcat(full_topic, "/#");
|
PRINTF("error: failed to subscribe to %s\n", fullTopic.c_str());
|
||||||
bool res = client.subscribe(full_topic, 1);
|
return packetId;
|
||||||
if (!res)
|
|
||||||
PRINTF("error: failed to subscribe to %s\n", full_topic);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTT::sendInitialStat() {
|
void MQTT::sendInitialStat() {
|
||||||
auto cfg = config::read();
|
auto cfg = config::read();
|
||||||
InitialStatPayload stat {
|
InitialStatPayload stat{
|
||||||
.ip = wifi::getIPAsInteger(),
|
.ip = wifi::getIPAsInteger(),
|
||||||
.fw_version = FW_VERSION,
|
.fw_version = FW_VERSION,
|
||||||
.rssi = wifi::getRSSI(),
|
.rssi = wifi::getRSSI(),
|
||||||
.free_heap = ESP.getFreeHeap(),
|
.free_heap = ESP.getFreeHeap(),
|
||||||
.flags = StatFlags {
|
.flags = StatFlags{
|
||||||
.state = static_cast<uint8_t>(relay::getState() ? 1 : 0),
|
.state = static_cast<uint8_t>(relay::getState() ? 1 : 0),
|
||||||
.config_changed_value_present = 1,
|
.config_changed_value_present = 1,
|
||||||
.config_changed = static_cast<uint8_t>(cfg.flags.node_configured || cfg.flags.wifi_configured ? 1 : 0)
|
.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();
|
statStopWatch.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTT::sendStat() {
|
void MQTT::sendStat() {
|
||||||
StatPayload stat {
|
StatPayload stat{
|
||||||
.rssi = wifi::getRSSI(),
|
.rssi = wifi::getRSSI(),
|
||||||
.free_heap = ESP.getFreeHeap(),
|
.free_heap = ESP.getFreeHeap(),
|
||||||
.flags = StatFlags {
|
.flags = StatFlags{
|
||||||
.state = static_cast<uint8_t>(relay::getState() ? 1 : 0),
|
.state = static_cast<uint8_t>(relay::getState() ? 1 : 0),
|
||||||
.config_changed_value_present = 0,
|
.config_changed_value_present = 0,
|
||||||
.config_changed = 0
|
.config_changed = 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
PRINTF("free heap: %d\n", ESP.getFreeHeap());
|
|
||||||
|
|
||||||
publish(TOPIC_STAT, reinterpret_cast<uint8_t*>(&stat), sizeof(stat));
|
publish(TOPIC_STAT, reinterpret_cast<uint8_t*>(&stat), sizeof(stat));
|
||||||
statStopWatch.save();
|
statStopWatch.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTT::callback(char* topic, uint8_t* payload, uint32_t length) {
|
uint16_t MQTT::sendOtaResponse(OTAResult status, uint8_t error_code) {
|
||||||
const size_t bufsize = 16;
|
OTAResponse resp{
|
||||||
char relevant_topic[bufsize];
|
.status = status,
|
||||||
strncpy(relevant_topic, topic+strlen(wifi::NODE_ID)+5, bufsize);
|
.error_code = error_code
|
||||||
|
};
|
||||||
if (strncmp(TOPIC_RELAY_POWER, relevant_topic, bufsize) == 0) {
|
return publish(TOPIC_OTA_RESPONSE, reinterpret_cast<uint8_t*>(&resp), sizeof(resp));
|
||||||
handleRelayPowerPayload(payload, length);
|
|
||||||
} else {
|
|
||||||
PRINTF("error: invalid topic %s\n", topic);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTT::handleRelayPowerPayload(uint8_t *payload, uint32_t length) {
|
void MQTT::handleRelayPowerPayload(const uint8_t *payload, uint32_t length) {
|
||||||
if (length != sizeof(PowerPayload)) {
|
if (length != sizeof(PowerPayload)) {
|
||||||
PRINTF("error: size of payload (%ul) does not match expected (%ul)\n",
|
PRINTF("error: size of payload (%ul) does not match expected (%ul)\n",
|
||||||
length, sizeof(PowerPayload));
|
length, sizeof(PowerPayload));
|
||||||
return;
|
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) {
|
if (strncmp(pd->secret, MQTT_SECRET, sizeof(pd->secret)) != 0) {
|
||||||
PRINTLN("error: invalid secret");
|
PRINTLN("error: invalid secret");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pd->state == 1) {
|
if (pd->state == 1) {
|
||||||
|
PRINTLN("mqtt: turning relay on");
|
||||||
relay::setOn();
|
relay::setOn();
|
||||||
} else if (pd->state == 0) {
|
} else if (pd->state == 0) {
|
||||||
|
PRINTLN("mqtt: turning relay off");
|
||||||
relay::setOff();
|
relay::setOff();
|
||||||
} else {
|
} else {
|
||||||
PRINTLN("error: unexpected state value");
|
PRINTLN("error: unexpected state value");
|
||||||
@ -169,4 +240,114 @@ void MQTT::handleRelayPowerPayload(uint8_t *payload, uint32_t length) {
|
|||||||
sendStat();
|
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 <ESP8266WiFi.h>
|
||||||
#include <PubSubClient.h>
|
#include <espMqttClient.h>
|
||||||
#include <Ticker.h>
|
#include <Ticker.h>
|
||||||
#include "stopwatch.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 {
|
class MQTT {
|
||||||
private:
|
private:
|
||||||
WiFiClientSecure wifiClient;
|
String homeId;
|
||||||
PubSubClient client;
|
WiFiClientSecure httpsSecureClient;
|
||||||
|
espMqttClientSecure client;
|
||||||
Ticker reconnectTimer;
|
Ticker reconnectTimer;
|
||||||
|
Ticker restartTimer;
|
||||||
|
|
||||||
void callback(char* topic, uint8_t* payload, size_t length);
|
void handleRelayPowerPayload(const uint8_t* payload, uint32_t length);
|
||||||
void handleRelayPowerPayload(uint8_t* payload, uint32_t length);
|
void handleAdminOtaPayload(uint16_t packetId, const uint8_t* payload, size_t length, size_t index, size_t total);
|
||||||
bool publish(const char* topic, uint8_t* payload, size_t length);
|
|
||||||
bool subscribe(const char* topic);
|
uint16_t publish(const String& topic, uint8_t* payload, size_t length);
|
||||||
|
uint16_t subscribe(const String& topic, uint8_t qos = 0);
|
||||||
void sendInitialStat();
|
void sendInitialStat();
|
||||||
|
uint16_t sendOtaResponse(OTAResult status, uint8_t error_code = 0);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StopWatch statStopWatch;
|
StopWatch statStopWatch;
|
||||||
|
OTAStatus ota;
|
||||||
|
|
||||||
MQTT();
|
MQTT();
|
||||||
void connect();
|
void connect();
|
||||||
void disconnect();
|
void disconnect();
|
||||||
void reconnect();
|
void reconnect();
|
||||||
bool loop();
|
void loop();
|
||||||
void sendStat();
|
void sendStat();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,4 +93,9 @@ struct PowerPayload {
|
|||||||
uint8_t state;
|
uint8_t state;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
}
|
struct OTAResponse {
|
||||||
|
OTAResult status;
|
||||||
|
uint8_t error_code;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
} }
|
@ -3,14 +3,14 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "config.def.h"
|
#include "config.def.h"
|
||||||
|
|
||||||
namespace homekit::relay {
|
namespace homekit { namespace relay {
|
||||||
|
|
||||||
inline void init() {
|
inline void init() {
|
||||||
pinMode(RELAY_PIN, OUTPUT);
|
pinMode(RELAY_PIN, OUTPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool getState() {
|
inline bool getState() {
|
||||||
return digitalRead(RELAY_PIN) == 1;
|
return digitalRead(RELAY_PIN) == HIGH;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void setOn() {
|
inline void setOn() {
|
||||||
@ -21,4 +21,4 @@ inline void setOff() {
|
|||||||
digitalWrite(RELAY_PIN, LOW);
|
digitalWrite(RELAY_PIN, LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} }
|
@ -7,269 +7,443 @@
|
|||||||
namespace homekit::files {
|
namespace homekit::files {
|
||||||
|
|
||||||
static const uint8_t index_html_content[] PROGMEM = {
|
static const uint8_t index_html_content[] PROGMEM = {
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x56, 0xdb, 0x8a, 0xdc, 0x38,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x56, 0x4d, 0x8f, 0xdb, 0x36,
|
||||||
0x10, 0x7d, 0x9f, 0xaf, 0x50, 0xf4, 0xb0, 0xcc, 0xc0, 0xb4, 0xbd, 0xd3, 0x81, 0x21, 0x6c, 0x6c,
|
0x10, 0xbd, 0xef, 0xaf, 0x60, 0x78, 0x28, 0x76, 0x81, 0xb5, 0xd4, 0xdd, 0xa0, 0x46, 0xd0, 0x48,
|
||||||
0xc3, 0x92, 0x0b, 0x04, 0xc2, 0x32, 0x4c, 0x13, 0x02, 0x79, 0x69, 0x64, 0xb9, 0xdc, 0x56, 0x5a,
|
0x02, 0x82, 0x34, 0x45, 0x02, 0xe4, 0xb0, 0x58, 0x23, 0x28, 0xd0, 0x8b, 0x41, 0x51, 0x23, 0x8b,
|
||||||
0x96, 0xb4, 0x56, 0xd9, 0x3d, 0xbd, 0x5f, 0x9f, 0x92, 0x6c, 0xf7, 0x65, 0xd3, 0x4c, 0x2e, 0x2f,
|
0x35, 0x45, 0xb2, 0xe2, 0x48, 0x5e, 0xe7, 0xd7, 0x77, 0x48, 0x49, 0xfe, 0xd8, 0x1a, 0x9b, 0x8f,
|
||||||
0x6d, 0x57, 0xa9, 0xea, 0xd4, 0xa9, 0xa3, 0x92, 0xd5, 0xd9, 0x8b, 0xca, 0x4a, 0xdc, 0x3b, 0x60,
|
0x8b, 0xa5, 0x19, 0xce, 0xbc, 0x79, 0xf3, 0x38, 0x14, 0x9d, 0xbd, 0xaa, 0xac, 0xc4, 0xbd, 0x03,
|
||||||
0x0d, 0xb6, 0xba, 0xb8, 0xca, 0xc2, 0x83, 0x69, 0x61, 0x36, 0x39, 0x07, 0xc3, 0x83, 0x03, 0x44,
|
0xd6, 0x60, 0xab, 0x8b, 0xab, 0x2c, 0x3c, 0x98, 0x16, 0x66, 0x93, 0x73, 0x30, 0x3c, 0x38, 0x40,
|
||||||
0x45, 0x8f, 0x16, 0x50, 0x50, 0x0c, 0xba, 0x05, 0xfc, 0xdb, 0xab, 0x21, 0xe7, 0xd2, 0x1a, 0x04,
|
0x54, 0xf4, 0x68, 0x01, 0x05, 0xc5, 0xa0, 0x5b, 0xc0, 0xbf, 0xbd, 0x1a, 0x72, 0x2e, 0xad, 0x41,
|
||||||
0x83, 0x8b, 0x90, 0xcc, 0xd9, 0x64, 0xe5, 0x1c, 0xe1, 0x09, 0xd3, 0x00, 0xf2, 0x9a, 0xc9, 0x46,
|
0x30, 0xb8, 0x08, 0xc9, 0x9c, 0x4d, 0x56, 0xce, 0x11, 0x9e, 0x30, 0x0d, 0x20, 0x6f, 0x99, 0x6c,
|
||||||
0x74, 0x1e, 0x30, 0xef, 0xb1, 0x5e, 0xbc, 0xe2, 0x33, 0x86, 0x11, 0x2d, 0xe4, 0x7c, 0x50, 0xb0,
|
0x44, 0xe7, 0x01, 0xf3, 0x1e, 0xeb, 0xc5, 0x1b, 0x3e, 0x63, 0x18, 0xd1, 0x42, 0xce, 0x07, 0x05,
|
||||||
0x73, 0xb6, 0xc3, 0x93, 0xcc, 0x9d, 0xaa, 0xb0, 0xc9, 0x2b, 0x18, 0x94, 0x84, 0x45, 0x34, 0x6e,
|
0x3b, 0x67, 0x3b, 0x3c, 0xc9, 0xdc, 0xa9, 0x0a, 0x9b, 0xbc, 0x82, 0x41, 0x49, 0x58, 0x44, 0xe3,
|
||||||
0x95, 0x51, 0xa8, 0x84, 0x5e, 0x78, 0x29, 0x34, 0xe4, 0x77, 0xb7, 0x2d, 0x39, 0xda, 0xbe, 0x3d,
|
0x56, 0x19, 0x85, 0x4a, 0xe8, 0x85, 0x97, 0x42, 0x43, 0x7e, 0x77, 0xdb, 0x92, 0xa3, 0xed, 0xdb,
|
||||||
0xda, 0xe2, 0xe9, 0xcc, 0xee, 0x3d, 0x74, 0xd1, 0x10, 0x25, 0xd9, 0xc6, 0x86, 0xa2, 0xa8, 0x50,
|
0xa3, 0x2d, 0x9e, 0xce, 0xec, 0xde, 0x43, 0x17, 0x0d, 0x51, 0x92, 0x6d, 0x6c, 0x28, 0x8a, 0x0a,
|
||||||
0x43, 0xf1, 0xc6, 0x9a, 0x5a, 0x6d, 0xfa, 0x4e, 0xa0, 0xb2, 0x26, 0x4b, 0x47, 0xe7, 0x55, 0xa6,
|
0x35, 0x14, 0xef, 0xad, 0xa9, 0xd5, 0xa6, 0xef, 0x04, 0x2a, 0x6b, 0xb2, 0x74, 0x74, 0x5e, 0x65,
|
||||||
0x95, 0xd9, 0xb2, 0x0e, 0x74, 0xce, 0x7d, 0x43, 0x6c, 0x64, 0x8f, 0x4c, 0x11, 0x21, 0xce, 0x9a,
|
0x5a, 0x99, 0x2d, 0xeb, 0x40, 0xe7, 0xdc, 0x37, 0xc4, 0x46, 0xf6, 0xc8, 0x14, 0x11, 0xe2, 0xac,
|
||||||
0x0e, 0xea, 0x9c, 0xa7, 0xb5, 0x18, 0x82, 0x9d, 0xd0, 0x0f, 0x67, 0xa1, 0xd3, 0x9c, 0xab, 0x56,
|
0xe9, 0xa0, 0xce, 0x79, 0x5a, 0x8b, 0x21, 0xd8, 0x09, 0xfd, 0x70, 0x16, 0x3a, 0xcd, 0xb9, 0x6a,
|
||||||
0x6c, 0x20, 0x7d, 0x5a, 0xc4, 0xb8, 0x73, 0x08, 0xdc, 0x6b, 0xf0, 0x0d, 0x00, 0xce, 0xb1, 0x51,
|
0xc5, 0x06, 0xd2, 0xa7, 0x45, 0x8c, 0x3b, 0x87, 0xc0, 0xbd, 0x06, 0xdf, 0x00, 0xe0, 0x1c, 0x1b,
|
||||||
0x0c, 0xe9, 0xfd, 0x01, 0x2f, 0x86, 0x24, 0xc1, 0x43, 0x99, 0x5e, 0x76, 0xca, 0x21, 0xf3, 0x9d,
|
0xc5, 0x90, 0xde, 0x1f, 0xf0, 0x62, 0x48, 0x12, 0x3c, 0x94, 0xe9, 0x65, 0xa7, 0x1c, 0x32, 0xdf,
|
||||||
0xa4, 0x15, 0xe1, 0x5c, 0xf2, 0x95, 0xdc, 0x59, 0x3a, 0xba, 0x69, 0x3d, 0x9d, 0xa4, 0x2f, 0x6d,
|
0x49, 0x5a, 0x69, 0xab, 0xdf, 0x92, 0x7f, 0xc8, 0x9d, 0xa5, 0xa3, 0xfb, 0xf9, 0xba, 0x70, 0xee,
|
||||||
0xb5, 0x67, 0xd6, 0x68, 0x2b, 0x2a, 0x2a, 0x4f, 0x92, 0xfc, 0xed, 0xdc, 0xf5, 0x4d, 0x40, 0xa8,
|
0xf9, 0x7a, 0x3a, 0x6d, 0x4d, 0x69, 0xab, 0x3d, 0xb3, 0x46, 0x5b, 0x51, 0x11, 0x3d, 0x92, 0xec,
|
||||||
0xd4, 0xc0, 0xa4, 0x16, 0xde, 0x53, 0xa9, 0xd0, 0x11, 0x2f, 0x56, 0x80, 0xa8, 0xcc, 0xc6, 0xb3,
|
0x9d, 0x73, 0xd7, 0x37, 0xa1, 0x42, 0xa5, 0x06, 0x26, 0xb5, 0xf0, 0x9e, 0xa8, 0x84, 0x8e, 0x79,
|
||||||
0xcc, 0x3b, 0x61, 0x98, 0xa2, 0x8c, 0x90, 0x47, 0xae, 0x35, 0x89, 0x02, 0x9a, 0x17, 0xd7, 0x93,
|
0xb1, 0x02, 0x44, 0x65, 0x36, 0x9e, 0x65, 0xde, 0x09, 0xc3, 0x14, 0x65, 0x84, 0x3c, 0x72, 0xad,
|
||||||
0x9d, 0x24, 0xc9, 0x0d, 0x15, 0xa3, 0x28, 0xaa, 0x49, 0x40, 0xe7, 0x70, 0xa5, 0xb6, 0x72, 0x1b,
|
0x49, 0x34, 0xd0, 0xbc, 0xb8, 0x9e, 0xec, 0x24, 0x49, 0x6e, 0xa8, 0x18, 0x45, 0x51, 0x4d, 0x02,
|
||||||
0x4a, 0xd4, 0xb6, 0x6b, 0x19, 0x6d, 0x5c, 0x63, 0x09, 0xca, 0x59, 0x4f, 0xbd, 0x09, 0x19, 0x44,
|
0x3a, 0x87, 0x2b, 0xb5, 0x95, 0xdb, 0x50, 0xa2, 0xb6, 0x5d, 0xcb, 0x68, 0x63, 0x1b, 0x4b, 0x50,
|
||||||
0x8c, 0xdd, 0x08, 0xec, 0xa9, 0xb9, 0x71, 0x4b, 0x0d, 0xe0, 0xce, 0x76, 0xdb, 0xb5, 0x9f, 0x28,
|
0xce, 0x7a, 0xea, 0x5d, 0xc8, 0x20, 0x72, 0xec, 0x56, 0x60, 0x4f, 0xcd, 0x8f, 0x5b, 0x6e, 0x00,
|
||||||
0xfc, 0x8f, 0x60, 0x00, 0x9a, 0x39, 0x7c, 0x56, 0xef, 0x15, 0x5b, 0xad, 0x3e, 0xbc, 0xbd, 0x50,
|
0x77, 0xb6, 0xdb, 0xae, 0xfd, 0x44, 0xe1, 0x19, 0xc1, 0x00, 0x34, 0x73, 0xf8, 0x4b, 0xfd, 0xa9,
|
||||||
0x39, 0xc6, 0x29, 0xe3, 0x7a, 0x8c, 0x1a, 0x81, 0x06, 0x89, 0xb1, 0x0f, 0xef, 0x55, 0xb5, 0x1e,
|
0xd8, 0x6a, 0xf5, 0xe9, 0x8f, 0x0b, 0x95, 0x63, 0x9c, 0x32, 0xae, 0xc7, 0xa8, 0x21, 0x68, 0x90,
|
||||||
0xed, 0xb9, 0x64, 0x70, 0xf1, 0x43, 0x62, 0xaf, 0xf5, 0x38, 0x37, 0x21, 0xd1, 0xba, 0x40, 0x92,
|
0x18, 0xfb, 0xf0, 0x5e, 0x55, 0xeb, 0xd1, 0x9e, 0x4b, 0x06, 0x17, 0x3f, 0x24, 0xf6, 0x5a, 0x8f,
|
||||||
0x0d, 0x42, 0xf7, 0x14, 0xc8, 0x8b, 0x8f, 0x87, 0xae, 0xb3, 0x74, 0x5c, 0x0b, 0x0a, 0x8f, 0x70,
|
0x73, 0x15, 0x12, 0xad, 0x0b, 0x24, 0xd9, 0x20, 0x74, 0x4f, 0x81, 0xbc, 0xf8, 0x7c, 0xe8, 0x3a,
|
||||||
0xe1, 0xed, 0x32, 0x8f, 0x53, 0xbe, 0x0f, 0xe4, 0xa6, 0x06, 0xab, 0x1f, 0x72, 0x8e, 0x2f, 0xd3,
|
0x4b, 0xc7, 0xb5, 0xa0, 0xf0, 0x08, 0x17, 0xde, 0x2e, 0xf3, 0x38, 0xe5, 0xfb, 0x40, 0x6e, 0x6a,
|
||||||
0x04, 0xb8, 0x29, 0x89, 0x1f, 0x98, 0x4c, 0xd4, 0x9d, 0xdf, 0x5e, 0x62, 0x1e, 0x3b, 0xad, 0x75,
|
0xb0, 0xfa, 0x26, 0xe7, 0xf8, 0x32, 0x4d, 0x88, 0x9b, 0x92, 0xf8, 0x81, 0xc9, 0x44, 0xdd, 0xf9,
|
||||||
0xb5, 0x8e, 0xeb, 0x34, 0xdf, 0x1a, 0xcc, 0x86, 0x8e, 0x05, 0xbf, 0x7f, 0xc9, 0x59, 0xa5, 0x7c,
|
0xed, 0x25, 0xe6, 0xb1, 0xd3, 0x5a, 0x57, 0xeb, 0xb8, 0x4e, 0xf3, 0xaf, 0xc1, 0x6c, 0xe8, 0xd8,
|
||||||
0x18, 0xec, 0xea, 0x42, 0x71, 0xd9, 0x80, 0xdc, 0x96, 0xf6, 0x29, 0x4e, 0x64, 0x20, 0xcd, 0xc8,
|
0xf0, 0xe5, 0x6b, 0xce, 0x2a, 0xe5, 0xc3, 0xe0, 0x57, 0x17, 0x8a, 0xfb, 0xbe, 0x9c, 0xb8, 0xd2,
|
||||||
0x1d, 0xa7, 0x7a, 0x17, 0xa1, 0x8a, 0x33, 0x56, 0x87, 0xe8, 0x59, 0xc7, 0x39, 0x6c, 0x14, 0xfa,
|
0xc4, 0x86, 0x17, 0x46, 0xee, 0x38, 0xf5, 0xbb, 0x08, 0x55, 0x9c, 0xb1, 0x92, 0x0d, 0xc8, 0x6d,
|
||||||
0x90, 0xc4, 0xc2, 0x2b, 0x73, 0x87, 0xc6, 0x23, 0xf2, 0x51, 0xad, 0xe7, 0x45, 0xfb, 0xc7, 0x56,
|
0x69, 0x9f, 0x0e, 0x3a, 0xce, 0x61, 0xa3, 0xd0, 0x87, 0x24, 0x16, 0x5e, 0x99, 0x3b, 0x34, 0x1e,
|
||||||
0xc0, 0x7e, 0x62, 0x8b, 0x4f, 0x89, 0x85, 0x03, 0x73, 0x22, 0xd5, 0x49, 0xff, 0x77, 0xf7, 0x87,
|
0x91, 0x8f, 0x6a, 0xbd, 0x2c, 0xda, 0x47, 0xdb, 0x02, 0xfb, 0x8e, 0x2d, 0x3e, 0x25, 0x16, 0x0e,
|
||||||
0x39, 0x0b, 0x7b, 0x3e, 0xcb, 0x64, 0x2e, 0x0f, 0xc0, 0xa9, 0x54, 0x53, 0xfd, 0xb2, 0x47, 0xa4,
|
0xd4, 0x89, 0x54, 0x27, 0xfd, 0xdf, 0x2d, 0x67, 0xb2, 0x4d, 0xd8, 0xf3, 0x59, 0xa6, 0xe6, 0xf2,
|
||||||
0x81, 0x18, 0xeb, 0xf8, 0xbe, 0x6c, 0x15, 0x1e, 0xc3, 0x66, 0x1d, 0x46, 0x77, 0xb1, 0x12, 0x03,
|
0x00, 0x9c, 0x4a, 0x35, 0xd5, 0x2f, 0x7b, 0x44, 0x1a, 0x88, 0xb1, 0x0e, 0xc9, 0xd5, 0x2a, 0x3c,
|
||||||
0x30, 0x61, 0x2a, 0xf6, 0x08, 0xa5, 0xb5, 0x98, 0xa5, 0x63, 0x72, 0x00, 0x0b, 0xdc, 0x2f, 0xb6,
|
0x86, 0xcd, 0x3a, 0x8c, 0xee, 0x62, 0x25, 0x06, 0x60, 0xc2, 0x54, 0xec, 0x11, 0x4a, 0x6b, 0x31,
|
||||||
0x3e, 0x1d, 0xc0, 0x4f, 0xae, 0x12, 0x08, 0xac, 0x56, 0x5d, 0xbb, 0x13, 0x1d, 0xb0, 0xeb, 0xa4,
|
0x4b, 0xc7, 0xe4, 0x00, 0x16, 0xb8, 0x5f, 0x6c, 0x7d, 0x3a, 0x80, 0x5f, 0x5c, 0x25, 0x10, 0x58,
|
||||||
0x54, 0xe6, 0xe6, 0x77, 0x4f, 0x58, 0x1f, 0xd1, 0x38, 0x03, 0x23, 0x47, 0xe2, 0x6d, 0xaf, 0x51,
|
0xad, 0xba, 0x76, 0x27, 0x3a, 0x60, 0xd7, 0x49, 0xa9, 0xcc, 0xcd, 0xcf, 0x9e, 0xb0, 0x3e, 0xa2,
|
||||||
0x39, 0xd1, 0x61, 0x24, 0xb2, 0xa0, 0x55, 0x31, 0xeb, 0x32, 0xc6, 0x3e, 0x7b, 0xfc, 0x2e, 0x6a,
|
0x71, 0x06, 0x46, 0x8e, 0xc4, 0xdb, 0x5e, 0xa3, 0x72, 0xa2, 0xc3, 0x48, 0x64, 0x41, 0xab, 0x62,
|
||||||
0x5e, 0x2b, 0xe2, 0x4d, 0x25, 0x25, 0x38, 0xfa, 0x0a, 0x07, 0xba, 0xb7, 0xe1, 0x27, 0xd9, 0xfc,
|
0xd6, 0x65, 0x8c, 0x7d, 0xf1, 0xf8, 0x5d, 0xd4, 0xbc, 0x56, 0xc4, 0x9b, 0x4a, 0x4a, 0x70, 0xf4,
|
||||||
0x37, 0x23, 0xc7, 0x88, 0x1f, 0x28, 0x79, 0x2e, 0xe0, 0x27, 0x17, 0xbe, 0x32, 0xbf, 0xa2, 0xdb,
|
0x95, 0x0e, 0x74, 0x6f, 0xc3, 0x4f, 0xb2, 0xf9, 0x3a, 0x23, 0xc7, 0x88, 0x6f, 0x28, 0x79, 0x26,
|
||||||
0x23, 0x10, 0x71, 0x36, 0x93, 0xff, 0x5d, 0xbd, 0xba, 0x80, 0xc2, 0x7f, 0x8a, 0xe3, 0x8c, 0xab,
|
0xe0, 0x51, 0xfe, 0x2f, 0x2e, 0x7c, 0x6e, 0x7e, 0x44, 0xc0, 0x47, 0xa0, 0x0e, 0xd8, 0xdc, 0xc5,
|
||||||
0xfc, 0x7a, 0xca, 0x8a, 0x14, 0x7e, 0x85, 0xf3, 0x07, 0x53, 0xdb, 0x67, 0x98, 0xbe, 0x5b, 0x3d,
|
0xcf, 0x0a, 0xd7, 0x05, 0x14, 0xfe, 0x7d, 0x64, 0x27, 0x5c, 0xe5, 0xd7, 0x53, 0x56, 0xa4, 0xf0,
|
||||||
0xbc, 0x5a, 0xde, 0xdf, 0x2f, 0x4a, 0xe1, 0x69, 0xc2, 0xb2, 0xb2, 0xa0, 0x5b, 0x42, 0xec, 0x25,
|
0x23, 0x9c, 0x3f, 0x99, 0xda, 0xbe, 0xc0, 0xf4, 0xc3, 0xea, 0xe1, 0xcd, 0xfd, 0x72, 0xb9, 0x28,
|
||||||
0x6a, 0xaa, 0x51, 0xdc, 0x1e, 0x47, 0x64, 0x58, 0x66, 0x65, 0x57, 0x5c, 0x3d, 0xd0, 0xae, 0x32,
|
0x85, 0xa7, 0x51, 0xcb, 0xca, 0x82, 0xae, 0x13, 0xb1, 0x97, 0xa8, 0xa9, 0x46, 0x71, 0x7b, 0x9c,
|
||||||
0x5b, 0xb3, 0x4c, 0x4c, 0xb7, 0x45, 0xb8, 0x6d, 0xfd, 0x5f, 0x69, 0xba, 0x51, 0x98, 0xc8, 0xe6,
|
0x95, 0x61, 0x99, 0x95, 0x5d, 0x71, 0xf5, 0x40, 0xdb, 0xcb, 0x6c, 0xcd, 0x32, 0x31, 0x5d, 0x2b,
|
||||||
0xce, 0x25, 0xca, 0xa6, 0x8d, 0x6d, 0x61, 0x4b, 0x36, 0xf9, 0x52, 0x5e, 0x4c, 0x56, 0x96, 0x8a,
|
0xe1, 0x5a, 0xf6, 0xbf, 0xa7, 0xe9, 0x46, 0x61, 0x22, 0x9b, 0x3b, 0x97, 0x28, 0x9b, 0x36, 0x74,
|
||||||
0x82, 0x95, 0xfb, 0xef, 0x33, 0xa7, 0x2c, 0x5e, 0xbc, 0x1b, 0x36, 0x60, 0xf6, 0xec, 0x8b, 0x32,
|
0xba, 0xb6, 0x64, 0x93, 0x2f, 0xe5, 0xc5, 0x64, 0x65, 0xa9, 0x28, 0x58, 0xb9, 0xff, 0x7f, 0xe6,
|
||||||
0x96, 0x6e, 0xde, 0x21, 0x26, 0xfc, 0x21, 0xad, 0xdb, 0xbf, 0x66, 0xcb, 0x3f, 0x97, 0xcb, 0xe3,
|
0x94, 0xc5, 0x8b, 0x0f, 0xc3, 0x06, 0xcc, 0x9e, 0xfd, 0xad, 0x8c, 0xa5, 0x2b, 0x7a, 0x88, 0x09,
|
||||||
0x89, 0x0e, 0x77, 0x4d, 0xbc, 0x7a, 0xe2, 0xbf, 0x81, 0x6f, 0xa3, 0xf2, 0xc7, 0xe5, 0x1e, 0x08,
|
0xbf, 0x48, 0xeb, 0xf6, 0x6f, 0xd9, 0xfd, 0xaf, 0xf7, 0xf7, 0xc7, 0xa3, 0x1d, 0x2e, 0x9d, 0x78,
|
||||||
0x00, 0x00,
|
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};
|
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 = {
|
static const uint8_t app_js_content[] PROGMEM = {
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x56, 0x5b, 0x6f, 0xdc, 0x44,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x57, 0x6d, 0x6f, 0xdb, 0x46,
|
||||||
0x14, 0x7e, 0xe7, 0x57, 0x78, 0x47, 0x22, 0xf2, 0xb0, 0x8e, 0x73, 0x81, 0x87, 0x6a, 0x5d, 0x6b,
|
0x12, 0xfe, 0xde, 0x5f, 0x41, 0x2d, 0x70, 0x06, 0xf7, 0x44, 0xd3, 0x2f, 0xd7, 0x02, 0x85, 0x18,
|
||||||
0xd5, 0x2b, 0x2d, 0x6a, 0x9b, 0xaa, 0x49, 0x11, 0x52, 0x14, 0xa2, 0x59, 0xfb, 0x6c, 0xd6, 0x8d,
|
0x42, 0x48, 0xda, 0xe4, 0x92, 0x22, 0xa9, 0x8b, 0x24, 0x57, 0x1c, 0x60, 0xf8, 0x82, 0x95, 0x38,
|
||||||
0x77, 0xc6, 0x8c, 0xc7, 0xd9, 0x84, 0x6d, 0x24, 0xda, 0x3e, 0x80, 0x04, 0x52, 0x25, 0xde, 0xe1,
|
0xb2, 0x18, 0x53, 0xbb, 0xec, 0xee, 0xd2, 0xb2, 0x4f, 0x11, 0x70, 0x69, 0x0a, 0xb4, 0x40, 0x0b,
|
||||||
0x89, 0x1f, 0x90, 0x06, 0x02, 0x2d, 0x6d, 0xc3, 0x5f, 0xf0, 0xfe, 0x23, 0xce, 0x19, 0x7b, 0xaf,
|
0x04, 0xe8, 0xf7, 0xcb, 0xa7, 0xfe, 0x00, 0x37, 0x77, 0xbe, 0x4b, 0x2e, 0x4d, 0xfa, 0x17, 0xa8,
|
||||||
0x89, 0x44, 0xc5, 0x43, 0x36, 0xf6, 0x78, 0xce, 0xed, 0x3b, 0xe7, 0xfb, 0x66, 0xba, 0x85, 0x8c,
|
0x7f, 0xd4, 0x99, 0x25, 0x29, 0xd1, 0xb2, 0x81, 0x06, 0xf7, 0xc1, 0x12, 0xb5, 0x9c, 0x9d, 0x9d,
|
||||||
0x4c, 0xa2, 0xa4, 0x93, 0xe4, 0x1b, 0x9d, 0x27, 0x10, 0x19, 0x17, 0xf8, 0x50, 0x83, 0x29, 0xb4,
|
0x79, 0x66, 0x9e, 0x67, 0xc7, 0xa3, 0x42, 0x0e, 0x6d, 0xaa, 0xa4, 0x07, 0x3e, 0xf0, 0x99, 0x06,
|
||||||
0x64, 0xdb, 0xca, 0xae, 0x38, 0xd5, 0x87, 0x1d, 0x16, 0x86, 0x61, 0xf5, 0xe8, 0x67, 0x5a, 0x19,
|
0x5b, 0x68, 0xc9, 0xf6, 0xd5, 0xe0, 0x31, 0x0c, 0xad, 0xb7, 0xe7, 0xbe, 0x0e, 0x58, 0x1c, 0xc7,
|
||||||
0x65, 0x8e, 0x32, 0xf0, 0x8d, 0xda, 0x34, 0x3a, 0x91, 0x7b, 0x7e, 0x24, 0xd2, 0x14, 0x6d, 0x8f,
|
0xd5, 0x63, 0x98, 0x6b, 0x65, 0x95, 0x3d, 0xcd, 0x21, 0xb4, 0xea, 0x81, 0xd5, 0xa9, 0x3c, 0x0c,
|
||||||
0xbb, 0x63, 0x87, 0x7b, 0x30, 0x75, 0xe5, 0xc4, 0x2a, 0x2a, 0xfa, 0x20, 0x8d, 0xbf, 0x07, 0xe6,
|
0x87, 0x22, 0xcb, 0x70, 0xef, 0x7c, 0xd4, 0x78, 0xb2, 0x2b, 0x4f, 0x5e, 0xa2, 0x86, 0xc5, 0x04,
|
||||||
0x56, 0x0a, 0xf4, 0x78, 0xfd, 0xe8, 0x6e, 0x3c, 0x67, 0x10, 0x09, 0x19, 0x41, 0x7a, 0xeb, 0x00,
|
0xa4, 0x0d, 0x0f, 0xc1, 0xde, 0xcc, 0x80, 0x1e, 0x6f, 0x9c, 0xde, 0x49, 0x2e, 0xd8, 0x4b, 0xb2,
|
||||||
0xbf, 0xcd, 0x58, 0x02, 0x06, 0x03, 0x5a, 0xbb, 0x09, 0x5d, 0x51, 0xa4, 0x66, 0x69, 0x69, 0x71,
|
0x87, 0xd0, 0xd8, 0xd3, 0x0c, 0xc2, 0x24, 0x35, 0x79, 0x26, 0x4e, 0x63, 0x26, 0x95, 0x04, 0xb6,
|
||||||
0xc5, 0xe5, 0x1e, 0xf8, 0xb9, 0x51, 0xd9, 0x43, 0xad, 0x32, 0xb1, 0x27, 0xc8, 0x19, 0xed, 0x5a,
|
0x32, 0xd2, 0x2d, 0xa7, 0x80, 0x61, 0xc0, 0x31, 0xba, 0xfa, 0x14, 0x46, 0xa2, 0xc8, 0xec, 0xc6,
|
||||||
0x58, 0xb2, 0xdb, 0xaa, 0x18, 0xd7, 0x8b, 0x4e, 0x27, 0x85, 0xb0, 0xb1, 0x8a, 0x2b, 0x55, 0x98,
|
0xc6, 0xfa, 0x8a, 0xcf, 0x03, 0x72, 0xa8, 0xf2, 0x2f, 0xb4, 0xca, 0xc5, 0xa1, 0x20, 0x17, 0x64,
|
||||||
0x2f, 0x45, 0x5a, 0xe0, 0xc2, 0x9a, 0xd7, 0x58, 0x9b, 0x26, 0x04, 0x5a, 0x2b, 0xbd, 0x05, 0x87,
|
0xb5, 0xb6, 0xe4, 0xcc, 0x86, 0x42, 0x0e, 0x21, 0xbb, 0x51, 0x0c, 0x06, 0x19, 0xc4, 0x9d, 0x6d,
|
||||||
0x73, 0xe9, 0x38, 0x89, 0xcc, 0x0d, 0x79, 0x51, 0x5d, 0xe7, 0x16, 0x6d, 0x68, 0x83, 0xdf, 0x87,
|
0x5c, 0xa9, 0x8e, 0xf9, 0x52, 0x64, 0x05, 0x2e, 0xec, 0x04, 0x9d, 0x9d, 0x55, 0x18, 0x69, 0x3b,
|
||||||
0x3c, 0x17, 0x7b, 0xd0, 0x82, 0x26, 0x63, 0x53, 0xf3, 0x54, 0x45, 0xfb, 0x64, 0x89, 0x99, 0x80,
|
0x0c, 0x2f, 0x95, 0xc6, 0xd2, 0x6e, 0x35, 0xf2, 0x6e, 0x6a, 0xad, 0x74, 0x1f, 0xc2, 0x09, 0x18,
|
||||||
0xb9, 0x66, 0x10, 0xa6, 0x4e, 0x61, 0xc0, 0x65, 0x71, 0x92, 0x0b, 0x0c, 0x1f, 0x33, 0x6f, 0xfa,
|
0x23, 0x0e, 0xa1, 0x07, 0x5d, 0xd6, 0x8a, 0x5e, 0xd5, 0x29, 0x82, 0xbd, 0x6e, 0x11, 0xb5, 0x41,
|
||||||
0x38, 0x03, 0x43, 0x21, 0xa7, 0x86, 0x1a, 0xfa, 0xea, 0x00, 0x2e, 0xb3, 0x9d, 0x31, 0x48, 0x64,
|
0x61, 0xc1, 0x67, 0x98, 0xaa, 0xc0, 0x33, 0x13, 0x16, 0xac, 0x1e, 0x5b, 0xb0, 0x88, 0x6a, 0x8f,
|
||||||
0x62, 0x1e, 0x80, 0x19, 0x28, 0xbd, 0xbf, 0x09, 0xc6, 0x60, 0x33, 0x72, 0x97, 0x0f, 0xa7, 0x55,
|
0x86, 0x89, 0x3a, 0x86, 0xab, 0xb6, 0xb5, 0x6c, 0x8d, 0xcf, 0x67, 0xa3, 0x56, 0x25, 0x03, 0xcb,
|
||||||
0xb8, 0xe0, 0x19, 0x3e, 0x94, 0x45, 0x9a, 0x36, 0xc2, 0x10, 0xf1, 0x73, 0xc1, 0x3f, 0xb0, 0xe5,
|
0x67, 0xb2, 0xc8, 0xb2, 0x4e, 0x1c, 0x23, 0x3a, 0x3e, 0x84, 0xc7, 0x2e, 0x19, 0xcb, 0x03, 0xf2,
|
||||||
0x1a, 0xee, 0x4d, 0x82, 0x1d, 0x1f, 0x08, 0xed, 0x98, 0x70, 0x35, 0x98, 0xd8, 0x21, 0x60, 0xc3,
|
0x3a, 0x3f, 0x16, 0xda, 0x33, 0xf1, 0x76, 0xb4, 0xdc, 0x52, 0xe0, 0xfe, 0x5d, 0xac, 0x72, 0xb7,
|
||||||
0x75, 0x6c, 0x79, 0xb3, 0x49, 0x36, 0xd8, 0x4d, 0x96, 0x2a, 0x11, 0xa3, 0xf7, 0xdd, 0x54, 0x74,
|
0x6b, 0x36, 0x36, 0xa4, 0x6f, 0x7d, 0x96, 0x29, 0x91, 0x60, 0x75, 0x1f, 0x65, 0x62, 0x00, 0x19,
|
||||||
0x20, 0x65, 0x1c, 0x01, 0x3e, 0x4a, 0xc1, 0xc7, 0x94, 0xb2, 0x54, 0x1c, 0x85, 0x4c, 0x2a, 0x09,
|
0xe3, 0xd5, 0x9e, 0x24, 0x5e, 0xd6, 0x73, 0xa4, 0xf4, 0xc4, 0x84, 0x12, 0xec, 0x54, 0xe9, 0xa3,
|
||||||
0xac, 0x72, 0xa4, 0xc3, 0x49, 0xc3, 0xbb, 0x4a, 0xf7, 0x73, 0x5f, 0x56, 0xf9, 0xed, 0xe6, 0x75,
|
0x47, 0x98, 0x98, 0x45, 0x6b, 0x13, 0x25, 0xa1, 0x48, 0x92, 0x9b, 0x54, 0x89, 0xbb, 0xa9, 0xb1,
|
||||||
0x82, 0x81, 0xf6, 0x45, 0x1c, 0xdb, 0x5e, 0xdf, 0x4b, 0x72, 0x03, 0x12, 0xb4, 0xcb, 0xf2, 0xa2,
|
0x20, 0x41, 0xfb, 0xcc, 0x14, 0x83, 0x49, 0x6a, 0x59, 0xe0, 0x37, 0xc7, 0xb5, 0xfb, 0x23, 0x1c,
|
||||||
0xd3, 0x4f, 0x0c, 0xf3, 0xdc, 0x71, 0x0e, 0x33, 0xb8, 0x6b, 0x5f, 0x26, 0x71, 0x95, 0xb7, 0x8f,
|
0xa7, 0x49, 0x15, 0x5d, 0x88, 0x19, 0x4e, 0x7c, 0xde, 0x4f, 0xc2, 0xdc, 0x1c, 0xd5, 0x4b, 0x19,
|
||||||
0x68, 0xf4, 0x5d, 0xde, 0xd6, 0x7e, 0x96, 0xef, 0xd7, 0x4b, 0x29, 0xc8, 0x3d, 0xd3, 0xbb, 0x7a,
|
0xc8, 0x43, 0x3b, 0xbe, 0xf6, 0x71, 0xdf, 0x17, 0x19, 0x68, 0x0c, 0xab, 0xfc, 0x67, 0x79, 0x5e,
|
||||||
0xa5, 0xed, 0x8a, 0x14, 0xb4, 0x71, 0x59, 0xf9, 0x4b, 0x79, 0x56, 0x9e, 0x96, 0x67, 0xa3, 0xef,
|
0xbe, 0x2c, 0xcf, 0x17, 0xff, 0x28, 0xdf, 0x2e, 0xbe, 0x2f, 0x5f, 0x7b, 0xe5, 0xaf, 0xe5, 0x19,
|
||||||
0xca, 0xf7, 0xa3, 0x1f, 0xcb, 0x37, 0x4e, 0xf9, 0x4f, 0x79, 0x82, 0x2f, 0xe7, 0xe5, 0xdb, 0xd1,
|
0xfe, 0x78, 0x57, 0xbe, 0x59, 0xfc, 0xe0, 0xf9, 0xe5, 0x2f, 0xe5, 0xab, 0xf2, 0x2d, 0xfe, 0xfd,
|
||||||
0x4f, 0x8e, 0x5b, 0xbe, 0x2b, 0x5f, 0x97, 0xef, 0xf1, 0xef, 0x5d, 0x79, 0x42, 0x2b, 0xf8, 0x7c,
|
0x52, 0x9e, 0xd1, 0x0a, 0x3e, 0x9f, 0x2d, 0x9e, 0x7b, 0xe5, 0xbf, 0xcb, 0x37, 0xee, 0xc5, 0x99,
|
||||||
0x32, 0x7a, 0xe9, 0x94, 0x7f, 0x94, 0x6f, 0xed, 0x87, 0x13, 0x67, 0xd9, 0xb9, 0xe2, 0x8c, 0x9e,
|
0xb7, 0xe9, 0x7d, 0xec, 0x2d, 0x9e, 0x3a, 0x8b, 0x97, 0xb4, 0x0b, 0xff, 0x5e, 0x72, 0xc6, 0x03,
|
||||||
0xd9, 0x1d, 0xa7, 0x64, 0x85, 0x7f, 0xa7, 0x9c, 0x71, 0x6f, 0x7e, 0x4a, 0x79, 0x6b, 0x79, 0x2d,
|
0xea, 0x2f, 0xde, 0xdb, 0xdc, 0x41, 0x10, 0x92, 0xd0, 0x18, 0x0c, 0xca, 0x40, 0x86, 0x1d, 0x0f,
|
||||||
0x0c, 0xb5, 0x9f, 0xe7, 0x98, 0x5d, 0x0e, 0x29, 0x92, 0x03, 0xe2, 0xbb, 0x32, 0x86, 0xc3, 0xb9,
|
0xc9, 0x1d, 0x99, 0xc0, 0xc9, 0x85, 0x00, 0xbc, 0xf2, 0x25, 0x9e, 0xfd, 0x33, 0x1e, 0x7b, 0xe6,
|
||||||
0x4c, 0x9c, 0xf2, 0x14, 0x93, 0x78, 0x85, 0xf1, 0x4f, 0xac, 0xf3, 0xd1, 0xf3, 0xf2, 0x7c, 0xf4,
|
0x7c, 0x2e, 0xbe, 0x2e, 0xdf, 0x2d, 0xbe, 0x2d, 0xff, 0x87, 0x8f, 0x78, 0xd2, 0xbb, 0xc5, 0xd3,
|
||||||
0x7d, 0xf9, 0x37, 0x3e, 0x62, 0xc8, 0xf3, 0xd1, 0xb3, 0xd1, 0xf3, 0xd1, 0x0b, 0xca, 0xf0, 0x92,
|
0xc5, 0xd7, 0x8b, 0x67, 0x14, 0xd8, 0xd2, 0xef, 0xb1, 0x4a, 0x13, 0x6c, 0x02, 0x74, 0xea, 0xb0,
|
||||||
0x00, 0x07, 0x2a, 0x89, 0xab, 0x59, 0xc2, 0x30, 0x16, 0x21, 0xde, 0x9a, 0xf8, 0xfe, 0x99, 0x6a,
|
0xe0, 0xbd, 0xa5, 0xbb, 0x1f, 0x29, 0x1b, 0xdc, 0xf5, 0x0a, 0x9d, 0x9c, 0x7b, 0x63, 0x35, 0xc1,
|
||||||
0x44, 0x17, 0xaf, 0xd1, 0xe3, 0x99, 0x23, 0x55, 0x8c, 0xf3, 0x19, 0x5f, 0x74, 0x72, 0xcc, 0xb9,
|
0xae, 0x4a, 0x9a, 0x7d, 0x73, 0xce, 0x03, 0xdc, 0x33, 0x56, 0xd3, 0x47, 0x84, 0xc9, 0x65, 0x68,
|
||||||
0x87, 0xd6, 0x3d, 0x35, 0xd8, 0x25, 0xcc, 0x2e, 0x42, 0x1f, 0xf5, 0x84, 0xdc, 0x83, 0x45, 0xe8,
|
0x87, 0x63, 0x21, 0x0f, 0x61, 0x0d, 0xda, 0x0a, 0xc0, 0x8b, 0xbd, 0x46, 0x94, 0x65, 0xd8, 0xda,
|
||||||
0x2d, 0xc0, 0xf3, 0xc3, 0x4b, 0xa4, 0x67, 0x48, 0x1b, 0x23, 0x34, 0x52, 0xd9, 0x8f, 0x7a, 0x10,
|
0x56, 0x68, 0x64, 0x62, 0x38, 0x1c, 0xc3, 0xf0, 0x08, 0x92, 0x3e, 0xb3, 0x70, 0x62, 0x59, 0x8f,
|
||||||
0xed, 0x43, 0xdc, 0x66, 0x06, 0x69, 0xc2, 0x5a, 0x2c, 0x13, 0x79, 0x8e, 0x8d, 0xa6, 0xf9, 0xac,
|
0xe5, 0xc2, 0x18, 0x2c, 0x24, 0xf5, 0x54, 0x75, 0x24, 0x01, 0xf0, 0x9e, 0xc7, 0x51, 0x63, 0xd8,
|
||||||
0x42, 0x12, 0x2e, 0x1f, 0x18, 0xae, 0x9a, 0xc0, 0x89, 0xeb, 0x39, 0x34, 0x83, 0xe5, 0xb5, 0xc9,
|
0x78, 0xe9, 0xfa, 0x02, 0x6c, 0xd1, 0xe6, 0xce, 0xb2, 0x05, 0xeb, 0xf7, 0x2a, 0xa7, 0x8d, 0x66,
|
||||||
0xf0, 0xd6, 0xdf, 0x55, 0x46, 0x86, 0xf9, 0xb6, 0xd9, 0xc1, 0xd5, 0x99, 0x59, 0x08, 0x19, 0xab,
|
0xdf, 0x1e, 0xe0, 0x6a, 0xab, 0xd6, 0x31, 0x63, 0x55, 0xc2, 0xe2, 0xb1, 0x38, 0x21, 0xb9, 0xf0,
|
||||||
0x0a, 0x16, 0x4f, 0xc4, 0x21, 0xe9, 0x8d, 0xcb, 0x56, 0x90, 0xb1, 0xa6, 0xc8, 0x99, 0x37, 0x3c,
|
0xd9, 0x16, 0xb2, 0xcb, 0x16, 0x86, 0x05, 0xb3, 0x79, 0xeb, 0x48, 0x1b, 0x48, 0x3e, 0xb3, 0xfa,
|
||||||
0x9e, 0x09, 0x69, 0x3c, 0xc5, 0x87, 0x46, 0x1f, 0x0d, 0x93, 0xae, 0x6b, 0xb8, 0xe9, 0x69, 0x35,
|
0x74, 0x96, 0x8e, 0x7c, 0xcb, 0xed, 0x58, 0xab, 0xa9, 0x67, 0x23, 0xf0, 0x5d, 0x2b, 0x05, 0x32,
|
||||||
0x70, 0x4c, 0x00, 0xae, 0x1d, 0x35, 0x4f, 0xf9, 0x84, 0xe6, 0x6e, 0x12, 0x3f, 0x7d, 0x4a, 0xd4,
|
0x24, 0x0c, 0x1f, 0xa5, 0xc9, 0x93, 0x27, 0x44, 0x00, 0x24, 0x78, 0x75, 0x48, 0xb0, 0xfa, 0x55,
|
||||||
0x41, 0xf1, 0xa8, 0x82, 0x78, 0xd3, 0xb7, 0xaa, 0x1b, 0xf5, 0x02, 0xd2, 0xe5, 0x38, 0x12, 0x26,
|
0xd5, 0xa0, 0x5e, 0xc0, 0xce, 0x9f, 0x0f, 0x85, 0x1d, 0x8e, 0xd1, 0xd7, 0xac, 0x2a, 0x4a, 0x8a,
|
||||||
0xea, 0xa1, 0xaf, 0x61, 0xd5, 0x9e, 0xa9, 0x7e, 0x18, 0xcc, 0x68, 0x31, 0x25, 0x6c, 0xd1, 0x42,
|
0x8f, 0x7c, 0xbe, 0x1e, 0x0a, 0xca, 0xc4, 0x5a, 0x20, 0x8e, 0x65, 0x75, 0x20, 0x50, 0x07, 0x02,
|
||||||
0x42, 0x96, 0xa7, 0x75, 0x42, 0x50, 0x27, 0x04, 0x41, 0x0d, 0x62, 0x22, 0x11, 0xb9, 0x3b, 0x5b,
|
0x51, 0x0d, 0x5e, 0x2a, 0x11, 0xb1, 0xdb, 0x0f, 0xef, 0xdd, 0xc5, 0xbc, 0x22, 0x24, 0x8b, 0x4f,
|
||||||
0xf7, 0xef, 0x61, 0x7d, 0x01, 0x92, 0xca, 0x25, 0xb8, 0x14, 0x12, 0x56, 0x5d, 0x35, 0x7e, 0x8a,
|
0x30, 0x49, 0xe4, 0x9c, 0xbc, 0x66, 0xc3, 0x0c, 0x11, 0xad, 0x7b, 0x3b, 0x92, 0xdd, 0x6e, 0x85,
|
||||||
0xc8, 0xd6, 0x1c, 0x08, 0x54, 0xb3, 0x59, 0x61, 0x99, 0x84, 0xd5, 0x87, 0x6d, 0xb5, 0xb3, 0xbd,
|
0xa1, 0x8e, 0xab, 0x17, 0xfb, 0xf2, 0x60, 0x7f, 0xfb, 0x20, 0x50, 0xad, 0x9f, 0x3b, 0x07, 0x8d,
|
||||||
0xba, 0xe3, 0x89, 0x99, 0xd7, 0xb5, 0x9d, 0xb1, 0x5b, 0x91, 0x65, 0x20, 0x63, 0x57, 0xc2, 0xc0,
|
0x5b, 0x91, 0xe7, 0x20, 0x13, 0x5f, 0xc2, 0xd4, 0xdb, 0x73, 0x40, 0xfa, 0xba, 0xcb, 0x3c, 0x9f,
|
||||||
0xd9, 0xb0, 0x80, 0xba, 0x49, 0x93, 0x39, 0x2e, 0x6b, 0x0a, 0xfc, 0x17, 0x5f, 0xef, 0x73, 0xe6,
|
0x75, 0x15, 0x7e, 0x25, 0x37, 0x26, 0x9c, 0x05, 0x1a, 0x83, 0x17, 0x7e, 0x65, 0xdf, 0xce, 0x0f,
|
||||||
0x25, 0x98, 0x7c, 0xad, 0x13, 0x95, 0xd1, 0x6c, 0xb1, 0x70, 0xb1, 0x58, 0xa8, 0x8a, 0x9d, 0x17,
|
0x56, 0xf9, 0x41, 0x95, 0xdf, 0xfc, 0x02, 0xff, 0x29, 0x06, 0x58, 0x27, 0x78, 0x91, 0x27, 0xc2,
|
||||||
0xa8, 0xc7, 0x59, 0x2c, 0x0c, 0xdc, 0x46, 0x31, 0x70, 0xab, 0xec, 0x60, 0x51, 0x22, 0x0a, 0xbb,
|
0xc2, 0x8a, 0xdf, 0xf0, 0x5e, 0xfc, 0x46, 0x5c, 0x10, 0x13, 0x8d, 0xdf, 0x41, 0x07, 0xc2, 0x51,
|
||||||
0x63, 0xaa, 0x10, 0xf0, 0x41, 0x0a, 0x81, 0x88, 0x21, 0x5a, 0xb3, 0xe3, 0x8e, 0xaa, 0xd6, 0x00,
|
0x9a, 0x55, 0x1f, 0xa6, 0xce, 0x99, 0xd7, 0xe4, 0x6f, 0x38, 0xf0, 0x13, 0xf2, 0xe8, 0x75, 0xf9,
|
||||||
0xbf, 0x9b, 0xa4, 0xd5, 0x4f, 0x5e, 0xe3, 0xc2, 0x6b, 0x21, 0x19, 0x33, 0xe7, 0x37, 0xa4, 0xe2,
|
0xc6, 0x43, 0x2e, 0xfe, 0x8c, 0x84, 0x42, 0x46, 0x22, 0x2f, 0xcf, 0x89, 0xc7, 0xc4, 0xdd, 0xb7,
|
||||||
0x9b, 0xf2, 0xad, 0x83, 0xbc, 0x7e, 0x85, 0x9c, 0x44, 0x76, 0x23, 0xc7, 0xcf, 0x48, 0x13, 0x48,
|
0x6b, 0x84, 0x43, 0x72, 0x74, 0x76, 0x22, 0x54, 0xd4, 0x86, 0x4c, 0x51, 0x05, 0x2d, 0x61, 0xf2,
|
||||||
0x07, 0xde, 0x2f, 0x70, 0x16, 0x29, 0xd5, 0x58, 0x0b, 0x2a, 0xad, 0x1c, 0xb3, 0x30, 0xa0, 0x32,
|
0xd7, 0x7b, 0x77, 0x6f, 0x5b, 0x9b, 0xdf, 0x87, 0xaf, 0x0a, 0x30, 0x36, 0x10, 0x6e, 0xf1, 0x16,
|
||||||
0x64, 0x48, 0xd0, 0x7d, 0x75, 0xff, 0xde, 0x1d, 0x63, 0xb2, 0x47, 0xf0, 0x4d, 0x01, 0xb9, 0xf1,
|
0x66, 0xf2, 0xa9, 0xb0, 0x22, 0x6a, 0x8e, 0x6d, 0x50, 0x64, 0x14, 0x14, 0xb1, 0x62, 0x15, 0x21,
|
||||||
0xb4, 0x5d, 0xa4, 0x62, 0x6f, 0x0a, 0x23, 0x82, 0x89, 0x84, 0xd5, 0x60, 0x33, 0xca, 0x8b, 0x48,
|
0x22, 0xcf, 0xb1, 0x87, 0x8a, 0x9c, 0xf4, 0xef, 0x8a, 0x5c, 0xf1, 0xfe, 0x3b, 0xd4, 0x28, 0xf1,
|
||||||
0x34, 0x4d, 0x12, 0x1b, 0x84, 0xa8, 0x22, 0x06, 0xa4, 0xb1, 0x97, 0x14, 0x8e, 0x07, 0xee, 0x9e,
|
0x6b, 0xd9, 0xba, 0x08, 0x02, 0x1d, 0xaf, 0xb9, 0x0a, 0x4d, 0xfa, 0x77, 0x88, 0x24, 0x55, 0x12,
|
||||||
0xc6, 0x53, 0x66, 0xa1, 0x74, 0x9b, 0x01, 0x86, 0x5b, 0x70, 0xe5, 0xe7, 0xc9, 0xb7, 0x10, 0x48,
|
0xdd, 0x41, 0x72, 0x4d, 0xf7, 0xef, 0x09, 0x3b, 0x0e, 0xb5, 0x2a, 0xf0, 0xf8, 0x66, 0x75, 0x4b,
|
||||||
0x6a, 0x38, 0xba, 0x83, 0xf8, 0xaa, 0x6e, 0xdf, 0x17, 0xa6, 0xe7, 0x6b, 0x55, 0x60, 0xf8, 0xf1,
|
0xff, 0x71, 0x67, 0x7b, 0x9b, 0xe3, 0x8d, 0x7a, 0x2b, 0x3d, 0x81, 0xc4, 0xdf, 0xe5, 0x3d, 0xfc,
|
||||||
0xea, 0x8a, 0xfe, 0x64, 0x6d, 0x75, 0x95, 0xe3, 0x11, 0x7e, 0x3b, 0x39, 0x84, 0xd8, 0x5d, 0xe7,
|
0x1d, 0x34, 0xf9, 0xb5, 0x5a, 0x49, 0x76, 0xd9, 0x1f, 0x18, 0x35, 0xa6, 0x0c, 0x95, 0xd4, 0x20,
|
||||||
0x2d, 0x7c, 0xf7, 0xc6, 0xf5, 0xcd, 0x4c, 0x9c, 0x6c, 0xb2, 0x8f, 0x19, 0xcd, 0xaf, 0xf4, 0x95,
|
0x92, 0x53, 0x62, 0x08, 0x54, 0xb4, 0x8c, 0x97, 0x01, 0x35, 0xb5, 0x64, 0xe5, 0x8b, 0x75, 0x40,
|
||||||
0xd4, 0x20, 0xe2, 0x23, 0x22, 0x14, 0x54, 0x2c, 0x0e, 0x27, 0x09, 0xf1, 0xe1, 0x67, 0x78, 0x5e,
|
0x09, 0xcb, 0xff, 0x22, 0x8a, 0x4e, 0x42, 0x17, 0xdf, 0xb9, 0xc5, 0x77, 0x81, 0xb7, 0x78, 0xe6,
|
||||||
0x48, 0xdf, 0xee, 0xd8, 0xa4, 0x1d, 0xc8, 0x4c, 0x94, 0xc7, 0xf0, 0x8b, 0xcd, 0x8d, 0x07, 0x7e,
|
0x44, 0x8b, 0x74, 0xf4, 0x35, 0x3d, 0x91, 0x36, 0x92, 0xb4, 0x92, 0xce, 0x9e, 0xbb, 0x0d, 0xff,
|
||||||
0x26, 0x74, 0x0e, 0x2e, 0x7d, 0xcd, 0x33, 0xe4, 0x2d, 0xd0, 0xac, 0x70, 0x7a, 0xc3, 0x23, 0xbc,
|
0x42, 0xf3, 0x67, 0xe5, 0x7f, 0xf0, 0xe9, 0x1c, 0x0d, 0x9f, 0x2e, 0x9e, 0xb3, 0x08, 0x4b, 0xfc,
|
||||||
0x3d, 0xee, 0xca, 0xaf, 0x8b, 0xcd, 0xa0, 0x3e, 0xfc, 0x85, 0x1d, 0xb0, 0x52, 0x3e, 0xfa, 0xc1,
|
0x21, 0xca, 0xa5, 0x0c, 0x5d, 0x24, 0x0f, 0x28, 0x12, 0x4e, 0x74, 0x20, 0x0d, 0xfd, 0xec, 0xc1,
|
||||||
0x2e, 0x9e, 0x7b, 0xce, 0xe8, 0x85, 0xd5, 0x4c, 0xd2, 0xf3, 0x37, 0xf4, 0x44, 0x1a, 0x4d, 0x12,
|
0xde, 0xe7, 0x61, 0x2e, 0xb4, 0x01, 0x9f, 0xde, 0x9b, 0x1c, 0x29, 0x0f, 0x0f, 0x51, 0x6c, 0x38,
|
||||||
0x4f, 0x7a, 0x7f, 0x66, 0x0d, 0x7e, 0xc7, 0xed, 0x2f, 0xca, 0x3f, 0xf1, 0xe9, 0x0c, 0x37, 0x3e,
|
0xfd, 0xc2, 0x1b, 0xba, 0x5f, 0x15, 0x1f, 0x78, 0xaf, 0xe9, 0x82, 0x17, 0x18, 0xce, 0x2b, 0x8c,
|
||||||
0x1b, 0xbd, 0x64, 0xbc, 0x35, 0x89, 0x82, 0x8e, 0x5e, 0x63, 0x24, 0xab, 0xbf, 0x97, 0xf4, 0x9f,
|
0xd7, 0xc9, 0xe8, 0x15, 0x9d, 0xc0, 0x2e, 0x11, 0x15, 0x6f, 0xb2, 0xb9, 0x83, 0x02, 0xe8, 0xf2,
|
||||||
0x04, 0xc4, 0xd6, 0x6a, 0x87, 0x3a, 0x9c, 0x15, 0xa9, 0xcb, 0x86, 0x9d, 0xb6, 0x62, 0x63, 0x5d,
|
0x8d, 0xdb, 0xa2, 0xd4, 0xee, 0x74, 0x32, 0xc1, 0x7a, 0xfb, 0xec, 0x8b, 0xbd, 0x07, 0x0f, 0x59,
|
||||||
0xf6, 0x70, 0x63, 0x73, 0x8b, 0x79, 0x66, 0xac, 0x50, 0xc2, 0x5a, 0x11, 0x6a, 0x39, 0xb5, 0x5d,
|
0x60, 0x1b, 0x25, 0x12, 0xce, 0x9a, 0xc0, 0x34, 0xd4, 0x0d, 0x82, 0xfa, 0x8a, 0xc0, 0xad, 0x8b,
|
||||||
0xd3, 0x0c, 0x5d, 0x20, 0xc6, 0xb5, 0x2c, 0x43, 0xe0, 0x2e, 0x3d, 0xc3, 0xbd, 0x45, 0xe2, 0x1c,
|
0xf8, 0x3e, 0xf2, 0x57, 0x35, 0xfa, 0xd2, 0xe3, 0x85, 0x26, 0x9f, 0xad, 0xda, 0xf2, 0x16, 0xae,
|
||||||
0x37, 0x66, 0xb0, 0x9e, 0x3f, 0xe0, 0x3d, 0x1c, 0x03, 0x4b, 0x05, 0x1c, 0xc8, 0x4a, 0xb1, 0x3c,
|
0xdf, 0x47, 0x90, 0x40, 0x47, 0x14, 0x2e, 0x95, 0x7d, 0xbd, 0x5a, 0x36, 0x9e, 0xa6, 0x32, 0x51,
|
||||||
0x36, 0xde, 0xc1, 0x1a, 0x21, 0xc9, 0x36, 0x5e, 0x58, 0x74, 0xad, 0x2a, 0x34, 0x9e, 0xf6, 0xf2,
|
0xd3, 0x70, 0x92, 0x7c, 0x54, 0x41, 0x86, 0x20, 0xf1, 0x68, 0x7d, 0x66, 0xa8, 0x62, 0xc6, 0x89,
|
||||||
0x82, 0x52, 0x8c, 0xf7, 0xb6, 0x8e, 0x88, 0xf6, 0x9d, 0x7e, 0x91, 0x1b, 0xa7, 0x03, 0x8e, 0x70,
|
0x61, 0xab, 0x62, 0x66, 0x1f, 0xcd, 0x63, 0xd6, 0xad, 0x2e, 0xf6, 0x86, 0x10, 0x57, 0x82, 0xd2,
|
||||||
0x26, 0x76, 0x3c, 0x40, 0x87, 0x0d, 0x73, 0xd1, 0x48, 0x2a, 0xa7, 0xd0, 0xa9, 0x93, 0x67, 0x10,
|
0x60, 0x72, 0x11, 0xe8, 0xc5, 0xb7, 0x74, 0xef, 0xd4, 0x54, 0x5b, 0x7c, 0x53, 0x11, 0x91, 0x2e,
|
||||||
0x25, 0xdd, 0x84, 0xae, 0x23, 0x41, 0x3e, 0x48, 0x6a, 0x3d, 0x88, 0x44, 0x0e, 0xec, 0xf3, 0x5b,
|
0x2e, 0xf2, 0x41, 0x75, 0xbd, 0x6e, 0x6e, 0xa4, 0x52, 0xe8, 0xd3, 0x6a, 0xd8, 0x5b, 0x4b, 0x95,
|
||||||
0x5b, 0xac, 0x85, 0xd6, 0x93, 0x6b, 0xa5, 0xe4, 0x7c, 0xa2, 0x5a, 0x58, 0xb8, 0x23, 0xb9, 0xf4,
|
0xd8, 0xe2, 0x84, 0xa4, 0xd3, 0x3a, 0xa6, 0x35, 0x07, 0xa2, 0xc8, 0x06, 0x3a, 0x48, 0x2b, 0x35,
|
||||||
0x7b, 0x22, 0xdf, 0x18, 0x48, 0xba, 0xa3, 0x21, 0xa8, 0x47, 0xae, 0xe2, 0x38, 0x2f, 0xa6, 0x19,
|
0x88, 0x75, 0x25, 0xc9, 0x01, 0x6b, 0x2c, 0x58, 0x27, 0xa6, 0x7b, 0x09, 0xa7, 0xa7, 0xb4, 0x96,
|
||||||
0xba, 0x74, 0xa6, 0x86, 0x34, 0x80, 0xa8, 0xfc, 0x1b, 0x5d, 0x97, 0xb5, 0x19, 0x6f, 0xe3, 0x4f,
|
0x4f, 0x02, 0xcb, 0x4d, 0x52, 0x08, 0x36, 0x8e, 0x95, 0x03, 0x31, 0x3c, 0xf2, 0x26, 0x85, 0xb1,
|
||||||
0x8b, 0x2d, 0x31, 0xde, 0x04, 0x19, 0xa1, 0x30, 0x3f, 0x7e, 0x74, 0xf7, 0x86, 0xea, 0xe3, 0x20,
|
0xde, 0x00, 0x3c, 0xe1, 0x2d, 0xf7, 0x71, 0xea, 0xbd, 0x8e, 0xbc, 0xbc, 0x49, 0x2a, 0xaf, 0xd0,
|
||||||
0x11, 0xe5, 0x15, 0x6f, 0xb2, 0x90, 0x5d, 0xf6, 0x45, 0xa2, 0xda, 0x71, 0x1e, 0x74, 0x70, 0x24,
|
0x99, 0x67, 0x72, 0x18, 0xa6, 0xa3, 0x94, 0x66, 0xa4, 0xc8, 0x4c, 0xd3, 0xba, 0x69, 0x86, 0xc2,
|
||||||
0xf7, 0x03, 0x9b, 0x98, 0xed, 0xd1, 0x62, 0x66, 0xb5, 0x5e, 0x6e, 0xef, 0x04, 0x1f, 0x92, 0x63,
|
0x00, 0xfb, 0xf3, 0xcd, 0x87, 0xac, 0x47, 0x82, 0xed, 0xa3, 0x4e, 0x36, 0xba, 0xac, 0x70, 0x92,
|
||||||
0xe2, 0x67, 0x45, 0x8e, 0xa5, 0xfe, 0x8f, 0x44, 0x64, 0x98, 0xf8, 0x4f, 0x54, 0x82, 0xb3, 0x82,
|
0xf3, 0x34, 0xd7, 0xe1, 0x58, 0x98, 0xbd, 0xa9, 0xa4, 0x09, 0x11, 0xa1, 0x3a, 0xf5, 0x15, 0xc7,
|
||||||
0xa5, 0x1c, 0xdb, 0xbb, 0x92, 0xb8, 0x44, 0x41, 0xc6, 0x92, 0x21, 0xaa, 0xc1, 0xa2, 0x83, 0xc1,
|
0x3b, 0x4a, 0x76, 0x63, 0xdf, 0xcd, 0x05, 0x12, 0x99, 0x88, 0x77, 0xda, 0xde, 0xc8, 0x67, 0x7d,
|
||||||
0xab, 0x32, 0x47, 0x60, 0x90, 0x55, 0x82, 0x4e, 0xe5, 0x7a, 0xef, 0x1d, 0x64, 0x1b, 0x49, 0xc4,
|
0xc6, 0xfb, 0xf8, 0xd1, 0x63, 0x1b, 0x8c, 0x77, 0x41, 0x0e, 0x55, 0x02, 0x7f, 0xb9, 0x7f, 0xe7,
|
||||||
0x0d, 0x25, 0x51, 0x2c, 0xcc, 0x72, 0x75, 0x42, 0x33, 0xd4, 0x9a, 0x34, 0x89, 0xec, 0xcd, 0x77,
|
0x13, 0x35, 0xc1, 0x3e, 0xc7, 0x96, 0xc1, 0x8d, 0x5d, 0x86, 0x65, 0xb9, 0xe2, 0x8d, 0xde, 0x57,
|
||||||
0xe5, 0x70, 0x79, 0x30, 0x18, 0x2c, 0x93, 0xcc, 0x2e, 0x63, 0xdb, 0xaa, 0xec, 0xe8, 0x96, 0x20,
|
0x07, 0x9c, 0x47, 0x03, 0xc4, 0xf6, 0x28, 0x72, 0x11, 0xb9, 0xae, 0x5c, 0x86, 0xe4, 0x9a, 0x41,
|
||||||
0xfe, 0x83, 0xce, 0x88, 0x12, 0x31, 0x5a, 0xcc, 0x30, 0xda, 0x2e, 0xb2, 0xfa, 0x3c, 0x45, 0x88,
|
0xc4, 0xfb, 0x07, 0xd1, 0xfb, 0x04, 0x27, 0xc2, 0xbc, 0x30, 0x28, 0xed, 0xff, 0x47, 0x04, 0x3a,
|
||||||
0xc4, 0xd2, 0x52, 0x63, 0xe5, 0xeb, 0xf5, 0xa7, 0x6b, 0xeb, 0xeb, 0x9f, 0xae, 0xf8, 0x06, 0xf3,
|
0x16, 0xe1, 0x63, 0x95, 0x22, 0x2d, 0x30, 0x87, 0x79, 0x3d, 0x12, 0x5e, 0xd6, 0xd0, 0x46, 0x34,
|
||||||
0x71, 0x31, 0x39, 0xfb, 0x99, 0x5f, 0x9c, 0x9b, 0x1e, 0x96, 0xe8, 0x50, 0x74, 0x07, 0x0f, 0x98,
|
0x4d, 0xc5, 0x21, 0xba, 0x7c, 0x83, 0x2a, 0xe4, 0xd8, 0x5d, 0xec, 0x86, 0x1a, 0xb4, 0xb6, 0xbd,
|
||||||
0xf1, 0xb6, 0x40, 0xbb, 0x76, 0x5e, 0x67, 0x54, 0x42, 0xcc, 0xab, 0x04, 0x22, 0x65, 0x73, 0xbd,
|
0xed, 0x5a, 0xdc, 0x67, 0x9f, 0x28, 0x89, 0x8c, 0xb1, 0x9b, 0xd5, 0xd0, 0xc1, 0x50, 0x6d, 0xb3,
|
||||||
0x48, 0x47, 0x8d, 0xd8, 0xd8, 0xf3, 0x97, 0x36, 0x58, 0x96, 0xd9, 0xc9, 0x23, 0x94, 0xda, 0xb4,
|
0x74, 0xe8, 0x06, 0xee, 0xad, 0x93, 0xcd, 0xe9, 0x74, 0xba, 0x49, 0x77, 0xcb, 0x26, 0x16, 0xaa,
|
||||||
0xde, 0x42, 0xfa, 0x89, 0xe3, 0x01, 0x4e, 0x92, 0x1a, 0xf8, 0x74, 0xfa, 0x86, 0x43, 0x64, 0x66,
|
0x8a, 0x8e, 0xc6, 0x1d, 0xf3, 0x3b, 0x82, 0x56, 0x6b, 0x8d, 0x69, 0x6b, 0x0d, 0x2d, 0xb2, 0x7a,
|
||||||
0x0b, 0xfc, 0x0e, 0x2e, 0xa1, 0xad, 0xdd, 0xcd, 0xbd, 0x4c, 0xe5, 0xb3, 0x8b, 0x16, 0x69, 0x0c,
|
0x44, 0x40, 0x88, 0x70, 0x72, 0xed, 0x6c, 0xfd, 0x6d, 0xf7, 0xc9, 0xce, 0xee, 0xee, 0x9f, 0xb6,
|
||||||
0xea, 0xf2, 0xe0, 0xa3, 0x7f, 0x01, 0x1a, 0x08, 0xa7, 0x18, 0x21, 0x0d, 0x00, 0x00,
|
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};
|
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 = {
|
static const uint8_t style_css_content[] PROGMEM = {
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9d, 0x53, 0x5d, 0x6b, 0xdb, 0x30,
|
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,
|
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,
|
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,
|
0xeb, 0x26, 0x99, 0xf1, 0x7f, 0x9f, 0xfc, 0x11, 0x37, 0x69, 0x32, 0x28, 0xc3, 0x20, 0xb8, 0x1f,
|
||||||
0x3a, 0xe7, 0x9e, 0x7b, 0xcc, 0x8c, 0x38, 0x44, 0x05, 0x96, 0xba, 0xb1, 0x54, 0x08, 0x55, 0xe5,
|
0xd2, 0x39, 0xf7, 0xdc, 0x63, 0x66, 0xc4, 0x21, 0x2a, 0xb0, 0xd4, 0x8d, 0xa5, 0x42, 0xa8, 0x2a,
|
||||||
0x24, 0xc9, 0x4a, 0xea, 0x72, 0x55, 0x91, 0xa4, 0x65, 0x5d, 0x91, 0xd5, 0x88, 0xa6, 0x8a, 0x54,
|
0x27, 0x49, 0x56, 0x52, 0x97, 0xab, 0x8a, 0x24, 0x2d, 0xeb, 0x8a, 0xac, 0x46, 0x34, 0x55, 0xa4,
|
||||||
0x65, 0x6b, 0xfc, 0x8d, 0x07, 0x0b, 0x9f, 0x2d, 0xf5, 0x7e, 0x67, 0x9c, 0x78, 0x3a, 0x4d, 0x22,
|
0x2a, 0x5b, 0xe3, 0x6f, 0x3c, 0x58, 0xf8, 0x6c, 0xa9, 0xf7, 0x3b, 0xe3, 0xc4, 0xd3, 0x69, 0x12,
|
||||||
0xec, 0xf1, 0xa9, 0x91, 0xa6, 0xc2, 0xd8, 0xab, 0x3f, 0x40, 0xd2, 0x95, 0xdd, 0x67, 0x7d, 0x28,
|
0x61, 0x8f, 0x4f, 0x8d, 0x34, 0x15, 0xc6, 0x5e, 0xfd, 0x01, 0x92, 0xae, 0xec, 0x3e, 0xeb, 0x43,
|
||||||
0x69, 0xa9, 0xf4, 0x81, 0xc4, 0xd4, 0x5a, 0x0d, 0xb1, 0x3f, 0x78, 0x84, 0x32, 0xfa, 0xaa, 0x55,
|
0x49, 0x4b, 0xa5, 0x0f, 0x24, 0xa6, 0xd6, 0x6a, 0x88, 0xfd, 0xc1, 0x23, 0x94, 0xd1, 0x57, 0xad,
|
||||||
0xb5, 0xfd, 0x49, 0xf9, 0x63, 0x1f, 0x7e, 0x0b, 0x7d, 0xd1, 0x23, 0xe4, 0x06, 0x66, 0xbf, 0x7e,
|
0xaa, 0xed, 0x4f, 0xca, 0x1f, 0xfb, 0xf0, 0x5b, 0xe8, 0x8b, 0x1e, 0x21, 0x37, 0x30, 0xfb, 0xf5,
|
||||||
0x44, 0xdf, 0x41, 0xbf, 0x00, 0x2a, 0x4e, 0xa3, 0x2f, 0x4e, 0x51, 0x1d, 0x79, 0x5a, 0xf9, 0xd8,
|
0x23, 0xfa, 0x0e, 0xfa, 0x05, 0x50, 0x71, 0x1a, 0x7d, 0x71, 0x8a, 0xea, 0xc8, 0xd3, 0xca, 0xc7,
|
||||||
0x83, 0x53, 0xb2, 0x9d, 0xa3, 0x42, 0x0d, 0x47, 0xae, 0x69, 0x62, 0xf7, 0xb3, 0xfe, 0x38, 0xa2,
|
0x1e, 0x9c, 0x92, 0xed, 0x1c, 0x15, 0x6a, 0x98, 0xb8, 0xa6, 0x89, 0xdd, 0xcf, 0xfa, 0x63, 0x42,
|
||||||
0xed, 0x40, 0xe5, 0x05, 0x92, 0x55, 0x92, 0x64, 0x8c, 0xf2, 0x6d, 0xee, 0x4c, 0x5d, 0x89, 0x98,
|
0xdb, 0x81, 0xca, 0x0b, 0x24, 0xab, 0x24, 0xc9, 0x18, 0xe5, 0xdb, 0xdc, 0x99, 0xba, 0x12, 0x31,
|
||||||
0x1b, 0x6d, 0x1c, 0xb9, 0x01, 0x29, 0xef, 0xe5, 0x32, 0x63, 0x81, 0x3c, 0xb8, 0x98, 0x99, 0x30,
|
0x37, 0xda, 0x38, 0x72, 0x03, 0x52, 0xde, 0xcb, 0x65, 0xc6, 0x02, 0x79, 0x70, 0x31, 0x33, 0x61,
|
||||||
0x59, 0x49, 0xd2, 0x70, 0xdd, 0x1b, 0xad, 0xc4, 0xec, 0x46, 0x6c, 0x20, 0x81, 0x75, 0x36, 0x76,
|
0xb2, 0x92, 0xa4, 0xe1, 0xba, 0x37, 0x5a, 0x89, 0xd9, 0x8d, 0xd8, 0x40, 0x02, 0xeb, 0x6c, 0xec,
|
||||||
0xdf, 0xaf, 0x57, 0xc0, 0x1e, 0xb2, 0x93, 0x99, 0x96, 0x76, 0xdf, 0xce, 0x99, 0x36, 0x7c, 0x7b,
|
0xbe, 0x5f, 0xaf, 0x80, 0x3d, 0x64, 0x27, 0x33, 0x2d, 0xed, 0xbe, 0x9d, 0x33, 0x6d, 0xf8, 0xf6,
|
||||||
0x46, 0xa1, 0x9d, 0xcb, 0x5a, 0xeb, 0x78, 0xa7, 0x04, 0x16, 0x4d, 0x7f, 0x86, 0x74, 0xf2, 0x31,
|
0x8c, 0x42, 0x3b, 0x97, 0xb5, 0xd6, 0xf1, 0x4e, 0x09, 0x2c, 0x9a, 0xfe, 0x0c, 0xe9, 0xe4, 0x63,
|
||||||
0xe0, 0xec, 0xbb, 0x8b, 0x5d, 0xdb, 0x11, 0xb2, 0x6b, 0x36, 0xae, 0x7c, 0xd6, 0x94, 0xc1, 0x89,
|
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, 0x1c, 0x59, 0xf7,
|
0xe8, 0xb3, 0x64, 0xb6, 0xb8, 0x1c, 0x61, 0xec, 0xed, 0x65, 0x6e, 0x86, 0xb5, 0x4c, 0xac, 0x7b,
|
||||||
0x5c, 0xfa, 0x32, 0x2f, 0x80, 0x6f, 0xc3, 0xcb, 0xd3, 0x6b, 0x31, 0x1a, 0x4b, 0xc2, 0x6b, 0xed,
|
0x2e, 0x7d, 0xd9, 0xd7, 0xec, 0xec, 0xb5, 0x18, 0x8d, 0x25, 0xe1, 0xb5, 0xf6, 0x5d, 0x3b, 0x8b,
|
||||||
0xbb, 0x76, 0x16, 0x79, 0xd0, 0xc0, 0xb1, 0x19, 0x19, 0x3a, 0x2a, 0x54, 0xed, 0xc9, 0x43, 0x20,
|
0x3c, 0x68, 0xe0, 0xd8, 0x8c, 0x0c, 0x1d, 0x15, 0xaa, 0xf6, 0xe4, 0x21, 0x90, 0x19, 0x32, 0xa7,
|
||||||
0x33, 0x64, 0x4e, 0xf5, 0xe1, 0x1b, 0xce, 0xb9, 0xcc, 0x26, 0xd6, 0xeb, 0x50, 0xd9, 0x84, 0x46,
|
0xfa, 0xf0, 0x0d, 0xe7, 0x5c, 0x66, 0x47, 0xd6, 0xeb, 0x50, 0xd9, 0x84, 0x46, 0x53, 0x63, 0x58,
|
||||||
0x53, 0x63, 0x58, 0x27, 0x04, 0xc7, 0x5c, 0x43, 0x24, 0xd2, 0xf0, 0xda, 0x5f, 0xe0, 0x8e, 0xe9,
|
0x27, 0x04, 0xc7, 0x5c, 0x43, 0x24, 0xd2, 0xf0, 0xda, 0x5f, 0xe0, 0x8e, 0xe9, 0x01, 0x7d, 0x08,
|
||||||
0x01, 0x7d, 0x08, 0x9a, 0x5e, 0xb0, 0x82, 0x0a, 0xb3, 0xeb, 0x15, 0xe9, 0x37, 0xeb, 0x72, 0x46,
|
0x9a, 0x5e, 0xb0, 0x82, 0x0a, 0xb3, 0xeb, 0x15, 0xe9, 0x37, 0xeb, 0x72, 0x46, 0x6f, 0x93, 0xa8,
|
||||||
0x6f, 0x93, 0xa8, 0xfb, 0xe6, 0xe9, 0xf2, 0xae, 0x1d, 0xfc, 0x48, 0x84, 0xf2, 0x94, 0x69, 0x10,
|
0xfb, 0xe6, 0xe9, 0xf2, 0xae, 0x1d, 0xfc, 0x48, 0x84, 0xf2, 0x94, 0x69, 0x10, 0x57, 0x8d, 0x79,
|
||||||
0x57, 0x8d, 0x79, 0xb5, 0x3a, 0xa0, 0x1e, 0x2b, 0x23, 0xf0, 0x14, 0x37, 0x97, 0x6e, 0x91, 0x69,
|
0xb5, 0x3a, 0xa0, 0x4e, 0x95, 0x11, 0xf8, 0x18, 0x37, 0x97, 0x6e, 0x91, 0x69, 0x70, 0xcb, 0xe2,
|
||||||
0x70, 0xcb, 0x62, 0x72, 0xcb, 0x59, 0x72, 0x64, 0xf1, 0xbf, 0xa2, 0x75, 0xcb, 0x7b, 0x55, 0xed,
|
0xe8, 0x96, 0xb3, 0xe4, 0xc8, 0xe2, 0x7f, 0x45, 0xeb, 0x96, 0xf7, 0xaa, 0xda, 0x89, 0x51, 0xc3,
|
||||||
0xc4, 0xa8, 0xe1, 0x7d, 0x29, 0x27, 0xff, 0x25, 0xc1, 0xc3, 0xd6, 0x78, 0x85, 0x2a, 0xcc, 0xeb,
|
0xfb, 0x52, 0x1e, 0xfd, 0x97, 0x04, 0x0f, 0x5b, 0xe3, 0x15, 0xaa, 0x30, 0xaf, 0x03, 0x4d, 0x51,
|
||||||
0x40, 0x53, 0x54, 0x2f, 0x90, 0x75, 0x77, 0xe2, 0x62, 0xb0, 0x48, 0xfa, 0xe9, 0x8a, 0x67, 0x46,
|
0xbd, 0x40, 0xd6, 0xdd, 0x89, 0x8b, 0xc1, 0x22, 0xe9, 0xa7, 0x2b, 0x9e, 0x19, 0x05, 0xaa, 0x0c,
|
||||||
0x81, 0x2a, 0x83, 0xb7, 0xc7, 0xe9, 0xee, 0x48, 0x61, 0x5e, 0xc0, 0xbd, 0x47, 0xe0, 0x8c, 0xd7,
|
0xde, 0x4e, 0xd3, 0xdd, 0x91, 0xc2, 0xbc, 0x80, 0x7b, 0x8f, 0xc0, 0x19, 0xaf, 0x9d, 0x0f, 0xf0,
|
||||||
0xce, 0x07, 0x78, 0x6b, 0x54, 0x85, 0xe0, 0xde, 0x8c, 0xcf, 0x96, 0x9c, 0xc3, 0xe2, 0xfc, 0x0f,
|
0xd6, 0xa8, 0x0a, 0xc1, 0xbd, 0x19, 0x9f, 0x2d, 0x39, 0x87, 0xc5, 0xf9, 0x1f, 0xf2, 0x0f, 0x44,
|
||||||
0xf9, 0x07, 0x22, 0xe5, 0x1d, 0xdf, 0xa6, 0x33, 0x63, 0x50, 0x63, 0x6c, 0x9a, 0x2b, 0xff, 0xec,
|
0xca, 0x3b, 0xbe, 0x4d, 0x67, 0xc6, 0xa0, 0xc6, 0xd8, 0x34, 0x57, 0xfe, 0xd9, 0x81, 0x07, 0x8c,
|
||||||
0xc0, 0x03, 0x46, 0x6f, 0xe2, 0xeb, 0x7c, 0xa7, 0xff, 0x76, 0xb5, 0xd8, 0xa4, 0xeb, 0xf6, 0xc3,
|
0xde, 0xc4, 0xd7, 0xf9, 0x1e, 0xff, 0xdb, 0xd5, 0x62, 0x93, 0xae, 0xdb, 0x0f, 0x7f, 0x01, 0x37,
|
||||||
0x5f, 0xa4, 0x85, 0xb2, 0x78, 0xae, 0x04, 0x00, 0x00,
|
0xdb, 0x6e, 0xf6, 0xae, 0x04, 0x00, 0x00,
|
||||||
};
|
};
|
||||||
const StaticFile style_css PROGMEM = {(sizeof(style_css_content)/sizeof(style_css_content[0])), style_css_content};
|
const StaticFile style_css PROGMEM = {(sizeof(style_css_content)/sizeof(style_css_content[0])), style_css_content};
|
||||||
|
|
||||||
static const uint8_t favicon_ico_content[] PROGMEM = {
|
static const uint8_t favicon_ico_content[] PROGMEM = {
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0x99, 0x4b, 0x48, 0x15, 0x61,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0x99, 0x4b, 0x48, 0x15, 0x61,
|
||||||
0x14, 0xc7, 0xcf, 0xc5, 0x17, 0x2e, 0x4a, 0x57, 0xe5, 0x63, 0xe1, 0x85, 0x42, 0x23, 0x8c, 0x8c,
|
0x14, 0xc7, 0xcf, 0xc5, 0x17, 0x2e, 0x4a, 0x57, 0xe5, 0x63, 0xe1, 0x85, 0x42, 0x23, 0x8c, 0x8c,
|
||||||
0x20, 0x4d, 0x45, 0xdb, 0x59, 0x14, 0x2e, 0x7a, 0xab, 0x68, 0x1b, 0x17, 0xae, 0x24, 0x41, 0xf1,
|
0x20, 0x4d, 0x45, 0xdb, 0x59, 0x14, 0x2e, 0x7a, 0xab, 0x68, 0x1b, 0x17, 0xae, 0x24, 0x41, 0xf1,
|
||||||
0x41, 0xa0, 0x41, 0xa1, 0x11, 0x28, 0x1a, 0x2e, 0x12, 0x72, 0xe7, 0x03, 0x09, 0x74, 0x15, 0x54,
|
0x41, 0xa0, 0x41, 0xa1, 0x11, 0x28, 0x1a, 0x2e, 0x12, 0x72, 0xe7, 0x03, 0x09, 0x74, 0x15, 0x54,
|
||||||
0x1b, 0x97, 0xd9, 0x53, 0x23, 0x8a, 0x5a, 0x94, 0x25, 0x59, 0x91, 0x82, 0xa0, 0x81, 0x99, 0x39,
|
0x1b, 0x97, 0xd9, 0x53, 0x23, 0x8a, 0x5a, 0x94, 0x25, 0x59, 0x91, 0x82, 0xa0, 0x81, 0x99, 0x39,
|
||||||
0xfd, 0x8f, 0x73, 0x46, 0xbf, 0xc6, 0xb9, 0x73, 0x67, 0xee, 0x9d, 0xab, 0x41, 0x1e, 0xf8, 0x71,
|
0xfd, 0x8f, 0x73, 0x46, 0xbf, 0xc6, 0xb9, 0x73, 0x67, 0xee, 0x9d, 0xab, 0x41, 0x1e, 0xf8, 0x71,
|
||||||
0xe7, 0x7e, 0xe7, 0xf1, 0x9f, 0xb9, 0xdf, 0x7c, 0x8f, 0x99, 0x4b, 0xe4, 0xa3, 0x28, 0x4a, 0x4c,
|
0xe7, 0x7e, 0xe7, 0xf1, 0x9f, 0xb9, 0xdf, 0x7c, 0x8f, 0x99, 0x4b, 0xe4, 0xa3, 0x28, 0x4a, 0x4c,
|
||||||
0x24, 0x7c, 0xfa, 0xa9, 0x2a, 0x9a, 0xe8, 0x08, 0x11, 0x25, 0x25, 0xe9, 0xdf, 0xdb, 0xe2, 0x89,
|
0x24, 0x7c, 0xfa, 0xa9, 0x2a, 0x9a, 0xe8, 0x08, 0x11, 0x25, 0x25, 0xe9, 0xdf, 0xdb, 0xe2, 0x89,
|
||||||
0x7a, 0xd1, 0xe6, 0xf7, 0xeb, 0xdf, 0x07, 0x11, 0x97, 0xbe, 0x93, 0x68, 0x1f, 0x62, 0x70, 0x88,
|
0x7a, 0xd1, 0xe6, 0xf7, 0xeb, 0xdf, 0x07, 0x11, 0x97, 0xbe, 0x93, 0x68, 0x1f, 0x62, 0x70, 0x88,
|
||||||
0x16, 0xbd, 0x7d, 0xd5, 0x10, 0x37, 0x43, 0x3a, 0x0e, 0x2c, 0x06, 0xdc, 0x16, 0x62, 0x9c, 0xa5,
|
0x16, 0xbd, 0x7d, 0xd5, 0x10, 0x37, 0x43, 0x3a, 0x0e, 0x2c, 0x06, 0xdc, 0x16, 0x62, 0x9c, 0xa5,
|
||||||
0xfc, 0x65, 0xb1, 0x60, 0x58, 0x88, 0x75, 0x99, 0x9b, 0x06, 0x2a, 0xc1, 0x1b, 0xa1, 0x52, 0xda,
|
0xfc, 0x65, 0xb1, 0x60, 0x58, 0x88, 0x75, 0x99, 0x9b, 0x06, 0x2a, 0xc1, 0x1b, 0xa1, 0x52, 0xda,
|
||||||
0x82, 0x19, 0xae, 0x90, 0xca, 0xc0, 0x04, 0x58, 0x02, 0x9a, 0xb0, 0x24, 0x6d, 0xa5, 0x12, 0x13,
|
0x82, 0x19, 0xae, 0x90, 0xca, 0xc0, 0x04, 0x58, 0x02, 0x9a, 0xb0, 0x24, 0x6d, 0xa5, 0x12, 0x13,
|
||||||
0xc8, 0xd8, 0x3f, 0xab, 0xe4, 0x99, 0x99, 0x95, 0xfa, 0x56, 0xe6, 0x17, 0x8d, 0x40, 0xb9, 0x06,
|
0xc8, 0xd8, 0x3f, 0xab, 0xe4, 0x99, 0x99, 0x95, 0xfa, 0x56, 0xe6, 0x17, 0x8d, 0x40, 0xb9, 0x06,
|
||||||
0x13, 0x12, 0x6b, 0xb6, 0x4b, 0xa6, 0x73, 0x0e, 0xc4, 0x92, 0xc4, 0x9a, 0xed, 0xba, 0x83, 0x5c,
|
0x13, 0x12, 0x6b, 0xb6, 0x4b, 0xa6, 0x73, 0x0e, 0xc4, 0x92, 0xc4, 0x9a, 0xed, 0xba, 0x83, 0x5c,
|
||||||
0x83, 0x6b, 0x16, 0xf9, 0x1d, 0x2e, 0xf2, 0x3b, 0x2c, 0xf2, 0xdb, 0x5d, 0xe4, 0xb7, 0x9b, 0x72,
|
0x83, 0x6b, 0x16, 0xf9, 0x1d, 0x2e, 0xf2, 0x3b, 0x2c, 0xf2, 0xdb, 0x5d, 0xe4, 0xb7, 0x9b, 0x72,
|
||||||
0xf9, 0xfe, 0xea, 0x76, 0x91, 0xcf, 0xb1, 0x6a, 0x3f, 0x9e, 0x03, 0x5f, 0x5c, 0xe4, 0x73, 0xec,
|
0xf9, 0xfe, 0xea, 0x76, 0x91, 0xcf, 0xb1, 0x6a, 0x3f, 0x9e, 0x03, 0x5f, 0x5c, 0xe4, 0x73, 0xec,
|
||||||
0x59, 0x25, 0xbf, 0x00, 0xdc, 0x02, 0x5d, 0xe0, 0x2e, 0x58, 0x00, 0xf7, 0xe5, 0x7b, 0x97, 0x1c,
|
0x59, 0x25, 0xbf, 0x00, 0xdc, 0x02, 0x5d, 0xe0, 0x2e, 0x58, 0x00, 0xf7, 0xe5, 0x7b, 0x97, 0x1c,
|
||||||
0x2f, 0x88, 0xaf, 0x4b, 0x62, 0x0b, 0x4c, 0xd7, 0xe0, 0x93, 0xcf, 0x7c, 0xf0, 0x99, 0xf4, 0x7b,
|
0x2f, 0x88, 0xaf, 0x4b, 0x62, 0x0b, 0x4c, 0xd7, 0xe0, 0x93, 0xcf, 0x7c, 0xf0, 0x99, 0xf4, 0x7b,
|
||||||
0xc9, 0xb0, 0x52, 0x69, 0xcb, 0x37, 0xc5, 0x5a, 0x59, 0x1e, 0x98, 0x06, 0x25, 0x4a, 0x5b, 0x89,
|
0xc9, 0xb0, 0x52, 0x69, 0xcb, 0x37, 0xc5, 0x5a, 0x59, 0x1e, 0x98, 0x06, 0x25, 0x4a, 0x5b, 0x89,
|
||||||
0xb4, 0xe5, 0xd9, 0xe4, 0x19, 0x96, 0x09, 0x9e, 0x83, 0x22, 0xa5, 0xad, 0x48, 0xda, 0x32, 0xcd,
|
0xb4, 0xe5, 0xd9, 0xe4, 0x19, 0x96, 0x09, 0x9e, 0x83, 0x22, 0xa5, 0xad, 0x48, 0xda, 0x32, 0xcd,
|
||||||
0xc1, 0x8b, 0x18, 0x55, 0x93, 0x71, 0x44, 0xa3, 0x51, 0x44, 0x2d, 0x3e, 0x1d, 0xa7, 0xc6, 0x79,
|
0xc1, 0x8b, 0x18, 0x55, 0x93, 0x71, 0x44, 0xa3, 0x51, 0x44, 0x2d, 0x3e, 0x1d, 0xa7, 0xc6, 0x79,
|
||||||
0x3c, 0xcf, 0x60, 0x2a, 0xa2, 0x2c, 0x52, 0xe6, 0x99, 0x78, 0x57, 0xf3, 0x8c, 0xd9, 0x52, 0xc1,
|
0x3c, 0xcf, 0x60, 0x2a, 0xa2, 0x2c, 0x52, 0xe6, 0x99, 0x78, 0x57, 0xf3, 0x8c, 0xd9, 0x52, 0xc1,
|
||||||
0x43, 0xf0, 0x40, 0x8e, 0xbd, 0xb6, 0x1c, 0xf0, 0x5e, 0xc8, 0x89, 0x40, 0xfd, 0x6c, 0xf0, 0x41,
|
0x43, 0xf0, 0x40, 0x8e, 0xbd, 0xb6, 0x1c, 0xf0, 0x5e, 0xc8, 0x89, 0x40, 0xfd, 0x6c, 0xf0, 0x41,
|
||||||
0xc8, 0xf6, 0xb0, 0x2e, 0x8f, 0x91, 0xbd, 0xa0, 0x0a, 0x7c, 0x15, 0xf8, 0x78, 0x0f, 0xd9, 0xcf,
|
0xc8, 0xf6, 0xb0, 0x2e, 0x8f, 0x91, 0xbd, 0xa0, 0x0a, 0x7c, 0x15, 0xf8, 0x78, 0x0f, 0xd9, 0xcf,
|
||||||
0x69, 0xc1, 0x8c, 0x7b, 0x9c, 0xbb, 0xaf, 0x0d, 0x3c, 0x05, 0xf3, 0xb4, 0x3e, 0x4e, 0xe6, 0xa5,
|
0x69, 0xc1, 0x8c, 0x7b, 0x9c, 0xbb, 0xaf, 0x0d, 0x3c, 0x05, 0xf3, 0xb4, 0x3e, 0x4e, 0xe6, 0xa5,
|
||||||
0xad, 0x15, 0x1c, 0x24, 0xfb, 0xfb, 0xd6, 0xca, 0xf8, 0xbc, 0x78, 0x6c, 0x3a, 0x99, 0x17, 0xc7,
|
0xad, 0x15, 0x1c, 0x24, 0xfb, 0xfb, 0xd6, 0xca, 0xf8, 0xbc, 0x78, 0x6c, 0x3a, 0x99, 0x17, 0xc7,
|
||||||
0x25, 0xd6, 0xe9, 0xb5, 0xf0, 0xb9, 0x9c, 0x27, 0x7d, 0x5c, 0x38, 0x1d, 0xf7, 0xd3, 0x92, 0xe3,
|
0x25, 0xd6, 0xe9, 0xb5, 0xf0, 0xb9, 0x9c, 0x27, 0x7d, 0x5c, 0x38, 0x1d, 0xf7, 0xd3, 0x92, 0xe3,
|
||||||
0xe4, 0x3a, 0xf8, 0x37, 0x79, 0xe1, 0xa2, 0xb6, 0x7a, 0x1d, 0x59, 0x41, 0x6a, 0x73, 0x5f, 0xb6,
|
0xe4, 0x3a, 0xf8, 0x37, 0x79, 0xe1, 0xa2, 0xb6, 0x7a, 0x1d, 0x59, 0x41, 0x6a, 0x73, 0x5f, 0xb6,
|
||||||
0x85, 0x50, 0xdb, 0xe0, 0x06, 0xd9, 0xaf, 0xc9, 0xe9, 0xa4, 0xf7, 0x5b, 0xa8, 0xf5, 0x9f, 0x48,
|
0x85, 0x50, 0xdb, 0xe0, 0x06, 0xd9, 0xaf, 0xc9, 0xe9, 0xa4, 0xf7, 0x5b, 0xa8, 0xf5, 0x9f, 0x48,
|
||||||
0x8d, 0x40, 0x76, 0x0c, 0xfc, 0x08, 0xa3, 0x3e, 0xcf, 0xa5, 0x85, 0x36, 0xf5, 0xcf, 0x84, 0x51,
|
0x8d, 0x40, 0x76, 0x0c, 0xfc, 0x08, 0xa3, 0x3e, 0xcf, 0xa5, 0x85, 0x36, 0xf5, 0xcf, 0x84, 0x51,
|
||||||
0xdb, 0xe0, 0xb4, 0x4d, 0xfd, 0x72, 0x0f, 0xea, 0x97, 0xdb, 0xd4, 0xaf, 0xf0, 0xa0, 0x7e, 0xc5,
|
0xdb, 0xe0, 0xb4, 0x4d, 0xfd, 0x72, 0x0f, 0xea, 0x97, 0xdb, 0xd4, 0xaf, 0xf0, 0xa0, 0x7e, 0xc5,
|
||||||
0x16, 0xd6, 0x2f, 0xf3, 0xa0, 0x7e, 0xa0, 0x7d, 0x12, 0xcf, 0x57, 0x9d, 0x1e, 0xd4, 0xef, 0x94,
|
0x16, 0xd6, 0x2f, 0xf3, 0xa0, 0x7e, 0xa0, 0x7d, 0x12, 0xcf, 0x57, 0x9d, 0x1e, 0xd4, 0xef, 0x94,
|
||||||
0x5a, 0xaa, 0x61, 0x15, 0xa3, 0x5e, 0xf0, 0xd3, 0x83, 0xfa, 0x5c, 0xe3, 0x8e, 0xd4, 0x34, 0x8c,
|
0x5a, 0xaa, 0x61, 0x15, 0xa3, 0x5e, 0xf0, 0xd3, 0x83, 0xfa, 0x5c, 0xe3, 0x8e, 0xd4, 0x34, 0x8c,
|
||||||
0xe7, 0x8d, 0x26, 0xf0, 0x58, 0x18, 0x03, 0xcf, 0x48, 0x5f, 0xea, 0x38, 0x67, 0x4a, 0xf1, 0x19,
|
0xe7, 0x8d, 0x26, 0xf0, 0x58, 0x18, 0x03, 0xcf, 0x48, 0x5f, 0xea, 0x38, 0x67, 0x4a, 0xf1, 0x19,
|
||||||
0x4c, 0x89, 0x6f, 0x46, 0x62, 0xc7, 0x14, 0x5f, 0x13, 0x6d, 0x9c, 0x8b, 0x78, 0x5c, 0xef, 0x06,
|
0x4c, 0x89, 0x6f, 0x46, 0x62, 0xc7, 0x14, 0x5f, 0x13, 0x6d, 0x9c, 0x8b, 0x78, 0x5c, 0xef, 0x06,
|
||||||
0x29, 0xa4, 0x2f, 0xad, 0xbc, 0xc4, 0x8e, 0x80, 0x15, 0xd2, 0xe7, 0xe1, 0x64, 0xf1, 0xa5, 0xc8,
|
0x29, 0xa4, 0x2f, 0xad, 0xbc, 0xc4, 0x8e, 0x80, 0x15, 0xd2, 0xe7, 0xe1, 0x64, 0xf1, 0xa5, 0xc8,
|
||||||
0x71, 0xab, 0xf8, 0x46, 0x68, 0x7d, 0x39, 0x4e, 0x91, 0x1a, 0x4e, 0xf6, 0xed, 0x09, 0xa0, 0x5f,
|
0x71, 0xab, 0xf8, 0x46, 0x68, 0x7d, 0x39, 0x4e, 0x91, 0x1a, 0x4e, 0xf6, 0xed, 0x09, 0xa0, 0x5f,
|
||||||
0x6a, 0x34, 0x58, 0xf8, 0x1b, 0xc4, 0xd7, 0x2f, 0xb1, 0x6e, 0x8d, 0x1f, 0x43, 0x06, 0xa4, 0x46,
|
0x6a, 0x34, 0x58, 0xf8, 0x1b, 0xc4, 0xd7, 0x2f, 0xb1, 0x6e, 0x8d, 0x1f, 0x43, 0x06, 0xa4, 0x46,
|
||||||
0xa3, 0x85, 0xbf, 0x51, 0x7c, 0x03, 0x12, 0xeb, 0xd6, 0xf8, 0x9c, 0xfa, 0xa4, 0x46, 0xbd, 0x85,
|
0xa3, 0x85, 0xbf, 0x51, 0x7c, 0x03, 0x12, 0xeb, 0xd6, 0xf8, 0x9c, 0xfa, 0xa4, 0x46, 0xbd, 0x85,
|
||||||
0xbf, 0x5e, 0x7c, 0x7d, 0x14, 0xda, 0xf9, 0x73, 0xff, 0x5f, 0x01, 0x9f, 0xc0, 0x49, 0x0b, 0x3f,
|
0xbf, 0x5e, 0x7c, 0x7d, 0x14, 0xda, 0xf9, 0x73, 0xff, 0x5f, 0x01, 0x9f, 0xc0, 0x49, 0x0b, 0x3f,
|
||||||
0xb7, 0x4d, 0x49, 0x4c, 0x94, 0x85, 0x7f, 0xd5, 0xb4, 0xab, 0xda, 0x2a, 0x2b, 0xd4, 0x4c, 0x8b,
|
0xb7, 0x4d, 0x49, 0x4c, 0x94, 0x85, 0x7f, 0xd5, 0xb4, 0xab, 0xda, 0x2a, 0x2b, 0xd4, 0x4c, 0x8b,
|
||||||
0x78, 0x94, 0x99, 0xc3, 0xa9, 0x4c, 0x52, 0xdc, 0x2a, 0xa3, 0x48, 0x63, 0x5a, 0x70, 0x2b, 0x04,
|
0x78, 0x94, 0x99, 0xc3, 0xa9, 0x4c, 0x52, 0xdc, 0x2a, 0xa3, 0x48, 0x63, 0x5a, 0x70, 0x2b, 0x04,
|
||||||
0xc2, 0x6c, 0xdc, 0xb6, 0x98, 0x94, 0xb6, 0x06, 0x77, 0xbc, 0x9f, 0xf4, 0x89, 0x79, 0x6d, 0x1f,
|
0xc2, 0x6c, 0xdc, 0xb6, 0x98, 0x94, 0xb6, 0x06, 0x77, 0xbc, 0x9f, 0xf4, 0x89, 0x79, 0x6d, 0x1f,
|
||||||
0x96, 0xb8, 0x71, 0x1f, 0x76, 0xea, 0x72, 0x51, 0x48, 0x48, 0xf9, 0x77, 0xe0, 0x2d, 0x48, 0x0b,
|
0x96, 0xb8, 0x71, 0x1f, 0x76, 0xea, 0x72, 0x51, 0x48, 0x48, 0xf9, 0x77, 0xe0, 0x2d, 0x48, 0x0b,
|
||||||
0xb5, 0x4e, 0x18, 0xfa, 0xd5, 0xb4, 0x3e, 0x36, 0xab, 0xb7, 0x40, 0xbf, 0x56, 0xd1, 0xaf, 0xfd,
|
0xb5, 0x4e, 0x18, 0xfa, 0xd5, 0xb4, 0x3e, 0x36, 0xab, 0xb7, 0x40, 0xbf, 0x56, 0xd1, 0xaf, 0xfd,
|
||||||
0x0f, 0xf5, 0xeb, 0x14, 0xfd, 0xba, 0x4d, 0xd2, 0xe4, 0xdb, 0x9a, 0x9f, 0x35, 0x5f, 0x82, 0xdf,
|
0x0f, 0xf5, 0xeb, 0x14, 0xfd, 0xba, 0x4d, 0xd2, 0xe4, 0xdb, 0x9a, 0x9f, 0x35, 0x5f, 0x82, 0xdf,
|
||||||
0x8a, 0x3e, 0x1f, 0xf3, 0x9e, 0x92, 0x9f, 0x23, 0x33, 0x22, 0xa0, 0xcb, 0x73, 0xe7, 0x10, 0xe9,
|
0x8a, 0x3e, 0x1f, 0xf3, 0x9e, 0x92, 0x9f, 0x23, 0x33, 0x22, 0xa0, 0xcb, 0x73, 0xe7, 0x10, 0xe9,
|
||||||
0xf3, 0x46, 0xb0, 0x75, 0x82, 0x63, 0x06, 0xc1, 0x2e, 0x8f, 0xb4, 0xf3, 0x68, 0x7d, 0x3d, 0x71,
|
0xf3, 0x46, 0xb0, 0x75, 0x82, 0x63, 0x06, 0xc1, 0x2e, 0x8f, 0xb4, 0xf3, 0x68, 0x7d, 0x3d, 0x71,
|
||||||
0xc3, 0x77, 0x90, 0x1b, 0xa6, 0x76, 0x21, 0xe9, 0x7b, 0xb3, 0x50, 0xd7, 0x4c, 0xce, 0x2d, 0x08,
|
0xc3, 0x77, 0x90, 0x1b, 0xa6, 0x76, 0x21, 0xe9, 0x7b, 0xb3, 0x50, 0xd7, 0x4c, 0xce, 0x2d, 0x08,
|
||||||
0xe3, 0x37, 0x77, 0xb3, 0x97, 0x0f, 0xc4, 0x37, 0x90, 0x1c, 0x82, 0xfe, 0x90, 0x07, 0xda, 0x06,
|
0xe3, 0x37, 0x77, 0xb3, 0x97, 0x0f, 0xc4, 0x37, 0x90, 0x1c, 0x82, 0xfe, 0x90, 0x07, 0xda, 0x06,
|
||||||
0x83, 0x2e, 0xb5, 0xf7, 0x9b, 0xee, 0xef, 0x70, 0xe1, 0x5a, 0x07, 0x5c, 0xe8, 0xbb, 0x79, 0x77,
|
0x83, 0x2e, 0xb5, 0xf7, 0x9b, 0xee, 0xef, 0x70, 0xe1, 0x5a, 0x07, 0x5c, 0xe8, 0xbb, 0x79, 0x77,
|
||||||
0xe3, 0x94, 0x9b, 0x2e, 0xf4, 0x5f, 0x47, 0x40, 0xff, 0x95, 0x43, 0x6d, 0xde, 0x23, 0x39, 0x19,
|
0xe3, 0x94, 0x9b, 0x2e, 0xf4, 0x5f, 0x47, 0x40, 0xff, 0x95, 0x43, 0x6d, 0xde, 0x23, 0x39, 0x19,
|
||||||
0xe7, 0xa1, 0xf4, 0x41, 0xb4, 0x03, 0xfd, 0xd4, 0x08, 0x68, 0x1b, 0xa4, 0x38, 0xd0, 0xcf, 0x88,
|
0xe7, 0xa1, 0xf4, 0x41, 0xb4, 0x03, 0xfd, 0xd4, 0x08, 0x68, 0x1b, 0xa4, 0x38, 0xd0, 0xcf, 0x88,
|
||||||
0xa0, 0x7e, 0xd0, 0xb9, 0x79, 0x5b, 0x7f, 0x5b, 0x7f, 0xab, 0xf4, 0x61, 0x17, 0xc1, 0xbd, 0x08,
|
0xa0, 0x7e, 0xd0, 0xb9, 0x79, 0x5b, 0x7f, 0x5b, 0x7f, 0xab, 0xf4, 0x61, 0x17, 0xc1, 0xbd, 0x08,
|
||||||
0xea, 0x73, 0xed, 0x0b, 0x01, 0xb4, 0xe3, 0xc1, 0xaf, 0x08, 0x6a, 0x1b, 0xf0, 0xff, 0x05, 0xf1,
|
0xea, 0x73, 0xed, 0x0b, 0x01, 0xb4, 0xe3, 0xc1, 0xaf, 0x08, 0x6a, 0x1b, 0xf0, 0xff, 0x05, 0xf1,
|
||||||
0x16, 0xfa, 0xfc, 0x50, 0x31, 0xbe, 0x09, 0xfa, 0xfc, 0x0e, 0xcd, 0x67, 0xf3, 0x1b, 0x1c, 0x02,
|
0x16, 0xfa, 0xfc, 0x50, 0x31, 0xbe, 0x09, 0xfa, 0xfc, 0x0e, 0xcd, 0x67, 0xf3, 0x1b, 0x1c, 0x02,
|
||||||
0x87, 0x4d, 0xd4, 0x28, 0xf9, 0xc3, 0x16, 0x7e, 0x83, 0x61, 0x25, 0xae, 0xc6, 0xc2, 0x7f, 0xc8,
|
0x87, 0x4d, 0xd4, 0x28, 0xf9, 0xc3, 0x16, 0x7e, 0x83, 0x61, 0x25, 0xae, 0xc6, 0xc2, 0x7f, 0xc8,
|
||||||
0xea, 0xda, 0x1d, 0xdc, 0x93, 0xc5, 0x4a, 0xdd, 0x1e, 0x9b, 0xb8, 0x1e, 0x25, 0xae, 0xd8, 0xad,
|
0xea, 0xda, 0x1d, 0xdc, 0x93, 0xc5, 0x4a, 0xdd, 0x1e, 0x9b, 0xb8, 0x1e, 0x25, 0xae, 0xd8, 0xad,
|
||||||
0xce, 0xb6, 0xfe, 0x3f, 0xab, 0x7f, 0x42, 0xa9, 0xdb, 0x6d, 0x13, 0xa7, 0xfe, 0x67, 0x75, 0xdc,
|
0xce, 0xb6, 0xfe, 0x3f, 0xab, 0x7f, 0x42, 0xa9, 0xdb, 0x6d, 0x13, 0xa7, 0xfe, 0x67, 0x75, 0xdc,
|
||||||
0x43, 0xfd, 0x1d, 0xa4, 0xbf, 0x27, 0xe2, 0x77, 0xfc, 0x47, 0x6d, 0xe2, 0x72, 0x49, 0xdf, 0xef,
|
0x43, 0xfd, 0x1d, 0xa4, 0xbf, 0x27, 0xe2, 0x77, 0xfc, 0x47, 0x6d, 0xe2, 0x72, 0x49, 0xdf, 0xef,
|
||||||
0x3e, 0xe2, 0x1c, 0x27, 0xb5, 0xb5, 0xe5, 0x42, 0x6d, 0x03, 0x93, 0x71, 0x9a, 0xd6, 0xe2, 0xd3,
|
0x3e, 0xe2, 0x1c, 0x27, 0xb5, 0xb5, 0xe5, 0x42, 0x6d, 0x03, 0x93, 0x71, 0x9a, 0xd6, 0xe2, 0xd3,
|
||||||
0x70, 0x11, 0x1a, 0x36, 0x42, 0xcd, 0xcb, 0x78, 0x0e, 0x60, 0x16, 0xf1, 0x9c, 0x6f, 0x30, 0x47,
|
0x70, 0x11, 0x1a, 0x36, 0x42, 0xcd, 0xcb, 0x78, 0x0e, 0x60, 0x16, 0xf1, 0x9c, 0x6f, 0x30, 0x47,
|
||||||
0x94, 0x60, 0xc7, 0x24, 0x51, 0xdc, 0x28, 0x51, 0x14, 0xc3, 0xc7, 0x5a, 0xeb, 0x47, 0xcd, 0x8a,
|
0x94, 0x60, 0xc7, 0x24, 0x51, 0xdc, 0x28, 0x51, 0x14, 0xc3, 0xc7, 0x5a, 0xeb, 0x47, 0xcd, 0x8a,
|
||||||
0x3f, 0x05, 0x2f, 0x43, 0xb9, 0xce, 0x1e, 0x00, 0x00,
|
0x3f, 0x05, 0x2f, 0x43, 0xb9, 0xce, 0x1e, 0x00, 0x00,
|
||||||
};
|
};
|
||||||
const StaticFile favicon_ico PROGMEM = {(sizeof(favicon_ico_content)/sizeof(favicon_ico_content[0])), favicon_ico_content};
|
const StaticFile favicon_ico PROGMEM = {(sizeof(favicon_ico_content)/sizeof(favicon_ico_content[0])), favicon_ico_content};
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ typedef struct {
|
|||||||
|
|
||||||
extern const StaticFile index_html;
|
extern const StaticFile index_html;
|
||||||
extern const StaticFile app_js;
|
extern const StaticFile app_js;
|
||||||
|
extern const StaticFile md5_js;
|
||||||
extern const StaticFile style_css;
|
extern const StaticFile style_css;
|
||||||
extern const StaticFile favicon_ico;
|
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 "config.def.h"
|
||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@ -8,22 +9,20 @@ namespace homekit::wifi {
|
|||||||
using namespace homekit;
|
using namespace homekit;
|
||||||
using homekit::config::ConfigData;
|
using homekit::config::ConfigData;
|
||||||
|
|
||||||
const char NODE_ID[] = DEFAULT_NODE_ID;
|
const char HOME_ID[] = DEFAULT_HOME_ID;
|
||||||
const char WIFI_AP_SSID[] = DEFAULT_WIFI_AP_SSID;
|
const char AP_SSID[] = DEFAULT_WIFI_AP_SSID;
|
||||||
const char WIFI_STA_SSID[] = DEFAULT_WIFI_STA_SSID;
|
const char STA_SSID[] = DEFAULT_WIFI_STA_SSID;
|
||||||
const char WIFI_STA_PSK[] = DEFAULT_WIFI_STA_PSK;
|
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) {
|
if (cfg.flags.wifi_configured) {
|
||||||
*ssid_dst = cfg.wifi_ssid;
|
*ssid = cfg.wifi_ssid;
|
||||||
*psk_dst = cfg.wifi_psk;
|
*psk = cfg.wifi_psk;
|
||||||
if (hostname_dst != nullptr)
|
*hostname = cfg.home_id;
|
||||||
*hostname_dst = cfg.node_id;
|
|
||||||
} else {
|
} else {
|
||||||
*ssid_dst = (char*)WIFI_STA_SSID;
|
*ssid = STA_SSID;
|
||||||
*psk_dst = (char*)WIFI_STA_PSK;
|
*psk = STA_PSK;
|
||||||
if (hostname_dst != nullptr)
|
*hostname = HOME_ID;
|
||||||
*hostname_dst = (char*)NODE_ID;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,9 @@ struct ScanResult {
|
|||||||
String ssid;
|
String ssid;
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
||||||
std::shared_ptr<std::list<ScanResult>> scan();
|
|
||||||
|
|
||||||
inline int8_t getRSSI() {
|
std::shared_ptr<std::list<ScanResult>> scan();
|
||||||
return WiFi.RSSI();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint32_t getIPAsInteger() {
|
inline uint32_t getIPAsInteger() {
|
||||||
if (!WiFi.isConnected())
|
if (!WiFi.isConnected())
|
||||||
@ -27,9 +24,13 @@ inline uint32_t getIPAsInteger() {
|
|||||||
return WiFi.localIP().v4();
|
return WiFi.localIP().v4();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const char WIFI_AP_SSID[];
|
inline int8_t getRSSI() {
|
||||||
extern const char WIFI_STA_SSID[];
|
return WiFi.RSSI();
|
||||||
extern const char WIFI_STA_PSK[];
|
}
|
||||||
extern const char NODE_ID[];
|
|
||||||
|
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)
|
return document.getElementById(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hide(el) {
|
||||||
|
el.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
function cancelEvent(evt) {
|
function cancelEvent(evt) {
|
||||||
if (evt.preventDefault) evt.preventDefault();
|
if (evt.preventDefault) evt.preventDefault();
|
||||||
if (evt.stopPropagation) evt.stopPropagation();
|
if (evt.stopPropagation) evt.stopPropagation();
|
||||||
@ -100,14 +104,14 @@ function initNetworkSettings() {
|
|||||||
function onRequestDone() {
|
function onRequestDone() {
|
||||||
doneRequestsCount++;
|
doneRequestsCount++;
|
||||||
if (doneRequestsCount === 2) {
|
if (doneRequestsCount === 2) {
|
||||||
ge('loading_label').style.display = 'none';
|
hide(ge('loading_label'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var form = document.forms.network_settings;
|
var form = document.forms.network_settings;
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
if (!form.nid.value.trim()) {
|
if (!form.hid.value.trim()) {
|
||||||
alert('Введите node id');
|
alert('Введите home id');
|
||||||
return cancelEvent(e);
|
return cancelEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +120,7 @@ function initNetworkSettings() {
|
|||||||
return cancelEvent(e);
|
return cancelEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (form.ssid.selectedIndex == -1) {
|
if (form.ssid.selectedIndex === -1) {
|
||||||
alert('Не выбрана точка доступа');
|
alert('Не выбрана точка доступа');
|
||||||
return cancelEvent(e);
|
return cancelEvent(e);
|
||||||
}
|
}
|
||||||
@ -140,7 +144,7 @@ function initNetworkSettings() {
|
|||||||
if (error)
|
if (error)
|
||||||
throw error;
|
throw error;
|
||||||
|
|
||||||
setupField(form.nid, response.node_id || null);
|
setupField(form.hid, response.home_id || null);
|
||||||
setupField(form.psk, null);
|
setupField(form.psk, null);
|
||||||
setupField(form.submit, null);
|
setupField(form.submit, null);
|
||||||
|
|
||||||
@ -196,17 +200,23 @@ function initUpdateForm() {
|
|||||||
form.submit.innerHTML = progress + '%';
|
form.submit.innerHTML = progress + '%';
|
||||||
});
|
});
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
|
var errorMessage = 'Ошибка обновления';
|
||||||
|
var successMessage = 'Обновление завершено, устройство перезагружается';
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
var response = JSON.parse(xhr.responseText);
|
try {
|
||||||
if (response.result === 1) {
|
var response = JSON.parse(xhr.responseText);
|
||||||
alert('Обновление завершено, устройство перезагружается');
|
if (response.result === 1) {
|
||||||
} else {
|
alert(successMessage);
|
||||||
alert('Ошибка обновления');
|
} else {
|
||||||
|
alert(errorMessage);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert(successMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.onerror = function(e) {
|
xhr.onerror = function(e) {
|
||||||
alert(errorText(e))
|
alert(errorText(e));
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open('POST', e.target.action);
|
xhr.open('POST', e.target.action);
|
||||||
@ -214,9 +224,23 @@ function initUpdateForm() {
|
|||||||
|
|
||||||
return false;
|
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();
|
initNetworkSettings();
|
||||||
initUpdateForm();
|
initUpdateForm();
|
||||||
}
|
}
|
@ -6,6 +6,7 @@
|
|||||||
<title>Configuration</title>
|
<title>Configuration</title>
|
||||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||||
|
<script src="/md5.js"></script>
|
||||||
<script src="/app.js"></script>
|
<script src="/app.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body onload="initApp()">
|
<body onload="initApp()">
|
||||||
@ -22,14 +23,14 @@
|
|||||||
<div class="form_label">WiFi Password</div>
|
<div class="form_label">WiFi Password</div>
|
||||||
<div class="form_input">
|
<div class="form_input">
|
||||||
<input type="password" value="" name="psk" class="full-width" id="fld_psk" maxlength="63" disabled>
|
<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>
|
<label for="show_psk"><input type="checkbox" name="show_psk" id="show_psk"> show password</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form_label">Node ID</div>
|
<div class="form_label">Home ID</div>
|
||||||
<div class="form_input">
|
<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>
|
</div>
|
||||||
|
|
||||||
<button type="submit" disabled="disabled" name="submit">Save and Reboot</button>
|
<button type="submit" disabled="disabled" name="submit">Save and Reboot</button>
|
||||||
@ -42,7 +43,7 @@
|
|||||||
<div class="form_input">
|
<div class="form_input">
|
||||||
<input type="file" accept=".bin,.bin.gz" name="file">
|
<input type="file" accept=".bin,.bin.gz" name="file">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" name="submit">Upload</button>
|
<button type="submit" name="submit" disabled="disabled">Upload</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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 {
|
.form_input {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
.form_checkbox {
|
.form_sublabel {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ class BotType(Enum):
|
|||||||
ADMIN = auto()
|
ADMIN = auto()
|
||||||
SOUND = auto()
|
SOUND = auto()
|
||||||
POLARIS_KETTLE = auto()
|
POLARIS_KETTLE = auto()
|
||||||
|
PUMP_MQTT = auto()
|
||||||
|
|
||||||
|
|
||||||
class TemperatureSensorLocation(Enum):
|
class TemperatureSensorLocation(Enum):
|
||||||
|
@ -183,9 +183,9 @@ class WebAPIClient:
|
|||||||
data = json.loads(r.text)
|
data = json.loads(r.text)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise ApiResponseError(r.status_code,
|
raise ApiResponseError(r.status_code,
|
||||||
data['error']['type'],
|
data['error'],
|
||||||
data['error']['message'],
|
data['message'],
|
||||||
data['error']['stacktrace'] if 'stacktrace' in data['error'] else None)
|
data['stacktrace'] if 'stacktrace' in data['error'] else None)
|
||||||
|
|
||||||
return data['response'] if 'response' in data else True
|
return data['response'] if 'response' in data else True
|
||||||
finally:
|
finally:
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
from .mqtt import MQTTBase
|
from .mqtt import MQTTBase
|
||||||
from .util import poll_tick
|
from .util import poll_tick
|
||||||
|
from .relay import MQTTRelay
|
@ -6,8 +6,6 @@ import logging
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from ..config import config
|
from ..config import config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def username_and_password() -> Tuple[str, str]:
|
def username_and_password() -> Tuple[str, str]:
|
||||||
username = config['mqtt']['username'] if 'username' in config['mqtt'] else None
|
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_connect = self.on_connect
|
||||||
self._client.on_disconnect = self.on_disconnect
|
self._client.on_disconnect = self.on_disconnect
|
||||||
self._client.on_message = self.on_message
|
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__)
|
self._logger = logging.getLogger(self.__class__.__name__)
|
||||||
|
|
||||||
username, password = username_and_password()
|
username, password = username_and_password()
|
||||||
if username and password:
|
if username and password:
|
||||||
|
self._logger.debug(f'username={username} password={password}')
|
||||||
self._client.username_pw_set(username, password)
|
self._client.username_pw_set(username, password)
|
||||||
|
|
||||||
def configure_tls(self):
|
def configure_tls(self):
|
||||||
@ -50,6 +52,12 @@ class MQTTBase:
|
|||||||
self._client.loop_forever()
|
self._client.loop_forever()
|
||||||
else:
|
else:
|
||||||
self._client.loop_start()
|
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):
|
def on_connect(self, client: mqtt.Client, userdata, flags, rc):
|
||||||
self._logger.info("Connected with result code " + str(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):
|
def on_disconnect(self, client: mqtt.Client, userdata, rc):
|
||||||
self._logger.info("Disconnected with result code " + str(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):
|
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
|
from .base_payload import MQTTPayload, MQTTPayloadCustomField
|
||||||
|
|
||||||
|
|
||||||
|
# _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class StatFlags(MQTTPayloadCustomField):
|
class StatFlags(MQTTPayloadCustomField):
|
||||||
state: bool
|
state: bool
|
||||||
config_changed_value_present: bool
|
config_changed_value_present: bool
|
||||||
@ -8,9 +12,11 @@ class StatFlags(MQTTPayloadCustomField):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unpack(flags: int):
|
def unpack(flags: int):
|
||||||
|
# _logger.debug(f'StatFlags.unpack: flags={flags}')
|
||||||
state = flags & 0x1
|
state = flags & 0x1
|
||||||
ccvp = (flags >> 1) & 0x1
|
ccvp = (flags >> 1) & 0x1
|
||||||
cc = (flags >> 2) & 0x1
|
cc = (flags >> 2) & 0x1
|
||||||
|
# _logger.debug(f'StatFlags.unpack: state={state}')
|
||||||
return StatFlags(state=(state == 1),
|
return StatFlags(state=(state == 1),
|
||||||
config_changed_value_present=(ccvp == 1),
|
config_changed_value_present=(ccvp == 1),
|
||||||
config_changed=(cc == 1))
|
config_changed=(cc == 1))
|
||||||
@ -24,7 +30,7 @@ class StatFlags(MQTTPayloadCustomField):
|
|||||||
|
|
||||||
|
|
||||||
class InitialStatPayload(MQTTPayload):
|
class InitialStatPayload(MQTTPayload):
|
||||||
FORMAT = 'IBbIB'
|
FORMAT = '=IBbIB'
|
||||||
|
|
||||||
ip: int
|
ip: int
|
||||||
fw_version: int
|
fw_version: int
|
||||||
@ -34,7 +40,7 @@ class InitialStatPayload(MQTTPayload):
|
|||||||
|
|
||||||
|
|
||||||
class StatPayload(MQTTPayload):
|
class StatPayload(MQTTPayload):
|
||||||
FORMAT = 'bIB'
|
FORMAT = '=bIB'
|
||||||
|
|
||||||
rssi: int
|
rssi: int
|
||||||
free_heap: int
|
free_heap: int
|
||||||
@ -42,13 +48,42 @@ class StatPayload(MQTTPayload):
|
|||||||
|
|
||||||
|
|
||||||
class PowerPayload(MQTTPayload):
|
class PowerPayload(MQTTPayload):
|
||||||
FORMAT = '12sB'
|
FORMAT = '=12sB'
|
||||||
PACKER = {
|
PACKER = {
|
||||||
'state': lambda n: int(n)
|
'state': lambda n: int(n),
|
||||||
|
'secret': lambda s: s.encode('utf-8')
|
||||||
}
|
}
|
||||||
UNPACKER = {
|
UNPACKER = {
|
||||||
'state': lambda n: bool(n)
|
'state': lambda n: bool(n),
|
||||||
|
'secret': lambda s: s.decode('utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
secret: str
|
secret: str
|
||||||
state: bool
|
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)
|
ctx.reply_exc(e)
|
||||||
else:
|
else:
|
||||||
notify_user(ctx.user_id, exc2text(e))
|
notify_user(ctx.user_id, exc2text(e))
|
||||||
|
else:
|
||||||
|
_logger.exception(e)
|
||||||
|
|
||||||
|
|
||||||
def handler(**kwargs):
|
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
|
break
|
||||||
|
|
||||||
case 'css':
|
case 'css':
|
||||||
console.log(new CleanCSS({level:2}).minify(content).styles)
|
console.log(new CleanCSS({
|
||||||
|
level: 2
|
||||||
|
}).minify(content).styles)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'js':
|
case 'js':
|
||||||
console.log((await minifyJs(content, {
|
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)
|
})).code)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user