"""Step 5 - Drive the car with the keyboard.

WARNING: the car MOVES. Put it on the floor first.
It can fall off a table.

Type keys, then press Enter:
    w = forward     s = back
    a = turn left   d = turn right
    q = spin left   e = spin right
    x = quit

You can type more than one key. "wwd" goes forward twice, then turns.
Every key moves the car for a short time. Then the car stops by itself.
So the car can never run away from you.

Run it:
    Windows:  python move.py
    Mac:      python3 move.py

Install bleak first:
    Windows:  python -m pip install bleak
    Mac:      python3 -m pip install bleak

On a Mac, say yes when it asks for Bluetooth.
If you miss it: System Settings > Privacy and Security >
Bluetooth > turn on your Terminal app.
"""
import asyncio

from bleak import BleakClient, BleakScanner

BOX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"

# One key, one command.
KEYS = {
    "w": "A",   # forward
    "s": "B",   # back
    "a": "C",   # turn left
    "d": "D",   # turn right
    "q": "E",   # spin left
    "e": "F",   # spin right
}

# How long one key moves the car.
# Make it bigger for longer steps.
STEP = 0.5


async def say(car, command):
    """Send one command to the robot."""
    await car.write_gatt_char(BOX, command.encode() + b"#", response=False)
    await asyncio.sleep(0.05)


async def find_robot():
    return await BleakScanner.find_device_by_filter(
        lambda d, a: d.name and "micro:bit" in d.name)


async def drive(car):
    print()
    print("w = forward     s = back")
    print("a = turn left   d = turn right")
    print("q = spin left   e = spin right")
    print("x = quit")
    print()

    while True:
        # Wait for typing. to_thread keeps Bluetooth alive while we wait.
        line = await asyncio.to_thread(input, "> ")
        line = line.strip().lower()

        if line == "x":
            return

        for key in line:
            if key not in KEYS:
                print("  I do not know", key)
                continue
            await say(car, KEYS[key])       # go
            await asyncio.sleep(STEP)
            await say(car, "0")             # stop again


async def main():
    print("Looking for the robot...")
    robot = await find_robot()
    if robot is None:
        print("No robot. Is it on? Is the phone app closed?")
        return
    print("Found it:", robot.name)

    async with BleakClient(robot) as car:
        print("Put the car on the floor.")
        try:
            await drive(car)
        finally:
            # This always runs. So the car always stops.
            for i in range(3):
                await say(car, "0")
                await asyncio.sleep(0.15)
            print("Stopped.")

    print("Done.")


asyncio.run(main())
