Everything you need to connect your devices to Virtuino Cloud via MQTT — from first connection to secure production setup.
Use the values below every time you configure an MQTT client (MQTTX, Arduino library, Python, Node-RED, etc.).
| Parameter | Value | Notes |
|---|---|---|
| *Broker Host | cloud.virtuino.com | Same host for both plain and TLS connections |
| *Port (Plain) | 1883 plain TCP | No encryption — for development and testing only |
| *Port (TLS) | 8883 TLS / SSL | Encrypted — always use in production |
| *Username | vr-abcd1234 |
Your Sub-account Key — found in Console → Keys & Sub Users |
| *Password | your MQTT password | Found in Console → Keys & Sub Users → MQTT Credentials |
| Client ID | any name, e.g. kitchen-a1b2c3 |
Any name you like, as long as no other connection on your account uses the same one — adding the chip ID keeps it unique. It is the name the board shows in My Virtuino World. |
| MQTT Version | 3.1.1 or 5.0 |
Both supported; use 3.1.1 for maximum device compatibility |
| QoS | 0, 1 or 2 |
All QoS levels (0, 1, 2) are supported |
| Keep Alive | 60 seconds |
Increase to 120 s on slow or unstable networks |
| Clean Session | true |
Recommended for most IoT devices |
| TLS Certificate | CA Signed Server Certificate | ISRG Root X1 (Let's Encrypt). No client certificate required |
* Required field
The settings below have been tested and confirmed working with the MQTTX desktop client. Use them as a reference for any MQTT client application.
kitchen/temperature is the name of one field. If a message arrives on a topic with no matching field, it is ignored and listed in the Debug Monitor — unless Auto-create fields is on (top of the Fields page; on by default for new accounts): then the field is created automatically the first time a device sends to it, up to the fields limit of your plan.
A topic is simply the name of a field, and you choose it freely: a single word, or several parts
separated by / — as many levels as you like. The / only helps you keep related
fields together and filter them in the console; the platform gives the parts no special meaning.
The simplest option — fine for quick tests and small projects.
# Same topic used for both directions — the broker infers the # direction from PUBLISH vs SUBSCRIBE, you never write it yourself. temperature humidity relay1 V1 # virtual-pin style
Group related fields under a common beginning — by room, machine, board or anything else that suits you.
The whole string — kitchen/temperature — is the name of one field.
# Same topic used for both directions kitchen/temperature kitchen/relay1 greenhouse/soil_moisture farm/tank2/level # three levels are fine too
The same field's topic is used for both directions — what makes it "publish" or "subscribe" is simply which one your device code does with it:
| Your device does this… | …using this topic | …and it means |
|---|---|---|
mqtt.publish(topic, value) |
temperature |
Send a reading to the cloud (e.g. "23.75") |
mqtt.subscribe(topic) |
relay1 |
Receive a command from the cloud (e.g. "1" / "0") |
A field can be used either way, or both — it depends on how you write your device code, not on the field's name. Dashboard widgets, rules, schedules and scripts talk to the same fields from the platform side automatically; they don't need any MQTT topic configured.
| Wildcard | Meaning | Example | Matches |
|---|---|---|---|
+ |
Any single level | +/status |
every two-level topic ending in /status — kitchen/status, garage/status — but not deeper levels (subscribe only) |
# |
All levels from here down | # |
everything the cloud sends to you (subscribe only) |
# |
All levels from here down | kitchen/# |
every topic that starts with kitchen/, at any depth (subscribe only) |
These are the settings you configure in your MQTT client application (such as MQTTX, Node-RED, Home Assistant, etc.).
| Setting | What it does | Recommended value |
|---|---|---|
| MQTT Version | The protocol version used. 3.1.1 is universal and works with every Arduino/ESP library. 5.0 adds session expiry, message properties, and reason codes — useful in advanced setups but requires library support. | 3.1.1 (devices)5.0 (advanced) |
| Connect Timeout | How many seconds the client waits for the broker to acknowledge the connection (CONNACK) before giving up and reporting an error. | 10 s |
| Keep Alive | The client sends a PINGREQ to the broker at this interval to keep the connection alive when no data is flowing. If the broker receives no ping or message within 1.5× this value, it considers the client disconnected — that is also how fast Virtuino Cloud notices a power cut. | 60 s |
| Auto Reconnect | If the connection drops, the client automatically tries to reconnect without requiring a manual action or device restart. | ON |
| Reconnect Period | How many milliseconds to wait between reconnection attempts after a drop. | 4000 ms |
| Clean Session (MQTT 3.1.1) |
When ON: the broker discards all previous subscriptions and queued messages when the client disconnects. The client starts fresh every time it connects. Recommended for most IoT devices. When OFF: the broker remembers subscriptions and queues QoS 1 messages delivered while the device was offline. |
ON |
| Clean Start (MQTT 5.0 equivalent) |
Same concept as Clean Session but in MQTT 5.0. When ON, the session state is cleared on connect. | ON |
| Session Expiry Interval (MQTT 5.0 only) |
How many seconds the broker keeps a persistent session after the client disconnects. 0 means the session is deleted immediately on disconnect (equivalent to Clean Start ON). Only applies when Clean Start is OFF. |
0 |
| Setting | What it does | Virtuino Cloud |
|---|---|---|
| SSL/TLS | Enables encrypted transport. When ON, all data between your device and the broker is encrypted — nobody can intercept or read your sensor values or credentials. | Use port 8883 with SSL ON |
| SSL Secure | When ON, the client verifies the broker's certificate against a trusted Certificate Authority. If the certificate is invalid or expired, the connection is refused — protecting against man-in-the-middle attacks. | ON — always enable in production |
| Certificate: CA Signed Server Certificate |
The broker presents a certificate issued by a well-known public CA (Virtuino Cloud uses Let's Encrypt / ISRG Root X1). Your client trusts it automatically using the CA store built into your OS or library. You do not need to upload any certificate file. | ✅ Supported & required |
| Certificate: CA or Self-Signed |
Certificates created by the operator themselves, not by a public CA. These require you to manually install the certificate on every client. Not used by Virtuino Cloud. | ❌ Not applicable |
Virtuino Cloud knows the moment a board connects and the moment it disconnects — including after a
power cut, a crash or a network dropout. You write no code for this. Every connection
is identified by its client ID, exactly as written: a board that connects as
kitchen-a1b2c3 appears under that name in My Virtuino World, where you
can also give it a friendlier one.
Use a different client ID on every board: two clients sharing a client ID keep kicking each other off the broker. The chip ID is the easiest unique value — every example in section 6 builds its client ID that way. The client ID has nothing to do with the topics: a board can publish and subscribe to any field of your account.
mqtt.setKeepAlive(20) means roughly 30 seconds, 60 means roughly 90
seconds with less network traffic. A clean disconnect is reported immediately.
tasmota_4C11AE, which you
cannot change. That is fine — the platform identifies every board by the client ID it arrives with,
so it shows up in My Virtuino World on its own. Tick it as a real
device there to give it a name and watch it.
See Device Online/Offline Detection for the full
picture, and MQTT Confirmed for knowing whether a specific
command was actually applied.
Start with the plain connection (port 1883) to verify your credentials and topic setup works. Once confirmed, switch to the secure connection (port 8883) for production.
PubSubClient.
// ESP8266 — Plain MQTT connection (port 1883)
// For testing and development only — data is NOT encrypted.
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// ── Wi-Fi ──────────────────────────────────────────
const char* ssid = "YOUR_WIFI_SSID";
const char* wifiPass = "YOUR_WIFI_PASSWORD";
// ── Virtuino Cloud MQTT ────────────────────────────
const char* mqttHost = "cloud.virtuino.com";
const int mqttPort = 1883;
const char* mqttUser = "vr-abcd1234"; // Sub-account Key
const char* mqttPass = "YOUR_MQTT_PASSWORD";
String clientId; // built in setup(): "kitchen-" + chip ID
// ── Topics = field names, exactly as in Console → Fields ──
const char* topicTemp = "kitchen/temperature"; // this board publishes → cloud
const char* topicRelay = "kitchen/relay1"; // this board subscribes ← cloud
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);
// Control relay from dashboard
if (String(topic) == topicRelay) {
bool relayOn = (msg == "1");
digitalWrite(D1, relayOn ? HIGH : LOW);
}
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting to MQTT...");
if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass)) {
Serial.println(" connected!");
mqtt.subscribe(topicRelay);
} else {
Serial.print(" failed, rc="); Serial.println(mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
// Any name, unique among the boards of your account — it is how this board
// appears in My Virtuino World. The chip ID keeps it unique when you flash
// the same sketch to several boards.
clientId = "kitchen-" + String(ESP.getChipId(), HEX);
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(20); // power cut noticed within ~30 s
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
// Publish temperature every 10 seconds
static unsigned long lastPub = 0;
if (millis() - lastPub > 10000) {
lastPub = millis();
float temp = random(200, 300) / 10.0;
String payload = String(temp, 2);
mqtt.publish(topicTemp, payload.c_str());
Serial.println("Published: " + payload);
}
}
// ESP8266 — Secure MQTT connection (port 8883, TLS)
// Verifies the broker against the Let's Encrypt root certificate below.
//
// ⚠ REQUIRES: ESP8266 Arduino Core ≥ 2.5.0 (BearSSL support)
// Arduino IDE → Tools → Board → Boards Manager → "esp8266 by ESP8266 Community" → update to 3.x
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>
// ── Wi-Fi ──────────────────────────────────────────
const char* ssid = "YOUR_WIFI_SSID";
const char* wifiPass = "YOUR_WIFI_PASSWORD";
// ── Virtuino Cloud MQTT ────────────────────────────
const char* mqttHost = "cloud.virtuino.com";
const int mqttPort = 8883;
const char* mqttUser = "vr-abcd1234"; // Sub-account Key
const char* mqttPass = "YOUR_MQTT_PASSWORD";
String clientId; // built in setup(): "kitchen-" + chip ID
// ── Topics = field names, exactly as in Console → Fields ──
const char* topicTemp = "kitchen/temperature"; // this board publishes → cloud
const char* topicRelay = "kitchen/relay1"; // this board subscribes ← cloud
// ── ISRG Root X1 — Let's Encrypt root certificate (valid until 2035) ───
const char caCert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
BearSSL::WiFiClientSecure wifiClient;
BearSSL::X509List trustedCA(caCert);
PubSubClient mqtt(wifiClient);
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)
digitalWrite(D1, msg == "1" ? HIGH : LOW);
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting (TLS)...");
if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass)) {
Serial.println(" connected!");
mqtt.subscribe(topicRelay);
} else {
Serial.print(" failed, rc="); Serial.println(mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
// Any name, unique among the boards of your account (shown in My Virtuino World)
clientId = "kitchen-" + String(ESP.getChipId(), HEX);
pinMode(D1, OUTPUT);
WiFi.begin(ssid, wifiPass);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nWi-Fi: " + WiFi.localIP().toString());
// Certificate checks need the correct date — sync it from NTP first
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
Serial.print("Syncing time");
while (time(nullptr) < 1000000000UL) { delay(500); Serial.print("."); }
Serial.println(" done");
wifiClient.setTrustAnchors(&trustedCA); // verify broker cert against ISRG Root X1
mqtt.setServer(mqttHost, mqttPort);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(20); // power cut noticed within ~30 s
mqtt.setBufferSize(512);
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
static unsigned long lastPub = 0;
if (millis() - lastPub > 10000) {
lastPub = millis();
float temp = analogRead(A0) * 0.1; // replace with a real sensor read
mqtt.publish(topicTemp, String(temp, 2).c_str());
}
}
String classString.+, and there are no buffer sizes to get
right — which is why we use it in the examples aimed at getting started quickly.String grows or is
reassigned, the old block is freed and a new one allocated. On an ESP8266, with only tens of
kilobytes of heap, those gaps accumulate over days of uninterrupted operation: the total free
memory still looks fine, but no single contiguous block is large enough, and the device
eventually reboots. It is one of the most common causes of a sketch that “works for a week and
then restarts on its own”.char buffers and never allocates
again. Prefer it for anything meant to run permanently.
// ESP8266 — same sketch without the String class
// Fixed char buffers only: nothing is allocated after setup().
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* wifiPass = "YOUR_WIFI_PASSWORD";
const char* mqttHost = "cloud.virtuino.com";
const int mqttPort = 1883;
const char* mqttUser = "vr-abcd1234"; // Sub-account Key
const char* mqttPass = "YOUR_MQTT_PASSWORD";
// Topics = field names, exactly as in Console → Fields
const char* topicTemp = "kitchen/temperature"; // this board publishes → cloud
const char* topicRelay = "kitchen/relay1"; // this board subscribes ← cloud
char clientId[32]; // built once in setup()
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
void onMessage(char* topic, byte* payload, unsigned int len) {
// payload is NOT null-terminated — copy it first
char msg[64];
unsigned int n = (len < sizeof(msg) - 1) ? len : sizeof(msg) - 1;
memcpy(msg, payload, n);
msg[n] = '\0';
Serial.printf("[%s] %s\n", topic, msg);
// strcmp, not == : comparing char* with == compares addresses, not text
if (strcmp(topic, topicRelay) == 0) {
digitalWrite(D1, (strcmp(msg, "1") == 0) ? HIGH : LOW);
}
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting to MQTT...");
if (mqtt.connect(clientId, mqttUser, mqttPass)) {
Serial.println(" connected!");
mqtt.subscribe(topicRelay);
} else {
Serial.print(" failed, rc="); Serial.println(mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(D1, OUTPUT);
// Any name, unique among the boards of your account (shown in My Virtuino World)
snprintf(clientId, sizeof(clientId), "kitchen-%08x", ESP.getChipId());
WiFi.begin(ssid, wifiPass);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.print("\nWi-Fi connected: "); Serial.println(WiFi.localIP());
mqtt.setServer(mqttHost, mqttPort);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(20); // power cut noticed within ~30 s
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
static unsigned long lastPub = 0;
if (millis() - lastPub > 10000) {
lastPub = millis();
float temp = random(200, 300) / 10.0;
char payload[16];
snprintf(payload, sizeof(payload), "%.2f", temp);
mqtt.publish(topicTemp, payload);
Serial.printf("Published: %s\n", payload);
}
}
// ESP32 — Plain MQTT connection (port 1883)
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* wifiPass = "YOUR_WIFI_PASSWORD";
const char* mqttHost = "cloud.virtuino.com";
const int mqttPort = 1883;
const char* mqttUser = "vr-abcd1234"; // Sub-account Key
const char* mqttPass = "YOUR_MQTT_PASSWORD";
String clientId; // built in setup(): "kitchen-" + chip ID
// Topics = field names, exactly as in Console → Fields
const char* topicTemp = "kitchen/temperature"; // this board publishes → cloud
const char* topicRelay = "kitchen/relay1"; // this board subscribes ← cloud
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
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)
digitalWrite(2, msg == "1" ? HIGH : LOW); // GPIO 2 = built-in LED
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting...");
if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass)) {
Serial.println(" OK");
mqtt.subscribe(topicRelay);
} else {
Serial.print(" rc="); Serial.println(mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
// Any name, unique among the boards of your account (shown in My Virtuino World)
clientId = "kitchen-" + String((uint32_t)ESP.getEfuseMac(), HEX);
pinMode(2, OUTPUT);
WiFi.begin(ssid, wifiPass);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nIP: " + WiFi.localIP().toString());
mqtt.setServer(mqttHost, mqttPort);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(20); // power cut noticed within ~30 s
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
static unsigned long last = 0;
if (millis() - last > 10000) {
last = millis();
float temp = random(200, 300) / 10.0; // replace with real sensor
mqtt.publish(topicTemp, String(temp, 1).c_str());
}
}
// ESP32 — Secure MQTT connection (port 8883, TLS)
// Verifies the broker against the Let's Encrypt root certificate below.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* wifiPass = "YOUR_WIFI_PASSWORD";
const char* mqttHost = "cloud.virtuino.com";
const int mqttPort = 8883;
const char* mqttUser = "vr-abcd1234"; // Sub-account Key
const char* mqttPass = "YOUR_MQTT_PASSWORD";
String clientId; // built in setup(): "kitchen-" + chip ID
// Topics = field names, exactly as in Console → Fields
const char* topicTemp = "kitchen/temperature"; // this board publishes → cloud
const char* topicRelay = "kitchen/relay1"; // this board subscribes ← cloud
// ── ISRG Root X1 — Let's Encrypt root certificate (valid until 2035) ───────────
const char caCert[] = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
WiFiClientSecure wifiClient;
PubSubClient mqtt(wifiClient);
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)
digitalWrite(2, msg == "1" ? HIGH : LOW); // GPIO 2 = built-in LED
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting (TLS)...");
if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass)) {
Serial.println(" connected!");
mqtt.subscribe(topicRelay);
} else {
Serial.print(" failed, rc="); Serial.println(mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
// Any name, unique among the boards of your account (shown in My Virtuino World)
clientId = "kitchen-" + String((uint32_t)ESP.getEfuseMac(), HEX);
pinMode(2, OUTPUT);
WiFi.begin(ssid, wifiPass);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nIP: " + WiFi.localIP().toString());
wifiClient.setCACert(caCert); // verify broker against ISRG Root X1
mqtt.setServer(mqttHost, mqttPort);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(20); // power cut noticed within ~30 s
mqtt.setBufferSize(512);
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
static unsigned long last = 0;
if (millis() - last > 10000) {
last = millis();
float temp = random(200, 300) / 10.0; // replace with real sensor
mqtt.publish(topicTemp, String(temp, 1).c_str());
}
}
caCert in the TLS examples is the complete ISRG Root X1 root certificate of Let's Encrypt, valid until June 2035 — copy it as it is.
The same file is published at letsencrypt.org/certs/isrgrootx1.pem.
During development you can call wifiClient.setInsecure() instead of setCACert() / setTrustAnchors() to skip the check — but never in production.
The Virtuino Cloud platform includes a built-in HTTP → MQTT gateway. Any source that can make an HTTP request
can trigger an MQTT publish automatically — without running an MQTT client — simply by including
"publish": true in the JSON body.
| Source | How to enable | MQTT topic published |
|---|---|---|
| IoT Device (HTTP POST) | Add "publish": true to the JSON body of the HTTP request sent to the platform API |
The field's topic — its full name, e.g. kitchen/relay1 — is published automatically, so every board subscribed to it receives the value |
| Dashboard Widget | Enable the Publish toggle in the widget settings | Published on every value change |
| Rule | Enable the Publish option on the rule output | Published whenever the rule fires |
| Schedule | Enable the Publish option in the schedule action | Published at each scheduled trigger |
| Script | Enable the Publish option on the script output | Published on every script execution that produces a value |
"publish": true are available in the HTTP API documentation."publish": true is all it takes. The gateway handles the MQTT
connection, credentials, and routing on the server side — ideal for devices with limited memory or
libraries that only support HTTP.
Because publish and subscribe share the same bare topic string (see Topic Structure), device-to-device messaging needs no special convention: whatever Device A publishes is automatically available to every other MQTT client subscribed to that same topic — other devices, Node-RED flows, or any external MQTT client authenticated on your account. (Dashboard widgets don't connect over MQTT themselves — the platform relays the value to them automatically, no topic to configure.)
kitchen/relay1kitchen/relay1