diff --git a/docs/source/Plugin/P180.rst b/docs/source/Plugin/P180.rst index 83ef08c1f..4650fd8a8 100644 --- a/docs/source/Plugin/P180.rst +++ b/docs/source/Plugin/P180.rst @@ -71,7 +71,7 @@ Output Configuration * **Output Data Type**: This setting determines the number of available Values, and is also used by f.e. the Domoticz controllers for sending data in the correct format. F.e. when configuring a Temperature & Humidity sensor, it makes sense to choose Temp / Hum here. -* **I2C Init Commands**: A sequence of commands, documented in detail below, that will be used to manipulate the sensor when the plugin is enabled. This can f.e. be used to initialize the sensor, set the **'Enable' GPIO** in the Enabled state, or pulse the **'Reset' GPIO** to reset the device. +* **I2C Init Commands**: A sequence of commands, documented in detail below, that will be used to manipulate the sensor when the plugin is enabled. This can f.e. be used to initialize the sensor, set the **'Enable' GPIO** in the Enabled state, or pulse the **'Reset' GPIO** to reset the device. The last value is used as the initialization result value, where 0 is ``false`` and <> 0 is ``true``. Often this sequence needs ``;calc.1`` appended, to return a ``true`` result. * **I2C Exit Commands**: A sequence of commands that will be sent to the device when the plugin is disabled. This can f.e. be used to set the **'Enable' GPIO** in Disabled state, or set the device in a power-down state. @@ -150,9 +150,9 @@ For ``dataformat`` there are a limited set of options available: The commands available can either be used with the full command, or the 1 character shortcut to save some space in the limited available storage: * ``get.[.]``: ``(g)``: Read the number of bytes matching the format from the device. ```` can be a calculation, f.e. to use ``%value%`` from a previous command. -* ``put..[....]``: ``(p)``: Write data to the device, in the format specified. For ``b`` or ``w`` the provided data determines the length of the data to send. -* ``read.[.].``: ``(r)``: Read data in the requested format from a register in the device. ```` can be a calculation, f.e. to use ``%value%`` from a previous command. -* ``write...[....]``: ``(w)``: Write data to a register in the device in the specified format. +* ``put..[....]``: ``(p)``: Write data to the device, in the format specified. For ``b`` or ``w`` the provided data and format determines the length of the data to send. ``data`` can be from a global variable, expanded just before execution. +* ``read.[.].``: ``(r)``: Read data in the requested format from a register in the device. ```` can be a calculation, f.e. to use ``%value%`` from a previous command. ``reg`` can be from a global variable, expanded just before execution. +* ``write...[....]``: ``(w)``: Write data to a register in the device in the specified format. ``reg`` and/or ``data`` can be from a global variable, expanded just before execution. (not applicable for ``b`` and ``w`` format!) * ``eval``: ``(e)``: Evaluate the previous command, and set as selected data for ``calc`` or ``if`` commands. * ``value.``: ``(v)``: (1..4 or a value name) Store the last calculated result, or last read value, in the specified Value field. * ``calc.``: ``(c)``: Calculate a result, like Rules, extra available vars: ``%value%``, ``%pvalue%``, ``%h%``, ``%b0%..%b%``, ``%bx0%..%bx%``, ``%w0%..%w%``, ``%wx0%..%wx%`` @@ -169,13 +169,15 @@ During processing, the data has to be made available to some of the following st The next variables are only available after an ``eval`` I2C Command is executed: -* ``%value%``: The value retrieved in decimal presentation, or the read String for format ``str``. So if a ``read`` or ``get`` command was used. Not applicable for ``b.`` and ``w.`` dataformats, ``%h%``, ``%b..%`` or ``%w..%`` variables should be used. +* ``%value%``: The value retrieved in decimal presentation, or the read String for format ``str``. So if a ``read`` or ``get`` command was used. Not applicable for ``b.`` and ``w.`` dataformats, then ``%h%``, ``%b..%`` or ``%w..%`` variables should be used. * ``%h%``: The result of a ``read`` or ``get`` formatted as hex bytes with a single '0x' prefix, using the minimal number of digits needed. For ``b.`` or ``w.`` dataformat all bytes/words are concatenated. If 7 bytes are retrieved using ``get.b.7`` command, ``%h`` will be replaced by ``0x00010203040506``, or 3 words using ``get.w.3`` with ``0x000000010002``, showing the retrieved data of course. * ``%b%``: The value of byte N (0-based!), retrieved using the ``b.`` dataformat. This will be a decimal presentation of the data. * ``%bx%``: The value of byte N (0-based!), retrieved using the ``b.`` dataformat. This will be a 2 digit hexadecimal presentation, without '0x' prefix, of the data. * ``%w%``: The value of word N (0-based!), retrieved using the ``w.`` dataformat. This will be a decimal presentation of the data. * ``%wx%``: The value of word N (0-based!), retrieved using the ``w.`` dataformat. This will be a 4 digit hexadecimal presentation, without '0x' prefix, of the data. +For the ``put`` and ``write`` commands, the ``data`` can be from a global variable, in the usual notations, like ``%10%``, ``%v_bits%``, ``[var#5]`` etc. When sending an I2C command sequence from rules, the variable shuld be escaped, f.e. ``\%v_bits\%`` or ``\[int#bits\]``, etc. + Processing commands for 1/sec, 10/sec and 50/sec plugin events ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -614,6 +616,8 @@ Change log .. versionchanged:: 2.0 ... + |added| 2026-04-18 Add support for global variables in ``put``, ``write`` and ``read`` commands. + |added| 2025-12-08 Add index to example configurations. |added| diff --git a/src/_P180_I2C_generic.ino b/src/_P180_I2C_generic.ino index 15bf71e8c..40bfed09a 100644 --- a/src/_P180_I2C_generic.ino +++ b/src/_P180_I2C_generic.ino @@ -6,6 +6,7 @@ // ####################################################################################################### /** Changelog: + * 2026-04-18 tonhuisman: Process I2C commands synchronously when triggered from an internal source, like rules. * 2026-02-14 tonhuisman: Set default Cache-name when empty, for improved plugin runtime performance. * 2025-12-06 tonhuisman: Enable CustomVTypeVar and MQTTStateClass device options, as any type of sensor can be connected * 2025-06-04 tonhuisman: Add [#log] to Get Config to fetch the current Parsing and execution log setting @@ -52,9 +53,9 @@ * [.[.]][.][.];...[|(1ps|10ps|50ps)|] * * get.[.] - * put..[....] - * read.[.]. - * write...[....] + * put..[....] Data can be from global , except for b and w format + * read.[.]. Register can be from global + * write...[....] Data can be from global , except for b and w format * eval * value. (1..4) * calc. Like Rules, extra available vars: %value%, %pvalue%, %h%, %b0%..%b%, %bx0%..%bx%, %w0%..%w%, %wx0%..%wx% diff --git a/src/src/Helpers/BusCmd_Helper.cpp b/src/src/Helpers/BusCmd_Helper.cpp index 01d8fe0e3..832017714 100644 --- a/src/src/Helpers/BusCmd_Helper.cpp +++ b/src/src/Helpers/BusCmd_Helper.cpp @@ -1,20 +1,20 @@ #include "../Helpers/BusCmd_Helper.h" #if FEATURE_BUS_COMMAND -#include -#include "../Globals/RulesCalculate.h" +# include +# include "../Globals/RulesCalculate.h" // These commands (not case-sensitive) must have the same order as the BusCmd_Commands_e enum class const char BusCmd_commands[] PROGMEM = "n|g|p|r|w|s|t|e|c|v|d|a|z|i|l|" - #if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES "m|" - #endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES ; const char BusCmd_commandsLong[] PROGMEM = "nop|get|put|read|write|read16|write16|eval|calc|value|delay|enable|reset|if|let|" - #if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES "letstr|" - #endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES ; // Supported data formats, _ == undefined, not processed @@ -36,9 +36,9 @@ const char BusCmd_dataFormats[] PROGMEM = "32le|" "b|" "w|" - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING "str|" - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING ; /** @@ -52,27 +52,35 @@ BusCmd_Command_struct::BusCmd_Command_struct(BusCmd_Command_e _command, String _calculation, String _variable) :command(_command), format(_format), reg(_reg), len(_len), calculation(_calculation), variable(_variable) { - switch (format) { - case BusCmd_DataFormat_e::undefined: d0_uint32_t = (uint32_t)_data; break; // Special case - case BusCmd_DataFormat_e::uint8_t: d0_uint8_t = (uint8_t)_data; break; + switch (format) + { + case BusCmd_DataFormat_e::undefined: d0_uint32_t = (uint32_t)_data; + break; // Special case + case BusCmd_DataFormat_e::uint8_t: d0_uint8_t = (uint8_t)_data; + break; case BusCmd_DataFormat_e::uint16_t: - case BusCmd_DataFormat_e::uint16_t_LE: d0_uint16_t = (uint16_t)_data; break; + case BusCmd_DataFormat_e::uint16_t_LE: d0_uint16_t = (uint16_t)_data; + break; case BusCmd_DataFormat_e::uint24_t: case BusCmd_DataFormat_e::uint32_t: case BusCmd_DataFormat_e::uint24_t_LE: - case BusCmd_DataFormat_e::uint32_t_LE: d0_uint32_t = (uint32_t)_data; break; - case BusCmd_DataFormat_e::int8_t: d0_int8_t = _data; break; + case BusCmd_DataFormat_e::uint32_t_LE: d0_uint32_t = (uint32_t)_data; + break; + case BusCmd_DataFormat_e::int8_t: d0_int8_t = _data; + break; case BusCmd_DataFormat_e::int16_t: - case BusCmd_DataFormat_e::int16_t_LE: d0_int16_t = _data; break; + case BusCmd_DataFormat_e::int16_t_LE: d0_int16_t = _data; + break; case BusCmd_DataFormat_e::int24_t: case BusCmd_DataFormat_e::int32_t: case BusCmd_DataFormat_e::int24_t_LE: - case BusCmd_DataFormat_e::int32_t_LE: d0_int32_t = _data; break; + case BusCmd_DataFormat_e::int32_t_LE: d0_int32_t = _data; + break; case BusCmd_DataFormat_e::bytes: case BusCmd_DataFormat_e::words: - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING case BusCmd_DataFormat_e::string: - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING break; } } @@ -89,35 +97,40 @@ BusCmd_Command_struct::~BusCmd_Command_struct() { int64_t BusCmd_Command_struct::getIntValue() { int64_t data{}; - switch (format) { + switch (format) + { case BusCmd_DataFormat_e::undefined: break; - case BusCmd_DataFormat_e::uint8_t: data = d0_uint8_t; break; + case BusCmd_DataFormat_e::uint8_t: data = d0_uint8_t; + break; case BusCmd_DataFormat_e::uint16_t: - case BusCmd_DataFormat_e::uint16_t_LE: data = d0_uint16_t; break; + case BusCmd_DataFormat_e::uint16_t_LE: data = d0_uint16_t; + break; case BusCmd_DataFormat_e::uint24_t: case BusCmd_DataFormat_e::uint32_t: case BusCmd_DataFormat_e::uint24_t_LE: - case BusCmd_DataFormat_e::uint32_t_LE: data = d0_uint32_t; break; - case BusCmd_DataFormat_e::int8_t: data = d0_int8_t; break; + case BusCmd_DataFormat_e::uint32_t_LE: data = d0_uint32_t; + break; + case BusCmd_DataFormat_e::int8_t: data = d0_int8_t; + break; case BusCmd_DataFormat_e::int16_t: - case BusCmd_DataFormat_e::int16_t_LE: data = d0_int16_t; break; + case BusCmd_DataFormat_e::int16_t_LE: data = d0_int16_t; + break; case BusCmd_DataFormat_e::int24_t: case BusCmd_DataFormat_e::int32_t: case BusCmd_DataFormat_e::int24_t_LE: - case BusCmd_DataFormat_e::int32_t_LE: data = d0_int32_t; break; + case BusCmd_DataFormat_e::int32_t_LE: data = d0_int32_t; + break; case BusCmd_DataFormat_e::bytes: case BusCmd_DataFormat_e::words: - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING case BusCmd_DataFormat_e::string: - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING break; } return data; } -String BusCmd_Command_struct::getHexValue(const bool withPrefix) { - return withPrefix ? concat(F("0x"), getHexValue()) : getHexValue(); -} +String BusCmd_Command_struct::getHexValue(const bool withPrefix) { return withPrefix ? concat(F("0x"), getHexValue()) : getHexValue(); } String BusCmd_Command_struct::getHexValue() { uint64_t data64 = getUIntValue(); @@ -126,15 +139,16 @@ String BusCmd_Command_struct::getHexValue() { return formatToHex_array(&data_b[0], data_b.size()); } else if (BusCmd_DataFormat_e::words == format) { return formatToHex_wordarray(&data_w[0], data_w.size()); - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING } else if (BusCmd_DataFormat_e::string == format) { return EMPTY_STRING; - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING } return formatToHex(data64); } -#if FEATURE_BUSCMD_STRING +# if FEATURE_BUSCMD_STRING + String BusCmd_Command_struct::getString() { if (BusCmd_DataFormat_e::string == format) { return variable; @@ -142,9 +156,10 @@ String BusCmd_Command_struct::getString() { return EMPTY_STRING; } -#endif // if FEATURE_BUSCMD_STRING +# endif // if FEATURE_BUSCMD_STRING + +# ifndef LIMIT_BUILD_SIZE -#ifndef LIMIT_BUILD_SIZE String BusCmd_Command_struct::toString() { char cmd[8]{}; char cmdS[3]{}; @@ -175,9 +190,9 @@ String BusCmd_Command_struct::toString() { if (!variable.isEmpty()) { result = concat(result, strformat( - # if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING BusCmd_DataFormat_e::string == format ? F(", string: %s") : - # endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING F(", variable: %s"), variable.c_str())); } @@ -187,7 +202,7 @@ String BusCmd_Command_struct::toString() { return result; } -#endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE /** * Constructor BusCmd_Buffer @@ -216,7 +231,8 @@ BusCmd_Helper_struct::~BusCmd_Helper_struct() { } const __FlashStringHelper * BusCmd_Helper_struct::cacheSuffix(BusCmd_CommandSource_e source) { - switch (source) { + switch (source) + { case BusCmd_CommandSource_e::PluginIdle: case BusCmd_CommandSource_e::PluginGetConfigVar: case BusCmd_CommandSource_e::PluginRead: @@ -264,14 +280,15 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons if (!key.isEmpty() && (_commandCache.count(key) == 1) && !update) { commands = _commandCache.find(key)->second; -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Retrieve '%s' from cache with %d commands."), name.c_str(), commands.size())); } -#endif +# endif // ifndef BUILD_NO_DEBUG } - if (!line.isEmpty() && ((commands.empty()) || update) && parseAndLogOK) { + if (!line.isEmpty() && (commands.empty() || update) && parseAndLogOK) { int evt = 1; while (evt > 0) { @@ -324,12 +341,12 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons arg0 = parseStringKeepCaseNoTrim(cmdAll, i, BUSCMD_ARGUMENT_SEPARATOR); } - #ifndef LIMIT_BUILD_SIZE + # ifndef LIMIT_BUILD_SIZE if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Arguments parsed: %d (%s)"), args.size(), cmdAll.c_str())); } - #endif // ifndef LIMIT_BUILD_SIZE + # endif // ifndef LIMIT_BUILD_SIZE args[0].toLowerCase(); String sFmt = args[1]; @@ -353,9 +370,9 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons } if ((BusCmd_DataFormat_e::bytes == fmt) || (BusCmd_DataFormat_e::words == fmt) - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING || (BusCmd_DataFormat_e::string == fmt) - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING ) { if (!validUIntFromString(args[arg], len) && !args[arg].isEmpty()) { variable = args[arg]; @@ -367,20 +384,27 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons if (arg1i > -1) { BusCmd_Command_e cmd = static_cast(arg1i); int64_t val = 0; - validInt64FromString(args[arg], val); - switch (cmd) { + if (!validInt64FromString(args[arg], val) && ( + cmd.isRegisterCmd() || cmd.isWriteCmd() + )) { + calculation = wrapWithQuotes(args[arg]); + stripEscapeCharacters(calculation); + } + + switch (cmd) + { case BusCmd_Command_e::NOP: break; case BusCmd_Command_e::Read: // get - g. break; case BusCmd_Command_e::Write: // put - p.. - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING if (BusCmd_DataFormat_e::string == fmt) { variable = args[arg]; } - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING break; case BusCmd_Command_e::Calculate: // calc - c. case BusCmd_Command_e::If: // if - i.[.skip] @@ -394,9 +418,9 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons } break; case BusCmd_Command_e::Let: // let - l.. - #if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES case BusCmd_Command_e::LetStr: // letstr - m.. - #endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES fmt = BusCmd_DataFormat_e::undefined; val = 0; variable = args[arg - 1]; @@ -429,22 +453,26 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons val = 0; if (!((BusCmd_DataFormat_e::bytes == fmt) || (BusCmd_DataFormat_e::words == fmt) - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING || (BusCmd_DataFormat_e::string == fmt) - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING )) { ++arg; - validInt64FromString(args[arg], val); + if (!validInt64FromString(args[arg], val)) { + if (!calculation.isEmpty()) { calculation += BUSCMD_VARIABLE_SEPARATOR; } // Add separator + calculation += wrapWithQuotes(args[arg]); + stripEscapeCharacters(calculation); + } } - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING if (BusCmd_DataFormat_e::string == fmt) { ++arg; variable = args[arg]; } - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING break; case BusCmd_Command_e::EnableGPIO: // enable - l. case BusCmd_Command_e::ResetGPIO: // reset - z.. @@ -498,23 +526,24 @@ std::vectorBusCmd_Helper_struct::parseBusCmdCommands(cons } if (evt > 0) { - #ifndef LIMIT_BUILD_SIZE + # ifndef LIMIT_BUILD_SIZE if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog) { for (auto it = commands.begin(); it != commands.end(); ++it) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Parsing command: %s, name: %s"), it->toString().c_str(), name.c_str())); } } - #endif // ifndef LIMIT_BUILD_SIZE + # endif // ifndef LIMIT_BUILD_SIZE if (!key.isEmpty()) { _commandCache[concat(key, keyPostfix)] = commands; -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Insert '%s%s' into cache with %d commands."), name.c_str(), keyPostfix.c_str(), commands.size())); } -#endif +# endif // ifndef BUILD_NO_DEBUG } } } @@ -577,11 +606,45 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { _lastCommand = _it->command; // We only need this for long ResetGPIO pulses _lastReg = _it->reg; - switch (_it->command) { + if (!_it->calculation.isEmpty() && ( + _it->command.isRegisterCmd() || _it->command.isWriteCmd() + )) { + String toCalc(replacePluginValues(parseStringKeepCase(_it->calculation, 1, BUSCMD_VARIABLE_SEPARATOR))); + const String newCalc = parseTemplate(toCalc); // Process like rules + ESPEASY_RULES_FLOAT_TYPE tmp{}; + + if (Calculate(newCalc, tmp) == CalculateReturnCode::OK) { + // If we have 2 variable parts in calculation, it's a write command with vars for both register and value + String toCalc(replacePluginValues(parseStringKeepCase(_it->calculation, 2, BUSCMD_VARIABLE_SEPARATOR))); + + if (_it->command.isReadCmd() || !toCalc.isEmpty()) { // get is already excluded, 'if' above + _it->reg = static_cast(tmp); // First part is always register if we have 2 variable parts + } else if (_it->command.isWriteCmd()) { + _it->d0_int32_t = static_cast(tmp); + } + + if (!toCalc.isEmpty() && _it->command.isWriteCmd()) { + const String newCalc = parseTemplate(toCalc); // Process like rules + ESPEASY_RULES_FLOAT_TYPE tmp{}; + + if (Calculate(newCalc, tmp) == CalculateReturnCode::OK) { + _it->d0_int32_t = static_cast(tmp); + } + } + } + + // NOTE: Keep for development-testing + // addLog(LOG_LEVEL_INFO, strformat(F("BusCmd: Pre-calc '%s' -> '%s' = '%s'"), + // _it->calculation.c_str(), newCalc.c_str(), doubleToString(tmp).c_str())); + } + + switch (_it->command) + { case BusCmd_Command_e::NOP: break; case BusCmd_Command_e::Read: - switch (_it->format) { + switch (_it->format) + { case BusCmd_DataFormat_e::undefined: break; case BusCmd_DataFormat_e::uint8_t: case BusCmd_DataFormat_e::int8_t: @@ -654,17 +717,18 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { case BusCmd_DataFormat_e::words: _it->data_w = _iBusCmd_Handler->read16uW(_len); break; - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING case BusCmd_DataFormat_e::string: _it->variable = _iBusCmd_Handler->readString(_len); break; - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING } result = true; break; case BusCmd_Command_e::Write: - switch (_it->format) { + switch (_it->format) + { case BusCmd_DataFormat_e::undefined: break; case BusCmd_DataFormat_e::uint8_t: case BusCmd_DataFormat_e::int8_t: @@ -733,18 +797,19 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { case BusCmd_DataFormat_e::words: result = _iBusCmd_Handler->write16uW(_it->data_w); break; - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING case BusCmd_DataFormat_e::string: result = _iBusCmd_Handler->writeString(_it->variable); break; - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING } break; case BusCmd_Command_e::Register16Read: wideReg = true; // fall through case BusCmd_Command_e::RegisterRead: - switch (_it->format) { + switch (_it->format) + { case BusCmd_DataFormat_e::undefined: break; case BusCmd_DataFormat_e::uint8_t: case BusCmd_DataFormat_e::int8_t: @@ -817,11 +882,11 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { case BusCmd_DataFormat_e::words: _it->data_w = _iBusCmd_Handler->read16uWREG(_it->reg, _len, wideReg); break; - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING case BusCmd_DataFormat_e::string: _it->variable = _iBusCmd_Handler->readStringREG(_it->reg, _len, wideReg); break; - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING } result = true; break; @@ -829,7 +894,8 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { wideReg = true; // fall through case BusCmd_Command_e::RegisterWrite: - switch (_it->format) { + switch (_it->format) + { case BusCmd_DataFormat_e::undefined: break; case BusCmd_DataFormat_e::uint8_t: case BusCmd_DataFormat_e::int8_t: @@ -898,11 +964,11 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { case BusCmd_DataFormat_e::words: result = _iBusCmd_Handler->write16uWREG(_it->reg, _it->data_w, wideReg); break; - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING case BusCmd_DataFormat_e::string: result = _iBusCmd_Handler->writeStringReg(_it->reg, _it->variable, wideReg); break; - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING } break; case BusCmd_Command_e::Value: @@ -916,9 +982,9 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { case BusCmd_Command_e::Calculate: case BusCmd_Command_e::If: case BusCmd_Command_e::Let: - #if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES case BusCmd_Command_e::LetStr: - #endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES result = true; @@ -927,7 +993,7 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { const String newCalc = parseTemplate(toCalc); // Process like rules ESPEASY_RULES_FLOAT_TYPE tmp{}; - #if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES if ((BusCmd_Command_e::LetStr == _it->command)) { String toVar(replacePluginValues(_it->variable)); @@ -942,14 +1008,16 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { } } } else - #endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + # endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES if (Calculate(newCalc, tmp) == CalculateReturnCode::OK) { -#ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Calculation: %s, result: %s"), toCalc.c_str(), doubleToString(tmp).c_str())); } -#endif + # endif // ifndef BUILD_NO_DEBUG + if (BusCmd_Command_e::If == _it->command) { if (essentiallyZero(tmp)) { // 0 = false => cancel execution if (0 == _it->len) { @@ -1030,7 +1098,8 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { } if ((BusCmd_CommandState_e::Processing == _commandState) && !_valueIsSet) { - switch (_it->format) { + switch (_it->format) + { case BusCmd_DataFormat_e::undefined: break; case BusCmd_DataFormat_e::uint8_t: case BusCmd_DataFormat_e::uint16_t: @@ -1053,36 +1122,37 @@ bool BusCmd_Helper_struct::executeBusCmdCommands() { case BusCmd_DataFormat_e::bytes: case BusCmd_DataFormat_e::words: break; - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING case BusCmd_DataFormat_e::string: - # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE validDoubleFromString(_it->variable, _value); - # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE validFloatFromString(_it->variable, _value); - # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE break; - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING } } -#ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog && parseAndLogOK) { - # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE const String valStr = doubleToString(_value, 2, true); - # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE const String valStr = toString(_value, 2, true); - # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE addLog(LOG_LEVEL_INFO, strformat(F("BUSCMD: Executing command: %s, value[%d]:(%c): %s"), _it->toString().c_str(), _varIndex, _valueIsSet ? 't' : 'f', valStr.c_str())); } -#endif // ifndef LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE ++_it; // Next command while (toSkip > 0 && _it != _commands.end()) { ++_it; --toSkip; // Skip commands, forward-only, for now } + delay(0); } if ((BusCmd_CommandState_e::Processing == _commandState) && @@ -1103,9 +1173,9 @@ String BusCmd_Helper_struct::replacePluginValues(const String& inVar) { if (_evalIsSet) { result.replace(F("%value%"), - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING BusCmd_DataFormat_e::string == _evalCommand->format ? _evalCommand->getString() : - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING toString(_evalCommand->getIntValue())); // %value% result.replace(F("%h%"), _evalCommand->getHexValue(true)); // %h% @@ -1138,11 +1208,12 @@ bool BusCmd_Helper_struct::parseAndExecute(BusCmd_CommandSource_e source, _commands = parseBusCmdCommands(EMPTY_STRING, line); if (!_commands.empty()) { -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO) && _showLog) { addLog(LOG_LEVEL_INFO, strformat(logFormat, _commands.size())); } -#endif +# endif // ifndef BUILD_NO_DEBUG _commandState = BusCmd_CommandState_e::Processing; result = executeBusCmdCommands(); _commands.clear(); @@ -1271,7 +1342,8 @@ void BusCmd_Helper_struct::setCommands(std::vectorcommand _commandState = commandState; } -#ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE + bool BusCmd_Helper_struct::plugin_get_config(struct EventStruct *event, String & string) { const String cmd = parseString(string, 1); @@ -1305,7 +1377,7 @@ bool BusCmd_Helper_struct::plugin_get_config(struct EventStruct *event, string = strformat(F("%g"), _value); } } - # ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { // Not logging log value or early return addLog(LOG_LEVEL_DEBUG, strformat(F("BUSCMD: GET_CONFIG, result: %s, success: %d, value: %g, cmds: %d"), @@ -1314,10 +1386,10 @@ bool BusCmd_Helper_struct::plugin_get_config(struct EventStruct *event, _value, cmds.size())); } - # endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG return success; } -#endif // ifndef LIMIT_BUILD_SIZE -#endif \ No newline at end of file +# endif // ifndef LIMIT_BUILD_SIZE +#endif // if FEATURE_BUS_COMMAND diff --git a/src/src/Helpers/BusCmd_Helper.h b/src/src/Helpers/BusCmd_Helper.h index 1c9b19b77..1ab6c5e96 100644 --- a/src/src/Helpers/BusCmd_Helper.h +++ b/src/src/Helpers/BusCmd_Helper.h @@ -2,6 +2,9 @@ #define _HELPERS_BUSCMD_HELPER_H /** Changelog: + * 2026-04-19 tonhuisman: Allow also variables to be used for register in write commands + * 2026-04-18 tonhuisman: Limit variable expansion to read (register) and put/write (value) commands + * 2026-04-12 tonhuisman: Allow rules-variables to be used for get/read and put/write commands * 2025-08-16 tonhuisman: Extend If I2C command to optionally skip forward N commands on false (0) result * 2025-08-07 tonhuisman: Add LetStr I2C command, analogue to Rules LetStr command * 2025-06-03 tonhuisman: Add PLUGIN_GET_CONFIG_VALUE support, guarded with ifndef LIMIT_BUILD_SIZE @@ -11,32 +14,67 @@ #include "../../_Plugin_Helper.h" #if FEATURE_BUS_COMMAND -#include "../Helpers/IBusCmd_Handler.h" +# include "../Helpers/IBusCmd_Handler.h" -#define BUSCMD_EVENT_SEPARATOR '|' -#define BUSCMD_COMMAND_SEPARATOR ';' -#define BUSCMD_ARGUMENT_SEPARATOR '.' +# define BUSCMD_EVENT_SEPARATOR '|' +# define BUSCMD_COMMAND_SEPARATOR ';' +# define BUSCMD_ARGUMENT_SEPARATOR '.' +# define BUSCMD_VARIABLE_SEPARATOR ';' -enum class BusCmd_Command_e : uint8_t { - NOP = 0u, // 'n' - Read, // 'g' - Write, // 'p' - RegisterRead, // 'r' 8 bit register value - RegisterWrite, // 'w' - Register16Read, // 's' 16 bit register value - Register16Write, // 't' - Eval, // 'e' - Calculate, // 'c' - Value, // 'v' - Delay, // 'd' - EnableGPIO, // 'a' - ResetGPIO, // 'z' - If, // 'i' - Let, // 'l' - #if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES - LetStr, // 'm' - #endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES -}; +// enum class with methods (workaround): https://stackoverflow.com/questions/21295935/can-a-c-enum-class-have-methods +class BusCmd_Command_e +{ +public: + + enum Cmds : uint8_t { + NOP = 0u, // 'n' + Read, // 'g' + Write, // 'p' + RegisterRead, // 'r' 8 bit register value + RegisterWrite, // 'w' + Register16Read, // 's' 16 bit register value + Register16Write, // 't' + Eval, // 'e' + Calculate, // 'c' + Value, // 'v' + Delay, // 'd' + EnableGPIO, // 'a' + ResetGPIO, // 'z' + If, // 'i' + Let, // 'l' + # if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + LetStr, // 'm' + # endif // if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES + + }; + + BusCmd_Command_e() = default; + constexpr BusCmd_Command_e(Cmds aCmd): value(aCmd) {} + + constexpr BusCmd_Command_e(uint8_t uCmd): value(static_cast(uCmd)) {} + + constexpr operator Cmds() const { + return value; + } + explicit operator bool() const = delete; + + constexpr bool isReadCmd() const { + return Cmds::Read == value || Cmds::RegisterRead == value || Cmds::Register16Read == value; + } + + constexpr bool isWriteCmd() const { + return Cmds::Write == value || Cmds::RegisterWrite == value || Cmds::Register16Write == value; + } + + constexpr bool isRegisterCmd() const { + return Cmds::RegisterRead == value || Cmds::RegisterWrite == value || + Cmds::Register16Read == value || Cmds::Register16Write == value; + } + +private: + + Cmds value; +}; // class BusCmd_Command_e enum class BusCmd_DataFormat_e : uint8_t { undefined = 0u, @@ -56,9 +94,10 @@ enum class BusCmd_DataFormat_e : uint8_t { int32_t_LE, bytes, words, - #if FEATURE_BUSCMD_STRING + # if FEATURE_BUSCMD_STRING string, - #endif // if FEATURE_BUSCMD_STRING + # endif // if FEATURE_BUSCMD_STRING + }; enum class BusCmd_CommandState_e :uint8_t { @@ -67,6 +106,7 @@ enum class BusCmd_CommandState_e :uint8_t { StartingDelay, // Interrupts the command execution loop WaitingForDelay, ConditionalExit, // Cancels further command execution + }; enum class BusCmd_CommandSource_e : uint8_t { @@ -77,6 +117,7 @@ enum class BusCmd_CommandSource_e : uint8_t { PluginTenPerSecond, PluginFiftyPerSecond, PluginGetConfigVar, + }; struct BusCmd_Command_struct { @@ -89,19 +130,18 @@ struct BusCmd_Command_struct { uint32_t _len, String _calculation, String _variable); - #ifndef LIMIT_BUILD_SIZE + # ifndef LIMIT_BUILD_SIZE String toString(); - #endif // ifndef LIMIT_BUILD_SIZE + # endif // ifndef LIMIT_BUILD_SIZE String getHexValue(); String getHexValue(const bool withPrefix); int64_t getIntValue(); - uint32_t getUIntValue() { - return static_cast(getIntValue()); - } - #if FEATURE_BUSCMD_STRING - String getString(); - #endif // if FEATURE_BUSCMD_STRING + uint32_t getUIntValue() { return static_cast(getIntValue()); } + + # if FEATURE_BUSCMD_STRING + String getString(); + # endif // if FEATURE_BUSCMD_STRING BusCmd_Command_e command = BusCmd_Command_e::NOP; BusCmd_DataFormat_e format = BusCmd_DataFormat_e::undefined; @@ -113,28 +153,39 @@ struct BusCmd_Command_struct { uint8_t d1_uint8_t; uint8_t d2_uint8_t; uint8_t d3_uint8_t; + }; + struct { int8_t d0_int8_t; int8_t d1_int8_t; int8_t d2_int8_t; int8_t d3_int8_t; + }; + struct { uint16_t d0_uint16_t; uint16_t d1_uint16_t; + }; + struct { int16_t d0_int16_t; int16_t d1_int16_t; + }; + int32_t d0_int32_t; uint32_t d0_uint32_t{}; + }; + std::vector data_b; std::vectordata_w; String calculation; String variable; + }; struct BusCmd_Buffer { @@ -142,6 +193,7 @@ struct BusCmd_Buffer { const String& line); String cacheName; String commandSet; + }; struct BusCmd_Helper_struct { @@ -158,10 +210,10 @@ struct BusCmd_Helper_struct { bool plugin_once_a_second(struct EventStruct *event); bool plugin_ten_per_second(struct EventStruct *event); bool plugin_fifty_per_second(struct EventStruct *event); - #ifndef LIMIT_BUILD_SIZE + # ifndef LIMIT_BUILD_SIZE bool plugin_get_config(struct EventStruct *event, String & string); - #endif // ifndef LIMIT_BUILD_SIZE + # endif // ifndef LIMIT_BUILD_SIZE std::vectorparseBusCmdCommands(const String& name, const String& line); @@ -174,11 +226,10 @@ struct BusCmd_Helper_struct { bool parseAndExecute(BusCmd_CommandSource_e source, const String & line, const String & logFormat); + bool processCommands(struct EventStruct *event); // Setters - void setLog(bool showLog) { - _showLog = showLog; - } + void setLog(bool showLog) { _showLog = showLog; } void setCommands(std::vectorcommands, taskVarIndex_t taskVarIndex, @@ -190,9 +241,7 @@ struct BusCmd_Helper_struct { const String& name, const String& line); - inline void setCommandSource(BusCmd_CommandSource_e commandSource) { - _commandSource = commandSource; - } + inline void setCommandSource(BusCmd_CommandSource_e commandSource) { _commandSource = commandSource; } inline BusCmd_CommandSource_e getCommandSource() const { return _commandSource; @@ -208,7 +257,6 @@ struct BusCmd_Helper_struct { private: String replacePluginValues(const String& inVar); - bool processCommands(struct EventStruct *event); IBusCmd_Handler*_iBusCmd_Handler = nullptr; @@ -238,6 +286,8 @@ private: std::vector::iterator _evalCommand; std::vector::iterator _it; std::vector _buffer; + }; -#endif + +#endif // if FEATURE_BUS_COMMAND #endif // ifndef _HELPERS_BUSCMD_HELPER_H diff --git a/src/src/PluginStructs/P180_data_struct.cpp b/src/src/PluginStructs/P180_data_struct.cpp index eabc55737..1d81a16fc 100644 --- a/src/src/PluginStructs/P180_data_struct.cpp +++ b/src/src/PluginStructs/P180_data_struct.cpp @@ -165,9 +165,15 @@ bool P180_data_struct::plugin_write(struct EventStruct *event, busCmd_Helper->setCommands(cmds, taskVar, 0, - 1, // Process single entry + 1, // Process single entry BusCmd_CommandState_e::Processing); - Scheduler.schedule_task_device_timer(_taskIndex, millis() + 5); + + if (EventValueSource::isExternalSource(event->Source)) { // Execute on next occasion + Scheduler.schedule_task_device_timer(_taskIndex, millis() + 5); + } else { // Execute immediately + busCmd_Helper->setCommandSource(BusCmd_CommandSource_e::PluginWrite); + busCmd_Helper->processCommands(event); + } success = true; } @@ -180,6 +186,7 @@ bool P180_data_struct::plugin_write(struct EventStruct *event, * Handle get config value retrieval processing ********************************************************************************************/ # ifndef LIMIT_BUILD_SIZE + bool P180_data_struct::plugin_get_config(struct EventStruct *event, String & string) { bool success = false;