ESP32 Quickstart
Flash, connect, and test the ESP32-based Fantom Interio hub.
Connect Your ESP Board โ Super Easy Guide ๐
Anyone can do this! Even a 10-year-old. Takes about 15 minutes total.
Which Boards Work?
ALL of these work with Fantom Interio โ the app auto-detects whichever you plug in:
| Board | Chip on USB | Notes |
|---|---|---|
| ESP8266 NodeMCU (v1/v2/v3) | CH340 | Most popular beginner board |
| Wemos D1 Mini | CH340 | Tiny & cheap |
| ESP32 DevKit V1 | CP2102 | Most powerful beginner board |
| ESP32-S3 | Native USB | Newest, fastest |
| ESP32-S2 | Native USB | Good for USB projects |
| ESP32-C3 | Native USB | Small, energy-efficient |
| ESP32-C6 / H2 | Native USB | Supports Thread / Matter |
Not sure which you have? Doesn't matter โ just plug it in and the app will tell you!
What You Need
- An ESP board (any from above) โ about $3โ8 on Amazon
- USB cable (check if your board uses Micro-USB or USB-C)
- A 4-channel relay module 5V โ about $3 on Amazon
- Jumper wires (female-to-female) โ about $2
- Your WiFi name and password
- Google Chrome or Microsoft Edge (free)
No Arduino IDE needed! The Fantom Interio app has a built-in Browser Firmware Flasher that flashes your ESP board directly from Chrome. See Option A below.
If you prefer the traditional Arduino IDE approach, see Option B (Step 2โ4).
Option A โ Flash Firmware from Browser (No Arduino IDE!) โก
The fastest way to get started. The Fantom Interio app includes a built-in firmware flasher powered by esptool-js (Espressif's official WebSerial flasher) โ no software to install.
How to use the Browser Flasher
- Open the Fantom Interio app in Chrome or Edge
- Go to Smart Home Hub โ Connections โ ESP32 / ESP8266
- Expand โก Browser Firmware Flasher (No Arduino IDE)
- Select your board type (ESP32 DevKit, ESP8266, S3, C3, etc.)
- Choose firmware type:
- WebSocket โ for local development on
localhost - MQTT โ for HTTPS deployments or if you have a firewall
- WebSocket โ for local development on
- Enter your WiFi SSID and password
- Plug your ESP board into USB
- Click โก Flash Firmware via USB
- Select your board from the browser's port picker
- Wait for the progress bar to finish โ Done! ๐
Also available in: Studio โ Smart Home tab โ ESP Board Setup โ โก Browser Firmware Flasher
What the Browser Flasher does
- Connects to your board via Chrome's WebSerial API
- Detects the chip type (ESP32 / ESP8266 / S3 / C3 / C6)
- Flashes the firmware binary with your WiFi credentials baked in
- Sends WiFi configuration via serial
- Resets the board โ ready for Smart Connect!
Alternative: Download & Upload Manually
If you prefer, click ๐ฅ .ino in the flasher to download a pre-configured Arduino sketch with your WiFi credentials already filled in. Upload it using Arduino IDE (Option B below).
Option B โ Traditional Arduino IDE Setup
Step 1 โ Connect the Wires (No Soldering!)
It's like plugging in LEGO pieces. The relay module has little pins, and you push jumper wires into them.
ESP8266 NodeMCU or Wemos D1 Mini โ Relay
Board D1 (GPIO5) โ Relay IN1 (Light 1)
Board D2 (GPIO4) โ Relay IN2 (Light 2)
Board D5 (GPIO14) โ Relay IN3 (Light 3)
Board D6 (GPIO12) โ Relay IN4 (Fan/AC)
Board GND โ Relay GND
Board 5.0V โ Relay VCC
ESP32 DevKit V1 or any ESP32 โ Relay
Board GPIO2 โ Relay IN1 (Light 1)
Board GPIO4 โ Relay IN2 (Light 2)
Board GPIO5 โ Relay IN3 (Light 3)
Board GPIO18 โ Relay IN4 (Fan/AC)
Board GND โ Relay GND
Board 5.0V โ Relay VCC
โ ๏ธ Ask an adult to connect the other side of the relay (the 230V/110V side) to the light switch wires. The board side is safe low voltage.
Step 2 โ Install Arduino IDE and Board Support
- Download Arduino IDE from arduino.cc/en/software and install it
- Open Arduino IDE โ File โ Preferences
- In "Additional Boards Manager URLs" paste:
https://arduino.esp8266.com/stable/package_esp8266com_index.json,https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Click OK
- Go to Tools โ Board โ Boards Manager
- Search esp8266 โ Install
- Search esp32 โ Install
- Go to Sketch โ Include Library โ Manage Libraries
- Search WebSockets by Markus Sattler โ Install
- Search ArduinoJson by Benoit Blanchon โ Install
Step 3 โ Copy This Firmware (The Board's Brain)
Create a new sketch in Arduino IDE and paste ALL of this code:
// Fantom Interio Smart Home Firmware
// Works with ESP8266 NodeMCU, Wemos D1 Mini, and ALL ESP32 boards!
// Change only the two WiFi lines below, then upload.
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
// โโ ONLY CHANGE THESE TWO LINES โโ
const char* WIFI_SSID = "YourWiFiName";
const char* WIFI_PASS = "YourWiFiPassword";
// โโ That's it! Everything else is automatic. โโ
const uint16_t WS_PORT = 81;
struct DevicePin { const char* id; uint8_t pin; bool activeLow; };
#ifdef ESP8266
// ESP8266 pin mapping โ D1(5), D2(4), D5(14), D6(12)
const DevicePin DEVICES[] = {
{ "d1", 5, true }, // Living Room Light
{ "d4", 4, true }, // Bedroom Light
{ "d8", 14, true }, // Kitchen Light
{ "d2", 12, true }, // Fan or AC
};
#else
// ESP32 pin mapping โ GPIO 2, 4, 5, 18
const DevicePin DEVICES[] = {
{ "d1", 2, true }, // Living Room Light
{ "d4", 4, true }, // Bedroom Light
{ "d8", 5, true }, // Kitchen Light
{ "d2", 18, true }, // Fan or AC
};
#endif
const int N = sizeof(DEVICES)/sizeof(DEVICES[0]);
WebSocketsServer ws(WS_PORT);
const DevicePin* findDev(const char* id) {
for (int i=0;i<N;i++) if (!strcmp(DEVICES[i].id,id)) return &DEVICES[i];
return nullptr;
}
void applyDev(const DevicePin* d, bool on) {
digitalWrite(d->pin, (d->activeLow?!on:on) ? HIGH : LOW);
}
void onWS(uint8_t num, WStype_t t, uint8_t* p, size_t len) {
if (t==WStype_CONNECTED) { Serial.printf("[WS] #%u connected\n",num); return; }
if (t!=WStype_TEXT) return;
StaticJsonDocument<256> doc;
if (deserializeJson(doc,p,len)) return;
if (doc["type"]=="ping") {
StaticJsonDocument<64> r; r["type"]="pong"; r["ts"]=doc["ts"];
char b[64]; serializeJson(r,b); ws.sendTXT(num,b); return;
}
if (doc["type"]=="list") {
StaticJsonDocument<1024> r; r["type"]="deviceList";
JsonArray a=r.createNestedArray("devices");
for (int i=0;i<N;i++) {
JsonObject o=a.createNestedObject();
o["deviceId"]=DEVICES[i].id; o["name"]=DEVICES[i].id;
o["type"]="light"; o["isOn"]=false; o["watts"]=12;
}
char b[1024]; serializeJson(r,b); ws.sendTXT(num,b); return;
}
// Relay channel list โ returns GPIO pin map
if (doc["type"]=="relayList") {
StaticJsonDocument<512> r; r["type"]="relayList";
JsonArray a=r.createNestedArray("relays");
for (int i=0;i<N;i++) {
JsonObject o=a.createNestedObject();
o["id"]=DEVICES[i].id;
o["name"]=DEVICES[i].id;
o["gpio"]=DEVICES[i].pin;
}
char b[512]; serializeJson(r,b); ws.sendTXT(num,b); return;
}
// Relay test โ "ping" checks firmware ACK, "pulse" physically clicks relay 200ms
if (doc["type"]=="relayTest") {
const char* relayId = doc["relay"] | "";
const char* mode = doc["mode"] | "ping";
const DevicePin* d = findDev(relayId);
StaticJsonDocument<128> r; r["type"]="relayTestResult"; r["relay"]=relayId;
if (d) {
if (strcmp(mode,"pulse")==0) {
applyDev(d,true); delay(200); applyDev(d,false);
Serial.printf("[TEST] Pulsed relay %s (GPIO %d)\n", relayId, d->pin);
}
r["status"]="ok";
} else {
r["status"]="error";
}
char b[128]; serializeJson(r,b); ws.sendTXT(num,b); return;
}
const char* id=doc["deviceId"]|""; bool on=doc["isOn"]|false;
const DevicePin* d=findDev(id);
if (d) {
applyDev(d,on);
StaticJsonDocument<128> r; r["deviceId"]=id; r["isOn"]=on; r["ts"]=millis();
char b[128]; serializeJson(r,b); ws.broadcastTXT(b);
Serial.printf("[CMD] %s to %s\n",id,on?"ON":"OFF");
}
}
void setup() {
Serial.begin(115200); delay(200);
for (int i=0;i<N;i++) { pinMode(DEVICES[i].pin,OUTPUT); applyDev(&DEVICES[i],false); }
Serial.print("[WiFi] Connecting");
WiFi.begin(WIFI_SSID,WIFI_PASS);
while (WiFi.status()!=WL_CONNECTED) { delay(400); Serial.print("."); }
Serial.println(" Done!");
Serial.print("IP:"); Serial.println(WiFi.localIP());
ws.begin(); ws.onEvent(onWS);
WiFi.setSleep(false);
}
void loop() {
ws.loop();
if (Serial.available()) {
String cmd=Serial.readStringUntil('\n'); cmd.trim();
if (cmd=="AT+IP") { Serial.print("IP:"); Serial.println(WiFi.localIP()); }
}
}
Step 4 โ Upload to Your Board
Plug the board into your computer with the USB cable
In Arduino IDE โ Tools โ Board โ pick your board:
Your Board Choose ESP8266 NodeMCU NodeMCU 1.0 (ESP-12E Module) Wemos D1 Mini LOLIN(WEMOS) D1 R2 & mini ESP32 DevKit V1 ESP32 Dev Module ESP32-S3 ESP32S3 Dev Module ESP32-C3 ESP32C3 Dev Module Tools โ Port โ pick the port with your board
Click the Upload โถ button
Wait for "Done uploading"
Check It Worked
- Tools โ Serial Monitor โ set to 115200 baud
- Press the RST button on the board
- You should see:
IP:192.168.1.XXX - Write down that IP address! (you may need it as backup)
Step 5 โ Connect in the Fantom Interio App (1 click!)
- Open Chrome or Edge โ go to Fantom Interio
- Go to Smart Home Hub โ click Connections tab
- Click ESP32 / ESP8266 to expand it
- Click the big ๐ Smart Connect button
The app will automatically:
- Detect your environment (HTTPS โ MQTT bridge, localhost โ direct connection)
- Find your board (USB detection โ network scan)
- Detect the board type and display its name badge
- Discover your devices and import them
- Run a relay health check (ping test on each channel)
Status dot goes green = you're connected! ๐
Advanced: Click "Advanced Connection Options" for manual IP, USB detect, network scan, or custom MQTT bridge settings.
Test It!
- Tap a light card in the app โ relay should click!
- Say "Lights on" โ all lights on
- Say "All off" โ everything off
Something Not Working?
| Problem | Fix |
|---|---|
| "No board found" | Board and device must be on same WiFi. Try Manual IP. |
| Relay doesn't click | Check wires. Try changing true to false for activeLow |
| "WebSerial not available" | Use Chrome or Edge โ not Firefox or Safari |
| Upload fails (Arduino IDE) | Check Tools โ Board type and Port are correct |
| ESP8266 error about pin 18 | The firmware now auto-detects ESP8266 vs ESP32 pins (#ifdef ESP8266) |
| App shows connected but nothing works | Device IDs must match: d1, d4, d8, d2 |
| Firewall blocks connection | Use MQTT firmware โ it works through firewalls |
| HTTPS mixed content error | Use MQTT firmware + Smart Connect (auto-selects MQTT bridge on HTTPS) |
Want to Ask for Help?
Click the glowing orb ๐ซ๏ธ (bottom-right corner of any page) โ that's the Fantom Interio AI assistant! Ask it anything: "How do I add more relay channels?" or "Find my smart home devices" โ it knows everything about the app and will walk you through it step by step!
Alternative: MQTT Firmware (for HTTPS / Cloud Deployments)
If you're deploying the Fantom Interio app on HTTPS (e.g. https://fantom-studio.vercel.app), the browser can't connect directly to your ESP board via WebSocket (ws:// is blocked on HTTPS pages). Use this MQTT firmware instead โ it connects your ESP board to a cloud MQTT broker, and the Fantom Interio app subscribes via secure WebSocket (WSS).
Extra Library Needed
In Arduino IDE โ Sketch โ Include Library โ Manage Libraries:
- Search PubSubClient by Nick O'Leary โ Install
MQTT Firmware Code
Create a new sketch in Arduino IDE and paste this:
// Fantom Interio Smart Home โ MQTT Firmware
// For HTTPS deployments. ESP connects to HiveMQ broker via TCP.
// Fantom Interio app connects to same broker via WSS.
// Change only WiFi credentials and topic prefix below.
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include <PubSubClient.h>
#include <ArduinoJson.h>
// โโ CHANGE THESE โโ
const char* WIFI_SSID = "YourWiFiName";
const char* WIFI_PASS = "YourWiFiPassword";
const char* TOPIC_PREFIX = "fantom/devices";
// โโ That's it! โโ
const char* MQTT_SERVER = "broker.hivemq.com";
const int MQTT_PORT = 1883;
struct DevicePin { const char* id; uint8_t pin; bool activeLow; };
#ifdef ESP8266
// ESP8266 pin mapping โ D1(5), D2(4), D5(14), D6(12)
const DevicePin DEVICES[] = {
{ "d1", 5, true }, // Living Room Light
{ "d4", 4, true }, // Bedroom Light
{ "d8", 14, true }, // Kitchen Light
{ "d2", 12, true }, // Fan or AC
};
#else
// ESP32 pin mapping โ GPIO 2, 4, 5, 18
const DevicePin DEVICES[] = {
{ "d1", 2, true }, // Living Room Light
{ "d4", 4, true }, // Bedroom Light
{ "d8", 5, true }, // Kitchen Light
{ "d2", 18, true }, // Fan or AC
};
#endif
const int N = sizeof(DEVICES)/sizeof(DEVICES[0]);
WiFiClient espClient;
PubSubClient mqtt(espClient);
char clientId[24];
const DevicePin* findDev(const char* id) {
for (int i=0;i<N;i++) if (!strcmp(DEVICES[i].id,id)) return &DEVICES[i];
return nullptr;
}
void applyDev(const DevicePin* d, bool on) {
digitalWrite(d->pin, (d->activeLow?!on:on) ? HIGH : LOW);
}
void publishState(const char* id, bool on) {
char topic[64];
snprintf(topic, sizeof(topic), "%s/%s", TOPIC_PREFIX, id);
StaticJsonDocument<128> doc;
doc["deviceId"] = id;
doc["status"] = on;
doc["isOn"] = on;
doc["ts"] = millis();
char buf[128];
serializeJson(doc, buf);
mqtt.publish(topic, buf);
}
void onMqttMessage(char* topic, byte* payload, unsigned int length) {
StaticJsonDocument<256> doc;
if (deserializeJson(doc, payload, length)) return;
// Relay list request
if (doc["type"] == "relayList") {
char respTopic[64];
snprintf(respTopic, sizeof(respTopic), "%s/relayList/response", TOPIC_PREFIX);
StaticJsonDocument<512> r; r["type"] = "relayList";
JsonArray a = r.createNestedArray("relays");
for (int i=0;i<N;i++) {
JsonObject o = a.createNestedObject();
o["id"] = DEVICES[i].id;
o["name"] = DEVICES[i].id;
o["gpio"] = DEVICES[i].pin;
}
char buf[512]; serializeJson(r, buf);
mqtt.publish(respTopic, buf);
return;
}
// Relay test
if (doc["type"] == "relayTest") {
const char* relayId = doc["relay"] | "";
const char* mode = doc["mode"] | "ping";
const DevicePin* d = findDev(relayId);
char respTopic[64];
snprintf(respTopic, sizeof(respTopic), "%s/relayTest/response", TOPIC_PREFIX);
StaticJsonDocument<128> r; r["type"] = "relayTestResult"; r["relay"] = relayId;
if (d) {
if (strcmp(mode, "pulse") == 0) {
applyDev(d, true); delay(200); applyDev(d, false);
Serial.printf("[TEST] Pulsed %s\n", relayId);
}
r["status"] = "ok";
} else {
r["status"] = "error";
}
char buf[128]; serializeJson(r, buf);
mqtt.publish(respTopic, buf);
return;
}
// Device command (from Fantom Interio app)
const char* action = doc["action"] | "";
// Extract deviceId from topic: fantom/devices/{id}/command โ {id}
String t = String(topic);
int lastSlash = t.lastIndexOf('/');
int prevSlash = t.lastIndexOf('/', lastSlash - 1);
String devId = t.substring(prevSlash + 1, lastSlash);
const DevicePin* d = findDev(devId.c_str());
if (!d) return;
bool on = (strcmp(action, "on") == 0);
if (strcmp(action, "off") == 0) on = false;
applyDev(d, on);
publishState(d->id, on);
Serial.printf("[MQTT] %s โ %s\n", d->id, on ? "ON" : "OFF");
}
void mqttReconnect() {
while (!mqtt.connected()) {
Serial.print("[MQTT] Connecting...");
if (mqtt.connect(clientId)) {
Serial.println(" OK!");
// Subscribe to command topics and special topics
char subTopic[64];
for (int i=0;i<N;i++) {
snprintf(subTopic, sizeof(subTopic), "%s/%s/command", TOPIC_PREFIX, DEVICES[i].id);
mqtt.subscribe(subTopic);
}
// Subscribe to relay test/list topics
char relayListTopic[64], relayTestTopic[64];
snprintf(relayListTopic, sizeof(relayListTopic), "%s/relayList", TOPIC_PREFIX);
snprintf(relayTestTopic, sizeof(relayTestTopic), "%s/relayTest", TOPIC_PREFIX);
mqtt.subscribe(relayListTopic);
mqtt.subscribe(relayTestTopic);
} else {
Serial.printf(" failed (%d), retrying in 3s\n", mqtt.state());
delay(3000);
}
}
}
void setup() {
Serial.begin(115200); delay(200);
for (int i=0;i<N;i++) { pinMode(DEVICES[i].pin, OUTPUT); applyDev(&DEVICES[i], false); }
snprintf(clientId, sizeof(clientId), "fantom-%08X", (uint32_t)ESP.getEfuseMac());
Serial.print("[WiFi] Connecting");
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); }
Serial.println(" Done!");
Serial.print("IP:"); Serial.println(WiFi.localIP());
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setCallback(onMqttMessage);
mqtt.setBufferSize(512);
WiFi.setSleep(false);
}
void loop() {
if (!mqtt.connected()) mqttReconnect();
mqtt.loop();
}
Upload & Test
Same as Step 4 โ select your board in Arduino IDE, upload, open Serial Monitor.
You should see:
[WiFi] Connecting... Done!
IP:192.168.1.42
[MQTT] Connecting... OK!
Then in the Fantom Interio app:
- Go to Smart Home Hub โ Connections โ ESP32
- Click ๐ Smart Connect (on HTTPS it auto-selects MQTT bridge)
- Make sure the Topic Prefix matches:
fantom/devices - Toggle a device โ relay clicks!
Which Firmware Should I Use?
| Scenario | Firmware | Why |
|---|---|---|
Local development (localhost:3000) |
WebSocket (Step 3) | Direct connection, no internet needed |
| HTTPS deployment (Vercel, etc.) | MQTT (this section) | Browser can't use ws:// on HTTPS pages |
| Both local and cloud | Upload MQTT firmware | Works everywhere โ MQTT broker bridges the gap |