2023-06-10 23:20:37 +03:00

283 lines
8.6 KiB
C++

#include "http_server.h"
#include <Arduino.h>
#include <string.h>
#include <homekit/static.h>
#include <homekit/config.h>
#include <homekit/logging.h>
#include <homekit/macros.h>
#include <homekit/util.h>
namespace homekit {
using files::StaticFile;
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 = "{\"node_id\":\"%s\""
#ifdef DEBUG
",\"configured\":%d"
",\"crc\":%u"
",\"fl_n\":%d"
",\"fl_w\":%d"
#endif
"}";
static const size_t JSON_BUF_SIZE = 192;
static const char JSON_SCAN_FIRST_LIST[] PROGMEM = "{\"list\":[";
static const char MSG_IS_INVALID[] PROGMEM = " is invalid";
static const char MSG_IS_MISSING[] PROGMEM = " is missing";
static const char GZIP[] PROGMEM = "gzip";
static const char CONTENT_ENCODING[] PROGMEM = "Content-Encoding";
static const char NOT_FOUND[] PROGMEM = "Not Found";
static const char ROUTE_STYLE_CSS[] PROGMEM = "/style.css";
static const char ROUTE_APP_JS[] PROGMEM = "/app.js";
static const char ROUTE_MD5_JS[] PROGMEM = "/md5.js";
static const char ROUTE_FAVICON_ICO[] PROGMEM = "/favicon.ico";
static const char ROUTE_STATUS[] PROGMEM = "/status";
static const char ROUTE_SCAN[] PROGMEM = "/scan";
static const char ROUTE_RESET[] PROGMEM = "/reset";
// #ifdef DEBUG
static const char ROUTE_HEAP[] PROGMEM = "/heap";
// #endif
static const char ROUTE_UPDATE[] PROGMEM = "/update";
void HttpServer::start() {
server.on(FPSTR(ROUTE_STYLE_CSS), HTTP_GET, [&]() { sendGzip(files::style_css, CONTENT_TYPE_CSS); });
server.on(FPSTR(ROUTE_APP_JS), HTTP_GET, [&]() { sendGzip(files::app_js, CONTENT_TYPE_JS); });
server.on(FPSTR(ROUTE_MD5_JS), HTTP_GET, [&]() { sendGzip(files::md5_js, CONTENT_TYPE_JS); });
server.on(FPSTR(ROUTE_FAVICON_ICO), HTTP_GET, [&]() { sendGzip(files::favicon_ico, CONTENT_TYPE_FAVICON); });
server.on("/", HTTP_GET, [&]() { sendGzip(files::index_html, CONTENT_TYPE_HTML); });
server.on(FPSTR(ROUTE_STATUS), HTTP_GET, [&]() {
char json_buf[JSON_BUF_SIZE];
auto cfg = config::read();
if (!isValid(cfg) || !cfg.flags.node_configured) {
sprintf_P(json_buf, JSON_STATUS_FMT
, CONFIG_NODE_ID
#ifdef DEBUG
, 0
, cfg.crc
, cfg.flags.node_configured
, cfg.flags.wifi_configured
#endif
);
} else {
char escaped_node_id[32];
char *escaped_node_id_res = cfg.escapeHomeId(escaped_node_id, 32);
sprintf_P(json_buf, JSON_STATUS_FMT
, escaped_node_id_res == nullptr ? "?" : escaped_node_id
#ifdef DEBUG
, 1
, cfg.crc
, cfg.flags.node_configured
, cfg.flags.wifi_configured
#endif
);
}
server.send(200, FPSTR(CONTENT_TYPE_JSON), json_buf);
});
server.on(FPSTR(ROUTE_STATUS), HTTP_POST, [&]() {
auto cfg = config::read();
String s;
if (!getInputParam("ssid", 32, s)) return;
strncpy(cfg.wifi_ssid, s.c_str(), 32);
PRINTF("saving ssid: %s\n", cfg.wifi_ssid);
if (!getInputParam("psk", 63, s)) return;
strncpy(cfg.wifi_psk, s.c_str(), 63);
PRINTF("saving psk: %s\n", cfg.wifi_psk);
if (!getInputParam("hid", 16, s)) return;
strcpy(cfg.node_id, s.c_str());
PRINTF("saving home id: %s\n", cfg.node_id);
cfg.flags.node_configured = 1;
cfg.flags.wifi_configured = 1;
config::write(cfg);
restartTimer.once(0, restart);
});
server.on(FPSTR(ROUTE_RESET), HTTP_POST, [&]() {
config::erase();
restartTimer.once(1, restart);
});
server.on(FPSTR(ROUTE_HEAP), HTTP_GET, [&]() {
server.send(200, FPSTR(CONTENT_TYPE_HTML), String(ESP.getFreeHeap()));
});
server.on(FPSTR(ROUTE_SCAN), HTTP_GET, [&]() {
size_t i = 0;
size_t len;
const char* ssid;
bool enough = false;
bzero(reinterpret_cast<uint8_t*>(scanBuf), scanBufSize);
char* cur = scanBuf;
strncpy_P(cur, JSON_SCAN_FIRST_LIST, scanBufSize);
cur += 9;
for (auto& res: *scanResults) {
ssid = res.ssid.c_str();
len = res.ssid.length();
// new item (array with 2 items)
*cur++ = '[';
// 1. ssid (string)
*cur++ = '"';
for (size_t j = 0; j < len; j++) {
if (*(ssid+j) == '"')
*cur++ = '\\';
*cur++ = *(ssid+j);
}
*cur++ = '"';
*cur++ = ',';
// 2. rssi (number)
cur += sprintf(cur, "%d", res.rssi);
// close array
*cur++ = ']';
if ((size_t)(cur - scanBuf) >= (size_t) ARRAY_SIZE(scanBuf) - 40)
enough = true;
if (i < scanResults->size() - 1 || enough)
*cur++ = ',';
if (enough)
break;
i++;
}
*cur++ = ']';
*cur++ = '}';
*cur++ = '\0';
server.send(200, FPSTR(CONTENT_TYPE_JSON), scanBuf);
});
server.on(FPSTR(ROUTE_UPDATE), HTTP_POST, [&]() {
char json_buf[16];
bool should_reboot = !Update.hasError() && !ota.invalidMd5;
Update.clearError();
sprintf_P(json_buf, JSON_UPDATE_FMT, should_reboot ? 1 : 0);
server.send(200, FPSTR(CONTENT_TYPE_JSON), json_buf);
if (should_reboot)
restartTimer.once(1, restart);
}, [&]() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
ota.clean();
String s;
if (!getInputParam("md5", 0, s)) {
ota.invalidMd5 = true;
PRINTLN("http/ota: md5 not found");
return;
}
if (!Update.setMD5(s.c_str())) {
ota.invalidMd5 = true;
PRINTLN("http/ota: setMD5() failed");
return;
}
Serial.printf("http/ota: starting, filename=%s\n", upload.filename.c_str());
if (!Update.begin(otaGetMaxUpdateSize())) {
#ifdef DEBUG
Update.printError(Serial);
#endif
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (!Update.isRunning())
return;
PRINTF("http/ota: writing %ul\n", upload.currentSize);
ota_led();
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
#ifdef DEBUG
Update.printError(Serial);
#endif
}
} else if (upload.status == UPLOAD_FILE_END) {
if (!Update.isRunning())
return;
if (Update.end(true)) {
PRINTF("http/ota: ok, total size %ul\n", upload.totalSize);
} else {
#ifdef DEBUG
Update.printError(Serial);
#endif
}
}
});
server.onNotFound([&]() {
server.send(404, FPSTR(CONTENT_TYPE_HTML), NOT_FOUND);
});
server.begin();
}
void HttpServer::loop() {
server.handleClient();
}
void HttpServer::sendGzip(const StaticFile& file, PGM_P content_type) {
server.sendHeader(FPSTR(CONTENT_ENCODING), FPSTR(GZIP));
server.send_P(200, content_type, (const char*)file.content, file.size);
}
void HttpServer::sendError(const String& message) {
char buf[32];
if (snprintf_P(buf, 32, PSTR("error: %s"), message.c_str()) == 32)
buf[31] = '\0';
server.send(400, FPSTR(CONTENT_TYPE_HTML), buf);
}
bool HttpServer::getInputParam(const char *field_name,
size_t max_len,
String& dst) {
if (!server.hasArg(field_name)) {
sendError(String(field_name) + String(MSG_IS_MISSING));
return false;
}
String field = server.arg(field_name);
if (!field.length() || (max_len != 0 && field.length() > max_len)) {
sendError(String(field_name) + String(MSG_IS_INVALID));
return false;
}
dst = field;
return true;
}
void HttpServer::ota_led() const {}
}