Project: the fail-safe controller
A command program that authenticates, validates every command, and is tested against a plan.
Do this lesson in the simulatorA robot in a warehouse takes commands from operators all day. Some are mistyped, some come from people who should not be giving commands, and some would drive it into a wall. In this project you build a command controller for BugBot that survives all of that: it authenticates the operator, validates every command, refuses unsafe ones, and is tested against a plan.
The brief
The controller asks for a username and password, allowing three attempts. Once logged in, it repeatedly asks
Command?and obeys valid commands:
forward <cm>,back <cm>,left <cm>andright <cm>, with a distance from 1 to 50;beep;quit, which printsbyeand ends the program.Anything else, including a missing or out-of-range distance, prints
invalid commandand beeps low, and the controller asks again. It never crashes, whatever is typed.
Decompose it
fail-safe controller
├── login() up to three attempts; returns the username or None
├── parse(command) checks a command; returns (word, distance) or None
└── main loop ask, parse, obey or refuse, until quit
Validation lives in parse, so every command goes through the same checks. The main loop only acts on commands that parse has approved.
Step 1: validate one command
Write and test parse before anything else. It must handle every possible string without crashing:
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
MOVES = ["forward", "back", "left", "right"]
def parse(command):
"""Return (word, distance) for a valid command, (word, 0) for beep or quit, or None."""
parts = command.strip().lower().split()
if len(parts) == 1 and parts[0] in ["beep", "quit"]:
return (parts[0], 0)
if len(parts) != 2 or parts[0] not in MOVES:
return None
if not parts[1].isdigit() or not 1 <= int(parts[1]) <= 50:
return None
return (parts[0], int(parts[1]))
tests = [("forward 20", ("forward", 20)), ("beep", ("beep", 0)), ("fly 10", None),
("right 500", None), ("left", None), ("", None), ("BACK 5", ("back", 5))]
for data, expected in tests:
print(repr(data), "pass" if parse(data) == expected else "FAIL")
The test list is the test plan, turned into code: normal commands, boundary distances, invalid values, and erroneous input like an empty string. Add boundary tests for 1, 50, 0 and 51 before you move on.
Step 2: log in
login() is the function from lesson F6.2. Copy it, and test it with a wrong password followed by a right one.
Step 3: the main loop
The loop asks for a command, calls parse, and then either obeys or refuses:
while True:
result = parse(input("Command? "))
if result is None:
print("invalid command")
tone(200, 0.3)
elif result[0] == "quit":
print("bye")
break
...
Fill in the rest: beep, and the four moves.
Test plan for the whole program
Before running the finished controller, write the expected result of each test:
| Test | Typed | Kind | Expected |
|---|---|---|---|
| 1 | a wrong password, then the right one | authentication | access denied, then welcome |
| 2 | forward 20 |
normal | drives 20 cm |
| 3 | fly 10 |
invalid | invalid command, low beep |
| 4 | right 500 |
boundary / invalid | invalid command |
| 5 | an empty line | erroneous | invalid command, no crash |
| 6 | quit |
normal | bye, program ends |
The task runs almost exactly this plan.
Task: the fail-safe controller
Build the controller from the brief with operators = {"ada": "robot42"}. Print access denied for a wrong login, welcome <name> for a right one, invalid command for every command it refuses, and bye on quit. The task types: ada and oops, then ada and robot42, then the commands forward 20, fly 10, right 500, an empty line, right 15, beep and quit.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
operators = {"ada": "robot42"}
MOVES = ["forward", "back", "left", "right"]
Challenges
- Refuse a
forwardcommand that would take the robot closer than 10 cm to a wall, usingdistance(). - Keep a log of every command in a list, with whether it was obeyed, and print it at
quit. - Log out automatically after five invalid commands in a row, and ask for the login again.