Skip to content

ESP32 WiFi Relay Control with SiliconWit IO

In this tutorial, you will learn how to set up an ESP32 microcontroller to control a relay remotely over WiFi using the SiliconWit IO IoT platform. The ESP32 connects to the SiliconWit IO MQTT broker over a secure TLS connection, sends telemetry data (relay state, WiFi signal strength, uptime), and receives commands to toggle the relay on or off from the SiliconWit IO dashboard.

  • How to wire a relay module to an ESP32
  • How to connect the ESP32 to WiFi and establish a secure MQTT connection
  • How to publish telemetry data to SiliconWit IO
  • How to subscribe to commands from SiliconWit IO and control the relay
  • How to set up the project using PlatformIO
ComponentDescription
ESP32 Development BoardAny ESP32, ESP32-S3, or ESP32-C3 board (e.g., ESP32-S3-DevKitC-1)
Relay ModuleSingle-channel 5V or 3.3V relay module with optocoupler
Jumper WiresMale-to-female for connecting the relay to the ESP32
USB CableFor programming and powering the ESP32
WiFi Network2.4 GHz WiFi network with internet access
SoftwarePurpose
PlatformIOBuild system and IDE (VSCode extension or CLI)
SiliconWit IO AccountFree IoT platform account
LibraryVersionPurpose
PubSubClient^2.8MQTT client for Arduino
ArduinoJson^7.0.0JSON serialization and deserialization

WiFi and WiFiClientSecure are built into the ESP32 Arduino framework and do not need to be installed separately.

┌─────────────────────────────────────────────────────────┐
│ SiliconWit IO Cloud │
│ │
│ Dashboard ←→ MQTT Broker (TLS on port 8883) │
│ ↕ ↕ │
│ Telemetry Topic Commands Topic │
└────────────────────┬───────────────┬────────────────────┘
│ │
│ Internet │
│ │
┌────────────────────┴───────────────┴────────────────────┐
│ ESP32 (WiFi) │
│ │
│ WiFiClientSecure (TLS) → PubSubClient (MQTT) │
│ │
│ GPIO 5 → Relay Module → Load (lamp, motor, etc.) │
└─────────────────────────────────────────────────────────┘

Data Flow:

  1. Telemetry (ESP32 → Cloud): The ESP32 publishes JSON data every 30 seconds to the telemetry topic.
  2. Commands (Cloud → ESP32): The SiliconWit IO dashboard sends relay on/off commands to the commands topic. The ESP32 receives and executes them.

Step 1: Create a SiliconWit IO Account and Register a Device

Section titled “Step 1: Create a SiliconWit IO Account and Register a Device”
  1. Go to siliconwit.io/register and create a free account.
  2. Verify your email and log in to the dashboard.
  3. Navigate to Devices and click Add Device.
  4. Give your device a name (e.g., “ESP32 Relay Controller”).
  5. After registration, note down the following credentials:
CredentialExampleDescription
Device IDAbCdEfGhIjUnique 10-character identifier for your device
Access Tokenyour_access_token_hereAuthentication password for MQTT
Publish Topicd/{device_id}/tTopic for sending sensor data (d = device, t = telemetry)
Subscribe Topicd/{device_id}/cTopic for receiving commands (c = commands)

Keep your access token secure. Never share it publicly or commit it to version control.

Connect the relay module to the ESP32 as follows:

Relay Module PinESP32 PinDescription
VCC3.3V or 5VPower supply (check your relay module voltage)
GNDGNDGround
IN1 (Signal)GPIO 5Control signal from ESP32
ESP32 Relay Module
┌──────────┐ ┌──────────────┐
│ │ │ │
│ GPIO 5 ├───────────→│ IN1 │
│ │ │ │
│ 3.3V ├───────────→│ VCC │ Load
│ │ │ COM ├─────────┐
│ GND ├───────────→│ GND │ ┌────┴────┐
│ │ │ NO ├────│ Lamp / │
└──────────┘ └──────────────┘ │ Motor │
└────┬────┘
AC Mains
(Neutral)

Important Safety Notes:

  • If switching mains voltage (110V/220V AC), use a relay rated for your voltage and current.
  • Always disconnect mains power before wiring.
  • Use the NO (Normally Open) terminal so the load is off when the relay is not energized.
  • Consider using an optocoupler relay module for electrical isolation.

Create a new PlatformIO project or use an existing one. Your project structure should look like this:

project/
├── src/
│ └── main.cpp
├── platformio.ini
└── ...
; PlatformIO Project Configuration File
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM0
upload_flags = --before=no_reset
build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
lib_deps =
knolleary/PubSubClient@^2.8
bblanchon/ArduinoJson@^7.0.0

Board Selection: Change the board value to match your ESP32 variant:

  • ESP32-S3: esp32-s3-devkitc-1
  • ESP32-C3: esp32-c3-devkitm-1
  • ESP32 (original): esp32dev

USB CDC Flags: The build_flags for USB CDC are only needed for ESP32-S3 and ESP32-C3 boards that use the built-in USB for serial output. Remove them if your board uses a dedicated USB-to-UART chip (like CP2102 or CH340).

Upload Port: On Linux, this is typically /dev/ttyACM0 or /dev/ttyUSB0. On macOS, /dev/cu.usbmodemXXXX. On Windows, COM3 or similar.

Create src/main.cpp with the following code:

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// ========== WIFI CREDENTIALS ==========
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// ========== SILICONWIT IO MQTT ==========
const char* mqtt_server = "mqtt.siliconwit.io";
const int mqtt_port = 8883;
const char* device_id = "YOUR_DEVICE_ID";
const char* access_token = "YOUR_ACCESS_TOKEN";
const char* pub_topic = "d/YOUR_DEVICE_ID/t";
const char* sub_topic = "d/YOUR_DEVICE_ID/c";
// ========== PINS ==========
#define RELAY_PIN 5
// ========== COMMUNICATION STACK ==========
WiFiClientSecure espClient;
PubSubClient mqtt(espClient);
// ========== TIMING ==========
unsigned long lastTelemetry = 0;
const unsigned long TELEMETRY_INTERVAL = 30000;
// ========== MQTT CALLBACK ==========
// This function is called automatically when a message arrives
// on the subscribed commands topic from SiliconWit IO.
//
// SiliconWit IO sends commands in this JSON format:
// {"command":"Relay","timestamp":"2026-02-07T11:58:10.242Z","value":true}
//
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message;
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("[CMD] Received: ");
Serial.println(message);
// Handle plain text commands: "true", "false", "1", "0", "on", "off"
message.trim();
if (message == "true" || message == "1" || message == "on") {
digitalWrite(RELAY_PIN, HIGH);
Serial.println("[CMD] Relay ON");
return;
}
if (message == "false" || message == "0" || message == "off") {
digitalWrite(RELAY_PIN, LOW);
Serial.println("[CMD] Relay OFF");
return;
}
// Handle JSON commands: {"command":"Relay","value":true}
JsonDocument doc;
if (deserializeJson(doc, message)) return;
bool relayState = false;
if (doc["value"].is<bool>()) {
relayState = doc["value"].as<bool>();
} else if (doc["relay"].is<bool>()) {
relayState = doc["relay"].as<bool>();
} else if (doc["state"].is<bool>()) {
relayState = doc["state"].as<bool>();
} else {
Serial.println("[CMD] Unknown format");
return;
}
digitalWrite(RELAY_PIN, relayState ? HIGH : LOW);
Serial.print("[CMD] Relay: ");
Serial.println(relayState ? "ON" : "OFF");
}
// ========== WIFI CONNECTION ==========
void setupWifi() {
Serial.print("[WIFI] Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WIFI] Connected!");
Serial.print("[WIFI] IP: ");
Serial.println(WiFi.localIP());
}
// ========== MQTT CONNECTION ==========
bool connectMQTT() {
Serial.print("[MQTT] Connecting to SiliconWit IO...");
// Connect using device_id as client ID and username,
// access_token as password
if (mqtt.connect(device_id, device_id, access_token)) {
Serial.println(" connected!");
// Subscribe to commands topic with QoS 1 for reliable delivery
mqtt.subscribe(sub_topic, 1);
Serial.print("[MQTT] Subscribed to: ");
Serial.println(sub_topic);
return true;
}
Serial.print(" failed, rc=");
Serial.println(mqtt.state());
return false;
}
// ========== SEND TELEMETRY ==========
void sendTelemetry() {
JsonDocument doc;
doc["relay_state"] = digitalRead(RELAY_PIN) == HIGH;
doc["wifi_rssi"] = WiFi.RSSI();
doc["uptime"] = millis() / 1000;
String payload;
serializeJson(doc, payload);
if (mqtt.publish(pub_topic, payload.c_str())) {
Serial.print("[TELEMETRY] Sent: ");
Serial.println(payload);
} else {
Serial.println("[TELEMETRY] Publish failed!");
}
}
// ========== SETUP ==========
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n=== ESP32 WiFi Relay → SiliconWit IO ===\n");
// Initialize relay pin
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
// Connect to WiFi
setupWifi();
// Configure TLS - skip certificate verification for simplicity
// For production, use espClient.setCACert(root_ca) instead
espClient.setInsecure();
// Configure MQTT
mqtt.setServer(mqtt_server, mqtt_port);
mqtt.setCallback(mqttCallback);
mqtt.setBufferSize(512);
// Connect to MQTT broker with retries
int retries = 0;
while (!connectMQTT() && retries < 5) {
retries++;
delay(5000);
}
// Send initial telemetry
sendTelemetry();
lastTelemetry = millis();
}
// ========== LOOP ==========
void loop() {
// Reconnect WiFi if disconnected
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WIFI] Disconnected, reconnecting...");
setupWifi();
}
// Reconnect MQTT if disconnected
if (!mqtt.connected()) {
Serial.println("[MQTT] Disconnected, reconnecting...");
connectMQTT();
}
// Process incoming MQTT messages
mqtt.loop();
// Send telemetry at regular intervals
if (millis() - lastTelemetry >= TELEMETRY_INTERVAL) {
lastTelemetry = millis();
sendTelemetry();
}
}

The ESP32 connects to your local WiFi network using the built-in WiFi library. For the secure MQTT connection, WiFiClientSecure provides TLS encryption. The setInsecure() call skips certificate verification for simplicity, but for production deployments you should pin the server’s CA certificate using setCACert().

WiFiClientSecure espClient;
espClient.setInsecure(); // Skip cert verification (OK for testing)

Every 30 seconds, the ESP32 publishes a JSON payload to the telemetry topic:

{
"relay_state": true,
"wifi_rssi": -45,
"uptime": 120
}
FieldTypeDescription
relay_statebooleanCurrent state of the relay (true = ON)
wifi_rssiintegerWiFi signal strength in dBm
uptimeintegerSeconds since the ESP32 booted

You can add any custom sensor data to this JSON object. SiliconWit IO accepts any valid JSON structure.

The ESP32 subscribes to the commands topic with QoS 1 to ensure reliable message delivery. When you toggle the relay from the SiliconWit IO dashboard, it sends a command in this format:

{
"command": "Relay",
"timestamp": "2026-02-07T11:58:10.242Z",
"value": true
}

The callback function parses the value field and sets the relay GPIO accordingly.

The main loop continuously checks both the WiFi and MQTT connections. If either drops, it automatically reconnects. This ensures the device stays online even after temporary network interruptions.

Using the PlatformIO CLI:

Terminal window
pio run --target upload

Or in VSCode with the PlatformIO extension, click the Upload button (right arrow icon) in the bottom toolbar.

If the upload fails with a connection error, you may need to manually put the ESP32 into bootloader mode:

  1. Hold the BOOT button on the ESP32 board
  2. Press and release the RST (Reset) button
  3. Release the BOOT button
  4. Run the upload command within a few seconds
Terminal window
pio device monitor

You should see output similar to:

=== ESP32 WiFi Relay → SiliconWit IO ===
[WIFI] Connecting to YOUR_WIFI_SSID
......
[WIFI] Connected!
[WIFI] IP: 192.168.1.100
[MQTT] Connecting to SiliconWit IO... connected!
[MQTT] Subscribed to: d/xxxxxxxx/c
[TELEMETRY] Sent: {"relay_state":false,"wifi_rssi":-45,"uptime":5}

Step 7: Control the Relay from SiliconWit IO

Section titled “Step 7: Control the Relay from SiliconWit IO”
  1. Log in to your SiliconWit IO dashboard.
  2. Navigate to your registered device.
  3. You should see the telemetry data (relay state, WiFi RSSI, uptime) updating every 30 seconds.
  4. Use the dashboard controls to send relay commands:
    • Relay → true turns the relay ON
    • Relay → false turns the relay OFF
  5. The relay should toggle within 1-2 seconds of sending the command.
SymptomSolution
Stuck on “Connecting to WiFi…”Verify SSID and password. ESP32 only supports 2.4 GHz WiFi.
WiFi connects then disconnectsMove closer to the router or check for interference.
No IP address assignedCheck if your router’s DHCP is enabled.
Symptommqtt.state() CodeSolution
Connection refused-2Check internet connectivity. Verify the MQTT server address.
Connection refused-1TLS handshake failed. Ensure setInsecure() is called.
Bad credentials4 or 5Verify device_id and access_token match your SiliconWit IO dashboard.
Connection drops frequentlyIncrease TELEMETRY_INTERVAL to reduce traffic.
SymptomSolution
No “[CMD]” messages in serialCheck that mqtt.subscribe() succeeded. Verify the commands topic string.
”[CMD] Unknown format”The command JSON format may have changed. Check serial output for the actual payload.
Relay clicks but load doesn’t switchCheck relay wiring. Ensure the relay is rated for your load voltage and current.

You can easily extend the telemetry payload with additional sensor data. For example, to add a DHT22 temperature and humidity sensor:

#include <DHT.h>
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void sendTelemetry() {
JsonDocument doc;
doc["relay_state"] = digitalRead(RELAY_PIN) == HIGH;
doc["wifi_rssi"] = WiFi.RSSI();
doc["uptime"] = millis() / 1000;
doc["temperature"] = dht.readTemperature();
doc["humidity"] = dht.readHumidity();
String payload;
serializeJson(doc, payload);
mqtt.publish(pub_topic, payload.c_str());
}

SiliconWit IO automatically detects and displays new data fields on the dashboard.

For production deployments, consider these improvements:

  1. Pin the TLS Certificate: Replace setInsecure() with setCACert(root_ca) using the broker’s root CA certificate.
  2. Store Credentials Securely: Use the ESP32’s NVS (Non-Volatile Storage) or encrypted flash to store WiFi passwords and access tokens instead of hardcoding them.
  3. Use a Watchdog Timer: Enable the ESP32’s hardware watchdog to automatically reset the device if the firmware hangs.
  4. OTA Updates: Implement Over-The-Air firmware updates so you can patch the device remotely.

In this tutorial, you built a complete IoT relay controller using:

  • ESP32 as the microcontroller
  • WiFi for internet connectivity
  • MQTT over TLS for secure communication
  • SiliconWit IO as the cloud platform for monitoring and control

The system publishes telemetry data every 30 seconds and responds to relay commands from the dashboard in real time. This same pattern can be extended to control multiple relays, read sensors, and build more complex IoT applications.