diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg new file mode 100644 index 00000000..4a7baa3c --- /dev/null +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -0,0 +1,320 @@ +# Configuration file for ebusd MQTT integration with Home Assistant (https://www.home-assistant.io/). + +# Use this file with ebusd in MQTT JSON mode to have many seen messages automatically appear on HA. This is achieved by +# using the MQTT Discovery feature of Home Assistant (see https://www.home-assistant.io/docs/mqtt/discovery/). +# The commandline options to ebusd should contain e.g.: +# --mqttport=1883 --mqttjson --mqttint=/etc/ebusd/mqtt-hassio.cfg + + +# This file allows flexible construction of MQTT topics and payloads depending on message and field definitions as well +# as global status topics. +# It is a set of named variables (or constants) with the name on the left of an equal sign and the value on the right, +# where only the value is allowed to contain references to other variables. +# A reference to a variable is escaped by a leading "%" character in front of the variable name consisting of only +# alphabetic characters or the underscore. A double percent character "%%" is replaced by a single "%" in the value. +# Curly braces may be used to separate a variable explicitly from the rest, e.g. as "%{version}" instead of "%version". +# Every variable without an underscore in the name is automatically made available as well with an uppercase name and a +# normalized value composed of only alphanumeric characters or underscore (i.e. suitable for being part of a topic). +# When using "?=" instead of only the equal sign in a variable definition, the variable value is set to empty when one +# of the variables referred to is empty or missing. + +# The variable replacements are constructed for each message and/or field (depending on whether the topic is field +# specific or not) in the following order: + +# 1. predefined constants from ebusd, i.e. +# %version the ebusd version number. +# %prefix the invariable prefix part of the "--mqtttopic" configuration option, defaults to "ebusd/". +# %prefixn the same as %prefix but without trailing slashes or underscores. + +# 2. variables referring to constants only (directly or indirectly). + +# 3. circuit and message specific variables: +# %circuit the circuit name. +# %name the message name. +# %priority the message poll priority (or 0). +# %level the message access level (or empty). +# %messagecomment the message comment. +# %direction the message direction ("r", "w", "u", or "uw"). +# %topic the message update topic built from the mqtttopic configuration option (only if the topic is not field +# specific) and/or the %topic variable defined here. + +# 4. the direction mapped from variables named "direction_map-%direction" with the actual direction in the suffix. +# Available as "%direction_map". + +# 5. the field type mapped from variables named "type_map-%direction-%type" or "type_map-%type" as fallback with the +# field type and the optional direction in the suffix. The variable including the direction is evaluated first and +# if it is missing or the result is empty, the variable excluding the direction is used instead. +# This is also an implicit field type filter as missing or empty mappings are not published at all. +# Available as "%type_map". + +# 6. other field specific variables for each field (only once for each message if the topic is field specific): +# %index the field index (excluding ignored fields). +# %field the field name (or the field index if no name is defined or the names are not unique in the message). +# %type the field type (one of "number", "list", "string", "date", "time", or "datetime"). +# %basetype the base data type ID (e.g. "UCH"). +# %comment the field comment (if any). +# %unit the field unit (if any). +# %min and %max the minimum/maximum possible value for number fields. +# %topic the field (or message) update topic built from the mqtttopic configuration option and/or the %topic variable +# defined here. + +# 7. optional field type switch in "%type_switch" using the value of the variable named "type_switch-by" and matching it +# against the value lines of the variable named "type_switch-%type" with the field type in the suffix. +# The value of such a variable needs to be a constant list of string pairs with one pair per line. Each pair is +# separated by "=" and the left part is the value set to the "type_switch" variable when the wildcard pattern in the +# right part matches the value of the "type_switch-by" value. The list is traversed from top to bottom with the +# first match stopping the evaluation. Optionally, "type_switch-names" can be used to define a list of variable +# names to be set in addition to the "type_switch" variable, in which case a list of variable names (comma +# separated) is expected as "type_switch-names" value and the appropriate values to set on the left side of each +# match list entry. +# Available as "%type_switch" and optionally by the set of fields as defined in "%type_switch-names". + +# 8. optional field type part mapping in "%type_part" for each field type in variables named "type_part-%type" with the +# field type in the suffix (or the value of the "type_part-by" variable). + +# 9. if the %fields_payload variable is used, then it is set to the concatenation of all fields of the message: +# %field_payload is expected to build a single field payload. +# %field-separator is the separator placed between consecutive field payloads. +# %fields_payload is set to the concatenation of all fields of the message, separated by %field-separator. + +# 10. %definition-topic, %definition-payload, and %definition-retain to build the message definition topic and payload +# and determine the retain value. + + +# Before constructing the variable replacements, the messages and fields can optionally be filtered in order to use +# certain message definitions only. All of these criteria are applied, so they are basically combined by "AND". +# Filters with a "partial match" mention in the documentation allow using a limited glob/regex inspired case insensitive +# matching with the following options: +# - "|" allows defining alternatives, e.g. "a|b" matches "but" as well as "all". +# - "^" matches the beginning of the input, e.g. "^al" matches "al" but not "hal". +# - "$" matches the end of the input, e.g. "al$" matches "hal" but not "all". +# - "*" matches a single arbitrary length wildcard part in the middle, e.g. "^a*l$" matches "all" but not "always". + +# include only messages having data sent at least once (only checked for passive or read messages, not for active write). +# HA integration: filtering only seen messages to avoid unnecessary "pollution" +filter-seen = 1 +# include only messages having a priority less than or equal to the specified value. +#filter-priority = +# include only messages having the specified circuit (partial match, alternatives and wildcard supported). +#filter-circuit = +# include only messages having the specified name (partial match, alternatives and wildcard supported). +# HA integration: filter to some useful names for monitoring the heating circuit +filter-name = status|temp|yield|count|energy|power|runtime|hours|starts|mode|curve|^load$|^party$ +# include only messages having the specified level (partial match, alternatives and wildcard supported). +# Note: Since the empty string matches all levels, an explicit check for empty string (with "^$") needs to be used for +# including only messages without a level. +filter-level = ^$ +# include only messages having the specified direction ("r", "w", "u", or "uw". partial match, alternatives and wildcard supported). +# HA integration: writable messages excluded for now +filter-direction = r|u +# include only fields having the specified name (partial match, alternatives and wildcard supported). +#filter-field = + + +# HA integration: home assistant configuration topics prefix +haprefix = homeassistant +# HA integration: the area of all entities in home assistant +area = Heating + +# HA integration: circuit specific part used as device entity for message definitions below +circuit_part = { + "identifiers":"%{PREFIX}_%CIRCUIT", + "manufacturer":"ebusd.eu", + "name":"%prefixn %circuit", + "via_device":"%PREFIXN", + "sw_version":"%version", + "suggested_area":"%area" + } + +# the field type mapped from variables named "type_map-%direction-%type" or "type_map-%type" as fallback with the +# field type and the optional direction in the suffix. The variable including the direction is evaluated first and +# if it is missing or the result is empty, the variable excluding the direction is used instead. +# This is also an implicit field type filter as missing or empty mappings are not published at all. +type_map-number = number +type_map-list = string +# HA integration: skip string/date/time types completely +type_map-string = +type_map-date = +type_map-time = +type_map-datetime = + +# field type switch designator, see below. +# HA integration: this is used to set several variable values depending on the field type, name, message, and field unit. +type_switch-by = %name%field,%unit + +# field type switch variables names to use in addition to %type_switch in case of multiple keys (separated by comma). +# HA integration: var names for topic/class/state values mapped from field type, name, message, and unit. +type_switch-names = type_topic,type_class,type_state + +# field type switch for each field type and optionally direction (between dash before the field type) available as +# %type_switch (as well as the variable names defined in "type_switch-names" if any). +# The value needs to be a constant list of string pairs with one pair per line. Each pair is separated by "=" and the +# left part is the value set to the type_switch variable (as well as the variable names defined in %type_switch-names) +# when the wildcard string in the right part matched the "type_switch-by" value. The list is traversed from top to +# bottom stopping at the first match. If direction specific definitions exist, these are traversed first. If no line +# matches at all, the variable(s) are set to the empty string. +# HA integration: the mapping list for (potentially) writable number entities by field type, name, message, and unit. +type_switch-w-number = + number,temperature, = temp|,°C$ + number,power_factor, = power*%% + number,power, = power|,kW$|,W$ + number,voltage, = volt|,V$ + number,current, = current,|,A$ + number,pressure, = bar$ + number,gas, = gas*/min$ + number,humidity, = humid*%%$ + +# HA integration: the mapping list for numeric sensor entities by field type, name, message, and unit. +type_switch-number = + sensor,temperature,measurement = temp|,°C$ + sensor,power_factor,measurement = power*%% + sensor,power,measurement = power|,kW$|,W$ + sensor,voltage,measurement = volt|,V$ + sensor,current,measurement = current,|,A$ + sensor,energy,total_increasing = energy|,Wh$ + sensor,yield,total_increasing = total*,Wh$ + sensor,,total_increasing = hours|,h$ + sensor,,total_increasing = starts*,$ + sensor,pressure,measurement = bar$ + sensor,gas,measurement = gas*/min$ + sensor,humidity,measurement = humid*%%$ + sensor,, = + +# HA integration: the mapping list for (potentially) writable binary switch entities by field type, name, message, and unit. +type_switch-w-list = + switch,, = onoff + +# HA integration: the mapping list for rather binary sensor entities by field type, name, message, and unit. +type_switch-list = + binary_sensor,,measurement = onoff + sensor,, = + +# HA integration: currently unused mapping lists for non-numeric/non-binary entities. +#type_switch-string = +# sensor,, = +#type_switch-date = +# sensor,,measurement = +#type_switch-time = +# sensor,,measurement = +#type_switch-datetime = +# sensor, = + +# HA integration: optional variable with the entity device class for numbers +type_class_number ?= , + "device_class":"%type_class" + +# HA integration: optional variable with the entity device class for sensors +type_class_sensor ?= , + "device_class":"%type_class" + +# HA integration: optional variable with the entity state class +state_class ?= , + "state_class":"%type_state" + +# HA integration: optional variable with the entity unit +unit_of_measurement ?= , + "unit_of_measurement":"%unit" + +# field type part suffix to use instead of the field type itself, see below. +# HA integration: %type_part variable mapped from the mapped %type_topic +type_part-by = %type_topic + +# field type part mappings for each field type (or the "type_part-by" variable value) in the suffix (available as +# %type_part). +# HA integration: %type_part variable for number %type_topic +type_part-number = , + "command_topic":"%topic/set", + "min":%min, + "max":%max%unit_of_measurement%state_class%type_class_number + +# HA integration: %type_part variable for sensor %type_topic +type_part-sensor = %unit_of_measurement%state_class%type_class_sensor + +# HA integration: %type_part variable for switch %type_topic +type_part-switch = , + "command_topic":"%topic/set", + "payload_on":"on", + "payload_off":"off"%state_class + +# HA integration: %type_part variable for binary_sensor %type_topic +type_part-binary_sensor = , + "payload_on":"on", + "payload_off":"off"%state_class + +# the field specific part (evaluated after the message specific part). +# HA integration: set to the mapped %type_part from above +field_payload = %type_part + + +# the message definition config topic, payload, and retain setting. +# HA integration: the config topic for HA's MQTT discovery for the ebusd message definition found. +# set to conditionally to avoid incomplete configs. +definition-topic ?= %haprefix/%type_topic/%{TOPIC}_%FIELD/config +# HA integration: this is the config topic payload for HA's MQTT discovery. +definition-payload = { + "unique_id":"%{TOPIC}_%FIELD", + "name":"%prefixn %circuit %name %field", + "device":%circuit_part, + "value_template":"{{value_json[\"%field\"].value}}", + "state_topic":"%topic"%field_payload + } +#definition-retain = 0 + + +# the message value topic (if other than the default). If set here, it will be replaced by the "--mqtttopic" +# configuration option only if that one contains at least one variable. If the the "--mqtttopic" configuration option +# does not contain any variable, it is taken as prefix and can be used here as well. +# HA integration: use the prefix from --mqtttopic and non field specific topic for the actual data +topic = %prefix/%circuit/%name + + +# HA integration: global part used as device entity for the global config topics +global_device = { + "identifiers":"%PREFIXN", + "manufacturer":"ebusd.eu", + "name":"%prefixn", + "sw_version":"%version", + "suggested_area":"%area" + } + +# HA integration: common prefix for global parts +global_prefix = { + "unique_id":"%TOPIC", + "device":%global_device, + "state_topic":"%topic", + "name":"%prefixn %name" + +# HA integration: boolean suffix for global parts +global_boolean_suffix = , + "state_class":"measurement", + "payload_on":"true", + "payload_off":"false" + } + +# the common global config topic, payload, and retain setting (used by running, version, signal, uptime, updatecheck, +# and scan if not otherwise defined explicitly). +# HA integration: the config topic for HA's MQTT discovery for the ebusd global parts. +def_global-topic = %haprefix/sensor/%TOPIC/config +def_global-payload = %global_prefix, + "state_class":"measurement" + } +#def_global-retain = 0 + +# individual global running, version, signal, uptime, updatecheck, and scan config topic, payload, and retain setting. +def_global_running-topic = %haprefix/binary_sensor/%TOPIC/config +def_global_running-payload = %global_prefix, + "device_class":"running"%global_boolean_suffix +def_global_version-topic = +def_global_uptime-payload = %global_prefix, + "state_class":"total_increasing", + "unit_of_measurement":"s" + } +def_global_signal-topic = %haprefix/binary_sensor/%TOPIC/config +def_global_signal-payload = %global_prefix, + "device_class":"connectivity"%global_boolean_suffix + + +# the topic and payload to listen to in order to republish all config messages. +# HA integration: force republish of all configs when HA goes down and comes up again. +config_restart-topic = %haprefix/status +config_restart-payload = online diff --git a/contrib/etc/ebusd/mqtt-integration.cfg b/contrib/etc/ebusd/mqtt-integration.cfg new file mode 100644 index 00000000..59fdeb50 --- /dev/null +++ b/contrib/etc/ebusd/mqtt-integration.cfg @@ -0,0 +1,180 @@ +# Configuration file for ebusd MQTT integration. + +# This file allows flexible construction of MQTT topics and payloads depending on message and field definitions as well +# as global status topics. +# It is a set of named variables (or constants) with the name on the left of an equal sign and the value on the right, +# where only the value is allowed to contain references to other variables. +# A reference to a variable is escaped by a leading "%" character in front of the variable name consisting of only +# alphabetic characters or the underscore. A double percent character "%%" is replaced by a single "%" in the value. +# Curly braces may be used to separate a variable explicitly from the rest, e.g. as "%{version}" instead of "%version". +# Every variable without an underscore in the name is automatically made available as well with an uppercase name and a +# normalized value composed of only alphanumeric characters or underscore (i.e. suitable for being part of a topic). +# When using "?=" instead of only the equal sign in a variable definition, the variable value is set to empty when one +# of the variables referred to is empty or missing. + +# The variable replacements are constructed for each message and/or field (depending on whether the topic is field +# specific or not) in the following order: + +# 1. predefined constants from ebusd, i.e. +# %version the ebusd version number. +# %prefix the invariable prefix part of the "--mqtttopic" configuration option, defaults to "ebusd/". +# %prefixn the same as %prefix but without trailing slashes or underscores. + +# 2. variables referring to constants only (directly or indirectly). + +# 3. circuit and message specific variables: +# %circuit the circuit name. +# %name the message name. +# %priority the message poll priority (or 0). +# %level the message access level (or empty). +# %messagecomment the message comment. +# %direction the message direction ("r", "w", "u", or "uw"). +# %topic the message update topic built from the mqtttopic configuration option (only if the topic is not field +# specific) and/or the %topic variable defined here. + +# 4. the direction mapped from variables named "direction_map-%direction" with the actual direction in the suffix. +# Available as "%direction_map". + +# 5. the field type mapped from variables named "type_map-%direction-%type" or "type_map-%type" as fallback with the +# field type and the optional direction in the suffix. The variable including the direction is evaluated first and +# if it is missing or the result is empty, the variable excluding the direction is used instead. +# This is also an implicit field type filter as missing or empty mappings are not published at all. +# Available as "%type_map". + +# 6. other field specific variables for each field (only once for each message if the topic is field specific): +# %index the field index (excluding ignored fields). +# %field the field name (or the field index if no name is defined or the names are not unique in the message). +# %type the field type (one of "number", "list", "string", "date", "time", or "datetime"). +# %basetype the base data type ID (e.g. "UCH"). +# %comment the field comment (if any). +# %unit the field unit (if any). +# %min and %max the minimum/maximum possible value for number fields. +# %topic the field (or message) update topic built from the mqtttopic configuration option and/or the %topic variable +# defined here. + +# 7. optional field type switch in "%type_switch" using the value of the variable named "type_switch-by" and matching it +# against the value lines of the variable named "type_switch-%type" with the field type in the suffix. +# The value of such a variable needs to be a constant list of string pairs with one pair per line. Each pair is +# separated by "=" and the left part is the value set to the "type_switch" variable when the wildcard pattern in the +# right part matches the value of the "type_switch-by" value. The list is traversed from top to bottom with the +# first match stopping the evaluation. Optionally, "type_switch-names" can be used to define a list of variable +# names to be set in addition to the "type_switch" variable, in which case a list of variable names (comma +# separated) is expected as "type_switch-names" value and the appropriate values to set on the left side of each +# match list entry. +# Available as "%type_switch" and optionally by the set of fields as defined in "%type_switch-names". + +# 8. optional field type part mapping in "%type_part" for each field type in variables named "type_part-%type" with the +# field type in the suffix (or the value of the "type_part-by" variable). + +# 9. if the %fields_payload variable is used, then it is set to the concatenation of all fields of the message: +# %field_payload is expected to build a single field payload. +# %field-separator is the separator placed between consecutive field payloads. +# %fields_payload is set to the concatenation of all fields of the message, separated by %field-separator. + +# 10. %definition-topic, %definition-payload, and %definition-retain to build the message definition topic and payload +# and determine the retain value. + + +# Before constructing the variable replacements, the messages and fields can optionally be filtered in order to use +# certain message definitions only. All of these criteria are applied, so they are basically combined by "AND". +# Filters with a "partial match" mention in the documentation allow using a limited glob/regex inspired case insensitive +# matching with the following options: +# - "|" allows defining alternatives, e.g. "a|b" matches "but" as well as "all". +# - "^" matches the beginning of the input, e.g. "^al" matches "al" but not "hal". +# - "$" matches the end of the input, e.g. "al$" matches "hal" but not "all". +# - "*" matches a single arbitrary length wildcard part in the middle, e.g. "^a*l$" matches "all" but not "always". + +# include only messages having data sent at least once (only checked for passive or read messages, not for active write). +filter-seen = 1 +# include only messages having a priority less than or equal to the specified value. +#filter-priority = +# include only messages having the specified circuit (partial match, alternatives and wildcard supported). +#filter-circuit = +# include only messages having the specified name (partial match, alternatives and wildcard supported). +#filter-name = +# include only messages having the specified level (partial match, alternatives and wildcard supported). +# Note: Since the empty string matches all levels, an explicit check for empty string (with "^$") needs to be used for +# including only messages without a level. +filter-level = ^$ +# include only messages having the specified direction ("r", "w", "u", or "uw". partial match, alternatives and wildcard supported). +filter-direction = r|u +# include only fields having the specified name (partial match, alternatives and wildcard supported). +#filter-field = + + +# the field type mapped from variables named "type_map-%direction-%type" or "type_map-%type" as fallback with the +# field type and the optional direction in the suffix. The variable including the direction is evaluated first and +# if it is missing or the result is empty, the variable excluding the direction is used instead. +# This is also an implicit field type filter as missing or empty mappings are not published at all. +type_map-number = number +type_map-list = string +type_map-string = string +type_map-date = string +type_map-time = string +type_map-datetime = string + + +# field type switch designator, see below. +#type_switch-by = %name%field,%unit + +# field type switch variables names to use in addition to %type_switch in case of multiple keys (separated by comma). +#type_switch-names = type_topic,type_class,type_state + +# field type switch for each field type and optionally direction (between dash before the field type) available as +# %type_switch (as well as the variable names defined in "type_switch-names" if any). +# The value needs to be a constant list of string pairs with one pair per line. Each pair is separated by "=" and the +# left part is the value set to the type_switch variable (as well as the variable names defined in %type_switch-names) +# when the wildcard string in the right part matched the "type_switch-by" value. The list is traversed from top to +# bottom stopping at the first match. If direction specific definitions exist, these are traversed first. If no line +# matches at all, the variable(s) are set to the empty string. +#type_switch-w-number = + + +# field type part suffix to use instead of the field type itself, see below. +#type_part-by = %type_topic + +# field type part mappings for each field type (or the "type_part-by" variable value) in the suffix (available as +# %type_part). +#type_part-number = , + + +# the field specific part (evaluated after the message specific part). +#field_payload = %type_part + + +# the message definition config topic, payload, and retain setting. +definition-topic = %prefixn/config/%CIRCUIT/%NAME/%FIELD +definition-payload = { + "name":"%prefixn %circuit %name %field", + "topic":"%topic" + } +#definition-retain = 0 + + +# the message value topic (if other than the default). If set here, it will be replaced by the "--mqtttopic" +# configuration option only if that one contains at least one variable. If the the "--mqtttopic" configuration option +# does not contain any variable, it is taken as prefix and can be used here as well. +#topic=%prefix/%circuit/%name + + +# the common global config topic, payload, and retain setting (used by running, version, signal, uptime, updatecheck, +# and scan if not otherwise defined explicitly). +def_global-topic = %prefixn/config/global/%FIELD +def_global-payload = { + "name":"%prefixn global %field", + "topic":"%topic" + } +#def_global-retain = 0 + +# individual global running, version, signal, uptime, updatecheck, and scan config topic, payload, and retain setting. +#def_global_running-... +#def_global_version-... +#def_global_signal-... +#def_global_uptime-... +#def_global_updatecheck-... +#def_global_scan-... + + +# the topic and payload to listen to in order to republish all config messages. +#config_restart-topic = +#config_restart-payload = diff --git a/make_debian.sh b/make_debian.sh index ebd354e6..77b4d8ce 100755 --- a/make_debian.sh +++ b/make_debian.sh @@ -66,15 +66,18 @@ if [ -n "$reusebuilddir" ] || [ -z "$keepbuilddir" ]; then fi make DESTDIR="$PWD/$RELEASE" install-strip || exit 1 extralibs= +mqtt= ldd $RELEASE/usr/bin/ebusd | egrep -q libmosquitto.so.0 if [ $? -eq 0 ]; then extralibs=', libmosquitto0' PACKAGE="${PACKAGE}_mqtt0" + mqtt=1 else ldd $RELEASE/usr/bin/ebusd | egrep -q libmosquitto.so.1 if [ $? -eq 0 ]; then extralibs=', libmosquitto1' PACKAGE="${PACKAGE}_mqtt1" + mqtt=1 fi fi @@ -105,6 +108,10 @@ cp contrib/debian/systemd/ebusd.service $RELEASE/lib/systemd/system/ebusd.servic mkdir -p $RELEASE/etc/init.d || exit 1 cp contrib/debian/init.d/ebusd $RELEASE/etc/init.d/ebusd || exit 1 cp contrib/debian/default/ebusd $RELEASE/etc/default/ebusd || exit 1 +if [ -n "$mqtt" ]; then + mkdir -p $RELEASE/etc/ebusd || exit 1 + cp contrib/etc/ebusd/mqtt*.cfg $RELEASE/etc/ebusd/ || exit 1 +fi cp contrib/etc/logrotate.d/ebusd $RELEASE/etc/logrotate.d/ || exit 1 cp ChangeLog.md $RELEASE/DEBIAN/changelog || exit 1 cat < $RELEASE/DEBIAN/control @@ -132,6 +139,12 @@ EOF cat < $RELEASE/DEBIAN/conffiles /etc/default/ebusd EOF +if [ -n "$mqtt" ]; then + echo '/etc/ebusd' >> $RELEASE/DEBIAN/dirs + for i in contrib/etc/ebusd/mqtt*.cfg; do + echo "${i##contrib}" >> $RELEASE/DEBIAN/conffiles + done +fi cat < $RELEASE/DEBIAN/postinst #!/bin/sh if [ -d /run/systemd/system ]; then diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index e4ad13bc..ab5dcb79 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -338,7 +338,7 @@ static const char* knownFieldNames[] = { /** the number of known field names. */ static const size_t knownFieldCount = sizeof(knownFieldNames) / sizeof(char*); -std::pair makeField(const string name, bool isField) { +std::pair makeField(const string& name, bool isField) { if (!isField) { return {name, -1}; } @@ -350,38 +350,55 @@ std::pair makeField(const string name, bool isField) { return {name, knownFieldCount}; } +void addPart(ostringstream& stack, int inField, vector>& parts) { + string str = stack.str(); + if (inField == 1 && str == "_") { + inField = 0; // single "%_" pattern to reduce to "_" + } else if (inField == 2) { + str = "%{" + str; + inField = 0; + } + if (inField == 0 && str.empty()) { + return; + } + stack.str(""); + if (inField == 0 && !parts.empty() && parts[parts.size()-1].second < 0) { + // append constant to previous constant + parts[parts.size()-1].first += str; + return; + } + parts.push_back(makeField(str, inField>0)); +} bool MqttReplacer::parse(const string& templateStr, bool onlyKnown, bool noKnownDuplicates, bool emptyIfMissing) { m_parts.clear(); - size_t end = templateStr.length(); - bool inField = false; + int inField = 0; // 1 after '%', 2 after '%{' ostringstream stack; - for (size_t pos = 0; pos < end+1; pos++) { - char ch = pos= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_')) { + if (inField>0 && !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_')) { // invalid field character - if (stack.tellp()>0) { - m_parts.push_back(makeField(stack.str(), true)); - stack.str(""); - } - inField = false; + addPart(stack, inField, m_parts); + inField = 0; } stack << ch; } } + addPart(stack, inField, m_parts); if (onlyKnown || noKnownDuplicates) { int foundMask = 0; int knownCount = knownFieldCount; @@ -576,7 +593,7 @@ ssize_t MqttReplacer::matchTopic(const string& topic, string* circuit, string* n // non-name in remainder found return -static_cast(idx)-1; } - value = topic; + value = topic.substr(last); } last += value.length(); switch (part.second) { @@ -1075,7 +1092,7 @@ void MqttHandler::notifyTopic(const string& topic, const string& data) { logOtherDebug("mqtt", "received topic %s with data %s", topic.c_str(), data.c_str()); string circuit, name, field; ssize_t match = m_replacers.get("topic").matchTopic(topic.substr(0, pos), &circuit, &name, &field); - if (match<0 && !isList) { // TODO + if (match<0 && !isList) { logOtherError("mqtt", "received unmatchable topic %s", topic.c_str()); } if (isList) { @@ -1251,12 +1268,16 @@ void MqttHandler::run() { m_definitionsSince = 1; } if (m_connected && m_hasDefinitionTopic) { + ostringstream ostr; deque messages; m_messages->findAll("", "", "", false, true, true, true, true, true, 0, 0, false, &messages); for (const auto& message : messages) { if (filterSeen) { if (message->getLastUpdateTime()==0) { - continue; // no data ever + if (message->isPassive() || !message->isWrite()) { + // only wait for data on passive or read messages + continue; // no data ever + } } if (message->getDataHandlerState()==1) { // already seen in the past, check for poll prio update @@ -1286,9 +1307,6 @@ void MqttHandler::run() { msgValues.set("level", message->getLevel()); msgValues.set("direction", direction); msgValues.set("messagecomment", message->getAttribute("comment")); - if (!m_publishByField) { - msgValues.set("topic", getTopic(message, "", "")); // TODO already present? - } msgValues.reduce(true); string str = msgValues.get("direction_map-"+direction, false, false); msgValues.set("direction_map", str); @@ -1325,7 +1343,7 @@ void MqttHandler::run() { } else { typeStr = "string"; } - ostringstream ostr; + ostr.str(""); ostr << "type_map-" << direction << "-" << typeStr; str = msgValues.get(ostr.str(), false, false); if (str.empty()) { @@ -1337,11 +1355,11 @@ void MqttHandler::run() { continue; } MqttReplacers values = msgValues; // need a copy here as the contents are manipulated + values.set("index", static_cast(index)); + values.set("field", fieldName); values.set("type", typeStr); values.set("type_map", str); values.set("basetype", dataType->getId()); - values.set("index", static_cast(index)); - values.set("field", fieldName); values.set("comment", field->getAttribute("comment")); values.set("unit", field->getAttribute("unit")); if (dataType->isNumeric()) { @@ -1390,9 +1408,6 @@ void MqttHandler::run() { } str = values.get("type_part-" + typePartSuffix, false, false); values.set("type_part", str); - if (m_publishByField) { - values.set("topic", getTopic(message, "", fieldName)); // TODO already present? - } values.reduce(); if (m_hasDefinitionFieldsPayload) { string value = values["field_payload"]; @@ -1413,6 +1428,12 @@ void MqttHandler::run() { if (filterSeen && message->getLastUpdateTime()>message->getCreateTime()) { // ensure data is published as well m_updatedMessages[message->getKey()]++; + } else if (filterSeen && direction=="w") { + // publish data for read pendant of write message + Message* read = m_messages->find(message->getCircuit(), message->getName(), "", true); + if (read && read->getLastUpdateTime()>0) { + m_updatedMessages[read->getKey()]++; + } } } time(&m_definitionsSince); diff --git a/src/lib/ebus/filereader.cpp b/src/lib/ebus/filereader.cpp index 3e1414aa..69e20817 100755 --- a/src/lib/ebus/filereader.cpp +++ b/src/lib/ebus/filereader.cpp @@ -129,7 +129,7 @@ bool FileReader::matches(const string& input, const string& search, bool ignoreC return matches(inputSub, search, false, true); } string searchSub = search; - tolower(&inputSub); + tolower(&searchSub); return matches(inputSub, searchSub, false, true); } // walk through alternatives