summaryrefslogtreecommitdiff
path: root/include/pio/libs/http_server
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-09-27 00:54:57 +0300
committerEvgeny Zinoviev <me@ch1p.io>2023-09-27 00:54:57 +0300
commitd3a295872c49defb55fc8e4e43e55550991e0927 (patch)
treeb9dca15454f9027d5a9dad0d4443a20de04dbc5d /include/pio/libs/http_server
parentb7cbc2571c1870b4582ead45277d0aa7f961bec8 (diff)
parentbdbb296697f55f4c3a07af43c9aaf7a9ea86f3d0 (diff)
Merge branch 'master' of ch1p.io:homekit
Diffstat (limited to 'include/pio/libs/http_server')
-rw-r--r--include/pio/libs/http_server/homekit/http_server.cpp282
-rw-r--r--include/pio/libs/http_server/homekit/http_server.h62
-rw-r--r--include/pio/libs/http_server/library.json8
3 files changed, 352 insertions, 0 deletions
diff --git a/include/pio/libs/http_server/homekit/http_server.cpp b/include/pio/libs/http_server/homekit/http_server.cpp
new file mode 100644
index 0000000..ea81f5b
--- /dev/null
+++ b/include/pio/libs/http_server/homekit/http_server.cpp
@@ -0,0 +1,282 @@
+#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 {}
+
+}
diff --git a/include/pio/libs/http_server/homekit/http_server.h b/include/pio/libs/http_server/homekit/http_server.h
new file mode 100644
index 0000000..8725a88
--- /dev/null
+++ b/include/pio/libs/http_server/homekit/http_server.h
@@ -0,0 +1,62 @@
+#ifndef COMMON_HOMEKIT_HTTP_SERVER_H
+#define COMMON_HOMEKIT_HTTP_SERVER_H
+
+#include <ESP8266WebServer.h>
+#include <Ticker.h>
+#include <memory>
+#include <list>
+#include <utility>
+
+#include <homekit/config.h>
+#include <homekit/wifi.h>
+#include <homekit/static.h>
+
+namespace homekit {
+
+struct OTAStatus {
+ bool invalidMd5;
+
+ OTAStatus() : invalidMd5(false) {}
+
+ inline void clean() {
+ invalidMd5 = false;
+ }
+};
+
+using files::StaticFile;
+
+class HttpServer {
+private:
+ ESP8266WebServer server;
+ Ticker restartTimer;
+ std::shared_ptr<std::list<wifi::ScanResult>> scanResults;
+ OTAStatus ota;
+
+ char* scanBuf;
+ size_t scanBufSize;
+
+ void sendGzip(const StaticFile& file, PGM_P content_type);
+ void sendError(const String& message);
+
+ bool getInputParam(const char* field_name, size_t max_len, String& dst);
+ virtual void ota_led() const;
+
+public:
+ explicit HttpServer(std::shared_ptr<std::list<wifi::ScanResult>> scanResults)
+ : server(80)
+ , scanResults(std::move(scanResults))
+ , scanBufSize(512) {
+ scanBuf = new char[scanBufSize];
+ };
+
+ ~HttpServer() {
+ delete[] scanBuf;
+ }
+
+ void start();
+ void loop();
+};
+
+}
+
+#endif //COMMON_HOMEKIT_HTTP_SERVER_H
diff --git a/include/pio/libs/http_server/library.json b/include/pio/libs/http_server/library.json
new file mode 100644
index 0000000..ee2d369
--- /dev/null
+++ b/include/pio/libs/http_server/library.json
@@ -0,0 +1,8 @@
+{
+ "name": "homekit_http_server",
+ "version": "1.0.3",
+ "build": {
+ "flags": "-I../../include"
+ }
+}
+