Generating PDF…

Preparing…
← Back

🌡️ IoT Weather Station

ESP-01 + DHT11 → Web Page & Cloud Dashboard

DE6417 — Microcontrollers 2 · Week 9

Today's Agenda

PartTopic
1Quick Recap from Week 8 — interactive review
2Meet the DHT11 — temperature & humidity sensor
3Combining ESP-01 + DHT11 on one Arduino
4🌐 Project A: Serve a live web page on the LAN
5☁️ Project B: Push readings to ThingSpeak
6🚀 Stretch goals — pick a challenge
7Common pitfalls & summary
🎯 Today's outcome: By the end of class you'll have a working IoT weather station that both serves its own web page and publishes data to the cloud.

Part 1: Quick Recap

Where we left off in Week 8

What we built last week

  • Wired ESP-01 to Arduino Uno using SoftwareSerial on pins D2/D3
  • Added a voltage divider on the ESP RX line (5V → 3.3V)
  • Held CH_PD high; ESP is a 3.3V module
  • Set the baud rate to 9600 with AT+UART_DEF
  • Talked to the ESP using AT commands
  • Connected to NZSE Wi-Fi with AT+CWMODE=1 + AT+CWJAP
  • Wrote a sendATCommand() helper to keep code clean
✅ You should already be able to: upload a sketch, see WIFI GOT IP, and read the assigned IP from AT+CIFSR.
#include <SoftwareSerial.h>
SoftwareSerial esp(2, 3);   // RX, TX

String sendATCommand(String cmd, int timeout) {
  String response = "";
  esp.println(cmd);
  long t = millis();
  while ((millis() - t) < timeout) {
    while (esp.available()) {
      response += (char)esp.read();
    }
  }
  Serial.println(response);
  return response;
}

void setup() {
  Serial.begin(9600);
  esp.begin(9600);          // ALWAYS 9600 with SoftwareSerial!
  delay(2000);

  sendATCommand("AT", 1000);
  sendATCommand("AT+CWMODE=1", 1000);
  sendATCommand("AT+CWJAP=\"NZSE-Student\","
                "\"password\"", 10000);
  sendATCommand("AT+CIFSR", 2000);
}

void loop() {}

Recap Exercise — Test Yourself

Fill in each blank with the correct AT command or value. Press Enter or click Check.

esp.println(" ");
esp.begin( );
💡 Stuck? Open the floating 📋 AT Cheat Sheet button (visible from Project A onwards) for a full command reference.

Now we add a sensor 🌡️

Connecting to Wi-Fi is fun, but an IoT device only matters when it's measuring something real.

Today we add a DHT11 temperature & humidity sensor and publish its readings two different ways.

🌐 Project A

ESP-01 hosts its own web page.
Open the IP in any browser on the same Wi-Fi → see live readings.

☁️ Project B

Push readings to ThingSpeak.
Get an automatic chart from anywhere in the world.

Part 2: The DHT11 Sensor

Temperature & humidity in one tiny package

What is the DHT11?

A cheap, beginner-friendly digital sensor that measures both temperature and relative humidity on a single data pin.

SpecValue
Temperature range0 – 50 °C (±2 °C)
Humidity range20 – 90 % RH (±5 %)
Supply voltage3.3 V – 5 V
Sample rate1 reading every ~2 s
ProtocolSingle-wire, 40-bit frame + checksum
Cost≈ $1–$3
⚠️ Sample rate limit: Reading faster than ~once every 2 s returns stale data or NaN. We'll use a non-blocking 5-second timer in our project.
📚 Already covered: You met the DHT11 in Microcontrollers 1. The full tutorial with photos & library steps is available at:
https://s1.rezafara.com/Microcontroller_1/sensors/DHT11.html

Why the DHT11 is great for IoT

  • One data wire — saves Arduino pins (we need them for the ESP-01)
  • Library hides the tricky single-wire timing
  • Reads in °C/°F and % directly — no scaling math
  • Tolerates 5 V, so it shares the same supply as the Arduino
📈 Want better accuracy? The DHT22 is pin-compatible: ±0.5 °C, ±2 %, but costs more. Drop-in replacement — change DHTTYPE in the code and you're done.

DHT11 Pinout & Wiring

Two flavours you'll see in the lab:

  • Blue 4-pin module — bare sensor. You must add a 10 kΩ pull-up between DATA and VCC.
  • Blue/green 3-pin breakout — has the pull-up resistor already on the PCB. Just wire it up.
DHT11 PinWireArduino Uno
VCCred5V
DATAyellowD4
NC (4-pin only)not connected
GNDblackGND
Why D4? D2 and D3 are already taken by SoftwareSerial talking to the ESP-01. D4 is the next free digital pin.
DHT11 grid + thermistor VCC DATA GND → 5V → D4 → GND 10kΩ 10 kΩ pull-up (4-pin only)

3-pin breakouts already include the pull-up resistor.

Installing the DHT Library

The DHT11 single-wire protocol involves microsecond timing. Don't write that yourself — use Adafruit's library.

Arduino IDE

  1. Sketch → Include Library → Manage Libraries…
  2. Search "DHT sensor library" → install the one by Adafruit
  3. You'll be prompted to also install Adafruit Unified Sensor — say yes

PlatformIO

Add to platformio.ini:

[env:uno]
platform  = atmelavr
board     = uno
framework = arduino
monitor_speed = 9600
lib_deps =
  adafruit/DHT sensor library@^1.4.6
  adafruit/Adafruit Unified Sensor@^1.1.14
✅ How to know it worked: after installing, in the IDE you should be able to use File → Examples → DHT sensor library → DHTtester. If that compiles, the library is installed correctly.
⚠️ Two common mistakes:
  • Forgetting to also install Adafruit Unified Sensor → compiler error Adafruit_Sensor.h: No such file or directory
  • Picking the wrong library (search returns several "DHT" libraries — use the one by Adafruit)
📖 Reference: The Microcontrollers 1 tutorial walks through every install step with screenshots — keep it open in another tab if you get stuck.

Smoke-Test: Read the DHT11

Before we touch the ESP-01 again, prove the DHT11 is wired correctly with this minimal sketch:

  1. Disconnect the ESP-01 (less to debug).
  2. Wire DHT11: VCC→5V, GND→GND, DATA→D4.
  3. Upload the sketch on the right.
  4. Open Serial Monitor at 9600.
  5. You should see a fresh reading every 2 s.
Expected output:
DHT11 smoke test
Humidity: 56.0 %  Temperature: 24.3 *C
Humidity: 56.0 %  Temperature: 24.4 *C
Humidity: 57.0 %  Temperature: 24.4 *C
Got NaN? Check the data wire is on D4 (not D2/D3) and that VCC is on 5V — the most common cause is loose breadboard contact.
#include <DHT.h>

#define DHTPIN  4          // DATA pin → Arduino D4
#define DHTTYPE DHT11      // sensor model

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);
  Serial.println("DHT11 smoke test");
  dht.begin();
}

void loop() {
  // DHT11 needs >= 2 s between reads
  delay(2000);

  float h = dht.readHumidity();
  float t = dht.readTemperature();   // °C

  // Library returns NaN on bad reads — always check!
  if (isnan(h) || isnan(t)) {
    Serial.println("Sensor read failed");
    return;
  }

  Serial.print("Humidity: ");    Serial.print(h);
  Serial.print(" %  Temperature: "); Serial.print(t);
  Serial.println(" *C");
}

Part 3: Putting Them Together

ESP-01 + DHT11 on one Arduino

The Combined Circuit

Reconnect the ESP-01 from week 8 and add the DHT11. Here is the full pin budget:

Arduino PinGoes toNotes
D2ESP-01 TXArduino RX (SoftwareSerial)
D3ESP-01 RXThrough 1k/2k voltage divider (5V → 3.3V)
D4DHT11 DATANew this week
3.3VESP VCC + CH_PDUse external supply if Uno's 3.3V brown-outs
5VDHT11 VCCDHT11 is 5V tolerant
GNDCommon groundESP & DHT & Arduino all share GND
⚠️ Power budget: The Uno's onboard 3.3V regulator can sag when the ESP transmits. If you see random resets, power the ESP from a separate 3.3V bench supply (or a step-down off the 5V rail) and keep grounds tied together.
Arduino Uno D2 D3 D4 3V3 5V GND ESP-01 TX RX VCC CH_PD GND DHT11 VCC DATA GND ÷ divider Wires: signals · 3V3 · 5V · GND. ÷ divider only on Arduino TX → ESP RX.

Read DHT Without Blocking Wi-Fi

If we just delay(5000) between sensor reads, the ESP-01 stops responding for 5 seconds — incoming HTTP requests get dropped.

Use the millis() pattern instead so the loop keeps spinning fast enough to service the ESP serial buffer.

The pattern:
  1. Remember when we last took a reading.
  2. Each loop(), ask: "is it time yet?"
  3. If yes — read DHT and update g_temp / g_hum.
  4. If no — fall through and check the ESP for incoming data.
⚠️ Sample rate: Keep SAMPLE_MS ≥ 2000. Faster than that and the DHT11 returns NaN.
#include <SoftwareSerial.h>
#include <DHT.h>

SoftwareSerial esp(2, 3);
#define DHTPIN  4
DHT dht(DHTPIN, DHT11);

float g_temp = 0.0;
float g_hum  = 0.0;

const unsigned long SAMPLE_MS = 5000;
unsigned long lastSample = 0;

void readSensor() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  if (!isnan(h) && !isnan(t)) {
    g_hum  = h;
    g_temp = t;
    Serial.print("DHT  T=");  Serial.print(t);
    Serial.print("C  H=");    Serial.print(h);
    Serial.println("%");
  }
}

void loop() {
  unsigned long now = millis();
  if (now - lastSample >= SAMPLE_MS) {
    lastSample = now;
    readSensor();
  }

  // ... handle incoming ESP data here ...
}

setup() — Boot Sequence

Everything that happens once, in order:

Serial.begin(9600)
USB debug output
dht.begin()
Init the DHT11 driver
esp.begin(9600)
SoftwareSerial to ESP-01
AT  ·  AT+CWMODE=1
Sanity check + Station mode
AT+CWJAP="ssid","pw"
Join NZSE Wi-Fi (~5–10 s)
AT+CIFSR
Print our IP
void setup() {
  Serial.begin(9600);
  Serial.println(F("Weather Station booting..."));

  // 1. Sensor
  dht.begin();
  delay(500);

  // 2. ESP-01 serial link
  esp.begin(9600);
  esp.setTimeout(2000);
  delay(2000);                  // ESP boot delay

  // 3. Sanity check
  sendATCommand("AT", 1000);
  sendATCommand("AT+CWMODE=1", 1000);

  // 4. Join the Wi-Fi
  String join = "AT+CWJAP=\"" + ssid + "\",\""
              + password + "\"";
  String r = sendATCommand(join, 10000);
  if (r.indexOf("OK") == -1) {
    Serial.println(F("WiFi join FAILED."));
    while (1);                  // halt
  }

  // 5. Show our IP
  sendATCommand("AT+CIFSR", 2000);

  // 6. Project A: open a TCP server
  sendATCommand("AT+CIPMUX=1", 1000);
  sendATCommand("AT+CIPSERVER=1,80", 1000);
  Serial.println(F("Server ready on port 80"));
}

Part 4: Project A

🌐 Serve a Web Page on the LAN

How it works

The ESP-01 becomes a tiny HTTP server. Anyone on the same Wi-Fi can open http://<ESP-IP> in a browser and the Arduino replies with a freshly-built HTML page showing the latest DHT11 reading.

  • You are the server. The phone/laptop is the client.
  • Communication is plain TCP on port 80 — exactly like a real web server.
  • The ESP gives you a hint when a request arrives: +IPD,<id>,<len>:<data>
📨 Anatomy of +IPD:
+IPD,0,328:GET / HTTP/1.1\r\nHost: 192.168.1.42…
→ connection ID 0, 328 bytes follow, then the raw HTTP request.
📱 Phone opens
http://192.168.1.42
Router routes TCP/80 packet
to the ESP-01
ESP forwards
+IPD,0,…:GET / …
over SoftwareSerial
Arduino reads DHT,
builds HTML
AT+CIPSEND=0,len
send response
AT+CIPCLOSE=0
browser shows page

Bring up the TCP server

After joining Wi-Fi, two more commands turn the ESP into a TCP server:

CommandWhy
AT+CIPMUX=1Allow several simultaneous connections (required before CIPSERVER).
AT+CIPSERVER=1,801 = start server. 80 = port (HTTP).
AT+CIPMUX=1
OK
AT+CIPSERVER=1,80
OK
// Browser opens http://192.168.1.42 …
+IPD,0,328:GET / HTTP/1.1
Host: 192.168.1.42
User-Agent: Mozilla/5.0 …

Detecting an incoming request

// In loop() — non-blocking
if (esp.available()) {
  if (esp.find("+IPD,")) {
    int id = esp.read() - '0';   // ASCII trick
    esp.find(":");                // skip the length

    // Read whatever the browser sent
    String req = esp.readStringUntil('\r');
    Serial.print("Request: ");
    Serial.println(req);

    // Build & send the page
    sendPage(id);

    // Close the connection
    esp.print("AT+CIPCLOSE=");
    esp.println(id);
  }
}
💡 ASCII trick: esp.read() - '0' converts the digit character '0'..'9' into its integer value 0..9 — handy for parsing the connection ID.

Building the HTTP Response

A valid HTTP response has three parts:

  1. Status lineHTTP/1.1 200 OK
  2. Headers — at least Content-Type: text/html + a blank line
  3. Body — the actual HTML
⚠️ Two gotchas:
  • Every line ends with \r\n (CRLF) — a single \n won't work.
  • AT+CIPSEND=id,len needs the exact byte count of what follows. Compute page.length(); off-by-one and the ESP hangs waiting for more bytes.
🎨 Auto-refresh: the meta tag <meta http-equiv="refresh" content="5"> tells the browser to reload every 5 seconds — instant live dashboard, zero JavaScript.
String buildPage() {
  String s;
  s += F("<!DOCTYPE html><html><head>");
  s += F("<meta http-equiv='refresh' content='5'>");
  s += F("<title>Weather</title>");
  s += F("<style>body{font-family:sans-serif;"
         "text-align:center;background:#222;color:#eee}"
         "h1{font-size:4em;margin:20px}</style>");
  s += F("</head><body>");
  s += F("<h1>🌡️ ");
  s += String(g_temp, 1);  s += F(" °C</h1>");
  s += F("<h1>💧 ");
  s += String(g_hum,  0);  s += F(" %</h1>");
  s += F("<p>Auto-refresh every 5 s</p>");
  s += F("</body></html>");
  return s;
}

void sendPage(int id) {
  String body = buildPage();
  String hdr  = "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/html\r\n"
                "Connection: close\r\n\r\n";
  String full = hdr + body;

  esp.print("AT+CIPSEND=");
  esp.print(id);  esp.print(",");
  esp.println(full.length());      // <— byte count!
  delay(50);
  if (esp.find(">")) {              // wait for ESP prompt
    esp.print(full);                 // raw response
  }
}

Full Sketch — Project A

Glue it all together. The whole weather-station server fits in < 100 lines.

✅ Build & verify:
  1. Set ssid + password.
  2. Upload sketch.
  3. Open Serial Monitor @9600.
  4. Note the IP after +CIFSR:STAIP,….
  5. On your phone (same Wi-Fi) browse to that IP.
  6. Watch the page auto-refresh every 5 s.
📁 Annotations →
  1. Globals + helper
  2. setup(): boot ESP, join Wi-Fi, start server
  3. loop(): periodic DHT read + handle +IPD
  4. buildPage() / sendPage()
// ── 1. Globals ──────────────────────────
#include <SoftwareSerial.h>
#include <DHT.h>

SoftwareSerial esp(2, 3);
DHT dht(4, DHT11);

String ssid     = "NZSE-Student";
String password = "ASK_INSTRUCTOR";

float g_temp = 0, g_hum = 0;
unsigned long lastSample = 0;

String sendATCommand(String cmd, int t) {
  String r; esp.println(cmd);
  long start = millis();
  while (millis() - start < t) {
    while (esp.available()) r += (char)esp.read();
  }
  Serial.println(r);
  return r;
}

// ── 2. setup() ──────────────────────────
void setup() {
  Serial.begin(9600); esp.begin(9600); dht.begin();
  delay(2000);
  sendATCommand("AT", 1000);
  sendATCommand("AT+CWMODE=1", 1000);
  sendATCommand("AT+CWJAP=\""+ssid+"\",\""+password+"\"", 10000);
  sendATCommand("AT+CIFSR", 2000);
  sendATCommand("AT+CIPMUX=1", 1000);
  sendATCommand("AT+CIPSERVER=1,80", 1000);
}

// ── 3. loop() ───────────────────────────
void loop() {
  if (millis() - lastSample >= 5000) {
    lastSample = millis();
    float h = dht.readHumidity(), t = dht.readTemperature();
    if (!isnan(h) && !isnan(t)) { g_hum = h; g_temp = t; }
  }

  if (esp.available() && esp.find("+IPD,")) {
    int id = esp.read() - '0';
    esp.find(":");
    esp.readStringUntil('\r');         // discard
    sendPage(id);
    esp.print("AT+CIPCLOSE="); esp.println(id);
  }
}

// ── 4. buildPage() / sendPage() above ───

What it looks like 🎉

http://192.168.1.42/
🌡️ 24.3 °C
💧 56 %
Auto-refresh every 5 s

↑ what your phone shows when it browses to the ESP IP

Troubleshooting

SymptomLikely cause
Browser hangs foreverCIPSEND length is wrong → ESP waits for more bytes. Compute full.length() after concatenation.
"Connection reset"Forgot AT+CIPCLOSE, or sent before the > prompt. Add esp.find(">").
Page shows nan °CDHT not wired correctly, or readSensor() ran before dht.begin().
No +IPD ever appearsForgot AT+CIPMUX=1 before CIPSERVER — the order matters.
ESP keeps resetting3.3V brown-out. Use a separate supply for the ESP.
Page is mojibakeWrong Content-Type or missing blank line between headers & body.

Part 5: Project B

☁️ Push to the Cloud (ThingSpeak)

What is ThingSpeak?

A free MathWorks service that stores time-series data sent over plain HTTP and draws it as live charts. Perfect for IoT learning — no app, no server, no JavaScript.

5-minute setup

  1. Sign up free at thingspeak.com (uses a MathWorks account).
  2. Channels → New Channel. Name it "Weather".
  3. Tick Field 1 → label it Temperature.
  4. Tick Field 2 → label it Humidity.
  5. Save → click the API Keys tab → copy the Write API Key.
✅ Outcome: a 16-character key like ABC123XYZQRS9876 that proves "this device is allowed to write to my channel".
🔓 Why HTTP, not HTTPS?
The ESP-01's stock AT firmware speaks HTTP on port 80 reliably; HTTPS support is patchy and slow. ThingSpeak accepts plain HTTP at api.thingspeak.com:80 — perfect for learning. (For real production: use the ESP8266 as a stand-alone microcontroller; that's lecture 9.)
⏱️ Rate limit: the free tier accepts one update per 15 seconds. We'll send every 20 s to stay safely under it.
🔒 Don't share: The Write API Key is a password. If you commit it to GitHub, anyone can spam your channel. Store it in secrets.h and add that file to .gitignore.

The Update URL

ThingSpeak accepts a single HTTP GET request that bundles every reading into the URL:

GET /update?api_key=YOUR_KEY&field1=24.3&field2=56 HTTP/1.1
Host: api.thingspeak.com
Connection: close

Note the blank line at the end — HTTP requires \r\n\r\n after the headers to mark "end of request".

FieldWhere it goes
api_keyFrom ThingSpeak — your Write API key
field1Temperature (any number)
field2Humidity
extraAdd &field3=… for more sensors later

The AT command flow

AT+CIPSTART="TCP","api.thingspeak.com",80
AT+CIPSEND=<len>
byte count of GET request
Send the GET line + headers + blank line
ThingSpeak replies
+IPD,N:<entry-id>
AT+CIPCLOSE
💡 Reply meaning: ThingSpeak responds with a single integer — the entry ID. Anything > 0 means "stored". A 0 means "rate-limited or invalid key".

postToThingSpeak()

A single helper that does the whole "open → measure length → send → close" dance.

Usage:
postToThingSpeak(g_temp, g_hum);
How often? Add a millis() timer in loop():
if (millis() - lastPost >= 20000) {
  lastPost = millis();
  postToThingSpeak(g_temp, g_hum);
}
→ one update every 20 s, well under the 15 s rate limit.
const String API_KEY = "YOUR_WRITE_API_KEY";
const String HOST    = "api.thingspeak.com";

void postToThingSpeak(float t, float h) {
  // 1. Build the request string
  String req  = "GET /update?api_key=" + API_KEY;
         req += "&field1=" + String(t, 1);
         req += "&field2=" + String(h, 0);
         req += " HTTP/1.1\r\n";
         req += "Host: " + HOST + "\r\n";
         req += "Connection: close\r\n\r\n";

  // 2. Open TCP connection to ThingSpeak
  esp.print("AT+CIPSTART=\"TCP\",\""); esp.print(HOST);
  esp.println("\",80");
  if (!esp.find("OK")) { Serial.println("CIPSTART fail"); return; }

  // 3. Tell the ESP exactly how many bytes follow
  esp.print("AT+CIPSEND=");
  esp.println(req.length());
  if (!esp.find(">")) { Serial.println("No > prompt"); return; }

  // 4. Send it!
  esp.print(req);

  // 5. Wait briefly for the reply, then close
  delay(1500);
  while (esp.available()) Serial.write(esp.read());
  esp.println("AT+CIPCLOSE");

  Serial.print("Posted T="); Serial.print(t);
  Serial.print(" H=");        Serial.println(h);
}

Your data, in the cloud 📈

Open your channel page on thingspeak.com — the chart updates automatically. Each postToThingSpeak() call adds a new point.

Field 1 — Temperature (°C) 26 23 20 last 8 minutes

What you can do next on ThingSpeak

  • Share publicly — make the channel public; send a link to anyone.
  • Export CSV — Channel → Data Import/Export → download the lot.
  • React — set a React rule: e.g. send an email when temperature > 30 °C.
  • MATLAB analysis — built-in MATLAB widgets to compute averages, detect spikes, etc.
  • Map it — add latitude/longitude fields → Channel Map shows pins.
🌍 You did real IoT. Your sensor data is now reachable from any phone in the world — without writing a single line of server code.

Part 6: Stretch Goals

Pick one — show it off next week 🚀

Choose Your Challenge

🌪️ Smart Cooling Alert

Add a relay or LED on D5. When the DHT11 reads above 28 °C, fire it on; below 26 °C, off. Hysteresis prevents flapping.

You'll practise: digitalWrite, hysteresis logic, combining sensor + actuator.

Tip: control a real 5V relay driving a 12V fan for maximum dramatic effect.

📲 Telegram Alert Bot

When temperature crosses a threshold, send a Telegram message to your phone via the Bot API. Use CIPSTART to api.telegram.org.

You'll practise: outbound HTTP, URL escaping, BotFather setup, chat IDs.

URL: /bot<TOKEN>/sendMessage?chat_id=<ID>&text=Temp+high

🏠 Multi-Room Network

Pair with a classmate. Each Uno posts to a different field on the same ThingSpeak channel — Room A on field 1, Room B on field 2. Compare them on one chart.

You'll practise: sharing API keys, coordinating timing, channel design.

Stagger: offset the two devices' posts by 10 s so neither hits the rate limit.

Part 7: Wrap-Up

Pitfalls & takeaways

Top 8 Pitfalls (Save Yourself an Hour)

SymptomCauseFix
DHT returns nanReading faster than 2 s, or wiring looseUse millis() with SAMPLE_MS >= 2000; reseat wires
Browser hangs on Project ACIPSEND length wrongUse full.length() after concatenation
Garbage chars in Serial MonitorESP at 115200, not 9600Run baud rate detector + AT+UART_DEF=9600,8,1,0,0 (week 8)
+IPD never appearsForgot AT+CIPMUX=1Send CIPMUX=1 before CIPSERVER=1,80
ESP keeps resetting3.3V brown-out under TX loadUse a separate 3.3V supply; tie GNDs
ThingSpeak returns 0Wrong API key, or rate-limitedRe-copy Write key; ensure ≥ 15 s between posts
HTTP response is mojibakeMissing CRLF or blank header lineEvery line ends \r\n; blank line between headers and body
Wi-Fi never connectsSSID has spaces / wrong caseQuote exactly: AT+CWJAP="My WiFi","pw"

Summary

Today you turned the ESP-01 from "thing that joins WiFi" into a complete IoT weather station.

🌡️ DHT11
  • Single-wire digital sensor
  • ≥ 2 s between reads
  • Always check isnan()
🌐 Project A
  • CIPMUX=1 + CIPSERVER=1,80
  • Parse +IPD,id,len:
  • Reply with HTTP/1.1, then close
☁️ Project B
  • CIPSTARTCIPSEND → write → close
  • GET /update?api_key=…
  • Rate limit 15 s
DHT.h isnan() millis() pattern CIPMUX=1 CIPSERVER CIPSTART CIPSEND CIPCLOSE +IPD api.thingspeak.com

Lab: Build Project A end-to-end. Stretch goals are optional — but extras count toward Assignment 2.

📋 ESP-01 Quick Reference

⚡ AT Commands

CommandWhat it does
ATTest — responds OK
AT+RSTReset the ESP module
AT+CWMODE=1Station mode (join WiFi)
AT+CWMODE=2AP mode (create hotspot)
AT+CWMODE=3Both Station + AP
AT+CWJAP="ssid","pw"Connect to a WiFi network
AT+CWSAP="name","pw",ch,encConfigure AP (name, password, channel, encryption)
AT+CIFSRGet IP address
AT+CIPMUX=1Enable multiple connections
AT+CIPSERVER=1,80Start TCP server on port 80
AT+CIPSEND=id,lenSend len bytes on connection id
AT+CIPCLOSE=idClose connection id

📨 Incoming Data Format

PatternMeaning
+IPD,id,len:dataReceived len bytes from connection id
OKCommand succeeded
ERRORCommand failed
SEND OKData sent successfully
>Ready for data (after CIPSEND)

🔧 ESP Serial Functions (Arduino)

FunctionWhat it does
esp.available()Returns number of bytes waiting to be read
esp.read()Reads one byte (as int). Use - '0' to convert ASCII digit
esp.readString()Reads all available data as a String (blocks until timeout)
esp.find("text")Reads until "text" found → true, or timeout → false. Consumes data!
esp.println("cmd")Sends command + newline to ESP
esp.print(data)Sends data without newline
esp.write(byte)Sends a single raw byte
esp.setTimeout(ms)Sets timeout for find/readString (default 1000ms)

💡 Common Patterns

PatternUse
int id = esp.read() - '0';Get connection ID as integer (ASCII trick)
if (esp.find("+IPD,"))Wait for incoming client request
String s = esp.readString();Read full request after +IPD detected
s.indexOf("/led/on")Check if request URL contains a path
while(millis()-t < 3000)Non-blocking timeout loop