From 7f4fa7be03feadb80ea8e708765a6d544a53ac4d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 18 Aug 2023 21:28:17 +0200 Subject: [PATCH 01/11] [MQTT] Controller improvements --- src/_C002.cpp | 32 +- src/_C005.cpp | 249 ++++------ src/_C006.cpp | 98 ++-- src/_C014.cpp | 608 ++++++++++------------- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 188 +++++++ src/src/Helpers/_CPlugin_Helper_mqtt.h | 18 + 6 files changed, 606 insertions(+), 587 deletions(-) create mode 100644 src/src/Helpers/_CPlugin_Helper_mqtt.cpp create mode 100644 src/src/Helpers/_CPlugin_Helper_mqtt.h diff --git a/src/_C002.cpp b/src/_C002.cpp index 4c58f890e4..668023e83c 100644 --- a/src/_C002.cpp +++ b/src/_C002.cpp @@ -95,9 +95,9 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& bool mustSendEvent = false; switch (Settings.TaskDeviceNumber[x]) { - case 1: // temp solution, if input switch, update state + case 1: // temp solution, if input switch, update state { - action = F("inputSwitchState,"); + action = F("gpio,"); // FIXME tonhuisman: Was: InputSwitchState action += x; action += ','; action += nvalue; @@ -105,7 +105,7 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& } case 29: // temp solution, if plugin 029, set gpio { - int baseVar = x * VARS_PER_TASK; + const int baseVar = x * VARS_PER_TASK; if (switchtype.equalsIgnoreCase(F("dimmer"))) { @@ -130,8 +130,7 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& } if (checkValidPortRange(PLUGIN_GPIO, Settings.TaskDevicePin1[x])) { - action = F("pwm,"); - action += Settings.TaskDevicePin1[x]; + action = concat(F("pwm,"), Settings.TaskDevicePin1[x]); action += ','; action += pwmValue; } @@ -140,30 +139,25 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& UserVar[baseVar] = nvalue; if (checkValidPortRange(PLUGIN_GPIO, Settings.TaskDevicePin1[x])) { - action = F("gpio,"); - action += Settings.TaskDevicePin1[x]; + action = concat(F("gpio,"), Settings.TaskDevicePin1[x]); action += ','; action += static_cast(nvalue); } } break; } -# if defined(USES_P088) // || defined(USES_P115) - case 88: // Send heatpump IR (P088) if IDX matches - // case 115: // Send heatpump IR (P115) if IDX matches + # if defined(USES_P088) + case 88: // Send heatpump IR (P088) if IDX matches { - action = F("heatpumpir,"); - action += svalue1; // svalue1 is like 'gree,1,1,0,22,0,0' + action = concat(F("heatpumpir,"), svalue1); // svalue1 is like 'gree,1,1,0,22,0,0' break; } -# endif // USES_P088 || USES_P115 + # endif // if defined(USES_P088) default: break; } - const bool validCommand = action.length() > 0; - - if (validCommand) { + if (action.length() != 0) { mustSendEvent = true; // Try plugin and internal @@ -194,19 +188,19 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& if (event->idx != 0) { String json = serializeDomoticzJson(event); -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { addLogMove(LOG_LEVEL_DEBUG, concat(F("MQTT : "), json)); } -# endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG String pubname = CPlugin_002_pubname; parseControllerVariables(pubname, event, false); // Publish using move operator, thus pubname and json are empty after this call success = MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(pubname), std::move(json), CPlugin_002_mqtt_retainFlag); - } // if ixd !=0 + } // if idx !=0 else { addLog(LOG_LEVEL_ERROR, F("MQTT : IDX cannot be zero!")); diff --git a/src/_C005.cpp b/src/_C005.cpp index def20c26e8..a8e0a18d68 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -3,6 +3,7 @@ # include "src/Commands/InternalCommands.h" # include "src/Globals/EventQueue.h" +# include "src/Helpers/_CPlugin_Helper_mqtt.h" # include "src/Helpers/PeriodicalActions.h" # include "src/Helpers/StringParser.h" # include "_Plugin_Helper.h" @@ -11,6 +12,15 @@ // ################### Controller Plugin 005: Home Assistant (openHAB) MQTT ############################## // ####################################################################################################### +/** Changelog: + * 2023-08-18 tonhuisman: Clean up source for pull request + * 2023-03-15 tonhuisman: Add processing of topic endpoint /set to issue a TaskValueSet,taskname,taskvalue,payload command for + * topic %sysname%/#/taskname/valuename/set + * Move /cmd and /set handling to helper file for generic MQTT use + * Reformatted source using Uncrustify + * 2023-03 Changelog started + */ + # define CPLUGIN_005 # define CPLUGIN_ID_005 5 # define CPLUGIN_NAME_005 "Home Assistant (openHAB) MQTT" @@ -80,49 +90,10 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& break; } + // String pubname = CPlugin_005_pubname; - String pubname = CPlugin_005_pubname; - bool mqtt_retainFlag = CPlugin_005_mqtt_retainFlag; - - parseControllerVariables(pubname, event, false); + success = MQTT_protocol_send(event, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag); - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - // MFD: skip publishing for values with empty labels (removes unnecessary publishing of unwanted values) - if (getTaskValueName(event->TaskIndex, x).isEmpty()) { - continue; // we skip values with empty labels - } - String tmppubname = pubname; - parseSingleControllerVariable(tmppubname, event, x, false); - String value; - if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - value = event->String2.substring(0, 20); // For the log - } else { - value = formatUserVarNoCheck(event, x); - } -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("MQTT : "); - log += tmppubname; - log += ' '; - log += value; - addLogMove(LOG_LEVEL_DEBUG, log); - } -# endif // ifndef BUILD_NO_DEBUG - - // Small optimization so we don't try to copy potentially large strings - if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) - success = true; - } else { - // Publish using move operator, thus tmppubname and value are empty after this call - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), std::move(value), mqtt_retainFlag)) - success = true; - } - } break; } @@ -145,140 +116,88 @@ bool C005_parse_command(struct EventStruct *event) { // Topic : event->String1 // Message: event->String2 - String cmd; - bool validTopic = false; - const int lastindex = event->String1.lastIndexOf('/'); - const String lastPartTopic = event->String1.substring(lastindex + 1); - const bool has_cmd_arg_index = event->String1.lastIndexOf(F("cmd_arg")) != -1; - - if (equals(lastPartTopic, F("cmd"))) { - // Example: - // Topic: ESP_Easy/Bathroom_pir_env/cmd - // Message: gpio,14,0 - // Full command: gpio,14,0 - - cmd = event->String2; - - // SP_C005a: string= ;cmd=gpio,12,0 ;taskIndex=12 ;string1=ESPT12/cmd ;string2=gpio,12,0 - validTopic = true; - } else if (has_cmd_arg_index) { - // Example: - // Topic: ESP_Easy/Bathroom_pir_env/cmd_arg1/GPIO/0 - // Message: 14 - // Full command: gpio,14,0 - - uint8_t topic_index = 1; - String topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - - while(!topic_folder.startsWith(F("cmd_arg")) && !topic_folder.isEmpty()) { - ++topic_index; - topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - } - if (!topic_folder.isEmpty()) { - int cmd_arg_nr = -1; - if (validIntFromString(topic_folder.substring(7), cmd_arg_nr)) { - int constructed_cmd_arg_nr = 0; + bool validTopic = MQTT_handle_topic_commands(event); // default handling of /cmd and /set topics + + if (!validTopic) { + String cmd; + const int lastindex = event->String1.lastIndexOf('/'); + const String lastPartTopic = event->String1.substring(lastindex + 1); + const bool has_cmd_arg_index = event->String1.lastIndexOf(F("cmd_arg")) != -1; + + if (has_cmd_arg_index) { + // Example: + // Topic: ESP_Easy/Bathroom_pir_env/cmd_arg1/GPIO/0 + // Message: 14 + // Full command: gpio,14,0 + + uint8_t topic_index = 1; + String topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + + while (!topic_folder.startsWith(F("cmd_arg")) && !topic_folder.isEmpty()) { ++topic_index; topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - bool msg_added = false; - while(!topic_folder.isEmpty()) { - if (constructed_cmd_arg_nr != 0) { - cmd += ','; + } + + if (!topic_folder.isEmpty()) { + int cmd_arg_nr = -1; + + if (validIntFromString(topic_folder.substring(7), cmd_arg_nr)) { + int constructed_cmd_arg_nr = 0; + ++topic_index; + topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + bool msg_added = false; + + while (!topic_folder.isEmpty()) { + if (constructed_cmd_arg_nr != 0) { + cmd += ','; + } + + if (constructed_cmd_arg_nr == cmd_arg_nr) { + cmd += event->String2; + msg_added = true; + } else { + cmd += topic_folder; + ++topic_index; + topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + } + ++constructed_cmd_arg_nr; } - if (constructed_cmd_arg_nr == cmd_arg_nr) { + + if (!msg_added) { + cmd += ','; cmd += event->String2; - msg_added = true; - } else { - cmd += topic_folder; - ++topic_index; - topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); } - ++constructed_cmd_arg_nr; - } - if (!msg_added) { - cmd += ','; - cmd += event->String2; - } - //addLog(LOG_LEVEL_INFO, String(F("MQTT cmd: ")) + cmd); - validTopic = true; - } - } - } else { - // Example: - // Topic: ESP_Easy/Bathroom_pir_env/GPIO/14 - // Message: 0 or 1 - // Full command: gpio,14,0 - if (lastindex > 0) { - // Topic has at least one separator - int lastPartTopic_int; - float value_f; - - if (validFloatFromString(event->String2, value_f) && - validIntFromString(lastPartTopic, lastPartTopic_int)) { - int prevLastindex = event->String1.lastIndexOf('/', lastindex - 1); - cmd = event->String1.substring(prevLastindex + 1, lastindex); - cmd += ','; - cmd += lastPartTopic_int; - cmd += ','; - cmd += event->String2; // Just use the original format - validTopic = true; - } - } - } + // addLog(LOG_LEVEL_INFO, String(F("MQTT cmd: ")) + cmd); - if (validTopic) { - // in case of event, store to buffer and return... - const String command = parseString(cmd, 1); - - if ((equals(command, F("event"))) || (equals(command, F("asyncevent")))) { - if (Settings.UseRules) { - // Need to sanitize the event a bit to allow for sending event values as MQTT messages. - // For example: - // Publish topic: espeasy_node/cmd_arg2/event/myevent/2 - // Message: 1 - // Actual event: myevent=1,2 - - // Strip out the "event" or "asyncevent" part, leaving the actual event string - cmd = parseStringToEndKeepCase(cmd, 2); - - { - // Get the first part upto a parameter separator - // Example: "myEvent,1,2,3", which needs to be converted to "myEvent=1,2,3" - // N.B. This may contain the first eventvalue too - // e.g. "myEvent=1,2,3" => "myEvent=1" - String eventName = parseStringKeepCase(cmd, 1); - String eventValues = parseStringToEndKeepCase(cmd, 2); - const int equal_pos = eventName.indexOf('='); - if (equal_pos != -1) { - // We found an '=' character, so the actual event name is everything before that char. - eventName = cmd.substring(0, equal_pos); - eventValues = cmd.substring(equal_pos + 1); // Rest of the event, after the '=' char - } - if (eventValues.startsWith(F(","))) { - // Need to reconstruct the event to get rid of calls like these: - // myevent=,1,2 - eventValues = eventValues.substring(1); - } - // Now reconstruct the complete event - // Without event values: "myEvent" (no '=' char) - // With event values: "myEvent=1,2,3" - - // Re-using the 'cmd' String as that has pre-allocated memory which is - // known to be large enough to hold the entire event. - cmd = eventName; - if (eventValues.length() > 0) { - // Only append an = if there are eventvalues. - cmd += '='; - cmd += eventValues; - } + validTopic = true; } - // Check for duplicates, as sometimes a node may have multiple subscriptions to the same topic. - // Then it may add several of the same events in a burst. - eventQueue.addMove(std::move(cmd), true); } } else { - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str()); + // Example: + // Topic: ESP_Easy/Bathroom_pir_env/GPIO/14 + // Message: 0 or 1 + // Full command: gpio,14,0 + if (lastindex > 0) { + // Topic has at least one separator + int lastPartTopic_int; + float value_f; + + if (validFloatFromString(event->String2, value_f) && + validIntFromString(lastPartTopic, lastPartTopic_int)) { + const int prevLastindex = event->String1.lastIndexOf('/', lastindex - 1); + cmd = event->String1.substring(prevLastindex + 1, lastindex); + cmd += ','; + cmd += lastPartTopic_int; + cmd += ','; + cmd += event->String2; // Just use the original format + validTopic = true; + } + } + } + + if (validTopic) { + MQTT_execute_command(cmd); } } return validTopic; diff --git a/src/_C006.cpp b/src/_C006.cpp index dc4ec0f427..47cd58a814 100644 --- a/src/_C006.cpp +++ b/src/_C006.cpp @@ -5,9 +5,15 @@ // ########################### Controller Plugin 006: PiDome MQTT ######################################## // ####################################################################################################### +/** Changelog: + * 2023-08-18 tonhuisman: Clean up source for pull request + * 2023-03-15 tonhuisman: Handle setting payload to (Dummy) Devices via topic SysName/TaskName/ValueName/set + * 2023-03 Changelog started + */ # include "src/Commands/InternalCommands.h" # include "src/ESPEasyCore/Controller.h" # include "src/Globals/Settings.h" +# include "src/Helpers/_CPlugin_Helper_mqtt.h" # include "src/Helpers/Network.h" # include "src/Helpers/PeriodicalActions.h" # include "_Plugin_Helper.h" @@ -66,41 +72,46 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: { - // topic structure /Home/Floor/Location/device//gpio/16 - // Split topic into array - String tmpTopic = event->String1.substring(1); - String topicSplit[10]; - int SlashIndex = tmpTopic.indexOf('/'); - uint8_t count = 0; - - while (SlashIndex > 0 && count < 10 - 1) - { - topicSplit[count] = tmpTopic.substring(0, SlashIndex); - tmpTopic = tmpTopic.substring(SlashIndex + 1); - SlashIndex = tmpTopic.indexOf('/'); - count++; - } - topicSplit[count] = tmpTopic; - - String name = topicSplit[4]; - - if (name.equals(Settings.getName())) - { - String cmd = topicSplit[5]; - cmd += ','; - cmd += topicSplit[6].toInt(); // Par1 - cmd += ','; - - if ((event->String2.equalsIgnoreCase(F("false"))) || - (event->String2.equalsIgnoreCase(F("true")))) + if (!MQTT_handle_topic_commands(event, false)) { // Only handle /set option + // topic structure /Home/Floor/Location/device//gpio/16 + // Split topic into array + String tmpTopic = event->String1.substring(1); + String topicSplit[10]; + int SlashIndex = tmpTopic.indexOf('/'); + uint8_t count = 0; + + while (SlashIndex > 0 && count < 10 - 1) { - cmd += (event->String2.equalsIgnoreCase(F("true"))) ? '1' : '0'; // Par2 + topicSplit[count] = tmpTopic.substring(0, SlashIndex); + tmpTopic = tmpTopic.substring(SlashIndex + 1); + SlashIndex = tmpTopic.indexOf('/'); + count++; } - else + topicSplit[count] = tmpTopic; + + const String name = topicSplit[4]; + + if (name.equals(Settings.Name)) { - cmd += event->String2; // Par2 + String cmd = topicSplit[5]; + cmd += ','; + cmd += topicSplit[6].toInt(); // Par1 + cmd += ','; + const bool isTrue = event->String2.equalsIgnoreCase(F("true")); + + if ((event->String2.equalsIgnoreCase(F("false"))) || + (isTrue)) + { + cmd += isTrue ? '1' : '0'; // Par2 + } + else + { + cmd += event->String2; // Par2 + } + + // ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str()); + MQTT_execute_command(cmd); } - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str()); } break; } @@ -111,31 +122,8 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& break; } - String pubname = CPlugin_006_pubname; - bool mqtt_retainFlag = CPlugin_006_mqtt_retainFlag; + success = MQTT_protocol_send(event, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag); - statusLED(true); - - //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed - parseControllerVariables(pubname, event, false); - - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - String tmppubname = pubname; - parseSingleControllerVariable(tmppubname, event, x, false); - - // Small optimization so we don't try to copy potentially large strings - if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) - success = true; - } else { - String value = formatUserVarNoCheck(event, x); - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), value.c_str(), mqtt_retainFlag)) - success = true; - } - } break; } diff --git a/src/_C014.cpp b/src/_C014.cpp index 0ece7b5d2d..f756ca2026 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -7,14 +7,16 @@ # include "src/Globals/MQTT.h" # include "src/Globals/Plugins.h" # include "src/Globals/Statistics.h" +# include "src/Helpers/_CPlugin_Helper_mqtt.h" # include "src/Helpers/PeriodicalActions.h" # include "_Plugin_Helper.h" // ####################################################################################################### -// ################################# Controller Plugin 0014: Homie 3/4 ################################### +// ################################# Controller Plugin 014: Homie 3/4 #################################### // ####################################################################################################### /** Changelog: + * 2023-08-18 tonhuisman: Clean up source to improve resource usage * 2023-03-15 tonhuisman: Replace use of deprecated DummyValueSet with TaskValueSet * 2023-03 Changelog started */ @@ -23,8 +25,11 @@ # define CPLUGIN_ID_014 14 // Define which Homie version to use +# if !defined(CPLUGIN_014_V3) && !defined(CPLUGIN_014_V4) + // #define CPLUGIN_014_V3 -# define CPLUGIN_014_V4 +# define CPLUGIN_014_V4 +# endif // if !defined(CPLUGIN_014_V3) && !defined(CPLUGIN_014_V4) # ifdef CPLUGIN_014_V3 # define CPLUGIN_014_HOMIE_VERSION "3.0.0" @@ -42,68 +47,54 @@ # define CPLUGIN_014_BASE_TOPIC "homie/%sysname%/#" # define CPLUGIN_014_BASE_VALUE "homie/%sysname%/%device%/%node%/%property%" -# define CPLUGIN_014_INTERVAL "90" // to prevent timeout !ToDo set by lowest plugin interval -# define CPLUGIN_014_SYSTEM_DEVICE "SYSTEM" // name for system device Plugin for cmd and GIO values -# define CPLUGIN_014_CMD_VALUE "cmd" // name for command value -# define CPLUGIN_014_GPIO_VALUE "gpio" // name for gpio value i.e. "gpio1" -# define CPLUGIN_014_CMD_VALUE_NAME "Command" // human readabele name for command value +# define CPLUGIN_014_INTERVAL "90" // to prevent timeout !ToDo set by lowest plugin interval +# define CPLUGIN_014_SYSTEM_DEVICE "SYSTEM" // name for system device Plugin for cmd and GIO values +# define CPLUGIN_014_CMD_VALUE "cmd" // name for command value +# define CPLUGIN_014_GPIO_VALUE "gpio" // name for gpio value i.e. "gpio1" +# define CPLUGIN_014_CMD_VALUE_NAME "Command" // human readabele name for command value + +# define CPLUGIN_014_GPIO_COMMAND "gpio" // name for gpio command +# define CPLUGIN_014_TASKVALUESET_COMMAND "taskvalueset" // name for taskvalueset command +# define CPLUGIN_014_HOMIEVALUESET_COMMAND "homievalueset" // name for homievalueset command -uint8_t msgCounter = 0; // counter for send Messages (currently for information / log only! +uint8_t msgCounter = 0; // counter for send Messages (currently for information / log only! String CPlugin_014_pubname; bool CPlugin_014_mqtt_retainFlag = false; -/* - // send MQTT Message with complete Topic / Payload - bool CPlugin_014_sendMQTTmsg(String& topic, const char* payload, int& errorCounter) { - bool mqttReturn = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic, payload, true); - if (mqttReturn) msgCounter++; - else errorCounter++; - if (loglevelActiveFor(LOG_LEVEL_INFO) && mqttReturn) { - String log = F("C014 : msg T:"); - log += topic; - log += F(" P: "); - log += payload; - addLog(LOG_LEVEL_DEBUG_MORE, log+" success!"); - } - if (loglevelActiveFor(LOG_LEVEL_INFO) && !mqttReturn) { - String log = F("C014 : msg T:"); - log += topic; - log += F(" P: "); - log += payload; - addLog(LOG_LEVEL_ERROR, log+" ERROR!"); - } - return mqttReturn; - } - */ +void C014_replaceSysname(String& var) { + var.replace(F("%sysname%"), Settings.getName()); +} -bool CPlugin_014_sendMQTTdevice(String tmppubname, - taskIndex_t taskIndex, - const __FlashStringHelper *topic, - const String& payload, - int& errorCounter) { +bool CPlugin_014_sendMQTTdevice(String tmppubname, + taskIndex_t taskIndex, + const __FlashStringHelper *topic, + const String & payload, + int & errorCounter) { tmppubname.replace(F("#"), topic); bool mqttReturn = MQTTpublish(CPLUGIN_ID_014, taskIndex, tmppubname.c_str(), payload.c_str(), true); - if (mqttReturn) { msgCounter++; } - else { errorCounter++; } + if (mqttReturn) { + msgCounter++; + } else { + errorCounter++; + } + + String log; + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { // Also true for LOG_LEVEL_DEBUG_MORE + log = concat(F("C014 : T:"), topic); + log += concat(F(" P: "), payload); + } + # ifndef BUILD_NO_DEBUG -#ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE) && mqttReturn) { - String log = F("C014 : T:"); - log += topic; - log += F(" P: "); - log += payload; log += F(" success!"); addLogMove(LOG_LEVEL_DEBUG_MORE, log); } -#endif + # endif // ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_ERROR) && !mqttReturn) { - String log = F("C014 : T:"); - log += topic; - log += F(" P: "); - log += payload; log += F(" ERROR!"); addLogMove(LOG_LEVEL_ERROR, log); } @@ -112,22 +103,22 @@ bool CPlugin_014_sendMQTTdevice(String tmppubname, } // send MQTT Message with CPLUGIN_014_BASE_TOPIC Topic scheme / Payload -bool CPlugin_014_sendMQTTdevice(const String& tmppubname, - taskIndex_t taskIndex, - const __FlashStringHelper *topic, - const __FlashStringHelper *payload, - int& errorCounter) +bool CPlugin_014_sendMQTTdevice(const String & tmppubname, + taskIndex_t taskIndex, + const __FlashStringHelper *topic, + const __FlashStringHelper *payload, + int & errorCounter) { return CPlugin_014_sendMQTTdevice(tmppubname, taskIndex, topic, String(payload), errorCounter); } // send MQTT Message with CPLUGIN_014_BASE_VALUE Topic scheme / Payload -bool CPlugin_014_sendMQTTnode(String tmppubname, +bool CPlugin_014_sendMQTTnode(String tmppubname, const String& node, const String& value, const String& topic, const String& payload, - int & errorCounter) { + int & errorCounter) { tmppubname.replace(F("%device%"), node); tmppubname.replace(F("%node%"), value); tmppubname.replace(F("/%property%"), topic); // leading forward slash required to send "homie/device/value" topics @@ -136,26 +127,22 @@ bool CPlugin_014_sendMQTTnode(String tmppubname, if (mqttReturn) { msgCounter++; } else { errorCounter++; } - #ifndef BUILD_NO_DEBUG + String log; + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { // Also true for LOG_LEVEL_DEBUG_MORE + log = concat(F("C014 : V:"), value); + log += concat(F(" T: "), topic); + log += concat(F(" P: "), payload); + } + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE) && mqttReturn) { - String log = F("C014 : V:"); - log += value; - log += F(" T: "); - log += topic; - log += F(" P: "); - log += payload; log += F(" success!"); addLogMove(LOG_LEVEL_DEBUG_MORE, log); } - #endif + # endif // ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_ERROR) && !mqttReturn) { - String log = F("C014 : V:"); - log += value; - log += F(" T: "); - log += topic; - log += F(" P: "); - log += payload; log += F(" ERROR!"); addLogMove(LOG_LEVEL_ERROR, log); } @@ -163,8 +150,8 @@ bool CPlugin_014_sendMQTTnode(String tmppubname, return mqttReturn; } -// and String a comma seperated list -void CPLUGIN_014_addToList(String& valuesList, const String& node) +// and String to a comma separated list +void C014_addToList(String& valuesList, const String& node) { if (valuesList.length() > 0) { valuesList += ','; } valuesList += node; @@ -217,9 +204,9 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& errorCounter = 0; pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages - pubname.replace(F("%sysname%"), Settings.getName()); + C014_replaceSysname(pubname); -# ifdef CPLUGIN_014_V3 + # ifdef CPLUGIN_014_V3 // $stats/uptime Device → Controller Time elapsed in seconds since the boot of the device Yes Yes CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$stats/uptime"), toString(getUptimeMinutes() * 60, 0), errorCounter); @@ -230,7 +217,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& RssI = min(max(2 * (RssI + 100.0f), 0.0f), 100.0f); CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$stats/signal"), toString(RssI, 1), errorCounter); -# endif // ifdef CPLUGIN_014_V3 + # endif // ifdef CPLUGIN_014_V3 if (errorCounter > 0) { @@ -245,7 +232,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& success = true; } - #ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { String log = F("C014 : $stats information sent with "); @@ -257,7 +245,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& msgCounter = 0; addLogMove(LOG_LEVEL_DEBUG, log); } - #endif + # endif // ifndef BUILD_NO_DEBUG } break; } @@ -268,7 +256,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& // send autodiscover header pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages - pubname.replace(F("%sysname%"), Settings.getName()); + C014_replaceSysname(pubname); int deviceCount = 1; // minimum the SYSTEM device exists int nodeCount = 1; // minimum the cmd node exists errorCounter = 0; @@ -276,12 +264,12 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& if (lastBootCause != BOOT_CAUSE_DEEP_SLEEP) // skip sending autodiscover data when returning from deep sleep { String nodename = CPLUGIN_014_BASE_VALUE; // Scheme to form node messages - nodename.replace(F("%sysname%"), Settings.getName()); + C014_replaceSysname(nodename); String nodesList; // build comma separated List for nodes String valuesList; // build comma separated List for values String deviceName; // current Device Name nr:name String valueName; // current Value Name - String unitName; // estaimate Units + String unitName; // estimate Units // init: this is the state the device is in when it is connected to the MQTT broker, but has not yet sent all Homie messages and is // not yet ready to operate. This is the first message that must that must be sent. @@ -291,42 +279,45 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$homie"), F(CPLUGIN_014_HOMIE_VERSION), errorCounter); // $name Device → Controller Friendly name of the device Yes Yes - CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$name"), Settings.getName(), errorCounter); + CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$name"), Settings.getName(), errorCounter); // $localip Device → Controller IP of the device on the local network Yes Yes -# ifdef CPLUGIN_014_V3 + # ifdef CPLUGIN_014_V3 CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$localip"), formatIP(NetworkLocalIP()), errorCounter); // $mac Device → Controller Mac address of the device network interface. The format MUST be of the type A1:B2:C3:D4:E5:F6 Yes Yes CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$mac"), NetworkMacAddress(), errorCounter); // $implementation Device → Controller An identifier for the Homie implementation (example esp8266) Yes Yes - # if defined(ESP8266) - CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$implementation"), F("ESP8266"), errorCounter); - # endif // if defined(ESP8266) - # if defined(ESP32) - CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$implementation"), F("ESP32"), errorCounter); - # endif // if defined(ESP32) + CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$implementation"), + # if defined(ESP8266) + F("ESP8266"), + # endif // if defined(ESP8266) + # if defined(ESP32) + F("ESP32"), + # endif // if defined(ESP32) + errorCounter); // $fw/version Device → Controller Version of the firmware running on the device Yes Yes CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$fw/version"), toString(Settings.Build, 0), errorCounter); -#if FEATURE_ESPEASY_P2P + # if FEATURE_ESPEASY_P2P + // $fw/name Device → Controller Name of the firmware running on the device. Allowed characters are the same as the device ID Yes Yes CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$fw/name"), getNodeTypeDisplayString(NODE_TYPE_ID), errorCounter); -#endif + # endif // if FEATURE_ESPEASY_P2P // $stats/interval Device → Controller Interval in seconds at which the device refreshes its $stats/+: See next section for // details about statistical attributes Yes Yes CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$stats/interval"), F(CPLUGIN_014_INTERVAL), errorCounter); -# endif // ifdef CPLUGIN_014_V3 + # endif // ifdef CPLUGIN_014_V3 // always send the SYSTEM device with the cmd node - CPLUGIN_014_addToList(nodesList, F(CPLUGIN_014_SYSTEM_DEVICE)); - CPLUGIN_014_addToList(valuesList, F(CPLUGIN_014_CMD_VALUE)); + C014_addToList(nodesList, F(CPLUGIN_014_SYSTEM_DEVICE)); + C014_addToList(valuesList, F(CPLUGIN_014_CMD_VALUE)); // $name Device → Controller Friendly name of the Node Yes Yes CPlugin_014_sendMQTTnode(nodename, @@ -367,20 +358,21 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& int gpio = 0; while (gpio <= MAX_GPIO) { - if (Settings.getPinBootState(gpio) != PinBootState::Default_state) // anything but default + const PinBootState pinBootState = Settings.getPinBootState(gpio); + + if (pinBootState != PinBootState::Default_state) // anything but default { nodeCount++; - valueName = F(CPLUGIN_014_GPIO_VALUE); - valueName += toString(gpio, 0); - CPLUGIN_014_addToList(valuesList, valueName); + valueName = concat(F(CPLUGIN_014_GPIO_VALUE), gpio); + C014_addToList(valuesList, valueName); // $name Device → Controller Friendly name of the property. Any String Yes No ("") - CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), valueName, F("/$name"), valueName, errorCounter); + CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), valueName, F("/$name"), valueName, errorCounter); // $datatype The data type. See Payloads. Enum: [integer, float, boolean,string, enum, color] - CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), valueName, F("/$datatype"), F("boolean"), errorCounter); + CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), valueName, F("/$datatype"), F("boolean"), errorCounter); - if (Settings.getPinBootState(gpio) != PinBootState::Input) // defined as output + if (pinBootState != PinBootState::Input) // defined as output { // $settable Device → Controller Specifies whether the property is settable (true) or readonly (false) true or // false Yes No (false) @@ -391,8 +383,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } // $properties Device → Controller Properties the node exposes, with format id separated by a , if there are multiple nodes. Yes Yes - CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), F("$properties"), F(""), valuesList, errorCounter); - valuesList = F(""); + CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), F("$properties"), EMPTY_STRING, valuesList, errorCounter); + valuesList = EMPTY_STRING; deviceCount++; // SECOND Plugins @@ -401,13 +393,13 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& if (validPluginID_fullcheck((Settings.TaskDeviceNumber[x]))) { LoadTaskSettings(x); - deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); deviceName = getTaskDeviceName(x); if (validDeviceIndex(DeviceIndex) && Settings.TaskDeviceEnabled[x]) // Device is enabled so send information { // device enabled - valuesList = F(""); + valuesList = EMPTY_STRING; const uint8_t valueCount = getValueCountForTask(x); @@ -418,7 +410,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& for (uint8_t varNr = 0; varNr < valueCount; varNr++) { if (validPluginID_fullcheck(Settings.TaskDeviceNumber[x])) { if (ExtraTaskSettings.TaskDeviceValueNames[varNr][0] != 0) { // do not send if Value Name is empty! - CPLUGIN_014_addToList(valuesList, ExtraTaskSettings.TaskDeviceValueNames[varNr]); + C014_addToList(valuesList, ExtraTaskSettings.TaskDeviceValueNames[varNr]); // $settable Device → Controller Specifies whether the property is settable (true) or readonly (false) true // or false Yes No (false) @@ -443,7 +435,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& unitName = F(""); switch (Settings.TaskDevicePluginConfig[x][varNr]) { - case 0: valueName = F("integer"); + case 0: + valueName = F("integer"); if ((ExtraTaskSettings.TaskDevicePluginConfig[varNr] != 0) || (ExtraTaskSettings.TaskDevicePluginConfig[varNr + 5] != 0)) { @@ -452,7 +445,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& unitName += ExtraTaskSettings.TaskDevicePluginConfig[varNr + valueCount]; } break; - case 1: valueName = F("float"); + case 1: + valueName = F("float"); if ((ExtraTaskSettings.TaskDevicePluginConfig[varNr] != 0) || (ExtraTaskSettings.TaskDevicePluginConfig[varNr + 5] != 0)) { @@ -463,14 +457,17 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& break; case 2: valueName = F("boolean"); break; case 3: valueName = F("string"); break; - case 4: valueName = F("enum"); - unitName = ExtraTaskSettings.TaskDeviceFormula[varNr]; + case 4: + valueName = F("enum"); + unitName = ExtraTaskSettings.TaskDeviceFormula[varNr]; break; - case 5: valueName = F("color"); - unitName = F("rgb"); + case 5: + valueName = F("color"); + unitName = F("rgb"); break; - case 6: valueName = F("color"); - unitName = F("hsv"); + case 6: + valueName = F("color"); + unitName = F("hsv"); break; } CPlugin_014_sendMQTTnode(nodename, @@ -480,13 +477,14 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& valueName, errorCounter); - if (!unitName.isEmpty()) { + if (!unitName.isEmpty()) { CPlugin_014_sendMQTTnode(nodename, deviceName, ExtraTaskSettings.TaskDeviceValueNames[varNr], F("/$format"), unitName, - errorCounter); } + errorCounter); + } nodeCount++; } } @@ -506,7 +504,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& { if (ExtraTaskSettings.TaskDeviceValueNames[varNr][0] != 0) // do not send if Value Name is empty! { - CPLUGIN_014_addToList(valuesList, ExtraTaskSettings.TaskDeviceValueNames[varNr]); + C014_addToList(valuesList, ExtraTaskSettings.TaskDeviceValueNames[varNr]); // $name Device → Controller Friendly name of the property. Any String Yes No ("") CPlugin_014_sendMQTTnode(nodename, @@ -535,7 +533,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& nodeCount++; - /* // because values in ESPEasy are unitless lets assueme some units by the value name + /* // because values in ESPEasy are unitless lets assume some units by the value name (still case sensitive) if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "temp") != nullptr ) { @@ -548,7 +546,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& unitName = F("Pa"); } // ToDo: .... and more - if (unitName != F("")) // found a unit match + if (!unitName.isEmpty()) // found a unit match { // $unit Device → Controller A string containing the unit of this property. You are not limited to the recommended values, although they are the only well known ones @@ -564,14 +562,15 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } } // end loop throug values } else { // Device has custom Values - #ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("C014 : Device has custom values: "); - log += getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x)); + String log = concat(F("C014 : Device has custom values: "), + getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x))); log += F(" not implemented!"); addLogMove(LOG_LEVEL_DEBUG, log); } - #endif + # endif // ifndef BUILD_NO_DEBUG } } @@ -595,28 +594,29 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& errorCounter); // add device to device list - CPLUGIN_014_addToList(nodesList, deviceName); + C014_addToList(nodesList, deviceName); deviceCount++; // $properties Device → Controller Properties the node exposes, with format id separated by a , if there are multiple // nodes. Yes Yes - CPlugin_014_sendMQTTnode(nodename, - deviceName, - F("$properties"), - F(""), - valuesList, + CPlugin_014_sendMQTTnode(nodename, + deviceName, + F("$properties"), + F(""), + valuesList, errorCounter); - valuesList = F(""); + valuesList = EMPTY_STRING; } } else { // device not enabeled - #ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("C014 : Device Disabled: "); - log += getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x)); + String log = concat(F("C014 : Device Disabled: "), + getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x))); log += F(" not propagated!"); addLogMove(LOG_LEVEL_DEBUG, log); } - #endif + # endif // ifndef BUILD_NO_DEBUG } } // device configured } // loop through devices @@ -641,17 +641,14 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("C014 : autodiscover information of "); - log += deviceCount; - log += F(" Devices and "); - log += nodeCount; + String log = concat(F("C014 : autodiscover information of "), deviceCount); + log += concat(F(" Devices and "), nodeCount); log += F(" Nodes sent with "); if (errorCounter > 0) { log += errorCounter; } else { log += F("no"); } - log += F(" errors! ("); - log += msgCounter; - log += F(" messages)"); + log += concat(F(" errors! ("), msgCounter); + log += F(" messages)"); addLogMove(LOG_LEVEL_INFO, log); } msgCounter = 0; @@ -669,19 +666,16 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_GOT_INVALID: { pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages - pubname.replace(F("%sysname%"), Settings.getName()); + C014_replaceSysname(pubname); // disconnected: this is the state the device is in when it is cleanly disconnected from the MQTT broker. You must send this message // before cleanly disconnecting success = CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("disconnected"), errorCounter); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("C014 : Device: "); - log += Settings.getName(); + String log = concat(F("C014 : Device: "), Settings.getName()); log += F(" got invalid (disconnect"); - - if (success) { log += F("ed)."); } - else { log += F(") failed!"); } + log += success ? F("ed).") : F(") failed!"); addLogMove(LOG_LEVEL_INFO, log); } break; @@ -690,7 +684,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_FLUSH: { pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages - pubname.replace(F("%sysname%"), Settings.getName()); + C014_replaceSysname(pubname); // sleeping: this is the state the device is in when the device is sleeping. You have to send this message before sleeping. success = CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("sleeping"), errorCounter); @@ -724,81 +718,85 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& nodeName = nodeName.substring(lastindex + 1); String log; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { log = F("C014 : MQTT received: "); - log += F("/set: N: "); - log += nodeName; - log += F(" V: "); - log += valueName; + log += concat(F("/set: N: "), nodeName); + log += concat(F(" V: "), valueName); } - if (nodeName.equals(F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device + if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device { if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values { const size_t gpio_value_tag_length = String(F(CPLUGIN_014_GPIO_VALUE)).length(); - cmd = F("GPIO,"); - cmd += valueName.substring(gpio_value_tag_length).toInt(); // get the GPIO + cmd = concat(F("GPIO,"), valueName.substring(gpio_value_tag_length).toInt()); // get the GPIO + cmd += ','; - if ((equals(event->String2, F("true"))) || (equals(event->String2, '1'))) { cmd += F(",1"); } - else { cmd += F(",0"); } + if (equals(event->String2, F("true")) || equals(event->String2, '1')) { + cmd += '1'; + } else { + cmd += '0'; + } validTopic = true; - } else if (valueName.equals(F(CPLUGIN_014_CMD_VALUE))) // msg to send a command + } else if (equals(valueName, F(CPLUGIN_014_CMD_VALUE))) // msg to send a command { cmd = event->String2; validTopic = true; } else { - cmd = F("SYSTEM/"); - cmd += valueName; + cmd = concat(F("SYSTEM/"), valueName); cmd += F(" unknown!"); } } else // msg to a receiving plugin { taskIndex = findTaskIndexByName(nodeName); - deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - taskVarIndex_t taskVarIndex = event->Par2 - 1; + const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + const taskVarIndex_t taskVarIndex = event->Par2 - 1; if (validDeviceIndex(deviceIndex) && validTaskVarIndex(taskVarIndex)) { - int pluginID = Device[deviceIndex].Number; + const int pluginID = Device[deviceIndex].Number; - if (pluginID == 33) // Plugin 33 Dummy Device - { // TaskValueSet,,,, works only with - // new version of P033! + if (pluginID == 33) // Plugin 33 Dummy Device + { // TaskValueSet,,,, works only with + // new version of P033! valueNr = findDeviceValueIndexByName(valueName, taskIndex); - if (valueNr != VARS_PER_TASK) // value Name identified + if (valueNr != VARS_PER_TASK) // value Name identified { - cmd = F("TaskValueSet,"); // Set a Dummy Device Value - cmd += (taskIndex + 1); // set the device Number + // Set a Dummy Device Value and the device Number + cmd = concat(F("TaskValueSet,"), taskIndex + 1); cmd += ','; - cmd += (valueNr + 1); // set the value Number + cmd += (valueNr + 1); // set the value Number cmd += ','; - cmd += event->String2; // expect float as payload! + cmd += event->String2; // expect float as payload! validTopic = true; } - } else if (pluginID == 86) { // Plugin Homie receiver. Schedules the event defined in the plugin. Does NOT store the - // value. Use HomieValueSet to save the value. This will acknowledge back to the - // controller too. + } else if (pluginID == 86) { // Plugin Homie receiver. Schedules the event defined in the plugin. Does NOT store the + // value. Use HomieValueSet to save the value. This will acknowledge back to the + // controller too. valueNr = findDeviceValueIndexByName(valueName, taskIndex); if (valueNr != VARS_PER_TASK) { - cmd = F("event,"); - cmd += valueName; + cmd = concat(F("event,"), valueName); cmd += '='; - if (Settings.TaskDevicePluginConfig[taskIndex][valueNr] == 3) { // Quote Sting parameters. PLUGIN_086_VALUE_STRING + if (Settings.TaskDevicePluginConfig[taskIndex][valueNr] == 3) { // Quote String parameters. PLUGIN_086_VALUE_STRING cmd += wrapWithQuotes(event->String2); } else { if (Settings.TaskDevicePluginConfig[taskIndex][valueNr] == 4) { // Enumeration parameter, find Number of item. // PLUGIN_086_VALUE_ENUM - String enumList = ExtraTaskSettings.TaskDeviceFormula[taskVarIndex]; - int i = 1; + const String enumList = ExtraTaskSettings.TaskDeviceFormula[taskVarIndex]; + int i = 1; + String part = parseStringKeepCase(enumList, i); - while (!parseString(enumList, i).isEmpty()) { // lookup result in enum List is changed to lowercase - if (parseString(enumList, i).equalsIgnoreCase(event->String2)) { break; } + while (!part.isEmpty()) { // lookup result in enum List is changed to lowercase + if (part.equalsIgnoreCase(event->String2)) { + break; + } i++; + part = parseStringKeepCase(enumList, i); } cmd += i; cmd += ','; @@ -815,73 +813,18 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& parseCommandString(&TempEvent, cmd); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" cmd: "); - log += cmd; + log += concat(F(" cmd: "), cmd); log += F(" OK"); addLog(LOG_LEVEL_INFO, log); } - } else { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" INVALID MSG"); - addLog(LOG_LEVEL_INFO, log); - } + } else if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" INVALID MSG"); + addLog(LOG_LEVEL_INFO, log); } } if (validTopic) { - // in case of event, store to buffer and return... - String command = parseString(cmd, 1); - - if ((equals(command, F("event"))) || (equals(command, F("asyncevent")))) - { - if (Settings.UseRules) { - String newEvent = parseStringToEnd(cmd, 2); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("C014 : taskIndex:"); - - if (!validTaskIndex(taskIndex)) { - log += F("Invalid"); - } else { - log += taskIndex; - log += F(" valueNr:"); - log += valueNr; - log += F(" valueType:"); - log += Settings.TaskDevicePluginConfig[taskIndex][valueNr]; - } - log += F(" Event: "); - log += newEvent; - addLogMove(LOG_LEVEL_INFO, log); - } - eventQueue.addMove(std::move(newEvent)); - } - } else { // not an event - String log; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("C014 :"); - } - - // FIXME TD-er: Command is not parsed, should we call ExecuteCommand here? - if (ExecuteCommand_internal(EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str())) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" Internal Command: OK!"); - } - } else if (PluginCall(PLUGIN_WRITE, &TempEvent, cmd)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" PluginCall: OK!"); - } - } else { - remoteConfig(&TempEvent, cmd); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" Plugin/Internal command failed! remoteConfig?"); - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, log); - } - } + MQTT_execute_command(cmd, true); } } success = validTopic; @@ -894,50 +837,53 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& break; } - String pubname = CPlugin_014_pubname; - bool mqtt_retainFlag = CPlugin_014_mqtt_retainFlag; + success = MQTT_protocol_send(event, CPlugin_014_pubname, CPlugin_014_mqtt_retainFlag); - statusLED(true); + // String pubname = CPlugin_014_pubname; + // bool mqtt_retainFlag = CPlugin_014_mqtt_retainFlag; - parseControllerVariables(pubname, event, false); - LoadTaskSettings(event->TaskIndex); + // statusLED(true); - uint8_t valueCount = getValueCountForTask(event->TaskIndex); + // parseControllerVariables(pubname, event, false); + // LoadTaskSettings(event->TaskIndex); - for (uint8_t x = 0; x < valueCount; x++) - { - String tmppubname = pubname; - String value; - parseSingleControllerVariable(tmppubname, event, x, false); - - // Small optimization so we don't try to copy potentially large strings - if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) - success = true; - value = event->String2.substring(0, 20); // For the log - } else { - value = formatUserVarNoCheck(event, x); - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), value.c_str(), mqtt_retainFlag)) - success = true; - } + // uint8_t valueCount = getValueCountForTask(event->TaskIndex); -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("C014 : Sent to "); - log += tmppubname; - log += ' '; - log += value; - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif - } + // for (uint8_t x = 0; x < valueCount; x++) + // { + // String tmppubname = pubname; + // String value; + // parseSingleControllerVariable(tmppubname, event, x, false); + + // // Small optimization so we don't try to copy potentially large strings + // if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { + // if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) { + // success = true; + // } + // value = event->String2.substring(0, 20); // For the log + // } else { + // value = formatUserVarNoCheck(event, x); + + // if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), value.c_str(), mqtt_retainFlag)) { + // success = true; + // } + // } + + // # ifndef BUILD_NO_DEBUG + + // if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + // String log = concat(F("C014 : Sent to "), tmppubname); + // log += ' '; + // log += value; + // addLogMove(LOG_LEVEL_DEBUG, log); + // } + // # endif // ifndef BUILD_NO_DEBUG + // } break; } case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: { - - /* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { String log = F("CPLUGIN_ACKNOWLEDGE: "); log += string; @@ -976,96 +922,75 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& success = false; if (!string.isEmpty()) { - String commandName = parseString(string, 1); // could not find a way to get the command out of the event structure. + String commandName = parseString(string, 1); // could not find a way to get the command out of the event structure. - if (equals(commandName, F("gpio"))) // !ToDo : As gpio is like any other plugin commands should be integrated below! + if (equals(commandName, F(CPLUGIN_014_GPIO_COMMAND))) // !ToDo : As gpio is like any other plugin commands should be integrated + // below! { - int port = event->Par1; // parseString(string, 2).toInt(); - int valueInt = event->Par2; // parseString(string, 3).toInt(); - String valueBool = F("false"); - - if (valueInt == 1) { valueBool = F("true"); } + int port = event->Par1; // parseString(string, 2).toInt(); + int valueInt = event->Par2; // parseString(string, 3).toInt(); + const String valueBool = boolToString(valueInt == 1); - String topic = CPLUGIN_014_PUBLISH; // ControllerSettings.Publish not used because it can be modified by the user! - topic.replace(F("%sysname%"), Settings.getName()); + String topic = CPLUGIN_014_PUBLISH; // ControllerSettings.Publish not used because it can be modified by the user! + C014_replaceSysname(topic); topic.replace(F("%tskname%"), F(CPLUGIN_014_SYSTEM_DEVICE)); - topic.replace(F("%valname%"), String(F(CPLUGIN_014_GPIO_VALUE)) + toString(port, 0)); + topic.replace(F("%valname%"), concat(F(CPLUGIN_014_GPIO_VALUE), port)); success = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic.c_str(), valueBool.c_str(), false); + String log = concat(F("C014 : Acknowledged GPIO"), port); + log += concat(F(" value:"), valueBool); + log += concat(F(" ("), valueInt); + log += ')'; + if (loglevelActiveFor(LOG_LEVEL_INFO) && success) { - String log = F("C014 : Acknowledged GPIO"); - log += port; - log += F(" value:"); - log += valueBool; - log += F(" ("); - log += valueInt; - log += ')'; log += F(" success!"); addLogMove(LOG_LEVEL_INFO, log); } if (loglevelActiveFor(LOG_LEVEL_ERROR) && !success) { - String log = F("C014 : Acknowledged GPIO"); - log += port; - log += F(" value:"); - log += valueBool; - log += F(" ("); - log += valueInt; - log += ')'; log += F(" ERROR!"); addLogMove(LOG_LEVEL_ERROR, log); } } else // not gpio { - taskVarIndex_t taskVarIndex = event->Par2 - 1; + const taskVarIndex_t taskVarIndex = event->Par2 - 1; if (validTaskVarIndex(taskVarIndex)) { userVarIndex_t userVarIndex = event->BaseVarIndex + taskVarIndex; String topic = CPLUGIN_014_PUBLISH; - topic.replace(F("%sysname%"), Settings.getName()); - int deviceIndex = event->Par1; // parseString(string, 2).toInt(); + C014_replaceSysname(topic); + const int deviceIndex = event->Par1; // parseString(string, 2).toInt(); LoadTaskSettings(deviceIndex - 1); const String deviceName = getTaskDeviceName(event->TaskIndex); topic.replace(F("%tskname%"), deviceName); - String valueName = ExtraTaskSettings.TaskDeviceValueNames[event->Par2 - 1]; // parseString(string, 3).toInt()-1]; + const String valueName = ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; // parseString(string, 3).toInt()-1]; topic.replace(F("%valname%"), valueName); String valueStr; int valueInt = 0; - if ((equals(commandName, F("taskvalueset"))) || (equals(commandName, F("dummyvalueset")))) // should work for both + if (equals(commandName, F(CPLUGIN_014_TASKVALUESET_COMMAND))) // removed dummyvalueset command some time ago... { - valueStr = formatUserVarNoCheck(event, taskVarIndex); // parseString(string, 4); + valueStr = formatUserVarNoCheck(event, taskVarIndex); // parseString(string, 4); success = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic.c_str(), valueStr.c_str(), false); + String log = concat(F("C014 : Acknowledged: "), deviceName); + log += concat(F(" var: "), valueName); + log += concat(F(" topic: "), topic); + log += concat(F(" value: "), valueStr); + if (loglevelActiveFor(LOG_LEVEL_INFO) && success) { - String log = F("C014 : Acknowledged: "); - log += deviceName; - log += F(" var: "); - log += valueName; - log += F(" topic: "); - log += topic; - log += F(" value: "); - log += valueStr; log += F(" success!"); addLogMove(LOG_LEVEL_INFO, log); } if (loglevelActiveFor(LOG_LEVEL_ERROR) && !success) { - String log = F("C014 : Aacknowledged: "); - log += deviceName; - log += F(" var: "); - log += valueName; - log += F(" topic: "); - log += topic; - log += F(" value: "); - log += valueStr; log += F(" ERROR!"); addLogMove(LOG_LEVEL_ERROR, log); } - } else if (equals(parseString(commandName, 1), F("homievalueset"))) { // acknolages value form P086 Homie Receiver + } else if (equals(commandName, F(CPLUGIN_014_HOMIEVALUESET_COMMAND))) { // acknowledges value form P086 Homie Receiver switch (Settings.TaskDevicePluginConfig[deviceIndex - 1][taskVarIndex]) { - case 0: // PLUGIN_085_VALUE_INTEGER + case 0: // PLUGIN_085_VALUE_INTEGER valueInt = static_cast(UserVar[userVarIndex]); valueStr = toString(UserVar[userVarIndex], 0); break; @@ -1074,8 +999,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& break; case 2: // PLUGIN_085_VALUE_BOOLEAN - if (UserVar[userVarIndex] == 1) { valueStr = F("true"); } - else { valueStr = F("false"); } + valueStr = boolToString(UserVar[userVarIndex] == 1); break; case 3: // PLUGIN_085_VALUE_STRING // valueStr = ExtraTaskSettings.TaskDeviceFormula[taskVarIndex]; @@ -1096,37 +1020,25 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } success = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic.c_str(), valueStr.c_str(), false); + String log = concat(F("C014 : homie acknowledge: "), deviceName); + if (loglevelActiveFor(LOG_LEVEL_INFO) && success) { - String log = F("C014 : homie acknowledge: "); - log += deviceName; - log += F(" taskIndex:"); - log += deviceIndex; - log += F(" valueNr:"); - log += event->Par2; - log += F(" valueName:"); - log += valueName; - log += F(" valueType:"); - log += Settings.TaskDevicePluginConfig[deviceIndex - 1][taskVarIndex]; - log += F(" topic:"); - log += topic; - log += F(" valueInt:"); - log += valueInt; - log += F(" valueStr:"); - log += valueStr; + log += concat(F(" taskIndex:"), deviceIndex); + log += concat(F(" valueNr:"), event->Par2); + log += concat(F(" valueName:"), valueName); + log += concat(F(" valueType:"), Settings.TaskDevicePluginConfig[deviceIndex - 1][taskVarIndex]); + log += concat(F(" topic:"), topic); + log += concat(F(" valueInt:"), valueInt); + log += concat(F(" valueStr:"), valueStr); log += F(" success!"); addLogMove(LOG_LEVEL_INFO, log); } if (loglevelActiveFor(LOG_LEVEL_ERROR) && !success) { - String log = F("C014 : homie acknowledge: "); - log += deviceName; - log += F(" var: "); - log += valueName; - log += F(" topic: "); - log += topic; - log += F(" value: "); - log += valueStr; - log += F(" failed!"); + log += concat(F(" var: "), valueName); + log += concat(F(" topic: "), topic); + log += concat(F(" value: "), valueStr); + log += F(" ERROR!"); // was: failed! addLogMove(LOG_LEVEL_ERROR, log); } } else // Acknowledge not implemented yet diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp new file mode 100644 index 0000000000..edf11a4f17 --- /dev/null +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -0,0 +1,188 @@ + +#include "../Helpers/_CPlugin_Helper_mqtt.h" + +#if FEATURE_MQTT +# include "../Commands/InternalCommands.h" + +/*************************************************************************************** + * Parse MQTT topic for /cmd and /set ending to handle commands or TaskValueSet + **************************************************************************************/ +bool MQTT_handle_topic_commands(struct EventStruct *event, + bool handleCmd, + bool handleSet, + bool tryRemoteConfig) { + bool handled = false; + + // Topic : event->String1 + // Message: event->String2 + String cmd; + int lastindex = event->String1.lastIndexOf('/'); + const String lastPartTopic = event->String1.substring(lastindex + 1); + + if (!handled && handleCmd && equals(lastPartTopic, F("cmd"))) { + // Example: + // Topic: ESP_Easy/Bathroom_pir_env/cmd + // Message: gpio,14,0 + // Full command: gpio,14,0 + + cmd = event->String2; + + // SP_C005a: string= ;cmd=gpio,12,0 ;taskIndex=12 ;string1=ESPT12/cmd ;string2=gpio,12,0 + handled = true; + } + + if (!handled && handleSet && equals(lastPartTopic, F("set"))) { + // Example: + // Topic: ESP_Easy/DummyTask/DummyVar/set + // Message: 14 + // Full command: TaskValueSet,DummyTask,DummyVar,14 + const String topic = event->String1.substring(0, lastindex); + lastindex = topic.lastIndexOf('/'); + + if (lastindex > -1) { + String taskName = topic.substring(0, lastindex); + String valueName = topic.substring(lastindex + 1); + lastindex = taskName.lastIndexOf('/'); + + if (lastindex > -1) { + taskName = taskName.substring(lastindex + 1); + + if (!taskName.isEmpty() && !valueName.isEmpty() && !event->String2.isEmpty() && + cmd.reserve(12 + 3 + taskName.length() + valueName.length() + event->String2.length())) { + cmd = F("TaskValueSet"); + cmd += ','; + cmd += taskName; + cmd += ','; + cmd += valueName; + cmd += ','; + cmd += event->String2; + handled = true; + } + } + } + } + + if (handled) { + MQTT_execute_command(cmd, tryRemoteConfig); + } + return handled; +} + +/***************************************************************************************** + * Execute commands received via MQTT, sanitize event arguments with regard to commas vs = + * event/asyncevent are added to queue, other commands executed immediately + ****************************************************************************************/ +void MQTT_execute_command(String& cmd, + bool tryRemoteConfig) { + // in case of event, store to buffer and return... + const String command = parseString(cmd, 1); + + if (equals(command, F("event")) || equals(command, F("asyncevent"))) { + if (Settings.UseRules) { + // Need to sanitize the event a bit to allow for sending event values as MQTT messages. + // For example: + // Publish topic: espeasy_node/cmd_arg2/event/myevent/2 + // Message: 1 + // Actual event: myevent=1,2 + + // Strip out the "event" or "asyncevent" part, leaving the actual event string + String args = parseStringToEndKeepCase(cmd, 2); + + { + // Get the first part upto a parameter separator + // Example: "myEvent,1,2,3", which needs to be converted to "myEvent=1,2,3" + // N.B. This may contain the first eventvalue too + // e.g. "myEvent=1,2,3" => "myEvent=1" + String eventName = parseStringKeepCase(args, 1); + String eventValues = parseStringToEndKeepCase(args, 2); + const int equal_pos = eventName.indexOf('='); + + if (equal_pos != -1) { + // We found an '=' character, so the actual event name is everything before that char. + eventName = args.substring(0, equal_pos); + eventValues = args.substring(equal_pos + 1); // Rest of the event, after the '=' char + } + + if (eventValues.startsWith(F(","))) { + // Need to reconstruct the event to get rid of calls like these: + // myevent=,1,2 + eventValues = eventValues.substring(1); + } + + // Now reconstruct the complete event + // Without event values: "myEvent" (no '=' char) + // With event values: "myEvent=1,2,3" + + // Re-using the 'cmd' String as that has pre-allocated memory which is + // known to be large enough to hold the entire event. + args = eventName; + + if (eventValues.length() > 0) { + // Only append an = if there are eventvalues. + args += '='; + args += eventValues; + } + } + + // Check for duplicates, as sometimes a node may have multiple subscriptions to the same topic. + // Then it may add several of the same events in a burst. + eventQueue.addMove(std::move(args), true); + } + } else { + ExecuteCommand(INVALID_TASK_INDEX, EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str(), true, true, tryRemoteConfig); + } +} + +bool MQTT_protocol_send(EventStruct *event, + String pubname, + bool retainFlag) { + bool success = false; + + parseControllerVariables(pubname, event, false); + + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + // MFD: skip publishing for values with empty labels (removes unnecessary publishing of unwanted values) + if (getTaskValueName(event->TaskIndex, x).isEmpty()) { + continue; // we skip values with empty labels + } + String tmppubname = pubname; + parseSingleControllerVariable(tmppubname, event, x, false); + String value; + + if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { + value = event->String2.substring(0, 20); // For the log + } else { + value = formatUserVarNoCheck(event, x); + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = strformat(F("MQTT C%03d : "), event->ControllerIndex); + log += tmppubname; + log += ' '; + log += value; + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG + + // Small optimization so we don't try to copy potentially large strings + if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), + retainFlag)) { + success = true; + } + } else { + // Publish using move operator, thus tmppubname and value are empty after this call + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), std::move(value), + retainFlag)) { + success = true; + } + } + } + return success; +} + +#endif // if FEATURE_MQTT diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.h b/src/src/Helpers/_CPlugin_Helper_mqtt.h new file mode 100644 index 0000000000..f4f056385b --- /dev/null +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.h @@ -0,0 +1,18 @@ +#ifndef CPLUGIN_HELPER_MQTT_H +#define CPLUGIN_HELPER_MQTT_H + +#if FEATURE_MQTT +# include "../Helpers/_CPlugin_Helper.h" + +bool MQTT_handle_topic_commands(struct EventStruct *event, + bool handleCmd = true, + bool handleSet = true, + bool tryRemoteConfig = false); +void MQTT_execute_command(String& command, + bool tryRemoteConfig = false); +bool MQTT_protocol_send(EventStruct *event, + String pubname, + bool retainFlag); + +#endif // if FEATURE_MQTT +#endif // ifndef CPLUGIN_HELPER_MQTT_H From 1839456e770fad9d3b0115152bff894c6f6ece19 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 10 Sep 2023 22:00:19 +0200 Subject: [PATCH 02/11] [P086] Format source using uncrustify --- src/_P086_Homie.ino | 593 +++++++++++++++++++++++--------------------- 1 file changed, 309 insertions(+), 284 deletions(-) diff --git a/src/_P086_Homie.ino b/src/_P086_Homie.ino index f9d4f671a4..4a08ee3d7a 100644 --- a/src/_P086_Homie.ino +++ b/src/_P086_Homie.ino @@ -1,32 +1,36 @@ #include "_Plugin_Helper.h" #ifdef USES_P086 -//####################################################################################################### -//################################## Plugin 086: Homie receiver########################################## -//####################################################################################################### +// ####################################################################################################### +// ################################## Plugin 086: Homie receiver########################################## +// ####################################################################################################### -#define PLUGIN_086 -#define PLUGIN_ID_086 86 -#define PLUGIN_NAME_086 "Generic - Homie receiver" +/** Changelog: + * 2023-09-10 tonhuisman: Add changelog, uncrustify source + */ + +# define PLUGIN_086 +# define PLUGIN_ID_086 86 +# define PLUGIN_NAME_086 "Generic - Homie receiver" // empty default names because settings will be ignored / not used if value name is empty -#define PLUGIN_VALUENAME1_086 "" -#define PLUGIN_VALUENAME2_086 "" -#define PLUGIN_VALUENAME3_086 "" -#define PLUGIN_VALUENAME4_086 "" +# define PLUGIN_VALUENAME1_086 "" +# define PLUGIN_VALUENAME2_086 "" +# define PLUGIN_VALUENAME3_086 "" +# define PLUGIN_VALUENAME4_086 "" -#define PLUGIN_086_VALUE_INTEGER 0 -#define PLUGIN_086_VALUE_FLOAT 1 -#define PLUGIN_086_VALUE_BOOLEAN 2 -#define PLUGIN_086_VALUE_STRING 3 -#define PLUGIN_086_VALUE_ENUM 4 -#define PLUGIN_086_VALUE_RGB 5 -#define PLUGIN_086_VALUE_HSV 6 +# define PLUGIN_086_VALUE_INTEGER 0 +# define PLUGIN_086_VALUE_FLOAT 1 +# define PLUGIN_086_VALUE_BOOLEAN 2 +# define PLUGIN_086_VALUE_STRING 3 +# define PLUGIN_086_VALUE_ENUM 4 +# define PLUGIN_086_VALUE_RGB 5 +# define PLUGIN_086_VALUE_HSV 6 -#define PLUGIN_086_VALUE_TYPES 7 -#define PLUGIN_086_VALUE_MAX 4 +# define PLUGIN_086_VALUE_TYPES 7 +# define PLUGIN_086_VALUE_MAX 4 -#define PLUGIN_086_DEBUG true +# define PLUGIN_086_DEBUG true boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) { @@ -34,307 +38,328 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_086; - Device[deviceCount].Type = DEVICE_TYPE_DUMMY; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].DecimalsOnly = true; - Device[deviceCount].ValueCount = PLUGIN_086_VALUE_MAX; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - Device[deviceCount].GlobalSyncOption = false; - Device[deviceCount].Custom = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_086; + Device[deviceCount].Type = DEVICE_TYPE_DUMMY; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].DecimalsOnly = true; + Device[deviceCount].ValueCount = PLUGIN_086_VALUE_MAX; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + Device[deviceCount].GlobalSyncOption = false; + Device[deviceCount].Custom = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_086); - break; - } + { + string = F(PLUGIN_NAME_086); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_086)); + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_086)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_086)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_086)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_086)); - break; - } + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormNote(F("Translation Plugin for controllers able to receive value updates according to the Homie convention.")); - - uint8_t choice = 0; - String labelText; - String keyName; - const __FlashStringHelper * options[PLUGIN_086_VALUE_TYPES] = { - F("integer"), - F("float"), - F("boolean"), - F("string"), - F("enum"), - F("rgb"), - F("hsv") - }; - const int optionValues[PLUGIN_086_VALUE_TYPES] = { - PLUGIN_086_VALUE_INTEGER, - PLUGIN_086_VALUE_FLOAT, - PLUGIN_086_VALUE_BOOLEAN, - PLUGIN_086_VALUE_STRING, - PLUGIN_086_VALUE_ENUM, - PLUGIN_086_VALUE_RGB, - PLUGIN_086_VALUE_HSV - }; - for (int i=0;iTaskIndex, i), NAME_FORMULA_LENGTH_MAX); - labelText = F("Parameter Type"); - keyName = F("valueType"); - keyName += i; - addFormSelector(labelText, keyName, PLUGIN_086_VALUE_TYPES, options, optionValues, choice ); - keyName += F("_min"); - addFormNumericBox(F("Min"),keyName,Cache.getTaskDevicePluginConfig(event->TaskIndex, i)); - keyName = F("valueType"); - keyName += i; - keyName += F("_max"); - addFormNumericBox(F("Max"),keyName,Cache.getTaskDevicePluginConfig(event->TaskIndex, i+PLUGIN_086_VALUE_MAX)); - if (i==0) addFormNote(F("min max values only valid for numeric parameter")); - keyName = F("decimals"); - keyName += i; - addFormNumericBox(F("Decimals"),keyName,Cache.getTaskDeviceValueDecimals(event->TaskIndex, i) ,0,8); - if (i==0) addFormNote(F("Decimal counts for float parameter")); - keyName = F("string"); - keyName += i; - addFormTextBox(F("String or enum"), keyName, Cache.getTaskDeviceFormula(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); - if (i==0) addFormNote(F("Default string or enumumeration list (comma seperated).")); - } - success = true; - break; + { + addFormNote(F("Translation Plugin for controllers able to receive value updates according to the Homie convention.")); + + uint8_t choice = 0; + String labelText; + String keyName; + const __FlashStringHelper *options[PLUGIN_086_VALUE_TYPES] = { + F("integer"), + F("float"), + F("boolean"), + F("string"), + F("enum"), + F("rgb"), + F("hsv") + }; + const int optionValues[PLUGIN_086_VALUE_TYPES] = { + PLUGIN_086_VALUE_INTEGER, + PLUGIN_086_VALUE_FLOAT, + PLUGIN_086_VALUE_BOOLEAN, + PLUGIN_086_VALUE_STRING, + PLUGIN_086_VALUE_ENUM, + PLUGIN_086_VALUE_RGB, + PLUGIN_086_VALUE_HSV + }; + + for (int i = 0; i < PLUGIN_086_VALUE_MAX; i++) { + labelText = F("Function #"); + labelText += (i + 1); + addFormSubHeader(labelText); + choice = PCONFIG(i); + + if (i == 0) { addFormNote(F("Triggers an event when a ../%event%/set topic arrives")); } + labelText = F("Event Name"); + keyName = F("functionName"); + keyName += i; + addFormTextBox(labelText, keyName, Cache.getTaskDeviceValueName(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); + labelText = F("Parameter Type"); + keyName = F("valueType"); + keyName += i; + addFormSelector(labelText, keyName, PLUGIN_086_VALUE_TYPES, options, optionValues, choice); + keyName += F("_min"); + addFormNumericBox(F("Min"), keyName, Cache.getTaskDevicePluginConfig(event->TaskIndex, i)); + keyName = F("valueType"); + keyName += i; + keyName += F("_max"); + addFormNumericBox(F("Max"), keyName, Cache.getTaskDevicePluginConfig(event->TaskIndex, i + PLUGIN_086_VALUE_MAX)); + + if (i == 0) { addFormNote(F("min max values only valid for numeric parameter")); } + keyName = F("decimals"); + keyName += i; + addFormNumericBox(F("Decimals"), keyName, Cache.getTaskDeviceValueDecimals(event->TaskIndex, i), 0, 8); + + if (i == 0) { addFormNote(F("Decimal counts for float parameter")); } + keyName = F("string"); + keyName += i; + addFormTextBox(F("String or enum"), keyName, Cache.getTaskDeviceFormula(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); + + if (i == 0) { addFormNote(F("Default string or enumumeration list (comma seperated).")); } } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - String keyName; - for (int i=0;iTaskIndex, x); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - break; + String log = F("P086 : Value "); + log += x + 1; + log += F(": "); + log += formatUserVarNoCheck(event->TaskIndex, x); + addLogMove(LOG_LEVEL_INFO, log); } + success = true; + break; + } case PLUGIN_WRITE: + { + String command = parseString(string, 1); + + if (equals(command, F("homievalueset"))) { - String command = parseString(string, 1); - if (equals(command, F("homievalueset"))) - { - const taskVarIndex_t taskVarIndex = event->Par2 - 1; - const userVarIndex_t userVarIndex = event->BaseVarIndex + taskVarIndex; - if (validTaskIndex(event->TaskIndex) && - validTaskVarIndex(taskVarIndex) && - validUserVarIndex(userVarIndex) && - (event->Par1 == (event->TaskIndex + 1))) {// make sure that this instance is the target - String parameter = parseStringToEndKeepCase(string,4); - String log; -/* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - log = F("P086 : Acknowledge :"); - log += string; - log += F(" / "); - log += ExtraTaskSettings.TaskDeviceName; - log += F(" / "); - log += ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; - log += F(" sensorType:"); - log += event->sensorType; - log += F(" Source:"); - log += event->Source; - log += F(" idx:"); - log += event->idx; - log += F(" S1:"); - log += event->String1; - log += F(" S2:"); - log += event->String2; - log += F(" S3:"); - log += event->String3; - log += F(" S4:"); - log += event->String4; - log += F(" S5:"); - log += event->String5; - log += F(" P1:"); - log += event->Par1; - log += F(" P2:"); - log += event->Par2; - log += F(" P3:"); - log += event->Par3; - log += F(" P4:"); - log += event->Par4; - log += F(" P5:"); - log += event->Par5; - addLog(LOG_LEVEL_DEBUG, log); - } */ - float floatValue = 0.0f; - String enumList; - int i = 0; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("P086 : deviceNr:"); - log += event->TaskIndex + 1; - log += F(" valueNr:"); - log += event->Par2; - log += F(" valueType:"); - log += Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]; - } - - switch (Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]) { - case PLUGIN_086_VALUE_INTEGER: - case PLUGIN_086_VALUE_FLOAT: - if (!parameter.isEmpty()) { - if (string2float(parameter,floatValue)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" integer/float set to "); - log += floatValue; - addLogMove(LOG_LEVEL_INFO, log); - } - UserVar[userVarIndex]=floatValue; - } else { // float conversion failed! - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - log += F(" parameter:"); - log += parameter; - log += F(" not a float value!"); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - } else { + const taskVarIndex_t taskVarIndex = event->Par2 - 1; + const userVarIndex_t userVarIndex = event->BaseVarIndex + taskVarIndex; + + if (validTaskIndex(event->TaskIndex) && + validTaskVarIndex(taskVarIndex) && + validUserVarIndex(userVarIndex) && + (event->Par1 == (event->TaskIndex + 1))) { // make sure that this instance is the target + String parameter = parseStringToEndKeepCase(string, 4); + String log; + + /* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log = F("P086 : Acknowledge :"); + log += string; + log += F(" / "); + log += ExtraTaskSettings.TaskDeviceName; + log += F(" / "); + log += ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; + log += F(" sensorType:"); + log += event->sensorType; + log += F(" Source:"); + log += event->Source; + log += F(" idx:"); + log += event->idx; + log += F(" S1:"); + log += event->String1; + log += F(" S2:"); + log += event->String2; + log += F(" S3:"); + log += event->String3; + log += F(" S4:"); + log += event->String4; + log += F(" S5:"); + log += event->String5; + log += F(" P1:"); + log += event->Par1; + log += F(" P2:"); + log += event->Par2; + log += F(" P3:"); + log += event->Par3; + log += F(" P4:"); + log += event->Par4; + log += F(" P5:"); + log += event->Par5; + addLog(LOG_LEVEL_DEBUG, log); + } */ + float floatValue = 0.0f; + String enumList; + int i = 0; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log = F("P086 : deviceNr:"); + log += event->TaskIndex + 1; + log += F(" valueNr:"); + log += event->Par2; + log += F(" valueType:"); + log += Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]; + } + + switch (Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]) { + case PLUGIN_086_VALUE_INTEGER: + case PLUGIN_086_VALUE_FLOAT: + + if (!parameter.isEmpty()) { + if (string2float(parameter, floatValue)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" value:"); - log += UserVar[userVarIndex]; + log += F(" integer/float set to "); + log += floatValue; addLogMove(LOG_LEVEL_INFO, log); } - } - break; - - case PLUGIN_086_VALUE_BOOLEAN: - if (parameter=="false") { - floatValue = 0.0f; - } else { - floatValue = 1.0f; - } - UserVar[userVarIndex]=floatValue; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" boolean set to "); - log += floatValue; - addLogMove(LOG_LEVEL_INFO, log); - } - break; - - case PLUGIN_086_VALUE_STRING: - //String values not stored to conserve flash memory - //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" string set to "); - log += parameter; - addLogMove(LOG_LEVEL_INFO, log); - } - break; - - case PLUGIN_086_VALUE_ENUM: - enumList = Cache.getTaskDeviceFormula(event->TaskIndex, taskVarIndex); - i = 1; - while (!parseString(enumList,i).isEmpty()) { // lookup result in enum List - if (parseString(enumList,i)==parameter) { - floatValue = i; - break; + UserVar[userVarIndex] = floatValue; + } else { // float conversion failed! + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + log += F(" parameter:"); + log += parameter; + log += F(" not a float value!"); + addLogMove(LOG_LEVEL_ERROR, log); } - i++; } - UserVar[userVarIndex]=floatValue; + } else { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" enum set to "); - log += floatValue; - log += ' '; - log += wrap_braces(parameter); + log += F(" value:"); + log += UserVar[userVarIndex]; addLogMove(LOG_LEVEL_INFO, log); } - break; + } + break; - case PLUGIN_086_VALUE_RGB: - //String values not stored to conserve flash memory - //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" RGB received "); - log += parameter; - addLogMove(LOG_LEVEL_INFO, log); - } - break; + case PLUGIN_086_VALUE_BOOLEAN: - case PLUGIN_086_VALUE_HSV: - //String values not stored to conserve flash memory - //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" HSV received "); - log += parameter; - addLogMove(LOG_LEVEL_INFO, log); + if (parameter == "false") { + floatValue = 0.0f; + } else { + floatValue = 1.0f; + } + UserVar[userVarIndex] = floatValue; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" boolean set to "); + log += floatValue; + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_STRING: + + // String values not stored to conserve flash memory + // safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), + // sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" string set to "); + log += parameter; + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_ENUM: + enumList = Cache.getTaskDeviceFormula(event->TaskIndex, taskVarIndex); + i = 1; + + while (!parseString(enumList, i).isEmpty()) { // lookup result in enum List + if (parseString(enumList, i) == parameter) { + floatValue = i; + break; } - break; - } - success = true; + i++; + } + UserVar[userVarIndex] = floatValue; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" enum set to "); + log += floatValue; + log += ' '; + log += wrap_braces(parameter); + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_RGB: + + // String values not stored to conserve flash memory + // safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), + // sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" RGB received "); + log += parameter; + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_HSV: + + // String values not stored to conserve flash memory + // safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), + // sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" HSV received "); + log += parameter; + addLogMove(LOG_LEVEL_INFO, log); + } + break; } + success = true; } } break; } + } return success; } + #endif // USES_P086 From df0fb7ac6c236585f4e97470002e9f30fa319406 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 16 Sep 2023 22:53:04 +0200 Subject: [PATCH 03/11] [MQTT] Add support for P086 events to all MQTT Controllers --- src/_C014.cpp | 133 +++---------- src/_P086_Homie.ino | 231 ++++++++++------------- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 70 +++++-- 3 files changed, 184 insertions(+), 250 deletions(-) diff --git a/src/_C014.cpp b/src/_C014.cpp index f756ca2026..767d221496 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -83,8 +83,7 @@ bool CPlugin_014_sendMQTTdevice(String tmppubname, String log; if (loglevelActiveFor(LOG_LEVEL_ERROR)) { // Also true for LOG_LEVEL_DEBUG_MORE - log = concat(F("C014 : T:"), topic); - log += concat(F(" P: "), payload); + log = strformat(F("C014 : T:%s P: %s"), String(topic).c_str(), payload.c_str()); } # ifndef BUILD_NO_DEBUG @@ -130,9 +129,7 @@ bool CPlugin_014_sendMQTTnode(String tmppubname, String log; if (loglevelActiveFor(LOG_LEVEL_ERROR)) { // Also true for LOG_LEVEL_DEBUG_MORE - log = concat(F("C014 : V:"), value); - log += concat(F(" T: "), topic); - log += concat(F(" P: "), payload); + log = strformat(F("C014 : V:%s T: %s P: %s"), value.c_str(), topic.c_str(), payload.c_str()); } # ifndef BUILD_NO_DEBUG @@ -224,7 +221,6 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& // alert: this is the state the device is when connected to the MQTT broker, but something wrong is happening. E.g. a sensor is // not providing data and needs human intervention. You have to send this message when something is wrong. CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("alert"), errorCounter); - success = false; } else { // ready: this is the state the device is in when it is connected to the MQTT broker, has sent all Homie messages and is ready to // operate. You have to send this message after all other announcements message have been sent. @@ -239,9 +235,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& if (errorCounter > 0) { log += errorCounter; } else { log += F("no"); } - log += F(" errors! ("); - log += msgCounter; - log += F(" messages)"); + log += strformat(F(" errors! (%d messages)"), msgCounter); msgCounter = 0; addLogMove(LOG_LEVEL_DEBUG, log); } @@ -565,10 +559,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = concat(F("C014 : Device has custom values: "), - getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x))); - log += F(" not implemented!"); - addLogMove(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, strformat(F("C014 : Device has custom values: %s not implemented!"), + getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x)).c_str())); } # endif // ifndef BUILD_NO_DEBUG } @@ -611,10 +603,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = concat(F("C014 : Device Disabled: "), - getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x))); - log += F(" not propagated!"); - addLogMove(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, concat(F("C014 : Device Disabled: %s not propagated!"), + getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x)).c_str())); } # endif // ifndef BUILD_NO_DEBUG } @@ -632,7 +622,6 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& // alert: this is the state the device is when connected to the MQTT broker, but something wrong is happening. E.g. a sensor is not // providing data and needs human intervention. You have to send this message when something is wrong. CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("alert"), errorCounter); - success = false; } else { // ready: this is the state the device is in when it is connected to the MQTT broker, has sent all Homie messages and is ready to // operate. You have to send this message after all other announcements message have been sent. @@ -647,8 +636,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& if (errorCounter > 0) { log += errorCounter; } else { log += F("no"); } - log += concat(F(" errors! ("), msgCounter); - log += F(" messages)"); + log += strformat(F(" errors! (%d messages)"), msgCounter); addLogMove(LOG_LEVEL_INFO, log); } msgCounter = 0; @@ -695,17 +683,14 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& { controllerIndex_t ControllerID = findFirstEnabledControllerWithId(CPLUGIN_ID_014); bool validTopic = false; + bool cmdExecuted = false; if (!validControllerIndex(ControllerID)) { // Controller is not enabled. break; } else { String cmd; - int valueNr = 0; - taskIndex_t taskIndex = INVALID_TASK_INDEX; - struct EventStruct TempEvent(event->TaskIndex); - TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_MQTT; // to trigger the correct acknowledgment - int lastindex = event->String1.lastIndexOf('/'); + int lastindex = event->String1.lastIndexOf('/'); errorCounter = 0; if (equals(event->String1.substring(lastindex + 1), F("set"))) @@ -720,22 +705,20 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& String log; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("C014 : MQTT received: "); - log += concat(F("/set: N: "), nodeName); - log += concat(F(" V: "), valueName); + log = strformat(F("C014 : MQTT received: /set: N: %s V: %s"), nodeName.c_str(), valueName.c_str()); } - if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device + if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device { - if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values + if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values { - const size_t gpio_value_tag_length = String(F(CPLUGIN_014_GPIO_VALUE)).length(); + const size_t gpio_value_tag_length = String(F(CPLUGIN_014_GPIO_VALUE)).length(); // FIXME use fixed length or constexpr - cmd = concat(F("GPIO,"), valueName.substring(gpio_value_tag_length).toInt()); // get the GPIO + cmd = concat(F("GPIO,"), valueName.substring(gpio_value_tag_length).toInt()); // get the GPIO cmd += ','; - if (equals(event->String2, F("true")) || equals(event->String2, '1')) { - cmd += '1'; + if (equals(event->String2, F("true")) || equals(event->String2, '1')) { // Homie spec says it should be 'true' or + cmd += '1'; // 'false'... } else { cmd += '0'; } @@ -751,70 +734,14 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } } else // msg to a receiving plugin { - taskIndex = findTaskIndexByName(nodeName); - const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - const taskVarIndex_t taskVarIndex = event->Par2 - 1; - - if (validDeviceIndex(deviceIndex) && validTaskVarIndex(taskVarIndex)) { - const int pluginID = Device[deviceIndex].Number; - - if (pluginID == 33) // Plugin 33 Dummy Device - { // TaskValueSet,,,, works only with - // new version of P033! - valueNr = findDeviceValueIndexByName(valueName, taskIndex); - - if (valueNr != VARS_PER_TASK) // value Name identified - { - // Set a Dummy Device Value and the device Number - cmd = concat(F("TaskValueSet,"), taskIndex + 1); - cmd += ','; - cmd += (valueNr + 1); // set the value Number - cmd += ','; - cmd += event->String2; // expect float as payload! - validTopic = true; - } - } else if (pluginID == 86) { // Plugin Homie receiver. Schedules the event defined in the plugin. Does NOT store the - // value. Use HomieValueSet to save the value. This will acknowledge back to the - // controller too. - valueNr = findDeviceValueIndexByName(valueName, taskIndex); - - if (valueNr != VARS_PER_TASK) { - cmd = concat(F("event,"), valueName); - cmd += '='; - - if (Settings.TaskDevicePluginConfig[taskIndex][valueNr] == 3) { // Quote String parameters. PLUGIN_086_VALUE_STRING - cmd += wrapWithQuotes(event->String2); - } else { - if (Settings.TaskDevicePluginConfig[taskIndex][valueNr] == 4) { // Enumeration parameter, find Number of item. - // PLUGIN_086_VALUE_ENUM - const String enumList = ExtraTaskSettings.TaskDeviceFormula[taskVarIndex]; - int i = 1; - String part = parseStringKeepCase(enumList, i); - - while (!part.isEmpty()) { // lookup result in enum List is changed to lowercase - if (part.equalsIgnoreCase(event->String2)) { - break; - } - i++; - part = parseStringKeepCase(enumList, i); - } - cmd += i; - cmd += ','; - } - cmd += event->String2; - } - validTopic = true; - } - } - } + // Only handle /set case that supports P033 Dummy device and P086 Homie receiver + cmdExecuted = MQTT_handle_topic_commands(event, false, true); + validTopic = true; // Avoid error messages } if (validTopic) { - parseCommandString(&TempEvent, cmd); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += concat(F(" cmd: "), cmd); - log += F(" OK"); + log += strformat(F(" cmd: %s OK"), cmd.c_str()); addLog(LOG_LEVEL_INFO, log); } } else if (loglevelActiveFor(LOG_LEVEL_INFO)) { @@ -823,7 +750,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } } - if (validTopic) { + if (validTopic && !cmdExecuted) { // Don't execute twice MQTT_execute_command(cmd, true); } } @@ -919,7 +846,6 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& log += event->Par5; addLog(LOG_LEVEL_DEBUG, log); } */ - success = false; if (!string.isEmpty()) { String commandName = parseString(string, 1); // could not find a way to get the command out of the event structure. @@ -939,9 +865,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& success = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic.c_str(), valueBool.c_str(), false); String log = concat(F("C014 : Acknowledged GPIO"), port); - log += concat(F(" value:"), valueBool); - log += concat(F(" ("), valueInt); - log += ')'; + log += strformat(F(" value:%s (%d)"), valueBool.c_str(), valueInt); if (loglevelActiveFor(LOG_LEVEL_INFO) && success) { log += F(" success!"); @@ -974,10 +898,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& valueStr = formatUserVarNoCheck(event, taskVarIndex); // parseString(string, 4); success = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic.c_str(), valueStr.c_str(), false); - String log = concat(F("C014 : Acknowledged: "), deviceName); - log += concat(F(" var: "), valueName); - log += concat(F(" topic: "), topic); - log += concat(F(" value: "), valueStr); + String log = strformat(F("C014 : Acknowledged: %s var: %s topic: %s value: %s"), + deviceName.c_str(), valueName.c_str(), topic.c_str(), valueStr.c_str()); if (loglevelActiveFor(LOG_LEVEL_INFO) && success) { log += F(" success!"); @@ -1035,9 +957,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } if (loglevelActiveFor(LOG_LEVEL_ERROR) && !success) { - log += concat(F(" var: "), valueName); - log += concat(F(" topic: "), topic); - log += concat(F(" value: "), valueStr); + log += strformat(F(" var: %s topic: %s value: %s"), + valueName.c_str(), topic.c_str(), valueStr.c_str()); log += F(" ERROR!"); // was: failed! addLogMove(LOG_LEVEL_ERROR, log); } diff --git a/src/_P086_Homie.ino b/src/_P086_Homie.ino index 4a08ee3d7a..fc7b75d29f 100644 --- a/src/_P086_Homie.ino +++ b/src/_P086_Homie.ino @@ -6,6 +6,7 @@ // ####################################################################################################### /** Changelog: + * 2023-09-10 tonhuisman: Reduce string usage to lower the .bin footprint * 2023-09-10 tonhuisman: Add changelog, uncrustify source */ @@ -27,8 +28,12 @@ # define PLUGIN_086_VALUE_RGB 5 # define PLUGIN_086_VALUE_HSV 6 -# define PLUGIN_086_VALUE_TYPES 7 -# define PLUGIN_086_VALUE_MAX 4 +// Unsupported (yet) value types: +// - Percent +// - DateTime (convert to linuxtime?) +// - Duration + +# define PLUGIN_086_VALUE_MAX VARS_PER_TASK # define PLUGIN_086_DEBUG true @@ -74,12 +79,11 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { + # ifndef BUILD_NO_DEBUG addFormNote(F("Translation Plugin for controllers able to receive value updates according to the Homie convention.")); + # endif // ifndef BUILD_NO_DEBUG - uint8_t choice = 0; - String labelText; - String keyName; - const __FlashStringHelper *options[PLUGIN_086_VALUE_TYPES] = { + const __FlashStringHelper *options[] = { F("integer"), F("float"), F("boolean"), @@ -88,7 +92,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) F("rgb"), F("hsv") }; - const int optionValues[PLUGIN_086_VALUE_TYPES] = { + const int optionValues[] = { PLUGIN_086_VALUE_INTEGER, PLUGIN_086_VALUE_FLOAT, PLUGIN_086_VALUE_BOOLEAN, @@ -97,40 +101,34 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) PLUGIN_086_VALUE_RGB, PLUGIN_086_VALUE_HSV }; + constexpr int PLUGIN_086_VALUE_TYPES = NR_ELEMENTS(optionValues); for (int i = 0; i < PLUGIN_086_VALUE_MAX; i++) { - labelText = F("Function #"); - labelText += (i + 1); - addFormSubHeader(labelText); - choice = PCONFIG(i); - - if (i == 0) { addFormNote(F("Triggers an event when a ../%event%/set topic arrives")); } - labelText = F("Event Name"); - keyName = F("functionName"); - keyName += i; - addFormTextBox(labelText, keyName, Cache.getTaskDeviceValueName(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); - labelText = F("Parameter Type"); - keyName = F("valueType"); - keyName += i; - addFormSelector(labelText, keyName, PLUGIN_086_VALUE_TYPES, options, optionValues, choice); - keyName += F("_min"); - addFormNumericBox(F("Min"), keyName, Cache.getTaskDevicePluginConfig(event->TaskIndex, i)); - keyName = F("valueType"); - keyName += i; - keyName += F("_max"); - addFormNumericBox(F("Max"), keyName, Cache.getTaskDevicePluginConfig(event->TaskIndex, i + PLUGIN_086_VALUE_MAX)); - - if (i == 0) { addFormNote(F("min max values only valid for numeric parameter")); } - keyName = F("decimals"); - keyName += i; - addFormNumericBox(F("Decimals"), keyName, Cache.getTaskDeviceValueDecimals(event->TaskIndex, i), 0, 8); - - if (i == 0) { addFormNote(F("Decimal counts for float parameter")); } - keyName = F("string"); - keyName += i; - addFormTextBox(F("String or enum"), keyName, Cache.getTaskDeviceFormula(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); - - if (i == 0) { addFormNote(F("Default string or enumumeration list (comma seperated).")); } + addFormSubHeader(concat(F("Function #"), i + 1)); + + if (i == 0) { addFormNote(F("Triggers an event when a ../%taskname%/%event%/set MQTT topic arrives")); } + + addFormTextBox(F("Event Name"), getPluginCustomArgName((i * 10) + 0), + Cache.getTaskDeviceValueName(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); + addFormSelector(F("Parameter Type"), getPluginCustomArgName((i * 10) + 1), + PLUGIN_086_VALUE_TYPES, options, optionValues, PCONFIG(i)); + + addFormNumericBox(F("Min"), getPluginCustomArgName((i * 10) + 2), + Cache.getTaskDevicePluginConfig(event->TaskIndex, i)); + addFormNumericBox(F("Max"), getPluginCustomArgName((i * 10) + 3), + Cache.getTaskDevicePluginConfig(event->TaskIndex, i + PLUGIN_086_VALUE_MAX)); + + if (i == 0) { addFormNote(F("min/max values only valid for numeric parameter")); } + + addFormNumericBox(F("Decimals"), getPluginCustomArgName((i * 10) + 4), + Cache.getTaskDeviceValueDecimals(event->TaskIndex, i), 0, 8); + + if (i == 0) { addFormNote(F("Decimal precision for float parameter")); } + + addFormTextBox(F("String or enum"), getPluginCustomArgName((i * 10) + 5), + Cache.getTaskDeviceFormula(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); + + if (i == 0) { addFormNote(F("Default string or enumeration list (comma separated)")); } } success = true; break; @@ -138,30 +136,15 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { - String keyName; - for (int i = 0; i < PLUGIN_086_VALUE_MAX; i++) { - keyName = F("valueType"); - keyName += i; - PCONFIG(i) = getFormItemInt(keyName); - keyName = F("functionName"); - keyName += i; - strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceValueNames[i], keyName); - keyName = F("valueType"); - keyName += i; - keyName += F("_min"); - ExtraTaskSettings.TaskDevicePluginConfig[i] = getFormItemInt(keyName); - keyName = F("valueType"); - keyName += i; - keyName += F("_max"); - ExtraTaskSettings.TaskDevicePluginConfig[i + PLUGIN_086_VALUE_MAX] = getFormItemInt(keyName); - keyName = F("decimals"); - keyName += i; - ExtraTaskSettings.TaskDeviceValueDecimals[i] = getFormItemInt(keyName); - keyName = F("string"); - keyName += i; - strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceFormula[i], keyName); + strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceValueNames[i], getPluginCustomArgName((i * 10) + 0)); + PCONFIG(i) = getFormItemInt(getPluginCustomArgName((i * 10) + 1)); + ExtraTaskSettings.TaskDevicePluginConfig[i] = getFormItemInt(getPluginCustomArgName((i * 10) + 2)); + ExtraTaskSettings.TaskDevicePluginConfig[i + PLUGIN_086_VALUE_MAX] = getFormItemInt(getPluginCustomArgName((i * 10) + 3)); + ExtraTaskSettings.TaskDeviceValueDecimals[i] = getFormItemInt(getPluginCustomArgName((i * 10) + 4)); + strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceFormula[i], getPluginCustomArgName((i * 10) + 5)); } + success = true; break; } @@ -174,13 +157,10 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_READ: { - for (uint8_t x = 0; x < PLUGIN_086_VALUE_MAX; x++) - { - String log = F("P086 : Value "); - log += x + 1; - log += F(": "); - log += formatUserVarNoCheck(event->TaskIndex, x); - addLogMove(LOG_LEVEL_INFO, log); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + for (uint8_t x = 0; x < PLUGIN_086_VALUE_MAX; x++) { + addLogMove(LOG_LEVEL_INFO, strformat(F("P086 : Value %d: %s"), x + 1, formatUserVarNoCheck(event->TaskIndex, x).c_str())); + } } success = true; break; @@ -202,52 +182,49 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) String parameter = parseStringToEndKeepCase(string, 4); String log; - /* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - log = F("P086 : Acknowledge :"); - log += string; - log += F(" / "); - log += ExtraTaskSettings.TaskDeviceName; - log += F(" / "); - log += ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; - log += F(" sensorType:"); - log += event->sensorType; - log += F(" Source:"); - log += event->Source; - log += F(" idx:"); - log += event->idx; - log += F(" S1:"); - log += event->String1; - log += F(" S2:"); - log += event->String2; - log += F(" S3:"); - log += event->String3; - log += F(" S4:"); - log += event->String4; - log += F(" S5:"); - log += event->String5; - log += F(" P1:"); - log += event->Par1; - log += F(" P2:"); - log += event->Par2; - log += F(" P3:"); - log += event->Par3; - log += F(" P4:"); - log += event->Par4; - log += F(" P5:"); - log += event->Par5; - addLog(LOG_LEVEL_DEBUG, log); - } */ + /* + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log = F("P086 : Acknowledge :"); + log += string; + log += F(" / "); + log += ExtraTaskSettings.TaskDeviceName; + log += F(" / "); + log += ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; + log += F(" sensorType:"); + log += event->sensorType; + log += F(" Source:"); + log += event->Source; + log += F(" idx:"); + log += event->idx; + log += F(" S1:"); + log += event->String1; + log += F(" S2:"); + log += event->String2; + log += F(" S3:"); + log += event->String3; + log += F(" S4:"); + log += event->String4; + log += F(" S5:"); + log += event->String5; + log += F(" P1:"); + log += event->Par1; + log += F(" P2:"); + log += event->Par2; + log += F(" P3:"); + log += event->Par3; + log += F(" P4:"); + log += event->Par4; + log += F(" P5:"); + log += event->Par5; + addLog(LOG_LEVEL_DEBUG, log); + } */ float floatValue = 0.0f; String enumList; int i = 0; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("P086 : deviceNr:"); - log += event->TaskIndex + 1; - log += F(" valueNr:"); - log += event->Par2; - log += F(" valueType:"); - log += Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]; + log = strformat(F("P086 : deviceNr: %d valueNr: %d valueType: %d"), + event->TaskIndex + 1, event->Par2, Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]); } switch (Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]) { @@ -257,23 +234,20 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) if (!parameter.isEmpty()) { if (string2float(parameter, floatValue)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" integer/float set to "); - log += floatValue; + log += concat(F(" integer/float set to "), floatValue); addLogMove(LOG_LEVEL_INFO, log); } UserVar[userVarIndex] = floatValue; } else { // float conversion failed! if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - log += F(" parameter:"); - log += parameter; + log += concat(F(" parameter: "), parameter); log += F(" not a float value!"); addLogMove(LOG_LEVEL_ERROR, log); } } } else { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" value:"); - log += UserVar[userVarIndex]; + log += concat(F(" value: "), UserVar[userVarIndex]); addLogMove(LOG_LEVEL_INFO, log); } } @@ -281,7 +255,8 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_086_VALUE_BOOLEAN: - if (parameter == "false") { + if (parameter.equalsIgnoreCase(F("false"))) { // This should be a case-sensitive check... + // and also check for "true" floatValue = 0.0f; } else { floatValue = 1.0f; @@ -289,8 +264,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) UserVar[userVarIndex] = floatValue; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" boolean set to "); - log += floatValue; + log += concat(F(" boolean set to "), floatValue); addLogMove(LOG_LEVEL_INFO, log); } break; @@ -301,42 +275,42 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) // safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), // sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" string set to "); - log += parameter; + log += concat(F(" string set to "), parameter); addLogMove(LOG_LEVEL_INFO, log); } break; case PLUGIN_086_VALUE_ENUM: + { enumList = Cache.getTaskDeviceFormula(event->TaskIndex, taskVarIndex); i = 1; + String enumItem = parseStringKeepCase(enumList, i); - while (!parseString(enumList, i).isEmpty()) { // lookup result in enum List - if (parseString(enumList, i) == parameter) { + while (!enumItem.isEmpty()) { // lookup result in enum List + if (enumItem.equalsIgnoreCase(parameter)) { // This should be a case-sensitive check... floatValue = i; break; } i++; + enumItem = parseStringKeepCase(enumList, i); } UserVar[userVarIndex] = floatValue; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" enum set to "); - log += floatValue; + log += concat(F(" enum set to "), floatValue); log += ' '; log += wrap_braces(parameter); addLogMove(LOG_LEVEL_INFO, log); } break; - + } case PLUGIN_086_VALUE_RGB: // String values not stored to conserve flash memory // safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), // sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" RGB received "); - log += parameter; + log += concat(F(" RGB received "), parameter); addLogMove(LOG_LEVEL_INFO, log); } break; @@ -347,8 +321,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) // safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), // sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" HSV received "); - log += parameter; + log += concat(F(" HSV received "), parameter); addLogMove(LOG_LEVEL_INFO, log); } break; diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index edf11a4f17..31371f86f1 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -6,6 +6,7 @@ /*************************************************************************************** * Parse MQTT topic for /cmd and /set ending to handle commands or TaskValueSet + * Special C014 case: handleCmd = false and handleSet is true, so *only* pluginID 33 & 86 are accepted **************************************************************************************/ bool MQTT_handle_topic_commands(struct EventStruct *event, bool handleCmd, @@ -47,16 +48,59 @@ bool MQTT_handle_topic_commands(struct EventStruct *event, if (lastindex > -1) { taskName = taskName.substring(lastindex + 1); - if (!taskName.isEmpty() && !valueName.isEmpty() && !event->String2.isEmpty() && - cmd.reserve(12 + 3 + taskName.length() + valueName.length() + event->String2.length())) { - cmd = F("TaskValueSet"); - cmd += ','; - cmd += taskName; - cmd += ','; - cmd += valueName; - cmd += ','; - cmd += event->String2; - handled = true; + const taskIndex_t taskIndex = findTaskIndexByName(taskName); + const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + const taskVarIndex_t taskVarIndex = event->Par2 - 1; + uint8_t valueNr; + + if (validDeviceIndex(deviceIndex) && validTaskVarIndex(taskVarIndex)) { + const int pluginID = Device[deviceIndex].Number; + + if ((pluginID == 33) || // Plugin 33 Dummy Device, + // backward compatible behavior: if handleCmd = true then execute TaskValueSet regardless of AllowTaskValueSetAllPlugins + ((handleCmd || Settings.AllowTaskValueSetAllPlugins()) && (pluginID != 86))) + { // TaskValueSet,,,, works only with new + // version of P033! + valueNr = findDeviceValueIndexByName(valueName, taskIndex); + + if (validTaskVarIndex(valueNr)) // value Name identified + { + // Set a Dummy Device Value, device Number, var number and argument + cmd = strformat(F("TaskValueSet,%d,%d,%s"), taskIndex + 1, valueNr + 1, event->String2.c_str()); + handled = true; + } + } else if (pluginID == 86) { // Plugin 86 Homie receiver. Schedules the event defined in the plugin. + // Does NOT store the value. + // Use HomieValueSet to save the value. This will acknowledge back to the controller too. + valueNr = findDeviceValueIndexByName(valueName, taskIndex); + + if (validTaskVarIndex(valueNr)) { + cmd = strformat(F("event,%s="), valueName.c_str()); + + if (Settings.TaskDevicePluginConfig[taskIndex][valueNr] == 3) { // Quote String parameters. PLUGIN_086_VALUE_STRING + cmd += wrapWithQuotes(event->String2); + } else { + if (Settings.TaskDevicePluginConfig[taskIndex][valueNr] == 4) { // Enumeration parameter, find Number of item. + // PLUGIN_086_VALUE_ENUM + const String enumList = ExtraTaskSettings.TaskDeviceFormula[taskVarIndex]; + int i = 1; + String part = parseStringKeepCase(enumList, i); + + while (!part.isEmpty()) { // lookup result in enum List, keep it backward compatible, but + if (part.equalsIgnoreCase(event->String2)) { // Homie spec says it should be case-sensitive... + break; + } + i++; + part = parseStringKeepCase(enumList, i); + } + cmd += i; + cmd += ','; + } + cmd += event->String2; + } + handled = true; + } + } } } } @@ -160,11 +204,7 @@ bool MQTT_protocol_send(EventStruct *event, # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = strformat(F("MQTT C%03d : "), event->ControllerIndex); - log += tmppubname; - log += ' '; - log += value; - addLogMove(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, strformat(F("MQTT C%03d : %s %s"), event->ControllerIndex, tmppubname.c_str(), value.c_str())); } # endif // ifndef BUILD_NO_DEBUG From 95236d36a4c1f5801f38fd2d54cc3201f44e6a6f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 16 Sep 2023 23:01:26 +0200 Subject: [PATCH 04/11] [MQTT] Only handle plugin if plugin included --- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index 31371f86f1..88d390e139 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -56,6 +56,8 @@ bool MQTT_handle_topic_commands(struct EventStruct *event, if (validDeviceIndex(deviceIndex) && validTaskVarIndex(taskVarIndex)) { const int pluginID = Device[deviceIndex].Number; + # ifdef USES_P033 + if ((pluginID == 33) || // Plugin 33 Dummy Device, // backward compatible behavior: if handleCmd = true then execute TaskValueSet regardless of AllowTaskValueSetAllPlugins ((handleCmd || Settings.AllowTaskValueSetAllPlugins()) && (pluginID != 86))) @@ -69,9 +71,16 @@ bool MQTT_handle_topic_commands(struct EventStruct *event, cmd = strformat(F("TaskValueSet,%d,%d,%s"), taskIndex + 1, valueNr + 1, event->String2.c_str()); handled = true; } - } else if (pluginID == 86) { // Plugin 86 Homie receiver. Schedules the event defined in the plugin. - // Does NOT store the value. - // Use HomieValueSet to save the value. This will acknowledge back to the controller too. + } + # endif // ifdef USES_P033 + # if defined(USES_P033) && defined(USES_P086) + else + # endif // if defined(USES_P033) && defined(USES_P086) + # ifdef USES_P086 + + if (pluginID == 86) { // Plugin 86 Homie receiver. Schedules the event defined in the plugin. + // Does NOT store the value. + // Use HomieValueSet to save the value. This will acknowledge back to the controller too. valueNr = findDeviceValueIndexByName(valueName, taskIndex); if (validTaskVarIndex(valueNr)) { @@ -101,6 +110,7 @@ bool MQTT_handle_topic_commands(struct EventStruct *event, handled = true; } } + # endif // ifdef USES_P086 } } } From 92a50d1f20233872b43073b0c65332d7fc43ed95 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 30 Oct 2023 21:50:21 +0100 Subject: [PATCH 05/11] [C014] Use getHostname() for %sysname%, some code optimization --- src/_C005.cpp | 9 ++++----- src/_C014.cpp | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/_C005.cpp b/src/_C005.cpp index 44d4e0b30c..57aa689da7 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -186,11 +186,10 @@ bool C005_parse_command(struct EventStruct *event) { if (validFloatFromString(event->String2, value_f) && validIntFromString(lastPartTopic, lastPartTopic_int)) { const int prevLastindex = event->String1.lastIndexOf('/', lastindex - 1); - cmd = event->String1.substring(prevLastindex + 1, lastindex); - cmd += ','; - cmd += lastPartTopic_int; - cmd += ','; - cmd += event->String2; // Just use the original format + cmd = strformat(F("%s,%d,%s"), + event->String1.substring(prevLastindex + 1, lastindex).c_str(), + lastPartTopic_int, + event->String2.c_str()); // Just use the original format validTopic = true; } } diff --git a/src/_C014.cpp b/src/_C014.cpp index af9cda5ca9..26d13d518a 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -16,6 +16,8 @@ // ####################################################################################################### /** Changelog: + * 2023-10-30 tonhuisman: Fix using getHostname() instead of getName() for %sysname%. This might break some configurations! + * minor improvements * 2023-08-18 tonhuisman: Clean up source to improve resource usage * 2023-03-15 tonhuisman: Replace use of deprecated DummyValueSet with TaskValueSet * 2023-03 Changelog started @@ -51,6 +53,7 @@ # define CPLUGIN_014_SYSTEM_DEVICE "SYSTEM" // name for system device Plugin for cmd and GIO values # define CPLUGIN_014_CMD_VALUE "cmd" // name for command value # define CPLUGIN_014_GPIO_VALUE "gpio" // name for gpio value i.e. "gpio1" +# define CPLUGIN_014_GPIO_VALUE_LEN 4 // length of GPIO to avoid creating a String to get the length # define CPLUGIN_014_CMD_VALUE_NAME "Command" // human readabele name for command value # define CPLUGIN_014_GPIO_COMMAND "gpio" // name for gpio command @@ -63,7 +66,7 @@ String CPlugin_014_pubname; bool CPlugin_014_mqtt_retainFlag = false; void C014_replaceSysname(String& var) { - var.replace(F("%sysname%"), Settings.getName()); + var.replace(F("%sysname%"), Settings.getHostname()); // Used to be getName(), but that doesn't include the UnitNr when configured } bool CPlugin_014_sendMQTTdevice(String tmppubname, @@ -385,6 +388,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& for (taskIndex_t x = 0; x < TASKS_MAX; x++) { const pluginID_t pluginID = Settings.getPluginID_for_task(x); + if (validPluginID_fullcheck(pluginID)) { LoadTaskSettings(x); @@ -401,6 +405,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& if (!Device[DeviceIndex].SendDataOption) // check if device is not sending data = assume that it can receive. { constexpr pluginID_t HOMIE_RECEIVER_PLUGIN_ID(86); + if (pluginID == HOMIE_RECEIVER_PLUGIN_ID) { for (uint8_t varNr = 0; varNr < valueCount; varNr++) { @@ -497,6 +502,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& for (uint8_t varNr = 0; varNr < valueCount; varNr++) { const pluginID_t pluginID = Settings.getPluginID_for_task(x); + if (validPluginID_fullcheck(pluginID)) { if (ExtraTaskSettings.TaskDeviceValueNames[varNr][0] != 0) // do not send if Value Name is empty! @@ -520,6 +526,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& errorCounter); constexpr pluginID_t DUMMY_PLUGIN_ID(33); + if (pluginID == DUMMY_PLUGIN_ID) { // Dummy Device can send AND receive Data CPlugin_014_sendMQTTnode(nodename, deviceName, @@ -712,17 +719,17 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& log = strformat(F("C014 : MQTT received: /set: N: %s V: %s"), nodeName.c_str(), valueName.c_str()); } - if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device + if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device { - if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values + if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values { - const size_t gpio_value_tag_length = String(F(CPLUGIN_014_GPIO_VALUE)).length(); // FIXME use fixed length or constexpr + constexpr size_t gpio_value_tag_length = CPLUGIN_014_GPIO_VALUE_LEN; // now uses fixed length or constexpr - cmd = concat(F("GPIO,"), valueName.substring(gpio_value_tag_length).toInt()); // get the GPIO + cmd = concat(F("GPIO,"), valueName.substring(gpio_value_tag_length).toInt()); // get the GPIO cmd += ','; - if (equals(event->String2, F("true")) || equals(event->String2, '1')) { // Homie spec says it should be 'true' or - cmd += '1'; // 'false'... + if (equals(event->String2, F("true")) || equals(event->String2, '1')) { // Homie spec says it should be 'true' or + cmd += '1'; // 'false'... } else { cmd += '0'; } From 4dea3a1c055342780f750e26d918bfcca55911c6 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 2 Mar 2024 15:00:52 +0100 Subject: [PATCH 06/11] [C014] Process %sysname% via parseSystemVariables(), logging improvements --- src/_C014.cpp | 134 ++++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 70 deletions(-) diff --git a/src/_C014.cpp b/src/_C014.cpp index 4969a162fd..ce69f1b3d1 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -16,6 +16,8 @@ // ####################################################################################################### /** Changelog: + * 2024-03-02 tonhuisman: Fix using parseSystemVariables() for processing %sysname%. Might still break the same configurations, + * logging improvements * 2023-10-30 tonhuisman: Fix using getHostname() instead of getName() for %sysname%. This might break some configurations! * minor improvements * 2023-08-18 tonhuisman: Clean up source to improve resource usage @@ -66,7 +68,7 @@ String CPlugin_014_pubname; bool CPlugin_014_mqtt_retainFlag = false; void C014_replaceSysname(String& var) { - var.replace(F("%sysname%"), Settings.getHostname()); // Used to be getName(), but that doesn't include the UnitNr when configured + parseSystemVariables(var, false); // Used to be getName(), but that doesn't include the UnitNr when configured } bool CPlugin_014_sendMQTTdevice(String tmppubname, @@ -234,15 +236,13 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("C014 : $stats information sent with "); - - if (errorCounter > 0) { log += errorCounter; } - else { log += F("no"); } - log += strformat(F(" errors! (%d messages)"), msgCounter); - msgCounter = 0; - addLogMove(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, + strformat(F("C014 : $stats information sent with %s errors! (%d messages)"), + errorCounter > 0 ? String(errorCounter).c_str() : "no", + msgCounter)); } # endif // ifndef BUILD_NO_DEBUG + msgCounter = 0; } break; } @@ -538,30 +538,31 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& nodeCount++; - /* // because values in ESPEasy are unitless lets assume some units by the value name - (still case sensitive) - if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "temp") != nullptr ) - { - unitName = F("°C"); - } else if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "humi") != nullptr ) - { - unitName = F("%"); - } else if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "press") != nullptr ) - { - unitName = F("Pa"); - } // ToDo: .... and more - - if (!unitName.isEmpty()) // found a unit match - { - // $unit Device → Controller A string containing the unit of this property. You - are not limited to the recommended values, although they are the only well known ones - that will have to be recognized by any Homie consumer. Recommended: Yes No - ("") - CPlugin_014_sendMQTTnode(nodename, deviceName, - ExtraTaskSettings.TaskDeviceValueNames[varNr], F("/$unit"), unitName, - errorCounter); - } - unitName = F(""); + /* + // because values in ESPEasy are unitless lets assume some units by the value name (still case sensitive) + + if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "temp") != nullptr) + { + unitName = F("°C"); + } else if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "humi") != nullptr) + { + unitName = F("%"); + } else if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "press") != nullptr) + { + unitName = F("Pa"); + } // ToDo: .... and more + + if (!unitName.isEmpty()) // found a unit match + { + // $unit Device → Controller A string containing the unit of this property. You + are not limited to the recommended values, although they are the only well known ones + that will have to be recognized by any Homie consumer.Recommended: Yes No + ("") + CPlugin_014_sendMQTTnode(nodename, deviceName, + ExtraTaskSettings.TaskDeviceValueNames[varNr], F("/$unit"), unitName, + errorCounter); + } + unitName = F(""); */ } } @@ -614,8 +615,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, concat(F("C014 : Device Disabled: %s not propagated!"), - getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x)).c_str())); + addLog(LOG_LEVEL_DEBUG, strformat(F("C014 : Device Disabled: %s not propagated!"), + getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(x)).c_str())); } # endif // ifndef BUILD_NO_DEBUG } @@ -641,14 +642,13 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = concat(F("C014 : autodiscover information of "), deviceCount); - log += concat(F(" Devices and "), nodeCount); - log += F(" Nodes sent with "); - - if (errorCounter > 0) { log += errorCounter; } - else { log += F("no"); } - log += strformat(F(" errors! (%d messages)"), msgCounter); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("C014 : autodiscover information of %d Devices and %d Nodes sent with %s errors! (%d messages)"), + deviceCount, + nodeCount, + errorCounter > 0 ? String(errorCounter).c_str() : "no", + msgCounter) + ); } msgCounter = 0; errorCounter = 0; @@ -672,9 +672,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& success = CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("disconnected"), errorCounter); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = concat(F("C014 : Device: "), Settings.getName()); - log += F(" got invalid (disconnect"); - log += success ? F("ed).") : F(") failed!"); + String log = strformat(F("C014 : Device: %s got invalid (disconnect%s"), + Settings.getHostname().c_str(), String(success ? F("ed).") : F(") failed!")).c_str()); addLogMove(LOG_LEVEL_INFO, log); } break; @@ -719,20 +718,17 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& log = strformat(F("C014 : MQTT received: /set: N: %s V: %s"), nodeName.c_str(), valueName.c_str()); } - if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device + if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device { - if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values + if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values { - constexpr size_t gpio_value_tag_length = CPLUGIN_014_GPIO_VALUE_LEN; // now uses fixed length or constexpr + constexpr size_t gpio_value_tag_length = CPLUGIN_014_GPIO_VALUE_LEN; // now uses fixed length or constexpr - cmd = concat(F("GPIO,"), valueName.substring(gpio_value_tag_length).toInt()); // get the GPIO - cmd += ','; - - if (equals(event->String2, F("true")) || equals(event->String2, '1')) { // Homie spec says it should be 'true' or - cmd += '1'; // 'false'... - } else { - cmd += '0'; - } + // get the GPIO + // Homie spec says state should be 'true' or 'false'... + cmd = strformat(F("GPIO,%d,%c"), + valueName.substring(gpio_value_tag_length).toInt(), + (equals(event->String2, F("true")) || equals(event->String2, '1')) ? '1' : '0'); validTopic = true; } else if (equals(valueName, F(CPLUGIN_014_CMD_VALUE))) // msg to send a command { @@ -740,8 +736,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& validTopic = true; } else { - cmd = concat(F("SYSTEM/"), valueName); - cmd += F(" unknown!"); + cmd = strformat(F("SYSTEM/%s unknown!"), valueName.c_str()); } } else // msg to a receiving plugin { @@ -875,8 +870,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& success = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic.c_str(), valueBool.c_str(), false); - String log = concat(F("C014 : Acknowledged GPIO"), port); - log += strformat(F(" value:%s (%d)"), valueBool.c_str(), valueInt); + String log = strformat(F("C014 : Acknowledged GPIO%d value:%s (%d)"), + port, valueBool.c_str(), valueInt); if (loglevelActiveFor(LOG_LEVEL_INFO) && success) { log += F(" success!"); @@ -956,21 +951,20 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& String log = concat(F("C014 : homie acknowledge: "), deviceName); if (loglevelActiveFor(LOG_LEVEL_INFO) && success) { - log += concat(F(" taskIndex:"), deviceIndex); - log += concat(F(" valueNr:"), event->Par2); - log += concat(F(" valueName:"), valueName); - log += concat(F(" valueType:"), Settings.TaskDevicePluginConfig[deviceIndex - 1][taskVarIndex]); - log += concat(F(" topic:"), topic); - log += concat(F(" valueInt:"), valueInt); - log += concat(F(" valueStr:"), valueStr); - log += F(" success!"); + log += strformat(F(" taskIndex:%d valueNr:%d valueName:%s valueType:%d topic:%s valueInt:%d valueStr:%s success!"), + deviceIndex, + event->Par2, + valueName.c_str(), + Settings.TaskDevicePluginConfig[deviceIndex - 1][taskVarIndex], + topic.c_str(), + valueInt, + valueStr.c_str()); addLogMove(LOG_LEVEL_INFO, log); } if (loglevelActiveFor(LOG_LEVEL_ERROR) && !success) { - log += strformat(F(" var: %s topic: %s value: %s"), + log += strformat(F(" var: %s topic: %s value: %s ERROR!"), // was: failed! valueName.c_str(), topic.c_str(), valueStr.c_str()); - log += F(" ERROR!"); // was: failed! addLogMove(LOG_LEVEL_ERROR, log); } } else // Acknowledge not implemented yet From d944777f0aa49f2d21ca0c62de153dac498fa4e1 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 24 May 2024 22:37:18 +0200 Subject: [PATCH 07/11] [Controllers] Minor improvements and corrections --- src/_C002.cpp | 8 +- src/_C014.cpp | 274 +++++++++-------------- src/_P086_Homie.ino | 46 ++-- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 17 +- 4 files changed, 131 insertions(+), 214 deletions(-) diff --git a/src/_C002.cpp b/src/_C002.cpp index 16d2bfa5cb..6d9082613f 100644 --- a/src/_C002.cpp +++ b/src/_C002.cpp @@ -105,12 +105,12 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& bool mustSendEvent = false; switch (Settings.getPluginID_for_task(x).value) { - case 1: // temp solution, if input switch, update state + case 1: // temp solution, if input switch, update state { action = strformat(F("gpio,%u,%.2f"), x, nvalue); // FIXME tonhuisman: Was: InputSwitchState break; } - case 29: // temp solution, if plugin 029, set gpio + case 29: // temp solution, if plugin 029, set gpio { if (switchtype.equalsIgnoreCase(F("dimmer"))) { @@ -153,9 +153,9 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& break; } # if defined(USES_P088) - case 88: // Send heatpump IR (P088) if IDX matches + case 88: // Send heatpump IR (P088) if IDX matches { - action = concat(F("heatpumpir,"),svalue1); // svalue1 is like 'gree,1,1,0,22,0,0' + action = concat(F("heatpumpir,"), svalue1); // svalue1 is like 'gree,1,1,0,22,0,0' break; } # endif // if defined(USES_P088) diff --git a/src/_C014.cpp b/src/_C014.cpp index ce69f1b3d1..34dff29f80 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -111,8 +111,7 @@ bool CPlugin_014_sendMQTTdevice(const String & tmppubname, taskIndex_t taskIndex, const __FlashStringHelper *topic, const __FlashStringHelper *payload, - int & errorCounter) -{ + int & errorCounter) { return CPlugin_014_sendMQTTdevice(tmppubname, taskIndex, topic, String(payload), errorCounter); } @@ -153,9 +152,10 @@ bool CPlugin_014_sendMQTTnode(String tmppubname, } // and String to a comma separated list -void C014_addToList(String& valuesList, const String& node) -{ - if (valuesList.length() > 0) { valuesList += ','; } +void C014_addToList(String& valuesList, const String& node) { + if (valuesList.length() > 0) { + valuesList += ','; + } valuesList += node; } @@ -201,11 +201,10 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INTERVAL: { - if (MQTTclient.connected()) - { + if (MQTTclient.connected()) { errorCounter = 0; - pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages + pubname = F(CPLUGIN_014_BASE_TOPIC); // Scheme to form device messages C014_replaceSysname(pubname); # ifdef CPLUGIN_014_V3 @@ -221,8 +220,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$stats/signal"), toString(RssI, 1), errorCounter); # endif // ifdef CPLUGIN_014_V3 - if (errorCounter > 0) - { + if (errorCounter > 0) { // alert: this is the state the device is when connected to the MQTT broker, but something wrong is happening. E.g. a sensor is // not providing data and needs human intervention. You have to send this message when something is wrong. CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("alert"), errorCounter); @@ -252,21 +250,20 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& statusLED(true); // send autodiscover header - pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages + pubname = F(CPLUGIN_014_BASE_TOPIC); // Scheme to form device messages C014_replaceSysname(pubname); - int deviceCount = 1; // minimum the SYSTEM device exists - int nodeCount = 1; // minimum the cmd node exists + int deviceCount = 1; // minimum the SYSTEM device exists + int nodeCount = 1; // minimum the cmd node exists errorCounter = 0; - if (lastBootCause != BOOT_CAUSE_DEEP_SLEEP) // skip sending autodiscover data when returning from deep sleep - { - String nodename = CPLUGIN_014_BASE_VALUE; // Scheme to form node messages + if (lastBootCause != BOOT_CAUSE_DEEP_SLEEP) { // skip sending autodiscover data when returning from deep sleep + String nodename = F(CPLUGIN_014_BASE_VALUE); // Scheme to form node messages C014_replaceSysname(nodename); - String nodesList; // build comma separated List for nodes - String valuesList; // build comma separated List for values - String deviceName; // current Device Name nr:name - String valueName; // current Value Name - String unitName; // estimate Units + String nodesList; // build comma separated List for nodes + String valuesList; // build comma separated List for values + String deviceName; // current Device Name nr:name + String valueName; // current Value Name + String unitName; // estimate Units // init: this is the state the device is in when it is connected to the MQTT broker, but has not yet sent all Homie messages and is // not yet ready to operate. This is the first message that must that must be sent. @@ -357,8 +354,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& while (gpio <= MAX_GPIO) { const PinBootState pinBootState = Settings.getPinBootState(gpio); - if (pinBootState != PinBootState::Default_state) // anything but default - { + if (pinBootState != PinBootState::Default_state) { // anything but default nodeCount++; valueName = concat(F(CPLUGIN_014_GPIO_VALUE), gpio); C014_addToList(valuesList, valueName); @@ -369,8 +365,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& // $datatype The data type. See Payloads. Enum: [integer, float, boolean,string, enum, color] CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), valueName, F("/$datatype"), F("boolean"), errorCounter); - if (pinBootState != PinBootState::Input) // defined as output - { + if (pinBootState != PinBootState::Input) { // defined as output // $settable Device → Controller Specifies whether the property is settable (true) or readonly (false) true or // false Yes No (false) CPlugin_014_sendMQTTnode(nodename, F(CPLUGIN_014_SYSTEM_DEVICE), valueName, F("/$settable"), F("true"), errorCounter); @@ -385,30 +380,26 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& deviceCount++; // SECOND Plugins - for (taskIndex_t x = 0; x < TASKS_MAX; x++) - { + for (taskIndex_t x = 0; x < TASKS_MAX; ++x) { const pluginID_t pluginID = Settings.getPluginID_for_task(x); - if (validPluginID_fullcheck(pluginID)) - { + if (validPluginID_fullcheck(pluginID)) { LoadTaskSettings(x); const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); deviceName = getTaskDeviceName(x); - if (validDeviceIndex(DeviceIndex) && Settings.TaskDeviceEnabled[x]) // Device is enabled so send information - { // device enabled + if (validDeviceIndex(DeviceIndex) && Settings.TaskDeviceEnabled[x]) { // Device is enabled so send information + // device enabled valuesList = EMPTY_STRING; const uint8_t valueCount = getValueCountForTask(x); - if (!Device[DeviceIndex].SendDataOption) // check if device is not sending data = assume that it can receive. - { + if (!Device[DeviceIndex].SendDataOption) { // check if device is not sending data = assume that it can receive. constexpr pluginID_t HOMIE_RECEIVER_PLUGIN_ID(86); - if (pluginID == HOMIE_RECEIVER_PLUGIN_ID) - { - for (uint8_t varNr = 0; varNr < valueCount; varNr++) { + if (pluginID == HOMIE_RECEIVER_PLUGIN_ID) { + for (uint8_t varNr = 0; varNr < valueCount; ++varNr) { if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) { if (ExtraTaskSettings.TaskDeviceValueNames[varNr][0] != 0) { // do not send if Value Name is empty! C014_addToList(valuesList, ExtraTaskSettings.TaskDeviceValueNames[varNr]); @@ -433,7 +424,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& errorCounter); // $datatype The data type. See Payloads. Enum: [integer, float, boolean,string, enum, color] - unitName = F(""); + unitName = EMPTY_STRING; switch (Settings.TaskDevicePluginConfig[x][varNr]) { case 0: @@ -451,9 +442,9 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& if ((ExtraTaskSettings.TaskDevicePluginConfig[varNr] != 0) || (ExtraTaskSettings.TaskDevicePluginConfig[varNr + 5] != 0)) { - unitName = ExtraTaskSettings.TaskDevicePluginConfig[varNr]; - unitName += ':'; - unitName += ExtraTaskSettings.TaskDevicePluginConfig[varNr + valueCount]; + unitName = strformat(F("%d:%d"), + ExtraTaskSettings.TaskDevicePluginConfig[varNr], + ExtraTaskSettings.TaskDevicePluginConfig[varNr + valueCount]); } break; case 2: valueName = F("boolean"); break; @@ -497,16 +488,12 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& // customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesStr); uint8_t customValues = false; - if (!customValues) - { // standard Values - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { + if (!customValues) { // standard Values + for (uint8_t varNr = 0; varNr < valueCount; ++varNr) { const pluginID_t pluginID = Settings.getPluginID_for_task(x); - if (validPluginID_fullcheck(pluginID)) - { - if (ExtraTaskSettings.TaskDeviceValueNames[varNr][0] != 0) // do not send if Value Name is empty! - { + if (validPluginID_fullcheck(pluginID)) { + if (ExtraTaskSettings.TaskDeviceValueNames[varNr][0] != 0) { // do not send if Value Name is empty! C014_addToList(valuesList, ExtraTaskSettings.TaskDeviceValueNames[varNr]); // $name Device → Controller Friendly name of the property. Any String Yes No ("") @@ -538,7 +525,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& nodeCount++; - /* + /* TODO Fix units? // because values in ESPEasy are unitless lets assume some units by the value name (still case sensitive) if (strstr(ExtraTaskSettings.TaskDeviceValueNames[varNr], "temp") != nullptr) @@ -578,8 +565,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } } - if (!valuesList.isEmpty()) - { + if (!valuesList.isEmpty()) { // only add device to list if it has nodes! // $name Device → Controller Friendly name of the Node Yes Yes CPlugin_014_sendMQTTnode(nodename, @@ -629,8 +615,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$nodes"), nodesList, errorCounter); } - if (errorCounter > 0) - { + if (errorCounter > 0) { // alert: this is the state the device is when connected to the MQTT broker, but something wrong is happening. E.g. a sensor is not // providing data and needs human intervention. You have to send this message when something is wrong. CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("alert"), errorCounter); @@ -664,7 +649,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_GOT_INVALID: { - pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages + pubname = F(CPLUGIN_014_BASE_TOPIC); // Scheme to form device messages C014_replaceSysname(pubname); // disconnected: this is the state the device is in when it is cleanly disconnected from the MQTT broker. You must send this message @@ -681,7 +666,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_FLUSH: { - pubname = CPLUGIN_014_BASE_TOPIC; // Scheme to form device messages + pubname = F(CPLUGIN_014_BASE_TOPIC); // Scheme to form device messages C014_replaceSysname(pubname); // sleeping: this is the state the device is in when the device is sleeping. You have to send this message before sleeping. @@ -691,9 +676,9 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: { - controllerIndex_t ControllerID = findFirstEnabledControllerWithId(CPLUGIN_ID_014); - bool validTopic = false; - bool cmdExecuted = false; + const controllerIndex_t ControllerID = findFirstEnabledControllerWithId(CPLUGIN_ID_014); + bool validTopic = false; + bool cmdExecuted = false; if (!validControllerIndex(ControllerID)) { // Controller is not enabled. @@ -703,12 +688,11 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& int lastindex = event->String1.lastIndexOf('/'); errorCounter = 0; - if (equals(event->String1.substring(lastindex + 1), F("set"))) - { + if (equals(event->String1.substring(lastindex + 1), F("set"))) { pubname = event->String1.substring(0, lastindex); lastindex = pubname.lastIndexOf('/'); - String nodeName = pubname.substring(0, lastindex); - String valueName = pubname.substring(lastindex + 1); + String nodeName = pubname.substring(0, lastindex); + const String valueName = pubname.substring(lastindex + 1); lastindex = nodeName.lastIndexOf('/'); nodeName = nodeName.substring(lastindex + 1); @@ -718,10 +702,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& log = strformat(F("C014 : MQTT received: /set: N: %s V: %s"), nodeName.c_str(), valueName.c_str()); } - if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) // msg to a system device - { - if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) // msg to to set gpio values - { + if (equals(nodeName, F(CPLUGIN_014_SYSTEM_DEVICE))) { // msg to a system device + if (valueName.startsWith(F(CPLUGIN_014_GPIO_VALUE))) { // msg to to set gpio values constexpr size_t gpio_value_tag_length = CPLUGIN_014_GPIO_VALUE_LEN; // now uses fixed length or constexpr // get the GPIO @@ -730,16 +712,13 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& valueName.substring(gpio_value_tag_length).toInt(), (equals(event->String2, F("true")) || equals(event->String2, '1')) ? '1' : '0'); validTopic = true; - } else if (equals(valueName, F(CPLUGIN_014_CMD_VALUE))) // msg to send a command - { + } else if (equals(valueName, F(CPLUGIN_014_CMD_VALUE))) { // msg to send a command cmd = event->String2; validTopic = true; - } else - { + } else { cmd = strformat(F("SYSTEM/%s unknown!"), valueName.c_str()); } - } else // msg to a receiving plugin - { + } else { // msg to a receiving plugin // Only handle /set case that supports P033 Dummy device and P086 Homie receiver cmdExecuted = MQTT_handle_topic_commands(event, false, true); validTopic = true; // Avoid error messages @@ -772,98 +751,58 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& success = MQTT_protocol_send(event, CPlugin_014_pubname, CPlugin_014_mqtt_retainFlag); - // String pubname = CPlugin_014_pubname; - // bool mqtt_retainFlag = CPlugin_014_mqtt_retainFlag; - - // statusLED(true); - - // parseControllerVariables(pubname, event, false); - // LoadTaskSettings(event->TaskIndex); - - // uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - // for (uint8_t x = 0; x < valueCount; x++) - // { - // String tmppubname = pubname; - // String value; - // parseSingleControllerVariable(tmppubname, event, x, false); - - // // Small optimization so we don't try to copy potentially large strings - // if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { - // if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) { - // success = true; - // } - // value = event->String2.substring(0, 20); // For the log - // } else { - // value = formatUserVarNoCheck(event, x); - - // if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), value.c_str(), mqtt_retainFlag)) { - // success = true; - // } - // } - - // # ifndef BUILD_NO_DEBUG - - // if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - // String log = concat(F("C014 : Sent to "), tmppubname); - // log += ' '; - // log += value; - // addLogMove(LOG_LEVEL_DEBUG, log); - // } - // # endif // ifndef BUILD_NO_DEBUG - // } break; } case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: { - /* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("CPLUGIN_ACKNOWLEDGE: "); - log += string; - log += F(" / "); - log += getTaskDeviceName(event->TaskIndex); - log += F(" / "); - log += ExtraTaskSettings.TaskDeviceValueNames[event->Par2-1]; - log += F(" sensorType:"); - log += event->sensorType; - log += F(" Source:"); - log += event->Source; - log += F(" idx:"); - log += event->idx; - log += F(" S1:"); - log += event->String1; - log += F(" S2:"); - log += event->String2; - log += F(" S3:"); - log += event->String3; - log += F(" S4:"); - log += event->String4; - log += F(" S5:"); - log += event->String5; - log += F(" P1:"); - log += event->Par1; - log += F(" P2:"); - log += event->Par2; - log += F(" P3:"); - log += event->Par3; - log += F(" P4:"); - log += event->Par4; - log += F(" P5:"); - log += event->Par5; - addLog(LOG_LEVEL_DEBUG, log); - } */ + /* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("CPLUGIN_ACKNOWLEDGE: "); + log += string; + log += F(" / "); + log += getTaskDeviceName(event->TaskIndex); + log += F(" / "); + log += ExtraTaskSettings.TaskDeviceValueNames[event->Par2-1]; + log += F(" sensorType:"); + log += event->sensorType; + log += F(" Source:"); + log += event->Source; + log += F(" idx:"); + log += event->idx; + log += F(" S1:"); + log += event->String1; + log += F(" S2:"); + log += event->String2; + log += F(" S3:"); + log += event->String3; + log += F(" S4:"); + log += event->String4; + log += F(" S5:"); + log += event->String5; + log += F(" P1:"); + log += event->Par1; + log += F(" P2:"); + log += event->Par2; + log += F(" P3:"); + log += event->Par3; + log += F(" P4:"); + log += event->Par4; + log += F(" P5:"); + log += event->Par5; + addLog(LOG_LEVEL_DEBUG, log); + } */ if (!string.isEmpty()) { - String commandName = parseString(string, 1); // could not find a way to get the command out of the event structure. + const String commandName = parseString(string, 1); // could not find a way to get the command out of the event structure. if (equals(commandName, F(CPLUGIN_014_GPIO_COMMAND))) // !ToDo : As gpio is like any other plugin commands should be integrated // below! { - int port = event->Par1; // parseString(string, 2).toInt(); - int valueInt = event->Par2; // parseString(string, 3).toInt(); + const int port = event->Par1; // parseString(string, 2).toInt(); + const int valueInt = event->Par2; // parseString(string, 3).toInt(); const String valueBool = boolToString(valueInt == 1); - String topic = CPLUGIN_014_PUBLISH; // ControllerSettings.Publish not used because it can be modified by the user! + String topic = F(CPLUGIN_014_PUBLISH); // ControllerSettings.Publish not used because it can be modified by the user! C014_replaceSysname(topic); topic.replace(F("%tskname%"), F(CPLUGIN_014_SYSTEM_DEVICE)); topic.replace(F("%valname%"), concat(F(CPLUGIN_014_GPIO_VALUE), port)); @@ -882,13 +821,12 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& log += F(" ERROR!"); addLogMove(LOG_LEVEL_ERROR, log); } - } else // not gpio - { + } else { // not gpio const taskVarIndex_t taskVarIndex = event->Par2 - 1; if (validTaskVarIndex(taskVarIndex)) { userVarIndex_t userVarIndex = event->BaseVarIndex + taskVarIndex; - String topic = CPLUGIN_014_PUBLISH; + String topic = F(CPLUGIN_014_PUBLISH); C014_replaceSysname(topic); const int deviceIndex = event->Par1; // parseString(string, 2).toInt(); LoadTaskSettings(deviceIndex - 1); @@ -899,9 +837,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& String valueStr; int valueInt = 0; - if (equals(commandName, F(CPLUGIN_014_TASKVALUESET_COMMAND))) // removed dummyvalueset command some time ago... - { - valueStr = formatUserVarNoCheck(event, taskVarIndex); // parseString(string, 4); + if (equals(commandName, F(CPLUGIN_014_TASKVALUESET_COMMAND))) { // removed dummyvalueset command some time ago... + valueStr = formatUserVarNoCheck(event, taskVarIndex); // parseString(string, 4); success = MQTTpublish(CPLUGIN_ID_014, INVALID_TASK_INDEX, topic.c_str(), valueStr.c_str(), false); String log = strformat(F("C014 : Acknowledged: %s var: %s topic: %s value: %s"), @@ -967,18 +904,17 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& valueName.c_str(), topic.c_str(), valueStr.c_str()); addLogMove(LOG_LEVEL_ERROR, log); } - } else // Acknowledge not implemented yet - { - /* if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("C014 : Plugin acknowledged: "); - log+=function; - log+=F(" / "); - log+=commandName; - log+=F(" cmd: "); - log+=string; - log+=F(" not implemented!"); - addLog(LOG_LEVEL_ERROR, log); - } */ + } else { // Acknowledge not implemented yet + /* if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("C014 : Plugin acknowledged: "); + log+=function; + log+=F(" / "); + log+=commandName; + log+=F(" cmd: "); + log+=string; + log+=F(" not implemented!"); + addLog(LOG_LEVEL_ERROR, log); + } */ success = false; } } diff --git a/src/_P086_Homie.ino b/src/_P086_Homie.ino index 80c99b76fb..0b0db5270c 100644 --- a/src/_P086_Homie.ino +++ b/src/_P086_Homie.ino @@ -16,9 +16,6 @@ // empty default names because settings will be ignored / not used if value name is empty # define PLUGIN_VALUENAME1_086 "" -# define PLUGIN_VALUENAME2_086 "" -# define PLUGIN_VALUENAME3_086 "" -# define PLUGIN_VALUENAME4_086 "" # define PLUGIN_086_VALUE_INTEGER 0 # define PLUGIN_086_VALUE_FLOAT 1 @@ -45,19 +42,13 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) { case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_086; - Device[deviceCount].Type = DEVICE_TYPE_DUMMY; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].DecimalsOnly = true; - Device[deviceCount].ValueCount = PLUGIN_086_VALUE_MAX; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - Device[deviceCount].GlobalSyncOption = false; - Device[deviceCount].Custom = true; + Device[++deviceCount].Number = PLUGIN_ID_086; + Device[deviceCount].Type = DEVICE_TYPE_DUMMY; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].DecimalsOnly = true; + Device[deviceCount].ValueCount = PLUGIN_086_VALUE_MAX; + Device[deviceCount].Custom = true; break; } @@ -70,9 +61,6 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_GET_DEVICEVALUENAMES: { strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_086)); break; } @@ -103,7 +91,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) }; constexpr int PLUGIN_086_VALUE_TYPES = NR_ELEMENTS(optionValues); - for (int i = 0; i < PLUGIN_086_VALUE_MAX; i++) { + for (int i = 0; i < PLUGIN_086_VALUE_MAX; ++i) { addFormSubHeader(concat(F("Function #"), i + 1)); if (i == 0) { addFormNote(F("Triggers an event when a ../%taskname%/%event%/set MQTT topic arrives")); } @@ -136,7 +124,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { - for (int i = 0; i < PLUGIN_086_VALUE_MAX; i++) { + for (int i = 0; i < PLUGIN_086_VALUE_MAX; ++i) { strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceValueNames[i], getPluginCustomArgName((i * 10) + 0)); PCONFIG(i) = getFormItemInt(getPluginCustomArgName((i * 10) + 1)); ExtraTaskSettings.TaskDevicePluginConfig[i] = getFormItemInt(getPluginCustomArgName((i * 10) + 2)); @@ -158,7 +146,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_READ: { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - for (uint8_t x = 0; x < PLUGIN_086_VALUE_MAX; x++) { + for (uint8_t x = 0; x < PLUGIN_086_VALUE_MAX; ++x) { addLogMove(LOG_LEVEL_INFO, strformat(F("P086 : Value %d: %s"), x + 1, formatUserVarNoCheck(event->TaskIndex, x).c_str())); } } @@ -168,16 +156,15 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WRITE: { - String command = parseString(string, 1); + const String command = parseString(string, 1); - if (equals(command, F("homievalueset"))) - { + if (equals(command, F("homievalueset"))) { const taskVarIndex_t taskVarIndex = event->Par2 - 1; if (validTaskIndex(event->TaskIndex) && validTaskVarIndex(taskVarIndex) && (event->Par1 == (event->TaskIndex + 1))) { // make sure that this instance is the target - String parameter = parseStringToEndKeepCase(string, 4); + const String parameter = parseStringToEndKeepCase(string, 4); String log; /* @@ -238,8 +225,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, taskVarIndex, floatValue); } else { // float conversion failed! if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - log += concat(F(" parameter: "), parameter); - log += F(" not a float value!"); + log += strformat(F(" parameter: %s not a float value!"), parameter.c_str()); addLogMove(LOG_LEVEL_ERROR, log); } } @@ -295,9 +281,7 @@ boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, event->Par2 - 1, floatValue); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += concat(F(" enum set to "), floatValue); - log += ' '; - log += wrap_braces(parameter); + log += strformat(F(" enum set to %.2f %s"), floatValue, wrap_braces(parameter).c_str()); addLogMove(LOG_LEVEL_INFO, log); } break; diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index f49c241e83..18c7be3f85 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -41,8 +41,8 @@ bool MQTT_handle_topic_commands(struct EventStruct *event, lastindex = topic.lastIndexOf('/'); if (lastindex > -1) { - String taskName = topic.substring(0, lastindex); - String valueName = topic.substring(lastindex + 1); + String taskName = topic.substring(0, lastindex); + const String valueName = topic.substring(lastindex + 1); lastindex = taskName.lastIndexOf('/'); if (lastindex > -1) { @@ -58,15 +58,13 @@ bool MQTT_handle_topic_commands(struct EventStruct *event, # ifdef USES_P033 - if ((pluginID == 33) || // Plugin 33 Dummy Device, + if ((pluginID == 33) || // Plugin 33 Dummy Device, // backward compatible behavior: if handleCmd = true then execute TaskValueSet regardless of AllowTaskValueSetAllPlugins - ((handleCmd || Settings.AllowTaskValueSetAllPlugins()) && (pluginID != 86))) - { // TaskValueSet,,,, works only with new - // version of P033! + ((handleCmd || Settings.AllowTaskValueSetAllPlugins()) && (pluginID != 86))) { + // TaskValueSet,,,, works only with new version of P033! valueNr = findDeviceValueIndexByName(valueName, taskIndex); - if (validTaskVarIndex(valueNr)) // value Name identified - { + if (validTaskVarIndex(valueNr)) { // value Name identified // Set a Dummy Device Value, device Number, var number and argument cmd = strformat(F("TaskValueSet,%d,%d,%s"), taskIndex + 1, valueNr + 1, event->String2.c_str()); handled = true; @@ -196,8 +194,7 @@ bool MQTT_protocol_send(EventStruct *event, const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - for (uint8_t x = 0; x < valueCount; x++) - { + for (uint8_t x = 0; x < valueCount; ++x) { // MFD: skip publishing for values with empty labels (removes unnecessary publishing of unwanted values) if (getTaskValueName(event->TaskIndex, x).isEmpty()) { continue; // we skip values with empty labels From 4bb1d38c35f044870c6b4456fb5883660ed4220b Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 29 May 2024 21:45:01 +0200 Subject: [PATCH 08/11] [C004] Some string optimization --- src/_C004.cpp | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/_C004.cpp b/src/_C004.cpp index 0c2f5328ed..0b3e1e7dcf 100644 --- a/src/_C004.cpp +++ b/src/_C004.cpp @@ -67,6 +67,7 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& if (C004_DelayHandler == nullptr) { break; } + if (C004_DelayHandler->queueFull(event->ControllerIndex)) { break; } @@ -97,40 +98,37 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& bool do_process_c004_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C004_queue_element& element = static_cast(element_base); // *INDENT-ON* - String postDataStr = F("api_key="); - - postDataStr += getControllerPass(element._controller_idx, ControllerSettings); // used for API key - - if (element.sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - postDataStr += F("&status="); - postDataStr += element.txt[0]; // FIXME TD-er: Is this correct? - // See: https://nl.mathworks.com/help/thingspeak/writedata.html - } else { - for (uint8_t x = 0; x < element.valueCount; x++) - { - postDataStr += F("&field"); - postDataStr += element.idx + x; - postDataStr += '='; - postDataStr += element.txt[x]; - } - } - if (!ControllerSettings.UseDNS) { - // Patch the ControllerSettings to make sure we're using a hostname instead of an IP address - ControllerSettings.setHostname(F("api.thingspeak.com")); // PM_CZ: HTTP requests must contain host headers. - ControllerSettings.UseDNS = true; +String postDataStr = concat(F("api_key="), + getControllerPass(element._controller_idx, ControllerSettings)); // used for API key + +if (element.sensorType == Sensor_VType::SENSOR_TYPE_STRING) { + postDataStr += concat(F("&status="), element.txt[0]); // FIXME TD-er: Is this correct? + // See: https://nl.mathworks.com/help/thingspeak/writedata.html +} else { + for (uint8_t x = 0; x < element.valueCount; x++) { + postDataStr += strformat(F("&field%d=%s"), + element.idx + x, + element.txt[x].c_str()); } +} + +if (!ControllerSettings.UseDNS) { + // Patch the ControllerSettings to make sure we're using a hostname instead of an IP address + ControllerSettings.setHostname(F("api.thingspeak.com")); // PM_CZ: HTTP requests must contain host headers. + ControllerSettings.UseDNS = true; +} - int httpCode = -1; - send_via_http( - controller_number, - ControllerSettings, - element._controller_idx, - F("/update"), // uri - F("POST"), - F("Content-Type: application/x-www-form-urlencoded\r\n"), - postDataStr, - httpCode); - return (httpCode >= 100) && (httpCode < 300); +int httpCode = -1; +send_via_http( + controller_number, + ControllerSettings, + element._controller_idx, + F("/update"), // uri + F("POST"), + F("Content-Type: application/x-www-form-urlencoded\r\n"), + postDataStr, + httpCode); +return (httpCode >= 100) && (httpCode < 300); } #endif // ifdef USES_C004 From 48869243666ddddb8c4d3617f98f64117734266a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 29 May 2024 21:45:50 +0200 Subject: [PATCH 09/11] [C004] Add documentation and examples --- docs/source/Controller/C004.rst | 113 ++++++++++++++++++ .../C004_ControllerConfiguration.png | Bin 0 -> 36172 bytes 2 files changed, 113 insertions(+) create mode 100644 docs/source/Controller/C004_ControllerConfiguration.png diff --git a/docs/source/Controller/C004.rst b/docs/source/Controller/C004.rst index 672f470d7a..1b47087df8 100644 --- a/docs/source/Controller/C004.rst +++ b/docs/source/Controller/C004.rst @@ -20,12 +20,125 @@ GitHub: |C004_github|_ Maintainer: |C004_maintainer| +Description +----------- + +The ThingSpeak controller allows to send data to the ThingSpeak web service so it can be presented using the tools available there. + +Both free and paid accounts are supported, where a free account has some restrictions like the number of 'Channels' that can be created, and the maximum update-frequency. + +Configuration +------------- + +.. image:: C004_ControllerConfiguration.png + +* **Protocol**: The selected Controller Protocol. + +* **Locate Controller**: This should be set to ``Use Hostname``, this will be forced when data is sent via the controller, but not saved. + +* **Controller Hostname**: This should be set to ``api.thingspeak.com``, the only available API entrypoint. No url suffix should be added (at the time of writing this documentation). + +* **Controller Port**: The default port number ``80`` should be used. + +Controller Queue +^^^^^^^^^^^^^^^^ + +* **Minimum Send Interval**: The minimum time to wait before the next set of data can be sent to the controller. This can be set quite high, as the update frequency is not very high, and when sending data in too quick succession, the new data will be ignored. Especially on a free account, only a limited number of updates per day can be sent, so setting this to 600000 msec. (10 minutes) is quite realistic. + +* **Max Queue Depth**: Determines the number of elements that can be stored in the queue before new samples are dismissed. When the receiving server is available, the data elements will be sent, emptying the queue, and making room for new samples again. To avoid sending too many messages at once, causing them to be discarded by ThingSpeak, the queue depth should best be set to 1. At the same time, to avoid samples to be discarded by the controller, the Interval for devices using this controller should also be set rather high, in the 20 to 30 minutes range. + +* **Max Retries**: Setting is not used for this controller. + +* **Full Queue Action**: The controller always uses the ``Ignore New`` strategy when the queue is filled. + +* **Allow Expire**: Not applicable for this controller. + +* **De-duplicate**: When enabled, avoids sending duplicate data, comparing to what's currently in the send-queue. + +* **Check Reply**: The controller always uses the ``Ignore Acknowledgement`` setting. + +* **Client Timeout**: The timeout to allow before the connection is failing. As ThingSpeak is an external internet service, a somewhat longer than the default timeout should be used. 1000 msec. should work in most cases, but on very high latency connections, this value can be increased. + +Credentials +^^^^^^^^^^^ + +* **ThingHTTP Name**: This setting is currently not used for this controller. + +* **API Key**: Enter the Write API Key for the channel the data should be sent to. To use multiple channels, an extra ThingSpeak controller should be configured, so a different API Key can be configured. + +* **Enabled**: To enable the controller this box has to be checked. + +Practical use-cases +------------------- + +As ThingSpeak is a low-frequency external service, only a limited number of updates per day can be sent. For a Free account that's limited to 3 million messages per year, with a minimum interval of 15 seconds, effectively ca. 8000 updates per day. For a paid account it depends on the type of the account used, details and current price information can be found via the `ThingSpeak licensing FAQ `_ + +Multiple sensors to a single channel +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To send out data from multiple ESPEasy tasks (max. 4 values) to a single ThingSpeak Channel (max. 8 fields), the data has to be sent at least 15 seconds apart. To avoid losing data that is sent too soon, it's best to control the task 'activation' from rules. A simple round-robin system will do: + +.. code-block:: none + + On Clock#Time=All,**:** Do // Once every minute + If %v1% = 0 + TaskRun,BME_280_1 // First BME, Idx 1 (field 1..3) + Elseif %v1% = 1 + TaskRun,BME_280_2 // Second BME, Idx 4 (field 4..6) + Elseif %v1% = 2 + TaskRun,DallasAB // 2 Dallas temperature sensors, Idx 7 (field 7..8) + Endif + Let 1,%v1%+1 // Next task + If %v1% > 2 // All done? + Let,1,0 // Reset + Endif + Endon + +All above tasks, ``BME_280_1``, ``BME_280_2`` and ``DallasAB`` should be set to high Interval values, like 600 (every 10 minutes) to avoid them being run unexpectedly. + +For adding more than 8 values, a new ThingSpeak Channel should be created, and because each Channel is using a different Write API Key, an extra ThingSpeak Controller should be configured, and extra tasks set up similarly but for the extra controller. ESPEasy allows up to 3 controllers to be configured, that *can* all 3 be ThingSpeak controllers, if needed. + +Selected values from multiple sensors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When sending data to a controller via the default task configuration options, all values will be sent to the controller. + +If you want to send only a selection of the values to ThingSpeak, an intermediate Dummy Device should be used to store the values to be sent out, and configured to send the data to the ThingSpeak controller. Multiple sensor values can be collected in the Dummy Device, before it is triggered to send out the data. + +.. code-block:: none + + On BME_280_1#All Do // Single event with all values can best be enabled for the sensor + TaskValueSet,Dummy,1,%eventvalue1% + TaskValueSet,Dummy,2,%eventvalue2% + Endon + + On BME_280_2#All Do + TaskValueSet,Dummy,3,%eventvalue1% + TaskValueSet,Dummy,4,%eventvalue2% + Endon + + On Clock#Time=All,**:** Do // Once every minute + If %v1% = 0 + TaskRun,Dummy // Dummy with BME values, Idx 1 (field 1..4) + Elseif %v1% = 1 + TaskRun,DallasABCD // 4 Dallas temperature sensors, Idx 5 (field 5..8) + Endif + Let 1,%v1%+1 // Next task + If %v1% > 1 // All done? + Let,1,0 // Reset + Endif + Endon + +The Interval for the BMEs can be set to 10..30 seconds, and the Interval for the Dummy task to 0, as it will be triggered from the minute timer. + Change log ---------- .. versionchanged:: 2.0 ... + |added| 2024-05-29 Update documentation and add implementation examples. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Controller/C004_ControllerConfiguration.png b/docs/source/Controller/C004_ControllerConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..2be719cc8bcfd3a85e3346e01566790e95d51c40 GIT binary patch literal 36172 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
3%Rhs=zrU42W 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 Date: Sat, 22 Jun 2024 22:12:48 +0200 Subject: [PATCH 10/11] [C007] Emoncms: add configurable url (Controller Publish) --- src/_C007.cpp | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/_C007.cpp b/src/_C007.cpp index dcab924655..360a9e63fd 100644 --- a/src/_C007.cpp +++ b/src/_C007.cpp @@ -11,6 +11,7 @@ # define CPLUGIN_ID_007 7 # define CPLUGIN_NAME_007 "Emoncms" +# define C007_DEFAULT_URL "/emoncms/input/post.json" bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& string) { @@ -26,6 +27,7 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& proto.usesPassword = true; proto.defaultPort = 80; proto.usesID = true; + proto.usesTemplate = true; break; } @@ -35,6 +37,12 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& break; } + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + event->String2 = F(C007_DEFAULT_URL); + break; + } + case CPlugin::Function::CPLUGIN_INIT: { success = init_c007_delay_queue(event->ControllerIndex); @@ -52,6 +60,7 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& if (C007_DelayHandler == nullptr) { break; } + if (C007_DelayHandler->queueFull(event->ControllerIndex)) { break; } @@ -93,27 +102,22 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& bool do_process_c007_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C007_queue_element& element = static_cast(element_base); // *INDENT-ON* - String url = F("/emoncms/input/post.json?node="); - - url += Settings.Unit; - url += F("&json="); + if (ControllerSettings.Publish[0] == '\0') { + strcpy_P(ControllerSettings.Publish, PSTR(C007_DEFAULT_URL)); + } + String url = strformat(F("%s?node=%d&json="), ControllerSettings.Publish, Settings.Unit); for (uint8_t i = 0; i < element.valueCount; ++i) { - url += (i == 0) ? '{' : ','; - url += F("field"); - url += element.idx + i; - url += ':'; - url += element.txt[i]; + url += strformat(F("%cfield%d:%s"), (i == 0) ? '{' : ',', element.idx + i, element.txt[i].c_str()); } - url += '}'; - url += F("&apikey="); - url += getControllerPass(element._controller_idx, ControllerSettings); // "0UDNN17RW6XAS2E5" // api key + url += strformat(F("}&apikey=%s"), getControllerPass(element._controller_idx, ControllerSettings).c_str()); // "0UDNN17RW6XAS2E5" // api key + + # ifndef BUILD_NO_DEBUG -#ifndef BUILD_NO_DEBUG - if (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE) { - serialPrintln(url); + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLog(LOG_LEVEL_DEBUG_MORE, url); } -#endif + # endif // ifndef BUILD_NO_DEBUG int httpCode = -1; send_via_http( From 60af4a6fc82b67a32b5e3d5e0163607487a3bc4a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 5 Jul 2024 22:31:53 +0200 Subject: [PATCH 11/11] [Coo4] Fix merge conflict issue --- src/_C004.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/_C004.cpp b/src/_C004.cpp index 60d199eaaf..d9903d9b18 100644 --- a/src/_C004.cpp +++ b/src/_C004.cpp @@ -72,7 +72,7 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& break; } - std::unique_ptr element(new C004_queue_element(event)); + std::unique_ptr element(new (std::nothrow) C004_queue_element(event)); success = C004_DelayHandler->addToQueue(std::move(element)); Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE, C004_DelayHandler->getNextScheduleTime()); @@ -118,17 +118,17 @@ if (!ControllerSettings.UseDNS) { ControllerSettings.UseDNS = true; } - int httpCode = -1; - send_via_http( - cpluginID, - ControllerSettings, - element._controller_idx, - F("/update"), // uri - F("POST"), - F("Content-Type: application/x-www-form-urlencoded\r\n"), - postDataStr, - httpCode); - return (httpCode >= 100) && (httpCode < 300); +int httpCode = -1; +send_via_http( + cpluginID, + ControllerSettings, + element._controller_idx, + F("/update"), // uri + F("POST"), + F("Content-Type: application/x-www-form-urlencoded\r\n"), + postDataStr, + httpCode); +return (httpCode >= 100) && (httpCode < 300); } #endif // ifdef USES_C004