MQTT Confirmed

Knowing that a command actually reached the device, and knowing when a device stops responding. Two related problems that MQTT does not solve on its own.

MQTT QoS does not confirm execution.
QoS is hop-by-hop, between your client and the broker. The PUBACK of QoS 1 means the broker accepted the message — never that the device received it, and certainly not that it applied it. Raising QoS to 2 changes nothing here. This is the single most common misunderstanding.

1How confirmation works

Virtuino Cloud uses the same convention as Tasmota and other established firmwares: the platform sends a command on one topic, and the device publishes the value back on the matching topic in the opposite direction.

DirectionTopic your device uses
Cloud → device (command)<device>/<field> — subscribe
Device → cloud (confirmation)<device>/<field> — publish

Both directions map to the same field — in fact the same bare topic string — so nothing extra has to be configured. You subscribe to it to receive the command, and publish back to the same topic string to confirm; the broker keeps the two directions separate automatically.

# cloud sends the command (you receive it on your subscription)
esp32/relay1   "1"

# device applies it, then confirms by publishing the same value
# to the SAME topic string
esp32/relay1   "1"

Enabling it

Turn on “command confirmation” for the field in Console → Fields → Edit and set how long to wait. The default is 5 seconds, which is generous — a device that answers at all answers in milliseconds.

Only works with MQTT publish enabled.
If the field does not publish to a device, there is no command and therefore nothing to confirm. Confirmation applies exclusively to direct commands sent to a device over MQTT.

2What changes when you enable it

This is the most important section on this page. Enabling confirmation changes when values are stored, not just whether you get an alert.

Normally, a command from a dashboard, scheduler, rule or script is written to the database immediately, the widgets update, and any scripts or rules that watch that field are triggered — all before the device has done anything.

With confirmation enabled, the command is only sent to the device. Nothing is stored, no widget is updated from the command itself, and no scripts or rules run. When the device publishes the value back, that is what gets stored, updates the dashboard and triggers everything downstream.

Without confirmationWith confirmation
Databasewritten immediatelywritten when the device answers
Widgetsupdated immediatelyupdated when the device answers
Scripts / rules / HTTP triggersrun immediatelyrun when the device answers
If the device never answersvalue stored anywaynothing stored, you are alerted

The result is that your history reflects what the equipment actually did, not what was requested. A rule such as “when the pump turns on, start a timer” no longer fires for a pump that never started.

On the dashboard

While waiting, a small spinner appears on the widget and an indicator lights up orange next to the zoom controls. On confirmation it flashes green and disappears. If the timeout expires it turns red and stays, and the widget is reset to its previous value.

Failures are always recorded in the dashboard logs, whether or not you asked for email or push notifications. To also be notified, tick “Alert me if a command is not confirmed” in the dashboard settings. That option only appears if at least one of your fields has confirmation enabled.

3Device code — confirming a command

Publish the value back on the same bare topic after applying it — the broker keeps your subscription (command in) and your publish (confirmation out) on separate real topics automatically, so one variable is all you need.

// ── Topic ─────────────────────────────────────────
// One bare topic, used for BOTH subscribe (receive command)
// and publish (send confirmation) — no account key needed.
String deviceName = "esp32";
String topicRelay = deviceName + "/relay1";

void onMessage(char* topic, byte* payload, unsigned int len) {
  String msg = "";
  for (int i = 0; i < len; i++) msg += (char)payload[i];

  if (String(topic) == topicRelay) {
    digitalWrite(RELAY_PIN, msg == "1" ? HIGH : LOW);

    // Confirmation — publish the same value back to the same topic string
    mqtt.publish(topicRelay.c_str(), msg.c_str());
  }
}
Using the older, fully-qualified topic form instead?
Older firmware that already writes out the full account-scoped topic explicitly needs two separate topic variables — one for receiving the command, one for publishing the confirmation — and must be careful to publish the confirmation to the correct one of the two, otherwise the platform never sees it and the command times out, or the device ends up receiving its own confirmation back and loops. With the bare topic form above, this mistake simply isn't possible: one topic, one variable.
Echo the received value, not a pin reading.
Reading the pin back only works for digital outputs. For a slider, a potentiometer or any numeric field, digitalRead() returns 0 or 1 and never matches the value that was sent, so every command would be reported as unconfirmed.

On connect it is worth publishing the current state once, so the dashboard starts in sync:

if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass)) {
  mqtt.subscribe(topicRelay.c_str());
  mqtt.publish(topicRelay.c_str(), digitalRead(RELAY_PIN) ? "1" : "0");
}

4Knowing when a device goes offline

Confirmation tells you that one particular command failed. It cannot tell you that a device died at three in the morning while nobody was sending anything. For that, add a Device Monitor widget to your dashboard: choose the device, set how long of a silence means offline, and it shows the live state and alerts you when it drops.

The widget monitors the device, not a value — leave the field selector empty. It works with any transport, MQTT or HTTP polling, because it measures the time since the last incoming value from any source.

Making detection instant with LWT

Without help from the device, the only signal is silence: the device is considered offline once the timeout expires. A device that only answers commands and never reports on its own will therefore look online until that timeout passes.

The Last Will and Testament fixes this. You register a message with the broker when you connect, and the broker publishes it for you if you disappear — power loss, network drop or crash. Detection becomes immediate instead of waiting for the timeout.

SettingValue
Will topic<device>/LWT (bare — no account key, no in/out)
Will payloadoffline
Birth messagepublish online to the same topic after connecting
QoS / RetainQoS 1, Retain ON
LWT is a reserved field name.
Messages on .../<device>/LWT are read as connection state and are not stored as a field value. Do not create a data field called LWT.
String topicStatus = deviceName + "/LWT";  // bare — broker scopes it to your account automatically

void connectMQTT() {
  while (!mqtt.connected()) {
    // The last four arguments register the Last Will:
    // topic, QoS, retain, message
    if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass,
                     topicStatus.c_str(), 1, true, "offline")) {

      // Birth message — clears the retained "offline" from last time
      mqtt.publish(topicStatus.c_str(), "online", true);

      mqtt.subscribe(topicRelay.c_str());
    } else {
      delay(5000);
    }
  }
}

void setup() {
  // The broker declares the device dead after about 1.5x this interval
  mqtt.setKeepAlive(30);
}
Add a heartbeat as well. Publishing any value every minute or so keeps the “last seen” time fresh and lets the platform notice a device that is still connected but stuck — something the Last Will can never catch, because the TCP connection is still alive.

5Troubleshooting

SymptomCause
Always “not confirmed”, but the output works The confirmation is published to a different field name than the command — or, if you're using the older fully-qualified topic form, to the wrong one of the two topics.
The output switches on and off endlessly Only possible with the older fully-qualified topic form: the device published its confirmation to its own command topic, so it received its own message back. Not possible with the bare topic form, since subscribe and publish are automatically kept on separate real topics.
Works for a relay, always fails for a slider The sketch confirms with digitalRead(), which can only return 0 or 1. Echo the received value instead.
Device Monitor says online although the device is unplugged No Last Will is configured, so detection waits for the silence timeout. Lower the timeout, or add LWT for instant detection.
The widget does not react at all Confirmation is enabled but the device never answers, so nothing is stored. Check the dashboard logs.
A setting change seems to have no effect Field settings are cached for up to one minute. Wait, then retry.
Complete working example: Availability & Command Confirmation — ESP8266/ESP32 sketch with Last Will, birth message, heartbeat and command confirmation already wired up.

For topic structure, broker settings and connection reference, see the MQTT Broker Setup guide.