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 → API & Connections |
| *Password | your MQTT password | Found in Console → API & Connections → MQTT Credentials |
| Client ID | vr_abcd1234_anything |
Must begin with your Sub-account Key as prefix; the suffix is arbitrary |
| 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.
device_name and sensor_name segments in the topic must exactly match the names you defined on the platform — otherwise the broker will not recognise the topic and the data will be ignored.
Every topic is namespaced by your Sub-account Key, which keeps your data completely isolated from all other accounts. The broker supports two topic formats — choose the one that fits your project.
The simplest option. Use it for quick tests, prototypes, and single-device learning projects.
# Publish to broker (device → cloud) {sub_account_key}/in/sensor_name # Subscribe from broker (cloud → device) {sub_account_key}/out/sensor_name # Examples — device publishes vr_abcd1234/in/temperature vr_abcd1234/in/humidity # Examples — device subscribes (receives from cloud) vr_abcd1234/out/relay1 vr_abcd1234/out/V1 # virtual-pin style
Organises topics by device name, making it easy to manage multiple devices under the same account.
# Publish to broker (device → cloud) {sub_account_key}/in/device/device_name/sensor_name # Subscribe from broker (cloud → device) {sub_account_key}/out/device/device_name/sensor_name # Examples — device publishes vr_abcd1234/in/device/esp32/temperature vr_abcd1234/in/device/arduino_uno/soil_moisture # Examples — device subscribes (receives from cloud) vr_abcd1234/out/device/esp32/relay vr_abcd1234/out/device/raspi/status
The broker uses an in / out convention: messages sent to the cloud use the /in/ prefix; messages sent from the cloud use the /out/ prefix.
| Direction | Who | Prefix | Example Topic | Example Payload |
|---|---|---|---|---|
| PUBLISH → broker | Device (ESP, Arduino…) | /in/ |
vr_abcd1234/in/temperature |
"23.75" |
| SUBSCRIBE ← broker | Device (ESP, Arduino…) | /out/ |
vr_abcd1234/out/relay1 |
"1" / "0" |
| PUBLISH → broker | Dashboard widget | /in/ |
vr_abcd1234/in/relay1 |
"1" |
| SUBSCRIBE ← broker | Dashboard widget | /out/ |
vr_abcd1234/out/temperature |
live sensor reading |
| Wildcard | Meaning | Example | Matches |
|---|---|---|---|
+ |
Any single level | vr_abcd1234/out/device/+/status |
status of all devices, but not deeper levels |
# |
All levels from here down | vr_abcd1234/out/# |
all outgoing messages in your namespace |
# |
All levels from here down | vr_abcd1234/out/device/esp32/# |
all outgoing topics for the esp32 device 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 and triggers the Last Will. | 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 |
The Last Will is a message you pre-register with the broker at connect time. If your device disconnects unexpectedly (power loss, network dropout, crash), the broker publishes this message automatically. A clean disconnect (device calls DISCONNECT) does not trigger the LWT.
Use it to make dashboards show accurate online/offline status for every device.
| Field | Description & Usage |
|---|---|
| Last-Will Topic |
The topic the broker will publish to on your behalf when you disconnect unexpectedly. Should be a dedicated status topic for the device. Example: vr_abcd1234/out/device/esp32/statusThe broker publishes the LWT under the /out/ prefix. Configure the dashboard widget to subscribe to this topic to show live device state.
|
| Last-Will Payload |
The message content the broker will publish. Use a value that clearly signals that the device is down. Common values: "offline", "0", "disconnected"Pair with a regular publish of "online" on successful connect so the dashboard always reflects the true device state.
|
| Last-Will QoS |
The QoS level for the LWT message itself. QoS 0: broker sends the LWT once, no acknowledgement. QoS 1: broker retries until all current subscribers acknowledge receipt — recommended so the dashboard always receives the disconnect event. Recommended: QoS 1
|
| Last-Will Retain |
When ON: the broker stores the LWT as the retained message for that topic. Any new subscriber (e.g. a dashboard widget that opens later) immediately receives "offline" as the current state — even if no new message has arrived. This is almost always the correct setting for status topics.When OFF: only subscribers currently connected at the moment of disconnect will receive the LWT. Recommended: Retain ON
|
"online" with retain ON to vr_abcd1234/in/device/esp32/status (device → cloud).vr_abcd1234/out/device/esp32/status, payload = "offline", retain ON, QoS 1 (broker → subscribers).vr_abcd1234/out/device/esp32/status to always see the current device state.
# LWT configuration at CONNECT time (pseudocode) # The broker publishes the LWT, so the topic uses the /out/ prefix will_topic = "vr_abcd1234/out/device/esp32/status" will_payload = "offline" will_qos = 1 will_retain = true # After successful connect, publish your "I'm online" message (device → cloud, /in/ prefix): publish("vr_abcd1234/in/device/esp32/status", "online", retain=true)
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(): prefix + chip ID // ── Topics ───────────────────────────────────────── const char* topicTemp = "vr_abcd1234/in/device/esp8266/temperature"; // device publishes → cloud const char* topicRelay = "vr_abcd1234/out/device/esp8266/relay1"; // device subscribes ← cloud WiFiClient wifiClient; PubSubClient mqtt(wifiClient); // ── Incoming message handler ─────────────────────── void onMessage(char* topic, byte* payload, unsigned int len) { String msg = ""; for (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); clientId = String(mqttUser) + "_" + 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); } 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) // Uses Let's Encrypt CA certificate — no file upload needed. // // ⚠ 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 <BearSSLHelpers.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 = 8883; const char* mqttUser = "vr_abcd1234"; const char* mqttPass = "YOUR_MQTT_PASSWORD"; String clientId; // built in setup(): prefix + chip ID // ── Topics ───────────────────────────────────────── const char* topicTemp = "vr_abcd1234/in/device/esp8266/temperature"; // device publishes → cloud const char* topicRelay = "vr_abcd1234/out/device/esp8266/relay1"; // device subscribes ← cloud const char* topicStatusIn = "vr_abcd1234/in/device/esp8266/status"; // device publishes "online" const char* topicStatusOut = "vr_abcd1234/out/device/esp8266/status"; // LWT topic (broker publishes "offline") // ── ISRG Root X1 — Let's Encrypt CA Certificate ─── 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 (int i = 0; i < len; i++) msg += (char)payload[i]; Serial.println("[" + String(topic) + "] " + msg); } void connectMQTT() { while (!mqtt.connected()) { Serial.print("Connecting (TLS)..."); // Last Will: broker publishes "offline" to /out/ topic if device drops unexpectedly if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass, topicStatusOut, 1, true, "offline")) { Serial.println(" connected!"); mqtt.publish(topicStatusIn, "online", true); // device publishes "online" via /in/ mqtt.subscribe(topicRelay); } else { Serial.print(" failed, rc="); Serial.println(mqtt.state()); delay(5000); } } } void setup() { Serial.begin(115200); clientId = String(mqttUser) + "_" + String(ESP.getChipId(), HEX); configTime(3 * 3600, 0, "pool.ntp.org"); // TLS needs correct time WiFi.begin(ssid, wifiPass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi: " + WiFi.localIP().toString()); wifiClient.setTrustAnchors(&trustedCA); // verify broker cert against ISRG Root X1 mqtt.setServer(mqttHost, mqttPort); mqtt.setCallback(onMessage); 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; mqtt.publish(topicTemp, String(temp, 2).c_str()); } }
// 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"; const char* mqttPass = "YOUR_MQTT_PASSWORD"; String clientId; // built in setup(): prefix + chip ID const char* topicTemp = "vr_abcd1234/in/device/esp32/temperature"; // device publishes → cloud const char* topicRelay = "vr_abcd1234/out/device/esp32/relay1"; // device subscribes ← cloud WiFiClient wifiClient; PubSubClient mqtt(wifiClient); void onMessage(char* topic, byte* payload, unsigned int len) { String msg = ""; for (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); clientId = String(mqttUser) + "_" + 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); } 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) // WiFiClientSecure verifies the broker's Let's Encrypt certificate automatically. #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"; const char* mqttPass = "YOUR_MQTT_PASSWORD"; String clientId; // built in setup(): prefix + chip ID const char* topicTemp = "vr_abcd1234/in/device/esp32/temperature"; // device publishes → cloud const char* topicRelay = "vr_abcd1234/out/device/esp32/relay1"; // device subscribes ← cloud const char* topicStatusIn = "vr_abcd1234/in/device/esp32/status"; // device publishes "online" const char* topicStatusOut = "vr_abcd1234/out/device/esp32/status"; // LWT topic (broker publishes "offline") // ── ISRG Root X1 — Let's Encrypt CA Certificate ─────────────────────────────── 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); } void connectMQTT() { while (!mqtt.connected()) { Serial.print("Connecting (TLS)..."); // connect() with Last Will arguments: (id, user, pass, willTopic, willQoS, willRetain, willMsg) if (mqtt.connect(clientId.c_str(), mqttUser, mqttPass, topicStatusOut, 1, true, "offline")) { Serial.println(" connected!"); mqtt.publish(topicStatusIn, "online", true); // device publishes "online" via /in/ mqtt.subscribe(topicRelay); } else { Serial.print(" failed, rc="); Serial.println(mqtt.state()); delay(5000); } } } void setup() { Serial.begin(115200); clientId = String(mqttUser) + "_" + 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(60); 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; mqtt.publish(topicTemp, String(temp, 1).c_str()); } }
caCert in the examples above contains a truncated placeholder. Replace it with the complete ISRG Root X1 certificate from
letsencrypt.org/certs/isrgrootx1.pem.
Alternatively, call wifiClient.setInsecure() instead of setCACert() during development to skip certificate verification — 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 corresponding /out/sensor_name topic is published automatically |
| 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.
The /in/ ↔ /out/ convention makes device-to-device messaging straightforward:
whatever Device A publishes on /in/sensor is automatically available on
/out/sensor for every subscriber — other devices, dashboards, Node-RED, or any
MQTT client.
vr_abcd1234/in/sensor_namevr_abcd1234/out/sensor_name/in/.
If you are reading data out of the cloud, use /out/.
The broker maps them automatically — the sensor name stays the same.