Calico Myro

From IPRE Wiki
Jump to: navigation, search

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.

Getting Started

After starting Calico, you can use the Myro module to control any of a number of robots (including a simulated robot), create art, visualizations, and many other activities.

Robots

There are currently 7 robots (described below) you can use with Myro:


Scribbler

Before using Myro with your robot, you need to connect to it via Bluetooth. Follow the instructions here Optional: Using Calico with Robots. Once you have the connection, then you can start.

  • Turn on the robot.
  • Enter the following:
from Myro import *
init("COM4")

or

from Myro import *
init("/dev/tty.scribbler")

(for Macs, where tty.scribbler might be different depending on what you named it).

When using the scribbler robot, the code init() is usually used. But you can also use the more general interface which works with all robots:

from Myro import *
makeRobot("Scribbler", "COM5")

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.

Briefly, you can start the simulator with:

from Myro import *
init("sim")

or

from Myro import *
makeSimulation(calico, "water.py")

There are a number of predefined worlds, including "indoor.py", "outdoor.py", and "water.py". You can create your own worlds. You can copy one of Calico/modules/Myro/Robots/Worlds/ to you own folder.

NxtRobot

You can use:

robot = makeRobot("NxtRobot")

to have access to Nxt-specific functionality.

Finch

You can use the #Advanced 2 commands for the Finch. See Finch for details on the Finch.

Hummingbird

You can use the #Advanced 2 commands for the Hummingbird. See Hummingbird for details on the Hummingbird robot kit.

ARDrone

You can use the #Advanced 3 commands for the ARDrone Quad-Copter. See examples for more information.

Arduino

You can use the #Advanced 4 commands for the Arduino. See examples for more information.

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.

gamepad(): control robot with a gamepad.

NOTE: you must plug in the USB-based gamepad before importing Myro.

        Pad   Action
     ------   -------
 Left/Right   turnLeft() and turnRight()
    Up/Down   forward() and backward()

     Button   Action
     ------   -------
          1   stop()
          2   takePicture()
          3   beep(.25, 523)
          4   beep(.25, 587)
          5   beep(.25, 659)
          6   speak('Hello. My name is Scribby.')
          7   speak('Ouch! I'm a sensitive robot.')
          8   speak('I'm hungry. Do you have any batteries?')

Gamepad is now running... Press button 1 to stop.

Output Functions

beep(duration, frequency, frequency2 = None): make a tone. If two tones are given, the robot will combine them.

speak(message) - text-to-speech, turns message into spoken words (waits to finish speaking)

speakNoWait(message) - text-to-speech, turns message into spoken words (doesn't wait to finish)

speak(message, async = 0) - text-to-speech, turns message into spoken words (asynchronous flag, 0=blocks, 1=continues)

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:

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 int() or float() function to turn strings into numbers. For example:

python> int(input("How old are you? "))
How old are you? 19
19


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("left" | "right" | 0 | 1 | "all"): 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

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

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.


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))
  1. doTogether([f1, f2, ...]): will run f1(), f2() at the same time and return their results in a list
  2. doTogether([f1, f2, ...], arg): will run f1(arg), f2(arg) at the same and return their results in a list
  3. 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.

Lucy.jpg Pig.jpg

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()

configureBlob can also set the blob's color parameters based on the statistics of an image. You pass a picture along with a bounding box (x1, y1, x2, y2) and Myro will find the correct UV range to describe that color.

 configureBlob(p, 50, 50, 150, 150)

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() or .drawAt() 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() pop up window.

pickAFolder() - allows you to select a folder

python> pickAFolder()
'C:/Python24'
pickAFile() pop up window

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. (Note that this does not actually work right now, and a workaround is to use sound.Volume=0 )

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()

  1. getGamepad() waits for an event before returning (ie, is blocking),
  2. 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

  1. getGamepad("count") - returns (immediately) the number of gamepads connected
  2. 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).
  3. 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:

  1. "count" - returns (immediately) number of gamepads plugged in
  2. "robot" - returns axis values (floats) ready to be used by move() Myro command as [translate, rotate]
  3. empty - if nothing is passed in, it returns all of the following as a dictionary
  4. "name" - name of gamepad, as string
  5. "axis" - returns values of axis controller in a list (as floats)
  6. "ball" - returns values of ball controller in a list
  7. "button" - returns values of buttons in a list (as integers)
  8. "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 (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). You can control each robot 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()