Review 3 of 3 · Weeks 5 and 6 · plan two sessions
Design & classes
Weeks 5 and 6 are one continuous idea: decide what the system must do, design it on paper, prove the design by hand, then give its state and behaviour a home in a class. The exam's richest questions live here, and so does this page's worked micro-example running the entire pipeline on one screen.
The design cycle, end to end
Requirements: the five qualities
A requirement says what the system must do, observable from outside; design says how, and the two must not blur. Good R-lines are: testable (a pass/fail check exists), unambiguous (one reading), atomic (one obligation per line), necessary (traceable to a real need) and feasible (buildable with what you have). House style: an ID (R1, R2, ...), condition first, shall for the obligation, a measurable tail, and an acceptance criterion saying how the check is performed. The exam's favourite form is spec repair: given "the system should respond quickly", name the faults (untestable, ambiguous) and rewrite, e.g. "R3: When the button is pressed, the LED shall light within 100 ms, verified by slow-motion video." Repair drill and the full quality definitions: Week 5's requirements page.
Pseudocode: the conventions
Pseudocode is logic without syntax: precise enough to trace, free enough to write at a whiteboard. There is no official standard anywhere, which is why the course fixed one convention set and sticks to it; the exam accepts exactly this style:
| Convention | Python it maps to |
|---|---|
SET x TO value | x = value |
IF condition THEN ... ELSE ... END IF (and ELSE IF) | if ... : / elif / else: |
WHILE condition ... END WHILE · LOOP FOREVER | while ... : · while True: |
FOR each item IN collection ... END FOR | for item in collection: |
FUNCTION name(parameters) ... RETURN value | def name(parameters): ... return value |
INPUT ... / OUTPUT ... | Reading a sensor or value / print(...) or an actuator |
WAIT ... / WAIT UNTIL ... | sleep(...) / wait_for_press() and kin |
Blocks are shown by indentation plus the explicit END ... line; conversion questions in either direction are mechanical once this table is cold. Refinement is the writing method: start with three fat steps, split each until every line is directly translatable. Full conventions and the construct map: Week 5's pseudocode page.
Trace tables
A trace table executes a program with a pencil: one column per variable (plus condition and output columns), one row per executed line or loop pass, filled strictly in execution order without skipping ahead. The discipline that keeps traces honest: write what the line does, not what you believe the program intends; the gap between the two is where bugs hide, and the exam plants exactly such gaps. Week 6 extended the format by one column: tracing a method call starts by rewriting log.add(431) as add(self=log, ms=431), after which self is just another column. Worked nine-row example: Week 5; method trace: Week 6.
Flowcharts and state machines
Two notations, two questions. A flowchart answers "what happens in what order": ovals for start and end, rectangles for actions, diamonds for decisions with labelled yes/no exits, arrows for flow; every diamond's exits must both lead somewhere, and loops appear as arrows flowing back. A state machine answers "what modes does the system live in": circles for states, arrows for transitions labelled event [guard] / action; the machine is always in exactly one state, and behaviour depends on which. The automation bridge: GRAFCET's steps and transitions are this same formalism in industrial dress, so you already think this way. The Week 6 upgrade made the mapping to code mechanical: state → value of self.mode, event → method call, guard → the if at the method's top, action → the branch's body, extra memory → more attributes. Notation reference: Week 5's flow page; the mapping table: Week 6's state page.
The timing rule
One sentence, frequently examined: wall clock for timestamps, monotonic for intervals. The wall clock (time()) names moments for humans and logs but can jump when the system adjusts it; monotonic() never goes backward, so durations are always monotonic() − t0. Measuring a reaction time with the wall clock is the kind of design flaw spec-repair questions enjoy hiding.
Classes: the compressed core
- Anatomy.
class ReactionLog:opens the blueprint (CapWords name);__init__(self, ...)runs at construction and creates every attribute throughself.; methods are defs inside the class withselffirst.log = ReactionLog("Sam")is a constructor call producing an instance. - The
selftruth.log.add(431)≡ReactionLog.add(log, 431): Python passes the instance as the first argument, so inside the method,selfis the object the call went through. Listed in every def, passed by no caller. - Attribute vs method. State reads without parentheses (
led.is_lit); behaviour runs with them (led.on()); a method without parentheses is a value you can store, which is exactly what callbacks do:button.when_pressed = self.press, neverself.press(). - Independence. Each instance owns its state because
__init__creates it fresh per instance; a list written in the class body instead belongs to the blueprint and is shared by all, the classic silent bug. - Composition (has-a). Your classes contain gpiozero objects and your own: a NightLight has an LED, has a Button, has a mode. Write has-a; read is-a (the "Bases:" lines in library docs are inheritance, consumed not authored this term).
- Encapsulation. State changes only through the methods that know its rules, so the mode and the hardware can never drift apart and guards cannot be forgotten by callers.
- Modules. Any
.pyfile imports by its name from the same folder:from reactionlog import ReactionLog; refactoring means restructuring like this while the old verification table still passes.
Deep versions: First class and State & devices.
One worked micro-example, spec to class
The whole pipeline on a system small enough to memorize: a press counter with a reset rule.
Stations 1 and 2 · Spec, then pseudocode
T1: A new tally shall start at zero. T2: When press() is called, the tally shall increase by one. T3: When the tally reaches 10, the system shall output "FULL" and return to zero. Acceptance: call press() 10 times from new; observe one FULL and a zero count.
FUNCTION press()
SET count TO count + 1
IF count = 10 THEN
OUTPUT "FULL"
SET count TO 0
END IF
END FUNCTION
Station 5 · The class
class Tally:
def __init__(self):
self.count = 0 # T1
def press(self):
self.count = self.count + 1
if self.count == 10: # T3
print("FULL")
self.count = 0
Stations 3 and 6 are yours: trace eleven calls to press() on a fresh Tally, columns self.count and output. Expected: FULL exactly once, at the tenth call, finishing at count 1. If your table says otherwise, one of us has a bug, and the table will say which line.
Checklist for this review
Check yourself
"The program should use a while loop to check the button often." Which quality does this requirement violate most fundamentally?
Convert to Python: WHILE count < 5 ... SET count TO count + 1 ... END WHILE
while count < 5: with count = count + 1 indented beneath. The END WHILE disappears into indentation; the condition and the move toward ending it survive unchanged.What single discipline makes a trace table an instrument instead of wishful thinking?
On a state diagram, where does a transition's guard end up in the class, and what happens when the guard fails?
if at the top of the transition's method; on failure the method returns without changing self.mode, so the machine stays put, exactly as the diagram's unfired transition means.Why is times = [] in the class body a bug for per-instance data, and what is the one-line rule preventing it?
__init__, through self.