Mintaki Haytam
Published

Wi-Fi Home Environment Monitor (XIAO ESP32S3 + SHT31)

No-solder Wi-Fi temp & humidity monitor using Seeed XIAO ESP32S3 + Grove SHT31. Live web UI and /json API you can open on your phone.

BeginnerFull instructions provided10 hours96
Wi-Fi Home Environment Monitor (XIAO ESP32S3 + SHT31)

Things used in this project

Hardware components

Seeed Studio XIAO ESP32S3 Sense
Seeed Studio XIAO ESP32S3 Sense
×1
Seeed Studio grove base for XIAO
×1
Seeed Studio Grove - Temp&Humi Sensor (SHT31)
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Code

Home Environment Monitor — Web UI + /json (XIAO ESP32S3 + Grove SHT31)

C/C++
Serves a local web page (/) and JSON API (/json) with temperature (°C) and humidity (%) from a Grove SHT31.
Board: Seeed XIAO ESP32S3 (+ Grove Shield). I2C pins: SDA=GPIO5 (D4), SCL=GPIO6 (D5).
Edit WIFI_SSID/WIFI_PASS before uploading; Serial @ 115200.
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include "Adafruit_SHT31.h"

// ---- Wi-Fi (EDIT THESE) ----
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASS";

// ---- I2C pins via Grove Shield (XIAO ESP32S3) ----
#define SDA_PIN 5   // D4
#define SCL_PIN 6   // D5

Adafruit_SHT31 sht31;
WebServer server(80);

// Optional smoothing + tiny calibration
const float ALPHA = 0.20f;    // 0.1 smoother, 0.3 snappier
const float TEMP_OFFSET = 0.0f;
const float RH_OFFSET   = 0.0f;

float tEMA = NAN, hEMA = NAN;
unsigned long lastRead = 0;
const unsigned long READ_MS = 2000;

String page() {
  String s = F(
    "<!doctype html><html><head><meta charset=utf-8>"
    "<meta name=viewport content='width=device-width,initial-scale=1'>"
    "<title>Home Environment Monitor</title>"
    "<style>body{font-family:system-ui,Segoe UI,Roboto,Arial;margin:24px;max-width:520px}"
    "h1{margin:0 0 12px;font-size:22px}"
    ".card{border:1px solid #ddd;border-radius:12px;padding:16px;margin:12px 0}"
    ".k{color:#666;font-size:14px}.v{font-size:22px;margin:6px 0 0}"
    ".grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}"
    ".small{color:#888;font-size:12px;margin-top:8px}</style></head><body>"
    "<h1>Home Environment Monitor</h1>"
    "<div class=grid>"
      "<div class=card><div class=k>Temperature</div><div id=t class=v>—</div></div>"
      "<div class=card><div class=k>Humidity</div><div id=h class=v>—</div></div>"
    "</div>"
    "<div class=small>Auto-refreshing. JSON at <code>/json</code>.</div>"
    "<script>"
    "async function refresh(){"
      "try{const r=await fetch('/json',{cache:'no-store'});"
      "if(!r.ok)throw 0; const j=await r.json();"
      "document.getElementById('t').textContent=(j.temperature).toFixed(2)+' °C';"
      "document.getElementById('h').textContent=(j.humidity).toFixed(1)+' %';"
      "}catch(e){}"
    "}"
    "refresh(); setInterval(refresh,2000);"
    "</script></body></html>"
  );
  return s;
}

void root() {
  server.sendHeader("Cache-Control", "no-store");
  server.send(200, "text/html", page());
}

void json() {
  String p = "{\"temperature\":";
  p += isnan(tEMA) ? "null" : String(tEMA, 2);
  p += ",\"humidity\":";
  p += isnan(hEMA) ? "null" : String(hEMA, 1);
  p += "}";
  server.sendHeader("Cache-Control", "no-store");
  server.send(200, "application/json", p);
}

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print("WiFi: connecting");
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.print("\nWiFi OK, IP: "); Serial.println(WiFi.localIP());
}

void setup() {
  Serial.begin(115200);
  delay(300);

  Wire.begin(SDA_PIN, SCL_PIN);
  delay(20);
  if (!sht31.begin(0x44) && !sht31.begin(0x45)) {
    Serial.println("SHT31 not found on 0x44/0x45. Check I2C port/cable.");
    while (1) delay(10);
  }

  connectWiFi();
  server.on("/", root);
  server.on("/json", json);
  server.begin();
}

void loop() {
  server.handleClient();

  if (millis() - lastRead >= READ_MS) {
    float t = sht31.readTemperature();
    float h = sht31.readHumidity();
    if (!isnan(t) && !isnan(h)) {
      // simple EMA smoothing and optional offsets
      t += TEMP_OFFSET; h += RH_OFFSET;
      if (isnan(tEMA)) { tEMA = t; hEMA = h; }
      else {
        tEMA = ALPHA * t + (1 - ALPHA) * tEMA;
        hEMA = ALPHA * h + (1 - ALPHA) * hEMA;
      }
      Serial.printf("Temp: %.2f C | RH: %.1f %%\n", tEMA, hEMA);
    }
    lastRead = millis();
  }
}

Credits

Mintaki Haytam
1 project • 0 followers

Comments