The inPulse Magic 8-Ball

A Beginner's Tutorial for Programming inPulse Smartwatch Apps

Download .zip Download .tar.gz View on GitHub

Get Started Programming inPulse Smartwatch Apps

A pair of inPulse watches, one displaying the time, the other displaying the body of an email.

inPulse Magic 8-Ball is a tutorial for programmers who want to get started developing apps for the inPulse smartwatch and who don't have very much experience with C or any experience programming embedded devices. In this tutorial, you'll:

  • Download and set up a Linux virtual machine on your computer, in which you'll develop apps and run them in an inPulse watch simulator
  • Get comfortable with the development environment and inPulse programming by writing a "Hello, world!" app
  • Get a little more inPulse programming practice by writing a a "Magic 8-Ball" app

What You'll Need

In order to go through this tutorial and build the inPulse Magic 8 Ball app, you'll need the following:

A computer that can run Oracle VM VirtualBox 4.1.x. You'll develop the app and run it in the simulator, all from within a virtual Linux environment, and you'll need Oracle VM VirtualBox to run that virtual Linux environment. If you go to the Oracle VM VirtualBox downloads page, you'll see all the operating systems it runs on: Windows 32- and 64-bit, Mac OS X, Solaris 10 and a fair number of Linux distributions (Debian, Fedora, Mandriva, openSUSE, Red Hat Enterprise Linux, SUSE and Ubuntu). Ideally, your computer should have at least 8GB of RAM.

Some programming experience and a little familiarity with the C programming language. Don't let the fact that the inPulse is programmed in C scare you off: C programming can be pretty straightforward, and you'll find that many of the concepts from your preferred programming language will apply. If you use a programming language that's similar to C, such as C#, Java, JavaScript, Perl or PHP, you'll be at home with much of the C syntax. If you're new to C, there are many free online resources to help you get started, such as Zed Shaw's Learn C the Hard Way.

(In case you're wondering which flavor of C is used for inPulse programming, it's gcc's implementation of C99.)

That's it! You won't even need an inPulse smartwatch. In this tutorial, you'll run all the apps in the simulator.

Setting Up the Development Environment

In order to develop apps for the inPulse smartwatch simulator, you'll need to do two things:

  • Download and install VirtualBox, which you'll use to run the virtual machine image.
  • Download and uncompress the virtual disk image, a virtual hard disk containing a complete Linux system that's already been set up with everything you need to start developing inPulse apps. This system will run in its own window as if it were another program running on your computer.

Download and Install VirtualBox

Go to the Oracle VM VirtualBox download page , look under the section marked Oracle VM VirtualBox for the link for the installer for your computer's operating system and click on it.

If You're Running Mac OS

If your operating system is Mac OS, the installer will come in a disk image (.dmg) file. Double-click the installer icon to open the disk image, which will create a virtual disk drive named VirtualBox on your desktop. The virtual disk drive should open automatically; if it doesn't, double-click it to see its contents. You should see something like this:

Double-click on the icon marked VirtualBox.mpkg to install Oracle VM VirtualBox. Once it's installed, open your Applications folder and double-click the VirtualBox icon to launch it.

If You're Running Windows

If your operating system is Windows, the installer will come as an executable (.exe) file. Double-click on its icon to start the installer, then launch VirtualBox from the Start menu.

If You're Running Linux

If your operating system is Linux, chances are you know what to do with the package format for your particular distribution and how to launch VirtualBox once it's installed. (I'll post a set of instructions for Ubuntu soon.)

Download and Uncompress the Virtual Disk Image

Download the compressed virtual disk image, inPulseSimulator.rar, then uncompress it. You might need to download a utility to uncompress .rar files, such as The Unarchiver for Mac OS or WinRAR or 7-Zip for Windows.

The uncompressed file will have the filename inPulseSimulator.vdi. Put it in whatever directory you use to store your development work.

Set Up the Virtual Machine

If you haven't done so already, launch VirtualBox. You should see a window that looks like this:

Click New. This will start the New Virtual Machine Wizard, which walks you through the steps of setting up a virtual machine:

Click Continue, which takes you to the next step, where you specify what sort of operating system will run in the virtual machine:

Do the following:

  • Give your virtual machine a name -- I chose inPulseDev, but you can name it however you like -- and enter it into the Name field.
  • For the Operating System, select Linux.
  • For the Version, select Linux 2.6.
  • Click Continue.

You'll be taken to the next step, where you specify the minimum amount of RAM to be allocated to the virtual machine.

On my computer, an 8GB MacBook Pro, I gave the virtual machine 4096MB (4GB) of RAM. This setting seems to work well for both the Mac OS host and Linux running in the virtual machine. You can experiment and see if you get acceptable performance from the virtual machine with less RAM.

Once you've set the amount of RAM to be allocated to the virtual machine, click Continue. You'll be taken to the next step, where you set up a virtual hard disk.

We want the virtual machine to boot from this virtual disk, so make sure that Start-up Disk is checked. The virtual disk image you downloaded earlier is the one we want to use, so:

  • Select Use existing hard disk
  • Click on the folder icon, use it to navigate to wherever you stored inPulseSimulator.vdi and select it.
  • Click Continue.

You'll be taken to the summary page, where you can review the settings you chose:

The summary should list the following:

  • Name: Whatever name you gave the virtual machine
  • OS Type: Linux 2.6
  • Base Memory: Whatever you allocated -- mine says 4096MB
  • Start-up Disk: inPulseSimulator.vdi (Normal, 10.00GB)

Click the Create button. Your virtual machine will be created, and you'll be returned to the window you saw when you launched VirtualBox, with one notable difference:

In the left pane of the window, you'll see the virtual machine you just created.

Start the Virtual Machine

It's time to start the virtual machine to confirm that it works. Click on the virtual machine to make sure it's selected, then click Start. The virtual machine will start booting up, just like a hardware machine would, only more quickly. Dismiss any dialog boxes that may appear during the startup process.

At some point, you should see the Ubuntu logo:

...and once the system has booted, you should see something like this:

The virtual disk image contains an older, out-of-date version of Ubuntu that isn't supported anymore. Don't let this bother you; this version is perfectly fine for running the development tools and the inPulse simulator.

Dismiss the dialog box that says "Your Ubuntu release is not supported anymore" and close the Update Manager. You should now have a clear view of the desktop, which has four icons. One of the icons may be on top of another; rearrange the icons so that none of them overlap.

The desktop should now look something like this:

Congratulations! You've set up the virtual machine and are now ready to start writing inPulse apps.

Before we start developing apps, let me show you how to shut down the virtual machine.

Shut Down the Virtual Machine

Eventually, you'll want to shut down the virtual machine and work on something else (or get away from the computer entirely). You can do this by simply closing the virtual machine's window. The contents of the window will fade to grey and you'll be prompted with a dialog box:

You can choose any of these options; pick the one you prefer and click OK to shut down the virtual machine. I prefer the Save the machine state option, which "freezes the virtual machine in time". When I start it again, restores the virtual machine to the exact same state as when I "froze it in time", running programs and all.

Exploring the Development Environment

You'll find four icons on the desktop of the development environment:

pulse_simulator: A link to the directory containing the files you'll be using to develop apps, as well as some example code. The actual directory is located at /home/pulsesim/pulse_simulator. When you start developing inPulse apps, you work in this directory.

Getting Started: A link to inPulse's Getting Started with the inPulse Simulator web page. That topic is also covered in this tutorial.

username+password.txt: A text file containing the username and password of the pulsesim user, which you might need from time to time. The username and password are the same: pulsesim.

Terminal: A link to the command-line Terminal program, which you'll use to compile and run your apps.

Let's take a closer look at the contents of pulse_simulator:

Of these contents, the ones that of the most important to you in the short term are:

  • Makefile: Configures the make command, which you'll use to compile inPulse apps.
  • src: The directory for source files. The code you write goes here.
  • include: The directory for the header files that define inPulse's built-in functions and supported data types, which you'll need to include in your source code.
  • build: The directory where your app, once compiled, goes.
  • examples: A directory full of example code that you can use to learn more about inPulse programming.

Making Sure You Have the Latest Simulator

Before we do anything else, we need to make sure that the simulator is up to date. We'll do this by running the update_simulator script.

Open the Terminal program. You can do this many ways; one way is to double-click on the Terminal icon on the desktop.

Once Terminal is open, get a directory listing by entering the ls command. You should see the following:

pulsesim@ubuntu-desktop:~$ ls
Desktop    examples.desktop  Public           update_simulator
Documents  Music             pulse_simulator  Videos
Downloads  Pictures          Templates

(Note: Any time we show you an example from a Terminal session, any commands you enter will be show in green.)

Enter ./update_simulator at the prompt. This will run a script that will download the latest versions of the simulator files into the pulse_simulator directory (your computer will have to be online). You should see the following:

pulsesim@ubuntu-desktop:~$ ./update_simulator 
Downloading latest Pulse Simulator
--2012-07-20 15:47:23--  http://www.getinpulse.com/images/pulsesim/pulse_simulator.zip
Resolving www.getinpulse.com... 107.21.230.67
Connecting to www.getinpulse.com|107.21.230.67|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 817048 (798K) [application/zip]
Saving to: `pulse_simulator.zip'

100%[======================================>] 817,048      744K/s   in 1.1s    

2012-07-20 15:47:24 (744 KB/s) - `pulse_simulator.zip' saved [817048/817048]

Archive:  pulse_simulator.zip
replace pulse_simulator/Makefile? [y]es, [n]o, [A]ll, [N]one, [r]ename:

The script will pause at a prompt where it asks replace pulse_simulator/Makefile? [y]es, [n]o, [A]ll, [N]one, [r]ename:. Enter A for all, since we want all the files to be updated. You'll see a large list of messages announcing files being decompressed and put into their proper directories. When the messages stop and you are returned to the command prompt, the update is complete.

Compiling and Running the Sample App

Now that we've got the development environment set up, let's see the simulator in action! inPulse have set things up so that there's already a program ready to be compiled and run in the simulator.

Change to the pulse_simulator directory by entering cd pulse_simulator at the command line. Then enter make to compile the code:

pulsesim@ubuntu-desktop:~$ cd pulse_simulator/
pulsesim@ubuntu-desktop:~/pulse_simulator$ make
gcc -o build/pulse_app  -Iinclude -std=gnu99 -g -Wall lib/obj/main.o -Llib -D PULSE_SIMULATOR -lapp_common -lpulse_os -lpulse_protocol -lpulse_widgets -lgtk-x11-2.0 src/pulse_app.c
src/pulse_app.c: In function ‘main_app_handle_button_down’:
src/pulse_app.c:43: warning: implicit declaration of function ‘pulse_printf’
pulsesim@ubuntu-desktop:~/pulse_simulator$

make uses the Makefile in this directory as instructions for compiling the source code, which is in the file pulse_app.c, which in turn is stored in the src directory. The resulting compiled file, pulse_app, is put into the build directory.

(Don't worry about the warning issued by the compiler about the implicit declaration of function ‘pulse_printf’; it's the result of a little hack that makes development for the simulator a little easier.)

We have a compiled app -- let's run it! Enter build/pulse_app at the prompt, and you should see this window appear:

This app is called the Falling Box Demo. It draws a box that constantly falls from the top of the screen. When the box reaches the bottom of the screen, it's brought back to the top. Make a note of the size of the black area where the box is drawn: that's the area of the inPulse display, which is:

  • 96 pixels wide
  • 128 pixels high

The inPulse has a single button on its right side. In the simulator, it's represented by the big button labelled Button on the right side of the window.

Click Button. You'll see something like this:

The Button Down text appeared when you pressed down on the button, and the Button Up text appeared when you released it. This suggests that there's an event for each action, which you can use in your own apps.

Look at Terminal. You'll see that it's displaying the following:

Button Down
Button Up

The function that prints text to the watch screen also prints to the terminal, which is handy for debugging.

The simulator also has a menu bar, with two menus labelled Notifications and Hardware. For now, we're more interested in the menu bar itself, because it's used to simulate the inPulse vibrate function:

  • When vibrate is off, the menu bar is brown.
  • When vibrate is on, the menu bar is blue.

Once you're done trying out the sample app, stop it. You can do this by either:

  • Clicking on the simulator's "close window" button
  • Typing control-C in Terminal

Your First inPulse App: "Hello, World!"

Getting the Code

We're going to start coding from here on in. You can type in the code yourself or copy and paste it, and if you want to download it, it's all available on this GitHub source page.

The Skeleton

For all the exercises in this tutorial, we're going to be working on the pulse_app.c file, which is located in the src directory of pulse_simulator.

Open pulse_app.c with the editor of your choice. If you prefer to use a GUI-based editor, navigate to pulse_app.c and double-click it. This will open the file in gEdit, Ubuntu's default GUI editor.

pulse_app.c currently contains the code for the demo app we just ran. This code also exists in the examples/api_examples directory, in the file titled falling_box_using_main_loop.c, so we can overwrite it without worry. Since the Makefile is already set up to compile pulse_app.c, it's easiest just to use this file.

Select all the code in pulse_app.c and delete it. Copy the following code, paste it into pulse_app.c and save it:

hello_world_1.c

//
// Hello, World!
// =============
//
// A simple tutorial app that demonstrates displaying the time
// and other text on the inPulse smartwatch screen,
// as well as responding to button events.
//
// Since this is a tutorial app, it's a little more heavily
// commented than your typical code.


#include <stdint.h>

// Headers we'll need to program inPulse apps.
// - pulse_os.h gives us access to the inPulse API functions,
//   from telling the time to drawing on the screen to controlling
//   vibration and so on.
// - pulse_types.h provides data types that we use when using
//   the inPulse API.
#include <pulse_os.h>
#include <pulse_types.h>


// inPulse Event Handlers
// ======================
// These routines are called in response to inPulse watch events,
// such as the watch booting up, the user pressing and releasing
// the button and so on.

// main_app_init is called once: after the watch has booted up, its
// OS has been loaded and when the app is starting up. 
// Use this to initialize your app.
void main_app_init()
{

}

// main_app_handle_button_down is called every time the user presses down
// on the watch button.
void main_app_handle_button_down()
{

}

// main_app_handle_button_up is called every time the user releases the
// watch button after having pressed it.
void main_app_handle_button_up()
{

}

// main_app_loop is a function that gets called regularly and frequently.
// Use this for operations that your app will perform on a regular basis.
// No blocking calls are allowed in this function; any call that blocks
// for over 5 seconds will cause the inPulse watchdog timer to start and
// reset the watch.
void main_app_loop()
{

}

// main_app_handle_doz is called whenever the processor is about to go into
// sleep mode to conserve power. Use this to do any "housekeeping" before the
// watch goes to sleep.
// The sleep functionality is scheduled with pulse_update_power_down_timer.
void main_app_handle_doz()
{

}

// main_app_handle_hardware_update is called whenever certain hardware events
// take place, such as Bluetooth connection/disconnetion and changes in the
// "battery charging" status.
void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

This is the skeleton -- a little framework that we'll flesh out throughout this exercise. Read the comments in the code, as they explain a lot.

Switch to Terminal, compile the app with make and then run it. Your terminal window should look like this:

pulsesim@ubuntu-desktop:~/pulse_simulator$ make
gcc -o build/pulse_app  -Iinclude -std=gnu99 -g -Wall lib/obj/main.o -Llib -D PULSE_SIMULATOR -lapp_common -lpulse_os -lpulse_protocol -lpulse_widgets -lgtk-x11-2.0 src/pulse_app.c
pulsesim@ubuntu-desktop:~/pulse_simulator$ ./build/pulse_app 
No resources read from ../resources/inpulse_resources.bin

The simulator should be running and look like this:

It runs, but since it's just a skeleton without any functioning code within, it's not doing anything you can see. Stop the app (either by closing the simulator window or typing control-C in the terminal).

Displaying "Hello World" on the Screen

Just as with command-line desktop apps written in C, the printf function for inPulse sends text to stdout, or "standard output". In this particular case, anything sent to stdout is displayed in two places:

  • The simulator display
  • The console

To show the message Hello, world! on the simulator display, the following line of code needs to be executed:

printf("Hello, world!\n");

That's the easy part. The question is now "Where do we put that line of code?" In a desktop C program, you'd put it in the main function, but our inPulse app skeleton (and inPulse apps in general) doesn't have one.

The functions in our skeleton where we could place our printf statement are:

  • main_app_init: Called only once, when the watch boots up, the OS has been loaded and the app is starting up.
  • main_app_handle_button_down: Called whenever the user presses down on the watch button.
  • main_app_handle_button_up: Called whenever the user, after having pressed down on the watch button, releases it.
  • main_app_loop: Called regularly as the watch continues to operate.
  • main_app_handle_doz: Called at the time right before the watch is about to go into power-conserving sleep mode.
  • main_app_handle_hardware_update: Called whenever certain hardware events, such as Bluetooth connection/disconnection or changes in battery charging status take place.

main_app_init is called once, when the app starts up, so it sounds like a good candidate to place that line of code. Your code skeleton should look like this now:

hello_world_2.c

//
// Hello, World!
// =============
//
// A simple tutorial app that demonstrates displaying the time
// and other text on the inPulse smartwatch screen,
// as well as responding to button events.
//
// Since this is a tutorial app, it's a little more heavily
// commented than your typical code.


include <stdint.h>

// Headers we'll need to program inPulse apps.
// - pulse_os.h gives us access to the inPulse API functions,
//   from telling the time to drawing on the screen to controlling
//   vibration and so on.
// - pulse_types.h provides data types that we use when using
//   the inPulse API.
#include <pulse_os.h>
#include <pulse_types.h>


// inPulse Event Handlers
// ======================
// These routines are called in response to inPulse watch events,
// such as the watch booting up, the user pressing and releasing
// the button and so on.

// main_app_init is called once: after the watch has booted up, its
// OS has been loaded and when the app is starting up. 
// Use this to initialize your app.
void main_app_init()
{
    printf("Hello, world!");
}

// main_app_handle_button_down is called every time the user presses down
// on the watch button.
void main_app_handle_button_down()
{

}

// main_app_handle_button_up is called every time the user releases the
// watch button after having pressed it.
void main_app_handle_button_up()
{

}

// main_app_loop is a function that gets called regularly and frequently.
// Use this for operations that your app will perform on a regular basis.
// No blocking calls are allowed in this function; any call that blocks
// for over 5 seconds will cause the inPulse watchdog timer to start and
// reset the watch.
void main_app_loop()
{

}

// main_app_handle_doz is called whenever the processor is about to go into
// sleep mode to conserve power. Use this to do any "housekeeping" before the
// watch goes to sleep.
// The sleep functionality is scheduled with pulse_update_power_down_timer.
void main_app_handle_doz()
{

}

// main_app_handle_hardware_update is called whenever certain hardware events
// take place, such as Bluetooth connection/disconnetion and changes in the
// "battery charging" status.
void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

(Yes, that's a lot of code to show for a one-line change, but I thought it might be reassuring to see the whole thing, at least for your first attempt at inPulse code.)

Switch to Terminal, compile the app with make and then run it. Your terminal window should look like this:

pulsesim@ubuntu-desktop:~/pulse_simulator$ make
gcc -o build/pulse_app  -Iinclude -std=gnu99 -g -Wall lib/obj/main.o -Llib -D PULSE_SIMULATOR -lapp_common -lpulse_os -lpulse_protocol -lpulse_widgets -lgtk-x11-2.0 src/pulse_app.c
src/pulse_app.c: In function ‘main_app_init’:
src/pulse_app.c:41: warning: implicit declaration of function ‘pulse_printf’
pulsesim@ubuntu-desktop:~/pulse_simulator$ ./build/pulse_app
No resources read from ../resources/inpulse_resources.bin
Hello, world!

Note that the text Hello, world! appears in the terminal. Any output created by printf will appear on both the simulator display and in the terminal.

The simulator should be running and look like this:

Responding to Button Presses

Let's make a couple of changes to the current app:

  • When the user presses down on the button, let's have the watch display the text Button goes down...
  • When the user releases the button, the watch should display the text ...button goes up!

To do this, we'll add a couple of printf statements: one to main_app_handle_button_down and one to main_app_handle_button_up. Here's how the code should look:

hello_world_3.c

//
// Hello, World!
// =============
//
// A simple tutorial app that demonstrates displaying the time
// and other text on the inPulse smartwatch screen,
// as well as responding to button events.
//
// Since this is a tutorial app, it's a little more heavily
// commented than your typical code.


#include <stdint.h>

// Headers we'll need to program inPulse apps.
// - pulse_os.h gives us access to the inPulse API functions,
//   from telling the time to drawing on the screen to controlling
//   vibration and so on.
// - pulse_types.h provides data types that we use when using
//   the inPulse API.
#include <pulse_os.h>
#include <pulse_types.h>


// inPulse Event Handlers
// ======================
// These routines are called in response to inPulse watch events,
// such as the watch booting up, the user pressing and releasing
// the button and so on.

// main_app_init is called once: after the watch has booted up, its
// OS has been loaded and when the app is starting up. 
// Use this to initialize your app.
void main_app_init()
{
    printf("Hello, world!\n");
}

// main_app_handle_button_down is called every time the user presses down
// on the watch button.
void main_app_handle_button_down()
{
    printf("Button goes down...\n");
}

// main_app_handle_button_up is called every time the user releases the
// watch button after having pressed it.
void main_app_handle_button_up()
{
    printf("...button goes up!\n");
}

// main_app_loop is a function that gets called regularly and frequently.
// Use this for operations that your app will perform on a regular basis.
// No blocking calls are allowed in this function; any call that blocks
// for over 5 seconds will cause the inPulse watchdog timer to start and
// reset the watch.
void main_app_loop()
{

}

// main_app_handle_doz is called whenever the processor is about to go into
// sleep mode to conserve power. Use this to do any "housekeeping" before the
// watch goes to sleep.
// The sleep functionality is scheduled with pulse_update_power_down_timer.
void main_app_handle_doz()
{

}

// main_app_handle_hardware_update is called whenever certain hardware events
// take place, such as Bluetooth connection/disconnetion and changes in the
// "battery charging" status.
void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

Switch to Terminal, compile the app with make and then run it. Your terminal window should look like this:

pulsesim@ubuntu-desktop:~/pulse_simulator$ make
gcc -o build/pulse_app  -Iinclude -std=gnu99 -g -Wall lib/obj/main.o -Llib -D PULSE_SIMULATOR -lapp_common -lpulse_os -lpulse_protocol -lpulse_widgets -lgtk-x11-2.0 src/pulse_app.c
src/pulse_app.c: In function ‘main_app_init’:
src/pulse_app.c:42: warning: implicit declaration of function ‘pulse_printf’
pulsesim@ubuntu-desktop:~/pulse_simulator$ ./build/pulse_app
No resources read from ../resources/inpulse_resources.bin
Hello, world!

The simulator will run and initially look like this:

If you press and release Button, it should look like this:

If you press and release Button more times, the output text will reach past the bottom of the display. When that happens, the text doesn't scroll; instead, the display is cleared, and any remaining text is put at the top:

Telling the Time and Clearing the Display

The inPulse is a smartwatch, which implies that its users will use it to tell the time. It uses its own type (defined in <pulse_types.h>) -- a struct called pulse_time_tm -- to represent time and date data. Here are its members:

Member Type Description
tm_sec int32_t Seconds [0 - 60]
tm_min int32_t Minutes [0 - 59]
tm_hour int32_t Hour [0 - 23]
tm_mday int32_t Day of the month [1 - 31]
tm_mon int32_t Month [0 - 11, where 0 is January]
tm_year int32_t

Year.

Note that this is different from the way the year is expressed in the tm structure of the standard <time.h> or <ctime> library, where this value is expressed as years since 1900.

tm_wday int32_t Day of the week [0 - 6, where 0 is Sunday]
tm_yday int32_t Day of the year [0 - 365, where 0 is January 1]
tm_isdat int32_t (Unused) Daylight saving time status

To get the current time and date, pass the address of a variable of type pulse_time_tm to the API function pulse_get_time_date. When the function returns, you can extract the time and date information from the pulse_time_tm variable. Here's an example:

struct pulse_time_tm current_time;
pulse_get_time_date(&current_time);
printf("The time is\n%d:%0.2d:%0.2d\n", 
       current_time.tm_hour,
       current_time.tm_min,
       current_time.tm_sec);

Clearing the display is pretty simple: a call to the API function pulse_blank_canvas does just that.

We can take the current "Hello, world!" code and with very little work, change it into an app that does the following:

  • Display Hello, world! when it starts up
  • Clear the display when the button is pushed down
  • Display the current time when the button is released

Here's what the code looks like:

hello_world_4.c

//
// Hello, World!
// =============
//
// A simple tutorial app that demonstrates displaying the time
// and other text on the inPulse smartwatch screen,
// as well as responding to button events.
//
// Since this is a tutorial app, it's a little more heavily
// commented than your typical code.


#include <stdint.h>

// Headers we'll need to program inPulse apps.
// - pulse_os.h gives us access to the inPulse API functions,
//   from telling the time to drawing on the screen to controlling
//   vibration and so on.
// - pulse_types.h provides data types that we use when using
//   the inPulse API.
#include <pulse_os.h>
#include <pulse_types.h>


struct pulse_time_tm current_time;


// inPulse Event Handlers
// ======================
// These routines are called in response to inPulse watch events,
// such as the watch booting up, the user pressing and releasing
// the button and so on.

// main_app_init is called once: after the watch has booted up, its
// OS has been loaded and when the app is starting up. 
// Use this to initialize your app.
void main_app_init()
{
    printf("Hello, world!\n");
}

// main_app_handle_button_down is called every time the user presses down
// on the watch button.
void main_app_handle_button_down()
{
    pulse_blank_canvas();
}

// main_app_handle_button_up is called every time the user releases the
// watch button after having pressed it.
void main_app_handle_button_up()
{
    pulse_get_time_date(amp;urrent_time);
    printf("The time is\n%d:%0.2d:%0.2d\n", 
           current_time.tm_hour,
           current_time.tm_min,
           current_time.tm_sec);
}

// main_app_loop is a function that gets called regularly and frequently.
// Use this for operations that your app will perform on a regular basis.
// No blocking calls are allowed in this function; any call that blocks
// for over 5 seconds will cause the inPulse watchdog timer to start and
// reset the watch.
void main_app_loop()
{

}

// main_app_handle_doz is called whenever the processor is about to go into
// sleep mode to conserve power. Use this to do any "housekeeping" before the
// watch goes to sleep.
// The sleep functionality is scheduled with pulse_update_power_down_timer.
void main_app_handle_doz()
{

}

// main_app_handle_hardware_update is called whenever certain hardware events
// take place, such as Bluetooth connection/disconnetion and changes in the
// "battery charging" status.
void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

Switch to Terminal, compile the app with make and then run it. Your terminal window should look like this:

pulsesim@ubuntu-desktop:~/pulse_simulator$ make
gcc -o build/pulse_app  -Iinclude -std=gnu99 -g -Wall lib/obj/main.o -Llib -D PULSE_SIMULATOR -lapp_common -lpulse_os -lpulse_protocol -lpulse_widgets -lgtk-x11-2.0 src/pulse_app.c
src/pulse_app.c: In function ‘main_app_init’:
src/pulse_app.c:45: warning: implicit declaration of function ‘pulse_printf’
pulsesim@ubuntu-desktop:~/pulse_simulator$ ./build/pulse_app
No resources read from ../resources/inpulse_resources.bin
Hello, world!

Here's what the simulator looks like when the app starts:

Here's what it looks like while the user presses or holds down Button:

And here's what it looks like when the user releases Button:

Let's Make That Magic 8-Ball App

A Leaner Skeleton

Let's start with some leaner skeleton code -- it's the same as the skeleton code from above, but without all the comments:

lean_inpulse_skeleton.c

//
// Lean inPulse Skeleton
// =====================
//
// A skeleton for inPulse smartwatch apps.

#include <stdint.h>

#include <pulse_os.h>
#include <pulse_types.h>


// inPulse Event Handlers
// ======================

void main_app_init()
{

}

void main_app_handle_button_down()
{

}

void main_app_handle_button_up()
{

}

void main_app_loop()
{

}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

The main_app_loop Handler

The main_app_loop handler is called regularly and often. If you're familiar with event-driven programming, think of it as being similar to a method called every time you go through the event loop. If you're familiar with game programming, think of it as being like a method called every time you go through the game loop.

Here's a little code that shows main_app_loop in action:

main_app_loop_demo.c

//
// Main App Loop Demo
// ==================
//
// An app that demonstrates the inPulse API's
// main_app_loop event handler.

#include <stdint.h>

#include <pulse_os.h>
#include <pulse_types.h>


struct pulse_time_tm current_time;


// inPulse Event Handlers
// ======================

void main_app_init()
{

}

void main_app_handle_button_down()
{

}

void main_app_handle_button_up()
{

}

void main_app_loop()
{
    pulse_get_time_date(&current_time);
    printf("The time is\n%d:%0.2d:%0.2d\n", 
           current_time.tm_hour,
           current_time.tm_min,
           current_time.tm_sec);
}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

When you run the app, this is what the simulator looks like:

Here's what the output to the console looks like on my computer (an early 2011-era MacBook Pro):

The time is
3:54:13
The time is
3:54:13
The time is
3:54:13
The time is
3:54:13
The time is
3:54:13
The time is
3:54:14
The time is
3:54:14
The time is
3:54:14
The time is
3:54:14
The time is
3:54:14
The time is
3:54:15
The time is
3:54:15
The time is
3:54:15
The time is
3:54:15
The time is
3:54:15
The time is
3:54:16
The time is
3:54:16
The time is
3:54:16
The time is
3:54:16
The time is
3:54:16

On my system, main_app_loop seems to get called about 5 times a second. I don't know anything about how the simulator was programmed, so it's possible that the rate at which mail_app_loop gets called may vary with the speed of your system.

At the very least, you can count on it getting called a handful of times each second. This means you can use it for:

  • "Listener"-type operations, where the code constantly checks to see if some condition has been met
  • "Clockwork"-type operations, where the code needs to perform some kind of regular update, such as an animation

In the next example, we'll look at a "listener"-type use of main_app_loop.

The Main Timer (or: "How Long Have I Been Up?")

The main timer keeps track of the number of milliseconds since the watch (or simulator) has booted up. It continues to do so until the watch goes into sleep mode. When the watch wakes up, it resets to zero and starts counting the milliseconds since it woke up.

You can get the main timer's count of milliseconds with the API function pulse_get_millis, which returns a value of type uint32_t.

Here's an app that makes use of pulse_get_millis. Here's what it does:

  • Display Starting app! on startup, along with the main timer's value at that time.
  • Every 5 seconds from then on, it clears the display, announces that 5 seconds have passed and shows both the main timer's value at that time as well as the interval (in milliseconds) between this announcement and the previous one.

main_timer_experiment.c

//
// Main Timer Experiment
// =====================
//
// An app written to show the use of the inPulse API's
// pulse_get_millis and main_app_loop functions
// in tandem.

#include <stdint.h>

#include <pulse_os.h>
#include <pulse_types.h>


#define INTERVAL_LENGTH_MS 5000
uint32_t start_interval_ms;
uint32_t now_ms;


// inPulse Event Handlers
// ======================

void main_app_init()
{
    start_interval_ms = pulse_get_millis();
    printf("Starting app!\n");
    printf("Timer: %d\n", start_interval_ms);
}

void main_app_handle_button_down()
{

}

void main_app_handle_button_up()
{

}

void main_app_loop()
{
    now_ms = pulse_get_millis();
    if (now_ms - start_interval_ms > INTERVAL_LENGTH_MS) {
        pulse_blank_canvas();
        printf("5 seconds have\npassed!\n");
        printf("Timer: %d\n", now_ms);
        printf("Actual interval:%d\n", now_ms - start_interval_ms);
        start_interval_ms = pulse_get_millis();
    }
}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

Here's the app's initial output:

Here it is, about five seconds later:

Here it is again, a few five-second intervals later:

Here's a capture of the app's console output:

Starting app!
Timer: 0
5 seconds have
passed!
Timer: 5197
Actual interval:5197
5 seconds have
passed!
Timer: 10397
Actual interval:5195
5 seconds have
passed!
Timer: 15597
Actual interval:5194
5 seconds have
passed!
Timer: 20802
Actual interval:5200
5 seconds have
passed!
Timer: 25998
Actual interval:5192
5 seconds have
passed!
Timer: 31193
Actual interval:5190
5 seconds have
passed!
Timer: 36388
Actual interval:5189
5 seconds have
passed!
Timer: 41587
Actual interval:5194

As you can see, on my computer, the accuracy of the intervals is about off by a little under 200 milliseconds -- one-fifth of a second. This shouldn't be a surprise, as we determined in the previous exercise that on my computer, the main_app_loop handler gets called 5 times a second.

For many apps, being off by a fifth of a second isn't a problem. But let's suppose we want this app to be more accurate. We can do this by reducing the value of INTERVAL_LENGTH_MS. Looking at the console output above and doing a little math, we find that the average actual interval is 5193 milliseconds, or 193 milliseconds longer than our desired interval length. If we subtract 193 from INTERVAL_LENGTH_MS, changing its value to 4807, the app becomes considerably more accurate. Here's the console output for this case:

Starting app!
Timer: 0
5 seconds have
passed!
Timer: 4994
Actual interval:4994
5 seconds have
passed!
Timer: 9992
Actual interval:4993
5 seconds have
passed!
Timer: 14986
Actual interval:4988
5 seconds have
passed!
Timer: 19986
Actual interval:4996
5 seconds have
passed!
Timer: 24989
Actual interval:4999
5 seconds have
passed!
Timer: 29988
Actual interval:4994
5 seconds have
passed!
Timer: 34986
Actual interval:4994
5 seconds have
passed!
Timer: 39984
Actual interval:4994

With this single adjustment, the average interval is within 6 milliseconds of our target interval of 5 seconds. That's pretty good; 6 milliseconds is approximately the time it takes sound to travel 6 feet.

At Last, The Magic 8-Ball

We now have enough inPulse programming know-how to code a "Magic 8-Ball" app. It should do the following:

  • It should normally display the current time, showing the hour, minutes and seconds.
  • When the user presses the button down, the display should read Think of a yes-or-no question.
  • When the user releases the button, the display should show a "Magic 8-Ball" answer for 5 seconds, then return to its normal display of the current time.

Here's the code for this app:

inpulse_magic_8_ball.c

//
// InPulse Magic 8-Ball
// ====================
//
// A "Magic 8-ball" app for the inPulse smartwatch.
// Normally, it displays the time in h:mm:ss format.
// When the user presses the button, it displays
// a "Magic 8-ball"-style answer for five seconds,
// and then returns to its normal mode of showing
// the time.

#include <stdint.h>
#include <stdlib.h>

#include <pulse_os.h>
#include <pulse_types.h>


enum DisplayMode_t { TIME_MODE, ANSWER_MODE } display_mode;

// Magic 8-ball answers
#define  NUM_8BALL_ANSWERS 15
typedef  char* string;
string   magic_8ball_quotes[NUM_8BALL_ANSWERS];

// Timing
#define  TIME_UPDATE_INTERVAL_LENGTH_MS 900
#define  ANSWER_INTERVAL_LENGTH_MS 5000 
struct   pulse_time_tm current_time;
uint32_t interval_start_time_ms;
uint32_t interval_length_ms;
uint32_t now_ms;


void initialize_8ball_answers()
{
    magic_8ball_quotes[0]  = "I'd bet money on it";
    magic_8ball_quotes[1]  = "HELLZ YEAH!";
    magic_8ball_quotes[2]  = "Yes. Absolutely.";
    magic_8ball_quotes[3]  = "Signs point to yes.";
    magic_8ball_quotes[4]  = "Without a doubt.";
    magic_8ball_quotes[5]  = "Ask again later.";
    magic_8ball_quotes[6]  = "I'm not sure; try again.";
    magic_8ball_quotes[7]  = "Mmmmmmmmmaybe.";
    magic_8ball_quotes[8]  = "Think and ask again.";
    magic_8ball_quotes[9]  = "Can't predict now.";
    magic_8ball_quotes[10] = "Ummm...no.";
    magic_8ball_quotes[11] = "HELLZ NO!";
    magic_8ball_quotes[12] = "Absolutely not!";
    magic_8ball_quotes[13] = "I seriously doubt it.";
    magic_8ball_quotes[14] = "No. No. No. NO!";
}

string random_magic_8ball_quote()
{
    return magic_8ball_quotes[rand() % NUM_8BALL_ANSWERS];
}

void set_time_mode()
{
    display_mode = TIME_MODE;
    interval_length_ms = TIME_UPDATE_INTERVAL_LENGTH_MS;
    interval_start_time_ms = pulse_get_millis();
}

void update_time_display()
{
    pulse_blank_canvas();
    pulse_get_time_date(¤t_time);
    printf("The time is\n%d:%0.2d:%0.2d\n", 
           current_time.tm_hour,
           current_time.tm_min,
           current_time.tm_sec);
}

void display_prequote_message()
{
    pulse_blank_canvas();
    printf("Think of a yes-\nor-no question.\n");
}

void set_answer_mode()
{
    display_mode = ANSWER_MODE;
    interval_length_ms = ANSWER_INTERVAL_LENGTH_MS;
    interval_start_time_ms = pulse_get_millis();
}

void display_answer()
{
    pulse_blank_canvas();
    printf("The 8-ball says:\n\"%s\"\n", random_magic_8ball_quote());
}


// inPulse Event Handlers
// ======================

void main_app_init()
{
    initialize_8ball_answers();
    set_time_mode();
    update_time_display();
}

void main_app_handle_button_down()
{
    display_prequote_message();
}

void main_app_handle_button_up()
{
    set_answer_mode();
    display_answer();
}

void main_app_loop()
{
    now_ms = pulse_get_millis();
    if (now_ms - interval_start_time_ms > interval_length_ms) {
        if (display_mode == ANSWER_MODE) {
            set_time_mode();
        }
        update_time_display();
    }
}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

Most of the time, the app is in "time mode", where it looks like this:

In time mode, the app displays the current time. The time interval when we check to see if it's time to update the display is set to 900 milliseconds, just below a full second. In main_app_loop, we check to see if we've been waiting longer than that interval. If that's the case, it means that nearly a second has passed and it's time to update the time display, which we do in update_time_display.

When the user presses and releases the button, the app is put in "answer mode" (with set_answer_mode), which looks like this:

In answer mode, the app displays a random "Magic 8-ball" answer for 5 seconds (display_answer), after which it returns to time mode. The time interval when we check to see if it's time to switch back to time mode is set to 5000 milliseconds, which is 5 seconds. In main_app_loop, we check to see if we've been waiting longer than that interval. If that's the case, it means that 5 seconds have passed and it's time to change to time mode (set_time_mode) and then display the time (update_time_display).

Another Way to Build the App

Using Timers

There's another way to implement this app. If you look through the pulse_os.h header file, you'll find the prototypes for a couple of API functions whose names contain the word timer:

  • pulse_register_timer: This function sets up a timer that calls a specified function after a defined amount of time has passed.
  • pulse_cancel_timer: This function cancels any timer that has been set up using pulse_register_timer.

pulse_register_timer lets you set up a timer to call a callback function with an argument after a specified number of milliseconds has passed. Here's its signature:

int32_t pulse_register_timer(uint32_t timeout_ms, 
                             PulseCallback callback,
                             void *data);

Here are its parameters, explained:

Parameter Type Description
timeout_ms uint32_t The time, in milliseconds, that the registered timer wait until it goes off and calls the callback function.
callback PulseCallback
-- a typedef in <pulse_types.h> for a proper C callback interface, i.e.:
(* callback)(void *callback_data).
The address of the function to be called when the timer goes off.
data void* The data to be passed as a parameter to the callback function.

Let's try a little experiment with pulse_register_timer. We'll put together an app that does the following:

  • It will start by displaying the message "Starting the timer".
  • 5 seconds later, it should clear the display, show the message "Hello there!" and the number of milliseconds that have passed since the app started.

Here's the code for this app:

timer_demo.c

//
// Timer Demo
// ==========
//
// An app that demonstrates the inPulse API's
// pulse_register_timer function. 

#include <stdint.h>

#include <pulse_os.h>
#include <pulse_types.h>


typedef char* string;


void display_message(string message)
{
    pulse_blank_canvas();
    printf("%s\n", message);
    printf("Main timer:\n%d\n", pulse_get_millis());
}

// inPulse Event Handlers
// ======================

void main_app_init()
{
    printf("Starting the\ntimer.");
    pulse_register_timer(5000, &display_message, "Hello there!");
}

void main_app_handle_button_down()
{

}

void main_app_handle_button_up()
{

}

void main_app_loop()
{

}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

Before we run the app, let's take a quick look at the call to pulse_register_timer:

pulse_register_timer(5000, &display_message, "Hello there!");

Here's what the call does:

  • It sets the timer interval for 5000 milliseconds -- 5 seconds.
  • When the timer interval is reached, the timer calls the callback function, display_message.
  • When calling the callback function, the timer passes it a parameter: Hello there!.

For now, we're ignoring pulse_register_timer's return value; we'll get back to it after we run the app.

Switch to Terminal, compile the app with make and then run it. Your terminal window should look like this:

pulsesim@ubuntu-desktop:~/pulse_simulator$ make
gcc -o build/pulse_app  -Iinclude -std=gnu99 -g -Wall lib/obj/main.o -Llib -D PULSE_SIMULATOR -lapp_common -lpulse_os -lpulse_protocol -lpulse_widgets -lgtk-x11-2.0 src/pulse_app.c
src/pulse_app.c: In function ‘display_message’:
src/pulse_app.c:20: warning: implicit declaration of function ‘pulse_printf’
src/pulse_app.c: In function ‘main_app_init’:
src/pulse_app.c:30: warning: passing argument 2 of ‘pulse_register_timer’ from incompatible pointer type
include/pulse_os.h:93: note: expected ‘PulseCallback’ but argument is of type ‘void (*)(char *)’
pulsesim@ubuntu-desktop:~/pulse_simulator$ ./build/pulse_app
No resources read from ../resources/inpulse_resources.bin
Starting the
timer.Hello there!
Main timer:
5196

Here's what the simulator looks like when the app starts:

And here's what it looks like about 5 seconds later:

The value below Main timer: is from pulse_get_millis, the main timer's count of milliseconds since the simulator started and the app started running. Note that the interval measured is a little more than 5 seconds -- on average, you'll that that there's a 200 millisecondthe offset is around 200 milliseconds, which is the same as the offset with the timing method we used in the Magic 8-Ball app above.

Multiple Timers

pulse_register_timer can be used to register multiple timers. Here's an app that uses three timers, which are set to go off at 3, 5 and 7 seconds.

Here's the code:

multiple_timer_demo.c

//
// Mulitple Timer Demo
// ===================
//
// An app that demonstrates the inPulse API's
// pulse_register_timer function to create
// multiple timers. 

#include <stdint.h>

#include <pulse_os.h>
#include <pulse_types.h>


typedef char* string;


void display_message(string message)
{
    uint32_t interval = pulse_get_millis();
    pulse_blank_canvas();
    printf("%s\n", message);
    printf("Main timer:\n%d\n", interval);
}

// inPulse Event Handlers
// ======================

void main_app_init()
{
    printf("Starting the\ntimer.");
    pulse_register_timer(3000, &display_message, "3 seconds.");
    pulse_register_timer(5000, &display_message, "5 seconds.");
    pulse_register_timer(7000, &display_message, "7 seconds.");
}

void main_app_handle_button_down()
{

}

void main_app_handle_button_up()
{

}

void main_app_loop()
{

}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

Once again, we're ignoring pulse_register_timer's return values.

Switch to Terminal, compile the app with make and then run it. Your terminal window should look like this:

pulsesim@ubuntu-desktop:~/pulse_simulator$ make
gcc -o build/pulse_app  -Iinclude -std=gnu99 -g -Wall lib/obj/main.o -Llib -D PULSE_SIMULATOR -lapp_common -lpulse_os -lpulse_protocol -lpulse_widgets -lgtk-x11-2.0 src/pulse_app.c
src/pulse_app.c: In function ‘display_message’:
src/pulse_app.c:21: warning: implicit declaration of function ‘pulse_printf’
src/pulse_app.c: In function ‘main_app_init’:
src/pulse_app.c:31: warning: passing argument 2 of ‘pulse_register_timer’ from incompatible pointer type
include/pulse_os.h:93: note: expected ‘PulseCallback’ but argument is of type ‘void (*)(char *)’
src/pulse_app.c:32: warning: passing argument 2 of ‘pulse_register_timer’ from incompatible pointer type
include/pulse_os.h:93: note: expected ‘PulseCallback’ but argument is of type ‘void (*)(char *)’
src/pulse_app.c:33: warning: passing argument 2 of ‘pulse_register_timer’ from incompatible pointer type
include/pulse_os.h:93: note: expected ‘PulseCallback’ but argument is of type ‘void (*)(char *)’
pulsesim@ubuntu-desktop:~/pulse_simulator$ ./build/pulse_app
No resources read from ../resources/inpulse_resources.bin
Starting the
timer.3 seconds.
Main timer:
3198
5 seconds.
Main timer:
5197
7 seconds.
Main timer:
7193
**************************

Here's what the simulator looks like when the app starts:

Here's what the simulator looks like about 3 seconds later:

Here's what the simulator looks like about 5 seconds later:

Here's what the simulator looks like about 7 seconds later:

Cancelling Timers

pulse_register_time returns a value of type int32_t, which is a unique ID for the timer. This ID can be fed into pulse_cancel_timer to cancel that specific timer.

Here's the code for an app that does the following:

  • Registers a timer that displays a message after 3 seconds have passed, saving its ID.
  • Registers another time that displays a message after 5 seconds have passed.
  • Cancels the first timer, using the ID that was saved.

cancel_timer_demo.c

//
// Timer Cancellation Demo
// =======================
//
// An app that demonstrates the inPulse API's
// pulse_register_timer and pulse_cancel_timer
// functions to create and cancel timers.

#include <stdint.h>

#include <pulse_os.h>
#include <pulse_types.h>


typedef char* string;


void display_message(string message)
{
    uint32_t interval = pulse_get_millis();
    pulse_blank_canvas();
    printf("%s\n", message);
    printf("Main timer:\n%d\n", interval);
}

// inPulse Event Handlers
// ======================

void main_app_init()
{
    printf("Starting the\ntimer.");
    int32_t timer_id = pulse_register_timer(3000, &display_message, "3 seconds.");
    pulse_register_timer(5000, &display_message, "5 seconds.");
    pulse_cancel_timer(&timer_id);
}

void main_app_handle_button_down()
{

}

void main_app_handle_button_up()
{

}

void main_app_loop()
{

}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

Here's what the simulator looks like when the app starts:

Here's what the simulator looks like about 3 seconds later. Note that the "3 seconds." message never appears, because the timer that would trigger it has been cancelled.

Here's what the simulator looks like about 5 seconds later:

And Now, Magic 8-Ball, With Timers!

Here's the code:

inpulse_magic_8_ball_timer.c

//
// InPulse Magic 8-Ball, Timer-Style!
// ==================================
//
// A "Magic 8-ball" app for the inPulse smartwatch.
// Normally, it displays the time in h:mm:ss format.
// When the user presses the button, it displays
// a "Magic 8-ball"-style answer for five seconds,
// and then returns to its normal mode of showing
// the time.
//
// To the user, this app is the same as the
// previous version. The difference is in the way
// timing is done -- this version uses inPulse's
// registered timer and the pulse_register_timer
// function instead of watching the main timer
// from inside the main app loop.

#include <stdint.h>
#include <stdlib.h>

#include <pulse_os.h>
#include <pulse_types.h>


// Magic 8-ball answers
#define  NUM_8BALL_ANSWERS 15
typedef  char* string;
string   magic_8ball_quotes[NUM_8BALL_ANSWERS];

// Timing
#define  TIME_UPDATE_INTERVAL_LENGTH_MS 900
#define  ANSWER_INTERVAL_LENGTH_MS 5000
#define  TIMER_CANCELLED -1
struct   pulse_time_tm current_time;
int32_t  timer_id = TIMER_CANCELLED;


void initialize_8ball_answers()
{
    magic_8ball_quotes[0]  = "I'd bet money on it";
    magic_8ball_quotes[1]  = "HELLZ YEAH!";
    magic_8ball_quotes[2]  = "Yes. Absolutely.";
    magic_8ball_quotes[3]  = "Signs point to yes.";
    magic_8ball_quotes[4]  = "Without a doubt.";
    magic_8ball_quotes[5]  = "Ask again later.";
    magic_8ball_quotes[6]  = "I'm not sure; try again.";
    magic_8ball_quotes[7]  = "Mmmmmmmmmaybe.";
    magic_8ball_quotes[8]  = "Think and ask again.";
    magic_8ball_quotes[9]  = "Can't predict now.";
    magic_8ball_quotes[10] = "Ummm...no.";
    magic_8ball_quotes[11] = "HELLZ NO!";
    magic_8ball_quotes[12] = "Absolutely not!";
    magic_8ball_quotes[13] = "I seriously doubt it.";
    magic_8ball_quotes[14] = "No. No. No. NO!";
}

string random_magic_8ball_quote()
{
    return magic_8ball_quotes[rand() % NUM_8BALL_ANSWERS];
}

void update_time_display();

void start_timer(uint32_t duration, PulseCallback callback)
{
    if (timer_id != TIMER_CANCELLED) {
      pulse_cancel_timer(&timer_id);
    }
    timer_id = pulse_register_timer(duration, callback, NULL);
}

void set_time_mode()
{
    start_timer(TIME_UPDATE_INTERVAL_LENGTH_MS,
                &update_time_display);
}

void update_time_display()
{
    pulse_blank_canvas();
    pulse_get_time_date(&current_time);
    printf("The time is\n%d:%0.2d:%0.2d\n", 
           current_time.tm_hour,
           current_time.tm_min,
           current_time.tm_sec);
    set_time_mode();
}

void display_prequote_message()
{
    pulse_blank_canvas();
    printf("Think of a yes-\nor-no question.\n");
}

void set_answer_mode()
{
    start_timer(ANSWER_INTERVAL_LENGTH_MS,
                &update_time_display);
}

void display_answer()
{
    pulse_blank_canvas();
    printf("The 8-ball says:\n\"%s\"\n", random_magic_8ball_quote());
}


// inPulse Event Handlers
// ======================

void main_app_init()
{
    initialize_8ball_answers();
    set_time_mode();
    update_time_display();
}

void main_app_handle_button_down()
{
    display_prequote_message();
}

void main_app_handle_button_up()
{
    set_answer_mode();
    display_answer();
}

void main_app_loop()
{

}

void main_app_handle_doz()
{

}

void main_app_handle_hardware_update(enum PulseHardwareEvent event)
{

}

At any given moment when the app is running, there is just one active timer, whose ID is stored in the variable timer_id. The timer does different things depending on the mode:

  • Time mode: The timer is set to 900 milliseconds, after which the display is updated with the current time with update_time_display, which also sets the mode to time mode.
  • Answer mode: The time is set to 5000 milliseconds, during which the display shows the Magic 8-Ball answer generated when the user pressed the button. When 5000 milliseconds has elapsed, update_time_display is called, which clears the answer from the display, shows the current time and sets the mode back to time mode.