SparkFun Inventor's Kit for Edison Experiment Guide
Experiment 12: Bluetooth Game Controller
We will continue with the concept of BLE. This time, we will keep the phone as the central (master) device, but we will use the Edison as a controller to send data as notifications to the phone.
In this experiment, we are going to construct a type of very powerful (probably overkill) Bluetooth game controller out of the Edison with 4 buttons. The Edison will periodically poll the state of these buttons and notify the smartphone over BLE of any button pushes.
On the phone, we will create a very simple "game" that consists of a red ball in the middle of a box. Pushing one of the four buttons on the Edison will cause the ball to move in a cardinal direction (up, down, left, or right). While the game itself does not have a real purpose, it could easily be the starting point for a real video game that requires some kind of Bluetooth controller.
- If you have an iPhone, you will need to enroll in the Apple Developer Program (there is a yearly membership fee) and create Ad Hoc Provisioning Profiles (discussed later)
- If you have an Android, you can allow the installation of apps from "Unknown Sources" and install the app from a downloaded .apk file
In addition to the Edison and Block Stack, you will need the following parts:
- 1x Breadboard
- 4x Push Buttons
- 4x 1kΩ Resistors
- 10x Jumper Wires
- The Game Loop -- What is a game loop, and why almost every video game has one
Wait, this is a function! Correct. In order to create an instance of the class
MyBlueprint, we need to use the special
new keyword. The following creates one instance (named
myInstance) from the class
MyBlueprint (notice that we capitalize the first character in the class but not for instances):
language:java var myInstance = new MyBlueprint();
We've just created an object from our class! It is very similar to following a blueprint, schematic, or recipe to make a bridge, circuit, or meal.
myInstance, thanks to the constructor, contains a member variable called
_member. The value of
_member is unique to that one instance. We can access that value with
myInstance._member in order to get or set its value.
In addition to members, we can create functions that are shared among all instances of our class. We need to use the special keyword
This function (called a method), when called from an instance, allows you to set the value of that instance's member variable (
_member in this case). We use the
this keyword to refer to the instance. For example, if we named our instance
this would refer to the
myInstance object. By specifying
this._member, we refer to the member variable within that particular instance.
Some jargon you should be aware of:
- Object -- A piece of code that contains properties (variables and/or functions)
- Class -- A blueprint to create several objects of the same type
- Instance -- An object created using a class (as a blueprint)
- Member -- A variable unique to an instance as defined within a class
- Method -- A function defined by a class
Here is a basic example of a class and some instances created from that class. Feel free to run the code on your Edison. What do you expect to see in the console when you run it? Can you identify the class, the instances, the member, and the methods?
The Game Loop
Almost all video games rely on the Game Loop. This loop is a simple programming construct with 4 parts and executes endlessly (at least until the game ends).
The Game Loop consists of the 4 main parts:
- Process Input -- Look for input from the controller or input device. For example, the player is pushing a button.
- Update Game -- Take input from the controller and update the game variables as a result. For example, the button input told the character to move up. Update the character's Y position by -1 (toward the top of the screen).
- Render -- Render is another fancy name for "draw." Most simple games will clear the screen and re-draw the whole scene. In our example, this includes having the character appear 1 pixel above where they used to be.
- Wait -- Do not do anything so that we can reach our target framerate (measured in frames per second or FPS). This is often variable. For example, if the first three steps took 32 ms, we need to wait 18 ms in order to meet our framerate goal of 20 FPS (1 frame every 50 ms).
However, steps 3 and 4 will still execute in an endless loop. Every 50 ms, the game's canvas (play area on the phone) is cleared and a new ball is drawn in the position determined by the ball's x and y properties. We then wait for the rest of the 50 ms until the
draw function is called again.
Before we run any code on the Edison, we need to enable Bluetooth. Connect via SSH or Serial, and enter the following commands:
rfkill unblock bluetooth killall bluetoothd hciconfig hci0 up
In the XDK, create a new Blank IoT Application. In package.json, copy in the following:
In main.js, copy in the following:
As in the previous experiment, create a blank HTML5 + Cordova app under HTML5 Companion Hybrid Mobile or Web App.
In the project settings, add the plugin cordova-plugin-ble-central.
Once again, we need to include jQuery. Download the latest, uncompressed version of jQuery from http://jquery.com/download/. Create two new directories in your project so that you have /www/lib/jquery. Copy the .js file into the jquery directory.
Go back to the Develop tab. In www/index.html, copy in the following:
In www/js/app.js, copy in the following:
Refer to the previous example on how to build the phone app for your smartphone.
What You Should See
Make sure that you have enabled Bluetooth on the Edison and that it is running the controller program. Run the phone app, and you should be presented with an input, a blank game canvas, and a debugging console (much like in the last experiment). Enter Edison into the input field and tap Connect.
Once your phone connects to the Edison, a small, red ball should appear in the middle of the canvas (you should also see a "Connected to" message in the debugging console). Push the buttons connected to the Edison to move the ball around!
Code to Note
The Game Object
As introduced in the Concepts section, we create a class named
Game along with some members and methods. The
window.app.game object (an instance of the
Game class) is responsible for remembering the ball's X and Y coordinates (stored as variables within the
this._ball member) as well as drawing the ball on the canvas, which is accomplished using an HTML Canvas element.
Once a BLE connection has been made, the program starts the game loop by calling
window.app.game.start(). This function sets up an interval timer that calls the
.draw() function every 50 ms. In
.draw(), the canvas is cleared, and the ball is drawn every interval.
window.app.onNotify, which is called on a received BLE notification, the BLE's data (the first and only byte) is parsed to determine which way the ball should move. A received '0' means "move the ball up", '1' means "down", '2' means "left", and '3' means "right". Updating the ball's position is accomplished by calling
BLE by Name
In the previous experiment, we needed to hardcode the Edison's Bluetooth MAC address into the program. This time, we take a different approach. When the user presses the Connect button, the program retrieves the value of the input (ideally, "Edison") and stores it in the global
window.edison object (as
The program then scans for all available BLE peripherals, noting their names (
device.name in the
onDiscoverDevice callback). If one is found to be equal to
window.edison.name (ideally, "Edison" once again), the program attempts to connect to that device. No need to set the MAC address anywhere!
In the Edison code, we assign the BLE name "Edison" in the global
edison object. If we change that name, we will need to enter the same name into the input field of the phone app.
In the Edison code, we define a BLE characteristic called
controllerCharacteristic. Unlike the previous example where we only allowed writing to the characteristic, we allow notifications to this characteristic.
A BLE notification sends out an update to all devices that have subscribed whenever that characteristic has changed. To accomplish this in the Edison, we create a member variable of the
_updateValueCallback. This variable is assigned the function
updateValueCallback whenever a device subscribes to the characteristic. We create the external function
controllerCharacteristic.sendNotification(buf) so that other parts of the code may send data as a notification over BLE.
calls the functionupdateValueCallback()
with the data it received from the caller (buf`). The causes the bleno module to send a notification for that particular characteristic over BLE.
On the phone side, cordova-plugin-ble-central is configured to subscribe to the characteristic with
ble.startNotification(...). Notifications to that characteristic call the function
onNotify(), which then parses the received data to determine how to move the ball.
This and That (Dummy Functions)
As mentioned previously, the keyword
this refers to the calling object. Sometimes, we have to create a wrapper around a callback function so that we can pass the object to the function.
For example, in the phone code app.js, the method
Game.prototype.start uses the built-in function
setInterval() to call the draw method repeatedly. As it is,
setInterval() is a method of the global
window object. If we were to write
this (only within
setInterval) would refer to
window, not the
Game object! And, as you might guess,
window has no
draw() function. This piece of code would throw an error (unless you made a
To get around this, we first assign
this to a placeholder variable, which we will (humorously) call
that. Then, we can create a dummy function as the callback for
setInterval, which calls
that.draw(), referring to the
draw() method in the Game object.
- Bluetooth is not connecting -- If you restarted the Edison since the last experiment, make sure you enter the three commands
rfkill unblock bluetooth,
killall bluetoothd, and
hciconfig hci0 upinto an Edison terminal (SSH or serial) before running the Edison code.
- The ball does not move -- If you see the ball on the screen, then Bluetooth is connected. A visible ball that does not move could be caused by several issues:
- The Edison might not be detecting button pushes. Insert
console.log()statements into the Edison code to determine the state of each of the buttons.
- The Edison might not be sending notifications over BLE. Use another BLE phone app (e.g. BLE Scanner) to see if the characteristic is being updated.
- The phone app might not be receiving notifications. Place
onNotifyto see if it is being called.
- The phone app might not be updating the ball postion. Place
Game.prototype.updateBallPosto see if it is being called.
- The Edison might not be detecting button pushes. Insert
- Make the ball's radius grow by 1 every time it touches a wall.
- Make the ball move faster (this can be accomplished in several ways).
- Make a real game! For example, you could make a Breakout clone that uses the Edison controller to move the paddle. See this tutorial to help you get started.