Tutorial 3: Afero + Arduino

In Tutorial 2 we looked at a Device Profile that used only GPIO attributes and involved no external MCU. In this lesson we’ll add an Arduino as an example of an MCU that communicates with the Afero Secure Radio. This will demonstrate how you can incorporate ASR into a more complex product.

This project will provide the mobile user a start/stop control for LED blinking. When the user taps Start, an MCU attribute is set, which tells the Arduino to start a loop that blinks the LED on and off. This blinking will continue until the app user taps the Stop control to halt it.

In addition, the mobile app UI will demonstrate the use of a second control, used as a read-only indicator, that reflects the state of the blinking LED in real-time. When the LED is blinking on the Modulo, the remote mobile app will provide a blinking display as well:

afBlink App

First, we’ll run through the example, then take a closer look at how it all works.

Before You Begin

Be sure you’ve done the following before starting the steps below:

  • You’ve downloaded, installed, and signed in to the Afero mobile app and the Afero Profile Editor.
  • You have an Afero Modulo board.
  • You’ve got an Arduino Uno plus an Afero Plinto shield OR an Arduino Teensy, and you’ve connected the Afero Modulo to your Arduino. Refer to the Data Sheet appropriate for your Modulo if needed.
  • You’ve got the Arduino IDE (1.8 or later) up and running.

The Steps

  1. Download and install afLib3 for Arduino:
    1. You can obtain afLib3 by going to http://github.com/aferodeveloper/afLib3 .
    2. Follow your IDE instructions on how to install the library.
    3. afLib3 contains an Examples directory. In this directory, you’ll find an Arduino sketch as well as a directory containing an Afero Device Profile that can be published to your Modulo.
    4. The examples live in your Documents directory under Arduino/libraries/afLib3/examples/.
  2. If you haven’t already done so, register your Modulo to your account by scanning the QR code on your Modulo using the Afero mobile app.
  3. Load the afBlink profile in Afero Profile Editor:
    1. From the Profile Editor start page, select the Open button.
    2. In the Open dialog, navigate to Arduino/libraries/afLib3/examples/afBlink/profile/afBlink/profile.
    3. Open the afLib3 device profile that is appropriate for your Modulo: The directory named “afBlink” contains a profile for the Modulo-1; the directory named “afBlink2” is for the Modulo-2.
  4. From the Afero mobile app, make sure your Modulo is connected.
  5. Go to the Publish tab in the Afero Profile Editor and check that your device is online and selected.
  6. Click Publish. The profile will be uploaded over-the-air and in about a minute you should see the UI on your smartphone update to the new profile UI.
  7. Now that the Modulo is all set, let’s update the Arduino:
    1. Open the Arduino IDE and from the File menu, select Examples > afLib > afBlink.
    2. Make sure the Board and Port are set correctly in the Tools menu.
    3. With the afBlink sketch open, select Upload from the Sketch menu.
    4. Once the sketch has uploaded, open the Serial Monitor to see output from the example.
  8. Open the Afero mobile app and have some fun controlling your LED! The uppermost control, labeled “Blink”, lets you start and stop blinking; the lower control, labeled “LED”, displays the state of the module LED in real-time.

How It Works

This section gives you a bit more insight into what’s happening behind the scenes.

Defining the Attributes

The Device Profile in this lesson has two GPIO attribute definitions similar to those in Tutorial 2, but has an additional MCU attribute. This MCU attribute is a Boolean, made Writeable so that clicks in the mobile app UI can set the attribute value. We’ve named that attribute “Blink” because it will turn on/off the blinking of the LED.

Blink Attribute

Defining the UI Controls

Our mobile app UI will consist of two controls: a Menu control linked to the Blink attribute, and a read-only Text Box control, linked to the Module LED attribute.

Note that although this Device Profile has three attributes, we define only two UI controls. There is no requirement that every attribute you define has a corresponding UI control; UI controls are required only for attributes that must be viewable and/or modifiable via the mobile app UI.

The “Blink” Menu Control

The Blink control allows the end-user to change the value of the Blink attribute through the UI, starting and stopping the blinking of the LED.

The definition of this UI Control should be familiar if you worked through Tutorial 2: Creating a Device Profile. It’s a Menu control, meaning it has a few discrete states selectable through the UI. In this example, our Menu control has two Value Options: Start and Stop. And since Is Running is set to the Start state, the device icon will highlight when blinking has been started.

Blink Menu Control

The “LED” Text Control

This control allows the user of the Afero mobile app to observe the state of the LED, even when that user is far from the Modulo itself. Its definition is similar to the Menu control above, in that it has two Value Options with labels OFF and ON. The value option labels provide strings that will be displayed in the UI based upon the value of the attribute; that is, if the value of GPIO 0 (the LED attribute) is 0, which turns on the LED, the label shown in the UI will be “ON”, and if the value is 1, the label will be “OFF”.

LED Text Control

Defining a UI Control Group

Remember that every UI control must be a member of a UI Control Group even if it’s the only member. In this case, we’ll create a single Control Group, named “Blink Controls” and place both controls into it.

Since both controls belong to the same group, they will both be visible on the same screen in the Afero mobile app. If your application has many controls, you should organize them into groups that will make functional sense to the user when displayed together.

LED Text Control

As you saw when you ran through the lesson, this Profile results in a mobile app UI consisting of buttons that control the Modulo LED: tap Start and the LED starts blinking; tap Stop and the blinking stops. While the LED is blinking, the “LED" control in the Afero mobile app UI will also change its label to reflect the LED state.

What’s Happening on the MCU

Recall that when the UI gets a tap on the Start control, a message to the Afero Cloud tells the MCU program to start blinking the LED on the Modulo. Here’s the Arduino console output when we tap the Start button in the app UI to start the blinking, and after letting the LED blink a few times, tap Stop to halt:

28971682 attrEventCallback: AF_BLINK                    len:  1 value: true
28972148 attrEventCallback: AF_MODULO_LED               len:  2 value: 0
28973931 attrEventCallback: AF_MODULO_LED               len:  2 value: 1
28975922 attrEventCallback: AF_MODULO_LED               len:  2 value: 0
28977933 attrEventCallback: AF_MODULO_LED               len:  2 value: 1
28979924 attrEventCallback: AF_MODULO_LED               len:  2 value: 0
28981935 attrEventCallback: AF_MODULO_LED               len:  2 value: 1
28982675 attrEventCallback: AF_BLINK                    len:  1 value: false

In the first line of output we see the MCU program logging execution of attrEventCallback(). We know from the afLib3 for Arduino API that the MCU runs attrEventCallback() when ASR has executed af_lib_set_attribute*(). So we know that ASR must have called af_lib_set_attribute*() to tell MCU to set attribute 1 to value 1, and the MCU is handling that. How does that happen?

Take a look at the MCU’s attrEventCallback() definition (some code snipped out for clarity):

// Callback executed any time ASR has information for the MCU
void attrEventCallback(const af_lib_event_type_t eventType,
                       const af_lib_error_t error,
                       const uint16_t attributeId,
                       const uint16_t valueLen,
                       const uint8_t* value) {
  printAttribute("attrEventCallback", attributeId, valueLen, value);

  switch (eventType) {

// SNIP //

    case AF_LIB_EVENT_MCU_SET_REQUEST:
      // Request from ASR to MCU to set an MCU attribute, requires a call to af_lib_send_set_response()
      switch (attributeId) {

        case AF_BLINK:
          // This MCU attribute controls whether we should be blinking.
          blinking = (*value == 1);
          af_lib_send_set_response(AF_BLINK, true, valueLen, value);
          break;

        default:
          break;
      }
      break;

    default:
      break;
  }
}

What’s happening here is that the user’s tap in the mobile app UI goes up to the Afero servers as a message to change the value of attribute #1 (AF_BLINK). The message results in a call to attrEventCallback() that specifies a request has been made to set the value of MCU attribute AF_BLINK. In response, the MCU code above sets a local variable, blinking, to the new value of the AF_BLINK attribute. It then calls af_lib_send_set_response(), which it must to do confirm that the change was made.

Back to the console output, where we see several lines containing attrEventCallback calls with attribute ID AF_MODULO_LED. You probably recognize that the alternating 0’s and 1’s reflect that the LED is being blinked.

Based on the afLib3 for Arduino API, we know that attrEventCallback() is executed by the MCU whenever ASR sends an update message about an attribute change. So we deduce that ASR is sending updates every time it changes the value of GPIO 0 (the LED). The one piece of the puzzle we haven’t seen is what’s making ASR change that value. One more look at the code:

void loop() {
// SNIP //
  // Flash the LED whenever the 'blinking' value is true
  if (blinking) {
    if (millis() - lastBlink > BLINK_INTERVAL) {
      toggleModuloLED();
      lastBlink = millis();
    }
  }

  // Give the afLib state machine some time.
  af_lib_loop(af_lib);
}

void toggleModuloLED() {
  setModuloLED(!moduloLEDIsOn);
}

void setModuloLED(bool on) {
  if (moduloLEDIsOn != on) {
    int16_t attrVal = on ? LED_ON : LED_OFF; // Modulo LED is active low
// SNIP //
    af_lib_set_attribute_16(af_lib, AF_MODULO_LED, attrVal;
// SNIP //
    }

    moduloLEDIsOn = on;
  }
}

And there it is: while variable “blinking” is true, the MCU calls af_lib_set_attribute_16() every BLINK_INTERVAL (0.5 seconds) to set the GPIO attribute to the opposite state. In response to that setAttribute() call, ASR updates the attribute, and then sends an update message, which causes the MCU to execute attrEventCallback().

In a typical product containing an MCU, any LED indicator in the device would likely be connected directly to the MCU, whereas in this example we have used the LED on the Modulo. The difference is that in this lesson, the MCU changes the LED state by making a af_lib_set_attribute() call, which causes ASR to make the change and send an update; whereas in a product, the MCU would probably set the LED directly. We used this design not only for setup simplicity, but also to emphasize the way attributes are affected by making af_lib_set_attribute() calls.


The flow above illustrates the basic messaging pattern:

  1. A user action on the mobile app UI becomes a message to set the value of an attribute on a specific device.
  2. The app sends the “set attribute value” message to the Afero Cloud, which broadcasts the message.
  3. The ASR for the targeted device receives the message that the attribute value should be set.
  4. ASR does a couple of things:

    1. Stores the attribute’s current value and the new desired value.
    2. Tells the MCU that the attribute value should be set to the desired value.
  5. When the MCU gets the message, attrEventCallback() executes. In that call, you must write code to enable the MCU to make a state change that will correspond to the desired attribute value. This will typically involve some device action (e.g., starting LED blinking).
  6. After attrEventCallback() runs, afLib3 informs ASR, which then:

    1. Stores the new current value of the attribute, which should equal the desired value.
    2. Sends the attribute value back to the Afero Cloud.
  7. The Afero Cloud broadcasts the new attribute value.
  8. The mobile app receives the broadcast and updates the UI, so the end-user knows the request has been filled.

System Attributes

Up to this point, we’ve confined ourselves to discussing attributes that you, the developer, define using the Afero Profile Editor. It turns out that every Device Profile you define also includes several other attributes defined automatically by the system. These are called system attributes.

Attribute Types and ID Ranges

As you know, when you author an MCU sketch for Afero Secure Radio, you must include the device-description.h file generated by the Afero Profile Editor. The device-description.h file consists of #defines for all attributes, both user-defined and system-defined. Different types of attributes are organized into ranges based on ID. Let’s take a look in that file:

#define ATTRIBUTE_TYPE_SINT8                                         2
#define ATTRIBUTE_TYPE_SINT16                                        3
#define ATTRIBUTE_TYPE_SINT32                                        4
#define ATTRIBUTE_TYPE_SINT64                                        5
#define ATTRIBUTE_TYPE_BOOLEAN                                       1
#define ATTRIBUTE_TYPE_UTF8S                                        20
#define ATTRIBUTE_TYPE_BYTES                                        21
#define ATTRIBUTE_TYPE_FIXED_16_16                                   6

// SNIP //

// Attribute Blink
#define AF_BLINK                                                     1
#define AF_BLINK_SZ                                                  1
#define AF_BLINK_TYPE                           ATTRIBUTE_TYPE_BOOLEAN

// Attribute Modulo LED
#define AF_MODULO_LED                                             1024
#define AF_MODULO_LED_SZ                                             2
#define AF_MODULO_LED_TYPE                       ATTRIBUTE_TYPE_SINT16

// SNIP //

// Attribute Command
#define AF_SYSTEM_COMMAND                                        65012
#define AF_SYSTEM_COMMAND_SZ                                         4
#define AF_SYSTEM_COMMAND_TYPE                   ATTRIBUTE_TYPE_SINT32

// Attribute ASR State
#define AF_SYSTEM_ASR_STATE                                      65013
#define AF_SYSTEM_ASR_STATE_SZ                                       1
#define AF_SYSTEM_ASR_STATE_TYPE                  ATTRIBUTE_TYPE_SINT8

// SNIP //

In the sample above, you can see that the file begins with a set of defines that simply provide names for the data types that will be described in the remainder of the file.

Following that, you should see something that looks familiar: the define for the AF_BLINK attribute. We used the name AF_BLINK to refer to attribute #1 in the sketch we developed earlier in this exercise. At this point we’ll note two features of this attribute:

  • The Blink attribute you defined using the Profile Editor is an MCU attribute, and
  • The attribute ID = 1.

It turns out that any MCU attributes you define will have ID numbers from 1 to 1023. Of course, you should use the #define names for the attributes and not their ID numbers, but we raise this point here because the ID range defines the number of MCU attributes you can create, and to introduce the fact that different types of attributes have different ID ranges.

After the definition of AF_BLINK, you see another attribute you created: AF_MODULO_LED. This is one of the GPIO attributes you defined in your profile. GPIO attributes start at ID 1024, and each GPIO has a pair of attributes:

  • One for the base definition (what you defined in the profile), and
  • One for extended attribute definition data (you don’t have to worry about this)

Thus, GPIO 0 owns IDs 1024 and 1025, GPIO 1 owns 1026 and 1027, and so on.

Starting with ID 2001 and above, you’ll see attribute definitions that you did not create when you defined your device profile. Above 65000, the attributes have names that start with “AF_SYSTEM_”. These are the system attributes. We won’t describe all of the system attributes here, though most have names that explain their purpose clearly enough. In general, you can ignore these attributes, but because they are defined in the device-description.h, you can access them in your sketch. In fact, one of these attributes is critically important for you as the author of MCU code: the AF_SYSTEM_ASR_STATE attribute.

The AF_SYSTEM_ASR_STATE Attribute

In most cases, you can ignore the system attributes, but when your project includes an MCU, you’ll need to pay attention to the AF_SYSTEM_ASR_STATE (a.k.a. ASR_STATE) attribute. That’s because this attribute is used to provide your MCU code important status information about your ASR in real-time.

The ASR_STATE attribute can have one of a small range of values, or “states”:

  • 0 = Rebooted
  • 1 = Linked
  • 2 = Updating
  • 3 = Update Ready to Apply (Reboot Requested)
  • 4 = Initialized
  • 5 = Re-Linked

Let’s take a look at how your code can use three of these events:

What to Do When You Receive the “Rebooted” State Message

The event callback will receive AF_SYSTEM_ASR_STATE “Rebooted” whenever ASR has been rebooted. After a reboot, ASR is not ready to act on requests from afLib until it has signaled by sending the AF_SYSTEM_ASR_STATE “Initialized”.

A typical way to respond to the Rebooted state is to set a variable that indicates your code should hold off on afLib requests until it hears that ASR is Initialized. In skeleton form, this might look like:

bool initializationPending = true;   // If true, we're waiting on AF_MODULE_STATE_INITIALIZED
bool rebootPending = false;          // If true, a reboot is needed (e.g. if we received an OTA firmware update.)

// SNIP //

void attrEventCallback(const af_lib_event_type_t eventType,
             const af_lib_error_t error,
             const uint16_t attributeId,
             const uint16_t valueLen,
             const uint8_t* value) {

  switch (eventType) {

  case AF_LIB_EVENT_ASR_NOTIFICATION:
    switch (attributeId) {
    case AF_SYSTEM_ASR_STATE:
      switch (value[0]) {

      case AF_MODULE_STATE_REBOOTED:
        // ASR has been rebooted
        initializationPending = true;
        break;

      case AF_MODULE_STATE_INITIALIZED:
        // ASR signals that it's ready
        initializationPending = false;
        break;
      }
      break;

    }
    break;
  }
}

void loop() {
  af_lib_loop(af_lib);    // Give the afLib state machine some time.

  if (initializationPending) {
    // If we're awaiting initialization, don't bother checking/setting attributes
  } else {
    
    if (rebootPending) {      // Someone wants us to reboot
      int retVal = af_lib_set_attribute_32(af_lib, AF_SYSTEM_COMMAND, AF_MODULE_COMMAND_REBOOT);
      rebootPending = (retVal != AF_SUCCESS);
      if (!rebootPending) {
        // Reboot command sent; now awaiting AF_MODULE_STATE_INITIALIZED
        initializationPending = true;
      }
    }

  // Your application code here //
  
  }
}

Waiting Until Initialized

As noted above, ASR will send the AF_SYSTEM_ASR_STATE “Initialized” message when it is ready to handle requests from afLib. Requests made before this message may lead to unexpected results. So, as seen in the example above, it is typical for MCU code to avoid making any afLib calls before it receives the Initialized message. Note though, that even during the pre-initialized phase, the MCU code must regularly call af_lib_loop().

In the example above, you can see that we keep track of the pre-initialized state in the variable initializationPending. The main loop() does nothing except call af_lib_loop() if initializationPending is true. Once the signal AF_MODULE_STATE_INITIALIZED is received, initializationPending is set to false, and the application code will run. Any time ASR is rebooted, though, initializationPending is set once again set to true, and the code resumes waiting until it gets the all-clear.

How to Handle a Reboot Request

The status “Reboot Requested” means that ASR has received an over-the-air (OTA) software update, and requires rebooting for that update to be installed. If your project does not include an MCU, then the reboot will execute automatically, as soon as possible after the update has been received. However, if your project includes an MCU, then responsibility for triggering the reboot falls on the MCU code. This allows your MCU to restrict the ASR reboot to times that are safe for your application.

So, you need to watch for reboot requests, and you need to respond by signaling ASR to reboot – how exactly do you go about this? Well, of course, this is all about communication via attribute values!

  1. When ASR receives an OTA, it signals receipt by changing the value of the ASR_STATE attribute.
  2. As we saw earlier in the example, whenever ASR sends an update message about an attribute, the MCU executes the attrEventCallback().
  3. So, we’ll need to use our attrEventCallback() code to watch for a change to AF_SYSTEM_ASR_STATE; and specifically, for that attribute to change to value 3.
  4. Once we see that condition, we’ll want to write something into our attrEventCallback() to trigger an ASR reboot. How can we do that? Set an attribute! It turns out that there’s another system attribute, AF_SYSTEM_COMMAND, that provides a way to trigger that reboot.

Here’s a detailed example:


bool initializationPending = true;  // If true, we're waiting on AF_MODULE_STATE_INITIALIZED
bool rebootPending = false;          // If true, a reboot is needed (e.g. if we received an OTA firmware update.)

// SNIP //

void attrEventCallback(const af_lib_event_type_t eventType,
                       const af_lib_error_t error,
                       const uint16_t attributeId,
                       const uint16_t valueLen,
                       const uint8_t* value) {

  switch (attributeId) {
// SNIP //

    case AF_SYSTEM_ASR_STATE:
      switch (value[0]) {

    case AF_MODULE_STATE_REBOOTED:
      // ASR has been rebooted
      initializationPending = true;
      break;

    case AF_MODULE_STATE_LINKED:
      break;

    case AF_MODULE_STATE_UPDATING:
      break;

    case AF_MODULE_STATE_UPDATE_READY:
      // ASR signals that an update has been received and a reboot is required to use it
      rebootPending = true;
      break;

    case AF_MODULE_STATE_INITIALIZED:
      // ASR signals that it's ready
      initializationPending = false;
      break;

    default:
      break;
    }
    break;

  default:
    break;
  }
}

void loop() {
  af_lib_loop(af_lib);    // Give the afLib state machine some time.

  if (initializationPending) {
    // If we're awaiting initialization, don't bother checking/setting attributes
  } else {
    
    if (rebootPending) {  // Someone wants us to reboot
      int retVal = af_lib_set_attribute_32(af_lib, AF_SYSTEM_COMMAND, AF_MODULE_COMMAND_REBOOT);
      rebootPending = (retVal != AF_SUCCESS);

      // Check for success; if not successful, we'll re-try next time around loop()
      if (!rebootPending) {
        // Reboot command sent; now awaiting AF_MODULE_STATE_INITIALIZED
        initializationPending = true;
      }

    }

  // Your application code here //
  
  }
}

In the above definition of attrEventCallback(), we check the supplied attribute ID, and if that ID is AF_SYSTEM_ASR_STATE, we check the attribute value. For values 0-2, we simply print helpful information, but if the value is 3 (AF_MODULE_STATE_UPDATE_READY), then we know we’ve been asked to reboot ASR. We trigger that reboot by setting a global, rebootPending. In our main loop() function, any time rebootPending is true, we’ll call af_lib_set_attribute_32() for the AF_SYSTEM_COMMAND attribute, with value 1 (which is the value that signals a reboot). Notice that our code resets rebootPending to false if it succeeds; if the set_attribute() call fails, we’ll try again next time around the loop().