Try to solve the task yourself before looking at the answer. Different solutions that give the same result are also correct.

1 Beginner

Connecting, switching outputs and waiting.
01

Connect to the simulation

Start the garage simulation. Write a program that connects to the simulation, prints Connected to the garage! if the connection succeeded and then closes the connection.
Then close the simulation, run the program once more and observe what happens.

sim_connect() attaches to the shared memory opened by the simulation. If the simulation is not running it returns 1 and the library prints an error message; the program then ends at once (return 1 in C, exit(1) in Python). The Python library also prints a line itself when it connects and disconnects. Calling sim_disconnect() at the end of every program is a good habit.

sim_connect()sim_disconnect()printf / print
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;   // simulation is not running

    printf("Connected to the garage!\n");

    sim_disconnect();
    return 0;
}
from garage import *

if sim_connect() != 0:   # simulation is not running
    exit(1)

print("Connected to the garage!")

sim_disconnect()
02

Turn on the garage lamp

Write a program that turns on the garage's interior lamp. Observe that the lamp stays on after the program has ended.
Press the Light Off button on the manual control panel to turn it off.

garage_lamp(LAMP_ON) writes 1 to the lamp's address. The simulation keeps this value after the program ends, so the lamp stays on until you switch it off. While the lamp is off the garage is drawn dark (shadowed).

garage_lamp()LAMP_ON
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    garage_lamp(LAMP_ON);   // stays on after the program ends

    sim_disconnect();
    return 0;
}
from garage import *

if sim_connect() != 0:
    exit(1)

garage_lamp(LAMP_ON)   # stays on after the program ends

sim_disconnect()
03

A lamp that burns for three seconds

Turn the lamp on, wait 3 seconds, then turn it off. The lamp must be off when the program ends.

Commands run one after another, from top to bottom. Without the wait the lamp would be on for such a short time that you could not see it. Sleep in C takes milliseconds, time.sleep in Python takes seconds.

garage_lamp()LAMP_OFFSleep / time.sleep
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    garage_lamp(LAMP_ON);
    Sleep(3000);            // 3000 ms = 3 s
    garage_lamp(LAMP_OFF);

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

garage_lamp(LAMP_ON)
time.sleep(3)          # 3 seconds
garage_lamp(LAMP_OFF)

sim_disconnect()
04

A traffic light with the warning light

Light the warning light red (255, 0, 0) for 2 seconds, then yellow (255, 255, 0) for 2 seconds, then green (0, 255, 0) for 2 seconds, and finally turn it off. The warning light is the coloured strip just above the keypad.

A colour is given by its red, green and blue (RGB) parts, each 0–255. The last parameter WARN_ON turns the light on. To change the colour just call warning_light() again with new values. warning_light_off() turns the light off but keeps the last colour.

warning_light()RGBwarning_light_off()
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    warning_light(255, 0, 0, WARN_ON);     // red
    Sleep(2000);
    warning_light(255, 255, 0, WARN_ON);   // yellow
    Sleep(2000);
    warning_light(0, 255, 0, WARN_ON);     // green
    Sleep(2000);
    warning_light_off();

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

warning_light(255, 0, 0, WARN_ON)     # red
time.sleep(2)
warning_light(255, 255, 0, WARN_ON)   # yellow
time.sleep(2)
warning_light(0, 255, 0, WARN_ON)     # green
time.sleep(2)
warning_light_off()

sim_disconnect()
05

Open the door for 2 seconds

Start with the door closed. Run the door motor upwards, wait 2 seconds and stop the motor. The door should be open about one third.
To close it again, press Door Down on the manual control panel, then Door Stop when the door is closed.

Once started, the motor does not stop by itself; the waiting time between the two commands decides how far the door moves. A full opening takes about 7 seconds, so in 2 seconds the door opens about 30%.

door_motor()DOOR_UPDOOR_STOP
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    door_motor(DOOR_UP);     // start opening
    Sleep(2000);
    door_motor(DOOR_STOP);   // stop the motor

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

door_motor(DOOR_UP)     # start opening
time.sleep(2)
door_motor(DOOR_STOP)   # stop the motor

sim_disconnect()
06

Open, wait, close

With the door closed, make one full door cycle:
1. Light the warning light orange (255, 165, 0) and run the door up for 7 seconds (the door opens fully).
2. Stop the motor, turn the warning light off and wait 3 seconds.
3. Light the warning light red (255, 0, 0) and run the door down for 7 seconds.
4. Stop the motor and turn the warning light off.

A full travel takes about 7 seconds, so waiting 7 seconds moves the door to the end. If the motor keeps running after the door reached the end, the door just stays there; but for a real motor this is harmful. Also, if the door starts half open, the timing is wrong. Reading the sensor instead of trusting the time is better; you will do that in Task 15.

door_motor()DOOR_DOWNwarning_light()Sleep / time.sleep
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    warning_light(255, 165, 0, WARN_ON);   // orange: door is moving
    door_motor(DOOR_UP);
    Sleep(7000);                           // full travel takes about 7 s
    door_motor(DOOR_STOP);
    warning_light_off();

    Sleep(3000);                           // door stays open

    warning_light(255, 0, 0, WARN_ON);     // red: door is closing
    door_motor(DOOR_DOWN);
    Sleep(7000);
    door_motor(DOOR_STOP);
    warning_light_off();

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

warning_light(255, 165, 0, WARN_ON)   # orange: door is moving
door_motor(DOOR_UP)
time.sleep(7)                         # full travel takes about 7 s
door_motor(DOOR_STOP)
warning_light_off()

time.sleep(3)                         # door stays open

warning_light(255, 0, 0, WARN_ON)     # red: door is closing
door_motor(DOOR_DOWN)
time.sleep(7)
door_motor(DOOR_STOP)
warning_light_off()

sim_disconnect()

2 Basic

Reading sensors, printing and deciding with if/else.
07

Read the door position

Read the closed percentage of the door and print it like Door closed: 100%.
Then press Door Up on the manual control panel, press Door Stop when the door is half way and run the program again. This time you should see a different number.

A variable is a named box that stores a value. door_pos_read() returns the current door position; the value is stored in pos and printed. In C, %% inside printf prints a single % sign.

door_pos_read()variableprintf / print
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    int pos = door_pos_read();          // 100 = closed, 0 = open
    printf("Door closed: %d%%\n", pos);

    sim_disconnect();
    return 0;
}
from garage import *

if sim_connect() != 0:
    exit(1)

pos = door_pos_read()          # 100 = closed, 0 = open
print(f"Door closed: {pos}%")

sim_disconnect()
08

Is the door closed?

Read the door position. If the door is fully closed (100), print Door is closed and light the warning light green; otherwise print Door is NOT closed and light it red.
Run the program once with the door closed and once after opening the door a little by hand.

if runs the first block when the condition is true and the else block when it is false; never both. Comparison is ==; a single = means assignment. In Python blocks are marked by indentation, in C by curly braces.

if / else==
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    int pos = door_pos_read();

    if (pos == 100)
    {
        printf("Door is closed\n");
        warning_light(0, 255, 0, WARN_ON);   // green
    }
    else
    {
        printf("Door is NOT closed\n");
        warning_light(255, 0, 0, WARN_ON);   // red
    }

    sim_disconnect();
    return 0;
}
from garage import *

if sim_connect() != 0:
    exit(1)

pos = door_pos_read()

if pos == 100:
    print("Door is closed")
    warning_light(0, 255, 0, WARN_ON)   # green
else:
    print("Door is NOT closed")
    warning_light(255, 0, 0, WARN_ON)   # red

sim_disconnect()
09

Three door states

Extend Task 8 to three states:
• 100: print Closed, warning light green (0, 255, 0).
• 0: print Fully open, warning light red (255, 0, 0).
• Anything in between: print the value like Partly open: 40%, warning light yellow (255, 255, 0).

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
10

Status report

Read and print the current state of the lamp, the warning light and the door motor, for example:
Lamp: ON
Warning light: OFF
Motor: UP
For the motor print 0 → STOPPED, 1 → UP, 2 → DOWN. Run the program; then press Light On and Door Up on the manual control panel and run it again. Do not forget to press Door Stop at the end.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
11

Read a key

The program prints Press and hold key 5 (3 s)... and waits 3 seconds. During this time hold key 5 of the keypad in the simulation down with the mouse.
When the time is up, read the key: if it is pressed, turn the lamp on and print Key 5 is pressed; otherwise turn the lamp off and print Key 5 is not pressed. Try it once with and once without pressing.

key_read() only gives the state at the moment of reading. If you press and release the key before the 3 seconds are over, the program never sees the press. In Level 3 you will solve this by reading the key over and over in a loop.

key_read()if / else
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    printf("Press and hold key 5 (3 s)...\n");
    Sleep(3000);

    if (key_read(5) == 1)
    {
        garage_lamp(LAMP_ON);
        printf("Key 5 is pressed\n");
    }
    else
    {
        garage_lamp(LAMP_OFF);
        printf("Key 5 is not pressed\n");
    }

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

print("Press and hold key 5 (3 s)...")
time.sleep(3)

if key_read(5) == 1:
    garage_lamp(LAMP_ON)
    print("Key 5 is pressed")
else:
    garage_lamp(LAMP_OFF)
    print("Key 5 is not pressed")

sim_disconnect()
12

Two conditions at once

The program prints Press and hold key 1 (3 s)... and waits 3 seconds. Open the door only if both conditions are true: the door is fully closed (100) AND key 1 is pressed.
In that case print Opening, run the door up for 3 seconds and stop it. Otherwise print Cannot open, light the warning light red for 2 seconds and turn it off. Try it with the door closed and with the door a little open.

A condition joined with && (and in Python) is true only if both parts are true. This way the program does not try to open a door that is already open.

&& / andkey_read()door_pos_read()
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    printf("Press and hold key 1 (3 s)...\n");
    Sleep(3000);

    if (door_pos_read() == 100 && key_read(1) == 1)
    {
        printf("Opening\n");
        door_motor(DOOR_UP);
        Sleep(3000);
        door_motor(DOOR_STOP);
    }
    else
    {
        printf("Cannot open\n");
        warning_light(255, 0, 0, WARN_ON);
        Sleep(2000);
        warning_light_off();
    }

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

print("Press and hold key 1 (3 s)...")
time.sleep(3)

if door_pos_read() == 100 and key_read(1) == 1:
    print("Opening")
    door_motor(DOOR_UP)
    time.sleep(3)
    door_motor(DOOR_STOP)
else:
    print("Cannot open")
    warning_light(255, 0, 0, WARN_ON)
    time.sleep(2)
    warning_light_off()

sim_disconnect()
13

Colour from the position

Calculate the colour of the warning light from the door position:
red = (100 − position) × 255 / 100
green = position × 255 / 100
blue = 0
So a closed door is green, a fully open door red and a half open door something in between. Print the position and the values like Door closed: 50% R=127 G=127. Move the door to different positions by hand and run the program a few times.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.

3 Intermediate

Loops, continuous control and tracking state.
14

Watch the door all the time

Put Task 13 into an endless loop: every 300 ms read and print the position and update the colour of the warning light.
While the program runs, open and close the door with the manual control panel (press Door Stop at the end) and watch the colour change step by step. Stop the program with Ctrl+C.

An endless loop repeats its commands without stopping, so the program sees every change of the sensor. The 300 ms wait keeps the output readable and does not waste the processor. Lines after the loop are never reached; the program is stopped with Ctrl+C.

while (1) / while TrueCtrl+C
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    while (1)                                // Ctrl+C to quit
    {
        int pos = door_pos_read();
        int red   = (100 - pos) * 255 / 100;
        int green = pos * 255 / 100;

        printf("Door closed: %d%%  R=%d G=%d\n", pos, red, green);
        warning_light(red, green, 0, WARN_ON);

        Sleep(300);
    }
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

while True:                              # Ctrl+C to quit
    pos = door_pos_read()
    red = (100 - pos) * 255 // 100
    green = pos * 255 // 100

    print(f"Door closed: {pos}%  R={red} G={green}")
    warning_light(red, green, 0, WARN_ON)

    time.sleep(0.3)
15

Open and close fully with the sensor

Do Task 6 with the sensor instead of timing:
1. Light the warning light orange (255, 165, 0), run the motor up and wait, checking every 50 ms, until door_pos_read() is 0. Then stop the motor, turn the light off and print Door is fully open.
2. Wait 1 second.
3. In the same way close the door until the position is 100 and print Door is fully closed.
Try it with the door half open too: it must still stop at the right place.

The loop reads the position again and again until the door reaches its target; when the condition becomes false the loop ends and the motor is stopped. Because this relies on measuring, not on time, the door stops at the right place wherever it starts. Waiting with > 0 instead of == 0 keeps the program from getting stuck even if a value jumps past the target.

while (condition)door_pos_read()sensor
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    warning_light(255, 165, 0, WARN_ON);
    door_motor(DOOR_UP);
    while (door_pos_read() > 0)      // wait until fully open
        Sleep(50);
    door_motor(DOOR_STOP);
    warning_light_off();
    printf("Door is fully open\n");

    Sleep(1000);

    warning_light(255, 165, 0, WARN_ON);
    door_motor(DOOR_DOWN);
    while (door_pos_read() < 100)    // wait until fully closed
        Sleep(50);
    door_motor(DOOR_STOP);
    warning_light_off();
    printf("Door is fully closed\n");

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

warning_light(255, 165, 0, WARN_ON)
door_motor(DOOR_UP)
while door_pos_read() > 0:       # wait until fully open
    time.sleep(0.05)
door_motor(DOOR_STOP)
warning_light_off()
print("Door is fully open")

time.sleep(1)

warning_light(255, 165, 0, WARN_ON)
door_motor(DOOR_DOWN)
while door_pos_read() < 100:     # wait until fully closed
    time.sleep(0.05)
door_motor(DOOR_STOP)
warning_light_off()
print("Door is fully closed")

sim_disconnect()
16

A guard that stops the motor at the ends

Write a guard program that runs all the time: if the motor is running up and the door is fully open (0), or the motor is running down and the door is fully closed (100), it stops the motor and prints Limit reached, motor stopped. The loop runs every 50 ms.
Leave the program running and move the door only with the Door Up / Door Down buttons of the manual control panel: the door must now stop by itself at each end.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
17

Keypad remote control

Use the keypad as a remote control: 1 → door up, 2 → door down, 0 → stop. When the door reaches an end the motor stops by itself (Task 16). While the motor runs the warning light is orange (255, 165, 0); when it stops the light goes off. The loop runs every 50 ms.
Try it: press 1, press 0 when the door is half way, then press 2.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
18

Which key is pressed?

Scan the 12 keys of the keypad (0–11) with a for loop and print the key that is pressed: Key 7 for digits, Key # for 10, Key X for 11. The outer loop runs every 200 ms.
Notice that the text repeats while you hold a key; you will fix that in the next task.

A for loop counts a variable through a range automatically; instead of writing 12 separate ifs the same check is repeated 12 times. On the keypad # is key 10 and X is key 11, so these two are printed differently.

forkey_read()ADR_KEY_HASHADR_KEY_X
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    while (1)
    {
        for (int k = 0; k <= 11; k++)          // check every key
        {
            if (key_read(k) == 1)
            {
                if (k == ADR_KEY_HASH)   printf("Key #\n");
                else if (k == ADR_KEY_X) printf("Key X\n");
                else                     printf("Key %d\n", k);
            }
        }
        Sleep(200);
    }
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

while True:
    for k in range(12):                    # check every key
        if key_read(k) == 1:
            if k == ADR_KEY_HASH:
                print("Key #")
            elif k == ADR_KEY_X:
                print("Key X")
            else:
                print(f"Key {k}")
    time.sleep(0.2)
19

Count the presses

Count how many times the # key is pressed. Each press must count only once, however long you hold the key; every time the number grows print it like # pressed 3 times.
The X key resets the counter and prints Counter reset (if the counter is already 0 it does nothing). The loop runs every 20 ms.

To catch a press you look for the moment the key changes from 0 to 1 (rising edge). The previous variable stores the last reading; the counter only grows when previous == 0 and now == 1. While the key is held both are 1, so it is not counted again.

edge detectioncounterprevious state
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    int count = 0;
    int previous = 0;                      // # state in the last round

    while (1)
    {
        int now = key_read(ADR_KEY_HASH);
        if (now == 1 && previous == 0)     // 0 -> 1: a new press
        {
            count++;
            printf("# pressed %d times\n", count);
        }
        previous = now;

        if (key_read(ADR_KEY_X) == 1 && count != 0)
        {
            count = 0;
            printf("Counter reset\n");
        }
        Sleep(20);
    }
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

count = 0
previous = 0                           # # state in the last round

while True:
    now = key_read(ADR_KEY_HASH)
    if now == 1 and previous == 0:     # 0 -> 1: a new press
        count += 1
        print(f"# pressed {count} times")
    previous = now

    if key_read(ADR_KEY_X) == 1 and count != 0:
        count = 0
        print("Counter reset")
    time.sleep(0.02)
20

Blink as many times as the digit

Wait until a digit key (1–9) is pressed and continue when it is released. Then print something like Blinking 4 times and blink the garage lamp that many times (0.5 s on, 0.5 s off). Then wait for the next digit. The keys 0, # and X are ignored.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
21

Read a four-digit code

Read 4 digits from the keypad (this time 0 is valid too; # and X are ignored). Each time a digit is pressed and released, light the warning light blue (0, 0, 255) for 150 ms (like a key beep) and print a *.
When 4 digits are in, join them into one number (1, 2, 3, 4 → 1234) and print Code: 1234. If the code is 1234 light the warning light green for 3 seconds, otherwise red for 3 seconds; then the program ends.

code * 10 shifts the number one place to the left and the new digit goes to the right: 0 → 1 → 12 → 123 → 1234. Digits are 0 to 9, so -1 is used for 'no key yet'. fflush(stdout) in C and flush=True in Python make the * appear at once. %04d / :04d prints 4 digits even if the code starts with 0.

code = code * 10 + digitfor-1 as no key
#include "garage.h"

int main(void)
{
    if (sim_connect() != 0) return 1;

    printf("Enter a 4-digit code: ");
    int code = 0;

    for (int n = 0; n < 4; n++)
    {
        int digit = -1;                        // -1 = no key yet
        while (digit == -1)
        {
            for (int k = 0; k <= 9; k++)
                if (key_read(k) == 1) digit = k;
            Sleep(20);
        }
        while (key_read(digit) == 1) Sleep(20);   // wait for release

        warning_light(0, 0, 255, WARN_ON);     // blue key beep
        Sleep(150);
        warning_light_off();
        printf("*");
        fflush(stdout);

        code = code * 10 + digit;
    }

    printf("\nCode: %04d\n", code);
    if (code == 1234) warning_light(0, 255, 0, WARN_ON);
    else              warning_light(255, 0, 0, WARN_ON);
    Sleep(3000);
    warning_light_off();

    sim_disconnect();
    return 0;
}
from garage import *
import time

if sim_connect() != 0:
    exit(1)

print("Enter a 4-digit code: ", end="", flush=True)
code = 0

for n in range(4):
    digit = -1                             # -1 = no key yet
    while digit == -1:
        for k in range(10):
            if key_read(k) == 1:
                digit = k
        time.sleep(0.02)
    while key_read(digit) == 1:            # wait for release
        time.sleep(0.02)

    warning_light(0, 0, 255, WARN_ON)      # blue key beep
    time.sleep(0.15)
    warning_light_off()
    print("*", end="", flush=True)

    code = code * 10 + digit

print(f"\nCode: {code:04d}")
if code == 1234:
    warning_light(0, 255, 0, WARN_ON)
else:
    warning_light(255, 0, 0, WARN_ON)
time.sleep(3)
warning_light_off()

sim_disconnect()

4 Advanced

Functions, timing, state machines and robustness.
22

A code lock built from functions

Split Task 21 into functions and build a code lock that runs all the time:
• wait_key(): waits until any key (0–11) is pressed and released, shows a 150 ms blue key light and returns the key number.
• open_door() and close_door(): open / close the door fully using the sensor; the warning light is orange while moving (Task 15).
The main loop reads 4 digits (# and X are ignored) and prints a * for each. If the code is 1234 it prints Access granted, turns the lamp on, opens the door, waits 5 seconds, closes the door and turns the lamp off. Otherwise it prints Wrong code and lights the warning light red for 2 seconds.

A function lets you write a job once and call it by name wherever you need it. The main loop now tells 'what happens', the details live in the functions; this makes the code easier to read and to debug. wait_key() hands the key number back with return; the check k <= 9 skips # (10) and X (11).

functionreturnvoid
#include "garage.h"

// Waits for a key press + release, returns the key number (0..11)
int wait_key(void)
{
    int key = -1;
    while (key == -1)
    {
        for (int k = 0; k <= 11; k++)
            if (key_read(k) == 1) key = k;
        Sleep(20);
    }
    while (key_read(key) == 1) Sleep(20);   // wait for release

    warning_light(0, 0, 255, WARN_ON);       // blue key beep
    Sleep(150);
    warning_light_off();
    return key;
}

void open_door(void)
{
    warning_light(255, 165, 0, WARN_ON);
    door_motor(DOOR_UP);
    while (door_pos_read() > 0) Sleep(50);
    door_motor(DOOR_STOP);
    warning_light_off();
}

void close_door(void)
{
    warning_light(255, 165, 0, WARN_ON);
    door_motor(DOOR_DOWN);
    while (door_pos_read() < 100) Sleep(50);
    door_motor(DOOR_STOP);
    warning_light_off();
}

int main(void)
{
    if (sim_connect() != 0) return 1;

    while (1)
    {
        printf("Enter code: ");
        int code = 0;
        int n = 0;
        while (n < 4)
        {
            int k = wait_key();
            if (k <= 9)                    // ignore # and X
            {
                code = code * 10 + k;
                n++;
                printf("*");
                fflush(stdout);
            }
        }
        printf("\n");

        if (code == 1234)
        {
            printf("Access granted\n");
            garage_lamp(LAMP_ON);
            open_door();
            Sleep(5000);
            close_door();
            garage_lamp(LAMP_OFF);
        }
        else
        {
            printf("Wrong code\n");
            warning_light(255, 0, 0, WARN_ON);
            Sleep(2000);
            warning_light_off();
        }
    }
}
from garage import *
import time


def wait_key():
    """Waits for a key press + release, returns the key number (0..11)."""
    key = -1
    while key == -1:
        for k in range(12):
            if key_read(k) == 1:
                key = k
        time.sleep(0.02)
    while key_read(key) == 1:              # wait for release
        time.sleep(0.02)

    warning_light(0, 0, 255, WARN_ON)      # blue key beep
    time.sleep(0.15)
    warning_light_off()
    return key


def open_door():
    warning_light(255, 165, 0, WARN_ON)
    door_motor(DOOR_UP)
    while door_pos_read() > 0:
        time.sleep(0.05)
    door_motor(DOOR_STOP)
    warning_light_off()


def close_door():
    warning_light(255, 165, 0, WARN_ON)
    door_motor(DOOR_DOWN)
    while door_pos_read() < 100:
        time.sleep(0.05)
    door_motor(DOOR_STOP)
    warning_light_off()


if sim_connect() != 0:
    exit(1)

while True:
    print("Enter code: ", end="", flush=True)
    code = 0
    n = 0
    while n < 4:
        k = wait_key()
        if k <= 9:                         # ignore # and X
            code = code * 10 + k
            n += 1
            print("*", end="", flush=True)
    print()

    if code == 1234:
        print("Access granted")
        garage_lamp(LAMP_ON)
        open_door()
        time.sleep(5)
        close_door()
        garage_lamp(LAMP_OFF)
    else:
        print("Wrong code")
        warning_light(255, 0, 0, WARN_ON)
        time.sleep(2)
        warning_light_off()
23

Code in an array: X clears, # confirms

The code no longer has a fixed length. The user types digits and confirms with #:
• Each digit is stored in an array and a * is printed; at most 8 digits are stored, more are ignored.
• X clears what was typed so far, prints Cleared and lights the warning light yellow (255, 255, 0) for 300 ms.
• The correct code is 2580 and is stored in an array ({2, 5, 8, 0}). When # is pressed compare the typed digits with the code: if the length and all digits match, open and close the door as in Task 22 (lamp included); otherwise print Wrong code and light red for 2 seconds.
The entry is reset after every attempt. Use the functions from Task 22.

An array stores many values of the same type in numbered boxes: entered[0] is the first digit. In C the array has a fixed size, so len keeps track of how many boxes are used. check_code() first checks the length, then compares the digits one by one and returns 0 at the first difference. In Python entered == PASSWORD does the same: it is true if both lists have the same length and the same elements.

array / listcheck_code()# / X keys
#include "garage.h"

#define MAX_DIGITS 8

int password[4] = {2, 5, 8, 0};
int password_len = 4;

int wait_key(void)
{
    int key = -1;
    while (key == -1)
    {
        for (int k = 0; k <= 11; k++)
            if (key_read(k) == 1) key = k;
        Sleep(20);
    }
    while (key_read(key) == 1) Sleep(20);
    warning_light(0, 0, 255, WARN_ON);
    Sleep(150);
    warning_light_off();
    return key;
}

void open_door(void)
{
    warning_light(255, 165, 0, WARN_ON);
    door_motor(DOOR_UP);
    while (door_pos_read() > 0) Sleep(50);
    door_motor(DOOR_STOP);
    warning_light_off();
}

void close_door(void)
{
    warning_light(255, 165, 0, WARN_ON);
    door_motor(DOOR_DOWN);
    while (door_pos_read() < 100) Sleep(50);
    door_motor(DOOR_STOP);
    warning_light_off();
}

// Returns 1 if the typed digits equal the password
int check_code(int entered[], int len)
{
    if (len != password_len) return 0;
    for (int i = 0; i < len; i++)
        if (entered[i] != password[i]) return 0;
    return 1;
}

int main(void)
{
    if (sim_connect() != 0) return 1;

    int entered[MAX_DIGITS];
    int len = 0;
    printf("Type the code and press #. X clears.\n");

    while (1)
    {
        int k = wait_key();
        if (k <= 9)                            // a digit
        {
            if (len < MAX_DIGITS)
            {
                entered[len] = k;
                len++;
                printf("*");
                fflush(stdout);
            }
        }
        else if (k == ADR_KEY_X)               // clear
        {
            len = 0;
            printf("\nCleared\n");
            warning_light(255, 255, 0, WARN_ON);
            Sleep(300);
            warning_light_off();
        }
        else                                   // # = confirm
        {
            printf("\n");
            if (check_code(entered, len))
            {
                printf("Access granted\n");
                garage_lamp(LAMP_ON);
                open_door();
                Sleep(5000);
                close_door();
                garage_lamp(LAMP_OFF);
            }
            else
            {
                printf("Wrong code\n");
                warning_light(255, 0, 0, WARN_ON);
                Sleep(2000);
                warning_light_off();
            }
            len = 0;
        }
    }
}
from garage import *
import time

MAX_DIGITS = 8
PASSWORD = [2, 5, 8, 0]


def wait_key():
    key = -1
    while key == -1:
        for k in range(12):
            if key_read(k) == 1:
                key = k
        time.sleep(0.02)
    while key_read(key) == 1:
        time.sleep(0.02)
    warning_light(0, 0, 255, WARN_ON)
    time.sleep(0.15)
    warning_light_off()
    return key


def open_door():
    warning_light(255, 165, 0, WARN_ON)
    door_motor(DOOR_UP)
    while door_pos_read() > 0:
        time.sleep(0.05)
    door_motor(DOOR_STOP)
    warning_light_off()


def close_door():
    warning_light(255, 165, 0, WARN_ON)
    door_motor(DOOR_DOWN)
    while door_pos_read() < 100:
        time.sleep(0.05)
    door_motor(DOOR_STOP)
    warning_light_off()


if sim_connect() != 0:
    exit(1)

entered = []
print("Type the code and press #. X clears.")

while True:
    k = wait_key()
    if k <= 9:                             # a digit
        if len(entered) < MAX_DIGITS:
            entered.append(k)
            print("*", end="", flush=True)
    elif k == ADR_KEY_X:                   # clear
        entered = []
        print("\nCleared")
        warning_light(255, 255, 0, WARN_ON)
        time.sleep(0.3)
        warning_light_off()
    else:                                  # # = confirm
        print()
        if entered == PASSWORD:            # same length and same digits
            print("Access granted")
            garage_lamp(LAMP_ON)
            open_door()
            time.sleep(5)
            close_door()
            garage_lamp(LAMP_OFF)
        else:
            print("Wrong code")
            warning_light(255, 0, 0, WARN_ON)
            time.sleep(2)
            warning_light_off()
        entered = []
24

Wrong-attempt counter and lockout

Add security to Task 23: count the wrong attempts and print them like Wrong code (2/3). After 3 wrong attempts in a row the system locks for 15 seconds:
• Print LOCKED for 15 s.
• During the lockout the warning light blinks red (250 ms on, 250 ms off) and keys are ignored.
• When the lockout ends print Unlocked and reset the counter.
The counter is also reset when the correct code is entered. After the 1st and 2nd wrong attempt the light is red for 2 seconds as before.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
25

Obstacle while closing: safety reverse

Real garage doors stop and open again if there is an obstacle while closing. The simulation has no obstacle sensor, so use the X key as an 'obstacle detected' button.
Write a function close_door_safe(): while closing, every 50 ms it checks the X key together with the position. If X is pressed it stops the motor at once, prints Obstacle! Reopening, lights the warning light red for 1 second, opens the door fully and returns 0. If the door closes without an obstacle it returns 1.
Main program: when # is pressed it opens the door, waits 5 seconds and closes it with close_door_safe(); if the door was reopened because of an obstacle it waits 5 seconds and tries again. When the door is closed it prints Door closed.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
26

Timeout during code entry

Add a timeout to the code entry of Task 23: if the user typed at least one digit and then presses no key for 5 seconds, the entry is cleared, Timeout, entry cleared is printed and the warning light is yellow for 1 second.
For this write a function wait_key_timeout(ms): if a key is pressed and released within the given time (with the blue key light) it returns the key number, otherwise -1. Do not move the door in this task: for the correct code print Access granted and light green for 3 seconds, for a wrong one print Wrong code and light red for 2 seconds.

A timeout is found by asking 'current time − start time ≥ limit?'. While waiting, wait_key_timeout() keeps checking the clock and returns -1 when the time is up. When the main loop gets -1 it does nothing if the entry is empty and simply waits again; otherwise it clears the entry. So a half-typed code is not left for the next user.

GetTickCount / time.timetimeoutwait_key_timeout()
#include "garage.h"

#define MAX_DIGITS 8

int password[4] = {2, 5, 8, 0};
int password_len = 4;

// Returns the pressed key (0..11) or -1 if no key came within ms
int wait_key_timeout(DWORD ms)
{
    DWORD start = GetTickCount();
    while (GetTickCount() - start < ms)
    {
        for (int k = 0; k <= 11; k++)
        {
            if (key_read(k) == 1)
            {
                while (key_read(k) == 1) Sleep(20);   // wait for release
                warning_light(0, 0, 255, WARN_ON);
                Sleep(150);
                warning_light_off();
                return k;
            }
        }
        Sleep(20);
    }
    return -1;                                 // time is up
}

int check_code(int entered[], int len)
{
    if (len != password_len) return 0;
    for (int i = 0; i < len; i++)
        if (entered[i] != password[i]) return 0;
    return 1;
}

int main(void)
{
    if (sim_connect() != 0) return 1;

    int entered[MAX_DIGITS];
    int len = 0;
    printf("Type the code and press #. X clears.\n");

    while (1)
    {
        int k = wait_key_timeout(5000);
        if (k == -1)                           // 5 s without a key
        {
            if (len > 0)
            {
                len = 0;
                printf("\nTimeout, entry cleared\n");
                warning_light(255, 255, 0, WARN_ON);
                Sleep(1000);
                warning_light_off();
            }
        }
        else if (k <= 9)
        {
            if (len < MAX_DIGITS)
            {
                entered[len] = k;
                len++;
                printf("*");
                fflush(stdout);
            }
        }
        else if (k == ADR_KEY_X)
        {
            len = 0;
            printf("\nCleared\n");
            warning_light(255, 255, 0, WARN_ON);
            Sleep(300);
            warning_light_off();
        }
        else                                   // #
        {
            printf("\n");
            if (check_code(entered, len))
            {
                printf("Access granted\n");
                warning_light(0, 255, 0, WARN_ON);
                Sleep(3000);
            }
            else
            {
                printf("Wrong code\n");
                warning_light(255, 0, 0, WARN_ON);
                Sleep(2000);
            }
            warning_light_off();
            len = 0;
        }
    }
}
from garage import *
import time

MAX_DIGITS = 8
PASSWORD = [2, 5, 8, 0]


def wait_key_timeout(ms):
    """Returns the pressed key (0..11) or -1 if no key came within ms."""
    start = time.time()
    while (time.time() - start) * 1000 < ms:
        for k in range(12):
            if key_read(k) == 1:
                while key_read(k) == 1:    # wait for release
                    time.sleep(0.02)
                warning_light(0, 0, 255, WARN_ON)
                time.sleep(0.15)
                warning_light_off()
                return k
        time.sleep(0.02)
    return -1                              # time is up


if sim_connect() != 0:
    exit(1)

entered = []
print("Type the code and press #. X clears.")

while True:
    k = wait_key_timeout(5000)
    if k == -1:                            # 5 s without a key
        if len(entered) > 0:
            entered = []
            print("\nTimeout, entry cleared")
            warning_light(255, 255, 0, WARN_ON)
            time.sleep(1)
            warning_light_off()
    elif k <= 9:
        if len(entered) < MAX_DIGITS:
            entered.append(k)
            print("*", end="", flush=True)
    elif k == ADR_KEY_X:
        entered = []
        print("\nCleared")
        warning_light(255, 255, 0, WARN_ON)
        time.sleep(0.3)
        warning_light_off()
    else:                                  # #
        print()
        if entered == PASSWORD:
            print("Access granted")
            warning_light(0, 255, 0, WARN_ON)
            time.sleep(3)
        else:
            print("Wrong code")
            warning_light(255, 0, 0, WARN_ON)
            time.sleep(2)
        warning_light_off()
        entered = []
27

Blinking without stopping the program

Improve the keypad remote control of Task 17: while the door moves the warning light blinks orange (255, 165, 0) (300 ms on, 300 ms off), but the keys still react immediately.
So do NOT blink with Sleep(300) / time.sleep(0.3): the loop runs every 20 ms; in every round read the clock and, if 300 ms have passed since the last change, flip the light. When the motor stops the light stays off. Press 0 while the door is half way and check that it stops at once.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
28

A door state machine

Control the door with a state machine. There are four states: CLOSED, OPENING, OPEN, CLOSING.
• CLOSED: motor stopped, lamp and warning light off. When # is pressed → OPENING.
• OPENING: lamp on, warning light orange, motor up. When the position is 0 → OPEN.
• OPEN: motor stopped, warning light green. After 10 seconds or when # is pressed → CLOSING.
• CLOSING: warning light orange, motor down. When the position is 100 → CLOSED; if X is pressed (obstacle) print Obstacle! and → OPENING.
On every state change print the new state like -> OPEN. If the door is not closed when the program starts, begin with CLOSING. The loop runs every 20 ms with no long Sleep anywhere; keys are read with edge detection (Task 19).

In a state machine the program is always in exactly one state and each state only knows its own exit conditions. The outputs are set once in enter() when the state changes. Because there is no long wait anywhere, the program can watch the X key and the 10-second timer at the same time, even while the door moves. Adding a new behaviour is as easy as adding a new state or transition.

state machineenumenter()new_key()
#include "garage.h"

enum { CLOSED, OPENING, OPEN, CLOSING };
const char *names[] = { "CLOSED", "OPENING", "OPEN", "CLOSING" };

int state = CLOSED;
DWORD open_time = 0;           // when the door became OPEN
int last_key = -1;

// Returns a key number only at the moment it is pressed, otherwise -1
int new_key(void)
{
    int key = -1;
    for (int k = 0; k <= 11; k++)
        if (key_read(k) == 1) key = k;
    int result = -1;
    if (key != last_key) result = key;
    last_key = key;
    return result;
}

// Switches to a new state and sets the outputs once
void enter(int s)
{
    state = s;
    printf("-> %s\n", names[s]);
    if (s == CLOSED)
    {
        door_motor(DOOR_STOP);
        warning_light_off();
        garage_lamp(LAMP_OFF);
    }
    else if (s == OPENING)
    {
        garage_lamp(LAMP_ON);
        warning_light(255, 165, 0, WARN_ON);
        door_motor(DOOR_UP);
    }
    else if (s == OPEN)
    {
        door_motor(DOOR_STOP);
        warning_light(0, 255, 0, WARN_ON);
        open_time = GetTickCount();
    }
    else                                       // CLOSING
    {
        garage_lamp(LAMP_ON);
        warning_light(255, 165, 0, WARN_ON);
        door_motor(DOOR_DOWN);
    }
}

int main(void)
{
    if (sim_connect() != 0) return 1;

    if (door_pos_read() >= 100) enter(CLOSED);
    else                        enter(CLOSING);

    while (1)
    {
        int key = new_key();
        int pos = door_pos_read();

        if (state == CLOSED)
        {
            if (key == ADR_KEY_HASH) enter(OPENING);
        }
        else if (state == OPENING)
        {
            if (pos <= 0) enter(OPEN);
        }
        else if (state == OPEN)
        {
            if (key == ADR_KEY_HASH || GetTickCount() - open_time >= 10000)
                enter(CLOSING);
        }
        else                                   // CLOSING
        {
            if (key == ADR_KEY_X)
            {
                printf("Obstacle!\n");
                enter(OPENING);
            }
            else if (pos >= 100) enter(CLOSED);
        }
        Sleep(20);
    }
}
from garage import *
import time

CLOSED, OPENING, OPEN, CLOSING = 0, 1, 2, 3
NAMES = ["CLOSED", "OPENING", "OPEN", "CLOSING"]

state = CLOSED
open_time = 0.0                # when the door became OPEN
last_key = -1


def new_key():
    """Returns a key number only at the moment it is pressed, otherwise -1."""
    global last_key
    key = -1
    for k in range(12):
        if key_read(k) == 1:
            key = k
    result = -1
    if key != last_key:
        result = key
    last_key = key
    return result


def enter(s):
    """Switches to a new state and sets the outputs once."""
    global state, open_time
    state = s
    print(f"-> {NAMES[s]}")
    if s == CLOSED:
        door_motor(DOOR_STOP)
        warning_light_off()
        garage_lamp(LAMP_OFF)
    elif s == OPENING:
        garage_lamp(LAMP_ON)
        warning_light(255, 165, 0, WARN_ON)
        door_motor(DOOR_UP)
    elif s == OPEN:
        door_motor(DOOR_STOP)
        warning_light(0, 255, 0, WARN_ON)
        open_time = time.time()
    else:                                  # CLOSING
        garage_lamp(LAMP_ON)
        warning_light(255, 165, 0, WARN_ON)
        door_motor(DOOR_DOWN)


if sim_connect() != 0:
    exit(1)

if door_pos_read() >= 100:
    enter(CLOSED)
else:
    enter(CLOSING)

while True:
    key = new_key()
    pos = door_pos_read()

    if state == CLOSED:
        if key == ADR_KEY_HASH:
            enter(OPENING)
    elif state == OPENING:
        if pos <= 0:
            enter(OPEN)
    elif state == OPEN:
        if key == ADR_KEY_HASH or time.time() - open_time >= 10:
            enter(CLOSING)
    else:                                  # CLOSING
        if key == ADR_KEY_X:
            print("Obstacle!")
            enter(OPENING)
        elif pos >= 100:
            enter(CLOSED)
    time.sleep(0.02)

5 Project

Complete systems that combine everything.
29

Complete garage controller

Combine the state machine of Task 28 with the code entry. States: CLOSED, OPENING, OPEN, CLOSING, LOCKED.
• CLOSED: the code is typed on the keypad (Tasks 23–26). Digit → * and 100 ms blue light (at most 8 digits); X → clear the entry, 300 ms yellow; # → check. The code is 2580. If correct, Access granted → OPENING. If wrong, Wrong code (n/3) and 1 s red; on the 3rd wrong one → LOCKED. If no key is pressed for 5 s while the entry is not empty: Timeout, entry cleared and 1 s yellow.
• LOCKED: for 15 s the warning light blinks red (250 ms), keys are ignored; then Unlocked → CLOSED.
• OPENING: lamp on, motor up, warning light blinks orange (300 ms, without stopping the program — Task 27). Position 0 → OPEN.
• OPEN: motor stopped, steady green light. After 10 s or with # → CLOSING.
• CLOSING: motor down, orange blinking. X → Obstacle! Reopening → OPENING. Position 100 → CLOSED.
In CLOSED and LOCKED the motor stops, lamp and warning light are off and the entry is cleared. Print every state change like -> OPEN; if the door is not closed at the start, begin with CLOSING.

The program is a combination of the parts from earlier tasks: edge-detected key reading, the code in an array, the attempt counter, the timeout, blinking without stopping the program and the safety reverse. The state machine keeps them tidy: each state only does its own job. Only the short key lights in CLOSED (100–1000 ms) wait, and the motor is stopped at that time anyway. Because state_time is reset on every new state, the same variable measures both the lockout and the auto-close time.

state machinenon-blockingarray / listtimeoutsafety stop
#include "garage.h"

enum { CLOSED, OPENING, OPEN, CLOSING, LOCKED };
const char *names[] = { "CLOSED", "OPENING", "OPEN", "CLOSING", "LOCKED" };

#define MAX_DIGITS 8
int password[4] = {2, 5, 8, 0};
int password_len = 4;

int state = CLOSED;
DWORD state_time = 0;          // when the current state started
DWORD blink_time = 0;          // last blink change
int blink_on = 0;
int last_key = -1;

int entered[MAX_DIGITS];
int len = 0;
DWORD last_key_time = 0;       // for the entry timeout
int wrong = 0;

int new_key(void)
{
    int key = -1;
    for (int k = 0; k <= 11; k++)
        if (key_read(k) == 1) key = k;
    int result = -1;
    if (key != last_key) result = key;
    last_key = key;
    return result;
}

int check_code(void)
{
    if (len != password_len) return 0;
    for (int i = 0; i < len; i++)
        if (entered[i] != password[i]) return 0;
    return 1;
}

// Short light signal (only used while the motor is stopped)
void flash(int r, int g, int b, int ms)
{
    warning_light(r, g, b, WARN_ON);
    Sleep(ms);
    warning_light_off();
}

// Blinks the warning light without stopping the program
void blink(int r, int g, int b, DWORD period)
{
    if (GetTickCount() - blink_time >= period)
    {
        blink_on = !blink_on;
        if (blink_on) warning_light(r, g, b, WARN_ON);
        else          warning_light_off();
        blink_time = GetTickCount();
    }
}

void enter(int s)
{
    state = s;
    state_time = GetTickCount();
    blink_time = state_time;
    blink_on = 0;
    warning_light_off();
    printf("-> %s\n", names[s]);

    if (s == CLOSED || s == LOCKED)
    {
        door_motor(DOOR_STOP);
        garage_lamp(LAMP_OFF);
        len = 0;
    }
    else if (s == OPENING)
    {
        garage_lamp(LAMP_ON);
        door_motor(DOOR_UP);
    }
    else if (s == OPEN)
    {
        door_motor(DOOR_STOP);
        warning_light(0, 255, 0, WARN_ON);
    }
    else                                       // CLOSING
    {
        garage_lamp(LAMP_ON);
        door_motor(DOOR_DOWN);
    }
}

// CLOSED state: collects and checks the code
void handle_code(int key)
{
    if (key >= 0 && key <= 9)
    {
        if (len < MAX_DIGITS)
        {
            entered[len] = key;
            len++;
            printf("*");
            fflush(stdout);
        }
        last_key_time = GetTickCount();
        flash(0, 0, 255, 100);
    }
    else if (key == ADR_KEY_X)
    {
        len = 0;
        printf("\nCleared\n");
        flash(255, 255, 0, 300);
    }
    else if (key == ADR_KEY_HASH)
    {
        printf("\n");
        if (check_code())
        {
            wrong = 0;
            printf("Access granted\n");
            enter(OPENING);
        }
        else
        {
            wrong++;
            printf("Wrong code (%d/3)\n", wrong);
            len = 0;
            if (wrong >= 3)
            {
                wrong = 0;
                enter(LOCKED);
            }
            else flash(255, 0, 0, 1000);
        }
    }
    else if (len > 0 && GetTickCount() - last_key_time >= 5000)
    {
        len = 0;
        printf("\nTimeout, entry cleared\n");
        flash(255, 255, 0, 1000);
    }
}

int main(void)
{
    if (sim_connect() != 0) return 1;

    printf("Code 2580 + #. X = clear / obstacle. Ctrl+C to quit.\n");
    if (door_pos_read() >= 100) enter(CLOSED);
    else                        enter(CLOSING);

    while (1)
    {
        int key = new_key();
        int pos = door_pos_read();

        if (state == CLOSED)
        {
            handle_code(key);
        }
        else if (state == LOCKED)
        {
            blink(255, 0, 0, 250);
            if (GetTickCount() - state_time >= 15000)
            {
                printf("Unlocked\n");
                enter(CLOSED);
            }
        }
        else if (state == OPENING)
        {
            blink(255, 165, 0, 300);
            if (pos <= 0) enter(OPEN);
        }
        else if (state == OPEN)
        {
            if (key == ADR_KEY_HASH || GetTickCount() - state_time >= 10000)
                enter(CLOSING);
        }
        else                                   // CLOSING
        {
            blink(255, 165, 0, 300);
            if (key == ADR_KEY_X)
            {
                printf("Obstacle! Reopening\n");
                enter(OPENING);
            }
            else if (pos >= 100) enter(CLOSED);
        }
        Sleep(20);
    }
}
from garage import *
import time

CLOSED, OPENING, OPEN, CLOSING, LOCKED = 0, 1, 2, 3, 4
NAMES = ["CLOSED", "OPENING", "OPEN", "CLOSING", "LOCKED"]

MAX_DIGITS = 8
PASSWORD = [2, 5, 8, 0]

state = CLOSED
state_time = 0.0               # when the current state started
blink_time = 0.0               # last blink change
blink_on = False
last_key = -1

entered = []
last_key_time = 0.0            # for the entry timeout
wrong = 0


def new_key():
    global last_key
    key = -1
    for k in range(12):
        if key_read(k) == 1:
            key = k
    result = -1
    if key != last_key:
        result = key
    last_key = key
    return result


def flash(r, g, b, seconds):
    """Short light signal (only used while the motor is stopped)."""
    warning_light(r, g, b, WARN_ON)
    time.sleep(seconds)
    warning_light_off()


def blink(r, g, b, period):
    """Blinks the warning light without stopping the program."""
    global blink_on, blink_time
    if time.time() - blink_time >= period:
        blink_on = not blink_on
        if blink_on:
            warning_light(r, g, b, WARN_ON)
        else:
            warning_light_off()
        blink_time = time.time()


def enter(s):
    global state, state_time, blink_time, blink_on
    state = s
    state_time = time.time()
    blink_time = state_time
    blink_on = False
    warning_light_off()
    print(f"-> {NAMES[s]}")

    if s == CLOSED or s == LOCKED:
        door_motor(DOOR_STOP)
        garage_lamp(LAMP_OFF)
        entered.clear()
    elif s == OPENING:
        garage_lamp(LAMP_ON)
        door_motor(DOOR_UP)
    elif s == OPEN:
        door_motor(DOOR_STOP)
        warning_light(0, 255, 0, WARN_ON)
    else:                                  # CLOSING
        garage_lamp(LAMP_ON)
        door_motor(DOOR_DOWN)


def handle_code(key):
    """CLOSED state: collects and checks the code."""
    global last_key_time, wrong
    if 0 <= key <= 9:
        if len(entered) < MAX_DIGITS:
            entered.append(key)
            print("*", end="", flush=True)
        last_key_time = time.time()
        flash(0, 0, 255, 0.1)
    elif key == ADR_KEY_X:
        entered.clear()
        print("\nCleared")
        flash(255, 255, 0, 0.3)
    elif key == ADR_KEY_HASH:
        print()
        if entered == PASSWORD:
            wrong = 0
            print("Access granted")
            enter(OPENING)
        else:
            wrong += 1
            print(f"Wrong code ({wrong}/3)")
            entered.clear()
            if wrong >= 3:
                wrong = 0
                enter(LOCKED)
            else:
                flash(255, 0, 0, 1)
    elif len(entered) > 0 and time.time() - last_key_time >= 5:
        entered.clear()
        print("\nTimeout, entry cleared")
        flash(255, 255, 0, 1)


if sim_connect() != 0:
    exit(1)

print("Code 2580 + #. X = clear / obstacle. Ctrl+C to quit.")
if door_pos_read() >= 100:
    enter(CLOSED)
else:
    enter(CLOSING)

while True:
    key = new_key()
    pos = door_pos_read()

    if state == CLOSED:
        handle_code(key)
    elif state == LOCKED:
        blink(255, 0, 0, 0.25)
        if time.time() - state_time >= 15:
            print("Unlocked")
            enter(CLOSED)
    elif state == OPENING:
        blink(255, 165, 0, 0.3)
        if pos <= 0:
            enter(OPEN)
    elif state == OPEN:
        if key == ADR_KEY_HASH or time.time() - state_time >= 10:
            enter(CLOSING)
    else:                                  # CLOSING
        blink(255, 165, 0, 0.3)
        if key == ADR_KEY_X:
            print("Obstacle! Reopening")
            enter(OPENING)
        elif pos >= 100:
            enter(CLOSED)
    time.sleep(0.02)
30

Move the door to a chosen position

Move the door to a position chosen on the keypad. The user types a closed percentage from 0 to 100 and confirms with # (e.g. 5, 0, # → 50%). The typed digits are shown on the screen; at most 3 digits are taken. X clears the typed number (Cleared).
• If no digit was typed or the value is above 100, print Invalid and light the warning light red for 1 second.
• If the door is already at that position, print Already there.
• Otherwise the program chooses the direction itself: if the target is smaller than the current value the door goes up, if it is bigger the door goes down; it stops at the target. While moving the warning light is orange and every time the position changes it prints something like 45% -> 50%. At the target it prints Arrived and lights green for 1 second.
• If X is pressed while moving, the door stops at once and Stopped is printed.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
31

Admin mode: changing the code

The code can be changed without changing the program. The user code is 1234 at the start, the admin code is a fixed 9999. Every entry is confirmed with #, X clears it (Cleared); a * is printed for each digit and at most 8 digits are taken.
• User code: Access granted, open the door, wait 5 s, close it (functions from Task 22).
• Admin code: print Admin mode; the warning light stays yellow (255, 255, 0). Print Enter new code: , the user types the new code and presses #; then print Repeat new code: and the same code is typed again. If both entries are the same and have at least 4 digits the code changes: print Code changed and blink the warning light green twice (300 ms on, 300 ms off). Otherwise print Code NOT changed and light red for 2 s.
• Any other entry: Wrong code and 2 s red.
Note: do not use the blue key light in this task, so the steady yellow light is not disturbed.

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.
32

Multi-user garage with a report

Three people use the garage and each has a 4-digit code: Ali 1111, Ayse 2222, Can 3333. Store the names in an array and the codes in a two-dimensional array (a list of lists). Entries are read as in Task 31 (*, X clears, # confirms).
• If a user's code is entered, print Welcome, Ayse, add 1 to that person's opening count, turn the lamp on, open the door, after 5 s close it with close_door_safe() from Task 25 (if it reopened because of an obstacle, try again after 5 s) and turn the lamp off.
• If an unknown code is entered, print Unknown code, add 1 to the wrong-attempt counter and light red for 2 s.
• If only 0 is entered and # pressed, print a report:
Ali: 2 openings
Ayse: 0 openings
Can: 1 openings
Wrong attempts: 1
Total openings: 3

Your turn

No answer is given for this task. Solve it yourself by combining the ideas from the answers of earlier tasks; your working program is your answer.