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 [1], 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 [2] 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")
Links
[1] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk
[2] https://learn.parallax.com/tutorials/robot/cyberbot/exception-handling-primer