mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 17:29:50 +03:00
log to webui - initial version
This commit is contained in:
307
src/web/WebAPIService.cpp
Normal file
307
src/web/WebAPIService.cpp
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// SUrlParser from https://github.com/Mad-ness/simple-url-parser
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _securityManager(securityManager)
|
||||
, _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS
|
||||
server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS
|
||||
server->addHandler(&_apiHandler);
|
||||
}
|
||||
|
||||
// GET /{device}
|
||||
// GET /{device}/{name}
|
||||
// GET /device={device}?cmd={name}?data={value}[?id={hc}
|
||||
void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) {
|
||||
std::string device("");
|
||||
std::string cmd("");
|
||||
int id = -1;
|
||||
std::string value("");
|
||||
|
||||
parse(request, device, cmd, id, value); // pass it defaults
|
||||
}
|
||||
|
||||
// For POSTS with an optional JSON body
|
||||
// HTTP_POST | HTTP_PUT | HTTP_PATCH
|
||||
// POST/PUT /{device}[/{hc}][/{name}]
|
||||
void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
// extra the params from the json body
|
||||
if (not json.is<JsonObject>()) {
|
||||
webAPIService_get(request);
|
||||
return;
|
||||
}
|
||||
|
||||
// extract values from the json
|
||||
// these will be used as default values
|
||||
auto && body = json.as<JsonObject>();
|
||||
|
||||
std::string device = body["name"].as<std::string>(); // note this was called device in the v2
|
||||
std::string cmd = body["cmd"].as<std::string>();
|
||||
int id = -1;
|
||||
if (body.containsKey("id")) {
|
||||
id = body["id"];
|
||||
} else if (body.containsKey("hc")) {
|
||||
id = body["hc"];
|
||||
} else {
|
||||
id = -1;
|
||||
}
|
||||
|
||||
// make sure we have a value. There must always be a value
|
||||
if (!body.containsKey(F_(value))) {
|
||||
send_message_response(request, 400, "Problems parsing JSON"); // Bad Request
|
||||
return;
|
||||
}
|
||||
std::string value = body["value"].as<std::string>(); // always convert value to string
|
||||
|
||||
// now parse the URL. The URL is always leading and will overwrite anything provided in the json body
|
||||
parse(request, device, cmd, id, value); // pass it defaults
|
||||
}
|
||||
|
||||
// parse the URL looking for query or path parameters
|
||||
// reporting back any errors
|
||||
void WebAPIService::parse(AsyncWebServerRequest * request, std::string & device_s, std::string & cmd_s, int id, std::string & value_s) {
|
||||
// parse URL for the path names
|
||||
SUrlParser p;
|
||||
|
||||
p.parse(request->url().c_str());
|
||||
|
||||
// remove the /api from the path
|
||||
if (p.paths().front() == "api") {
|
||||
p.paths().erase(p.paths().begin());
|
||||
} else {
|
||||
return; // bad URL
|
||||
}
|
||||
|
||||
uint8_t device_type;
|
||||
int8_t id_n = -1; // default hc
|
||||
|
||||
// check for query parameters first
|
||||
// /device={device}?cmd={name}?data={value}[?id={hc}
|
||||
if (p.paths().size() == 0) {
|
||||
// get the device
|
||||
if (request->hasParam(F_(device))) {
|
||||
device_s = request->getParam(F_(device))->value().c_str();
|
||||
}
|
||||
|
||||
// get cmd
|
||||
if (request->hasParam(F_(cmd))) {
|
||||
cmd_s = request->getParam(F_(cmd))->value().c_str();
|
||||
}
|
||||
|
||||
// get data, which is optional. This is now replaced with the name 'value' in JSON body
|
||||
if (request->hasParam(F_(data))) {
|
||||
value_s = request->getParam(F_(data))->value().c_str();
|
||||
}
|
||||
if (request->hasParam(F_(value))) {
|
||||
value_s = request->getParam(F_(value))->value().c_str();
|
||||
}
|
||||
|
||||
// get id (or hc), which is optional
|
||||
if (request->hasParam(F_(id))) {
|
||||
id_n = Helpers::atoint(request->getParam(F_(id))->value().c_str());
|
||||
}
|
||||
if (request->hasParam("hc")) {
|
||||
id_n = Helpers::atoint(request->getParam("hc")->value().c_str());
|
||||
}
|
||||
} else {
|
||||
// parse paths and json data
|
||||
// /{device}[/{hc}][/{name}]
|
||||
// first param must be a valid device, which includes "system"
|
||||
device_s = p.paths().front();
|
||||
|
||||
auto num_paths = p.paths().size();
|
||||
if (num_paths == 1) {
|
||||
// if there are no more paths parameters, default to 'info'
|
||||
// cmd_s = "info_short";
|
||||
// check empty command in Command::find_command and set the default there!
|
||||
} else if (num_paths == 2) {
|
||||
cmd_s = p.paths()[1];
|
||||
} else if (num_paths > 2) {
|
||||
// check in Command::find_command makes prefix to TAG
|
||||
cmd_s = p.paths()[1] + "/" + p.paths()[2];
|
||||
}
|
||||
}
|
||||
// now go and validate everything
|
||||
|
||||
// device check
|
||||
if (device_s.empty()) {
|
||||
send_message_response(request, 422, "Missing device"); // Unprocessable Entity
|
||||
return;
|
||||
}
|
||||
device_type = EMSdevice::device_name_2_device_type(device_s.c_str());
|
||||
if (device_type == EMSdevice::DeviceType::UNKNOWN) {
|
||||
send_message_response(request, 422, "Invalid call"); // Unprocessable Entity
|
||||
return;
|
||||
}
|
||||
|
||||
// check that we have permissions first. We require authenticating on 1 or more of these conditions:
|
||||
// 1. any HTTP POSTs or PUTs
|
||||
// 2. a HTTP GET which has a 'data' parameter which is not empty (to keep v2 compatibility)
|
||||
auto method = request->method();
|
||||
bool have_data = !value_s.empty();
|
||||
bool admin_allowed;
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||
admin_allowed = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication);
|
||||
});
|
||||
|
||||
if ((method != HTTP_GET) || ((method == HTTP_GET) && have_data)) {
|
||||
if (!admin_allowed) {
|
||||
send_message_response(request, 401, "Bad credentials"); // Unauthorized
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// now we have all the parameters go and execute the command
|
||||
PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
|
||||
JsonObject json = response->getRoot();
|
||||
|
||||
bool ok = Command::call(device_type, cmd_s.c_str(), (have_data ? value_s.c_str() : nullptr), id_n, json);
|
||||
|
||||
// check for errors
|
||||
if (!ok) {
|
||||
send_message_response(request, 400, "Problems parsing elements"); // Bad Request
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json.size()) {
|
||||
send_message_response(request, 200, "OK"); // OK
|
||||
return;
|
||||
}
|
||||
|
||||
// send the json that came back from the command call
|
||||
response->setLength();
|
||||
request->send(response); // send json response
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
Serial.print(COLOR_YELLOW);
|
||||
if (json.size() != 0) {
|
||||
serializeJsonPretty(json, Serial);
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print(COLOR_RESET);
|
||||
#endif
|
||||
}
|
||||
|
||||
// send a HTTP error back, with optional JSON body data
|
||||
void WebAPIService::send_message_response(AsyncWebServerRequest * request, uint16_t error_code, const char * error_message) {
|
||||
if (error_message == nullptr) {
|
||||
AsyncWebServerResponse * response = request->beginResponse(error_code); // just send the code
|
||||
request->send(response);
|
||||
} else {
|
||||
// build a return message and send it
|
||||
PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
|
||||
JsonObject json = response->getRoot();
|
||||
json["message"] = error_message;
|
||||
response->setCode(error_code);
|
||||
response->setLength();
|
||||
response->setContentType("application/json");
|
||||
request->send(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract only the path component from the passed URI
|
||||
* and normalized it.
|
||||
* Ex. //one/two////three///
|
||||
* becomes
|
||||
* /one/two/three
|
||||
*/
|
||||
std::string SUrlParser::path() {
|
||||
std::string s = "/"; // set up the beginning slash
|
||||
for (std::string & f : m_folders) {
|
||||
s += f;
|
||||
s += "/";
|
||||
}
|
||||
s.pop_back(); // deleting last letter, that is slash '/'
|
||||
return std::string(s);
|
||||
}
|
||||
|
||||
SUrlParser::SUrlParser(const char * uri) {
|
||||
parse(uri);
|
||||
}
|
||||
|
||||
bool SUrlParser::parse(const char * uri) {
|
||||
m_folders.clear();
|
||||
m_keysvalues.clear();
|
||||
enum Type { begin, folder, param, value };
|
||||
std::string s;
|
||||
|
||||
const char * c = uri;
|
||||
enum Type t = Type::begin;
|
||||
std::string last_param;
|
||||
|
||||
if (c != NULL || *c != '\0') {
|
||||
do {
|
||||
if (*c == '/') {
|
||||
if (s.length() > 0) {
|
||||
m_folders.push_back(s);
|
||||
s.clear();
|
||||
}
|
||||
t = Type::folder;
|
||||
} else if (*c == '?' && (t == Type::folder || t == Type::begin)) {
|
||||
if (s.length() > 0) {
|
||||
m_folders.push_back(s);
|
||||
s.clear();
|
||||
}
|
||||
t = Type::param;
|
||||
} else if (*c == '=' && (t == Type::param || t == Type::begin)) {
|
||||
m_keysvalues[s] = "";
|
||||
last_param = s;
|
||||
s.clear();
|
||||
t = Type::value;
|
||||
} else if (*c == '&' && (t == Type::value || t == Type::param || t == Type::begin)) {
|
||||
if (t == Type::value) {
|
||||
m_keysvalues[last_param] = s;
|
||||
} else if ((t == Type::param || t == Type::begin) && (s.length() > 0)) {
|
||||
m_keysvalues[s] = "";
|
||||
last_param = s;
|
||||
}
|
||||
t = Type::param;
|
||||
s.clear();
|
||||
} else if (*c == '\0' && s.length() > 0) {
|
||||
if (t == Type::value) {
|
||||
m_keysvalues[last_param] = s;
|
||||
} else if (t == Type::folder || t == Type::begin) {
|
||||
m_folders.push_back(s);
|
||||
} else if (t == Type::param) {
|
||||
m_keysvalues[s] = "";
|
||||
last_param = s;
|
||||
}
|
||||
s.clear();
|
||||
} else if (*c == '\0' && s.length() == 0) {
|
||||
if (t == Type::param && last_param.length() > 0) {
|
||||
m_keysvalues[last_param] = "";
|
||||
}
|
||||
s.clear();
|
||||
} else {
|
||||
s += *c;
|
||||
}
|
||||
} while (*c++ != '\0');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
76
src/web/WebAPIService.h
Normal file
76
src/web/WebAPIService.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WebAPIService_h
|
||||
#define WebAPIService_h
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#define EMSESP_API_SERVICE_PATH "/api"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
typedef std::unordered_map<std::string, std::string> KeyValueMap_t;
|
||||
typedef std::vector<std::string> Folder_t;
|
||||
|
||||
class SUrlParser {
|
||||
private:
|
||||
KeyValueMap_t m_keysvalues;
|
||||
Folder_t m_folders;
|
||||
|
||||
public:
|
||||
SUrlParser(){};
|
||||
SUrlParser(const char * url);
|
||||
|
||||
bool parse(const char * url);
|
||||
|
||||
Folder_t & paths() {
|
||||
return m_folders;
|
||||
};
|
||||
|
||||
KeyValueMap_t & params() {
|
||||
return m_keysvalues;
|
||||
};
|
||||
|
||||
std::string path();
|
||||
};
|
||||
|
||||
class WebAPIService {
|
||||
public:
|
||||
WebAPIService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
void webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json); // for POSTs
|
||||
void webAPIService_get(AsyncWebServerRequest * request); // for GETs
|
||||
|
||||
private:
|
||||
SecurityManager * _securityManager;
|
||||
AsyncCallbackJsonWebHandler _apiHandler; // for POSTs
|
||||
|
||||
void parse(AsyncWebServerRequest * request, std::string & device, std::string & cmd, int id, std::string & value);
|
||||
void send_message_response(AsyncWebServerRequest * request, uint16_t error_code, const char * error_message = nullptr);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
149
src/web/WebDevicesService.cpp
Normal file
149
src/web/WebDevicesService.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
|
||||
securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED))
|
||||
, _writevalue_dataHandler(WRITE_VALUE_SERVICE_PATH,
|
||||
securityManager->wrapCallback(std::bind(&WebDevicesService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
|
||||
server->on(EMSESP_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
server->on(SCAN_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&WebDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
_device_dataHandler.setMethod(HTTP_POST);
|
||||
_device_dataHandler.setMaxContentLength(256);
|
||||
server->addHandler(&_device_dataHandler);
|
||||
|
||||
_writevalue_dataHandler.setMethod(HTTP_POST);
|
||||
_writevalue_dataHandler.setMaxContentLength(256);
|
||||
server->addHandler(&_writevalue_dataHandler);
|
||||
}
|
||||
|
||||
void WebDevicesService::scan_devices(AsyncWebServerRequest * request) {
|
||||
EMSESP::scan_devices();
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
void WebDevicesService::all_devices(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
JsonArray devices = root.createNestedArray("devices");
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if (emsdevice) {
|
||||
JsonObject obj = devices.createNestedObject();
|
||||
obj["id"] = emsdevice->unique_id();
|
||||
obj["type"] = emsdevice->device_type_name();
|
||||
obj["brand"] = emsdevice->brand_to_string();
|
||||
obj["name"] = emsdevice->name();
|
||||
obj["deviceid"] = emsdevice->device_id();
|
||||
obj["productid"] = emsdevice->product_id();
|
||||
obj["version"] = emsdevice->version();
|
||||
}
|
||||
}
|
||||
|
||||
JsonArray sensors = root.createNestedArray("sensors");
|
||||
if (EMSESP::have_sensors()) {
|
||||
uint8_t i = 1;
|
||||
char s[8];
|
||||
for (const auto & sensor : EMSESP::sensor_devices()) {
|
||||
JsonObject obj = sensors.createNestedObject();
|
||||
obj["no"] = i++;
|
||||
obj["id"] = sensor.to_string();
|
||||
obj["temp"] = Helpers::render_value(s, sensor.temperature_c, 10);
|
||||
}
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
// The unique_id is the unique record ID from the Web table to identify which device to load
|
||||
// Compresses the JSON using MsgPack https://msgpack.org/index.html
|
||||
void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->unique_id() == json["id"]) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
JsonObject root = response->getRoot();
|
||||
emsdevice->generate_values_json_web(root);
|
||||
#endif
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// invalid
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
// takes a command and its data value from a specific Device, from the Web
|
||||
void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
JsonObject dv = json["devicevalue"];
|
||||
uint8_t id = json["id"];
|
||||
|
||||
// using the unique ID from the web find the real device type
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->unique_id() == id) {
|
||||
const char * cmd = dv["c"];
|
||||
uint8_t device_type = emsdevice->device_type();
|
||||
bool ok = false;
|
||||
char s[10];
|
||||
// the data could be in any format, but we need string
|
||||
JsonVariant data = dv["v"];
|
||||
if (data.is<const char *>()) {
|
||||
ok = Command::call(device_type, cmd, data.as<const char *>());
|
||||
} else if (data.is<int>()) {
|
||||
ok = Command::call(device_type, cmd, Helpers::render_value(s, data.as<int16_t>(), 0));
|
||||
} else if (data.is<float>()) {
|
||||
ok = Command::call(device_type, cmd, Helpers::render_value(s, (float)data.as<float>(), 1));
|
||||
} else if (data.is<bool>()) {
|
||||
ok = Command::call(device_type, cmd, data.as<bool>() ? "true" : "false");
|
||||
}
|
||||
|
||||
// send "Write command sent to device" or "Write command failed"
|
||||
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204);
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(204); // Write command failed
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
52
src/web/WebDevicesService.h
Normal file
52
src/web/WebDevicesService.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WebDevicesService_h
|
||||
#define WebDevicesService_h
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
|
||||
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
|
||||
#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData"
|
||||
#define WRITE_VALUE_SERVICE_PATH "/rest/writeValue"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class WebDevicesService {
|
||||
public:
|
||||
WebDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
// GET
|
||||
void all_devices(AsyncWebServerRequest * request);
|
||||
void scan_devices(AsyncWebServerRequest * request);
|
||||
|
||||
// POST
|
||||
void device_data(AsyncWebServerRequest * request, JsonVariant & json);
|
||||
void write_value(AsyncWebServerRequest * request, JsonVariant & json);
|
||||
|
||||
AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
126
src/web/WebLogService.cpp
Normal file
126
src/web/WebLogService.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _events(EVENT_SOURCE_LOG_PATH) {
|
||||
_events.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
|
||||
server->addHandler(&_events);
|
||||
server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1));
|
||||
start();
|
||||
}
|
||||
|
||||
void WebLogService::forbidden(AsyncWebServerRequest * request) {
|
||||
request->send(403);
|
||||
}
|
||||
|
||||
void WebLogService::start() {
|
||||
uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // default is INFO
|
||||
}
|
||||
|
||||
uuid::log::Level WebLogService::log_level() const {
|
||||
return uuid::log::Logger::get_log_level(this);
|
||||
}
|
||||
|
||||
void WebLogService::remove_queued_messages(uuid::log::Level level) {
|
||||
unsigned long offset = 0;
|
||||
|
||||
for (auto it = log_messages_.begin(); it != log_messages_.end();) {
|
||||
if (it->content_->level > level) {
|
||||
offset++;
|
||||
it = log_messages_.erase(it);
|
||||
} else {
|
||||
it->id_ -= offset;
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
log_message_id_ -= offset;
|
||||
}
|
||||
|
||||
void WebLogService::log_level(uuid::log::Level level) {
|
||||
uuid::log::Logger::register_handler(this, level);
|
||||
}
|
||||
|
||||
size_t WebLogService::maximum_log_messages() const {
|
||||
return maximum_log_messages_;
|
||||
}
|
||||
|
||||
void WebLogService::maximum_log_messages(size_t count) {
|
||||
maximum_log_messages_ = std::max((size_t)1, count);
|
||||
while (log_messages_.size() > maximum_log_messages_) {
|
||||
log_messages_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
WebLogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
|
||||
: id_(id)
|
||||
, content_(std::move(content)) {
|
||||
}
|
||||
|
||||
void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
|
||||
if (log_messages_.size() >= maximum_log_messages_) {
|
||||
log_messages_.pop_front();
|
||||
}
|
||||
log_messages_.emplace_back(log_message_id_++, std::move(message));
|
||||
}
|
||||
|
||||
void WebLogService::loop() {
|
||||
if (!_events.count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!log_messages_.empty() && can_transmit()) {
|
||||
transmit(log_messages_.front());
|
||||
log_messages_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
bool WebLogService::can_transmit() {
|
||||
const uint64_t now = uuid::get_uptime_ms();
|
||||
if (now < last_transmit_ || now - last_transmit_ < 100) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// send to web eventsource
|
||||
void WebLogService::transmit(const QueuedLogMessage & message) {
|
||||
DynamicJsonDocument jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_SMALL);
|
||||
JsonObject logEvent = jsonDocument.to<JsonObject>();
|
||||
logEvent["time"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3);
|
||||
logEvent["level"] = message.content_->level;
|
||||
logEvent["message"] = message.content_->text;
|
||||
|
||||
size_t len = measureJson(jsonDocument);
|
||||
char * buffer = new char[len + 1];
|
||||
if (buffer) {
|
||||
serializeJson(jsonDocument, buffer, len + 1);
|
||||
_events.send(buffer, "message", millis());
|
||||
}
|
||||
delete[] buffer;
|
||||
|
||||
last_transmit_ = uuid::get_uptime_ms();
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
74
src/web/WebLogService.h
Normal file
74
src/web/WebLogService.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WebLogService_h
|
||||
#define WebLogService_h
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#define EVENT_SOURCE_LOG_PATH "/es/log"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class WebLogService : public uuid::log::Handler {
|
||||
public:
|
||||
static constexpr size_t MAX_LOG_MESSAGES = 50;
|
||||
|
||||
WebLogService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
void start();
|
||||
uuid::log::Level log_level() const;
|
||||
void log_level(uuid::log::Level level);
|
||||
size_t maximum_log_messages() const;
|
||||
void maximum_log_messages(size_t count);
|
||||
void loop();
|
||||
|
||||
virtual void operator<<(std::shared_ptr<uuid::log::Message> message);
|
||||
|
||||
private:
|
||||
AsyncEventSource _events;
|
||||
|
||||
class QueuedLogMessage {
|
||||
public:
|
||||
QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content);
|
||||
~QueuedLogMessage() = default;
|
||||
|
||||
unsigned long id_; // Sequential identifier for this log message
|
||||
struct timeval time_; // Time message was received
|
||||
const std::shared_ptr<const uuid::log::Message> content_; // Log message content
|
||||
};
|
||||
|
||||
void forbidden(AsyncWebServerRequest * request);
|
||||
void remove_queued_messages(uuid::log::Level level);
|
||||
bool can_transmit();
|
||||
void transmit(const QueuedLogMessage & message);
|
||||
|
||||
uint64_t last_transmit_ = 0; // Last transmit time
|
||||
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output
|
||||
unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages
|
||||
std::list<QueuedLogMessage> log_messages_; // Queued log messages, in the order they were received
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
251
src/web/WebSettingsService.cpp
Normal file
251
src/web/WebSettingsService.cpp
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uint8_t WebSettings::flags_;
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE)
|
||||
, _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH,
|
||||
securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
|
||||
_boardProfileHandler.setMethod(HTTP_POST);
|
||||
_boardProfileHandler.setMaxContentLength(256);
|
||||
server->addHandler(&_boardProfileHandler);
|
||||
|
||||
addUpdateHandler([&](const String & originId) { onUpdate(); }, false);
|
||||
}
|
||||
|
||||
void WebSettings::read(WebSettings & settings, JsonObject & root) {
|
||||
root["tx_mode"] = settings.tx_mode;
|
||||
root["tx_delay"] = settings.tx_delay;
|
||||
root["ems_bus_id"] = settings.ems_bus_id;
|
||||
root["syslog_enabled"] = settings.syslog_enabled;
|
||||
root["syslog_level"] = settings.syslog_level;
|
||||
root["trace_raw"] = settings.trace_raw;
|
||||
root["syslog_mark_interval"] = settings.syslog_mark_interval;
|
||||
root["syslog_host"] = settings.syslog_host;
|
||||
root["syslog_port"] = settings.syslog_port;
|
||||
root["master_thermostat"] = settings.master_thermostat;
|
||||
root["shower_timer"] = settings.shower_timer;
|
||||
root["shower_alert"] = settings.shower_alert;
|
||||
root["rx_gpio"] = settings.rx_gpio;
|
||||
root["tx_gpio"] = settings.tx_gpio;
|
||||
root["dallas_gpio"] = settings.dallas_gpio;
|
||||
root["dallas_parasite"] = settings.dallas_parasite;
|
||||
root["led_gpio"] = settings.led_gpio;
|
||||
root["hide_led"] = settings.hide_led;
|
||||
root["notoken_api"] = settings.notoken_api;
|
||||
root["analog_enabled"] = settings.analog_enabled;
|
||||
root["pbutton_gpio"] = settings.pbutton_gpio;
|
||||
root["solar_maxflow"] = settings.solar_maxflow;
|
||||
root["board_profile"] = settings.board_profile;
|
||||
}
|
||||
|
||||
// call on initialization and also when settings are updated via web or console
|
||||
StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
|
||||
// load default GPIO configuration based on board profile
|
||||
std::vector<uint8_t> data; // led, dallas, rx, tx, button
|
||||
settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE;
|
||||
if (!System::load_board_profile(data, settings.board_profile.c_str())) {
|
||||
settings.board_profile = EMSESP_DEFAULT_BOARD_PROFILE; // invalid board configuration, override the default in case it has been misspelled
|
||||
}
|
||||
|
||||
uint8_t default_led_gpio = data[0];
|
||||
uint8_t default_dallas_gpio = data[1];
|
||||
uint8_t default_rx_gpio = data[2];
|
||||
uint8_t default_tx_gpio = data[3];
|
||||
uint8_t default_pbutton_gpio = data[4];
|
||||
|
||||
EMSESP::logger().info(F("EMS-ESP version %s"), EMSESP_APP_VERSION);
|
||||
|
||||
// check to see if we have a settings file, if not it's a fresh install
|
||||
if (!root.size()) {
|
||||
EMSESP::logger().info(F("Initializing configuration with board profile %s"), settings.board_profile.c_str());
|
||||
} else {
|
||||
EMSESP::logger().info(F("Using configuration from board profile %s"), settings.board_profile.c_str());
|
||||
}
|
||||
|
||||
int prev;
|
||||
reset_flags();
|
||||
|
||||
// tx_mode, rx and tx pins
|
||||
prev = settings.tx_mode;
|
||||
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
|
||||
check_flag(prev, settings.tx_mode, ChangeFlags::UART);
|
||||
prev = settings.tx_delay;
|
||||
settings.tx_delay = root["tx_delay"] | EMSESP_DEFAULT_TX_DELAY;
|
||||
check_flag(prev, settings.tx_delay, ChangeFlags::UART);
|
||||
prev = settings.rx_gpio;
|
||||
settings.rx_gpio = root["rx_gpio"] | default_rx_gpio;
|
||||
check_flag(prev, settings.rx_gpio, ChangeFlags::UART);
|
||||
prev = settings.tx_gpio;
|
||||
settings.tx_gpio = root["tx_gpio"] | default_tx_gpio;
|
||||
check_flag(prev, settings.tx_gpio, ChangeFlags::UART);
|
||||
|
||||
// syslog
|
||||
prev = settings.syslog_enabled;
|
||||
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
|
||||
check_flag(prev, settings.syslog_enabled, ChangeFlags::SYSLOG);
|
||||
|
||||
prev = settings.syslog_level;
|
||||
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
|
||||
check_flag(prev, settings.syslog_level, ChangeFlags::SYSLOG);
|
||||
|
||||
prev = settings.syslog_mark_interval;
|
||||
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
|
||||
check_flag(prev, settings.syslog_mark_interval, ChangeFlags::SYSLOG);
|
||||
|
||||
String old_syslog_host = settings.syslog_host;
|
||||
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
|
||||
if (old_syslog_host.equals(settings.syslog_host.c_str())) {
|
||||
add_flags(ChangeFlags::SYSLOG);
|
||||
}
|
||||
|
||||
prev = settings.syslog_port;
|
||||
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
|
||||
check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG);
|
||||
|
||||
prev = settings.trace_raw;
|
||||
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
|
||||
check_flag(prev, settings.trace_raw, ChangeFlags::SYSLOG);
|
||||
EMSESP::trace_raw(settings.trace_raw);
|
||||
|
||||
// adc
|
||||
prev = settings.analog_enabled;
|
||||
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
|
||||
check_flag(prev, settings.analog_enabled, ChangeFlags::ADC);
|
||||
|
||||
// button
|
||||
prev = settings.pbutton_gpio;
|
||||
settings.pbutton_gpio = root["pbutton_gpio"] | default_pbutton_gpio;
|
||||
check_flag(prev, settings.pbutton_gpio, ChangeFlags::BUTTON);
|
||||
|
||||
// dallas
|
||||
prev = settings.dallas_gpio;
|
||||
settings.dallas_gpio = root["dallas_gpio"] | default_dallas_gpio;
|
||||
check_flag(prev, settings.dallas_gpio, ChangeFlags::DALLAS);
|
||||
prev = settings.dallas_parasite;
|
||||
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
|
||||
check_flag(prev, settings.dallas_parasite, ChangeFlags::DALLAS);
|
||||
|
||||
// shower
|
||||
prev = settings.shower_timer;
|
||||
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
check_flag(prev, settings.shower_timer, ChangeFlags::SHOWER);
|
||||
prev = settings.shower_alert;
|
||||
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
|
||||
check_flag(prev, settings.shower_alert, ChangeFlags::SHOWER);
|
||||
|
||||
// led
|
||||
prev = settings.led_gpio;
|
||||
settings.led_gpio = root["led_gpio"] | default_led_gpio;
|
||||
check_flag(prev, settings.led_gpio, ChangeFlags::LED);
|
||||
prev = settings.hide_led;
|
||||
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
|
||||
check_flag(prev, settings.hide_led, ChangeFlags::LED);
|
||||
|
||||
// these both need reboots to be applied
|
||||
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
|
||||
|
||||
// doesn't need any follow-up actions
|
||||
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
|
||||
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
// this is called after any of the settings have been persisted to the filesystem
|
||||
// either via the Web UI or via the Console
|
||||
void WebSettingsService::onUpdate() {
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::SHOWER)) {
|
||||
EMSESP::shower_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::DALLAS)) {
|
||||
EMSESP::dallassensor_.start();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) {
|
||||
EMSESP::init_uart();
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
|
||||
EMSESP::system_.syslog_init(true); // reload settings
|
||||
EMSESP::system_.syslog_start(); // re-start (or stop)
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) {
|
||||
EMSESP::system_.adc_init(true); // reload settings
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::BUTTON)) {
|
||||
EMSESP::system_.button_init(true); // reload settings
|
||||
}
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
|
||||
EMSESP::system_.led_init(true); // reload settings
|
||||
}
|
||||
}
|
||||
|
||||
void WebSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
}
|
||||
|
||||
void WebSettingsService::save() {
|
||||
_fsPersistence.writeToFS();
|
||||
}
|
||||
|
||||
// build the json profile to send back
|
||||
void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM);
|
||||
JsonObject root = response->getRoot();
|
||||
if (json.containsKey("code")) {
|
||||
String board_profile = json["code"];
|
||||
std::vector<uint8_t> data; // led, dallas, rx, tx, button
|
||||
// check for valid board
|
||||
if (System::load_board_profile(data, board_profile.c_str())) {
|
||||
root["led_gpio"] = data[0];
|
||||
root["dallas_gpio"] = data[1];
|
||||
root["rx_gpio"] = data[2];
|
||||
root["tx_gpio"] = data[3];
|
||||
root["pbutton_gpio"] = data[4];
|
||||
} else {
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
120
src/web/WebSettingsService.h
Normal file
120
src/web/WebSettingsService.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WebSettingsConfig_h
|
||||
#define WebSettingsConfig_h
|
||||
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
|
||||
#include "../default_settings.h"
|
||||
|
||||
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
|
||||
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
|
||||
#define EMSESP_BOARD_PROFILE_SERVICE_PATH "/rest/boardProfile"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class WebSettings {
|
||||
public:
|
||||
uint8_t tx_mode;
|
||||
uint8_t tx_delay;
|
||||
uint8_t ems_bus_id;
|
||||
uint8_t master_thermostat;
|
||||
bool shower_timer;
|
||||
bool shower_alert;
|
||||
bool syslog_enabled;
|
||||
int8_t syslog_level; // uuid::log::Level
|
||||
uint32_t syslog_mark_interval;
|
||||
String syslog_host;
|
||||
uint16_t syslog_port;
|
||||
bool trace_raw;
|
||||
uint8_t rx_gpio;
|
||||
uint8_t tx_gpio;
|
||||
uint8_t dallas_gpio;
|
||||
bool dallas_parasite;
|
||||
uint8_t led_gpio;
|
||||
bool hide_led;
|
||||
bool notoken_api;
|
||||
bool analog_enabled;
|
||||
uint8_t pbutton_gpio;
|
||||
uint8_t solar_maxflow;
|
||||
String board_profile;
|
||||
|
||||
static void read(WebSettings & settings, JsonObject & root);
|
||||
static StateUpdateResult update(JsonObject & root, WebSettings & settings);
|
||||
|
||||
enum ChangeFlags : uint8_t {
|
||||
|
||||
NONE = 0,
|
||||
UART = (1 << 0), // 1
|
||||
SYSLOG = (1 << 1), // 2
|
||||
ADC = (1 << 2), // 4
|
||||
DALLAS = (1 << 3), // 8
|
||||
SHOWER = (1 << 4), // 16
|
||||
LED = (1 << 5), // 32
|
||||
BUTTON = (1 << 6) // 64
|
||||
|
||||
};
|
||||
|
||||
static void check_flag(int prev_v, int new_v, uint8_t flag) {
|
||||
if (prev_v != new_v) {
|
||||
add_flags(flag);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_flags(uint8_t flags) {
|
||||
flags_ |= flags;
|
||||
}
|
||||
|
||||
static bool has_flags(uint8_t flags) {
|
||||
return (flags_ & flags) == flags;
|
||||
}
|
||||
|
||||
static void reset_flags() {
|
||||
flags_ = ChangeFlags::NONE;
|
||||
}
|
||||
|
||||
static uint8_t get_flags() {
|
||||
return flags_;
|
||||
}
|
||||
|
||||
private:
|
||||
static uint8_t flags_;
|
||||
};
|
||||
|
||||
class WebSettingsService : public StatefulService<WebSettings> {
|
||||
public:
|
||||
WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
void save();
|
||||
|
||||
private:
|
||||
HttpEndpoint<WebSettings> _httpEndpoint;
|
||||
FSPersistence<WebSettings> _fsPersistence;
|
||||
AsyncCallbackJsonWebHandler _boardProfileHandler;
|
||||
|
||||
void board_profile(AsyncWebServerRequest * request, JsonVariant & json);
|
||||
|
||||
void onUpdate();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
95
src/web/WebStatusService.cpp
Normal file
95
src/web/WebStatusService.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
WebStatusService::WebStatusService(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
// rest endpoint for web page
|
||||
server->on(EMSESP_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&WebStatusService::webStatusService, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
WiFi.onEvent(std::bind(&WebStatusService::WiFiEvent, this, _1, _2));
|
||||
}
|
||||
|
||||
// handles both WiFI and Ethernet
|
||||
void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch (event) {
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
EMSESP::logger().info(F("WiFi Disconnected. Reason code=%d"), info.disconnected.reason);
|
||||
break;
|
||||
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
#ifndef EMSESP_STANDALONE
|
||||
EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
|
||||
#endif
|
||||
EMSESP::system_.wifi_tweak();
|
||||
EMSESP::system_.send_heartbeat();
|
||||
EMSESP::system_.syslog_start();
|
||||
break;
|
||||
|
||||
case SYSTEM_EVENT_ETH_START:
|
||||
EMSESP::logger().info(F("Ethernet initialized"));
|
||||
ETH.setHostname(EMSESP::system_.hostname().c_str());
|
||||
break;
|
||||
|
||||
case SYSTEM_EVENT_ETH_GOT_IP:
|
||||
// prevent double calls
|
||||
if (!EMSESP::system_.ethernet_connected()) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
EMSESP::logger().info(F("Ethernet Connected with IP=%s, speed %d Mbps"), ETH.localIP().toString().c_str(), ETH.linkSpeed());
|
||||
#endif
|
||||
EMSESP::system_.send_heartbeat();
|
||||
EMSESP::system_.syslog_start();
|
||||
EMSESP::system_.ethernet_connected(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case SYSTEM_EVENT_ETH_DISCONNECTED:
|
||||
EMSESP::logger().info(F("Ethernet Disconnected"));
|
||||
EMSESP::system_.ethernet_connected(false);
|
||||
break;
|
||||
|
||||
case SYSTEM_EVENT_ETH_STOP:
|
||||
EMSESP::logger().info(F("Ethernet Stopped"));
|
||||
EMSESP::system_.ethernet_connected(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebStatusService::webStatusService(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM_DYN);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root["status"] = EMSESP::bus_status(); // 0, 1 or 2
|
||||
root["rx_received"] = EMSESP::rxservice_.telegram_count();
|
||||
root["tx_sent"] = EMSESP::txservice_.telegram_read_count() + EMSESP::txservice_.telegram_write_count();
|
||||
root["rx_quality"] = EMSESP::rxservice_.quality();
|
||||
root["tx_quality"] = EMSESP::txservice_.quality();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
43
src/web/WebStatusService.h
Normal file
43
src/web/WebStatusService.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||
* Copyright 2020 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WebStatusService_h
|
||||
#define WebStatusService_h
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
|
||||
#define EMSESP_STATUS_SERVICE_PATH "/rest/emsespStatus"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class WebStatusService {
|
||||
public:
|
||||
WebStatusService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
void webStatusService(AsyncWebServerRequest * request);
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user