Learning Goals 5 min
- Build the Cluster C capstone: a multi-sensor Weather Station that streams temperature, humidity and light to the Serial Monitor every 5 seconds in clean, spreadsheet-friendly CSV.
- Use the truth-table thinking from L02-19 to generate a single human-readable headline label ("Hot & humid", "Cool & bright", "Stormy") instead of three separate numbers.
- Run the station for at least 10 minutes, paste the CSV into a spreadsheet, and produce two charts: one for temperature over time, one for humidity over time. Your first real data product.
Warm-Up 10 min
Cluster C started with single-sensor lessons — TMP36, DHT11, LDR, soil, rain, piezo. Yesterday you combined two with boolean rules. Today the whole cluster comes together as one project: a Weather Station that watches three things at once, prints them to the Serial Monitor as CSV, and labels each reading with a friendly "headline" like a weather app would.
Predict the spec
What would you want your station to print every 5 seconds? Discuss with a neighbour for 2 minutes.
One reasonable spec
- CSV header on boot:
time_s,temp_C,humidity_pct,light_pct,headline - One line every 5 s:
10,27.4,72,42,Warm & humid - Headline is one of 4–6 short labels:
Cool & dim,Cool & bright,Warm & humid,Hot,Stormy,Comfortable. - NaN-safe: if a sensor fails, the row prints
?for that column and the rest of the row still goes out.
New Concept · Architecting a multi-sensor project 20 min
The architecture in three layers
For the first time we're writing a project big enough that organisation matters. Here's the three-layer mental model we'll keep using for the rest of L2 and beyond:
- Sensors layer — one helper per sensor:
readTempC(),readHumidity(),readLightPct(). Each helper returns a single number (or NaN on failure). No printing, no decisions, just reading. - Logic layer — takes the numbers, classifies them into booleans (
hot,humid,bright), then produces the headline label. - Output layer — prints the CSV row, drives any LEDs, optionally beeps a buzzer.
The main loop() just calls one function per layer. Below 30 lines for the main loop is the goal. Anyone glancing at the file should be able to read loop() as a recipe and know what the project does.
The CSV format and why
CSV (Comma-Separated Values) is the simplest way to get structured data out of an Arduino. Every modern spreadsheet imports it natively. Three rules:
- One header row at the top with column names.
- One data row per measurement, fields separated by commas.
- No trailing spaces in numeric columns — they confuse some import wizards.
Example output:
time_s,temp_C,humidity_pct,light_pct,headline 5,27.4,72,38,Warm & humid 10,27.5,71,41,Warm & humid 15,27.5,72,45,Warm & humid 20,27.6,74,49,Warm & humid
The headline rule (truth-table-style)
From the three booleans hot (T > 30 °C), humid (H > 75%) and bright (light > 70%) we can derive a single label. One reasonable rule:
| hot | humid | bright | Headline |
|---|---|---|---|
| yes | yes | — | Stormy |
| yes | no | yes | Hot & bright |
| yes | no | no | Hot & dim |
| no | yes | — | Warm & humid |
| no | no | yes | Cool & bright |
| no | no | no | Cool & dim |
Each row maps to a friendly two-word headline. Notice some rows merge — when hot && humid we don't care about brightness; both rows say "Stormy". Truth tables aren't scripture — collapse rows whenever the output is the same.
NaN-safe printing
If dht.readTemperature() returns NaN, we don't want our CSV row to read 5,nan,72,38,.... Print a ? instead — most spreadsheets treat it as a missing value rather than choking on the row:
if (isnan(t)) Serial.print("?");
else Serial.print(t, 1);
Serial.print(",");Worked Example · The full station 25 min
Step 1 — wiring (three blocks)
| Component | Pin |
|---|---|
| DHT11 module (data) | D2 |
| LDR voltage divider midpoint | A0 |
| (optional) RGB or single LED | D9 (PWM, with 220 Ω) |
Plus +5V and GND on each module. The DHT11 you've wired in L02-14; the LDR divider in L02-15. The third row is optional eye-candy: a single LED that brightens as light increases, or three LEDs for state.
Step 2 — the sketch (layered)
Save as weather-station-v1.ino:
// L02-20: Weather Station v1
// Reads DHT11 + LDR, prints CSV every 5 s with a headline label.
// Architecture: sensors layer · logic layer · output layer.
#include <DHT.h>
#define DHT_PIN 2
#define DHT_TYPE DHT11
const int LDR = A0;
DHT dht(DHT_PIN, DHT_TYPE);
// LDR auto-calibration (5 s on boot)
int ldrDark = 1023, ldrBright = 0;
const unsigned long LOG_MS = 5000;
unsigned long lastLog = 0;
// ---- SENSORS LAYER ----
float readTempC() { return dht.readTemperature(); }
float readHumidity() { return dht.readHumidity(); }
int readLightPct() {
int raw = analogRead(LDR);
int clipped = constrain(raw, ldrDark, ldrBright);
return map(clipped, ldrDark, ldrBright, 0, 100);
}
// ---- LOGIC LAYER ----
const char* headlineFor(float t, float h, int lightPct) {
if (isnan(t) || isnan(h)) return "sensor error";
bool hot = (t > 30);
bool humid = (h > 75);
bool bright = (lightPct > 70);
if (hot && humid) return "Stormy";
if (hot && bright) return "Hot & bright";
if (hot) return "Hot & dim";
if (humid) return "Warm & humid";
if (bright) return "Cool & bright";
return "Cool & dim";
}
// ---- OUTPUT LAYER ----
void printRow(float t, float h, int lightPct, const char* hl) {
Serial.print(millis() / 1000); Serial.print(",");
if (isnan(t)) Serial.print("?"); else Serial.print(t, 1);
Serial.print(",");
if (isnan(h)) Serial.print("?"); else Serial.print(h, 0);
Serial.print(",");
Serial.print(lightPct); Serial.print(",");
Serial.println(hl);
}
// ---- SETUP / LOOP ----
void calibrateLdr() {
Serial.println("# calibrating LDR (5 s)...");
unsigned long t0 = millis();
while (millis() - t0 < 5000) {
int r = analogRead(LDR);
if (r < ldrDark) ldrDark = r;
if (r > ldrBright) ldrBright = r;
}
if (ldrBright - ldrDark < 50) { ldrDark = 0; ldrBright = 1023; }
}
void setup() {
Serial.begin(9600);
dht.begin();
delay(500);
calibrateLdr();
Serial.println("time_s,temp_C,humidity_pct,light_pct,headline");
}
void loop() {
if (millis() - lastLog < LOG_MS) return;
lastLog = millis();
float t = readTempC();
float h = readHumidity();
int l = readLightPct();
const char* hl = headlineFor(t, h, l);
printRow(t, h, l, hl);
}Step 3 — upload and let it run
Open Serial Monitor. After the calibration line you should see clean CSV:
# calibrating LDR (5 s)... time_s,temp_C,humidity_pct,light_pct,headline 5,27.4,72,38,Warm & humid 10,27.5,71,41,Warm & humid 15,27.5,72,45,Warm & humid 20,27.6,74,49,Warm & humid
Step 4 — exercise each state
Run through every headline by manipulating sensors:
- Stormy — breathe heavily near the DHT11 for 30 s.
- Hot & bright — palm warmth on DHT + phone torch on LDR.
- Cool & dim — leave alone in a dim room.
- Cool & bright — leave alone in a brightly-lit room.
Each manipulation should produce the right headline within one log interval. If a state never appears, check the thresholds in headlineFor() and the calibrated LDR range.
Step 5 — collect 10 minutes, plot
Let the station run for 10 minutes. Then in the Serial Monitor: select all (Ctrl/Cmd+A) → copy → paste into a new Google Sheet or Excel. Drop the comment line at the top. Data → Split text to columns if needed.
Insert two charts:
- X = time_s, Y = temp_C — line chart titled "Temperature over time".
- X = time_s, Y = humidity_pct — line chart titled "Humidity over time".
You now have an actual data product: a one-page weather report with two charts and a label per row. With a screenshot of the charts and the CSV file, you'd have everything a science-fair judge wants to see.
Try It Yourself 20 min
Goal: Add an LED on D9 that brightens in proportion to the light percentage. (So your station has a quick visual indicator besides the Serial Monitor.) Use analogWrite(9, map(lightPct, 0, 100, 0, 255));
Hint
Add the pin setup in setup() (LED already defaults to OUTPUT after analogWrite, but be explicit), then in loop() right before printRow:
analogWrite(9, map(l, 0, 100, 0, 255));Goal: Add a fourth sensor — a TMP36 or thermistor — on A1. Print its °C in a new column. Compare against the DHT11's temperature. Useful when one sensor disagrees with the other — which one do you trust?
Hint
Add a fifth helper in the sensors layer:
float readTmp36() {
int raw = analogRead(A1);
float mV = raw * (5000.0 / 1023.0);
return (mV - 500.0) / 10.0;
}Add a column to the CSV header: time_s,temp_dht_C,humidity_pct,light_pct,temp_tmp36_C,headline. Update printRow. Spreadsheet plot: put both temperatures on the same chart with two lines.
Goal: Compute a discomfort index = humidity × max(0, temperature − 25). High when it's both hot and humid. Print it as a new column and update the headline logic to use the discomfort index instead of separate hot/humid thresholds.
Hint
float discomfort = h * max(0.0f, t - 25.0f);
// Threshold for headline: > 1000 ≈ "very uncomfortable"
// > 500 ≈ "uncomfortable"
// < 500 ≈ "comfortable"The advantage: a single number replaces two booleans. A 28 °C / 90%RH afternoon (discomfort = 270) ranks differently from 32 °C / 50%RH (discomfort = 350). Subtle real-world wisdom, encoded in one line.
Mini-Challenge · Ship the v1 15 min
The previous sections give you a working baseline. The mini-challenge is to make it ship-ready — meaning good enough to leave on your desk overnight and produce useful data in the morning. Pick at least three of these "polish" items and apply them:
- Top-of-file product spec. A 4–6 line comment that explains what the project does, what sensors it uses, what each pin does, and what the headline thresholds mean.
- All thresholds named. No magic numbers in
headlineFor. Promote 30, 75, 70 to named constants at the top. - Boot self-test. In
setup(), read each sensor once and print a one-line "sensor X: OK" or "sensor X: FAILED" report. Useful for debugging when something doesn't work. - Heartbeat LED. An LED on D13 that blinks once per log cycle — a quiet visual confirmation that the station is alive.
- Overflow-safe time column.
millis()wraps after 49 days; print hours+minutes instead of raw seconds for long runs.
It's ship-ready when:
- The top-of-file comment lets a stranger understand the project in 30 seconds.
- No
delay()longer than 200 ms in the main loop. - Booting the station shows a clear "all sensors OK" message.
- You can leave it running for 30 minutes unattended and come back to a clean, readable CSV.
Recap 5 min
Cluster C done. The Weather Station stitches the cluster's eight lessons into one project: temperature from L02-13/14, humidity from L02-14, light from L02-15, calibration from L02-09/15, the AND/OR rule-design from L02-19, the CSV pipeline from… well, today. The architecture lesson — sensors layer → logic layer → output layer — is the project shape we'll keep using for the rest of L2 (data logger, combination lock, weather v2). Tomorrow we widen the toolkit further with ultrasonic distance — Cluster D starts.
- Layered architecture
- Splitting a project into clear roles — sensors layer (reads), logic layer (decides), output layer (acts/prints). Keeps the main loop short and the helpers single-purpose.
- CSV (comma-separated values)
- A simple plain-text format for tabular data, one row per line, fields separated by commas. Imported by every spreadsheet on Earth.
- Header row
- The first line of a CSV listing column names — printed once in
setup()in our case, never repeated in the stream. - Headline label
- A short human-readable summary string ("Hot & humid", "Stormy") derived from the underlying numbers — gives a glance-able answer for non-engineers.
- Ship-ready
- The mindset of a finished product: tight spec comment, named constants, self-test, runs unattended. The bridge between "sketch" and "thing on your desk".
- Sensor self-test
- A small routine in
setup()that reads each sensor once and reports whether the read was sensible — catches a missing wire before the first data row goes out.
Homework 5 min
Run your station for 2 hours, write the report. Pick a 2-hour window — late afternoon and early evening are good in Malaysia (you'll see the temperature drop, humidity often rise) — and leave the Weather Station running with the Serial Monitor open. Then:
- Save the CSV to disk (paste into a file called
weather-log.csv). - In Sheets or Excel: chart temperature, humidity, light over time as three lines on one chart.
- Note the maximum and minimum of each.
- Count how many minutes each headline label appeared.
On paper, answer:
- Which headline appeared most often? Does that match how the air felt during those 2 hours?
- Did temperature and humidity move together (rise together, fall together) or oppositely? Why might that be?
- What single change to the sketch (a new sensor, a new label, a different threshold) would have made the data more useful?
Bring back next class:
- Your
weather-log.csvfile (on a phone, USB stick, or in a shared drive). - Your three-line chart.
- Your three written answers.
- Your
hw-l02-20.inosketch — congratulations, this is your first L2 capstone. Cluster D starts tomorrow.