A complete, ready-to-flash sketch showing device availability tracking and command confirmation working together — copy the code for your board below.
ssid / wifiPass to your Wi-Fi credentials.mqttUser to your Sub-account Key, mqttPass to your MQTT password (Console → API & Connections → MQTT Credentials).esp8266 (or esp32) and two Fields, temperature and relay1, in Console → Devices — names must match the sketch exactly.// ============================================================================ // Virtuino Cloud — MQTT Availability & Command Confirmation (ESP8266) // // Demonstrates, all wired up together: // - Bare MQTT topics (no account key, no /in//out/, no "device/" segment) // - Last Will (LWT) + birth message for instant online/offline detection // - Heartbeat, so a stuck-but-connected device is also detected // - Command confirmation (Tasmota-style echo) for a relay // // See /docs/mqtt_docs.html for full topic reference and connection // settings, and /docs/mqtt_confirmed.html for the concepts this sketch // implements. // // Library: PubSubClient by Nick O'Leary // (Arduino IDE -> Sketch -> Include Library -> Manage Libraries -> "PubSubClient") // ============================================================================ #include <ESP8266WiFi.h> #include <PubSubClient.h> // ── Wi-Fi ──────────────────────────────────────────────────────────────── const char* ssid = "YOUR_WIFI_SSID"; const char* wifiPass = "YOUR_WIFI_PASSWORD"; // ── Virtuino Cloud MQTT ───────────────────────────────────────────────── // mqttUser is only ever used to LOG IN — it is never part of a topic. const char* mqttHost = "cloud.virtuino.com"; const int mqttPort = 1883; // use 8883 + WiFiClientSecure for TLS const char* mqttUser = "vr-abcd1234"; // your Sub-account Key const char* mqttPass = "YOUR_MQTT_PASSWORD"; String clientId; // built in setup(): "device_" + chip ID // ── Topics ────────────────────────────────────────────────────────────── // Bare topics only — no account key, no "device/" segment, no in/out. // Must match the Device and Field names you created in Console -> Devices. String deviceName = "esp8266"; String topicRelay = deviceName + "/relay1"; // used for BOTH subscribe (command) and publish (confirmation) String topicTemp = deviceName + "/temperature"; String topicLWT = deviceName + "/LWT"; // reserved field name — connection state, not a data field const unsigned long HEARTBEAT_INTERVAL_MS = 60000; // publish something at least this often unsigned long lastHeartbeat = 0; unsigned long lastTempPublish = 0; WiFiClient wifiClient; PubSubClient mqtt(wifiClient); // ── Incoming message handler ─────────────────────────────────────────── void onMessage(char* topic, byte* payload, unsigned int len) { String msg = ""; for (unsigned int i = 0; i < len; i++) msg += (char)payload[i]; Serial.println("[" + String(topic) + "] " + msg); if (String(topic) == topicRelay) { bool relayOn = (msg == "1"); digitalWrite(D1, relayOn ? HIGH : LOW); // Confirmation: echo the value we just applied back on the SAME bare // topic. The broker keeps the "receive command" and "send confirmation" // directions on separate real topics automatically — no risk of the // device looping on its own message. mqtt.publish(topicRelay.c_str(), msg.c_str()); } } void connectMQTT() { while (!mqtt.connected()) { Serial.print("Connecting to MQTT..."); // Last Will registered at connect time: topic, QoS, retain, message. // Bare topic, no account key, no /in/ or /out/ — see mqtt_confirmed.html. if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass, topicLWT.c_str(), 1, true, "offline")) { Serial.println(" connected!"); mqtt.subscribe(topicRelay.c_str()); // Birth message — clears the retained "offline" from last time. mqtt.publish(topicLWT.c_str(), "online", true); // Publish current relay state once, so the dashboard starts in sync. mqtt.publish(topicRelay.c_str(), digitalRead(D1) ? "1" : "0"); } else { Serial.print(" failed, rc="); Serial.println(mqtt.state()); delay(5000); } } } void setup() { Serial.begin(115200); clientId = "device_" + String(ESP.getChipId(), HEX); // unique per chip — no account key needed pinMode(D1, OUTPUT); WiFi.begin(ssid, wifiPass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi connected: " + WiFi.localIP().toString()); mqtt.setServer(mqttHost, mqttPort); mqtt.setCallback(onMessage); mqtt.setKeepAlive(30); // broker declares the device dead after ~1.5x this interval } void loop() { if (!mqtt.connected()) connectMQTT(); mqtt.loop(); unsigned long now = millis(); // Publish temperature every 10 seconds (replace with a real sensor reading). if (now - lastTempPublish > 10000) { lastTempPublish = now; float temp = random(200, 300) / 10.0; mqtt.publish(topicTemp.c_str(), String(temp, 2).c_str()); } // Heartbeat: even if nothing else changes, publish something at least // this often, so "last seen" stays fresh for devices that would // otherwise stay quiet for long periods (Device Monitor still catches a // stuck-but-connected device — the LWT alone can't, since the TCP // connection is still alive). if (now - lastHeartbeat > HEARTBEAT_INTERVAL_MS) { lastHeartbeat = now; mqtt.publish(topicLWT.c_str(), "online", true); } }
// ============================================================================ // Virtuino Cloud — MQTT Availability & Command Confirmation (ESP32) // // Demonstrates, all wired up together: // - Bare MQTT topics (no account key, no /in//out/, no "device/" segment) // - Last Will (LWT) + birth message for instant online/offline detection // - Heartbeat, so a stuck-but-connected device is also detected // - Command confirmation (Tasmota-style echo) for a relay // // See /docs/mqtt_docs.html for full topic reference and connection // settings, and /docs/mqtt_confirmed.html for the concepts this sketch // implements. // // Library: PubSubClient by Nick O'Leary // (Arduino IDE -> Sketch -> Include Library -> Manage Libraries -> "PubSubClient") // ============================================================================ #include <WiFi.h> #include <PubSubClient.h> // ── Wi-Fi ──────────────────────────────────────────────────────────────── const char* ssid = "YOUR_WIFI_SSID"; const char* wifiPass = "YOUR_WIFI_PASSWORD"; // ── Virtuino Cloud MQTT ───────────────────────────────────────────────── // mqttUser is only ever used to LOG IN — it is never part of a topic. const char* mqttHost = "cloud.virtuino.com"; const int mqttPort = 1883; // use 8883 + WiFiClientSecure for TLS const char* mqttUser = "vr-abcd1234"; // your Sub-account Key const char* mqttPass = "YOUR_MQTT_PASSWORD"; String clientId; // built in setup(): "device_" + chip ID const int RELAY_PIN = 26; // ── Topics ────────────────────────────────────────────────────────────── // Bare topics only — no account key, no "device/" segment, no in/out. // Must match the Device and Field names you created in Console -> Devices. String deviceName = "esp32"; String topicRelay = deviceName + "/relay1"; // used for BOTH subscribe (command) and publish (confirmation) String topicTemp = deviceName + "/temperature"; String topicLWT = deviceName + "/LWT"; // reserved field name — connection state, not a data field const unsigned long HEARTBEAT_INTERVAL_MS = 60000; // publish something at least this often unsigned long lastHeartbeat = 0; unsigned long lastTempPublish = 0; WiFiClient wifiClient; PubSubClient mqtt(wifiClient); // ── Incoming message handler ─────────────────────────────────────────── void onMessage(char* topic, byte* payload, unsigned int len) { String msg = ""; for (unsigned int i = 0; i < len; i++) msg += (char)payload[i]; Serial.println("[" + String(topic) + "] " + msg); if (String(topic) == topicRelay) { bool relayOn = (msg == "1"); digitalWrite(RELAY_PIN, relayOn ? HIGH : LOW); // Confirmation: echo the value we just applied back on the SAME bare // topic. The broker keeps the "receive command" and "send confirmation" // directions on separate real topics automatically — no risk of the // device looping on its own message. mqtt.publish(topicRelay.c_str(), msg.c_str()); } } void connectMQTT() { while (!mqtt.connected()) { Serial.print("Connecting to MQTT..."); // Last Will registered at connect time: topic, QoS, retain, message. // Bare topic, no account key, no /in/ or /out/ — see mqtt_confirmed.html. if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass, topicLWT.c_str(), 1, true, "offline")) { Serial.println(" connected!"); mqtt.subscribe(topicRelay.c_str()); // Birth message — clears the retained "offline" from last time. mqtt.publish(topicLWT.c_str(), "online", true); // Publish current relay state once, so the dashboard starts in sync. mqtt.publish(topicRelay.c_str(), digitalRead(RELAY_PIN) ? "1" : "0"); } else { Serial.print(" failed, rc="); Serial.println(mqtt.state()); delay(5000); } } } void setup() { Serial.begin(115200); clientId = "device_" + String((uint32_t)ESP.getEfuseMac(), HEX); // unique per chip — no account key needed pinMode(RELAY_PIN, OUTPUT); WiFi.begin(ssid, wifiPass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi connected: " + WiFi.localIP().toString()); mqtt.setServer(mqttHost, mqttPort); mqtt.setCallback(onMessage); mqtt.setKeepAlive(30); // broker declares the device dead after ~1.5x this interval } void loop() { if (!mqtt.connected()) connectMQTT(); mqtt.loop(); unsigned long now = millis(); // Publish temperature every 10 seconds (replace with a real sensor reading). if (now - lastTempPublish > 10000) { lastTempPublish = now; float temp = random(200, 300) / 10.0; mqtt.publish(topicTemp.c_str(), String(temp, 2).c_str()); } // Heartbeat: even if nothing else changes, publish something at least // this often, so "last seen" stays fresh for devices that would // otherwise stay quiet for long periods (Device Monitor still catches a // stuck-but-connected device — the LWT alone can't, since the TCP // connection is still alive). if (now - lastHeartbeat > HEARTBEAT_INTERVAL_MS) { lastHeartbeat = now; mqtt.publish(topicLWT.c_str(), "online", true); } }