SensESP 2.7.2
Universal Signal K sensor toolkit ESP32
Loading...
Searching...
No Matches
http_server.cpp
Go to the documentation of this file.
1#include "http_server.h"
2
3#include <ESPAsyncWebServer.h>
4#include <ESPmDNS.h>
5#include <FS.h>
6
7#include <functional>
8
9#include "ArduinoJson.h"
10#include "AsyncJson.h"
14#include "sensesp_base_app.h"
15
16// Include the web UI stored in PROGMEM space
17#include "web/css_bootstrap.h"
18#include "web/index.h"
19#include "web/js_jsoneditor.h"
20#include "web/js_sensesp.h"
21
22namespace sensesp {
23
24// HTTP port for the configuration interface
25#ifndef HTTP_SERVER_PORT
26#define HTTP_SERVER_PORT 80
27#endif
28
30 server = new AsyncWebServer(HTTP_SERVER_PORT);
31 using std::placeholders::_1;
32
33 server->onNotFound(std::bind(&HTTPServer::handle_not_found, this, _1));
34
35 // Handle setting configuration values of a Configurable via a Json PUT to
36 // /config
39 "/config",
41 // omit the "/config" part of the url
42 String url_tail = request->url().substring(7);
43
44 if (url_tail == "") {
45 request->send(405, "text/plain",
46 F("PUT to /config not allowed.\n"));
47 return;
48 }
49
50 std::map<String, Configurable*>::iterator it =
52 if (it == configurables.end()) {
53 request->send(404, "text/plain",
54 F("Configuration key not found.\n"));
55 return;
56 }
57 Configurable* confable = it->second;
58
60
62 request->send(400, "text/plain",
63 F("Unable to extract keys from JSON.\n"));
64 return;
65 }
67 request->send(200, "text/plain", F("Configuration successful.\n"));
68 return;
69 },
70 4096);
71 config_put_handler->setMethod(HTTP_PUT);
72 server->addHandler(config_put_handler);
73
74 // Handle requests to retrieve the current Json configuration of a
75 // Configurable via HTTP GET on /config
76 server->on("/config", HTTP_GET, [this](AsyncWebServerRequest* request) {
77 // omit the "/config" part of the url
78 String url_tail = request->url().substring(7);
79
80 if (url_tail == "" || url_tail == "/") {
81 this->handle_config_list(request);
82 return;
83 }
84
85 std::map<String, Configurable*>::iterator it = configurables.find(url_tail);
86 if (it == configurables.end()) {
87 request->send(404, "text/plain", F("Configuration key not found.\n"));
88 return;
89 }
90 Configurable* confable = it->second;
91
93 request->beginResponseStream("application/json");
95 JsonObject config = json_doc.createNestedObject("config");
98 json_doc["description"] = confable->get_description();
100 request->send(response);
101 });
102
103 server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
104 debugD("Serving gziped index.html");
105 AsyncWebServerResponse* response = request->beginResponse_P(
106 200, "text/html", PAGE_index, PAGE_index_size, NULL);
107 response->addHeader("Content-Encoding", "gzip");
108 request->send(response);
109 });
110
111 server->on(
112 "/css/bootstrap.min.css", HTTP_GET, [](AsyncWebServerRequest* request) {
113 debugD("Serving gziped bootstrap.min.css");
114 AsyncWebServerResponse* response = request->beginResponse_P(
116 response->addHeader("Content-Encoding", "gzip");
117 request->send(response);
118 });
119
120 server->on(
121 "/js/jsoneditor.min.js", HTTP_GET, [](AsyncWebServerRequest* request) {
122 debugD("Serving gziped jsoneditor.min.js");
123 AsyncWebServerResponse* response = request->beginResponse_P(
125 response->addHeader("Content-Encoding", "gzip");
126 request->send(response);
127 });
128
129 server->on("/js/sensesp.js", HTTP_GET, [](AsyncWebServerRequest* request) {
130 debugD("Serving gziped sensesp.js");
131 AsyncWebServerResponse* response = request->beginResponse_P(
132 200, "text/javascript", PAGE_js_sensesp, PAGE_js_jsoneditor_size);
133 response->addHeader("Content-Encoding", "gzip");
134 request->send(response);
135 });
136
137 server->on("/device/reset", HTTP_GET,
138 std::bind(&HTTPServer::handle_device_reset, this, _1));
139 server->on("/device/restart", HTTP_GET,
140 std::bind(&HTTPServer::handle_device_restart, this, _1));
141 server->on("/info", HTTP_GET, std::bind(&HTTPServer::handle_info, this, _1));
142
143 server->on("/command", HTTP_GET,
144 std::bind(&HTTPServer::handle_command, this, _1));
145}
146
148 // only start the server if WiFi is connected
149 if (WiFi.status() == WL_CONNECTED || WiFi.getMode() == WIFI_MODE_AP) {
150 server->begin();
151 debugI("HTTP server started");
152 } else {
153 debugW("HTTP server not started, WiFi not connected");
154 }
155 WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) {
158 server->begin();
159 debugI("HTTP server started");
160 }
161 });
162
163 // announce the server over mDNS
164 MDNS.addService("http", "tcp", 80);
165}
166
168 debugD("NOT_FOUND: ");
169 if (request->method() == HTTP_GET) {
170 debugD("GET");
171 } else if (request->method() == HTTP_POST) {
172 debugD("POST");
173 } else if (request->method() == HTTP_DELETE) {
174 debugD("DELETE");
175 } else if (request->method() == HTTP_PUT) {
176 debugD("PUT");
177 } else if (request->method() == HTTP_PATCH) {
178 debugD("PATCH");
179 } else if (request->method() == HTTP_HEAD) {
180 debugD("HEAD");
181 } else if (request->method() == HTTP_OPTIONS) {
182 debugD("OPTIONS");
183 } else {
184 debugD("UNKNOWN");
185 }
186 debugD(" http://%s%s", request->host().c_str(), request->url().c_str());
187
188 if (request->contentLength()) {
189 debugD("_CONTENT_TYPE: %s", request->contentType().c_str());
190 debugD("_CONTENT_LENGTH: %u", request->contentLength());
191 }
192
193 int headers = request->headers();
194 for (int i = 0; i < headers; i++) {
195 AsyncWebHeader* h = request->getHeader(i);
196 debugD("_HEADER[%s]: %s", h->name().c_str(), h->value().c_str());
197 }
198
199 int params = request->params();
200 for (int i = 0; i < params; i++) {
201 AsyncWebParameter* p = request->getParam(i);
202 if (p->isFile()) {
203 debugD("_FILE[%s]: %s, size: %u", p->name().c_str(), p->value().c_str(),
204 p->size());
205 } else if (p->isPost()) {
206 debugD("_POST[%s]: %s", p->name().c_str(), p->value().c_str());
207 } else {
208 debugD("_GET[%s]: %s", p->name().c_str(), p->value().c_str());
209 }
210 }
211
212 request->send(404);
213}
214
215void HTTPServer::handle_config_list(AsyncWebServerRequest* request) {
216 // to save memory, guesstimate the required output buffer size based
217 // on the number of elements
218 auto output_buffer_size = 200 * configurables.size();
220 request->beginResponseStream("application/json");
222 JsonArray arr = json_doc.createNestedArray("keys");
223 for (auto it = configurables.begin(); it != configurables.end(); ++it) {
224 arr.add(it->first);
225 }
226 serializeJson(json_doc, *response);
227 request->send(response);
228}
229
231 debugD("%s", request->url().c_str());
232 request->send(200, "text/plain", "/config");
233}
234
236 request->send(
237 200, "text/plain",
238 "OK, resetting the device settings back to factory defaults.\n");
239 ReactESP::app->onDelay(500, [this]() { SensESPBaseApp::get()->reset(); });
240}
241
243 request->send(200, "text/plain", "OK, restarting\n");
244 ReactESP::app->onDelay(50, []() { ESP.restart(); });
245}
246
248 // sort the configurables by their sort_order
249 std::vector<std::pair<int, String>> pairs;
250 for (auto it = configurables.begin(); it != configurables.end(); ++it) {
251 pairs.push_back(std::make_pair(it->second->get_sort_order(), it->first));
252 }
253
254 std::sort(pairs.begin(), pairs.end());
255
256 // add all configuration paths
257 for (auto it = pairs.begin(); it != pairs.end(); ++it) {
258 config.add(it->second);
259 }
260}
261
263 auto* response = request->beginResponseStream("application/json");
264 response->setCode(200);
265
268
269 std::size_t output_buffer_size =
270 (200 * configurables.size() + // configuration cards
271 200 * ui_outputs->size() + // status output entities
272 200 * ui_buttons.size()) // custom UI controls
273 + 512;
275
276 auto properties = json_doc.createNestedObject("Properties");
277 auto commands = json_doc.createNestedArray("Commands");
278 auto pages = json_doc.createNestedArray("Pages");
279 auto config = json_doc.createNestedArray("Config");
280
281 for (auto property = ui_outputs->begin(); property != ui_outputs->end();
282 ++property) {
283 property->second->set_json(properties);
284 }
285
287
288 for (auto button_it = ui_buttons.begin(); button_it != ui_buttons.end();
289 ++button_it) {
290 auto command_obj = commands.createNestedObject();
291 command_obj["Name"] = button_it->second->get_name();
292 command_obj["Title"] = button_it->second->get_title();
293 command_obj["Confirm"] = button_it->second->get_must_confirm();
294 }
295
297 request->send(response);
298}
299
302
303 if (request->hasParam("id")) {
304 String id = request->getParam("id")->value();
305 auto button_it = ui_buttons.find(id);
306
307 if (button_it != ui_buttons.end()) {
308 debugI("Handle button_it %s id=%s", request->url().c_str(), id.c_str());
309 button_it->second->notify();
310 request->send(200, "text/html", "Success!");
311 } else {
312 request->send(404, "text/html", "Command not found!");
313 }
314 } else {
315 request->send(400, "text/html", "Missing id parameter.");
316 }
317}
318
319} // namespace sensesp
An object that is capable of having configuration data that can be set remotely using a RESTful API,...
virtual void save_configuration()
void handle_info(AsyncWebServerRequest *request)
void handle_device_reset(AsyncWebServerRequest *request)
void add_sorted_configurables(JsonArray &config)
void handle_command(AsyncWebServerRequest *request)
void handle_device_restart(AsyncWebServerRequest *request)
void handle_config(AsyncWebServerRequest *request)
void handle_not_found(AsyncWebServerRequest *request)
virtual void start() override
Construct a new transform based on a single function.
bool set_configuration(const JsonObject &config) override
void get_configuration(JsonObject &doc) override
String get_config_schema() override
static SensESPBaseApp * get()
Get the singleton instance of the SensESPBaseApp.
Automatic calling of the start() method at startup.
Definition startable.h:20
static const std::map< String, UIButton * > & get_ui_buttons()
Definition ui_button.h:26
static const std::map< String, UIOutputBase * > * get_ui_outputs()
Definition ui_output.h:35
const uint PAGE_css_bootstrap_size
#define HTTP_SERVER_PORT
const uint PAGE_index_size
Definition index.h:153
const uint PAGE_js_jsoneditor_size
#define debugI(fmt,...)
Definition local_debug.h:48
#define debugD(fmt,...)
Definition local_debug.h:47
#define debugW(fmt,...)
Definition local_debug.h:49
std::map< String, Configurable * > configurables
std::map< String, UIOutputBase * > ui_outputs