Merge pull request #5526 from tonhuisman/feature/P180-buscmd-helper-support-for-data-from-variables

[P180] [BusCmd] Allow rules variables in read and put/write (bus) commands
This commit is contained in:
TD-er
2026-04-20 23:05:51 +02:00
committed by GitHub
5 changed files with 288 additions and 154 deletions
+9 -5
View File
@@ -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.<format>[.<len>]``: ``(g)``: Read the number of bytes matching the format from the device. ``<len>`` can be a calculation, f.e. to use ``%value%`` from a previous command.
* ``put.<format>.<data>[.<data>...]``: ``(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.<format>[.<len>].<reg>``: ``(r)``: Read data in the requested format from a register in the device. ``<len>`` can be a calculation, f.e. to use ``%value%`` from a previous command.
* ``write.<format>.<reg>.<data>[.<data>...]``: ``(w)``: Write data to a register in the device in the specified format.
* ``put.<format>.<data>[.<data>...]``: ``(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.<format>[.<len>].<reg>``: ``(r)``: Read data in the requested format from a register in the device. ``<len>`` 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.<format>.<reg>.<data>[.<data>...]``: ``(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.<valueIndex>``: ``(v)``: (1..4 or a value name) Store the last calculated result, or last read value, in the specified Value field.
* ``calc.<calculation>``: ``(c)``: Calculate a result, like Rules, extra available vars: ``%value%``, ``%pvalue%``, ``%h%``, ``%b0%..%b<n>%``, ``%bx0%..%bx<n>%``, ``%w0%..%w<n>%``, ``%wx0%..%wx<n>%``
@@ -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.<length>`` and ``w.<length>`` 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.<length>`` and ``w.<length>`` 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.<length>`` or ``w.<length>`` 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<N>%``: The value of byte N (0-based!), retrieved using the ``b.<length>`` dataformat. This will be a decimal presentation of the data.
* ``%bx<N>%``: The value of byte N (0-based!), retrieved using the ``b.<length>`` dataformat. This will be a 2 digit hexadecimal presentation, without '0x' prefix, of the data.
* ``%w<N>%``: The value of word N (0-based!), retrieved using the ``w.<length>`` dataformat. This will be a decimal presentation of the data.
* ``%wx<N>%``: The value of word N (0-based!), retrieved using the ``w.<length>`` 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|
+4 -3
View File
@@ -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 [<taskname>#log] to Get Config to fetch the current Parsing and execution log setting
@@ -52,9 +53,9 @@
* <cmd>[.<fmt>[.<len>]][.<register>][.<data>];...[|(1ps|10ps|50ps)|<cmd_sequences>]
*
* get.<fmt>[.<len>]
* put.<fmt>.<data>[.<data>...]
* read.<fmt>[.<len>].<reg>
* write.<fmt>.<reg>.<data>[.<data>...]
* put.<fmt>.<data>[.<data>...] Data can be from global <var>, except for b and w format
* read.<fmt>[.<len>].<reg> Register can be from global <var>
* write.<fmt>.<reg>.<data>[.<data>...] Data can be from global <var>, except for b and w format
* eval
* value.<valueIndex> (1..4)
* calc.<calculation> Like Rules, extra available vars: %value%, %pvalue%, %h%, %b0%..%b<n>%, %bx0%..%bx<n>%, %w0%..%w<n>%, %wx0%..%wx<n>%
+172 -100
View File
@@ -1,20 +1,20 @@
#include "../Helpers/BusCmd_Helper.h"
#if FEATURE_BUS_COMMAND
#include <GPIO_Direct_Access.h>
#include "../Globals/RulesCalculate.h"
# include <GPIO_Direct_Access.h>
# 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::vector<BusCmd_Command_struct>BusCmd_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::vector<BusCmd_Command_struct>BusCmd_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::vector<BusCmd_Command_struct>BusCmd_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::vector<BusCmd_Command_struct>BusCmd_Helper_struct::parseBusCmdCommands(cons
if (arg1i > -1) {
BusCmd_Command_e cmd = static_cast<BusCmd_Command_e>(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.<format>
break;
case BusCmd_Command_e::Write: // put - p.<format>.<value>
#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.<calculation>
case BusCmd_Command_e::If: // if - i.<calculation>[.skip]
@@ -394,9 +418,9 @@ std::vector<BusCmd_Command_struct>BusCmd_Helper_struct::parseBusCmdCommands(cons
}
break;
case BusCmd_Command_e::Let: // let - l.<variable>.<calculation>
#if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES
# if FEATURE_BUSCMD_STRING && FEATURE_STRING_VARIABLES
case BusCmd_Command_e::LetStr: // letstr - m.<variable>.<calculation>
#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::vector<BusCmd_Command_struct>BusCmd_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.<state>
case BusCmd_Command_e::ResetGPIO: // reset - z.<state>.<msec>
@@ -498,23 +526,24 @@ std::vector<BusCmd_Command_struct>BusCmd_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<uint16_t>(tmp); // First part is always register if we have 2 variable parts
} else if (_it->command.isWriteCmd()) {
_it->d0_int32_t = static_cast<int32_t>(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<int32_t>(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::vector<BusCmd_Command_struct>command
_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
# endif // ifndef LIMIT_BUILD_SIZE
#endif // if FEATURE_BUS_COMMAND
+94 -44
View File
@@ -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<Cmds>(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<uint32_t>(getIntValue());
}
#if FEATURE_BUSCMD_STRING
String getString();
#endif // if FEATURE_BUSCMD_STRING
uint32_t getUIntValue() { return static_cast<uint32_t>(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<uint8_t> data_b;
std::vector<uint16_t>data_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::vector<BusCmd_Command_struct>parseBusCmdCommands(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::vector<BusCmd_Command_struct>commands,
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<BusCmd_Command_struct>::iterator _evalCommand;
std::vector<BusCmd_Command_struct>::iterator _it;
std::vector<BusCmd_Buffer> _buffer;
};
#endif
#endif // if FEATURE_BUS_COMMAND
#endif // ifndef _HELPERS_BUSCMD_HELPER_H
+9 -2
View File
@@ -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;