Knowing that a command actually reached and was applied by the device — not just that it was sent.
Say a user taps a light switch on a dashboard. Here's exactly what happens, step by step:
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 confirmation | With confirmation | |
|---|---|---|
| Database & widgets | updated immediately | updated when the device answers |
| Rules / scripts / HTTP triggers | run immediately | run when the device answers |
| If the device never answers | value stored anyway | nothing stored — you're alerted |
Console → Fields → open the field → set Confirm commands to Enabled, choose a timeout (default 5s).
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.
In the dashboard's settings, tick "Alert me if a command is not confirmed" to get an email/push, not just a log entry.
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.
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());
}
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.
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.
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);
}
}