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.
What You Will Learn
Section titled “What You Will Learn”- 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
Prerequisites
Section titled “Prerequisites”Hardware
Section titled “Hardware”| Component | Description |
|---|---|
| ESP32 Development Board | Any ESP32, ESP32-S3, or ESP32-C3 board (e.g., ESP32-S3-DevKitC-1) |
| Relay Module | Single-channel 5V or 3.3V relay module with optocoupler |
| Jumper Wires | Male-to-female for connecting the relay to the ESP32 |
| USB Cable | For programming and powering the ESP32 |
| WiFi Network | 2.4 GHz WiFi network with internet access |
Software
Section titled “Software”| Software | Purpose |
|---|---|
| PlatformIO | Build system and IDE (VSCode extension or CLI) |
| SiliconWit IO Account | Free IoT platform account |
Libraries
Section titled “Libraries”| Library | Version | Purpose |
|---|---|---|
| PubSubClient | ^2.8 | MQTT client for Arduino |
| ArduinoJson | ^7.0.0 | JSON serialization and deserialization |
WiFi and WiFiClientSecure are built into the ESP32 Arduino framework and do not need to be installed separately.
Architecture Overview
Section titled “Architecture Overview”┌─────────────────────────────────────────────────────────┐│ 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:
- Telemetry (ESP32 → Cloud): The ESP32 publishes JSON data every 30 seconds to the telemetry topic.
- 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”- Go to siliconwit.io/register and create a free account.
- Verify your email and log in to the dashboard.
- Navigate to Devices and click Add Device.
- Give your device a name (e.g., “ESP32 Relay Controller”).
- After registration, note down the following credentials:
| Credential | Example | Description |
|---|---|---|
| Device ID | AbCdEfGhIj | Unique 10-character identifier for your device |
| Access Token | your_access_token_here | Authentication password for MQTT |
| Publish Topic | d/{device_id}/t | Topic for sending sensor data (d = device, t = telemetry) |
| Subscribe Topic | d/{device_id}/c | Topic for receiving commands (c = commands) |
Keep your access token secure. Never share it publicly or commit it to version control.
Step 2: Wiring the Relay to the ESP32
Section titled “Step 2: Wiring the Relay to the ESP32”Connect the relay module to the ESP32 as follows:
| Relay Module Pin | ESP32 Pin | Description |
|---|---|---|
| VCC | 3.3V or 5V | Power supply (check your relay module voltage) |
| GND | GND | Ground |
| IN1 (Signal) | GPIO 5 | Control 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.
Step 3: Set Up the PlatformIO Project
Section titled “Step 3: Set Up the PlatformIO Project”3.1 Create the Project
Section titled “3.1 Create the Project”Create a new PlatformIO project or use an existing one. Your project structure should look like this:
project/├── src/│ └── main.cpp├── platformio.ini└── ...3.2 Configure platformio.ini
Section titled “3.2 Configure platformio.ini”; PlatformIO Project Configuration File
[env:esp32-s3-devkitc-1]platform = espressif32board = esp32-s3-devkitc-1framework = arduinomonitor_speed = 115200upload_port = /dev/ttyACM0monitor_port = /dev/ttyACM0upload_flags = --before=no_resetbuild_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1lib_deps = knolleary/PubSubClient@^2.8 bblanchon/ArduinoJson@^7.0.0Board Selection: Change the
boardvalue to match your ESP32 variant:
- ESP32-S3:
esp32-s3-devkitc-1- ESP32-C3:
esp32-c3-devkitm-1- ESP32 (original):
esp32devUSB CDC Flags: The
build_flagsfor 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/ttyACM0or/dev/ttyUSB0. On macOS,/dev/cu.usbmodemXXXX. On Windows,COM3or similar.
Step 4: Write the Firmware
Section titled “Step 4: Write the Firmware”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(); }}Step 5: Understanding the Code
Section titled “Step 5: Understanding the Code”5.1 WiFi and TLS Connection
Section titled “5.1 WiFi and TLS Connection”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)5.2 MQTT Publish (Telemetry)
Section titled “5.2 MQTT Publish (Telemetry)”Every 30 seconds, the ESP32 publishes a JSON payload to the telemetry topic:
{ "relay_state": true, "wifi_rssi": -45, "uptime": 120}| Field | Type | Description |
|---|---|---|
relay_state | boolean | Current state of the relay (true = ON) |
wifi_rssi | integer | WiFi signal strength in dBm |
uptime | integer | Seconds since the ESP32 booted |
You can add any custom sensor data to this JSON object. SiliconWit IO accepts any valid JSON structure.
5.3 MQTT Subscribe (Commands)
Section titled “5.3 MQTT Subscribe (Commands)”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.
5.4 Auto-Reconnection
Section titled “5.4 Auto-Reconnection”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.
Step 6: Flash the Firmware
Section titled “Step 6: Flash the Firmware”6.1 Build and Upload
Section titled “6.1 Build and Upload”Using the PlatformIO CLI:
pio run --target uploadOr in VSCode with the PlatformIO extension, click the Upload button (right arrow icon) in the bottom toolbar.
6.2 Entering Bootloader Mode (if needed)
Section titled “6.2 Entering Bootloader Mode (if needed)”If the upload fails with a connection error, you may need to manually put the ESP32 into bootloader mode:
- Hold the BOOT button on the ESP32 board
- Press and release the RST (Reset) button
- Release the BOOT button
- Run the upload command within a few seconds
6.3 Monitor Serial Output
Section titled “6.3 Monitor Serial Output”pio device monitorYou 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”- Log in to your SiliconWit IO dashboard.
- Navigate to your registered device.
- You should see the telemetry data (relay state, WiFi RSSI, uptime) updating every 30 seconds.
- Use the dashboard controls to send relay commands:
- Relay → true turns the relay ON
- Relay → false turns the relay OFF
- The relay should toggle within 1-2 seconds of sending the command.
Troubleshooting
Section titled “Troubleshooting”WiFi Connection Issues
Section titled “WiFi Connection Issues”| Symptom | Solution |
|---|---|
| Stuck on “Connecting to WiFi…” | Verify SSID and password. ESP32 only supports 2.4 GHz WiFi. |
| WiFi connects then disconnects | Move closer to the router or check for interference. |
| No IP address assigned | Check if your router’s DHCP is enabled. |
MQTT Connection Issues
Section titled “MQTT Connection Issues”| Symptom | mqtt.state() Code | Solution |
|---|---|---|
| Connection refused | -2 | Check internet connectivity. Verify the MQTT server address. |
| Connection refused | -1 | TLS handshake failed. Ensure setInsecure() is called. |
| Bad credentials | 4 or 5 | Verify device_id and access_token match your SiliconWit IO dashboard. |
| Connection drops frequently | — | Increase TELEMETRY_INTERVAL to reduce traffic. |
Relay Not Responding to Commands
Section titled “Relay Not Responding to Commands”| Symptom | Solution |
|---|---|
| No “[CMD]” messages in serial | Check 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 switch | Check relay wiring. Ensure the relay is rated for your load voltage and current. |
Adding Custom Sensors
Section titled “Adding Custom Sensors”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 DHT22DHT 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.
Security Considerations
Section titled “Security Considerations”For production deployments, consider these improvements:
- Pin the TLS Certificate: Replace
setInsecure()withsetCACert(root_ca)using the broker’s root CA certificate. - 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.
- Use a Watchdog Timer: Enable the ESP32’s hardware watchdog to automatically reset the device if the firmware hangs.
- OTA Updates: Implement Over-The-Air firmware updates so you can patch the device remotely.
Summary
Section titled “Summary”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.