mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 01:09:51 +03:00
Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev
This commit is contained in:
@@ -51,6 +51,14 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant j
|
||||
parse(request, input);
|
||||
}
|
||||
|
||||
// for POSTS accepting plain text data
|
||||
void WebAPIService::webAPIService(AsyncWebServerRequest * request, const char * data) {
|
||||
JsonDocument input_doc;
|
||||
JsonObject input = input_doc.to<JsonObject>();
|
||||
input["data"] = data;
|
||||
parse(request, input);
|
||||
}
|
||||
|
||||
#ifdef EMSESP_TEST
|
||||
// for test.cpp and unit tests so we can invoke GETs to test the API
|
||||
void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
|
||||
@@ -100,7 +108,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
emsesp::EMSESP::system_.refreshHeapMem();
|
||||
|
||||
// output json buffer
|
||||
auto response = new AsyncJsonResponse(false);
|
||||
auto response = new AsyncJsonResponse();
|
||||
|
||||
// add more mem if needed - won't be needed in ArduinoJson 7
|
||||
// while (!response->getSize()) {
|
||||
@@ -155,10 +163,19 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
storeResponse(output);
|
||||
#endif
|
||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||
Serial.printf("%sweb output: %s[%s]", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str());
|
||||
Serial.printf(" %s(%d)%s ", ret_codes[return_code] == 200 ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED, ret_codes[return_code], COLOR_YELLOW);
|
||||
serializeJson(output, Serial);
|
||||
Serial.println(COLOR_RESET);
|
||||
std::string output_str;
|
||||
serializeJson(output, output_str);
|
||||
Serial.printf("%sweb output: %s[%s] %s(%d)%s %s%s",
|
||||
COLOR_WHITE,
|
||||
COLOR_BRIGHT_CYAN,
|
||||
request->url().c_str(),
|
||||
ret_codes[return_code] == 200 ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED,
|
||||
ret_codes[return_code],
|
||||
COLOR_YELLOW,
|
||||
output_str.c_str(),
|
||||
COLOR_RESET);
|
||||
Serial.println();
|
||||
EMSESP::logger().debug("web output: %s %s", request->url().c_str(), output_str.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class WebAPIService {
|
||||
WebAPIService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
void webAPIService(AsyncWebServerRequest * request, JsonVariant input);
|
||||
void webAPIService(AsyncWebServerRequest * request, const char * data); // for plain text data
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
// for test.cpp and running unit tests
|
||||
|
||||
@@ -153,6 +153,12 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
||||
|
||||
// set value by api command
|
||||
bool WebCustomEntityService::command_setvalue(const char * value, const int8_t id, const char * name) {
|
||||
// don't write if there is no value, to prevent setting an empty value by mistake when parsing attributes
|
||||
if (!strlen(value)) {
|
||||
EMSESP::logger().debug("can't set empty value!");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CustomEntityItem & entityItem : *customEntityItems_) {
|
||||
if (Helpers::toLower(entityItem.name) == Helpers::toLower(name)) {
|
||||
if (entityItem.ram == 1) {
|
||||
@@ -217,7 +223,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
|
||||
|
||||
// output of a single value
|
||||
// if add_uom is true it will add the UOM string to the value
|
||||
void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem & entity, const bool useVal, const bool web, const bool add_uom) {
|
||||
void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem const & entity, const bool useVal, const bool web, const bool add_uom) {
|
||||
char payload[20];
|
||||
std::string name = useVal ? "value" : entity.name;
|
||||
switch (entity.value_type) {
|
||||
@@ -286,6 +292,10 @@ void WebCustomEntityService::show_values(JsonObject output) {
|
||||
|
||||
// process json output for info/commands and value_info
|
||||
bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) {
|
||||
if (cmd == nullptr || strlen(cmd) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if no custom entries, return empty json
|
||||
// even if we're looking for a specific entity
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/1297
|
||||
@@ -315,17 +325,17 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
|
||||
// specific value info
|
||||
const char * attribute_s = Command::get_attribute(cmd);
|
||||
for (auto & entity : *customEntityItems_) {
|
||||
for (auto const & entity : *customEntityItems_) {
|
||||
if (Helpers::toLower(entity.name) == cmd) {
|
||||
get_value_json(output, entity);
|
||||
return Command::set_attribute(output, cmd, attribute_s);
|
||||
return Command::get_attribute(output, cmd, attribute_s);
|
||||
}
|
||||
}
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
// build the json for specific entity
|
||||
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem & entity) {
|
||||
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem const & entity) {
|
||||
output["name"] = entity.name;
|
||||
output["fullname"] = entity.name;
|
||||
output["storage"] = entity.ram ? "ram" : "ems";
|
||||
@@ -681,9 +691,10 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
|
||||
// hard coded tests
|
||||
// add the entity and also add the command for writeable entities
|
||||
#ifdef EMSESP_TEST
|
||||
void WebCustomEntityService::test() {
|
||||
void WebCustomEntityService::load_test_data() {
|
||||
update([&](WebCustomEntity & webCustomEntity) {
|
||||
webCustomEntity.customEntityItems.clear();
|
||||
webCustomEntity.customEntityItems.clear(); // delete all existing entities
|
||||
|
||||
auto entityItem = CustomEntityItem();
|
||||
|
||||
// test 1
|
||||
@@ -698,6 +709,7 @@ void WebCustomEntityService::test() {
|
||||
entityItem.value_type = 1;
|
||||
entityItem.writeable = true;
|
||||
entityItem.data = "70";
|
||||
entityItem.value = 70;
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
@@ -751,12 +763,12 @@ void WebCustomEntityService::test() {
|
||||
entityItem.type_id = 0;
|
||||
entityItem.offset = 0;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "seltemp";
|
||||
entityItem.name = "test_seltemp";
|
||||
entityItem.uom = 0;
|
||||
entityItem.value_type = 8;
|
||||
entityItem.writeable = true;
|
||||
entityItem.data = "14";
|
||||
entityItem.value = 12;
|
||||
entityItem.value = 14;
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
|
||||
@@ -60,10 +60,10 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
|
||||
void publish(const bool force = false);
|
||||
bool command_setvalue(const char * value, const int8_t id, const char * name);
|
||||
bool get_value_info(JsonObject output, const char * cmd);
|
||||
void get_value_json(JsonObject output, CustomEntityItem & entity);
|
||||
void get_value_json(JsonObject output, CustomEntityItem const & entity);
|
||||
bool get_value(std::shared_ptr<const Telegram> telegram);
|
||||
void fetch();
|
||||
void render_value(JsonObject output, CustomEntityItem & entity, const bool useVal = false, const bool web = false, const bool add_uom = false);
|
||||
void render_value(JsonObject output, CustomEntityItem const & entity, const bool useVal = false, const bool web = false, const bool add_uom = false);
|
||||
void show_values(JsonObject output);
|
||||
void generate_value_web(JsonObject output, const bool is_dashboard = false);
|
||||
|
||||
@@ -73,7 +73,7 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
|
||||
}
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void test();
|
||||
void load_test_data();
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
@@ -366,10 +366,11 @@ void WebCustomizationService::begin() {
|
||||
|
||||
// hard coded tests
|
||||
#ifdef EMSESP_TEST
|
||||
void WebCustomizationService::test() {
|
||||
void WebCustomizationService::load_test_data() {
|
||||
update([&](WebCustomization & webCustomization) {
|
||||
// Temperature sensors
|
||||
webCustomization.sensorCustomizations.clear();
|
||||
webCustomization.sensorCustomizations.clear(); // delete all existing sensors
|
||||
|
||||
auto sensor = SensorCustomization();
|
||||
sensor.id = "01_0203_0405_0607";
|
||||
sensor.name = "test_tempsensor1";
|
||||
|
||||
@@ -85,7 +85,7 @@ class WebCustomizationService : public StatefulService<WebCustomization> {
|
||||
void begin();
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void test();
|
||||
void load_test_data();
|
||||
#endif
|
||||
|
||||
// make all functions public so we can test in the debug and standalone mode
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
#include "emsesp.h"
|
||||
#include "WebSchedulerService.h"
|
||||
#include <HTTPClient.h>
|
||||
|
||||
#include "shuntingYard.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -173,7 +174,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (Helpers::toLower(scheduleItem.name) == cmd) {
|
||||
get_value_json(output, scheduleItem);
|
||||
return Command::set_attribute(output, cmd, attribute_s);
|
||||
return Command::get_attribute(output, cmd, attribute_s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,14 +322,14 @@ uint8_t WebSchedulerService::count_entities(bool cmd_only) {
|
||||
return count;
|
||||
}
|
||||
|
||||
#include "shuntingYard.hpp"
|
||||
|
||||
|
||||
// execute scheduled command
|
||||
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
|
||||
std::string cmd = Helpers::toLower(command);
|
||||
|
||||
// check http commands. e.g.
|
||||
// tasmota(get): http://<tasmotsIP>/cm?cmnd=power%20ON
|
||||
// tasmota(get): http://<tasmotaIP>/cm?cmnd=power%20ON
|
||||
// shelly(get): http://<shellyIP>/relais/0?turn=on
|
||||
// parse json
|
||||
JsonDocument doc;
|
||||
@@ -351,6 +352,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
||||
}
|
||||
std::string value = doc["value"] | data.c_str(); // extract value if its in the command, or take the data
|
||||
std::string method = doc["method"] | "GET"; // default GET
|
||||
|
||||
commands(value, false);
|
||||
// if there is data, force a POST
|
||||
int httpResult = 0;
|
||||
@@ -544,110 +546,38 @@ void WebSchedulerService::scheduler_task(void * pvParameters) {
|
||||
|
||||
// hard coded tests
|
||||
#if defined(EMSESP_TEST)
|
||||
void WebSchedulerService::test() {
|
||||
static bool already_added = false;
|
||||
if (!already_added) {
|
||||
update([&](WebScheduler & webScheduler) {
|
||||
// webScheduler.scheduleItems.clear();
|
||||
// test 1
|
||||
auto si = ScheduleItem();
|
||||
si.active = true;
|
||||
si.flags = 1;
|
||||
si.time = "12:00";
|
||||
si.cmd = "system/fetch";
|
||||
si.value = "10";
|
||||
si.name = "test_scheduler";
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
void WebSchedulerService::load_test_data() {
|
||||
update([&](WebScheduler & webScheduler) {
|
||||
webScheduler.scheduleItems.clear(); // delete all existing schedules
|
||||
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
// test 1
|
||||
auto si = ScheduleItem();
|
||||
si.active = true;
|
||||
si.flags = 1;
|
||||
si.time = "12:00";
|
||||
si.cmd = "system/fetch";
|
||||
si.value = "10";
|
||||
si.name = "test_scheduler";
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
|
||||
// test 2
|
||||
si = ScheduleItem();
|
||||
si.active = false;
|
||||
si.flags = 1;
|
||||
si.time = "13:00";
|
||||
si.cmd = "system/message";
|
||||
si.value = "20";
|
||||
si.name = ""; // to make sure its excluded from Dashboard
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
already_added = true;
|
||||
// test 2
|
||||
si = ScheduleItem();
|
||||
si.active = false;
|
||||
si.flags = 1;
|
||||
si.time = "13:00";
|
||||
si.cmd = "system/message";
|
||||
si.value = "20";
|
||||
si.name = ""; // to make sure its excluded from Dashboard
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
|
||||
// test shunting yard
|
||||
std::string test_cmd = "system/message";
|
||||
std::string test_value;
|
||||
|
||||
// should output 'locale is en'
|
||||
test_value = "\"locale is \"system/settings/locale";
|
||||
command("test", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// test with negative value
|
||||
// should output 'rssi is -23'
|
||||
test_value = "\"rssi is \"0+system/network/rssi";
|
||||
command("test1", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// should output 'rssi is -23 dbm'
|
||||
test_value = "\"rssi is \"(system/network/rssi)\" dBm\"";
|
||||
command("test2", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp/value)";
|
||||
command("test3", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "\"seltemp=\"(custom/seltemp/value)";
|
||||
command("test4", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp)";
|
||||
command("test5", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "boiler/flowtempoffset";
|
||||
command("test7", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/flowtempoffset/value)";
|
||||
command("test8", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/storagetemp1/value)";
|
||||
command("test9", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// (14 - 40) * 2.8 + 5 = -67.8
|
||||
test_value = "(custom/seltemp - boiler/flowtempoffset) * 2.8 + 5";
|
||||
command("test10", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// test case conversion
|
||||
test_value = "(thermostat/hc1/modetype == \"comfort\")";
|
||||
command("test11a", test_cmd.c_str(), compute(test_value).c_str()); // should be 1 true
|
||||
test_value = "(thermostat/hc1/modetype == \"Comfort\")";
|
||||
command("test11b", test_cmd.c_str(), compute(test_value).c_str()); // should be 1 true
|
||||
test_value = "(thermostat/hc1/modetype == \"unknown\")";
|
||||
command("test11c", test_cmd.c_str(), compute(test_value).c_str()); // should be 0 false
|
||||
|
||||
// can't find entity, should fail
|
||||
test_value = "(boiler/storagetemp/value1)";
|
||||
command("test12", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// can't find attribute, should fail
|
||||
test_value = "(boiler/storagetemp1/value1)";
|
||||
command("test13", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// check when entity has no value, should pass
|
||||
test_value = "(boiler/storagetemp2/value)";
|
||||
command("test14", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// should pass
|
||||
test_value = "(boiler/storagetemp1/value)";
|
||||
command("test15", test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// test HTTP POST to call HA script
|
||||
// test_cmd = "{\"method\":\"POST\",\"url\":\"http://192.168.1.42:8123/api/services/script/test_notify2\", \"header\":{\"authorization\":\"Bearer "
|
||||
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhMmNlYWI5NDgzMmI0ODE2YWQ2NzU4MjkzZDE2YWMxZSIsImlhdCI6MTcyMTM5MTI0NCwiZXhwIjoyMDM2NzUxMjQ0fQ."
|
||||
// "S5sago1tEI6lNhrDCO0dM_WsVQHkD_laAjcks8tWAqo\"}}";
|
||||
// command("test99", test_cmd.c_str(), "");
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
||||
bool onChange(const char * cmd);
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void test();
|
||||
void load_test_data();
|
||||
#endif
|
||||
|
||||
// make all functions public so we can test in the debug and standalone mode
|
||||
|
||||
Reference in New Issue
Block a user