Learning Goals 5 min
- Wire two sensors at the same time (LDR + DHT11, or rain + soil) and design a rule that depends on both.
- Distinguish AND rules (both conditions must be true) from OR rules (either condition is enough), and choose the right one for each use case.
- Express the rule as a small truth table on paper before writing the code — then translate the truth table line-by-line into
if/else if.
Warm-Up 10 min
So far every sensor lesson has been single-sensor: read it, label it, act. Real products almost never look like that. A smart sprinkler doesn't water just because the soil is dry — it waters only if the soil is dry and it's not currently raining. A safety alarm doesn't fire just because there's motion — it fires if there's motion and the door was closed and the time is between 10 pm and 6 am. The interesting question is always the combination.
The four cases
Any two boolean inputs produce four possible combinations:
| A | B |
|---|---|
| false | false |
| false | true |
| true | false |
| true | true |
Your job, before any code, is to decide which of these four cases should trigger your output. That decision is the design of the project. Pick well and the code is trivial; pick badly and no amount of cleverness will save it.
New Concept · Truth tables → if statements 20 min
AND, OR, and NOT — re-grounded
A && B— "A AND B" — true only when BOTH inputs are true.A || B— "A OR B" — true when EITHER input is true.!A— "NOT A" — inverts: true becomes false, false becomes true.
We met these in L01-21 with two buttons. Today they meet sensors.
Two design patterns to keep separate
Pattern 1 — Permissive (OR): the alarm fires if either sensor agrees something is wrong. Errs on the side of false positives. Example: a fire alarm — fires if either smoke OR heat is detected.
Pattern 2 — Confirmatory (AND): the alarm fires only when both sensors agree. Errs on the side of false negatives. Example: a soil-watering decision — water if soil is dry AND it's not raining.
Neither is universally better. The choice depends on the cost of being wrong:
- If a false positive is cheap (an unnecessary text message) → use OR.
- If a false positive is expensive (a flood from an unnecessary sprinkler) → use AND.
Truth-table-first workflow
Before writing any code, draw the truth table. Imagine a smart-curtain rule: close the curtain when (light is bright) AND (room is warm). Two sensors, two outputs, one decision:
| Bright? | Warm? | Close curtain? |
|---|---|---|
| no | no | no |
| no | yes | no |
| yes | no | no |
| yes | yes | YES |
That table dictates the code, exactly:
if (bright && warm) closeCurtain();
else openCurtain();Two lines. The thinking happened before, not during. If a teammate later asks "why doesn't it close on a hot cloudy day?", you can point at the table.
OR example: heat-or-humidity warning
Same sensors, different rule. Warn me if it's hot OR if it's humid:
| Hot? | Humid? | Warn? |
|---|---|---|
| no | no | no |
| no | yes | YES |
| yes | no | YES |
| yes | yes | YES |
if (hot || humid) warn();
else relax();Worked Example · Smart sprinkler rule 20 min
For this lesson we'll use two sensors you wired in earlier lessons: the soil moisture probe from L02-16 and the rain sensor from L02-17. Goal: a tiny smart-sprinkler controller that fires (well, lights an LED to represent firing) only when the soil is dry and it's not raining.
Step 1 — wiring
| Sensor | Pin |
|---|---|
| Soil moisture (analog out) | A0 |
| Rain sensor (analog out) | A1 |
| Sprinkler LED (with 220 Ω) | D9 |
Both sensors also need +5 V and GND. Use the breadboard's power rails to keep the wiring clean.
Step 2 — the truth table
| Soil dry? | Raining? | Sprinkler on? |
|---|---|---|
| no | no | no — soil is wet enough |
| no | yes | no — soil is wet AND it's raining |
| yes | yes | no — don't waste water in the rain |
| yes | no | YES — fire sprinkler |
One row out of four triggers the output. Boolean expression: dry && !raining.
Step 3 — the sketch
Save as smart-sprinkler.ino:
// L02-19: Smart sprinkler — soil + rain combined
const int SOIL = A0;
const int RAIN = A1;
const int LED = 9;
// Thresholds (tune for your sensors)
const int SOIL_DRY = 800; // raw above this = dry
const int RAIN_WET = 700; // raw below this = raining
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int soilRaw = analogRead(SOIL);
int rainRaw = analogRead(RAIN);
bool dry = (soilRaw > SOIL_DRY);
bool raining = (rainRaw < RAIN_WET);
bool shouldWater = dry && !raining;
digitalWrite(LED, shouldWater ? HIGH : LOW);
Serial.print("soil="); Serial.print(soilRaw);
Serial.print(" ("); Serial.print(dry ? "dry" : "moist"); Serial.print(") ");
Serial.print("rain="); Serial.print(rainRaw);
Serial.print(" ("); Serial.print(raining ? "RAIN" : "dry"); Serial.print(") ");
Serial.println(shouldWater ? ">> WATER" : "(hold)");
delay(500);
}Notice how the bool variables make the rule readable. shouldWater = dry && !raining is almost English.
Step 4 — test all four cases
Walk through every row of the truth table by manipulating the sensors:
- Wet soil + no rain: probe in moist soil, plate dry. Expected: LED off, "(hold)".
- Wet soil + rain: probe in moist soil, drip water on rain plate. Expected: LED off, "(hold)".
- Dry soil + rain: probe in dry soil, drip water on rain plate. Expected: LED off, "(hold)".
- Dry soil + no rain: probe in dry soil, plate dry. Expected: LED on, ">> WATER".
If any case behaves wrong, check that case in the truth table and check the boolean expression matches it. The bug is always in the table-to-code translation — never in the wiring (well, unless it's the wiring).
Step 5 — what about three states?
The sketch as written turns the sprinkler on or off instantly. A real one would also have:
- A maximum on-time (turn off after 2 min even if soil is still dry — don't flood).
- A minimum off-time (don't fire again within 10 min — let the water soak in).
- A "recently rained" memory (if it rained in the last hour, don't fire even if soil is dry now).
Each of these adds a millis() gate or a state variable. They're great Try-It-Yourself extensions and the homework asks for one. The pattern of starting from a truth table doesn't change — the truth table just gets more columns.
Try It Yourself 20 min
Goal: Add a second LED that lights up "weather permitting" — when it's NOT raining (regardless of soil). This is your daily "hang the laundry?" indicator.
Hint
Add a green LED on D10:
digitalWrite(10, raining ? LOW : HIGH);Just a single-sensor branch — no AND needed. But it's a clean way to factor a project into independent rules.
Goal: Re-design the sprinkler with a different rule: fire if soil is bone dry (raw > 950, ignoring rain) OR if soil is dry AND no rain. That way an emergency-dry plant gets watered even in a drizzle. Draw the truth table first, then code.
Hint
You now need three booleans: boneDry, dry, raining. The rule is:
bool boneDry = (soilRaw > 950);
bool dry = (soilRaw > SOIL_DRY);
bool raining = (rainRaw < RAIN_WET);
bool shouldWater = boneDry || (dry && !raining);The parentheses around (dry && !raining) aren't strictly needed (AND binds tighter than OR) but make the intent obvious to a reader.
Goal: Add a "minimum off-time" of 30 seconds. Once the sprinkler turns off, it can't fire again for 30 s — even if conditions return to dry-and-no-rain. Prevents rapid on/off cycling.
Hint
Track the last time the sprinkler turned off:
unsigned long lastOff = 0;
bool wasOn = false;
// in loop, after computing shouldWater:
if (wasOn && !shouldWater) {
lastOff = millis(); // record the moment we turned off
}
bool inCooldown = (millis() - lastOff < 30000);
digitalWrite(LED, (shouldWater && !inCooldown) ? HIGH : LOW);
wasOn = shouldWater && !inCooldown;The truth table now has a third input: "was the sprinkler off for < 30 s?". Three inputs = 8 rows. Write that table out by hand if you want full clarity. As you suspected — the rules grow combinatorially.
Mini-Challenge · Comfortable-vs-uncomfortable monitor 15 min
Wire two sensors of your choice from earlier in Cluster C — say a DHT11 (T + H) and an LDR. Design a "room comfort" monitor with these output states:
- Green LED (D9): comfortable.
- Amber LED (D10): borderline.
- Red LED (D11): uncomfortable.
You decide the rules — but they MUST involve at least one AND and one OR. Draw the truth table first. Some possible inputs:
hot— temperature > 30 °C.humid— humidity > 75%.bright— LDR percent > 70.
Example rule set (you can make your own):
| hot | humid | bright | LED |
|---|---|---|---|
| no | no | — | green |
| yes | yes | — | red |
| yes | no | yes | amber |
| yes | no | no | amber |
| no | yes | — | amber |
It's done when:
- The truth table is on paper, with the exact rule for each row.
- The sketch translates the truth table line-by-line into
if/else if. - You can demonstrate each row by manipulating the sensors (hot+humid by breathing on them, dim by covering, etc.).
- Exactly one LED is on at any time.
Reveal one valid sketch (matching the example table)
#include <DHT.h>
const int LDR = A0;
const int GREEN = 9;
const int AMBER = 10;
const int RED = 11;
DHT dht(2, DHT11);
void setColour(int g, int a, int r) {
digitalWrite(GREEN, g);
digitalWrite(AMBER, a);
digitalWrite(RED, r);
}
void setup() {
pinMode(GREEN, OUTPUT);
pinMode(AMBER, OUTPUT);
pinMode(RED, OUTPUT);
Serial.begin(9600);
dht.begin();
}
void loop() {
float t = dht.readTemperature();
float h = dht.readHumidity();
int l = analogRead(LDR);
if (isnan(t) || isnan(h)) { delay(1000); return; }
bool hot = (t > 30);
bool humid = (h > 75);
// LDR: lower raw = darker, higher raw = brighter in our wiring
bool bright = (l > 600);
if (hot && humid) setColour(LOW, LOW, HIGH); // red
else if (hot || humid) setColour(LOW, HIGH, LOW); // amber
else setColour(HIGH, LOW, LOW); // green
Serial.print("T="); Serial.print(t, 1);
Serial.print(" H="); Serial.print(h, 0);
Serial.print(" L="); Serial.print(l);
Serial.println(hot && humid ? " -> RED" : (hot || humid) ? " -> AMBER" : " -> GREEN");
delay(1000);
}I collapsed several rows by using hot || humid as the amber test — strictly equivalent to listing each row, because in the table every row with at least one "yes" in hot or humid maps to amber or red. Drawing the table first told us this collapse was safe. Without the table, you might write four separate if-branches and miss a row.
Recap 5 min
Real projects almost always combine multiple sensors. The hard part isn't reading them — it's deciding the rule that maps their combined state to an output. The two-step workflow is: (1) draw the truth table on paper, listing every input combination and the desired output; (2) translate the table line-by-line into if/else if. For two sensors there are four rows. For three sensors there are eight. Beyond that, switch to a state machine (L03). The two logical primitives — AND and OR — feel symmetric but solve different problems: AND for confirmation (avoid false positives), OR for permissiveness (avoid false negatives). Pick based on the cost of being wrong.
- Truth table
- A grid listing every possible combination of input booleans and the desired output for each row. The design document for combinational logic.
- AND (
&&) - True only when BOTH operands are true. Use for confirmation: "don't act unless both sensors agree".
- OR (
||) - True when AT LEAST ONE operand is true. Use for permissiveness: "act if any sensor flags a problem".
- NOT (
!) - Inverts a boolean. Useful for "X and NOT Y" rules — e.g. dry AND NOT raining.
- Confirmatory vs permissive logic
- Two opposite design philosophies. Confirmatory (AND) avoids false positives; permissive (OR) avoids false negatives. Pick based on the cost of each kind of error.
- Boolean variable
- A
boolthat reads astrueorfalse. Using one (e.g.bool dry = ...) makes the rule far more readable than inlining the comparison. - Combinational explosion
- The fact that N inputs produce 2N truth-table rows. Why we don't use raw truth tables beyond 3–4 inputs.
Homework 5 min
Design two rules. Pick any pair of sensors you've used in this course (LDR, DHT11, TMP36, thermistor, soil, rain, knock, button, tilt). For your pair:
- Write down a real-world problem your project would solve.
- Draw a 4-row truth table listing every input combination and the output state.
- State which logical operator (AND, OR, or a mix) the rule uses, and why that operator was the right choice given the cost of being wrong.
- Sketch (don't build) the wiring as a simple labelled diagram.
Then repeat the entire exercise for a different sensor pair solving a different problem.
Bring back next class:
- Your two problem statements (one sentence each).
- Your two truth tables.
- Your two short justifications of AND vs OR.
- Your two wiring sketches.
- Your
hw-l02-19.inosketch — pick ONE of the two designs and actually code it. Tomorrow we use this template to ship the Weather Station project.