diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index 77c88a093..44893d958 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -102,7 +102,7 @@ void AnalogSensor::start(const bool factory_settings) { Command::add( EMSdevice::DeviceType::ANALOGSENSOR, F_(setvalue), - [&](const char * value, const int8_t id) { return command_setvalue(value, id); }, + [&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, id); }, FL_(setiovalue_cmd), CommandFlag::ADMIN_ONLY); @@ -195,7 +195,7 @@ void AnalogSensor::reload(bool get_nvs) { Command::add( EMSdevice::DeviceType::ANALOGSENSOR, sensor.name, - [&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); }, + [&](const char * value, const int8_t id, JsonObject output) { return command_setvalue(value, sensor.gpio); }, sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter) : sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out) : sensor.type == AnalogType::RGB ? FL_(RGB) diff --git a/src/core/command.cpp b/src/core/command.cpp index 359288f9a..a0e9f41c6 100644 --- a/src/core/command.cpp +++ b/src/core/command.cpp @@ -443,16 +443,13 @@ uint8_t Command::call(const uint8_t device_type, const char * command, const cha // call the function based on command function type // commands return true or false only (bool) uint8_t return_code = CommandRet::OK; - if (cf->cmdfunction_json_) { - // handle commands that report back a JSON body - return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR; - } else if (cf->cmdfunction_) { - // if it's a read only command and we're trying to set a value, return an error - if (!single_command && EMSESP::cmd_is_readonly(device_type, device_id, cmd, id)) { + if (cf->cmdfunction_) { + // JSON-output commands bypass the readonly check; for the rest, reject a write to a read-only entity + if (!cf->has_json_output_ && !single_command && EMSESP::cmd_is_readonly(device_type, device_id, cmd, id)) { return_code = CommandRet::INVALID; // error on readonly or invalid hc } else { - // call the command... - return_code = ((cf->cmdfunction_)(value, id)) ? CommandRet::OK : CommandRet::ERROR; + // call the command (the output object is ignored by non-JSON commands) + return_code = ((cf->cmdfunction_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR; } } @@ -506,7 +503,7 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const char flags |= CommandFlag::HIDDEN; } - cmdfunctions_.emplace_back(device_type, device_id, flags, cmd, cb, nullptr, description); // callback for json is nullptr + cmdfunctions_.emplace_back(device_type, device_id, flags, false, cmd, cb, description); // not a json-output command } // add a command with no json output @@ -516,13 +513,14 @@ void Command::add(const uint8_t device_type, const char * cmd, const cmd_functio } // add a command to the list, which does return a json object as output -void Command::add(const uint8_t device_type, const char * cmd, const cmd_json_function_p cb, const char * const * description, uint8_t flags) { +// these commands bypass the readonly check (they are actions, not entity setters) +void Command::add_json(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags) { // if the command already exists for that device type don't add it if (find_command(device_type, 0, cmd, flags) != nullptr) { return; } - cmdfunctions_.emplace_back(device_type, 0, flags, cmd, nullptr, cb, description); // callback for json is included + cmdfunctions_.emplace_back(device_type, 0, flags, true, cmd, cb, description); // json-output command } // see if a command exists for that device type diff --git a/src/core/command.h b/src/core/command.h index 2c9cffc91..2644a29b4 100644 --- a/src/core/command.h +++ b/src/core/command.h @@ -54,33 +54,32 @@ enum CommandRet : uint8_t { NO_VALUE // 6 - no value }; -using cmd_function_p = std::function; -using cmd_json_function_p = std::function; +using cmd_function_p = std::function; class Command { public: struct CmdFunction { - uint8_t device_type_; // DeviceType:: + uint8_t device_type_; // DeviceType:: uint8_t device_id_; - uint8_t flags_; // mqtt flags for command subscriptions + uint8_t flags_; // mqtt flags for command subscriptions + bool has_json_output_; // true if the command writes JSON output; such commands bypass the readonly check const char * cmd_; cmd_function_p cmdfunction_; - cmd_json_function_p cmdfunction_json_; const char * const * description_; - CmdFunction(const uint8_t device_type, - const uint8_t device_id, - const uint8_t flags, - const char * cmd, - const cmd_function_p cmdfunction, - const cmd_json_function_p cmdfunction_json, - const char * const * description) + CmdFunction(const uint8_t device_type, + const uint8_t device_id, + const uint8_t flags, + const bool has_json_output, + const char * cmd, + const cmd_function_p cmdfunction, + const char * const * description) : device_type_(device_type) , device_id_(device_id) , flags_(flags) + , has_json_output_(has_json_output) , cmd_(cmd) , cmdfunction_(cmdfunction) - , cmdfunction_json_(cmdfunction_json) , description_(description) { } @@ -116,12 +115,13 @@ class Command { // same for system/temperature/analog devices static void add(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT); - // callback function taking value, id and a json object for its output - static void add(const uint8_t device_type, - const char * cmd, - const cmd_json_function_p cb, - const char * const * description, - uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT); + // command that writes a JSON object as its output; bypasses the readonly check + static void + add_json(const uint8_t device_type, const char * cmd, const cmd_function_p cb, const char * const * description, uint8_t flags = CommandFlag::CMD_FLAG_DEFAULT); + + static void reserve(size_t num) { + cmdfunctions_.reserve(num); + } static void show_all(uuid::console::Shell & shell); static Command::CmdFunction * find_command(const uint8_t device_type, const uint8_t device_id, const char * cmd, const uint8_t flag); diff --git a/src/core/emsesp.h b/src/core/emsesp.h index 5ec896b9b..e59db86ae 100644 --- a/src/core/emsesp.h +++ b/src/core/emsesp.h @@ -98,7 +98,9 @@ class Module {}; // forward declaration return +[](emsesp::EMSdevice * dev, const std::shared_ptr & t) { static_cast(dev)->__f(t); }; \ }()) -#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id) { return __f(value, id); } // for Command Function callbacks Command::cmd_function_p +// for Command Function callbacks (Command::cmd_function_p). The unified callback takes a JsonObject +// output which entity/setter commands ignore. +#define MAKE_CF_CB(__f) [&](const char * value, const int8_t id, JsonObject output) { return __f(value, id); } namespace emsesp { diff --git a/src/core/mqtt.cpp b/src/core/mqtt.cpp index 25113bfdb..5c77f7b9c 100644 --- a/src/core/mqtt.cpp +++ b/src/core/mqtt.cpp @@ -378,7 +378,7 @@ void Mqtt::start() { initialized_ = true; // add the 'publish' command ('call system publish' in console or via API) - Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, FL_(publish_cmd)); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), MAKE_CF_CB(System::command_publish), FL_(publish_cmd)); #if defined(EMSESP_STANDALONE) Mqtt::on_connect(); // simulate an MQTT connection diff --git a/src/core/shower.cpp b/src/core/shower.cpp index 105b155bb..cbbafdf61 100644 --- a/src/core/shower.cpp +++ b/src/core/shower.cpp @@ -33,7 +33,7 @@ void Shower::start() { shower_min_duration_ = settings.shower_min_duration; // in seconds }); - Command::add( + Command::add_json( EMSdevice::DeviceType::BOILER, F_(coldshot), [&](const char * value, const int8_t id, JsonObject output) { diff --git a/src/core/system.cpp b/src/core/system.cpp index 95e5019a9..208e657c5 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -990,22 +990,24 @@ void System::system_check() { // commands - takes static function pointers // can be called via Console using 'call system ' void System::commands_init() { - Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), System::command_read, FL_(read_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(sendmail), System::command_sendmail, FL_(sendmail_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), System::command_format, FL_(format_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(txpause), System::command_txpause, FL_(txpause_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(led), System::command_led, FL_(led_cmd), CommandFlag::ADMIN_ONLY); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, FL_(watch_cmd)); - Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd)); + // Command::reserve(200); + + Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), MAKE_CF_CB(System::command_read), FL_(read_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), MAKE_CF_CB(System::command_send), FL_(send_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), MAKE_CF_CB(System::command_fetch), FL_(fetch_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(sendmail), MAKE_CF_CB(System::command_sendmail), FL_(sendmail_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), MAKE_CF_CB(System::command_restart), FL_(restart_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), MAKE_CF_CB(System::command_format), FL_(format_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(txpause), MAKE_CF_CB(System::command_txpause), FL_(txpause_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(led), MAKE_CF_CB(System::command_led), FL_(led_cmd), CommandFlag::ADMIN_ONLY); + Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), MAKE_CF_CB(System::command_watch), FL_(watch_cmd)); + Command::add_json(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd)); #if defined(EMSESP_TEST) - Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), System::command_test, FL_(test_cmd)); + Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), MAKE_CF_CB(System::command_test), FL_(test_cmd)); #endif // these commands will return data in JSON format - Command::add(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response)); + Command::add_json(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response)); // MQTT subscribe "ems-esp/system/#" Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback diff --git a/src/web/WebCommandService.cpp b/src/web/WebCommandService.cpp index 1ead8a808..8da74bb35 100644 --- a/src/web/WebCommandService.cpp +++ b/src/web/WebCommandService.cpp @@ -197,7 +197,7 @@ StateUpdateResult WebCommands::update(JsonObject root, WebCommands & webCommands Command::add( EMSdevice::DeviceType::COMMAND, webCommands.commandItems.back().name, - [name = std::string(webCommands.commandItems.back().name)](const char * value, const int8_t id) { + [name = std::string(webCommands.commandItems.back().name)](const char * value, const int8_t id, JsonObject output) { return EMSESP::webCommandService.dispatchCommand(name.c_str(), value); // value is optional }, FL_(command_cmd), @@ -442,7 +442,7 @@ void WebCommandService::load_test_data() { Command::add( EMSdevice::DeviceType::COMMAND, item.name, - [name = std::string(item.name)](const char * value, const int8_t id) { + [name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) { return EMSESP::webCommandService.dispatchCommand(name.c_str(), value); }, FL_(command_cmd), diff --git a/src/web/WebCommandService.h b/src/web/WebCommandService.h index 6de4ad429..4077a39cf 100644 --- a/src/web/WebCommandService.h +++ b/src/web/WebCommandService.h @@ -34,7 +34,7 @@ #define EMSESP_COMMAND_RUNNING_CORE 1 #endif #ifndef EMSESP_COMMAND_STACKSIZE -#define EMSESP_COMMAND_STACKSIZE 8192 +#define EMSESP_COMMAND_STACKSIZE 8192 // needed for TLS #endif #ifndef EMSESP_COMMAND_PRIORITY #define EMSESP_COMMAND_PRIORITY 1 diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index b8aa024e1..a5ae53aa3 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -145,8 +145,8 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web Command::add( EMSdevice::DeviceType::CUSTOM, webCustomEntity.customEntityItems.back().name, - [webCustomEntity](const char * value, const int8_t id) { - return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name); + [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { + return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); }, FL_(entity_cmd), CommandFlag::ADMIN_ONLY); @@ -796,8 +796,8 @@ void WebCustomEntityService::load_test_data() { Command::add( EMSdevice::DeviceType::CUSTOM, webCustomEntity.customEntityItems.back().name, - [webCustomEntity](const char * value, const int8_t id) { - return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name); + [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { + return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); }, FL_(entity_cmd), CommandFlag::ADMIN_ONLY); @@ -832,8 +832,8 @@ void WebCustomEntityService::load_test_data() { Command::add( EMSdevice::DeviceType::CUSTOM, webCustomEntity.customEntityItems.back().name, - [webCustomEntity](const char * value, const int8_t id) { - return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name); + [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { + return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); }, FL_(entity_cmd), CommandFlag::ADMIN_ONLY); @@ -855,8 +855,8 @@ void WebCustomEntityService::load_test_data() { Command::add( EMSdevice::DeviceType::CUSTOM, webCustomEntity.customEntityItems.back().name, - [webCustomEntity](const char * value, const int8_t id) { - return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name); + [name = std::string(webCustomEntity.customEntityItems.back().name)](const char * value, const int8_t id, JsonObject output) { + return EMSESP::webCustomEntityService.command_setvalue(value, id, name.c_str()); }, FL_(entity_cmd), CommandFlag::ADMIN_ONLY); diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index df30da412..79c3fe097 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -98,7 +98,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu Command::add( EMSdevice::DeviceType::SCHEDULER, webScheduler.scheduleItems.back().name, - [name = std::string(webScheduler.scheduleItems.back().name)](const char * value, const int8_t id) { + [name = std::string(webScheduler.scheduleItems.back().name)](const char * value, const int8_t id, JsonObject output) { return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str()); }, FL_(schedule_cmd), @@ -466,7 +466,7 @@ void WebSchedulerService::load_test_data() { Command::add( EMSdevice::DeviceType::SCHEDULER, item.name, - [name = std::string(item.name)](const char * value, const int8_t id) { + [name = std::string(item.name)](const char * value, const int8_t id, JsonObject output) { return EMSESP::webSchedulerService.command_setvalue(value, id, name.c_str()); }, FL_(schedule_cmd),