Calico Myro
Myro is an interface for programming robots. It is implemented in many languages and designed for use in Introductory Computing courses. It is being developed by the Institute for Personal Robots in Education.
This page describes the Calico Python interface. To find out more about using Myro with robots, see Using Calico with Robots.
If you were familiar with the previous version of Python Myro, please see Calico Differences.
Contents
- 1 Getting Started
- 2 Output Functions
- 3 Input Functions
- 4 Movement Functions
- 5 Reading Sensors
- 6 Setting Values
- 7 Advanced 1
- 8 Advanced 2
- 9 Advanced 3
- 10 Advanced 4
- 11 Flow of Control
- 12 Image processing
- 13 Graphics Objects Interface
- 14 Miscellaneous commands
- 15 Audio
- 16 Gamepad
- 17 Controlling Multiple Robots
Getting Started
All the commands from Myro are available in Python by importing them. Here is a quick start list:
- Double-click on the "calico.bat" file in the Calico folder on the desktop
- Enter: from Myro import *
- Turn on the robot, and connect via Bluetooth (using outgoing port number)
- Now, you can enter init("COM4") port of your robot, where "COM4" might be another string (see Calico Download#Optional: Using Calico with Robots) or "sim" (see below)
Robots
There are currently 6 robots you can use:
- Scribbler
- SimScribbler
- NxtRobot
- Finch
- Hummingbird
- Arduino
Scribbler
Normally, you can simply init()
.
You can control multiple robots (say, on COM5, COM6, and COM7, with:
robot1 = makeRobot("Scribbler", "COM5") robot2 = makeRobot("Scribbler", "COM6") robot3 = makeRobot("Scribbler", "COM7")
The robots can be controlled individually using commands like robot1.forward(1, 1).
Simulation
Calico comes with a simulator, so you can do most of the robot activities without a real robot. See Calico Simulator for more details.
NxtRobot
You can use:
robot = makeRobot("NxtRobot")
to have access to Nxt-specific functionality.
Finch
See Finch for details on the Finch.
Hummingbird
See Hummingbird for details on the Hummingbird robot kit.
Testing
To test that everything is working, try:
python> from Myro import *
If you have a gamepad:
python> gamepad()
Otherwise:
python> joystick()
Manual Drive
joystick(): opens a joystick window; click and drag to move robot.
Output Functions
beep(duration, frequency, frequency2 = None): make a tone. If two tones are given, the robot will combine them.
speak(message, async = 0) - text-to-speech, turns message into spoken words
getVoice() - get the voice of the current speaker
getVoices() - get a list of all of the possible voices
getVoiceNames() - get a list of all of the possible voice names
setVoice(name) - set the voice to a known voice by name
setVoiceName(name) - set the voice to a known voice by name
Phonemes
The eSpeak system, which comes with Calico for Windows and Mac, and can be installed for Linux, offers the ability to turn words into text:
Myro.speak("Hello world!")
But you can also use it to pronounce Phonemes. Phoneme mnemonics can be used directly in the text input to speak. They are enclosed within double square brackets. Spaces are used to separate words, and all stressed syllables must be marked explicitly. For example:
Myro.speak("[[D,Is Iz sVm f@n'EtIk t'Ekst 'InpUt]]")
Each of the language's phonemes is represented by a mnemonic of 1, 2, 3, or 4 characters. Together with a number of utility codes (eg. stress marks and pauses). The utility 'phonemes' are:
' primary stress , secondary stress % unstressed syllable = put the primary stress on the preceding syllable _: short pause _ a shorter pause || indicates a word boundary within a phoneme string | can be used to separate two adjacent characters, to prevent them from being considered as a multi-character phoneme mnemonic
For more details see:
- http://espeak.sourceforge.net/phonemes.html
- http://www.kirshenbaum.net/IPA/ascii-ipa.pdf (not all phonemes are encoded)
Input Functions
ask(item) - will ask one time for item(s)
python> ask("Name") (window pops up, user enters Sarah, presses Ok button) 'Sarah'
input(prompt) - will prompt user for input. Identical to ask. This always returns a string.
python> input("How old are you? ") How old are you? 19 '19'
Note that input() and ask() both return strings. You can use the eval() function to turn strings into values. For example:
python> eval(input("How old are you? ")) How old are you? 19 19
This gives you the number 19, rather than the string '19'. Also:
python> eval(input("Enter x,y: ")) Enter x,y: 34, 56 (34, 56)
In this example, eval will turn the string '34, 56' into the tuple of numbers (34, 56).
askQuestion(question, [answerList]) - prompt a question and return answer.
python> askQuestion("Are you ready?") (window pops up, users selects Yes) 'Yes' python> askQuestion("How many lumps would you like?", ["One", "Two", "Three"]) (window pops up, user selects Three) 'Three'
yesno("Are you ready?") - asks a yes/no question and provides two buttons, "Yes" and "No". Returns "Yes" or "No". Same as askQuestion() with no answerList.
python> yesno("Are you ready?") (window pops up, users selects Yes) 'Yes'
Movement Functions
forward(amount, seconds): move forward, stop any rotation, for number of seconds
python> forward(1, .5)
backward(amount, seconds): move backward, stop any rotation, for number of seconds
python> backward(.9, 2)
turnLeft(amount, seconds): turn left, stop any forward movement, for number of seconds
python> turnLeft(.4, 1)
turnRight(amount, seconds): turn right, stop any forward movement, for number of seconds
python> turnRight(.5, 1)
stop(): stop all movement
python> stop()
translate(amount): move forward and backwards. 0 to 1 moves forward; 0 to -1 moves backwards rotate(amount): turn left or right. 0 to 1 turns left, 0 to -1 turns right
NOTE: translate and rotate are independent, although they may both effect each the wheel. That means that a translate() and a rotate() will blend into a meaningful combination.
python> translate(1) # full speed ahead python> translate(-1) # full speed backwards python> translate(0) # stop in the translate direction python> rotate(.5) # half-speed to the left python> rotate(-1) # full speed to the right
move(translate, rotate): rotate and translate
python> move(0, 1) # turn full speed to left python> move(0, -1) # turn full speed to right python> move(1, 1) # turn full speed to left while moving full speed ahead python> move(.5, 0) # go forward half speed
motors(left, right): control the left and right motors
motors(left, right, time): control the left and right motors for a given time (seconds), and then stop.
Reading Sensors
getLight(pos): read a light sensor on the scribbler; defaults to "all"
getIR(pos): read an IR sensor on the scribbler; defaults to "all"
getLine(pos): read line sensor on the scribbler; defaults to "all"
getStall(): read stall sensor on the scribbler
NOTE: Every time you issue a move command, the stall sensor resets, and it needs to wait a short time to see whether the motors are stalled. This means that the sensor won’t give accurate results if you test it too soon after the robot starts to move.
getName(): read the robot's name
getPassword(): read the robot's password
getAll(): read all positions of all of the major sensors; returns a dictionary
getVolume(): returns 0 or 1
getData(): get some bytes stored in the robots memory
getInfo(): retrieve information about the robot
getBright("left" | "middle" | "center" | "right" | 0 | 1 | 2): read one of the Fluke's virtual light sensors. The Fluke's virtual light sensors report the total intensity in the left, center, and right sides of the Fluke's camera.
getObstacle("left" | "middle"| "center" | "right" | 0 | 1 | 2): read one of the Fluke's IR obstacle sensors (see setIRPower below). Higher values mean that IR light is being reflected (e.g an obstacle is detected), a low value means IR is not being reflected and there seems to be open space in that direction. The value ranges from 0 to 6400.
getBattery() gets the voltage of the battery (note: If the battery drops below ~6.1V the Fluke's back LED will flash to alert you to change or preferably recharge your batteries)
get(sensor): read the sensor "stall"; or get all readings of "ir", "light", or "line"; or get "all" which is all of those. Also can get "config".
get("config"): returns meta data about the robot's hardware
get(sensor, pos): read any of the following sensors or items by name and position
python> get("stall") 0 python> get("light", 0) 128 python> get("line") [0, 0] python> get("all") {'light': [235, 13], 'line': [1, 0], 'ir': [0, 0], 'stall': 0} python> get("all") # using a fluke {'battery': 6.2436550642715174, 'light': [13176, 3058, 1848], 'ir': [1, 1], 'obstacle': [0, 0, 0], 'bright': [193536, 193536, 193536], 'stall': 0, 'blob': (0, 0, 0), 'line': [1, 1]}
senses() - show all of the sensor readings in a window.
Setting Values
setLED(position, value): set a LED
python> setLED("left", "on") python> setLED("right", "off")
setName(name): set the robot's name (limit of 16 characters)
python> setName("Howie")
setVolume(level): set the speaker's volume (0/"off" or 1/"on")
python> setVolume("off") python> setVolume(0) python> setVolume("on") python> setVolume(1)
setData(position, value): set a byte of data in the robot's memory to a value between 0 and 255.
setLEDFront(value): turn on the led on the front of the Fluke (0 for off and or 1 for on)
python> setLEDFront(1) # turn on the Fluke's front LED python> setLEDFront(0) # turn off the Fluke's front LED python> set("led", "front", 0) # turn off the Fluke's front LED
setLEDBack(value): turn on the LED on the back of the Fluke. The brightness of this LED is configurable between 0-1.
python> setLEDBack(0.5) # turn on the Fluke's back LED at 1/2 brightness python> setLEDBack(1.0) # turn on the Fluke's back LED at full brightness python> set("led", "back", 0) #turn off the Fluke's back LED
setIRPower(power): set the output power of the Fluke's IR obstacle sensors (defaults to 135). If getObstacle() always reports high values try lowering the IR output power. If you always receive a zero value, try increasing the power. The power value should be between 0 and 255.
python> setIRPower(135) python> setIRPower(140)
darkenCamera(level): turn off the camera's auto-exposure, auto-gain, and lower the gain to "level:. This is useful when using the getBright() virtual light sensors.
manualCamera(gain=0, brightness=128, exposure=65): turn off the camera's auto-exposure, auto-gain, and auto-white balance, and set the gain (integer in the range 0-63), brightness (integer in the range 0-255), and exposure manually (integer in the range 0-255). (In version 2.8.3 of Myro)
autoCamera(): turn on the auto-exposure, auto-gain, and auto-color-balance.
set(item, value): set a value (for "name", and "volume")
set(item, position, value): set a value (for "led")
python> set("name", "Duckman") python> set("led", "center", "off") python> set("volume", "off")
Advanced 1
The following are new functions added to the Scribbler2 (red Scribbler). The firmware for using these functions is now available (Feb 23, 2012) and is the default when you upgrade (see Firmware Upgrade). In Jigsaw they appear in the Advanced 1 tab when you Use the Myro module (menu -> Edit -> Use a Module -> Myro).
getEncoders(): reads and returns the left and right encoders
getEncoders(bool): if passed True the encoder count is reset
getDistance(): returns a number between 0-100 correlated roughly with distance from S2 IR sensors
setS2Volume(): set volume 0-100
beep(0, frequency): beeps frequency until you tell it not to.
getMicrophone(): returns a sample of the microphone correlated to ambient sound level (i.e. how loud)
The following Advanced commands are currently being tested. They work with the Scribbler2 real robot.
moveTo(x, y, "mm" | "s2"): move to position x, y in global coordinates (defaults to mm)
moveBy(x, y, "mm" | "s2"): move by x, y in global coordinates (defaults to mm)
turnTo(angle, "deg" | "s2"): move to angle (defaults to degrees)
turnBy(angle, "deg" | "s2"): move by angle (defaults to degrees)
arcTo(x, y, radius, "mm" | "s2"): arc to position x,y with radius (defaults to mm)
arcBy(x, y, radius, "mm" | "s2"): arc by position x,y with radius (defaults to mm)
arc(degrees, radius): move so many degrees along an arc (radius in mm). Negative degrees make the robot move forward and to the right, positive degrees make the robot move backward to the right. - does not update position and angle, yet
getPosition(): the position (in mm) used by turnTo, turnBy, moveTo, moveBy, arcTo, and arcBy
getAngle(): the angle (in degrees) used by turnTo, turnBy, moveTo, moveBy, arcTo, and arcBy
setPosition(x, y): set the current position (in mm) used by turnTo, turnBy, moveTo, moveBy, arcTo, and arcBy
setAngle(angle): set the current angle (in degrees) used by turnTo, turnBy, moveTo, moveBy, arcTo, and arcBy
Advanced 2
The following functions will work on the Finch robot. In Jigsaw they appear in the Advanced 2 tab when you Use the Myro module (menu -> Edit -> Use a Module -> Myro).
getTemperature(): get the current temperature
getAccelerometer(dimension): get the movement on the "x", "y", or "z" dimension
Advanced 3
The following functions will work on the quad-copter ARDrone robot. In Jigsaw they appear in the Advanced 3 tab when you Use the Myro module (menu -> Edit -> Use a Module -> Myro).
- takeoff()
- land()
- left(power, [duration])
- right(power, [duration])
- up(power, [duration])
- down(power, [duration])
- getLocation()
Advanced 4
The following functions will work on the Arduino robot. In Jigsaw they appear in the Advanced 4 tab when you Use the Myro module (menu -> Edit -> Use a Module -> Myro).
- analogRead(port)
- digitalRead(port)
- analogWrite(port, value)
- digitalWrite(port, value)
- pinMode(port, mode)
- makeInput(port)
- makeDigitalOutput(port)
- makePWMOutput(port)
- makeServoOutput(port)
NOTE: port is an integer.
Flow of Control
If you wished to perform a loop for 5 seconds, you could use the following idiom:
for seconds in timer(5): print "running for", seconds, "..."
Parallel Code
Myro allows running functions in parallel, at the same time. For example, this allows you to write programs that control multiple robots in synchronicity.
There are 5 variations, but they largely follow the same pattern:
python>>> doTogether(functions(s), argument(s))
- doTogether([f1, f2, ...]): will run f1(), f2() at the same time and return their results in a list
- doTogether([f1, f2, ...], arg): will run f1(arg), f2(arg) at the same and return their results in a list
- doTogether(f1, [arg1, arg2, ...]): will run f1(arg1), f1(arg2) at the same time and return their results in a list
These allow running either multiple functions with no arguments or with one, or running the same function on multiple arguments.
If you want to run different arguments for different functions, this does not work:
python>>> doTogether( f1(a1, a2), f2(a3, a4) )
Why? Well, Python will call the funtions f1 and f2, and pass the resulting values to doTogether(). Rather, we want to call and run the functions at the same time. To do that, we pass in the function and arguments which will be called in the appropriate manner. To allow this, we pass in the function and arguments as a list:
4. doTogether([f1, arg1, ...], [f2, arg2, ...], ...): will run f1(arg1), f2(arg2) at the same time and return their results in a list
Finally, there is a variation of #1 which allows you to leave out the list:
5. doTogether(f1, f2, ...): will run f1(), f2() at the same time and return their results in a list
Image processing
The following objects and functions are related to the camera functions on the Scribbler. There are two different interfaces: the Multimedia interface (largely based on Mark Guzdial's introductory book), and the Graphics Object interface (largely based on John Zelle's introductory book). These are independent; however, there are methods to move between the two. The first library is built on the second.
Creating a picture, manually, from a file, or from the robot:
picture = takePicture("color" | "gray" | "blob") # gets image from robot picture = makePicture(filename) # reads in a image file, examples: PNG, JPG, GIF picture = makePicture(columns, rows) # creates a blank picture picture = makePicture(columns, rows, array) # creates a new picture from an array (a column-major sequence of 0-255 values that represent Red, Green, Blue (in that order)) picture = makePicture(columns, rows, array, mode)# creates a new picture from an array, where mode = "color", or "gray" pictureCopy = copyPicture(picture) # creates a copy of picture
Output:
show(picture) savePicture(picture, filename) savePicture([picture, picture, ...], filename) # creates an animated GIF savePicture([picture, picture, ...], filename, duration) # creates an animated GIF, with duration between frames savePicture([picture, picture, ...], filename, duration, repeat) # creates an animated GIF, repeat the loop?
The show(picture) function has a couple of mouse functions. You can click with the left mouse on a pixel and you can see "(x,y): (r,g,b)" in the window's status bar. If you click and drag a rectangle in the window, you will set the blob-tracking colors to the ranges of colors inside the bounding box.
show() can also take an optional name:
show(pic2, "name2")
so that you can show more than one image at a time.
Image dimensions:
int_value = getWidth(picture) int_value = getHeight(picture)
Creating and manipulating color objects:
color = makeColor(r, g, b) color = makeColor("name") color = makeColor("#hexcode") color = pickAColor() color = getColor(pixel) setColor(pixel, color)
Pixel manipulation:
pixel = getPixel(picture, x, y) pixels = getPixels(picture) int_value = getRed(pixel) int_value = getGreen(pixel) int_value = getBlue(pixel) int_value = setRed(pixel, color) int_value = setGreen(pixel, color) int_value = setBlue(pixel, color) int_value = getX(pixel) int_value = getY(pixel) r, g, b = getRGB([color | pixel])
Red, green, and blue int_values are between 0 and 255, inclusive.
The getPixels() function is designed to be used with a for-statement, like so:
for pixel in getPixels(picture): # do something with each pixel
getPixels() returns a generator object. To get a pixel by index, you could first turn it into a list:
mylist = list(getPixels(picture))
You can also save any picture to a file:
savePicture(picture, filename) # does the same as above, but has intuitive name
copyPicture() is effectively defined as:
def copyPicture(picture): newPicture = makePicture(getWidth(picture), getHeight(picture)) for x in range(getWidth(picture)): for y in range(getHeight(picture)): setColor(getPixel(newPicture, x, y), getColor(getPixel(picture, x, y))) return newPicture
Examples
Processing by rows and cols:
from Myro import * picture = makePicture(pickAFile()) show(picture) for i in range(getWidth(picture)): for j in range(getHeight(picture)): pixel = getPixel(picture, i, j) setGreen(pixel, 255)
Processing by each pixel:
from Myro import * picture = makePicture(pickAFile()) show(picture) for pixel in getPixels(picture): setGreen(pixel, 255)
Advanced Vision Functions
The Fluke can not only take images, but it can do some very simple image processing. In particular, a simple form of on-board image segmentation. If you want to track something in an image based on its color or brightness, the on-board color segmentation can be very useful since its faster than doing it in python. By default, the Fluke is set to track pink objects. You can also graphically select an object in the image to track. First use the show(picture) function and then drag a box around the object you want to track, the software will automatically determine the color bounding box.
We can use the Fluke's computer vision in two ways. First, we can grab a "blob" image from the Fluke. This image is a black and white image with the white pixels being part of the object of interest. Blob images can be transmitted faster than a full color picture.
p = takePicture() show(p) # select object in the image b = takePicture("blob") show(b)
For instance, here we see two images of a dog and her toy. The first is a regular color picture and the second is a blob image with the pink dog toy selected.
Another useful function is getBlob() that will return three items, the total number of pixels that fell inside the bounding box, and the average x and y locations of those pixels.
pixel_count, average_x, average_y = getBlob()
Rather than using the mouse to select the bounding box for segmentation, you can manually configure the Fluke using the configureBlob() function call:
configureBlob(y_low = 0, y_high = 255, u_low = 0, u_high = 255, v_low = 0, v_high = 255)
The parameters to configureBlob() create a bounding box in YUV space. Instead of using RGB which stands for Red/Green/Blue, YUV is an alternate way to describe color. The Y component contains the brightness or intensity information of the pixel, the U/V components contain the color.
Finally, you can also use getBlob() to locate bright pixels. We do this by segmenting bright pixels, meaning the Y components of the pixels are large :
configureBlob(y_low=100, y_high=255) b = takePicture('blob') show(b) pxs, avg_x, avg_y = getBlob()
Graphics Objects Interface
The object oriented graph window can be used to create your own drawings:
win = Window(title, width, height) # Alternately Window(title) or Window() win.setBackground(color) win.close() win.getMouse() # Returns a Point object of where the mouse is clicked
To get additional functionality, you will need to:
from Graphics import *
Then you can do the following graphics examples.
Once you have a graph window created you can draw the following Graph Objects on it with their .draw() method:
Line(point1, point2) Circle(centerPoint, radius) Rectangle(topLeftPoint, bottomRightPoint) Oval(centerPoint, xradius, yradius) Polygon(point1, point2, point3, ...) Text(centerPoint, string) Picture(width, height[, color]) Arrow(centerPoint) Curve(p1, p2, p3, p4) SpeechBubble(p1, p2, text, p3) - p1, p2 form bounding box; p3 is point of origin
See the Calico Graphics for more details.
Example
Using the graphics window to draw:
from Myro import * from Graphics import *
win = Window("My Window", 500, 500) # Window(string, width, height) color = Color(100, 200, 50) # red, green, and blue are values from 0 to 255 win.setBackground(color) # sets background color of window to the color we created point = Point(100,25) # creates a point referencing location (100,25) message = Text(point, "Click on window") # creates message "Click on window" anchored at location (100,25) message.draw(win) # puts the message on the graph window click = win.getMouse() # Waits for user's mouse click and returns as a Point object message.undraw() # erases message from the graph window text = Text(click, "Some Text") # creates text "Some Text" anchored at location you clicked text.draw(win) # adds the text to the graph window p = Point(150,150) # creates the point for top left corner of the oval's bounding box xradius = 50 yradius = 20 oval = Oval(p, xradius, yradius) # creates the oval redColor = Color(255,0,0) # defines the color red oval.fill = redColor # colors the oval red oval.draw(win) # draws the oval to the graph window wait(3) # Window will close in 3 seconds
Miscellaneous commands
wait(seconds) - Pause for the given amount of seconds. Seconds can be a decimal number.
python> wait(5) [5 seconds go by] Ok python>
currentTime() - the current time, in seconds from an arbitrary starting point in time, many years ago. Can you figure out the date of the start time?
python> currentTime() 1164956956.2690001
odd(n) - returns True if n is odd
even(n) - returns True if n is even
Random decisions
flipCoin() - Returns "heads" or "tails" randomly.
python> flipCoin() 'tails' python> flipCoin() 'tails' python> flipCoin() 'heads'
heads() - returns True 50% of the time
tails() - returns True 50% of the time
pickOne(value) or pickOne(value1, value2, ...) - Returns a number or element randomly. New in Myro 1.0.2
python> pickOne(5) 0 # randomly returns 0 through 4 evenly over time python> pickOne(2, 4, 6, 8) 6 # randomly returns 2, 4, 6, or 8 evenly over time python> pickOne("red", "white", "blue", "green") 6 # randomly returns "red", "white", "blue", or "green" evenly over time
randomNumber() - Returns a random number between 0 (inclusive) and 1 (exclusive).
python> randomNumber() 0.65218366496862357
File and Folder Functions
pickAFolder() - allows you to select a folder
python> pickAFolder() 'C:/Python24'
pickAFile() - allows you to select a file
python> pickAFile() 'C:/Python24/README.txt'
Media Functions
readSong(filename) - reads a file in the Song File Format and returns a list of tuples.
makeSong(text) - make a song in the Song File Format where lines are separated by semicolons.
python> makeSong("c 1; g 1/4; a 1/2; e 3/4;") [(523.29999999999995, 1.0), (784.0, 0.25), (880.0, 0.5), (659.29999999999995, 0.75)] python> singsong = makeSong("c 1; g 1/4; a 1/2; e 3/4;")
saveSong(text or song, filename) - saves a song in tuple format, or in text format
Audio
Using Myro, you can add sounds to your programs. The easiest way is to just play a file:
Myro.playUntilDone(filename) Myro.play(filename)
These play .wav, .ogg, .mp3, .mod or .mid files.
There are some additional commands for controlling the sound. These commands also return a channel for additional control (see below).
channel = Myro.playUntilDone(filename) channel = Myro.play(filename) channel = Myro.play(filename, loop_forever) channel = Myro.play(filename, loop) channel = Myro.play(filename, loop, seconds)
The rest of this section uses a sound object to have more control.
sound = Myro.makeSound(filename)
Loads a .wav, .ogg, .mp3, .mod or .mid file into memory.
bytes = sound.Array()
Returns sound as an array of bytes.
channel = sound.Play()
Plays the sound.
channel = sound.Play(loops)
Plays the sound for a desired number of loops.
channel = sound.Play(loopIndefinitely)
Plays the sound.
channel = sound.Play(loops, milliseconds)
Plays a sound for a desired number of milliseconds or loops.
channel = sound.FadeIn(milliseconds)
Fades in a sample once using the first available channel.
channel = sound.FadeIn(milliseconds, loops)
Fades in a sample the specified number of times using the first available channel.
channel = sound.FadeInTimed(milliseconds, ticks)
Fades in a sample once using the first available channel, stopping after the specified number of ms.
channel = sound.FadeInTimed(milliseconds, loops, ticks)
Fades in a sample the specified number of times using the first available channel, stopping after the specified number of ms.
sound.Stop()
Stops the sound sample.
sound.Fadeout(fadeoutTime)
Fades out the sound sample.
Tones
Myro.beep(duration, frequency) Myro.beep(duration, frequency1, frequency2)
Plays a tone for a given amount of time. If duration is zero, then it plays until you stop it.
Myro.play(duration, function)
Plays the wave form generated by function. function is a function that takes an array and an index. It then fills the array with bytes (0 to 255) which represent the wave form.
Example:
import Myro import Graphics import math def makeTone(freq1, freq2, freq3): """ Make a function that is based on the given frequencies. """ # Each time slice is 1/playbackfreq of 2pi slice = (1/44100 * 360 * math.pi/180) # time in radians def wave(array, position): """ The actual function that will compute the wave. """ # Fill the array with bytes (unsigned 8-bit values) for i in range(len(array)): angle1 = position * slice * freq1 angle2 = position * slice * freq2 angle3 = position * slice * freq3 array[i] = ((127 + math.cos(angle1) * 127) + (127 + math.sin(angle2) * math.sin(angle1) * 127) + (127 + math.cos(angle3) * 127))/3 position += 1 return wave def plotSound(function, width=500): win = Myro.Window("Sound Plot", width, 255) array = [0] * width function(array, 0) prev = (0, 255) for i in range(width): Graphics.Line(prev, (i, array[i])).draw(win) prev = (i, array[i]) tone = makeTone(440, 440, 220) plotSound(tone) Myro.play(2, tone)
Advanced Sound Control
As Myro sound and audio is built on top of SDL (the same system that pygame and other languages use) we also have access to fine-grain control of sound. For more information, please see:
http://sourceforge.net/apps/mediawiki/cs-sdl/index.php?title=Audio
Gamepad
There is a pre-programmed gamepad() function for immediately driving the robot:
python> gamepad()
This will show a menua of options (depending on the type of gamepad your have). Allows speaking, moving the robot, and playing tones.
Also, one can write quick gamepad utilities with:
python> gamepad({"button": function, ...})
where "button" is one of the names of items that a gamepad can return, and function is a function that takes the button values as a list. For example:
def buttonHandler(args): # args is a list of values of the item being handled .... def axisHandler(args): # args is a list of values of the item being handled .... gamepad({"button": buttonHandler, "axis": axisHandler})
Whenever a axis or button is pressed or released, then the handlers will be called.
There are two additional commands: getGamepad() and getGamepadNow()
- getGamepad() waits for an event before returning (ie, is blocking),
- getGamepadNow() will immediately return the current state of the gamepad.
Gamepad Examples
python> getGamepad("count") 2 python> getGamepad("button") # waits till you press at least one button [0, 0, 1, 0, 1, 0, 0, 0] python> getGamepad(1, "button") # waits till ID 1 presses at least one button [1, 0, 0, 0, 0, 0, 0, 0] python> getGamepad(range(getGamepad("count")), "button") # waits till someone presses a button [[0, [1, 0, 0, 0, 0, 0, 0, 0]], [1, [0, 0, 0, 0, 0, 0, 0, 0]]] python> getGamepad(["button", "axis"]) {"button": [1, 0, 0, 0, 0, 0, 0, 0], "axis": [-0.999969482421875, 0.0]} (sometimes axis doesn't return exactly 1 or -1).
Here is a short, useful program:
python> while 1: move(*getGamepad("robot"))
Here is a more functional one:
done = False while not done: results = getGamepad(["button", "robot"]) move(*results["robot"]) if results["button"][1]: beep(.5, 440) if results["button"][2]: beep(.5, 880) done = (results["button"][0] == 1)
Gamepad Details
- getGamepad("count") - returns (immediately) the number of gamepads connected
- getGamepad(ID, [ITEM, ...]) - return the ITEMs for gamepad ID. ID can be left out and will default to 0, the first one. If you request more than one ITEM, then they come back in a dictionary. Just request one ITEM and you'll get the value (as a list, string, or number).
- getGamepad([ID1, ID2...], [ITEM, ...]) - return the ITEMs for gamepad IDs as a list of lists of ID, RESULTS. For example:
- python> getGamepad([0, 1], ["button", "axis"])
- [{'button': [0, 0, 0, 0, 0, 0, 0, 0], 'axis': [0.0, 1.0]}, {'button': [1, 1, 0, 0, 0, 0, 0, 0], 'axis': [-1.0, -1.0]}]
- python> getGamepad([0, 1], "axis")
- [[0.0, 1.0], [-1.0, -1.0]]
ITEM can be:
- "count" - returns (immediately) number of gamepads plugged in
- "robot" - returns axis values (floats) ready to be used by move() Myro command as [translate, rotate]
- empty - if nothing is passed in, it returns all of the following as a dictionary
- "name" - name of gamepad, as string
- "axis" - returns values of axis controller in a list (as floats)
- "ball" - returns values of ball controller in a list
- "button" - returns values of buttons in a list (as integers)
- "hat" - returns values of hat in a list
Here is a short little demo of how you could write a multi-player "game" where each player has a gamepad controller and appears as a circle on the screen.
from Myro import * from Graphics import * # imports Circle, and Point def game(): win = Window("My Game!", 500, 500) numplayers = getGamepad("count") colors = ['red', 'blue', 'green', 'yellow', 'orange'] players = [] # Create the players: for p in range(numplayers): circle = Circle(Point(randomNumber() * 500, randomNumber() * 500), 10) circle.fill = Color(colors[p]) players.append(circle) players[-1].draw(win) # Let's play! speak("Red is it! Don't let red touch you!") while True: for data in getGamepadNow(range(numplayers), ["axis","button"]): for id in range(numplayers): players[id].move(data[id]["axis"][0] * 20, data[id]["axis"][1] * 20) if data[id]["button"][0]: # fire a missile speak("Fire!") wait(.1)
The window produced with the show(picture) command allows mouse clicks.
Robot's Orientation
If you are using a bluetooth-serial adapter or a serial cable to control your robot, then the scribbler's normal forward direction is used. When using the Fluke the forward direction is flipped. "Forward" is in the direction of the Fluke's camera. However, the orientation of the scribbler can be manually changed using the setForwardness() function. This is particularly useful if you want to use the Scribbler's IR and light sensors.
setForwardness(orientation): orientation can be 0/"scribbler-forward" or 1/"fluke-forward"
getForwardness(): Returns the orientation of the robot "scribbler-forward" or "fluke-forward"
Controlling Multiple Robots
You can control multiple robots by making robot objects and then telling each robot object what you want it to do using the standard robot commands as methods on the objects:
#Connect to two different robots on different com ports r1 = makeRobot("Scribbler", "COM4") r2 = makeRobot("Scribbler", "COM8") #tell both robots to start moving forward r1.forward(1) r2.forward(1) #Tell both robots to beep for one second. #Note that robot 1 beeps first, followed by robot 2 #because the beep method is blocking, so the robots #travel for a total of 2 seconds r1.beep(1,800) r2.beep(2,1400) #Tell both robots to stop. r1.stop() r2.stop()