schedule conditions chack numbers and strings, fix custom commands

This commit is contained in:
MichaelDvP
2024-06-18 18:23:08 +02:00
parent f76c5f6afe
commit bae5a11264
8 changed files with 297 additions and 168 deletions

View File

@@ -437,6 +437,19 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ui
return nullptr; // command not found return nullptr; // command not found
} }
void Command::erase_device_commands(const uint8_t device_type) {
if (cmdfunctions_.empty()) {
return;
}
auto it = cmdfunctions_.end();
do {
int i = it - cmdfunctions_.begin();
if (cmdfunctions_[i].device_type_==device_type) {
cmdfunctions_.erase(it);
}
} while (it-- > cmdfunctions_.begin());
}
void Command::erase_command(const uint8_t device_type, const char * cmd, uint8_t flag) { void Command::erase_command(const uint8_t device_type, const char * cmd, uint8_t flag) {
if ((cmd == nullptr) || (strlen(cmd) == 0) || (cmdfunctions_.empty())) { if ((cmd == nullptr) || (strlen(cmd) == 0) || (cmdfunctions_.empty())) {
return; return;

View File

@@ -126,6 +126,7 @@ class Command {
static Command::CmdFunction * find_command(const uint8_t device_type, const uint8_t device_id, const char * cmd, const uint8_t flag); static Command::CmdFunction * find_command(const uint8_t device_type, const uint8_t device_id, const char * cmd, const uint8_t flag);
static std::string tagged_cmd(const std::string & cmd, const uint8_t flag); static std::string tagged_cmd(const std::string & cmd, const uint8_t flag);
static void erase_device_commands(const uint8_t device_type);
static void erase_command(const uint8_t device_type, const char * cmd, uint8_t flag = CommandFlag::CMD_FLAG_DEFAULT); static void erase_command(const uint8_t device_type, const char * cmd, uint8_t flag = CommandFlag::CMD_FLAG_DEFAULT);
static void show(uuid::console::Shell & shell, uint8_t device_type, bool verbose); static void show(uuid::console::Shell & shell, uint8_t device_type, bool verbose);
static void show_devices(uuid::console::Shell & shell); static void show_devices(uuid::console::Shell & shell);

View File

@@ -124,6 +124,10 @@ bool System::command_allvalues(const char * value, const int8_t id, JsonObject o
device_output = output["Custom Entities"].to<JsonObject>(); device_output = output["Custom Entities"].to<JsonObject>();
EMSESP::webCustomEntityService.get_value_info(device_output, ""); EMSESP::webCustomEntityService.get_value_info(device_output, "");
// Scheduler
device_output = output["Scheduler"].to<JsonObject>();
EMSESP::webSchedulerService.get_value_info(device_output, "");
// Sensors // Sensors
device_output = output["Analog Sensors"].to<JsonObject>(); device_output = output["Analog Sensors"].to<JsonObject>();
EMSESP::analogsensor_.get_value_info(device_output, "values"); EMSESP::analogsensor_.get_value_info(device_output, "values");

View File

@@ -34,6 +34,8 @@ WebCustomEntityService::WebCustomEntityService(AsyncWebServer * server, FS * fs,
// load the settings when the service starts // load the settings when the service starts
void WebCustomEntityService::begin() { void WebCustomEntityService::begin() {
_fsPersistence.readFromFS(); _fsPersistence.readFromFS();
// save a local pointer to the item list
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems_ = &webEntity.customEntityItems; });
EMSESP::logger().info("Starting Custom Entity service"); EMSESP::logger().info("Starting Custom Entity service");
Mqtt::subscribe(EMSdevice::DeviceType::CUSTOM, "custom/#", nullptr); // use empty function callback Mqtt::subscribe(EMSdevice::DeviceType::CUSTOM, "custom/#", nullptr); // use empty function callback
} }
@@ -63,9 +65,7 @@ void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) {
// this loads the data into the internal class // this loads the data into the internal class
StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & webCustomEntity) { StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & webCustomEntity) {
// reset everything to start fresh // reset everything to start fresh
for (CustomEntityItem & entityItem : webCustomEntity.customEntityItems) { Command::erase_device_commands(EMSdevice::DeviceType::CUSTOM);
Command::erase_command(EMSdevice::DeviceType::CUSTOM, entityItem.name.c_str());
}
webCustomEntity.customEntityItems.clear(); webCustomEntity.customEntityItems.clear();
EMSESP::webCustomEntityService.ha_reset(); EMSESP::webCustomEntityService.ha_reset();
@@ -112,12 +112,12 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
webCustomEntity.customEntityItems.push_back(entityItem); // add to list webCustomEntity.customEntityItems.push_back(entityItem); // add to list
if (entityItem.writeable) { if (webCustomEntity.customEntityItems.back().writeable && !webCustomEntity.customEntityItems.back().name.empty()) {
Command::add( Command::add(
EMSdevice::DeviceType::CUSTOM, EMSdevice::DeviceType::CUSTOM,
webCustomEntity.customEntityItems.back().name.c_str(), webCustomEntity.customEntityItems.back().name.c_str(),
[webCustomEntity](const char * value, const int8_t id) { [webCustomEntity](const char * value, const int8_t id) {
return EMSESP::webCustomEntityService.command_setvalue(value, webCustomEntity.customEntityItems.back().name); return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name.c_str());
}, },
FL_(entity_cmd), FL_(entity_cmd),
CommandFlag::ADMIN_ONLY); CommandFlag::ADMIN_ONLY);
@@ -128,9 +128,8 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
} }
// set value by api command // set value by api command
bool WebCustomEntityService::command_setvalue(const char * value, const std::string name) { bool WebCustomEntityService::command_setvalue(const char * value, const int8_t id, const char * name) {
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; }); for (CustomEntityItem & entityItem : *customEntityItems_) {
for (CustomEntityItem & entityItem : *customEntityItems) {
if (Helpers::toLower(entityItem.name) == Helpers::toLower(name)) { if (Helpers::toLower(entityItem.name) == Helpers::toLower(name)) {
if (entityItem.ram == 1) { if (entityItem.ram == 1) {
entityItem.data = value; entityItem.data = value;
@@ -249,20 +248,18 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem en
// display all custom entities // display all custom entities
// adding each one, with UOM to a json object string // adding each one, with UOM to a json object string
void WebCustomEntityService::show_values(JsonObject output) { void WebCustomEntityService::show_values(JsonObject output) {
for (const CustomEntityItem & entity : *customEntityItems) { for (const CustomEntityItem & entity : *customEntityItems_) {
render_value(output, entity, false, false, true); // with add_uom render_value(output, entity, false, false, true); // with add_uom
} }
} }
// process json output for info/commands and value_info // process json output for info/commands and value_info
bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) { bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) {
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; });
// if it's commands... // if it's commands...
if (Helpers::toLower(cmd) == F_(commands)) { if (Helpers::toLower(cmd) == F_(commands)) {
output[F_(info)] = Helpers::translated_word(FL_(info_cmd)); output[F_(info)] = Helpers::translated_word(FL_(info_cmd));
output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd)); output[F_(commands)] = Helpers::translated_word(FL_(commands_cmd));
for (const auto & entity : *customEntityItems) { for (const auto & entity : *customEntityItems_) {
output[entity.name] = "custom entity"; output[entity.name] = "custom entity";
} }
return true; return true;
@@ -270,14 +267,14 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
// if no entries, return empty json // if no entries, return empty json
// https://github.com/emsesp/EMS-ESP32/issues/1297 // https://github.com/emsesp/EMS-ESP32/issues/1297
if (customEntityItems->size() == 0) { if (customEntityItems_->size() == 0) {
return true; return true;
} }
// if it's info or values... // if it's info or values...
if (strlen(cmd) == 0 || Helpers::toLower(cmd) == F_(values) || Helpers::toLower(cmd) == F_(info)) { if (strlen(cmd) == 0 || Helpers::toLower(cmd) == F_(values) || Helpers::toLower(cmd) == F_(info)) {
// list all names // list all names
for (const CustomEntityItem & entity : *customEntityItems) { for (const CustomEntityItem & entity : *customEntityItems_) {
render_value(output, entity); render_value(output, entity);
} }
return true; return true;
@@ -293,7 +290,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
attribute_s = breakp + 1; attribute_s = breakp + 1;
} }
for (const auto & entity : *customEntityItems) { for (const auto & entity : *customEntityItems_) {
if (Helpers::toLower(entity.name) == command_s) { if (Helpers::toLower(entity.name) == command_s) {
output["name"] = entity.name; output["name"] = entity.name;
output["ram"] = entity.ram; output["ram"] = entity.ram;
@@ -370,12 +367,11 @@ void WebCustomEntityService::publish(const bool force) {
return; return;
} }
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; }); if (customEntityItems_->size() == 0) {
if (customEntityItems->size() == 0) {
return; return;
} }
if (Mqtt::publish_single() && force) { if (Mqtt::publish_single() && force) {
for (const CustomEntityItem & entityItem : *customEntityItems) { for (const CustomEntityItem & entityItem : *customEntityItems_) {
publish_single(entityItem); publish_single(entityItem);
} }
} }
@@ -384,7 +380,7 @@ void WebCustomEntityService::publish(const bool force) {
JsonObject output = doc.to<JsonObject>(); JsonObject output = doc.to<JsonObject>();
bool ha_created = ha_registered_; bool ha_created = ha_registered_;
for (const CustomEntityItem & entityItem : *customEntityItems) { for (const CustomEntityItem & entityItem : *customEntityItems_) {
render_value(output, entityItem); render_value(output, entityItem);
// create HA config // create HA config
if (Mqtt::ha_enabled() && !ha_registered_) { if (Mqtt::ha_enabled() && !ha_registered_) {
@@ -461,15 +457,14 @@ void WebCustomEntityService::publish(const bool force) {
// count only entities with valid value or command to show in dashboard // count only entities with valid value or command to show in dashboard
uint8_t WebCustomEntityService::count_entities() { uint8_t WebCustomEntityService::count_entities() {
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; }); if (customEntityItems_->size() == 0) {
if (customEntityItems->size() == 0) {
return 0; return 0;
} }
JsonDocument doc; JsonDocument doc;
JsonObject output = doc.to<JsonObject>(); JsonObject output = doc.to<JsonObject>();
uint8_t count = 0; uint8_t count = 0;
for (const CustomEntityItem & entity : *customEntityItems) { for (const CustomEntityItem & entity : *customEntityItems_) {
render_value(output, entity); render_value(output, entity);
count += (output.containsKey(entity.name) || entity.writeable) ? 1 : 0; count += (output.containsKey(entity.name) || entity.writeable) ? 1 : 0;
} }
@@ -478,9 +473,8 @@ uint8_t WebCustomEntityService::count_entities() {
} }
uint8_t WebCustomEntityService::has_commands() { uint8_t WebCustomEntityService::has_commands() {
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; });
uint8_t count = 0; uint8_t count = 0;
for (const CustomEntityItem & entity : *customEntityItems) { for (const CustomEntityItem & entity : *customEntityItems_) {
count += entity.writeable ? 1 : 0; count += entity.writeable ? 1 : 0;
} }
@@ -489,12 +483,10 @@ uint8_t WebCustomEntityService::has_commands() {
// send to dashboard, msgpack don't like serialized, use number // send to dashboard, msgpack don't like serialized, use number
void WebCustomEntityService::generate_value_web(JsonObject output) { void WebCustomEntityService::generate_value_web(JsonObject output) {
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; });
output["label"] = (std::string) "Custom Entities"; output["label"] = (std::string) "Custom Entities";
JsonArray data = output["data"].to<JsonArray>(); JsonArray data = output["data"].to<JsonArray>();
uint8_t index = 0; uint8_t index = 0;
for (const CustomEntityItem & entity : *customEntityItems) { for (const CustomEntityItem & entity : *customEntityItems_) {
JsonObject obj = data.add<JsonObject>(); // create the object, we know there is a value JsonObject obj = data.add<JsonObject>(); // create the object, we know there is a value
obj["id"] = "00" + entity.name; obj["id"] = "00" + entity.name;
obj["u"] = entity.uom; obj["u"] = entity.uom;
@@ -562,10 +554,9 @@ void WebCustomEntityService::generate_value_web(JsonObject output) {
// fetch telegram, called from emsesp::fetch // fetch telegram, called from emsesp::fetch
void WebCustomEntityService::fetch() { void WebCustomEntityService::fetch() {
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; });
const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3, 4}; const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3, 4};
for (auto & entity : *customEntityItems) { for (auto & entity : *customEntityItems_) {
if (entity.device_id > 0 && entity.type_id > 0) { // ths excludes also RAM type if (entity.device_id > 0 && entity.type_id > 0) { // ths excludes also RAM type
bool needFetch = true; bool needFetch = true;
uint8_t fetchblock = entity.type_id > 0x0FF ? 25 : 27; uint8_t fetchblock = entity.type_id > 0x0FF ? 25 : 27;
@@ -593,10 +584,9 @@ void WebCustomEntityService::fetch() {
// called on process telegram, read from telegram // called on process telegram, read from telegram
bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram) { bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram) {
bool has_change = false; bool has_change = false;
EMSESP::webCustomEntityService.read([&](WebCustomEntity & webEntity) { customEntityItems = &webEntity.customEntityItems; });
// read-length of BOOL, INT8, UINT8, INT16, UINT16, UINT24, TIME, UINT32 // read-length of BOOL, INT8, UINT8, INT16, UINT16, UINT24, TIME, UINT32
const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3, 4}; const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3, 4};
for (auto & entity : *customEntityItems) { for (auto & entity : *customEntityItems_) {
if (entity.value_type == DeviceValueType::STRING && telegram->type_id == entity.type_id && telegram->src == entity.device_id if (entity.value_type == DeviceValueType::STRING && telegram->type_id == entity.type_id && telegram->src == entity.device_id
&& telegram->offset <= entity.offset && (telegram->offset + telegram->message_length) >= (entity.offset + (uint8_t)entity.factor)) { && telegram->offset <= entity.offset && (telegram->offset + telegram->message_length) >= (entity.offset + (uint8_t)entity.factor)) {
auto data = Helpers::data_to_hex(telegram->message_data, (uint8_t)entity.factor); auto data = Helpers::data_to_hex(telegram->message_data, (uint8_t)entity.factor);

View File

@@ -43,8 +43,7 @@ class CustomEntityItem {
class WebCustomEntity { class WebCustomEntity {
public: public:
std::vector<CustomEntityItem> customEntityItems; std::list<CustomEntityItem> customEntityItems;
// std::list<CustomEntityItem> customEntityItems;
static void read(WebCustomEntity & webEntity, JsonObject root); static void read(WebCustomEntity & webEntity, JsonObject root);
static StateUpdateResult update(JsonObject root, WebCustomEntity & webEntity); static StateUpdateResult update(JsonObject root, WebCustomEntity & webEntity);
@@ -57,7 +56,7 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
void begin(); void begin();
void publish_single(const CustomEntityItem & entity); void publish_single(const CustomEntityItem & entity);
void publish(const bool force = false); void publish(const bool force = false);
bool command_setvalue(const char * value, const std::string name); bool command_setvalue(const char * value, const int8_t id, const char * name);
bool get_value_info(JsonObject output, const char * cmd); bool get_value_info(JsonObject output, const char * cmd);
bool get_value(std::shared_ptr<const Telegram> telegram); bool get_value(std::shared_ptr<const Telegram> telegram);
void fetch(); void fetch();
@@ -79,8 +78,7 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
HttpEndpoint<WebCustomEntity> _httpEndpoint; HttpEndpoint<WebCustomEntity> _httpEndpoint;
FSPersistence<WebCustomEntity> _fsPersistence; FSPersistence<WebCustomEntity> _fsPersistence;
std::vector<CustomEntityItem> * customEntityItems; // pointer to the list of entity items std::list<CustomEntityItem> * customEntityItems_; // pointer to the list of entity items
// std::list<CustomEntityItem> * customEntityItems; // pointer to the list of entity items
bool ha_registered_ = false; bool ha_registered_ = false;
}; };

View File

@@ -57,11 +57,8 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) {
// call on initialization and also when the Schedule web page is saved // call on initialization and also when the Schedule web page is saved
// this loads the data into the internal class // this loads the data into the internal class
StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) { StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) {
for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
Command::erase_command(EMSdevice::DeviceType::SCHEDULER, scheduleItem.name.c_str());
}
// reset the list // reset the list
Command::erase_device_commands(EMSdevice::DeviceType::SCHEDULER);
webScheduler.scheduleItems.clear(); webScheduler.scheduleItems.clear();
EMSESP::webSchedulerService.ha_reset(); EMSESP::webSchedulerService.ha_reset();
@@ -88,7 +85,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
EMSdevice::DeviceType::SCHEDULER, EMSdevice::DeviceType::SCHEDULER,
webScheduler.scheduleItems.back().name.c_str(), webScheduler.scheduleItems.back().name.c_str(),
[webScheduler](const char * value, const int8_t id) { [webScheduler](const char * value, const int8_t id) {
return EMSESP::webSchedulerService.command_setvalue(value, webScheduler.scheduleItems.back().name); return EMSESP::webSchedulerService.command_setvalue(value, id, webScheduler.scheduleItems.back().name.c_str());
}, },
FL_(schedule_cmd), FL_(schedule_cmd),
CommandFlag::ADMIN_ONLY); CommandFlag::ADMIN_ONLY);
@@ -102,20 +99,20 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
} }
// set active by api command // set active by api command
bool WebSchedulerService::command_setvalue(const char * value, const std::string name) { bool WebSchedulerService::command_setvalue(const char * value, const int8_t id, const char * name) {
bool v; bool v;
if (!Helpers::value2bool(value, v)) { if (!Helpers::value2bool(value, v)) {
return false; return false;
} }
for (ScheduleItem & scheduleItem : *scheduleItems_) { for (ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.name == name) { if (Helpers::toLower(scheduleItem.name) == Helpers::toLower(name)) {
if (scheduleItem.active == v) { if (scheduleItem.active == v) {
return true; return true;
} }
scheduleItem.active = v; scheduleItem.active = v;
publish_single(name.c_str(), v); publish_single(name, v);
if (EMSESP::mqtt_.get_publish_onchange(0)) { if (EMSESP::mqtt_.get_publish_onchange(0)) {
publish(); publish();
@@ -212,7 +209,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
// publish single value // publish single value
void WebSchedulerService::publish_single(const char * name, const bool state) { void WebSchedulerService::publish_single(const char * name, const bool state) {
if (!Mqtt::publish_single() || name == nullptr || name[0] == '\0') { if (!Mqtt::enabled() || !Mqtt::publish_single() || name == nullptr || name[0] == '\0') {
return; return;
} }
@@ -386,7 +383,7 @@ void WebSchedulerService::condition() {
#ifdef EMESESP_DEBUG #ifdef EMESESP_DEBUG
emsesp::EMSESP::logger().debug("condition match: %s", match.c_str()); emsesp::EMSESP::logger().debug("condition match: %s", match.c_str());
#endif #endif
if (!match.empty() && match.c_str()[0] == '1') { if (!match.empty() && match[0] == '1') {
if (scheduleItem.retry_cnt == 0xFF) { // default unswitched if (scheduleItem.retry_cnt == 0xFF) { // default unswitched
scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str()) ? 1 : 0xFF; scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str()) ? 1 : 0xFF;
} }

View File

@@ -58,7 +58,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
void publish_single(const char * name, const bool state); void publish_single(const char * name, const bool state);
void publish(const bool force = false); void publish(const bool force = false);
bool has_commands(); bool has_commands();
bool command_setvalue(const char * value, const std::string name); bool command_setvalue(const char * value, const int8_t id, const char * name);
bool get_value_info(JsonObject output, const char * cmd); bool get_value_info(JsonObject output, const char * cmd);
void ha_reset() { void ha_reset() {
ha_registered_ = false; ha_registered_ = false;

View File

@@ -34,24 +34,26 @@ class Token {
enum class Type { enum class Type {
Unknown, Unknown,
Number, Number,
String,
Operator, Operator,
Compare,
Logic,
Unary,
LeftParen, LeftParen,
RightParen, RightParen,
}; };
Token(Type type, const std::string & s, int8_t precedence = -1, bool rightAssociative = false, bool unary = false) Token(Type type, const std::string & s, int8_t precedence = -1, bool rightAssociative = false)
: type{type} : type{type}
, str(s) , str(s)
, precedence{precedence} , precedence{precedence}
, rightAssociative{rightAssociative} , rightAssociative{rightAssociative} {
, unary{unary} {
} }
const Type type; const Type type;
const std::string str; const std::string str;
const int8_t precedence; const int8_t precedence;
const bool rightAssociative; const bool rightAssociative;
const bool unary;
}; };
std::deque<Token> exprToTokens(const std::string & expr) { std::deque<Token> exprToTokens(const std::string & expr) {
@@ -60,22 +62,48 @@ std::deque<Token> exprToTokens(const std::string & expr) {
for (const auto * p = expr.c_str(); *p; ++p) { for (const auto * p = expr.c_str(); *p; ++p) {
if (isblank(*p)) { if (isblank(*p)) {
// do nothing // do nothing
} else if ((*p >= 'a' && *p <= 'z')) { } else if (*p >= 'a' && *p <= 'z') {
tokens.clear(); const auto * b = p;
return tokens; while ((*p >= 'a' && *p <= 'z') || (*p == '_')) {
++p;
}
const auto s = std::string(b, p);
tokens.push_back(Token{Token::Type::String, s, -2});
--p;
} else if (*p == '"') {
++p;
const auto * b = p;
while (*p && *p != '"') {
++p;
}
const auto s = std::string(b, p);
tokens.push_back(Token{Token::Type::String, s, -3});
if (*p == '\0') {
--p;
}
} else if (*p == '\'') {
++p;
const auto * b = p;
while (*p && *p != '\'') {
++p;
}
const auto s = std::string(b, p);
tokens.push_back(Token{Token::Type::String, s, -3});
if (*p == '\0') {
--p;
}
} else if (isdigit(*p)) { } else if (isdigit(*p)) {
const auto * b = p; const auto * b = p;
while (isdigit(*p) || *p == '.') { while (isdigit(*p) || *p == '.') {
++p; ++p;
} }
const auto s = std::string(b, p); const auto s = std::string(b, p);
tokens.push_back(Token{Token::Type::Number, s}); tokens.push_back(Token{Token::Type::Number, s, -4});
--p; --p;
} else { } else {
Token::Type token = Token::Type::Operator; Token::Type token = Token::Type::Operator;
int8_t precedence = -1; int8_t precedence = -1;
bool rightAssociative = false; bool rightAssociative = false;
bool unary = false;
char c = *p; char c = *p;
switch (c) { switch (c) {
default: default:
@@ -106,11 +134,16 @@ std::deque<Token> exprToTokens(const std::string & expr) {
case '-': case '-':
// If current token is '-' // If current token is '-'
// and if it is the first token, or preceded by another operator, or left-paren, // and if it is the first token, or preceded by another operator, or left-paren,
if (tokens.empty() || tokens.back().type == Token::Type::Operator || tokens.back().type == Token::Type::LeftParen) { if (tokens.empty() || tokens.back().type == Token::Type::Operator || tokens.back().type == Token::Type::Compare
|| tokens.back().type == Token::Type::Logic || tokens.back().type == Token::Type::Unary || tokens.back().type == Token::Type::LeftParen) {
// it's unary '-' // it's unary '-'
// note#1 : 'm' is a special operator name for unary '-' // note#1 : 'm' is a special operator name for unary '-'
// note#2 : It has highest precedence than any of the infix operators // note#2 : It has highest precedence than any of the infix operators
unary = true; if (!tokens.empty() && tokens.back().str[0] == 'm') { // double unary minus
tokens.pop_back();
continue;
}
token = Token::Type::Unary;
c = 'm'; c = 'm';
precedence = 5; precedence = 5;
} else { } else {
@@ -122,15 +155,23 @@ std::deque<Token> exprToTokens(const std::string & expr) {
if (p[1] == '&') if (p[1] == '&')
++p; ++p;
precedence = 0; precedence = 0;
token = Token::Type::Logic;
break; break;
case '|': case '|':
if (p[1] == '|') if (p[1] == '|')
++p; ++p;
precedence = 0; precedence = 0;
token = Token::Type::Logic;
break; break;
case '!': case '!':
unary = true; if (p[1] == '=') {
++p;
precedence = 1; precedence = 1;
token = Token::Type::Compare;
} else {
precedence = 1;
token = Token::Type::Unary;
}
break; break;
case '<': case '<':
if (p[1] == '=') { if (p[1] == '=') {
@@ -138,6 +179,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
c = '{'; c = '{';
} }
precedence = 1; precedence = 1;
token = Token::Type::Compare;
break; break;
case '>': case '>':
if (p[1] == '=') { if (p[1] == '=') {
@@ -145,15 +187,17 @@ std::deque<Token> exprToTokens(const std::string & expr) {
c = '}'; c = '}';
} }
precedence = 1; precedence = 1;
token = Token::Type::Compare;
break; break;
case '=': case '=':
if (p[1] == '=') if (p[1] == '=')
++p; ++p;
precedence = 1; precedence = 1;
token = Token::Type::Compare;
break; break;
} }
const auto s = std::string(1, c); const auto s = std::string(1, c);
tokens.push_back(Token{token, s, precedence, rightAssociative, unary}); tokens.push_back(Token{token, s, precedence, rightAssociative});
} }
} }
@@ -170,10 +214,14 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
// Read a token // Read a token
switch (token.type) { switch (token.type) {
case Token::Type::Number: case Token::Type::Number:
case Token::Type::String:
// If the token is a number, then add it to the output queue // If the token is a number, then add it to the output queue
queue.push_back(token); queue.push_back(token);
break; break;
case Token::Type::Unary:
case Token::Type::Compare:
case Token::Type::Logic:
case Token::Type::Operator: { case Token::Type::Operator: {
// If the token is operator, o1, then: // If the token is operator, o1, then:
const auto o1 = token; const auto o1 = token;
@@ -209,9 +257,8 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
stack.push_back(token); stack.push_back(token);
break; break;
case Token::Type::RightParen: case Token::Type::RightParen: {
// If token is right parenthesis: // If token is right parenthesis:
{
bool match = false; bool match = false;
// Until the token at the top of the stack // Until the token at the top of the stack
@@ -233,8 +280,7 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
// Pop the left parenthesis from the stack, // Pop the left parenthesis from the stack,
// but not onto the output queue. // but not onto the output queue.
stack.pop_back(); stack.pop_back();
} } break;
break;
default: default:
return {}; return {};
@@ -263,7 +309,7 @@ std::string commands(std::string & expr) {
const char * d = emsesp::EMSdevice::device_type_2_device_name(device); const char * d = emsesp::EMSdevice::device_type_2_device_name(device);
auto f = expr.find(d); auto f = expr.find(d);
while (f != std::string::npos) { while (f != std::string::npos) {
auto e = expr.find_first_of(" )=<>|&+-*\0", f); auto e = expr.find_first_of(" )=<>|&+-*", f);
if (e == std::string::npos) { if (e == std::string::npos) {
e = expr.length(); e = expr.length();
} }
@@ -284,14 +330,15 @@ std::string commands(std::string & expr) {
emsesp::Command::process(cmd_s.c_str(), true, input, output); emsesp::Command::process(cmd_s.c_str(), true, input, output);
if (output.containsKey("api_data")) { if (output.containsKey("api_data")) {
std::string data = output["api_data"].as<std::string>(); std::string data = output["api_data"].as<std::string>();
if (data == "true" || data == "ON" || data == "on") { // set strings in quotations for something like "3-way-valve"
data = "1"; if (isdigit(data[0] == 0 && data[0] != '-')) {
} data.insert(data.begin(), '"');
if (data == "false" || data == "OFF" || data == "off") { data.insert(data.end(), '"');
data = "0";
} }
expr.replace(f, l, data); expr.replace(f, l, data);
e = f + data.length(); e = f + data.length();
} else {
return expr = "";
} }
f = expr.find(d, e); f = expr.find(d, e);
} }
@@ -299,33 +346,49 @@ std::string commands(std::string & expr) {
return expr; return expr;
} }
int islogic(const std::string & s) {
if (s[0] == '1' || s == "on" || s == "ON" || s == "true") {
return 1;
}
if (s[0] == '0' || s == "off" || s == "OFF" || s == "false") {
return 0;
}
return 0;
}
bool isnum(const std::string & s) {
if (isdigit(s[0]) || (s[0] == '-' && isdigit(s[1]))) {
return true;
}
return false;
}
std::string compute(const std::string & expr) { std::string compute(const std::string & expr) {
auto expr_new = emsesp::Helpers::toLower(expr); auto expr_new = emsesp::Helpers::toLower(expr);
#ifdef EMESESP_DEBUG
emsesp::EMSESP::logger().debug("calculate: %s", expr_new.c_str());
#endif
commands(expr_new); commands(expr_new);
#ifdef EMESESP_DEBUG // emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
emsesp::EMSESP::logger().debug("calculate: %s", expr_new.c_str());
#endif
const auto tokens = exprToTokens(expr_new); const auto tokens = exprToTokens(expr_new);
if (tokens.empty()) { if (tokens.empty()) {
return "Error: no tokens"; return "";
} }
auto queue = shuntingYard(tokens); auto queue = shuntingYard(tokens);
std::vector<double> stack; if (queue.empty()) {
return "";
}
std::vector<std::string> stack;
while (!queue.empty()) { while (!queue.empty()) {
const auto token = queue.front(); const auto token = queue.front();
queue.pop_front(); queue.pop_front();
switch (token.type) { switch (token.type) {
case Token::Type::Number: case Token::Type::Number:
stack.push_back(std::stod(token.str)); case Token::Type::String:
stack.push_back(token.str);
break; break;
case Token::Type::Unary: {
case Token::Type::Operator: { if (stack.empty()) {
if (token.unary) { return "";
// unray operators }
const auto rhs = stack.back(); const auto rhs = stack.back();
stack.pop_back(); stack.pop_back();
switch (token.str[0]) { switch (token.str[0]) {
@@ -333,63 +396,129 @@ std::string compute(const std::string & expr) {
return ""; return "";
break; break;
case 'm': // Special operator name for unary '-' case 'm': // Special operator name for unary '-'
stack.push_back(-rhs); if (!isnum(rhs)) {
return "";
}
stack.push_back(std::to_string(-1 * std::stod(rhs)));
break; break;
case '!': case '!':
stack.push_back(!(int)rhs); stack.push_back(islogic(rhs) == 0 ? "1" : "0");
break; break;
} }
} else {
// binary operators } break;
case Token::Type::Compare: {
if (stack.size() < 2) {
return "";
}
const auto rhs = stack.back(); const auto rhs = stack.back();
stack.pop_back(); stack.pop_back();
const auto lhs = stack.back(); const auto lhs = stack.back();
stack.pop_back(); stack.pop_back();
switch (token.str[0]) {
default:
return "";
break;
case '<':
if (isnum(rhs) && isnum(lhs)) {
stack.push_back((std::stod(lhs) < std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs < rhs) ? "1" : "0");
break;
case '{':
if (isnum(rhs) && isnum(lhs)) {
stack.push_back((std::stod(lhs) <= std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs <= rhs) ? "1" : "0");
break;
case '>':
if (isnum(rhs) && isnum(lhs)) {
stack.push_back((std::stod(lhs) > std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs > rhs) ? "1" : "0");
break;
case '}':
if (isnum(rhs) && isnum(lhs)) {
stack.push_back((std::stod(lhs) >= std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs >= rhs) ? "1" : "0");
break;
case '=':
if (isnum(rhs) && isnum(lhs)) {
stack.push_back((std::stod(lhs) == std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs == rhs) ? "1" : "0");
break;
case '!':
if (isnum(rhs) && isnum(lhs)) {
stack.push_back((std::stod(lhs) != std::stod(rhs)) ? "1" : "0");
break;
}
stack.push_back((lhs != rhs) ? "1" : "0");
break;
}
} break;
case Token::Type::Logic: {
// binary operators
if (stack.size() < 2) {
return "";
}
const auto rhs = islogic(stack.back());
stack.pop_back();
const auto lhs = islogic(stack.back());
stack.pop_back();
switch (token.str[0]) {
default:
return "";
break;
case '&':
stack.push_back((lhs && rhs) ? "1" : "0");
break;
case '|':
stack.push_back((lhs || rhs) ? "1" : "0");
break;
}
} break;
case Token::Type::Operator: {
// binary operators
if (stack.empty() || !isnum(stack.back())) {
return "";
}
const auto rhs = std::stod(stack.back());
stack.pop_back();
if (stack.empty() || !isnum(stack.back())) {
return "";
}
const auto lhs = std::stod(stack.back());
stack.pop_back();
switch (token.str[0]) { switch (token.str[0]) {
default: default:
return ""; return "";
break; break;
case '^': case '^':
stack.push_back(static_cast<int>(pow(lhs, rhs))); stack.push_back(std::to_string(pow(lhs, rhs)));
break; break;
case '*': case '*':
stack.push_back(lhs * rhs); stack.push_back(std::to_string(lhs * rhs));
break; break;
case '/': case '/':
stack.push_back(lhs / rhs); stack.push_back(std::to_string(lhs / rhs));
break; break;
case '%': case '%':
stack.push_back((int)lhs % (int)rhs); stack.push_back(std::to_string(static_cast<int>(lhs) % static_cast<int>(rhs)));
break; break;
case '+': case '+':
stack.push_back(lhs + rhs); stack.push_back(std::to_string(lhs + rhs));
break; break;
case '-': case '-':
stack.push_back(lhs - rhs); stack.push_back(std::to_string(lhs - rhs));
break; break;
case '&':
stack.push_back(((int)lhs && (int)rhs) ? 1 : 0);
break;
case '|':
stack.push_back(((int)lhs || (int)rhs) ? 1 : 0);
break;
case '<':
stack.push_back(((int)lhs < (int)rhs) ? 1 : 0);
break;
case '{':
stack.push_back(((int)lhs <= (int)rhs) ? 1 : 0);
break;
case '>':
stack.push_back(((int)lhs > (int)rhs) ? 1 : 0);
break;
case '}':
stack.push_back(((int)lhs >= (int)rhs) ? 1 : 0);
break;
case '=':
stack.push_back(((int)lhs == (int)rhs) ? 1 : 0);
break;
}
} }
} break; } break;
@@ -397,8 +526,5 @@ std::string compute(const std::string & expr) {
return ""; return "";
} }
} }
if (stack.back() == (int)stack.back()) { return stack.back();
return (std::to_string((int)stack.back()));
}
return std::to_string(stack.back());
} }