Advanced Concepts

This section introduces concepts that will take your programs to the next level with more complex functionality.


String Concatenation


In Javascript, the '+' operator let's you join, or concatenate, string values.
Try the following simple example to see how this works:


async function startProgram() {
    speak("tar" + "get");
    await delay(1.0);
    exitProgram();
}


Combined with real-time sensor data, this can give you valuable data about your robot:

async function startProgram() {
    while (true) {
        speak("Yaw is " + getOrientation().yaw + " degrees.");
        await delay(3.0);
    }
}

Run this program, then rotate Sphero about its vertical axis. Compare the spoken values to the output from the streaming sensor data.


A Gotcha...and a Trick

If you use the speak command, you will probably need string concatenation at some point. The speak function only takes string arguments, so code like this:

speak(Math.PI);

won't work.

You can get around this with a trick, though. When Javascript sees mathematical operators like + and *, it automatically converts all the operands' data types to make the operation work. This applies to the string concatenator, +.

In other words, if you add a string and a number, Javascript will automatically convert the number to a string, concatenate the two arguments, and produce a resulting string. This works even if you add an empty string to the number.
In other words, if you give this to Javascript:

var myVal = "" + 7;

myVal will be "7" -- a string.

This means that, while speak(Math.PI) is an error, speak("" + Math.PI) will work.


Generalized Loops

A loop repeats a designated chunk of code until certain conditions are met. Each pass through the code is called an iteration. The number of iterations depends on the loop condition.

In Javascript, there are two fundamental loops: while and do...while.
The while loop like this:

while (loop condition is true)
{  <-- This 'opening curly brace' signals the start of the code to repeat

    Code to repeat goes here.
    
}  <-- This 'closing curly brace' signals the end of the code to repeat


Here is an example using a loop to count to ten:

var count = 1;          // <-- Define a loop variable
while (count <= 10) {   // <-- Check the loop condition
    speak("" + count);
    await delay(1.0);
    ++count;            // <-- Update the loop variable (in this case, add 1 to it)
}

The do...while loop looks similar:
do {
    // Code to repeat goes here
} while (test condition is true);

Here is the same example using a do...while loop:
var count = 1;          // <-- Define a loop variable
do {
    speak("" + count);
    await delay(1.0);
    ++count;            // <-- Update the loop variable (in this case, add 1 to it)
} while (count <= 10);     // <-- Check the loop condition

You might wonder why Javascript has while and do...while loops, given that they loop alike and behave the same way. The answer is, "Because they don't behave exactly the same way." Notice that the while loop checks its condition before executing the code, and the do...while loop checks the condition after executing the code. This means that, if the condition starts out false, a while loop will never execute it, but a do...while loop will execute it once.

You can test this by changing the initial value of the count variable in the above examples to 11. Try it in both examples and see what happens.


Javascript supports one more type of loop, based on the while loop. It's called a for loop, and it provides a shorthand way of writing a standard while loop.

If you look at the while loop above, you'll notice the following pattern:

  1. Define a loop variable
  2. Check the loop condition
    2a) Execute the loop code if the condition was true
  3. Update the loop variable


    This is such a common pattern that many languages, including Javascript, provide a template for it:
for (initialize loop variables; test loop condition; update loop variables) {
    // Code to loop goes here
}

This is identical to this while loop:

initialize loop variables
while (test loop condition)
{
    // Code to repeat goes here.

    update loop variables    
}

Returning to our counting example:


for (var count=1; count<=10; ++count) {
    speak("" + count);
    await delay(1.0);
}

for loops are powerful -- you can call functions inside them instead of checking variables:

async function startProgram() {
    resetAim();
    
    // A 'for' loop with no variables!
    for (setSpeed(50); getSpeed() <= 200; setSpeed(getSpeed() + 50)) {
        await delay(1.0);
    }
    
    setSpeed(0.0);
    speak("Busted!");
    setMainLed({r: 255, g:0, b:0});
    await delay(1.0);
    
    exitProgram();
}

Another Kind of for Loop

In Javascript, for loops can also iterate through structures with elements, like arrays and objects. Consider the following example:
async function startProgram() {
    var key = null;
    var aboutMe = {'my-dog': "Hershey", 'my-other-dog': "Koda", 'my-favorite-computer': "the Commodore 64"};
    for (key in aboutMe) {
        speak(key + " is " + aboutMe[key]);
        await delay(3.0);
    }
    exitProgram();
}

In Javascript, for...in loops have the following form:

for (iterator_variable in structure_with_elements) {
    // Code to repeat goes here.
}

where the structure with elements is a data structure with elements inside it, like an array or an object. The iterator variable takes on the indeces of each internal element, in turn.

So, in the above example, the key variable will be 'my-dog' the first time through the loop; 'my-other-dog' the second time through the loop, and 'my-favorite-computer' the third time through the loop. When the program tries to get the fourth key, it won't find one (because the 'aboutMe' object only has 3 elements), and the loop will end.


Keywords async and await

Javascript uses the keyword async to identify code that might not execute all at once. It uses the keyword await to pause code execution until the associated async function completes.

There is a lot going on under the hood with these keywords. We are only going to cover the points critical to writing programs for Sphero.

Point #1: if you declare one of your functions as async, you must call it using the await keyword.


This applies to some of the pre-written Sphero functions, too. For instance, if you use the roll() command in your program, you need to invoke it like this:

await roll(0, 100);

or you will get a runtime error, because roll is an async function.


Point #2: if you want a function to return a value, don't make it an async function.


Because async functions can take awhile to complete, they can't simply return a value. Instead, they return a Promise, which is just what it sounds like: something that might be a value one day, or something that might be broken (for example, if you lose connection to your robot). There are ways to ask a Promise if it has been fulfilled, but that's beyond the scope of this wiki. It's also unnecessary for most Sphero programs.

Point #3: if your function returns a value, it can't include any async code.


This is a corollary to Point #2, but it's worth pointing out. You'll get a compile-time error if you create a function declared without async that tries to call async functions in its body.

Here's an example to show how it all works:


// Define a regular (non-async) function to format the
// yaw angle so it's more readable and never negative.
angleAdjust = function(yaw) {
    while (yaw < 0.0) {
        yaw += 360.0;
    }
    
    while (yaw > 360.0) {
        yaw -= 360.0;
    }
    
    return Math.round(yaw * 10.0) / 10.0;
};

async function startProgram() {
    var easyYaw = 0.0;
    
    while (true) {
        // Call angleAdjust normally (i.e., without 'await') because it's
        // guaranteed to complete immediately.
        easyYaw = angleAdjust(getOrientation().yaw);
        
        speak("Yaw is " + easyYaw + " degrees.");
        await delay(3.0);
    }
}


getCurrentTime

Sphero has an internal clock which you can use in your programs. You can access the time via the function getCurrentTime -- but there's a catch. Sphero's clock keeps track of the number of seconds that have passed since January 1, 1970. So, the actual number isn't very useful.

Fortunately, you usually don't care about the actual time of day. Most likely, you'll want the amount of time that has passed since your program started running. This is easy to do.

getCurrentTime Example


async function startProgram() {
    var startTime = getCurrentTime();
    
    speak('' + (getCurrentTime() - startTime) + ' seconds.');
    await delay(2.0);
    speak('' + (getCurrentTime() - startTime) + ' seconds.');
    await delay(7.0);
    exitProgram();
};

setTimeout and setInterval

These functions allow you to execute code after a time delay.
Use setTimeout to call a function after a time delay.
Use setInterval to execute a function repeatedly, pausing for a period of time between calls.

setTimeout Example


async function delayedCode() {
    setMainLed({r: 0, g: 255, b:0});
    await delay(0.5);
    setMainLed({r:0, g:0, b:0});
    exitProgram();
};

async function startProgram() {
    setTimeout(delayedCode, 2000); // 2000 millisecond delay.
    
    while (true) {
        await delay(0.1);
    }
};


setInterval Example


async function blink() {
    setMainLed({r:255, g:0, b:0});
    await delay(0.167);
    setMainLed({r:0, g:0, b:0});
};
async function startProgram() { setInterval(blink, 333); // 333 millisecond delay. while(true) { await delay(0.1); } };

Function Pointers

Variables in Javascript can hold any object, including functions. This allows you to call functions indirectly by storing them in a variable, then "calling" the variable as if it were a function.

Simple Function Pointer Example


async function saySomething() {
    speak("Something.");
    await delay(1.0);
    exitProgram();
};

async function startProgram() {
    var indirectFn = saySomething;
    
    await indirectFn();
};

This might seem silly, but it's powerful. In particular, it allows us to create a model known as a "state machine" -- see below.


State Machines

A state machine -- or "finite state machine" -- is a mathematical model that represents a system that can be in exactly 1 of N states, where 'N' is any natural number.

That sounds complicated, but the core idea is simple. Let's look at an example to see how it works.

Consider an ice cream cone. It can be in one of three states:

  1. Frozen -- all ice cream is firm
  2. Melting -- some ice cream is firm, some is liquid
  3. Melted -- all ice cream is liquid

    Now think about licking the ice cream in each state. When the cone is frozen, you get a little ice cream per lick. When it's melting, you get a lot of ice cream per lick. When it's completely melted, you get no ice cream per lick, because it's all over the floor (and who wants to lick the floor???).

    Now, imagine writing a program that simulates licking an ice cream cone. One approach might look like this:


var STATES = {FROZEN:0, MELTING:1, MELTED:2};
var coneState = ???; // Set this to one of STATES.FROZEN, etc)

async function lick() {
    switch(coneState) {
        case STATES.FROZEN:
            speak("You got a little ice cream.");
        break;
        
        case STATES.MELTING:
            speak("You got a lot of ice cream!");
        break;
        
        case STATES.MELTED:
            speak("You didn't get any ice cream.");
        break;
        
        default:
            speak("This isn't an ice cream cone!");
        break;
    }
    
    await delay(2.0);
};



Easy enough, but imagine what the lick function would look like if the cone had 10 states, or 100. It gets big and messy, especially if each case requires more code than a single speak command.

State machines make this code much cleaner to write and easier to maintain. Consider this implementation:


var stateFrozen = {
    lick: async function() {
        speak("You got a little ice cream.");
        await delay(2.0);
    };
};

var stateMelting = {
    lick: async function() {
        speak("You got a lot of ice cream!");
        await delay(2.0);
    };
};

var stateMelted = {
    lick: async function() {
        speak("You didn't get any ice cream.");
        await delay(2.0);
    };
};

var coneState = ???; // One of stateFrozen, etc.

async function lick() {
    await coneState.lick();
};

So, how does this help us program Sphero?

We can use state machines to give Sphero multi-step behaviors. Consider an example where we want Sphero to count down ("Ready-set-go!"), then drive away and drive back. This program is easy to make using a state machine.

Ready-Set-Go Example


var currentState = null;

async function stateReady() {
    setStabilization(false);
    setMainLed({r:255, g:0, b:0});
    speak("Ready!");
    await delay(1.0);
    currentState = stateSet;
};

async function stateSet() {
    setMainLed({r:255, g:255, b:0});
    speak("Set!");
    await delay(1.0);
    currentState = stateGo;
};

async function stateGo() {
    setStabilization(true);
    resetAim();
    setMainLed({r:0, g:255, b:0});
    speak("Go!");
    await roll(0, 100);
    await delay(1.0);
    setMainLed({r:0, g: 0, b:0});
    await delay(2.0);
    currentState = statePause;
};

async function statePause() {
    stopRoll(180);
    await delay(1.0);
    currentState = stateReturn;
};

async function stateReturn() {
    await roll(180, 100);
    await delay(3.0);
    stopRoll(180);
    exitProgram();
};

async function startProgram() {
    currentState = stateReady;
    
    while(true) {
        await currentState();
    }
};

Advanced Examples

This section gives some examples of the advanced concepts in action. Keep checking back -- we'll add more to these over time.


Basic State Machine

The Basic State Machine is a shell you can use as a starting point for your programs.
It provides a basic state machine structure and function stubs for instructions and initialization. It serves as the basis for the examples below.


var YIELD_DELAY = 0.033;
var TIPPED_ANGLE = 45.0;

// GLOBAL VARIABLES ============================================================
var currentState = null;

// MAIN LOOP ===================================================================
async function startProgram() {
    await sayInstructions();
    await initialize();
    
    while(!tipped()) {
        if (currentState) {
            await currentState();
        }
    
        await delay(YIELD_DELAY);
    }

    exitProgram();
};

// HELPER FUNCTIONS ============================================================
tipped = function() {
    return Math.abs(getOrientation().roll) > TIPPED_ANGLE;
};

async function sayInstructions() {
    speak("Tip your robot to the left or right to end the program.");  // <-- Replace this with your instructions.
    await delay(3.0);
};

async function initialize() {
    // Add your setup code here.
};

// STATES ======================================================================
// Put your state functions here.

Rainbow Shaker

Rainbow Shaker cycles through the colors of the rainbow when you shake your robot.
It uses a state machine and the onCollision event to display the correct color
without using if or switch statements.

STOP_ANGLE = 45.0;
YIELD_DELAY = 0.03;

// GLOBAL VARIABLES ================================================================
var currentState = null; // points to the code for our current state

// MAIN LOOP =======================================================================
async function startProgram() {
    await giveInstructions();
    initialize();
    while (Math.abs(getOrientation().roll) < STOP_ANGLE) {
        await delay(YIELD_DELAY);
    }
    exitProgram();
};

// HELPER FUNCTIONS ================================================================
async function giveInstructions() {
    speak("Shake your robot to see a rainbow.");
    await delay(2.0);
};

initialize = function() {
    registerEvent(EventType.onCollision, doShakeState);
    setMainLed({
        r: 0,
        g: 0,
        b: 0
    });
    currentState = setToRed;
};

async function doShakeState() {
    await currentState();
};

// COLOR STATES ====================================================================
async function setToRed() {
    setMainLed({
        r: 255,
        g: 0,
        b: 0
    });
    currentState = setToOrange;
};

async function setToOrange() {
    setMainLed({
        r: 255,
        g: 128,
        b: 0
    });
    currentState = setToYellow;
};

async function setToYellow() {
    setMainLed({
        r: 255,
        g: 255,
        b: 0
    });
    currentState = setToGreen;
};

async function setToGreen() {
    setMainLed({
        r: 0,
        g: 255,
        b: 0
    });
    currentState = setToBlue;
};

async function setToBlue() {
    setMainLed({
        r: 0,
        g: 0,
        b: 255
    });
    currentState = setToIndigo;
};

async function setToIndigo() {
    setMainLed({
        r: 16,
        g: 0,
        b: 64
    });
    currentState = setToViolet;
};

async function setToViolet() {
    setMainLed({
        r: 64,
        g: 0,
        b: 64
    });
    currentState = setToRed;
    speak('All done! Shake again to start over.');
    await delay(2.0);
};

Spinner

Spinner turns your robot into a "flick spinner" (the kind that you flick with your finger, usually comes in board games). It uses a state machine, getGyroscope, and getOrientation to choose an item from an array depending on the robot's yaw.

// CONSTANTS ===================================================================
var YIELD_DELAY = 0.033;
var TIPPED_ANGLE = 45.0;
var SPIN_START_THRESH = 20.0;
var SPIN_STOP_THRESH = 5.0;
var YAW_BIAS = 45.0;
var DIRECTIONS = ['ONE', 'TWO', 'THREE', 'FOUR'];

// VARIABLES ===================================================================
var currentState = null;
var direction = null;

// MAIN LOOP ===================================================================
async function startProgram() {
    initialize();
    await giveDirections();
    while (!tipped()) {
        if (currentState) {
            await currentState();
        }
        await delay(YIELD_DELAY);
    }
    exitProgram();
};

// HELPER FUNCTIONS ============================================================
async function giveDirections() {
    speak('Spin me.');
    await delay(1.0);
};

tipped = function() {
    return Math.abs(getOrientation().roll) > TIPPED_ANGLE;
};

initialize = function() {
    setStabilization(false);
    resetAim();
    currentState = waitingForSpin;
};

boundAngle = function(angle) {
    while (angle > 360.0) {
        angle -= 360.0;
    }
    while (angle < 0.0) {
        angle += 360.0;
    }
    return angle;
};

valueFromHeading = function() {
    var yaw = getOrientation().yaw + YAW_BIAS;
    var index = 0;
    yaw = boundAngle(yaw);
    // speak('' + yaw);
    index = Math.floor(yaw / 360.0 * DIRECTIONS.length);
    direction = DIRECTIONS[index];
};

// STATES ======================================================================
async function waitingForSpin() {
    if (Math.abs(getGyroscope().pitch) > SPIN_START_THRESH) {
        currentState = updateWhileSpinning;
        valueFromHeading();
    }
};

async function updateWhileSpinning() {
    valueFromHeading();
    if (Math.abs(getGyroscope().pitch) < SPIN_STOP_THRESH) {
        speak(direction);
        await delay(1.0);
        currentState = waitingForSpin;
    }
};

Map

Map uses 2 dimensional arrays and a state machine to create a world you can explore by tipping your robot side-to-side or forward-and-back.

var YIELD_DELAY = 0.033;
var TIPPED_ANGLE = 35.0;
var RESET_THRESH = 15.0;

var currentState = null;
var map = [
    ["ON THE CLIFFS", "ON THE CLIFFS", "ON THE BEACH", "ON THE BEACH", "NEAR THE JETTY"],
    ["IN A CREVICE", "ON A RIDGE", "IN THE HILLS", "IN A GLADE", "BY A ROCKY OUTCROPPING"],
    ["ON A RIDGE", "IN THE HILLS", "IN A CLEARING", "ON THE PLAINS", "ON THE PLAINS"],
    ["IN A CANYON", "IN THE HILLS", "ON A GRASSY KNOLL", "IN THE FOREST", "IN THE FOREST"],
    ["IN A CANYON", "ON THE PLAINS", "IN THE FIELDS", "IN THE FOREST", "IN THE DARK FOREST"],
];
var location = {
    row: 0,
    col: 0
};
var locDesc = null;

// MAIN LOOP ===================================================================
async function startProgram() {
    await giveInstructions();
    await initialize();
    while (true) {
        if (currentState) {
            await currentState();
        }
        await delay(YIELD_DELAY);
    }
    exitProgram();
}

// HELPER FUNCTIONS ============================================================
tipped = function() {
    return Math.abs(getOrientation().roll) > TIPPED_ANGLE;
};

async function giveInstructions() {
    speak('Change pitch and roll to navigate the map.');
    await delay(3.0);
};

async function initialize() {
    location.row = Math.floor(map.length / 2);
    location.col = Math.floor(map[0].length / 2);
    currentState = updateLocation;
    registerEvent(EventType.onCollision, onShake);
    await sayWhere();
};

async function sayWhere() {
    speak('You are ' + map[location.row][location.col]);
    await delay(2.0);
};

async function sayNoGo() {
    speak("You can't go that way.");
    await delay(2.0);
};


async function onShake() {
    exitProgram();
};

async function changePosition(rowMod, colMod) {
    var oldCol = location.col;
    var oldRow = location.row;
    location.col += colMod;
    location.col = Math.min(location.col, map[0].length - 1);
    location.col = Math.max(0, location.col);
    location.row += rowMod;
    location.row = Math.min(location.row, map.length - 1);
    location.row = Math.max(0, location.row);
    if (oldCol === location.col && oldRow === location.row) {
        setMainLed({
            r: 255,
            g: 0,
            b: 0
        });
        await sayNoGo();
    } else {
        setMainLed({
            r: 0,
            g: 255,
            b: 0
        });
        await sayWhere();
    }
    currentState = waitForReset;
};

// STATES ======================================================================
async function updateLocation() {
    var orient = getOrientation();
    if (orient.roll > TIPPED_ANGLE) {
        await changePosition(0, 1);
    } else if (orient.roll < -TIPPED_ANGLE) {
        await changePosition(0, -1);
    } else if (orient.pitch > TIPPED_ANGLE) {
        await changePosition(1, 0);
    } else if (orient.pitch < -TIPPED_ANGLE) {
        await changePosition(-1, 0);
    }
};

async function waitForReset() {
    var roll = Math.abs(getOrientation().roll);
    var pitch = Math.abs(getOrientation().pitch);
    if (roll < RESET_THRESH && pitch < RESET_THRESH) {
        currentState = updateLocation;
        setMainLed({
            r: 0,
            g: 0,
            b: 0
        });
    }
};