This tutorial focuses on the basics of radio-controlling a cyber:bot from your terminal. You will be able to type in left and right wheel speeds and a number of milliseconds to execute a maneuver. After you’ve entered those three values, the cyber:bot will do the rest.
The first version of this app will be tethered, with all commands going into the cyber:bot through USB. The second version will be wireless. It will work the same as the tethered app, but you won’t have the constraint of a USB cable any more. At that point, it’ll be a great little script for remotely navigating through mazes.
Wireless cyber:bot control through a terminal builds on the radio countdown example in Send and Receive Packets from the Radio Data Tutorial. Instead of an output that counts down and displays a message, the output will be a cyber:bot that executes the maneuver you typed into the terminal.
You will need:
Complete these tutorials first:
You can use this app to make your cyber:bot remotely navigate a maze. Or, use one micro:bit transmitter to control a whole cyber:bot fleet in a synchronized dance routine!
You will also have more practice with a radio application, simplified by packet communication with key-value pairs. The transmitter script will packetize and transmit the speed and maneuver time values you type. The receiver will parse the packet it receives and then execute the maneuver.
You will also be ready to move on to the next tutorial (with more coming soon!):
Most radio applications started as working prototypes connected by wires. At some point in the development process, the wires get replaced by a radio link. When it comes to robots, we refer to that as tethered control. When a radio link replaces the wires, it becomes radio controlled (RC), or sometimes wireless controlled.
One reason many devices are developed with wired connections is that it simplifies troubleshooting. The engineers doing the development do not have to worry about whether the radio is having problems if they are using wires. Once the system behaves well with the wired connection, the next step is to replace the tether with a radio link.
In this tutorial we will do the same thing: start with tethered keyboard control of the cyber:bot, then replace the tether with a radio link.
This is a simple tethered application where you type in left and right wheel speeds, and run time in milliseconds, into a terminal. When you press Enter, your text gets parsed and executed by the micro:bit, which then makes the cyber:bot execute the maneuver.
Example Script: terminal_controlled_bot_tethered_intro
# terminal_controlled_bot_tethered_intro from cyberbot import * sleep(1000) print("\nSpeeds are -100 to 100\n") while(True): text = input("Enter left speed: ") vL = int(text) text = input("Enter right speed: ") vR = int(text) text = input("Enter ms to run: ") ms = int(text) bot(18).servo_speed(vL) bot(19).servo_speed(-vR) sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None) print()
Since your cyber:bot is currently tethered, it’s best to use low speeds and short run times. Otherwise, your cyber:bot might try to roll beyond the tether length. In other words, it’ll try to unplug itself from the USB cable, or maybe roll off the table.
For example, what happens if you type abc and press enter? For now, you can press/release the reset button to restart the app. You will add some exception handling in the your turn section to prevent this from happening.
Since this script involves cyber:bot motion, it imports the cyberbot module. The first print statement (after the recommended 1 second startup delay) just reminds the person typing in the terminal that valid speeds are from -100 to 100. The \n escape characters in the print string add lines before and after the “Speeds are -100 to 100” message.
# terminal_controlled_bot_tethered_intro from cyberbot import * sleep(1000) print("\nSpeeds are -100 to 100\n")
The main loop starts with three input statements getting text from the user that represents values. As mentioned in Terminal Talk [2], the input statement returns the character representations of the characters. Each one is stored in the temporary variable named text. The first one is text = input("Enter left speed: "). Then, vL = int(text) converts the text representation of a number to an actual value an int variable can store. Remember, once it’s in an int variable, it can be used in calculations.
while(True): text = input("Enter left speed: ") vL = int(text) text = input("Enter right speed: ") vR = int(text) text = input("Enter ms to run: ") ms = int(text)
In addition to working in calculations, int variables are required by the bot(pin).servo_speed(vL or vR) method as well as the sleep(ms) function call. When you typed 25 in response to the Enter left speed: prompt, the "25" string was stored in text. That won’t work for servo_speed or sleep. That’s why vL = int(text) stores the int version of the "25" string in the vL variable. The same applies to vR, and to ms for the sleep call.
Once vL, vR, and ms are all values stored in int variables, the script uses vL to set the left (P18) servo speed, and vR to set the right (P19) servo speed. Did you notice the negative sign in bot(19).servo_speed(-vR)? That makes it so that you can type positive values for left and right servo speeds and the result is forward motion. No more remembering that left is counterclockwise and right is clockwise for forward motion!
bot(18).servo_speed(vL) bot(19).servo_speed(-vR)
The ms int variable is used in the sleep function’s argument so that the servos continue running for the ms time you typed into the terminal. After that, servo_speed calls with the None arguments stop the servos from turning and complete the maneuver.
sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None) print()
After this, the while(True) loop repeats, and you can type in another maneuver for the cyber:bot to execute.
Input to int will always be a two step process, but it doesn’t have to take up two lines. For example,
text = input("Enter left speed: ") vL = int(text)
… can be condensed to:
vL = int(input("Enter left speed: "))
Expressions and statements contained by parentheses are evaluated from the inside toward the outside. So, in this case the result text string result of the digits you typed is returned by input("Enter left speed: "). Then, that result (like the string "25") is converted into an int with the outer int(…), and then stored in vL.
If you accidentally type a character like ‘q’ instead of the number 1, it will cause an exception. That’s because the int(…) function needs something with digits to convert to an int type. It can convert the string "25" to the value 25, but it cannot convert the string "q5" to any value.
The Exception Handling Primer [4] addressed a more methodical and comprehensive way of dealing with this. True, for apps lots of people use, that’s best, but there are lighter treatments for prototyping. For example, all of the input and servo code can go into one try statement, and an except can just have a helpful message about typing numbers.
(Note this is not a complete script.)
while(True): try: vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: ")) bot(18).servo_speed(vL) bot(19).servo_speed(-vR) sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None) print() except: print("Error in value entered.") print("Please try again. \n")
Let’s try reducing the input to int line count from six to three.
text = input("Enter left speed: ") vL = int(text) text = input("Enter right speed: ") vR = int(text) text = input("Enter ms to run: ") ms = int(text)
…with these three lines:
vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: "))
# terminal_controlled_bot_tethered_try_this from cyberbot import * sleep(1000) print("\nSpeeds are -100 to 100\n") while(True): vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: ")) bot(18).servo_speed(vL) bot(19).servo_speed(-vR) sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None) print()
As mentioned in the previous Did You Know section, sometimes a catchall exception handler is okay for prototyping.
Here is an example where a typing error is likely when entering numbers. All the exception handler needs to do is tell you there was a mistake and then allow the while(True) loop to repeat.
# terminal_controlled_bot_tethered_your_turn from cyberbot import * sleep(1000) print("\nSpeeds are -100 to 100\n") while(True): try: vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: ")) bot(18).servo_speed(vL) bot(19).servo_speed(-vR) sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None) print() except: print("Error in value entered.") print("Please try again. \n")
Now that you have keyboard control of your cyber:bot working with a USB tether, it's time to take the app wireless!
The transmitter and receiver scripts we will use may be familiar. They are in the Send and Receive Packets [8] activity; it’s the second one in the Cybersecurity: Radio Data [9] tutorial. Each script will get parts of the tethered script added, and some adjustments to make it all work.
Let's build on experience, and adapt a familiar script from a previous tutorial to use keyboard inputs.
Starting with the countdown_sender script from the Send and Receive Packets [8] activity, you can replace its input statements with the ones from terminal_controlled_bot_tethered_try_this. Then, the dictionary that’s created has to be adjusted to contain the vL, vR, and ms keys and values. That’s the most crucial part of incorporating the terminal-in, radio-out part of your tethered app into script that wirelessly transmits.
This animated GIF shows the script being modified:
You will also need to make a few other adjustments. Here they are, step-by-step:
vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: "))
text = input("Enter countdown start: ") value = int(text) message = input("Enter message after countdown: ")
dictionary['start'] = value dictionary['after'] = message
dictionary['vL'] = vL dictionary['vR'] = vR dictionary['ms'] = ms
print("Countdown App") print("micro:bit sender")
Replace them with this line:
print("\nSpeeds are -100 to 100\n")
radio.config(channel=7,length=50)
It should look like this:
radio.config(channel=7,length=64)
Now, your script should be ready.
# terminal_bot_controller_wireless.py from microbit import * import radio radio.on() radio.config(channel=7,length=64) sleep(1000) print("\nSpeeds are -100 to 100\n") while True: vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: ")) dictionary = { } dictionary['vL'] = vL dictionary['vR'] = vR dictionary['ms'] = ms packet = str(dictionary) print("Send: ", packet) radio.send(packet) print()
The receiver script is also adapted from a previous tutorial.
Starting with the countdown_receiver.py from the Send and Receive Packets [8] activity, you can replace its countdown while loop and printed message with the servo_speed and sleep(ms) calls from terminal_controlled_bot_tethered_try_this. You will also need to update the dictionary parsing so that it gets the vL, vR, and ms values using the 'vL', 'vR', and 'ms' keys. That’s the most crucial part of incorporating the radio-in to cyber:bot navigation-out part of your tethered app into script that wirelessly receives.
Here's a GIF showing the modification taking place:
You will also need to make a few other adjustments. Here they are step-by-step:
bot(18).servo_speed(vL) bot(19).servo_speed(-vR) sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None)
print("value = ", value) print("message = ", message, "\n") while value >= 0: print(value) sleep(1000) value = value - 1 print(message)
value = dictionary['start'] message = dictionary['after']
vL = dictionary['vL'] vR = dictionary['vR'] ms = dictionary['ms']
print() print("Parse: ")
print("Countdown App") print("micro:bit sender")
print("Ready...\n")
radio.config(channel=7,length=50)
It should look like this:
radio.config(channel=7,length=64)
Now, your script should be ready.
# terminal_controlled_bot_wireless from cyberbot import * import radio radio.on() radio.config(channel=7,length=64) sleep(1000) print("Ready...\n") while True: packet = radio.receive() if packet is not None: print("Receive: ", packet) dictionary = eval(packet) vL = dictionary['vL'] vR = dictionary['vR'] ms = dictionary['ms'] bot(18).servo_speed(vL) bot(19).servo_speed(-vR) sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None)
Now, it’s time to reconnect your transmitter micro:bit and start radio-broadcasting navigation commands for your cyber:bot.
Next, repeat the tests you typed into the app when it was tethered:
If you take a look at the dictionary that was displayed, it should contain {'ms': 750, 'vR': 25, 'vL': 25}. Remember from the Dictionary Primer [10], order does not matter for dictionaries because they keep the correct values paired with the correct keys.
The import statements and radio calls are similar to the countdown_transmitter.py script from Send and Receive Packets [8]. The only difference is that radio.config has length set to 64 to accommodate a packet string with up to 64 characters.
# terminal_bot_controller_wireless from microbit import * import radio radio.on() radio.config(channel=7,length=64)
As usual, we want at least a 1 second pause before printing anything to make sure the browser terminal is ready. Then, instructions for the range of speeds you’ll use are printed.
sleep(1000) print("\nSpeeds are -100 to 100\n")
The main loop starts with the input statements from terminal_controlled_bot_tethered_your_turn.
while True: vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: "))
Next, the values are stored in a dictionary. This makes it easy because the receiver just has to do the reverse to get the vL, vR, and ms values back out of the dictionary it received. For more background on this, see the Dictionary Primer [10].
dictionary = { } dictionary['vL'] = vL dictionary['vR'] = vR dictionary['ms'] = ms
The dictionary is converted to a string named packet. That packet is displayed with a print statement, and then transmitted with a call to radio.send.
packet = str(dictionary) print("Send: ", packet) radio.send(packet) print()
Like the transmitter script, the wireless receiver script’s import and radio calls are also similar to countdown_receiver.py. The radio.config call's length is set to 64 to accommodate longer strings if needed. More importantly, since it has to actually control the cyber:bot, from microbit import * was changed to from cyberbot import *.
# terminal_controlled_bot_wireless from cyberbot import * import radio radio.on() radio.config(channel=7,length=64) sleep(1000) print("Ready...\n")
Inside the main loop, the script rapidly and repeatedly checks the radio.receive() method. If the micro:bit has not received a radio message, it returns None. If the micro:bit receives a packet (because you finished typing all three values and pressed Enter), then radio.receive returns a string that contains the dictionary. That gets stored in a variable named packet.
while True: packet = radio.receive()
When radio.receive returns None, the rest of the script gets skipped and the while(True) loop repeats. When radio.receive returns a string (with characters that represent a dictionary), the statements under if packet is not non are executed, starting with printing the packet. (You can only see that if you have the cyber:bot tethered and connected to a terminal.)
if packet is not None: print("Receive: ", packet)
This statement converts a string containing a dictionary into an actual dictionary. The result is named dictionary.
dictionary = eval(packet)
Assuming you typed 25, -25, 1000 in response to the prompts, the dictionary will be something like {'vL': 25, 'vR': -25, 'ms': 1000}. (Again, the order of the key-value pairs doesn’t matter since you use the key to find its corresponding value.) These statements use the 'vL', 'vR', and 'ms' keys to fetch the 25, -25, and 1000 values.
vL = dictionary['vL'] vR = dictionary['vR'] ms = dictionary['ms']
With the variables named vL, vR, and ms now storing the correct values, all that’s left is to make the left and right wheels turn at 25 and -25. And, keep that maneuver going for 1000 ms with sleep before stopping the servos again.
bot(18).servo_speed(vL) bot(19).servo_speed(-vR) sleep(ms) bot(18).servo_speed(None) bot(19).servo_speed(None)
Just as we added exception handling to the tethered prototype, we need to add code that deals with unusable characters typed in for the left and right wheel speeds, and the run time in milliseconds.
What happened? Did an exception occur?
If you haven't done the the Exception Handling Primer [4] yet, you may want to read through it to understand what is going on here!
In any case, if you follow the steps in the next Your Turn section, your wireless app will be equipped to deal with such user entry errors without leaving your cyber:bot stranded.
The same simple approach for prototyping that was used in tethered control can also be used here.
# terminal_bot_controller_wireless_your_turn from microbit import * import radio radio.on() radio.config(channel=7,length=64) sleep(1000) print("\nSpeeds are -100 to 100\n") while True: try: vL = int(input("Enter left speed: ")) vR = int(input("Enter right speed: ")) ms = int(input("Enter ms to run: ")) dictionary = { } dictionary['vL'] = vL dictionary['vR'] = vR dictionary['ms'] = ms packet = str(dictionary) print("Send: ", packet) radio.send(packet) print() except: print("Error in value entered.") print("Please try again. \n")
Links
[1] https://learn.parallax.com/tutorials/robot/cyberbot/navigation-cyberbot
[2] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk
[3] https://learn.parallax.com/tutorials/robot/cyberbot/dictionary-primer
[4] https://learn.parallax.com/tutorials/robot/cyberbot/exception-handling-primer
[5] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-basics
[6] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-data
[7] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-tilt-control
[8] http://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-data/send-and-receive-packets
[9] http://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-data
[10] http://learn.parallax.com/tutorials/robot/cyberbot/dictionary-primer