This lab is the final project for this quarter of CSE466. You'll use all of the knowledge and skills you learned in the previous lab to develop your own wireless player controller for a 20-player soccer game. You'll use the AirStick board to control the movement of your player, the LCD and speaker on the SuperBird board to provide feedback, and the jog dial on the SuperBird to provide additional control.
This lab is presented as a design specification, rather than a series of steps. It is up to you to determine how you will implement the specification.
WARNING: NEVER attach or detach the iMote2 from the debug board, sensor board, SuperBird, or AirStick when power is attached. ALWAYS make sure you've unplugged ALL of the USB cables when connecting or disconnecting boards. (The one exception is the antenna board for the AirStick, which may be safely connected and disconnected while the system is operating.)
NOTE: There may be changes to this lab as the project develops. You should check this page frequently to make sure your implementation stays current. Any changes to this page will be noted here.
Show older updates
10 March, 7:20pm: Updated the alternate LCD driver to fix a bug that garbled images over a certain size.
12 March, 9:49pm: Added information about automatic startup.
Figure 1. The CSE466 Soccer Field |
The packets you receive from the game coordinator have the following format:
src team |
src player |
dest team |
dest player |
position (x) |
position (y) |
merged | score (blue) |
score (red) |
action | reset | on/off |
1 byte | 1 byte | 1 byte | 1 byte | 2 bytes | 2 bytes | 1 byte | 1 byte | 1 byte | 1 byte | 1 byte | 1 byte |
The first two bytes (src team and src player) are the address of the node that sent the packet. The game coordinator will use team 0, player 0 as its own address. The next two bytes (dest team and dest player) indicate to which player this packet is addressed.
The position bytes indicate the position of this player on the field. The x and y values are two bytes each, in little-endian order (16-bit unsigned) to make them easy to work with on the iMote2. You may not need this information, but it's provided just in case you want to use it.
The merged byte tells you whether or not you're merged, and if you are, who the captain of the merged player is. If this byte is zero, then it means you're not merged, and you should respond to the game controller directly. If this byte is not zero, and it's not your own player number, then it means that you are merged, and you should respond to the captain number given in the byte. If this byte is your own player number, then it means that you are the captain of a merged player and you should respond with your aggregated movement data to the game controller.
The score bytes tell you the current score of the game. You may wish to display this information on the LCD.
The action byte is a bitfield. Triggering certain actions in the game will cause bits in this field to be set. For example, if you've just merged with another player, the "merged" bit in action will be set for 1 packet. You can use this information to play sounds, display graphics on the LCD, and so on. The bits are defined as follows:
bit | meaning |
0 | this player just scored |
1 | this player just merged |
2 | this player just unmerged |
3 | this player just teleported |
4 | this player just tried to go out of bounds |
The reset byte should cause your player controller to reset any persistent state it might maintain (other than perhaps the calibration for the AirStick.) If your player has no persistent state, it's okay to ignore this byte. 1 means reset, 0 means don't reset.
The on/off byte should allow the game controller to toggle your player controller on and off. If your controller receives a value of 2 for this byte, it should stop sending any packets until it receives a value of 1. A value of 0 means that a controller should stay in its current state (on or off).
After receiving a packet from the game coordinator, your controller should send a response to either the game coordinator, or to the captain of the merged player, depending on the value of the merged byte (see above). The primary function of this response is to tell the game coordinator or captain which direction this player wants to move.
src team |
src player |
dest team |
dest player |
delta (x) |
delta (y) |
merge- able |
1 byte | 1 byte | 1 byte | 1 byte | 2 bytes | 2 bytes | 1 byte |
The src and dest fields are the same as above. Remember that team 0, player 0 is the controller.
The delta fields indicate the direction in which this player wants to move. These values are given as velocities, in number of units per second. These values can range from -20 to 20, unless a player is merged, in which case the maximum speed limit is sqrt(n) * 20. The values are 16-bit signed, little-endian integers. If this packet is from the captain, then the deltas are expected to be the average of all of the movements transmitted to the captain, including the captain's own movement.
The mergeable byte allows your player to specify whether or not it wants to be allowed to merge with other players. If this value is zero, then the game coordinator will not merge your player when you collide with another on your team. If you are already merged and you send a value of zero, you will be immediately unmerged from the group. If you send a value of 1, then it means you want to be merged when you collide with another player on your team, or stay merged if you are currently merged.
It is very important that you send this response as soon as possible after receiving a packet from the game controller. In order to keep the game as responsive as possible, the window in which you are allowed to send a response is very short. You should not wait until you receive a packet to do time-consuming calculation; you should have your response calculated and ready to go so you can respond quickly. If you do not respond within your allocated time, the game coordinator will ignore your packet (and may turn off your controller if you are frequently transmitting packets during someone else's timeslot.)
You should use the AirStick to allow the user to decide which direction to move the player. It is up to you exactly how you want to implement this. However you decide to make it work, the interface should be reasonable and easy to use.
If your AirStick requires a calibration procedure to be performed, you should allow the user to do so when you start your player controller (and possibly later, in case the sensor needs to be recalibrated during the match.) Your calibration should be as reasonable as possible; keep the number of steps and the complexity of the steps the user must perform to a minimum.
When you start your player controller, you should provide an interface that allows you to choose a player number, team, and radio channel using the LCD and jog dial.
You should use the LCD to display information such as:
You have an opportunity to be creative with what you display on the LCD. While a basic text display will suffice, you can improve the LCD interface by displaying fun graphics.
You should also draw a spot over the top of the LCD screen that corresponds to the position of the user's finger over the AirStick antenna. This provides feedback to the user that the AirStick is properly calibrated and working.
Figure 2. Example LCD graphics from last year |
A different LCD driver which allows you to mix text and graphics on the SuperBird LCD is available here, with documentation in the included README file. You may use this driver, or you may use the graphical driver you used before and figure out how to produce your own text on the display.
You can download a utility that will convert a .bmp file to a .lcd file which you can write to the /dev/lcd-gfx device. You can read these .lcd files into arrays into your program and write them to /dev/lcd-gfx when you want to display them. The utility is available here; you should untar and make it in your attu account and run it from your attu account (not the iMote2.) The syntax is ./lcd-bmpshow <input.bmp> <x> <y> <output.lcd>. The x and y values are the top left corner of where the image will be displayed on the LCD.
When an event occurs that affects your player (i.e. a bit in the action field is set) you should play an appropriate sound of your choosing.
In order to test your player controller, you have two options (and it is recommended that you use both methods):
When you are doing your testing, please use your own channel (use your group number + 11) so that you don't interfere with other groups' testing.
You will likely want to coordinate with other groups to test your implementation for merged players.
Read-only access to the latest version of the game coordinator code is available at http://svn466.orderedpixels.com. You can also check out a copy of the code by executing svn co http://svn466.orderedpixels.com/soccer466/trunk soccer466.
If you want to contribute additional features or make improvements, you will need to request write access to the subversion repository by e-mailing bmayton@cs.
When you turn your player controller on, it should automatically start running your controller code, so that no computer is required to start your controller.
Make your modules loadable at startup by adding them to the /lib/modules/2.6.14_r1.0/modules.dep file. Add one line for each module that you want to be loadable. This line should contain a full path to the module, followed by a colon, followed by a space-separated list of all modules that must be loaded before this module will work (in most cases, this list will be empty.)
Now, tell the startup scripts to load the modules at startup by adding them to the /etc/modules file. You only need to specify the names of the modules here; do not include full paths or the .ko extension.
You'll also need to make nodes in /dev at startup for each of your modules. You can do this with your own scripts (using awk to look up the device numbers) or you can use this script that does this for you. Untar the file, and place the S11mkdevs file in /etc/rcS.d (make sure to make it executable) and the devtab file in /etc. The devtab file already contains entries for most of the devices we're using, but you can see the comments at the top of S11mkdevs if you want to know how to add your own.
You can make scripts run at startup by placing them in the /etc/rc2.d directory. The scripts in this directory are executed after the system is finished starting up. They are executed in alphabetical order. You can put a script called "S99player" in this directory that does something like the following:
There is code for a simple launcher available here. You can use it as is, modify it to fit your needs, or not use it at all. You must create directories called /root/programs and /root/settings. Executables placed into these directories will be displayed in the launcher.
#!/bin/bash exec /root/player > /dev/null &
Note that it's important to route the output to /dev/null, since if your program prints messages, they'll fill up the output buffer since there's no terminal connected, and your program will be terminated.
It would be ideal if your program (or another program that launches your program) allows you to choose player, team, and channel numbers at startup, using the LCD and jog dial as an interface.