Stage 4 of 6 · Theory, kit optional · about 25 minutes
State & devices
Week 5 ended with state machines held together by discipline: a mode variable here, a flag there, functions everywhere trusted to respect them. A class holds them together by construction. This page turns a Week 5 state diagram into a device class, the pattern behind every system you build from now to December.
Giving state a home
Recall the Week 5 two-mode night light: NORMAL and NIGHT, a press toggles, presses ignored for 1 s after a switch. Its loose-variable implementation needs a mode variable, a last_switch timestamp, the hardware objects, and a function that touches all of them, four things whose only connection is your memory. The class version makes the connection physical: they become attributes of one object, and the transition rule becomes its method. State machines and classes are not merely compatible; the class is the state machine's natural container, states as attribute values, transitions as methods.
Worked example: NightLight
from gpiozero import LED, Button
from signal import pause
from time import monotonic
class NightLight:
def __init__(self, led_pin, button_pin):
self.led = LED(led_pin) # has-a
self.button = Button(button_pin, bounce_time=0.05)
self.mode = "NORMAL" # the state, at home
self.last_switch = 0.0
self.button.when_pressed = self.press # my method as the callback
def press(self):
if monotonic() - self.last_switch < 1.0: # the 1 s ignore rule
return
if self.mode == "NORMAL":
self.mode = "NIGHT"
self.led.on()
else:
self.mode = "NORMAL"
self.led.off()
self.last_switch = monotonic()
light = NightLight(17, 27)
print("Night light running. Ctrl+C to stop.")
pause()
Read it top to bottom against what you already know: the constructor takes pin numbers (the facts a NightLight cannot invent), builds its own gpiozero objects, sets its starting state, and wires its own callback. press() is the entire behaviour of the system, in one place, guarding its own state. The main program shrinks to two meaningful lines, which is the sign of a well-drawn capsule boundary.
State diagram to class, mapped
| On the Week 5 diagram | In the class |
|---|---|
| A state (NORMAL, NIGHT) | A value of self.mode |
| The current state marker | What self.mode holds right now |
| An event (button pressed) | A method being called (press()) |
| A transition's guard condition (≥ 1 s since switch) | The if at the top of the method |
| A transition's action (LED on/off) | The code in the branch that changes self.mode |
| Extra memory the machine carries | More attributes (self.last_switch) |
This table is a two-way translator. Given a state diagram (Week 5's design lab produced several), you can now write its class mechanically; given a class like this, you can draw its diagram, and drawing it is the fastest way to review someone else's state logic. GRAFCET-trained eyes will recognize the whole arrangement: steps, transitions, guards, actions, in Python clothes.
Methods as callbacks
The line self.button.when_pressed = self.press is Week 4's event pattern upgraded. self.press, no parentheses, is the method as a thing (the bound-method fact from two pages ago), and handing it to the Button means: when the press happens, call this method on this instance. The object wires its own reflexes in its own constructor. Two details with teeth:
- No parentheses.
when_pressed = self.press()would run the method once, now, and assign its return value (None) as the callback. The system then ignores every press, with no error anywhere. Week 4's rule, now withself. - The pattern is the course's future. Week 9's MQTT client works exactly like this: you build a client object and assign your methods to its
on_connectandon_messagehooks. Learn the shape once here, spend it twice later.
Composition: has-a
Design guidance that will serve you through the project: when tempted to write a big class, ask what it has. A greenhouse controller has a light sensor, has a pump relay, has a mode; each part is its own object with its own class, and the controller composes them. Small capsules, clearly related, beat one giant one, and the requirements document (Week 5) usually tells you where the seams are: nouns become classes, behaviours become their methods.
The payoff: encapsulation
Notice what is now impossible. No line outside NightLight can flip the light without going through press(), so the 1 s rule cannot be forgotten by a future caller; the mode and the LED cannot drift out of sync, because the only code that changes one changes both. State changed exclusively through the methods that know its rules is encapsulation, and it is the same safety philosophy as a guarded panel: not "trust everyone to be careful" but "build it so carelessness has no path". Python enforces this by convention rather than by lock (any code could write light.mode = "NIGHT"), so the discipline is a team norm: touch state through methods, and treat a teammate's attributes as read-only from outside. The OOP article covers the concept alongside its siblings.
Inheritance, for reading
Open gpiozero's LED documentation and the first line under the class name reads "Bases: DigitalOutputDevice". That is inheritance: LED is written as a specialization of DigitalOutputDevice (an LED is a digital output device), and receives everything the parent defines, on(), off(), blink(), value, for free, adding or adjusting only what makes an LED specific. Reading rules, which are all this course requires of you:
- A class's full ability = its own listing plus its parents', up the chain. A method you use but cannot find is defined on a base; follow the "Bases:" links.
help(LED)in the REPL shows the inherited members merged, "Methods inherited from ..." headers included; often faster than the chain-walk.- Is-a vs has-a: an LED is a digital output device (inheritance); your NightLight has an LED (composition). This course writes has-a and reads is-a; writing your own subclasses is real and useful, and belongs to a later course, not to your first month of Python.
Checklist for this stage
Check yourself
Map it: on the state diagram, what do a state, an event and a guard become in the class?
self.mode; an event is a method call; a guard is the if that decides whether the transition happens. Actions are the branch's code; extra machine memory becomes more attributes.Why does the 1 s ignore rule live inside press() rather than in the main program?
What does self.button.when_pressed = self.press hand to the Button, exactly?
press tied to this specific instance. When the hardware event fires, gpiozero calls it, which runs press() with self already set to this NightLight.Your teammate's version toggles the LED but sometimes shows mode NORMAL with the light on. Diagnose in OOP terms.
self.mode (or vice versa), meaning state is being touched outside the method that owns the rules. The fix is structural: route every change through the transition method so the two can never diverge.An LED "is a" DigitalOutputDevice; your NightLight "has an" LED. Why does the difference matter when reading docs?
light.led.blink.Where does this exact callback pattern reappear later in the course?
on_connect and on_message hooks you assign your own methods to, and Week 11's Flask routes follow the same spirit. One pattern, three technologies.