Share this page to your:
Mastodon

Main Class

Now that we covered some of the Apps and a little of the infrastructure it is time to look at VortexManipulator.cpp, which contains the main code. It is useful to realise that adding a new app does not require changing this class, though understanding what it does will surely help you design a better app.

All Arduino sketches have a setup method and a loop method, the setup is run first and the loop is typically a while-loop that runs forever. While we aren't using Arduino exactly, we do have the setup and loop methods, and they live in the VortexManipulator class. There are several other things in this class too, and they are here because the setup and loop need to call them.

Setup

The setup() method first of all syncs the CPU clock with the Teensy clock (the one that is backed up by the coin cell battery). That ensures the CPU time is correct and it initialises the Serial interface. If you're familliar with Arduino programming you'll know this is used for sending messages back to your computer for debugging etc. Whether we are debugging or not we initialise Serial.

Logger

After that there are a lot of calls to the LoggerFactory. I wrote the LoggerFactory as a separate library, though with this project in mind, and I cover it in detail here. All you really need to know for now is that it allows me to turn on and off logging on sections of code. I use the LoggerFactory to get the logger I want, and then I use that logger to log messages to Serial, like this:

loggerVM = loggerFactory.getLogger("VM");
loggerVM->info("Vortex Manipulator version %s\n",VERSION);

In that case it logs the version number as an info message.

The next part of the setup initialises the hardware, load the configuration and initialises the Appregistry. The Appregistry is the thing that holds all the registered apps and it will discover anything new apps that have been added. The Hardware class has come up before (and will come up again). This contains all the methods needed to access the various hardware such as the LMS303 etc.

There is also a call to the startup feature.

    startup.run();

All it does is print some amusing nonsense on the screen. It does the same when it wakes from deep sleep.

Notice the line:

loggerVM->debug("Screen: width = %d height = %d\n",Graphics.width(),Graphics.height());

This will print nothing because further up we defined the "VM" logger like this:

loggerFactory.add("VM",LOG_LEVEL_INFOS);

which means it will only print info messages, not debug messages. If you need debug messages from the VM logger then you just change the add("VM",LOG_LEVEL_INFOS) to add("VM",LOG_LEVEL_DEBUG).

Intervals

The next thing to cover are the intervals. During the loop, which is the rest of the life of the device until it is shut down, there are lots of things that have to be checked based on times. You normally do this with hardware interrupts that just run a method every time a defined interval is passed. But there are only so many interrupts available to be configured like that, and I don't need very fast response. Well, I do, but the loop is very fast provided it is not checking too many things it doesn't need to.

My solution was the Interval. Each Interval holds an Action class to run, and the amount of time to wait before running it. These are all held in a list and the loop checks each Interval to see if it is ready run yet, and runs it if it is.

So the next thing we do in setup is define all the Intervals using statements like:

intervals.create(10L,new HRAction()); // 10 milliseconds

intervals is the class that holds the list and the create method creates the Interval and adds it to the list. In this case this is a very short interval (so this action will be run often) and it checks for the current heart rate. Actually it checks what the pulse sensor value is so that it can work out the heart rate later.

There are two types of intervals. One holds a time in milliseconds, the other holds a number of cycles the loop had been around. They have slightly different uses. The second type is simpler to reset to zero and I use it, for example, to put the device to sleep after so many cycles of inactivity. Any activity, of course, must reset that counter to prevent it sleeping while the device is busy.

Let's take a look at one of these Action classes so you can see what is in there

class SerialCheck : public Action {
public:
    SerialCheck(){};
    virtual const char* getName() {return PSTR("SerialCheck");};
    virtual boolean execute() {
        const char* buffer = Hardware.readBluetooth();
        if (buffer == NULL) {
            return true;
        }
        loggerVM->debug("from bluetooth [%s]",buffer);
        App *notification = Appregistry.getApp("Notification");
        Notification *n = (Notification *)notification;
        n->addMessage(buffer);
        return true;
    }
    virtual ~SerialCheck(){};
};

They always extend Action and they always have a getName() and execute() method. The execute() method is where all the interesting things happen. It reads the buffer from the bluetooth hardware, checks if there was anything and, if there was, adds it to the message list in the Notification app. Easy. It is best to keep the code to a minimum in these actions, they are just there for the Interval to call, and they should delegate their work as much as possible.

You will find Interval classes for the following:

The landing pad is a strip at the left side of the screen. Regardless of what app is displaying if you touch that it will always go back to the menu.

Sleep

At this point it is worth mentioning the sleep states in more detail. Counting the complete switch off, there are three sleep states:

Loop

The loop() method mostly just calls to check the intervals so it is quite short. Before I added intervals it was treacherously complex.

void loop() {
//  recordTimestamp();
    intervals.check();

    bool asleep = Hardware.isSleeping();

    // refresh the current app if its update interval was reached.
    unsigned long m = micros();
    if ((m-lastEventMicros) > Appregistry.getCurrentApp()->getUpdateInterval()) {
        if (!asleep) {
            Appregistry.getCurrentApp()->display();
        }
        lastEventMicros = m;
    }
    delay(LOOP_DELAY);
}

The intervals are checked in intervals.checked() of course. The rest of the code is handling the updates needed on the current app. Each app can declare its own update interval and this checks if that has elapsed. If it has, and if the device has not gone to sleep, it calls the app to display itself again.

This redisplay is very much the choice of the app. The Gallery, for example, does not need to redisplay because it is driven by touch. Other apps like the Dalek detector, need a rapid redisplay of their data.