Files
espdht/esp32c3-dht-wifi.ino
T
2026-08-13 20:58:07 +00:00

291 lines
7.9 KiB
Arduino
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>
// ----------- WiFi -----------
static const char* WIFI_SSID = "SSID";
static const char* WIFI_PASS = "PASS";
// ----------- DHT -----------
static const int DHT_PIN = 10; // GPIO for DHT DATA
static const int DHT_TYPE = DHT22; // DHT11 / DHT22 / DHT12?
DHT dht(DHT_PIN, DHT_TYPE);
// ----------- Server -----------
WebServer server(80);
// ----------- Reading interval -----------
static const unsigned long READ_MS = 2000;
unsigned long lastRead = 0;
// ----------- Latest values -----------
float latestH = NAN;
float latestT_F = NAN;
String htmlPage() {
return R"HTML(
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>ESP32 DHT</title>
<style>
body { font-family: system-ui, Arial; margin: 2rem; }
.card { padding: 1rem 1.2rem; border: 1px solid #ddd; border-radius: 12px; max-width: 720px; }
.val { font-size: 2rem; margin: 0.2rem 0; }
.small { color: #555; font-size: 0.9rem; }
.row { display: flex; gap: 2rem; flex-wrap: wrap; }
.box { flex: 1; min-width: 240px; }
canvas { width: 100%; height: 80%; background: #d3d3d3; border: 1px solid #eee; border-radius: 10px; }
.legend { display: flex; gap: 1rem; align-items: center; margin-top: 0.5rem; }
.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 0.35rem; }
.btnbar { margin-top: 0.8rem; display: flex; gap: 0.6rem; flex-wrap: wrap; }
button { padding: 0.5rem 0.8rem; border-radius: 10px; border: 1px solid #ddd; background: #fff; cursor: pointer; }
</style>
</head>
<body>
<div class="card">
<h2>ESP32 DHT</h2>
<div class="small">Updates every 2 seconds • Last ~43,200 Samples Max • ~24 Hours</div>
<div class="row">
<div class="box">
<p><b>Temperature:</b></p>
<p class="val" id="temp">--</p>
</div>
<div class="box">
<p><b>Humidity:</b></p>
<p class="val" id="hum">--</p>
</div>
</div>
<canvas id="chart" width="800" height="600"></canvas>
<div class="legend">
<div><span class="dot" style="background:#e53935"></span>Temp (°F)</div>
<div><span class="dot" style="background:#1e88e5"></span>Humidity (%)</div>
</div>
<div class="btnbar">
<button onclick="clearData()">Clear chart</button>
</div>
</div>
<script>
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
// Keep separate series; chart shares the x-axis (time)
const MAX_POINTS = 43200;
let tempSeries = []; // {x, v}
let humSeries = []; // {x, v}
function resizeForDPR() {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = Math.round(rect.width * dpr);
canvas.height = Math.round(rect.height * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // draw in CSS pixels
}
window.addEventListener('resize', resizeForDPR);
resizeForDPR();
function draw() {
const w = canvas.getBoundingClientRect().width;
const h = canvas.getBoundingClientRect().height;
ctx.clearRect(0, 0, w, h);
// Background grid
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, w, h);
ctx.strokeStyle = '#eee';
ctx.lineWidth = 1;
const gridLines = 5;
for (let i = 1; i < gridLines; i++) {
const y = (h * i) / gridLines;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(w, y);
ctx.stroke();
}
// If no data, stop
if (tempSeries.length === 0 && humSeries.length === 0) return;
// Compute ranges (ignore missing nulls)
const allTemp = tempSeries.map(p => p.v).filter(v => Number.isFinite(v));
const allHum = humSeries.map(p => p.v).filter(v => Number.isFinite(v));
const tempMin = allTemp.length ? Math.min(...allTemp) : 0;
const tempMax = allTemp.length ? Math.max(...allTemp) : 1;
const humMin = allHum.length ? Math.min(...allHum) : 0;
const humMax = allHum.length ? Math.max(...allHum) : 1;
// Avoid flat-line divisions
const tempSpan = (tempMax - tempMin) || 1;
const humSpan = (humMax - humMin) || 1;
// x scale: index across MAX_POINTS window
const count = Math.max(tempSeries.length, humSeries.length);
const xForIndex = (i) => {
if (count <= 1) return 0;
return (w * i) / (count - 1);
};
function plot(series, color, min, span) {
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i < series.length; i++) {
const v = series[i].v;
if (!Number.isFinite(v)) continue;
const x = xForIndex(i);
const y = h - ((v - min) / span) * (h - 20) - 10; // padding
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
// Draw both series
// (We scale each series to its own min/max so you always get a visible line.)
plot(tempSeries, '#e53935', tempMin, tempSpan);
plot(humSeries, '#1e88e5', humMin, humSpan);
// Labels (min/max)
ctx.fillStyle = '#333';
ctx.font = '12px system-ui, Arial';
ctx.fillText(`Temp range: ${tempMin.toFixed(1)}${tempMax.toFixed(1)} °F`, 10, 16);
ctx.fillText(`Hum range: ${humMin.toFixed(1)}${humMax.toFixed(1)} %`, 10, 32);
}
function pushPoint(series, value) {
if (Number.isFinite(value)) {
series.push(value);
while (series.length > MAX_POINTS) series.shift();
} else {
// Keep time alignment by pushing NaN placeholder only if you want strict alignment.
// Here we just skip.
}
}
function clearData() {
tempSeries = [];
humSeries = [];
draw();
}
async function refresh() {
try {
const res = await fetch('/data?nocache=' + Date.now());
const j = await res.json();
// Update numbers
if (j.t_f === null) document.getElementById('temp').textContent = '--';
else document.getElementById('temp').textContent = j.t_f.toFixed(1) + ' °F';
if (j.h === null) document.getElementById('hum').textContent = '--';
else document.getElementById('hum').textContent = j.h.toFixed(1) + ' %';
// Update series + chart
if (j.t_f !== null) tempSeries.push({x: Date.now(), v: j.t_f});
if (j.h !== null) humSeries.push({x: Date.now(), v: j.h});
while (tempSeries.length > MAX_POINTS) tempSeries.shift();
while (humSeries.length > MAX_POINTS) humSeries.shift();
draw();
} catch (e) {
// ignore fetch errors
}
}
refresh();
setInterval(refresh, 2000);
</script>
</body>
</html>
)HTML";
}
void handleRoot() {
server.send(200, "text/html", htmlPage());
}
void handleData() {
// Convert NaN to null for easy JSON handling
float h = latestH;
float t = latestT_F;
String json = "{";
json += "\"h\": " + (isnan(h) ? String("null") : String(h, 1)) + ",";
json += "\"t_f\": " + (isnan(t) ? String("null") : String(t, 1));
json += "}";
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
delay(200);
Serial.println("ESP32-C3 DHT11 starting...");
dht.begin();
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println();
Serial.print("Connected. IP address: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/data", handleData);
server.begin();
Serial.println("HTTP server started.");
}
void loop() {
// keep server responsive
server.handleClient();
unsigned long now = millis();
if (now - lastRead < READ_MS) return;
lastRead = now;
float h = dht.readHumidity();
float tC = dht.readTemperature(); // Celsius
if (isnan(h) || isnan(tC)) {
Serial.println("DHT read failed (check wiring/pull-up).");
latestH = NAN;
latestT_F = NAN;
return;
}
latestH = h;
latestT_F = dht.convertCtoF(tC);
Serial.print("Humidity: ");
Serial.print(latestH, 1);
Serial.print(" %\tTemp: ");
Serial.print(latestT_F, 1);
Serial.println(" F");
Serial.print("Connected. IP address: ");
Serial.println(WiFi.localIP());
}