This repository provides an optional MQTT helper module.
- Module header:
src/mqtt/MQTTManager.h - You must explicitly include it from your sketch.
- You must add
PubSubClientto your project dependencies. - The recommended init pattern is Option A: call
mqtt.attach(ConfigManager)fromsetup()and register settings explicitly.
Add to your project platformio.ini:
lib_deps =
knolleary/PubSubClient@^2.8#include "mqtt/MQTTManager.h"
static cm::MQTTManager& mqtt = cm::MQTTManager::instance();void setup()
{
// Layout + MQTT settings registration (MQTT + MQTT-Topics pages/groups).
mqtt.attach(ConfigManager);
// Optional: place baseline MQTT settings into a custom card/group.
mqtt.addMqttSettingsToSettingsGroup(ConfigManager, "MQTT", "MQTT Settings", 40);
// Registers runtime provider only (live fields are explicit).
mqtt.addMQTTRuntimeProviderToGUI(ConfigManager, "mqtt");
// Optional: global hooks (define them in your sketch)
// See "Callbacks" section below.
}
void loop()
{
mqtt.loop(); // or: mqtt.update();
}static float powerInW = 0.0f;
void setup()
{
mqtt.attach(ConfigManager);
// Plain payload example: topic "device/power" => "123.4"
mqtt.addTopicReceiveFloat("power_w", "Power", "device/power", &powerInW, "W", 1, "none");
// JSON example: topic "tele/tasmota/SENSOR" => {"ENERGY":{"Total":12.34}}
mqtt.addTopicReceiveFloat("energy_total", "Energy Total", "tele/tasmota/SENSOR", &powerInW, "kWh", 3, "ENERGY.Total");
// GUI entries are explicit
mqtt.addMQTTRuntimeProviderToGUI(ConfigManager, "mqtt");
// Settings placement (MQTT Topics tab)
mqtt.addMqttTopicToSettingsGroup(ConfigManager, "power_w", "MQTT-Topics", "MQTT-Topics", "MQTT-Received", 50);
mqtt.addMqttTopicToSettingsGroup(ConfigManager, "energy_total", "MQTT-Topics", "MQTT-Topics", "MQTT-Received", 50);
// Live placement
mqtt.addMqttTopicToLiveGroup(ConfigManager, "power_w", "mqtt", "MQTT-Received", 1); // card only (no group)
mqtt.addMqttTopicToLiveGroup(ConfigManager, "energy_total", "mqtt", "MQTT-Received", "MQTT-Received", 2);
}- The Client ID must be unique per device on the broker.
- It is used as a fallback base topic when
MQTTBaseTopicis empty. - If not set, the module auto-generates one from the MAC address, e.g.
ESP32_AABBCCDDEEFF.
addTopicReceive* only defines items. Settings are explicit:
- Call
addMqttTopicToSettingsGroup(...)to create two settings under the MQTT Topics tab:<Label> Topic<Label> JSON Key
- Changes apply immediately on Save/Apply (subscriptions update on the fly).
- If you never call
addMqttTopicToSettingsGroup(...), defaults are used and no settings are created.
addMQTTRuntimeProviderToGUI(...)registers the runtime provider only.- Receive items are shown only when explicitly placed via
addMqttTopicToLiveGroup(...). getLastTopic(),getLastPayload(),getLastMessageAgeMs()can be exposed via runtime providers.addMqttTopicToSettingsGroup(...)registers the MQTT receive-topic settings in the Settings UI (MQTT Topics tab).
Classic (PubSubClient-style) hooks remain available:
onConnected(...)onDisconnected(...)onMessage(...)
Optional global hook voids (define in your sketch if you want them):
void onMQTTConnected()void onMQTTDisconnected()void onMQTTStateChanged(int state)void onNewMQTTMessage(const char* topic, const char* payload, unsigned int length)
Notes:
- MQTT is an optional module. The hooks are discovered only in
namespace cmso the core does not require global symbols when MQTT is not included. - This is why
onWiFiConnected()can be global (core), while MQTT hooks must live innamespace cm.
If you implement these hooks in the same translation unit (Arduino sketch), define this before including the header:
#define CM_MQTT_NO_DEFAULT_HOOKSExample:
namespace cm {
void onMQTTConnected()
{
CM_LOG("[MQTT][I] connected");
}
} // namespace cm- Default last-will message:
"offline" - Default topic:
<MQTTBaseTopic>/System-Info/status - Default: retain=true, qos=0
- Override via
setLastWill(topic, message, retained, qos) - On successful connect, the module publishes
online(retained) to the same will topic to clear staleoffline.
- Base topic:
<MQTTBaseTopic>/System-Info - Publishes every 60s while connected (automatic)
- Explicit publish:
publishSystemInfoNow(retained) - Payloads are split into two JSON messages:
<base>/System-Info/ESP(chip + memory)<base>/System-Info/WiFi(connection info)
uptimeMsanduptimeHumanare included in each payload.
publishTopic(id)publishes a receive item value to<base>/<id>publishExtraTopic(id, topic, value)publishes a custom value to a custom topic- Interval is taken from
MQTTPubPer(seconds). Use theImmediatelyvariants to bypass it. - Defaults (unless overridden by parameters):
- Non-bool: retain=true, qos=0
- Bool: retain=false, qos=1
- Immediately variants: qos=1
- You can override retain/qos via overloads that accept
(retained, qos). - PubSubClient only supports QoS 0 for publish. If qos != 0 is requested, a warning is logged and QoS 0 is used.
publishAllNow(retained)publishes System-Info and all receive items immediately.clearRetain(topic)clears the retained message by publishing an empty retained payload.
subscribe(topic, qos)/unsubscribe(topic)are available for direct topic filters.subscribeWildcard(topicFilter)supports+/#wildcards.
PubSubClient uses a fixed packet buffer for RX/TX. Larger JSON payloads (for example
tele/<device>/SENSOR from Tasmota) may exceed small defaults and will then not be processed.
Defaults in this module:
CM_MQTT_DEFAULT_BUFFER_SIZE = 1024bytes- The default is applied automatically in
MQTTManagerconstructor. - You can still override at runtime with
mqtt.setBufferSize(...).
Build-time override (example in platformio.ini):
build_flags =
-DCM_MQTT_DEFAULT_BUFFER_SIZE=1536Notes:
- Runtime system-info publishing may grow the buffer if needed.
- The module keeps the grown size and does not shrink it again during operation.
If you use the advanced logging module, you can publish logs via MQTT:
- Output class:
cm::MQTTLogOutput(src/mqtt/MQTTLogOutput.h) - Plain-text payloads, not retained (stream)
- Optional retained "last" entries per level
Topic scheme (base = <MQTTBaseTopic>):
<base>/log/<LEVEL>/LogMessages(unretained stream)<base>/log/last/INFO,/WARN,/ERROR(retained)<base>/log/last/Custom(retained, tag prefix "Custom")
Minimal example:
#include "mqtt/MQTTLogOutput.h"
auto mqttLog = std::make_unique<cm::MQTTLogOutput>(mqtt);
mqttLog->setLevel(cm::LoggingManager::Level::Trace);
mqttLog->addTimestamp(cm::LoggingManager::Output::TimestampMode::DateTime);
lmg.addOutput(std::move(mqttLog));MQTT wildcard rules:
+matches a single topic level#matches all remaining levels (must be the last token)
Example: subscribe to all Tasmota error topics and filter in the callback:
mqtt.subscribeWildcard("tasmota/#");
namespace cm {
void onNewMQTTMessage(const char* topic, const char* payload, unsigned int length)
{
if (!topic || !payload || length == 0) return;
String t(topic);
if (!t.endsWith("/main/error")) return;
String msg(payload, length);
msg.trim();
CM_LOG((String("[TASMOTA][E] ") + t + " => " + msg).c_str());
}
} // namespace cm// Register runtime provider
mqtt.addMQTTRuntimeProviderToGUI(ConfigManager, "mqtt");
// Explicit live entries
mqtt.addMqttTopicToLiveGroup(ConfigManager, "boiler_temp_c", "mqtt", "MQTT-Received", 1); // card only
mqtt.addMqttTopicToLiveGroup(ConfigManager, "powermeter_power_in_w", "mqtt", "MQTT-Received", "MQTT-Received", 2);
// Runtime provider for "other infos"
ConfigManager.getRuntime().addRuntimeProvider("mqtt", [](JsonObject& data) {
data["lastTopic"] = mqtt.getLastTopic();
data["lastPayload"] = mqtt.getLastPayload();
data["lastMsgAgeMs"] = mqtt.getLastMessageAgeMs();
}, 3);| Method | Overloads / Variants | Description | Notes |
|---|---|---|---|
cm::MQTTManager::attach |
attach(ConfigManagerClass& configManager, const char* basePageName = "MQTT") |
Attaches MQTT manager to ConfigManager and registers settings/runtime integration. | Recommended init path. |
cm::MQTTManager::begin / loop / disconnect |
begin()loop()disconnect() |
Starts and maintains MQTT state machine connection lifecycle. | loop() or update() must run continuously. |
cm::MQTTManager::publishTopic / publishTopicImmediately |
publishTopic(...) (6 overloads)publishTopicImmediately(...) (6 overloads) |
Publishes registered receive-item values to MQTT topics. | Overloads cover retained/qos and ConfigManager variants. |
cm::MQTTManager::publishExtraTopic / publishExtraTopicImmediately |
publishExtraTopic(...) (6 overloads)publishExtraTopicImmediately(...) (6 overloads) |
Publishes custom values to explicit topics. | Useful for ad-hoc telemetry. |
cm::MQTTManager::addTopicReceive* |
addTopicReceiveFloat(...)addTopicReceiveInt(...)addTopicReceiveBool(...)addTopicReceiveString(...) |
Registers inbound MQTT topics and parsing targets. | Pair with settings/live placement helpers. |
cm::MQTTManager UI helpers |
addMqttSettingsToSettingsGroup(...) (2 overloads)addMqttTopicToSettingsGroup(...) (2 overloads)addMqttTopicToLiveGroup(...) (2 overloads)addMQTTRuntimeProviderToGUI(...)addLastTopicToGUI(...)addLastPayloadToGUI(...)addLastMessageAgeToGUI(...) |
Places MQTT data/settings into Settings and Live UI. | Explicit placement model; nothing is auto-shown in Live without helper calls. |
- The module uses a non-blocking state machine with retry logic.
- TX/RX topic logging is emitted only when
CM_ENABLE_VERBOSE_LOGGING=1. - Settings include:
MQTTEnable(bool)MQTTHost,MQTTPort,MQTTUser,MQTTPass,MQTTClientIdMQTTBaseTopicMQTTPubPer(publish interval in seconds;0means publish-on-change for send helpers)MQTTListenMs(listen interval in ms;0means every loop)