mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 17:29:50 +03:00
Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev
This commit is contained in:
@@ -128,9 +128,9 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
if (return_code != CommandRet::OK) {
|
||||
char error[100];
|
||||
if (output.size()) {
|
||||
snprintf(error, sizeof(error), "API failed with error %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
|
||||
snprintf(error, sizeof(error), "API call failed. %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str());
|
||||
} else {
|
||||
snprintf(error, sizeof(error), "API failed with error %s", Command::return_code_string(return_code).c_str());
|
||||
snprintf(error, sizeof(error), "API call failed (%s)", Command::return_code_string(return_code).c_str());
|
||||
}
|
||||
emsesp::EMSESP::logger().err(error);
|
||||
api_fails_++;
|
||||
@@ -138,17 +138,27 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
|
||||
// if we're returning single values, just sent as plain text
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/462#issuecomment-1093877210
|
||||
if (output.containsKey("api_data")) {
|
||||
String data = output["api_data"].as<String>();
|
||||
request->send(200, "text/plain; charset=utf-8", data);
|
||||
const char * api_data = output["api_data"];
|
||||
if (api_data) {
|
||||
request->send(200, "text/plain; charset=utf-8", api_data);
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
Serial.printf("%sweb output: %s[%s] %s(200)%s ", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str(), COLOR_BRIGHT_GREEN, COLOR_MAGENTA);
|
||||
serializeJson(output, Serial);
|
||||
Serial.println(COLOR_RESET);
|
||||
Serial.println();
|
||||
#endif
|
||||
api_count_++;
|
||||
delete response;
|
||||
return;
|
||||
}
|
||||
|
||||
// send the json that came back from the command call
|
||||
// FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED = 400 (bad request), 200 (OK), 400 (not found), 400 (bad request), 401 (unauthorized)
|
||||
int ret_codes[6] = {400, 200, 400, 400, 401, 400};
|
||||
// sequence is FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED, INVALID
|
||||
// 400 (bad request)
|
||||
// 200 (OK)
|
||||
// 404 (not found)
|
||||
// 401 (unauthorized)
|
||||
int ret_codes[6] = {400, 200, 404, 400, 401, 400};
|
||||
response->setCode(ret_codes[return_code]);
|
||||
response->setLength();
|
||||
response->setContentType("application/json; charset=utf-8");
|
||||
@@ -156,15 +166,11 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
api_count_++;
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
Serial.print(COLOR_YELLOW);
|
||||
Serial.print("data: ");
|
||||
if (output.size()) {
|
||||
serializeJson(output, Serial);
|
||||
}
|
||||
Serial.print(" (response code ");
|
||||
Serial.print(ret_codes[return_code]);
|
||||
Serial.println(")");
|
||||
Serial.print(COLOR_RESET);
|
||||
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);
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -237,13 +237,12 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem en
|
||||
}
|
||||
break;
|
||||
case DeviceValueType::STRING:
|
||||
default:
|
||||
// if no type treat it as a string
|
||||
if (entity.data.length() > 0) {
|
||||
output[name] = entity.data;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// EMSESP::logger().warning("unknown value type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +268,8 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
return true;
|
||||
}
|
||||
|
||||
// if no entries, return empty json
|
||||
// if no custom entries, return empty json
|
||||
// even if we're looking for a specific entity
|
||||
// https://github.com/emsesp/EMS-ESP32/issues/1297
|
||||
if (customEntityItems_->size() == 0) {
|
||||
return true;
|
||||
@@ -287,6 +287,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
char command_s[COMMAND_MAX_LENGTH];
|
||||
strlcpy(command_s, Helpers::toLower(cmd).c_str(), sizeof(command_s));
|
||||
char * attribute_s = nullptr;
|
||||
|
||||
// check specific attribute to fetch instead of the complete record
|
||||
char * breakp = strchr(command_s, '/');
|
||||
if (breakp) {
|
||||
@@ -315,21 +316,16 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
output["bytes"] = (uint8_t)entity.factor;
|
||||
}
|
||||
}
|
||||
render_value(output, entity, true);
|
||||
render_value(output, entity, true); // create the "value" field
|
||||
|
||||
if (attribute_s) {
|
||||
if (output.containsKey(attribute_s)) {
|
||||
String data = output[attribute_s].as<String>();
|
||||
std::string data = output[attribute_s].as<std::string>();
|
||||
output.clear();
|
||||
output["api_data"] = data;
|
||||
output["api_data"] = data; // always as string
|
||||
return true;
|
||||
} else {
|
||||
char error[100];
|
||||
snprintf(error, sizeof(error), "cannot find attribute %s in entity %s", attribute_s, command_s);
|
||||
output.clear();
|
||||
output["message"] = error;
|
||||
return false;
|
||||
}
|
||||
return EMSESP::return_not_found(output, "attribute", command_s); // not found
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,8 +334,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
}
|
||||
}
|
||||
|
||||
output["message"] = "unknown command";
|
||||
return false;
|
||||
return EMSESP::return_not_found(output, "custom entity", cmd); // not found
|
||||
}
|
||||
|
||||
// publish single value
|
||||
@@ -641,8 +636,10 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
|
||||
void WebCustomEntityService::test() {
|
||||
update([&](WebCustomEntity & webCustomEntity) {
|
||||
webCustomEntity.customEntityItems.clear();
|
||||
auto entityItem = CustomEntityItem();
|
||||
|
||||
// test 1
|
||||
auto entityItem = CustomEntityItem();
|
||||
entityItem.id = 1;
|
||||
entityItem.ram = 0;
|
||||
entityItem.device_id = 8;
|
||||
entityItem.type_id = 24;
|
||||
@@ -656,6 +653,7 @@ void WebCustomEntityService::test() {
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
// test 2
|
||||
entityItem.id = 2;
|
||||
entityItem.ram = 0;
|
||||
entityItem.device_id = 24;
|
||||
entityItem.type_id = 677;
|
||||
@@ -668,7 +666,8 @@ void WebCustomEntityService::test() {
|
||||
entityItem.data = "48";
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
// test 2
|
||||
// test 3
|
||||
entityItem.id = 3;
|
||||
entityItem.ram = 1;
|
||||
entityItem.device_id = 0;
|
||||
entityItem.type_id = 0;
|
||||
@@ -681,6 +680,21 @@ void WebCustomEntityService::test() {
|
||||
entityItem.data = "14";
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
// test 4
|
||||
entityItem.id = 4;
|
||||
entityItem.ram = 1;
|
||||
entityItem.device_id = 0;
|
||||
entityItem.type_id = 0;
|
||||
entityItem.offset = 0;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "seltemp";
|
||||
entityItem.uom = 0;
|
||||
entityItem.value_type = 8;
|
||||
entityItem.writeable = true;
|
||||
entityItem.data = "14";
|
||||
entityItem.value = 12;
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
|
||||
EMSESP::system_.restart_requested(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// failed
|
||||
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
|
||||
request->send(response);
|
||||
|
||||
@@ -212,7 +212,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) {
|
||||
}
|
||||
|
||||
// invalid
|
||||
AsyncWebServerResponse * response = request->beginResponse(400);
|
||||
AsyncWebServerResponse * response = request->beginResponse(400); // bad request
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
|
||||
|
||||
// process json output for info/commands and value_info
|
||||
bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
// check of it a 'commmands' command
|
||||
// check of it a 'commands' command
|
||||
if (Helpers::toLower(cmd) == F_(commands)) {
|
||||
output[F_(info)] = Helpers::translated_word(FL_(info_cmd));
|
||||
output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd));
|
||||
@@ -196,17 +196,17 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
}
|
||||
|
||||
if (attribute_s && output.containsKey(attribute_s)) {
|
||||
String data = output[attribute_s].as<String>();
|
||||
std::string data = output[attribute_s].as<std::string>();
|
||||
output.clear();
|
||||
output["api_data"] = data;
|
||||
output["api_data"] = data; // always as a string
|
||||
return true;
|
||||
}
|
||||
|
||||
if (output.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
output["message"] = "unknown command";
|
||||
return false;
|
||||
return EMSESP::return_not_found(output, "schedule", cmd); // not found
|
||||
}
|
||||
|
||||
// publish single value
|
||||
@@ -304,8 +304,10 @@ void WebSchedulerService::publish(const bool force) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ha_registered_ = ha_created;
|
||||
if (doc.size() > 0) {
|
||||
|
||||
if (!doc.isNull()) {
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(topic, sizeof(topic), "%s_data", F_(scheduler));
|
||||
Mqtt::queue_publish(topic, doc.as<JsonObject>());
|
||||
@@ -340,6 +342,7 @@ bool WebSchedulerService::command(const char * cmd, const char * data) {
|
||||
// prefix "api/" to command string
|
||||
char command_str[COMMAND_MAX_LENGTH];
|
||||
snprintf(command_str, sizeof(command_str), "/api/%s", cmd);
|
||||
|
||||
uint8_t return_code = Command::process(command_str, true, input, output); // admin set
|
||||
|
||||
if (return_code == CommandRet::OK) {
|
||||
@@ -471,23 +474,69 @@ void WebSchedulerService::loop() {
|
||||
// hard coded tests
|
||||
#if defined(EMSESP_TEST)
|
||||
void WebSchedulerService::test() {
|
||||
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
|
||||
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
|
||||
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
already_added = true;
|
||||
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
return StateUpdateResult::CHANGED; // persist the changes
|
||||
});
|
||||
}
|
||||
|
||||
// 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_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(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
// should output 'rssi is -23 dbm'
|
||||
test_value = "\"rssi is \"(system/network/rssi)\" dBm\"";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp/value)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "\"seltemp=\"(custom/seltemp/value)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(custom/seltemp)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/outdoortemp)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "boiler/flowtempoffset";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/flowtempoffset/value)";
|
||||
command(test_cmd.c_str(), compute(test_value).c_str());
|
||||
|
||||
test_value = "(boiler/storagetemp1/value)";
|
||||
command(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(test_cmd.c_str(), compute(test_value).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -136,6 +136,9 @@ void WebStatusService::ESPsystemStatus(AsyncWebServerRequest * request) {
|
||||
root["has_loader"] = (buffer != 0xFFFFFFFFFFFFFFFF && running->size != partition->size);
|
||||
}
|
||||
}
|
||||
|
||||
root["model"] = EMSESP::system_.getBBQKeesGatewayDetails();
|
||||
|
||||
#endif
|
||||
|
||||
response->setLength();
|
||||
|
||||
@@ -386,9 +386,8 @@ std::string to_string(double d) {
|
||||
// RPN calculator
|
||||
std::string calculate(const std::string & expr) {
|
||||
auto expr_new = emsesp::Helpers::toLower(expr);
|
||||
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
||||
commands(expr_new);
|
||||
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
||||
|
||||
const auto tokens = exprToTokens(expr_new);
|
||||
if (tokens.empty()) {
|
||||
return "";
|
||||
@@ -397,6 +396,16 @@ std::string calculate(const std::string & expr) {
|
||||
if (queue.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
// debug only print tokens
|
||||
#ifdef EMSESP_STANDALONE
|
||||
for (const auto & t : queue) {
|
||||
emsesp::EMSESP::logger().debug("shunt token: %s(%d)", t.str.c_str(), t.type);
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
|
||||
std::vector<std::string> stack;
|
||||
|
||||
while (!queue.empty()) {
|
||||
@@ -556,7 +565,16 @@ std::string calculate(const std::string & expr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stack.back();
|
||||
|
||||
// concatenate all elements in stack to a single string, separated by spaces and return
|
||||
// experimental - for MDvP to check
|
||||
std::string result = "";
|
||||
for (const auto & s : stack) {
|
||||
result += s;
|
||||
}
|
||||
return result;
|
||||
|
||||
// return stack.back();
|
||||
}
|
||||
|
||||
// check for multiple instances of <cond> ? <expr1> : <expr2>
|
||||
@@ -586,5 +604,6 @@ std::string compute(const std::string & expr) {
|
||||
}
|
||||
q = expr_new.find_first_of("?"); // search next instance
|
||||
}
|
||||
|
||||
return calculate(expr_new);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user