This commit is contained in:
proddy
2024-08-01 22:19:38 +02:00
27 changed files with 682 additions and 780 deletions

View File

@@ -256,20 +256,6 @@ 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) {
// 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));
output[F_(values)] = Helpers::translated_word(FL_(values_cmd));
for (const auto & entity : *customEntityItems_) {
if (entity.writeable) {
output[entity.name] = "custom entity";
}
}
return true;
}
// if no custom entries, return empty json
// even if we're looking for a specific entity
// https://github.com/emsesp/EMS-ESP32/issues/1297
@@ -278,7 +264,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
}
// if it's info or values...
if (strlen(cmd) == 0 || Helpers::toLower(cmd) == F_(values) || Helpers::toLower(cmd) == F_(info)) {
if (!strlen(cmd) || !strcmp(cmd, F_(values)) || !strcmp(cmd, F_(info))) {
// list all names
for (const CustomEntityItem & entity : *customEntityItems_) {
render_value(output, entity);
@@ -286,59 +272,50 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
return true;
}
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) {
*breakp = '\0';
attribute_s = breakp + 1;
// list all entities
if (!strcmp(cmd, F_(entities))) {
for (const auto & entity : *customEntityItems_) {
auto nest = output[entity.name].to<JsonObject>();
get_value_json(nest, entity);
}
return true;
}
// specific value info
const char * attribute_s = Command::get_attribute(cmd);
for (const auto & entity : *customEntityItems_) {
if (Helpers::toLower(entity.name) == command_s) {
output["name"] = entity.name;
output["ram"] = entity.ram;
output["type"] = entity.value_type == DeviceValueType::BOOL ? "boolean" : entity.value_type == DeviceValueType::STRING ? "string" : F_(number);
if (entity.uom > 0) {
output["uom"] = EMSdevice::uom_to_string(entity.uom);
}
output["readable"] = true;
output["writeable"] = entity.writeable;
output["visible"] = true;
if (entity.ram == 0) {
output["device_id"] = Helpers::hextoa(entity.device_id);
output["type_id"] = Helpers::hextoa(entity.type_id);
output["offset"] = entity.offset;
if (entity.value_type != DeviceValueType::BOOL && entity.value_type != DeviceValueType::STRING) {
output["factor"] = entity.factor;
} else if (entity.value_type == DeviceValueType::STRING) {
output["bytes"] = (uint8_t)entity.factor;
}
}
render_value(output, entity, true); // create the "value" field
if (attribute_s) {
if (output.containsKey(attribute_s)) {
std::string data = output[attribute_s].as<std::string>();
output.clear();
output["api_data"] = data; // always as string
return true;
}
return EMSESP::return_not_found(output, attribute_s, command_s); // not found
}
}
if (output.size()) {
return true;
if (Helpers::toLower(entity.name) == cmd) {
get_value_json(output, entity);
return Command::set_attirbute(output, cmd, attribute_s);
}
}
return false; // not found
}
// build the json for specific entity
void WebCustomEntityService::get_value_json(JsonObject output, const CustomEntityItem & entity) {
output["name"] = entity.name;
output["storage"] = entity.ram ? "ram" : "ems";
output["type"] = entity.value_type == DeviceValueType::BOOL ? "boolean" : entity.value_type == DeviceValueType::STRING ? "string" : F_(number);
if (entity.uom > 0) {
output["uom"] = EMSdevice::uom_to_string(entity.uom);
}
output["readable"] = true;
output["writeable"] = entity.writeable;
output["visible"] = true;
if (entity.ram == 0) {
output["device_id"] = Helpers::hextoa(entity.device_id);
output["type_id"] = Helpers::hextoa(entity.type_id);
output["offset"] = entity.offset;
if (entity.value_type != DeviceValueType::BOOL && entity.value_type != DeviceValueType::STRING) {
output["factor"] = entity.factor;
} else if (entity.value_type == DeviceValueType::STRING) {
output["bytes"] = (uint8_t)entity.factor;
}
}
render_value(output, entity, true); // create the "value" field
}
// publish single value
void WebCustomEntityService::publish_single(const CustomEntityItem & entity) {
if (!Mqtt::enabled() || !Mqtt::publish_single()) {

View File

@@ -58,6 +58,7 @@ 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, const CustomEntityItem & 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);

View File

@@ -51,9 +51,9 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) {
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
JsonObject si = schedule.add<JsonObject>();
si["id"] = counter++; // id is only used to render the table and must be unique
si["active"] = scheduleItem.flags ? scheduleItem.active : false;
si["flags"] = scheduleItem.flags;
si["time"] = scheduleItem.time;
si["active"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.active : false;
si["time"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.time : "";
si["cmd"] = scheduleItem.cmd;
si["value"] = scheduleItem.value;
si["name"] = scheduleItem.name;
@@ -76,7 +76,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
auto si = ScheduleItem();
si.active = schedule["active"];
si.flags = schedule["flags"];
si.time = schedule["time"].as<std::string>();
si.time = si.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? "" : schedule["time"].as<std::string>();
si.cmd = schedule["cmd"].as<std::string>();
si.value = schedule["value"].as<std::string>();
si.name = schedule["name"].as<std::string>();
@@ -132,26 +132,11 @@ 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 '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));
output[F_(values)] = Helpers::translated_word(FL_(values_cmd));
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
if (!scheduleItem.name.empty()) {
output[scheduleItem.name] = "activate schedule";
}
}
return true;
}
if (scheduleItems_->size() == 0) {
return true;
}
if (strlen(cmd) == 0 || Helpers::toLower(cmd) == F_(values) || Helpers::toLower(cmd) == F_(info)) {
if (!strlen(cmd) || !strcmp(cmd, F_(values)) || !strcmp(cmd, F_(info))) {
// list all names
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
if (!scheduleItem.name.empty()) {
@@ -165,68 +150,59 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
}
}
}
return true;
}
char command_s[COMMAND_MAX_LENGTH];
strlcpy(command_s, Helpers::toLower(cmd).c_str(), sizeof(command_s));
char * attribute_s = nullptr;
const char * attribute_s = Command::get_attribute(cmd);
// check specific attribute to fetch instead of the complete record
char * breakp = strchr(command_s, '/');
if (breakp) {
*breakp = '\0';
attribute_s = breakp + 1;
if (!strcmp(cmd, F_(entities))) {
uint8_t i = 0;
char name[30];
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
strlcpy(name, scheduleItem.name == "" ? Helpers::smallitoa(name, i++) : scheduleItem.name.c_str(), sizeof(name));
get_value_json(output[name].to<JsonObject>(), scheduleItem);
}
return true;
}
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
if (Helpers::toLower(scheduleItem.name) == command_s) {
output["name"] = scheduleItem.name;
output["type"] = "boolean";
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
output["value"] = scheduleItem.active;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
output["value"] = scheduleItem.active ? 1 : 0;
} else {
char result[12];
output["value"] = Helpers::render_boolean(result, scheduleItem.active);
}
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
output["condition"] = scheduleItem.time;
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE) {
output["onchange"] = scheduleItem.time;
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER) {
output["timer"] = scheduleItem.time;
} else if (scheduleItem.flags != 0) {
output["time"] = scheduleItem.time;
}
output["command"] = scheduleItem.cmd;
output["cmd_data"] = scheduleItem.value;
output["readable"] = true;
output["writeable"] = true;
output["visible"] = true;
break;
if (Helpers::toLower(scheduleItem.name) == cmd) {
get_value_json(output, scheduleItem);
return Command::set_attirbute(output, cmd, attribute_s);
}
}
if (attribute_s) {
if (output.containsKey(attribute_s)) {
std::string data = output[attribute_s].as<std::string>();
output.clear();
output["api_data"] = data; // always as a string
return true;
}
return EMSESP::return_not_found(output, attribute_s, command_s); // not found
}
if (output.size()) {
return true;
}
return false; // not found
}
// build the json for specific entity
void WebSchedulerService::get_value_json(JsonObject output, const ScheduleItem & scheduleItem) {
output["name"] = scheduleItem.name;
output["type"] = "boolean";
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
output["value"] = scheduleItem.active;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
output["value"] = scheduleItem.active ? 1 : 0;
} else {
char result[12];
output["value"] = Helpers::render_boolean(result, scheduleItem.active);
}
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
output["condition"] = scheduleItem.time;
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE) {
output["onchange"] = scheduleItem.time;
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER) {
output["timer"] = scheduleItem.time;
} else if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
output["time"] = scheduleItem.time;
}
output["command"] = scheduleItem.cmd;
output["cmd_data"] = scheduleItem.value;
bool hasName = scheduleItem.name != "";
output["readable"] = hasName;
output["writeable"] = hasName;
output["visible"] = hasName;
}
// publish single value
void WebSchedulerService::publish_single(const char * name, const bool state) {
if (!Mqtt::enabled() || !Mqtt::publish_single() || name == nullptr || name[0] == '\0') {
@@ -346,6 +322,8 @@ bool WebSchedulerService::has_commands() {
return false;
}
#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);
@@ -355,13 +333,19 @@ bool WebSchedulerService::command(const char * name, const std::string & command
// shelly(get): http://<shellyIP>/relais/0?turn=on
// parse json
JsonDocument doc;
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
if (deserializeJson(doc, cmd) == DeserializationError::Ok) {
HTTPClient http;
int httpResult = 0;
String url = doc["url"];
if (http.begin(url)) {
// It's an HTTP call
String url = doc["url"] | "";
// for a GET with parameters replace commands with values
auto q = url.indexOf('?');
if (q != -1) {
auto s = url.substring(q + 1);
std::string v = s.c_str();
commands(v, false);
url.replace(s, v.c_str());
}
if (url.startsWith("http") && http.begin(url)) {
// add any given headers
for (JsonPair p : doc["header"].as<JsonObject>()) {
http.addHeader(p.key().c_str(), p.value().as<String>().c_str());
@@ -448,8 +432,6 @@ bool WebSchedulerService::onChange(const char * cmd) {
return false;
}
#include "shuntingYard.hpp"
// handle condition schedules, parse string stored in schedule.time field
void WebSchedulerService::condition() {
for (ScheduleItem & scheduleItem : *scheduleItems_) {

View File

@@ -68,6 +68,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
bool has_commands();
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, const ScheduleItem & scheduleItem);
void ha_reset() {
ha_registered_ = false;
}

View File

@@ -31,7 +31,7 @@
class Token {
public:
enum class Type {
enum class Type : uint8_t {
Unknown,
Number,
String,
@@ -332,12 +332,13 @@ bool isnum(const std::string & s) {
// replace commands like "<device>/<hc>/<cmd>" with its value"
std::string commands(std::string & expr) {
std::string commands(std::string & expr, bool quotes = true) {
for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) {
const char * d = emsesp::EMSdevice::device_type_2_device_name(device);
auto f = expr.find(d);
while (f != std::string::npos) {
auto e = expr.find_first_of(")=<>|&+-*!", f);
// entity names are alphanumeric or _
auto e = expr.find_first_not_of("/._abcdefghijklmnopqrstuvwxyz0123456789", f);
if (e == std::string::npos) {
e = expr.length();
}
@@ -361,7 +362,7 @@ std::string commands(std::string & expr) {
emsesp::Command::process(cmd_s.c_str(), true, input, output);
if (output.containsKey("api_data")) {
std::string data = output["api_data"].as<std::string>();
if (!isnum(data)) {
if (!isnum(data) && quotes) {
data.insert(data.begin(), '"');
data.insert(data.end(), '"');
}
@@ -596,7 +597,7 @@ std::string compute(const std::string & expr) {
auto expr_new = emsesp::Helpers::toLower(expr);
// search json with url:
auto f = expr_new.find_first_of("{");
auto f = expr_new.find_first_of('{');
while (f != std::string::npos) {
auto e = f + 1;
for (uint8_t i = 1; i > 0; e++) {
@@ -612,8 +613,8 @@ std::string compute(const std::string & expr) {
JsonDocument doc;
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
HTTPClient http;
String url = doc["url"];
if (http.begin(url)) {
String url = doc["url"] | "";
if (url.startsWith("http") && http.begin(url)) {
int httpResult = 0;
for (JsonPair p : doc["header"].as<JsonObject>()) {
http.addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
@@ -647,20 +648,22 @@ std::string compute(const std::string & expr) {
}
// positions: q-questionmark, c-colon
auto q = expr_new.find_first_of("?");
auto q = expr_new.find_first_of('?');
while (q != std::string::npos) {
// find corresponding colon
auto c1 = expr_new.find_first_of(":", q + 1);
auto q1 = expr_new.find_first_of("?", q + 1);
auto c1 = expr_new.find_first_of(':', q + 1);
auto q1 = expr_new.find_first_of('?', q + 1);
while (q1 < c1 && q1 != std::string::npos && c1 != std::string::npos) {
q1 = expr_new.find_first_of("?", q1 + 1);
c1 = expr_new.find_first_of(":", c1 + 1);
q1 = expr_new.find_first_of('?', q1 + 1);
c1 = expr_new.find_first_of(':', c1 + 1);
}
if (c1 == std::string::npos) {
return ""; // error: missing colon
}
std::string cond = calculate(expr_new.substr(0, q));
if (cond[0] == '1') {
if (cond.length() == 0) {
return "";
} else if (cond[0] == '1') {
expr_new.erase(c1); // remove second expression after colon
expr_new.erase(0, q + 1); // remove condition before questionmark
} else if (cond[0] == '0') {
@@ -668,7 +671,7 @@ std::string compute(const std::string & expr) {
} else {
return ""; // error
}
q = expr_new.find_first_of("?"); // search next instance
q = expr_new.find_first_of('?'); // search next instance
}
return calculate(expr_new);