Thermostat with a relay
A thermostat is the classic control loop: measure, compare, switch, repeat. A DHT22 reports the room's temperature over one data wire, the program compares it with a target, and an OutputDevice on GPIO17 switches a heater through a relay. In PyRasp the heater is world-coupled: switching it on really warms the room's simulated zone, and the DHT22 in the same zone sees the rise — so the loop closes in the world, not just on the bench.
Cheap version — wire it by hand:
- DHT22 sensor — $5
- 10 kΩ resistor for the data pull-up — $0.25
- Relay module — $4
- Heater — $10
Fast version — build it from kits:
- DHT22 module — $7: the sensor with its pull-up fitted
- Relay + heater — $20: relay, contacts loop and load assembled
Both versions run the same program on GPIO4 (data) and GPIO17 (relay signal).
The cheap version
The DHT22's data line is open drain, so it needs the 10 kΩ pull-up to 3.3 V or every read fails. The relay's coil and its switched loop both run from the 5 V rail; nothing about the heater ever touches a GPIO.
from wiring import *
pi = Pi()
sensor = DHT22()
relay = Relay("high") # IN high = contacts closed
heater = Heater() # Heater(powerW=1500) for a 1.5 kW unit
pi.V3_3 >> sensor.vcc
pi.V3_3 >> Resistor(10000) >> sensor.data
pi.GPIO4 >> sensor.data
sensor.gnd >> pi.GND
pi.V5 >> relay.dc_plus
pi.V5 >> relay.com # switched supply in at COM
relay.no >> heater.plus # out at NO, into the heater
heater.minus >> relay.dc_minus
relay.dc_minus >> pi.GND
pi.GPIO17 >> relay.signalfrom adafruit_dht import DHT22
from gpiozero import OutputDevice
from time import sleep
sensor = DHT22(4)
heater = OutputDevice(17, active_high=True, initial_value=False)
TARGET = 21.0
BAND = 0.5 # on below 20.5 °C, off above 21.5 °C
while True:
try:
temperature = sensor.temperature
except RuntimeError as error: # checksum or timeout: read again
print("read failed:", error)
sleep(2)
continue
if temperature < TARGET - BAND:
heater.on()
elif temperature > TARGET + BAND:
heater.off()
print(f"{temperature:5.1f} C heater {'ON' if heater.value else 'off'}")
sleep(2) # the DHT22 reads at most every 2 sThe fast version
The DHT22 module makes the pull-up impossible to forget, and the relay + heater kit is the whole power side assembled: vcc to 5 V, gnd to ground, sig to the GPIO, and sig high means heating. The program is unchanged.
from wiring import *
pi = Pi()
sensor = kits.DHT22Module()
heater = kits.RelayHeater()
pi.V3_3 >> sensor.vcc
pi.GPIO4 >> sensor.data
sensor.gnd >> pi.GND
pi.V5 >> heater.vcc
heater.gnd >> pi.GND
pi.GPIO17 >> heater.sigfrom adafruit_dht import DHT22
from gpiozero import OutputDevice
from time import sleep
sensor = DHT22(4)
heater = OutputDevice(17, active_high=True, initial_value=False)
TARGET = 21.0
BAND = 0.5 # on below 20.5 °C, off above 21.5 °C
while True:
try:
temperature = sensor.temperature
except RuntimeError as error: # checksum or timeout: read again
print("read failed:", error)
sleep(2)
continue
if temperature < TARGET - BAND:
heater.on()
elif temperature > TARGET + BAND:
heater.off()
print(f"{temperature:5.1f} C heater {'ON' if heater.value else 'off'}")
sleep(2) # the DHT22 reads at most every 2 sHow it works
- A DHT22 read is one 40-bit frame at most every two seconds; asking sooner returns the previous frame. A few per cent of reads fail by design (
Checksum did not validate/A full buffer was not returned), so thetry/except RuntimeErroris not optional — it is how the protocol reports a bad frame.sleep(2)then retries. - Hysteresis is the band between the two thresholds. The heater switches on below 20.5 °C and off above 21.5 °C; in between it keeps its state, so the relay does not chatter when the reading sits on the target. A room this size drifts slowly, so a 1 °C band costs nothing but saves the contacts.
OutputDevice(17)drives the relay's input stage, which draws about 2.5 mA — safe for any GPIO. The coil itself takes 72 mA from 5 V, and the contacts carry the heater's current. The contacts close about 10 ms aftersigrises, which is invisible at thermostat speeds.- In the world, the heater is a 5 V / 100 mA stand-in on the board; its effect on its zone is the rated power (1000 W by default,
(V ÷ 5 V)²scaled) while it is switched on. Theindoorzone is a heat capacity of 2.5 MJ/K leaking to the outdoors with a τ of about four hours, so a 1 kW heater raises a small room by a few degrees per hour and the curve flattens. The DHT22 reads the same zone, so the reading responds.
Try it
Build the board and press Run. The console prints one line every two seconds, e.g. 19.8 C heater ON while the room warms, and 21.6 C heater off once it is there.
The room has its own thermostat by default: the World dialog's Indoor → Heating / cooling holds 21 °C with its own ±0.4 °C deadband, which would fight your loop. Open World and switch it to Off: the room follows outdoors (and your heaters) so your program is the only controller. Then run at 50× speed and watch the temperature climb.
To fake a cold room without waiting, select the DHT22 and use its Datafeed section: set Temperature → Source → Manual to 18 °C and the heater comes on at the next read. (A pinned sensor does not feel the heater, so it is a logic test, not the loop.) The built-in Classroom demo preset is more fun: a sine wave around 20 °C with a 60 s period, which drives the loop through its whole on/in-band/off cycle in a minute. Either way, the world feed — DHT22 and heater in the same zone — is the honest test.
As a contest submission this becomes an objective with pinned stimuli: the built-in medium objective Thermostat holds 18 °C, steps to 21, 23, 21 and 19 °C, and checks the heater's power with the same hysteresis rules plus a damage check. Note that the judge pins the sensor's reading, so the heater's world effect is not what is scored — the switching decisions are.
Variations
- A DS18B20 probe instead: the DS18B20 module reads over 1-Wire from
/sys/bus/w1/devices, and the ready-made Thermostat example project in the Projects dialog pairs one with this same relay + heater and a 4-digit display. - Greenhouse control: put both the DHT22 and the heater in the
greenhousezone. The glass house follows sunlight and its soil dries as it warms, so a warmer setpoint also means a thirstier plant. - Add cooling: a second relay + fan on another GPIO, switched on above 26 °C and off below 24 °C. Two outputs, one shared hysteresis idea — the built-in Cooling fan objective is exactly that shape.
Going deeper
- Primer: relays and switching loads — why a heater never hangs off a GPIO.
- Parts: DHT22, relay, heater, resistor.
- Kits: DHT22 module, relay + heater, relay + fan.
- Libraries: adafruit_dht for the sensor, gpiozero for
OutputDevice. - The world model's zones and loads are described in the heater page's simulator notes.