MQTT Connection Guide

Everything you need to connect your devices to Virtuino Cloud via MQTT — from first connection to secure production setup.

Mosquitto Compatible — The Virtuino Cloud broker is 100% Mosquitto-compatible. Any MQTT client library or tool that supports standard MQTT works out of the box.

1How to Connect

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

2Tested Configuration (MQTTX)

The settings below have been tested and confirmed working with the MQTTX desktop client. Use them as a reference for any MQTT client application.

MQTTX Port 1883 — Plain
Name
virtuino
Host
mqtt:// cloud.virtuino.com
Port
1883
Client ID
vr_abcd1234_abcdefgh123
Username
vr_abcd1234
Password
••••••••••••••••••••••
SSL/TLS

MQTT Version
3.1.1
Connect Timeout
10 (s)
Keep Alive
60 (s)
Auto Reconnect
Reconnect Period
4000 (ms)
Clean Session
MQTTX Port 8883 — TLS
Name
virtuino
Host
mqtts:// cloud.virtuino.com
Port
8883
Client ID
vr_abcd1234_abcdefgh123
Username
vr_abcd1234
Password
••••••••••••••••••••••
SSL/TLS
SSL Secure
Certificate
● CA signed server certificate
ALPN
(empty)

MQTT Version
5.0
Connect Timeout
10 (s)
Keep Alive
60 (s)
Auto Reconnect
Reconnect Period
4000 (ms)
Clean Start
Session Expiry
0 (s)

3Topic Structure

Important — Devices & Fields must exist first:
Before publishing or subscribing to any topic, make sure you have already created the corresponding Device and Field in the Virtuino Cloud Console (Console → Devices). The 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.

Format 1 — Simple Recommended for beginners

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

Format 2 — Device Path Multi-device projects

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

Publish & Subscribe

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.

DirectionWhoPrefixExample TopicExample 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

Wildcards

WildcardMeaningExampleMatches
+ 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
Namespace isolation: You can only publish or subscribe to topics that begin with your own Sub-account Key. The broker rejects any attempt to access another account's namespace.

4Client Settings Explained

These are the settings you configure in your MQTT client application (such as MQTTX, Node-RED, Home Assistant, etc.).

Connection Settings

SettingWhat it doesRecommended 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

SSL / TLS Settings

SettingWhat it doesVirtuino 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

5Last Will and Testament (LWT)

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.

FieldDescription & 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/status
The 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
Best practice — Retained status pattern:
On connect: publish "online" with retain ON to vr_abcd1234/in/device/esp32/status (device → cloud).
Set LWT: topic = vr_abcd1234/out/device/esp32/status, payload = "offline", retain ON, QoS 1 (broker → subscribers).
Dashboard subscribes to 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)

6Code Examples

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.

Required library: All examples below use PubSubClient by Nick O'Leary. Install it via Arduino IDE → Sketch → Include Library → Manage Libraries → search PubSubClient.

ESP8266

Plain — Port 1883
Secure TLS — Port 8883
// 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 — Port 1883
Secure TLS — Port 8883
// 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());
  }
}
CA Certificate note: The 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.

7HTTP → MQTT Gateway & Device-to-Device

HTTP to MQTT Gateway

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.

SourceHow to enableMQTT 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
Examples: Full HTTP POST request examples with "publish": true are available in the HTTP API documentation.
No MQTT client needed on the device. A plain HTTP POST with "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.

Device-to-Device Communication

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.

Rule of thumb: If data is going into the cloud, use /in/. If you are reading data out of the cloud, use /out/. The broker maps them automatically — the sensor name stays the same.
HTTP-to-MQTT Gateway diagram 📡 IoT Device ESP32, Arduino… HTTP POST HTTP POST "publish": true Virtuino Cloud HTTP → MQTT Gateway auto-publishes to /out/ MQTT publish MQTT Broker cloud.virtuino.com PUBLISHES TO /out/ TOPICS FORMAT 1 — SIMPLE vr_abcd1234 /out/sensor_name FORMAT 2 — DEVICE PATH vr_abcd1234 /out/device/device_name /sensor_name /out/ topic SUBSCRIBERS 📡 Other Devices 📊 Dashboard 🔗 Node-RED / Apps