Docs/ESP32_QUICKSTART.md
โ† All docs
MarkdownHTML Render

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

  1. Open the Fantom Interio app in Chrome or Edge
  2. Go to Smart Home Hub โ†’ Connections โ†’ ESP32 / ESP8266
  3. Expand โšก Browser Firmware Flasher (No Arduino IDE)
  4. Select your board type (ESP32 DevKit, ESP8266, S3, C3, etc.)
  5. Choose firmware type:
    • WebSocket โ€” for local development on localhost
    • MQTT โ€” for HTTPS deployments or if you have a firewall
  6. Enter your WiFi SSID and password
  7. Plug your ESP board into USB
  8. Click โšก Flash Firmware via USB
  9. Select your board from the browser's port picker
  10. 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

  1. Download Arduino IDE from arduino.cc/en/software and install it
  2. Open Arduino IDE โ†’ File โ†’ Preferences
  3. 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
    
  4. Click OK
  5. Go to Tools โ†’ Board โ†’ Boards Manager
    • Search esp8266 โ†’ Install
    • Search esp32 โ†’ Install
  6. 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

  1. Plug the board into your computer with the USB cable

  2. 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
  3. Tools โ†’ Port โ†’ pick the port with your board

  4. Click the Upload โ–ถ button

  5. 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!)

  1. Open Chrome or Edge โ†’ go to Fantom Interio
  2. Go to Smart Home Hub โ†’ click Connections tab
  3. Click ESP32 / ESP8266 to expand it
  4. 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:

  1. Go to Smart Home Hub โ†’ Connections โ†’ ESP32
  2. Click ๐Ÿš€ Smart Connect (on HTTPS it auto-selects MQTT bridge)
  3. Make sure the Topic Prefix matches: fantom/devices
  4. 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