Command Confirmation

Knowing that a command actually reached and was applied by the device — not just that it was sent.

MQTT QoS is not confirmation. QoS only confirms the message reached the broker — never that the device received it or applied it. Confirmation is a separate feature, and it's off by default.

1What confirmation does

Say a user taps a light switch on a dashboard. Here's exactly what happens, step by step:

User taps the switch on the dashboard Dashboard sends home/light1 = 1 and WAITS nothing else runs yet — the switch shows a spinner Device replies home/light1 = 1 within 5 seconds No reply within 5 seconds Platform receives it → runs rules & scripts → updates Web + Virtuino Cloud app Nothing happens not stored, no automations run (logged — optional alert) Also publishes home/light1/state (retained) Third-party MQTT app must subscribe to home/light1/state (not home/light1) to get the right value

Two outcomes: either the device answers in time and everything — automations, both dashboards, and any third-party app — updates together; or it doesn't, and nothing changes anywhere. Never a half-applied, out-of-sync state.

Without confirmationWith confirmation
Database & widgetsupdated immediatelyupdated when the device answers
Rules / scripts / HTTP triggersrun immediatelyrun when the device answers
If the device never answersvalue stored anywaynothing stored — you're alerted
It's a per-field setting. Turn it on only for the fields that carry commands — a switch, a slider, a setpoint. Sensor fields keep it off, so their readings are stored the moment they arrive. Each field with confirmation has its own timeout.
Works with any MQTT client, not just Virtuino Cloud. Commands sent straight to the broker — from the Virtuino IoT app, MQTTX, Node-RED or your own script — reach the device and are confirmed exactly like commands from a dashboard. Virtuino Cloud tells an incoming command apart from a device echo by checking whether it is waiting for that value on that field, so neither gets mistaken for the other.

2Steps to enable it

1

What you have to do in the platform?

Console → Fields → open the field → set Confirm commands to Enabled, choose a timeout (default 5s).

2

What you have to do on device code

After applying a command, publish the same value back on the same topic.

Echo the fields that have confirmation on. The echo is what confirms the command. On a field without confirmation an echo does no harm — the platform recognises it and does not send it back — it is simply stored as one more value.

3

Optional

In the dashboard's settings, tick "Alert me if a command is not confirmed" to get an email/push, not just a log entry.

3Device code

Two command fields below, esp32/relay1 and esp32/relay2, both with confirmation on. Every field with confirmation needs its own echo — one you forget in the code will always time out.

Echo the received value, not a pin reading. digitalRead() only works for a simple on/off relay. For a slider or any numeric field, echo the value you actually parsed from the command — a pin reading will never match it.
// ── Topics = field names, exactly as in Console → Fields ─
// Each one is used for BOTH subscribe (receive the command)
// and publish (echo it back); the broker keeps the two
// directions apart automatically.
String topicRelay1 = "esp32/relay1";
String topicRelay2 = "esp32/relay2";

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

  if (String(topic) == topicRelay1) {
    digitalWrite(RELAY1_PIN, msg == "1" ? HIGH : LOW);
    mqtt.publish(topicRelay1.c_str(), msg.c_str());  // echo — confirms relay1
  }

  if (String(topic) == topicRelay2) {
    digitalWrite(RELAY2_PIN, msg == "1" ? HIGH : LOW);
    mqtt.publish(topicRelay2.c_str(), msg.c_str());  // echo — confirms relay2
  }
}

void setup() {
  // ... WiFi.begin(...), mqtt.setServer(...), mqtt.setCallback(onMessage) ...
  mqtt.subscribe(topicRelay1.c_str());
  mqtt.subscribe(topicRelay2.c_str());
}
Prefer a complete sketch? Section 4 below is the full program used in the video — Wi-Fi, reconnect, two LEDs with confirmation and a virtual sensor. Copy it, change the four settings at the top, and flash.

4Complete sketch (the one from the video)

This is the whole program, ready to flash on an ESP32. It controls a green and a red LED from the dashboard, confirms every command, and publishes a virtual sensor value every 5 seconds.

Change only the four settings at the top — your Wi-Fi, and the MQTT credentials from Console → Keys & Sub Users → MQTT Credentials. The fields esp32/green_led, esp32/red_led and esp32/sensor must already exist in Console → Fields.

Two things worth noticing.
The client ID is built from the chip ID (esp32- followed by it). It stays the same after every restart and differs on every board running the same sketch, so boards never knock each other off the broker — and it is the name the board shows in My Virtuino World, online or offline, without a single extra line of code.
KEEP_ALIVE_SEC decides how quickly a power cut is noticed — about 1.5× its value, so 20 seconds means roughly 30. See Device Online/Offline Detection.
Turn confirmation on for the two LED fields. The echo in this sketch only has an effect once confirm commands is on for esp32/green_led and esp32/red_led — see section 2 above. Leave it off for esp32/sensor. Without it the sketch still works, the dashboard just does not wait for proof.
// ====================================================
// Virtuino Cloud - ESP32 example
//
// Shows two features:
//
//   1. COMMAND CONFIRMATION
//      The device echoes back every command it applies,
//      so the dashboard shows the REAL state of the
//      device, not just the state you asked for.
//
//   2. ONLINE / OFFLINE STATUS
//      Nothing to do here. Virtuino Cloud is told by
//      the broker when this device connects and when
//      it disconnects, so there is no extra code —
//      it appears in My Virtuino World under the
//      client ID below.
// ====================================================

#include <WiFi.h>
#include <PubSubClient.h>

// ----------------------------------------------------
// WiFi settings
// ----------------------------------------------------
const char* WIFI_SSID     = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

// ----------------------------------------------------
// Virtuino Cloud MQTT settings
// Console -> Keys & Sub Users -> MQTT Credentials
// ----------------------------------------------------
const char* MQTT_HOST = "cloud.virtuino.com";
const int   MQTT_PORT = 1883;

const char* MQTT_USER = "YOUR_SUB_ACCOUNT_KEY";
const char* MQTT_PASS = "YOUR_MQTT_PASSWORD";

// ----------------------------------------------------
// How often the device tells the broker it is alive.
//
// This also decides how fast Virtuino Cloud notices a
// power cut: about 1.5 x this value.
//   20 -> offline within ~30 seconds
//   60 -> offline within ~90 seconds (less traffic)
// ----------------------------------------------------
const uint16_t KEEP_ALIVE_SEC = 20;

// ----------------------------------------------------
// GPIO
// ----------------------------------------------------
#define GREEN_LED_PIN 13
#define RED_LED_PIN   12

// ----------------------------------------------------
// Virtuino Cloud fields — the topic IS the field name.
// Create them first in Console -> Fields, and turn
// "confirm commands" on for the two LED fields.
// ----------------------------------------------------
String topicGreen  = "esp32/green_led";   // command, confirmed
String topicRed    = "esp32/red_led";     // command, confirmed
String topicSensor = "esp32/sensor";      // sensor value, no confirmation

// ----------------------------------------------------

WiFiClient   wifiClient;
PubSubClient mqtt(wifiClient);

unsigned long lastSensorTime = 0;
const unsigned long SENSOR_INTERVAL = 5000;


// ====================================================
// WiFi connection
// ====================================================

void connectWiFi() {

  if (WiFi.status() == WL_CONNECTED) return;

  Serial.print("Connecting to WiFi");

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.print("WiFi connected. IP: ");
  Serial.println(WiFi.localIP());
}


// ====================================================
// MQTT callback
// ====================================================

void mqttCallback(char* topic, byte* payload, unsigned int length) {

  String message;

  for (unsigned int i = 0; i < length; i++) {
    message += (char)payload[i];
  }

  String receivedTopic = String(topic);

  Serial.print("MQTT received [");
  Serial.print(receivedTopic);
  Serial.print("] ");
  Serial.println(message);

  // --------------------------------------------------
  // GREEN LED
  // --------------------------------------------------

  if (receivedTopic == topicGreen) {

    if (message == "1") {
      digitalWrite(GREEN_LED_PIN, HIGH);
    }
    else if (message == "0") {
      digitalWrite(GREEN_LED_PIN, LOW);
    }
    else {
      return;          // Ignore invalid command
    }

    // -----------------------------------------------
    // VIRTUINO CLOUD COMMAND CONFIRMATION
    //
    // Publish the value back to the SAME topic, but
    // only AFTER the command has actually been applied.
    //
    // Virtuino Cloud treats this message as proof that
    // the device did the job, and marks the command as
    // confirmed. Until it arrives, the dashboard keeps
    // showing the command as pending.
    //
    // Send it here, at the end of the command, and NOT
    // when the message first arrives - otherwise you
    // would be confirming something you have not done.
    // -----------------------------------------------

    mqtt.publish(topicGreen.c_str(), message.c_str());

    Serial.println("Green LED command confirmed.");
  }


  // --------------------------------------------------
  // RED LED
  // --------------------------------------------------

  else if (receivedTopic == topicRed) {

    if (message == "1") {
      digitalWrite(RED_LED_PIN, HIGH);
    }
    else if (message == "0") {
      digitalWrite(RED_LED_PIN, LOW);
    }
    else {
      return;
    }

    // Virtuino Cloud confirmation - same as above

    mqtt.publish(topicRed.c_str(), message.c_str());

    Serial.println("Red LED command confirmed.");
  }
}


// ====================================================
// MQTT connection
// ====================================================

void connectMQTT() {

  while (!mqtt.connected()) {

    if (WiFi.status() != WL_CONNECTED) {
      connectWiFi();
    }

    Serial.print("Connecting to Virtuino Cloud MQTT...");

    // ------------------------------------------------
    // Client ID
    //
    // Any name you like. It is the name this board
    // shows in My Virtuino World, e.g. esp32-a1b2c3...
    //
    // The chip ID keeps it the same after every restart
    // and different on every board running this same
    // sketch. Boards sharing a client ID would keep
    // disconnecting each other.
    // ------------------------------------------------

    uint64_t chipId = ESP.getEfuseMac();

    String clientId =
      "esp32-" +
      String((uint32_t)(chipId >> 32), HEX) +
      String((uint32_t)chipId, HEX);


    // ------------------------------------------------
    // Connect
    //
    // Nothing else to pass. Virtuino Cloud is told by
    // the broker when this client connects and when it
    // disconnects, including after a power cut.
    // ------------------------------------------------

    if (mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {

      Serial.print(" connected as ");
      Serial.println(clientId);

      // Subscribe to commands
      mqtt.subscribe(topicGreen.c_str());
      mqtt.subscribe(topicRed.c_str());

      Serial.println("Subscribed to LED commands.");
    }

    else {

      Serial.print(" failed, rc=");
      Serial.println(mqtt.state());

      delay(4000);
    }
  }
}


// ====================================================
// Setup
// ====================================================

void setup() {

  Serial.begin(115200);

  pinMode(GREEN_LED_PIN, OUTPUT);
  pinMode(RED_LED_PIN, OUTPUT);

  digitalWrite(GREEN_LED_PIN, LOW);
  digitalWrite(RED_LED_PIN, LOW);

  // Random generator, used by the virtual sensor below
  randomSeed(esp_random());

  connectWiFi();

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(mqttCallback);

  mqtt.setKeepAlive(KEEP_ALIVE_SEC);
  mqtt.setBufferSize(512);
}


// ====================================================
// Main loop
// ====================================================

void loop() {

  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }

  if (!mqtt.connected()) {
    connectMQTT();
  }

  mqtt.loop();


  // --------------------------------------------------
  // Virtual sensor every 5 seconds
  // --------------------------------------------------

  if (millis() - lastSensorTime >= SENSOR_INTERVAL) {

    lastSensorTime = millis();

    // Example random value: 0.0 - 100.0
    float sensorValue = random(0, 1001) / 10.0;

    String value = String(sensorValue, 1);

    mqtt.publish(topicSensor.c_str(), value.c_str());

    Serial.print("Virtual sensor: ");
    Serial.println(value);
  }
}