Video Tutorial · MQTT · ~9 minutes

Two-way control — ESP32

Control an ESP32's onboard button and LEDs from a Virtuino Cloud dashboard, and watch the physical button state update live on screen — all over MQTT. Full video walkthrough plus the wiring, code and dashboard steps below.

Create free account Jump to the code

What you'll build

A push button on the ESP32 controls an LED widget on the dashboard — press it, and the widget changes state instantly. Meanwhile two switch widgets on the dashboard control a green and a red LED wired to the board. Three fields, two directions, one dashboard.

Wire up the board

Connect a push button and two LEDs to your ESP32 as follows (button uses the internal pull-up, no external resistor needed on that pin):

ComponentESP32 PinNotes
Push buttonGPIO 4INPUT_PULLUP
Green LEDGPIO 13OUTPUT (+ resistor)
Red LEDGPIO 12OUTPUT (+ resistor)

Create your account & sign in

Register for a free account and log in to the console.

Create three fields

A field's name is the MQTT topic itself, so name them the way the board will publish: esp32/button (the physical button's state, reported by the board), esp32/green_led and esp32/red_led (the two LEDs, controlled from the dashboard). The / only keeps related fields together — you can name your fields however you like, as long as the sketch uses the same names.

Flash your ESP32

Install the PubSubClient library (by Nick O'Leary) in the Arduino IDE. Get your MQTT username (your Sub-account Key) and password from the console, fill them into the sketch below along with your WiFi credentials, then upload.

Broker: cloud.virtuino.com : 1883  ·  Publish (board → cloud): esp32/button  ·  Subscribe (cloud → board): esp32/green_led & esp32/red_led
#include <WiFi.h>
#include <PubSubClient.h>          // install "PubSubClient" by Nick O'Leary

const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";

const char* MQTT_HOST     = "cloud.virtuino.com";
const int   MQTT_PORT     = 1883;
const char* MQTT_USERNAME = "YOUR_MQTT_USERNAME";   // your Sub-account Key (Console)
const char* MQTT_PASS     = "YOUR_MQTT_PASSWORD";   // MQTT password (Console)

const int BUTTON_PIN    = 4;    // pull-up
const int GREEN_LED_PIN = 13;
const int RED_LED_PIN   = 12;

// The field names, exactly as in Console → Fields. No account key, no in/out —
// the broker knows your account from the login. PUBLISH = to the cloud, SUBSCRIBE = from the cloud.
const char* topicButton   = "esp32/button";      // the board publishes -> cloud
const char* topicGreenLed = "esp32/green_led";   // the board subscribes <- cloud
const char* topicRedLed   = "esp32/red_led";     // the board subscribes <- cloud

WiFiClient   net;
PubSubClient mqtt(net);
String       clientId;          // built once in setup()

void onMessage(char* topic, byte* payload, unsigned int len) {
  String msg; for (unsigned int i = 0; i < len; i++) msg += (char)payload[i];
  String t = String(topic);

  if (t == topicGreenLed) digitalWrite(GREEN_LED_PIN, msg == "1" ? HIGH : LOW);
  if (t == topicRedLed)   digitalWrite(RED_LED_PIN,   msg == "1" ? HIGH : LOW);
}

void connectMqtt() {
  while (!mqtt.connected()) {
    if (mqtt.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASS)) {
      Serial.println("MQTT connected");
      mqtt.subscribe(topicGreenLed);
      mqtt.subscribe(topicRedLed);
    } else {
      Serial.printf("MQTT failed rc=%d, retrying...\n", mqtt.state());
      delay(2000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(GREEN_LED_PIN, OUTPUT);
  pinMode(RED_LED_PIN, OUTPUT);

  // Client ID: any name, the same after every restart and unique among your boards —
  // the chip ID does both. It is the name this board shows in My Virtuino World.
  clientId = "esp32-" + String((uint32_t)ESP.getEfuseMac(), HEX);

  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nWiFi connected");

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(20);   // a power cut shows up in My Virtuino World within ~30 s
}

void loop() {
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  static int lastState = -1;
  static unsigned long lastChange = 0;
  int state = digitalRead(BUTTON_PIN) == LOW ? 1 : 0;   // pressed = LOW (pull-up)
  if (state != lastState && millis() - lastChange > 50) {   // 50 ms debounce
    lastState  = state;
    lastChange = millis();
    mqtt.publish(topicButton, String(state).c_str(), true);   // retained
    Serial.println("published " + String(state) + " -> " + topicButton);
  }
}
Prefer a plain publish-only example, or need TLS / ESP8266 code? See the full MQTT reference docs.

Check the Serial Monitor

Open the Serial Monitor (115200 baud) and wait for MQTT connected. Press the physical button — you'll see the published value logged, and the console's Data Monitor will show it arriving live.

Build the dashboard

Create a new dashboard, unlock it, and add three widgets:

1. An LED widget bound to the esp32/button field — this mirrors the physical button state.
2. A switch widget bound to esp32/green_led (inline alignment works well).
3. A switch widget bound to esp32/red_led.

Lock the dashboard when you're done positioning the widgets.

Test it

Press the button on the board — the LED widget on the dashboard changes state immediately. Tap the switches on the dashboard — the green and red LEDs on the board turn on and off in response. Everything is bidirectional and live.

Open My Virtuino World in the console to watch it from above: the board appears under its client ID (esp32-…), and every press and every switch travels along its line to the cloud and back.

Start now — it's free