Callbacks and Events

Calls to af_lib_get_attribute() and af_lib_set_attribute*() are enqueued for handling by afLib; and once handled, result in execution of a callback function named attrEventCallback(). You must define attrEventCallback() in your MCU code, and pass it as an argument to af_lib_create(). This callback contains your application-specific code to respond to attribute changes.

Your attrEventCallback() will be called with an event type and an error code. You’ll use these data to select an appropriate response. This page describes how your code can distinguish the events and respond accordingly.

attrEventCallback()

Description

Application callback that is executed any time ASR has data to communicate with the MCU.

Syntax

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)

Parameters

eventType The event type for this request.
error An afLib result code
attribute_id The ID of the attribute involved.
value_len The size in bytes of the attribute value.
value The new value for the attribute.

Event Types

The following event types are defined in af_lib.h:

Event Type Interpretation
AF_LIB_EVENT_UNKNOWN Not normally seen; intended for debugging.
AF_LIB_EVENT_ASR_SET_RESPONSE Response to af_lib_set_attribute() for an ASR attribute.
AF_LIB_EVENT_MCU_SET_REQ_SENT Request from af_lib_set_attribute() for an MCU attribute has been sent to ASR.
AF_LIB_EVENT_MCU_SET_REQ_REJECTION Request from af_lib_set_attribute() for an MCU attribute was rejected by ASR.
AF_LIB_EVENT_ASR_GET_RESPONSE Response to af_lib_get_attribute().
AF_LIB_EVENT_MCU_DEFAULT_NOTIFICATION Unsolicited default notification for an MCU attribute.
AF_LIB_EVENT_ASR_NOTIFICATION Unsolicited notification of non-MCU attribute change.
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().
AF_LIB_EVENT_COMMUNICATION_BREAKDOWN The communication between the MCU and the ASR seems to have stopped, take the appropriate action (i.e. rebooting the ASR).

Returns

None.

Example 1: Translating End-User Actions and afLib3 Function Calls into Events

attrEventCallback() will be called by afLib3 whenever ASR has information for the MCU. Your code will depend on the eventType parameter to determine why the callback has been fired, and therefore how you want to respond.

Let’s consider a few specific cases, just to make this clear:

  • If the button is pressed on a Modulo (changing the value of GPIO 3 directly), the attrEventCallback() will be executed with eventType AF_LIB_EVENT_ASR_NOTIFICATION.
  • If your MCU code calls af_lib_get_attribute(), the value will be returned via the callback with eventType AF_LIB_EVENT_ASR_GET_RESPONSE.
  • Consider that an end-user taps a control in your mobile application to change an attribute value. If the attribute changed is a GPIO, your code will be informed of the change by attrEventCallback() with eventType AF_LIB_EVENT_ASR_NOTIFICATION.
  • However…

  • If the attribute changed by the mobile app is an MCU attribute, your code will receive AF_LIB_EVENT_MCU_SET_REQUEST.
  • If your MCU application requires that any reboot of ASR will trigger a reset of MCU attribute values to defaults set in the device profile, then your code will need to check for event type AF_LIB_EVENT_MCU_DEFAULT_NOTIFICATION, and when that is received for an attribute, set the value.

Example 2: Structure of a Prototypical attrEventCallback()

Typically, your callback code will branch on the eventType, and within each case, will branch again depending on the attributeID. Given that, below is an example “shell” of an attrEventCallback() that has a case for all eventTypes, and is also set up with cases for all system states, in the case of an AF_LIB_EVENT_ASR_NOTIFICATION event for attribute AF_SYSTEM_ASR_STATE. You might find this useful to copy/paste into your code:

// 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) {

  switch (eventType) {
    case AF_LIB_EVENT_UNKNOWN:
      // Should never occur (indicates an eventType = 0); primarily for debugging
      break;

    case AF_LIB_EVENT_ASR_SET_RESPONSE:
      // Response to af_lib_set_attribute() for an ASR attribute
      break;

    case AF_LIB_EVENT_MCU_SET_REQ_SENT:
      // Request from af_lib_set_attribute() for an MCU attribute has been sent to ASR
      break;

    case AF_LIB_EVENT_MCU_SET_REQ_REJECTION:
      // Request from af_lib_set_attribute() for an MCU attribute was rejected by ASR
      break;

    case AF_LIB_EVENT_ASR_GET_RESPONSE:
      // Response to af_lib_get_attribute()
      break;

    case AF_LIB_EVENT_MCU_DEFAULT_NOTIFICATION:
      // Unsolicited default notification for an MCU attribute
      break;

    case AF_LIB_EVENT_ASR_NOTIFICATION:
      // Unsolicited notification of non-MCU attribute change
      switch (attributeId) {

        case AF_SYSTEM_ASR_STATE:
          switch (value[0]) {
            case AF_MODULE_STATE_REBOOTED:
              // ASR has rebooted. MCU Attributes are erased and must be reset
              break;

            case AF_MODULE_STATE_LINKED:
              // Linked - ASR has rebooted (MCU Attributes erased) and then linked
              break;

            case AF_MODULE_STATE_UPDATING:
              // Updating - There's an OTA in progress
              break;

            case AF_MODULE_STATE_UPDATE_READY:
              // Update ready - OTA is complete; reboot needed to begin using the new code
              break;

            case AF_MODULE_STATE_INITIALIZED:
              // Initialized - ASR now ready to service requests
              break;

            case AF_MODULE_STATE_RELINKED:
              //Relinked - ASR has relinked with cloud
              break;

            default:
              Serial.print("Unexpected state - "); Serial.println(value[0]);
              break;
          }
          break;

        default:
          break;
      }
      break;

    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) {

        default:
          break;
      }
      break;

    default:
      break;
  }
}

Example 3: afBlink

This afBlink example uses a Teensy to control the LED onboard a Modulo. The LED can be made to start/stop blinking by a tap on a UI control in the mobile app, or by a press on the Modulo button. The key lesson from this example comes from understanding how the code in attrEventCallback() distinguishes between various events, and handles them accordingly. Here are four cases:

  • In the case of a tap on the “Start” button in the mobile app UI…
    • The mobile app translates the button tap into an af_lib_set_bool() for attribute AF_BLINK.
    • As a result, attrEventCallback() will be called with an eventType AF_LIB_EVENT_MCU_SET_REQUEST and attributeId AF_BLINK.
    • So, when we get eventType AF_LIB_EVENT_MCU_SET_REQUEST belonging to attribute AF_BLINK, our code sets the value of our variable blinking, and then calls af_lib_send_set_response to confirm that MCU was able to update its state.
    • Within our loop() function, if blinking is true, we call toggleLED() every two seconds, which blinks the LED!
  • In the case of a button press on the Modulo…
    • Modulo changes the local attribute value and sends an update, which causes…
    • attrEventCallback() to be called with an eventType AF_LIB_EVENT_ASR_NOTIFICATION and attributeId AF_MODULO_BUTTON.
    • When we get eventType AF_LIB_EVENT_ASR_NOTIFICATION, belonging to attribute AF_MODULO_BUTTON, our code toggles the value of our global curButtonValue.
    • Within our loop() function, if our curButtonValue is different from prevButtonValue (that is, if the button value has changed), then we call af_lib_set_bool() on AF_BLINK, setting that attribute, which controls blinking.
    • That af_lib_set_bool() call triggers the same sequence of events as example #1 above, which results in the LED blinking.
  • Any time the Modulo reboots…

    The MCU is required to call set_attribute for each MCU attribute whenever ASR reboots, as described in MCU Attributes and Update All. So:

    • Upon reboot, attrEventCallback() will be called with an eventType AF_LIB_EVENT_ASR_NOTIFICATION and attributeId AF_SYSTEM_ASR_STATE.
    • Presented that combination of eventType and attributeId, our code checks the value delivered via the callback; in this case, that value is AF_MODULE_STATE_REBOOTED.
    • Our code responds by setting initializationPending and attr1SyncPending to true. Both values are checked in loop():
      • Any time initializationPending is true, we avoid making afLib calls until we receive the AF_SYSTEM_ASR_STATE AF_MODULE_STATE_INITIALIZED signal that tells us that ASR is ready to handle requests.
      • If attr1SyncPending is true, we call af_lib_set_attribute_bool for attribute 1 (AF_BLINK), setting it to the current value of the blinking variable.
  • If the Modulo receives an OTA update…
    • When the OTA is complete, attrEventCallback() will be called with an eventType AF_LIB_EVENT_ASR_NOTIFICATION and attributeId AF_SYSTEM_ASR_STATE.
    • We check the value delivered via the callback; in this case it's AF_MODULE_UPDATE_READY.
    • Our code responds by setting rebootPending to true. This value is checked in loop(), and if true, we trigger a reboot.
#include <SPI.h>

#include "af_lib.h"
#include "arduino_spi.h"
#include "arduino_uart.h"
#include "af_module_commands.h"
#include "af_module_states.h"
#include "arduino_transport.h"

#include "profile/afBlink/device-description.h"

#define ARDUINO_USE_SPI                   1

// Check for teensy and abort if not
#if defined(TEENSYDUINO)
#else
#error "Sorry, this example written for teensy. E.G. pins are defined accordingly."
#endif

#define INT_PIN                   14    // Modulo uses this to initiate communication
#define CS_PIN                    10    // Standard SPI chip select (aka SS)
#define RESET                     21    // This is used to reboot the Modulo when the Teensy boots
#define RX_PIN                    7
#define TX_PIN                    8

// Modulo LED is active low
#define LED_OFF                   1
#define LED_ON                    0

#define BLINK_INTERVAL            2000  // 2 seconds


af_lib_t* af_lib = NULL;
bool initializationPending = false; // 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.
bool attr1SyncPending = false;      // If true (e.g. after reboot), we need to set MCU attr 1

volatile long lastBlink = 0;                   // Time of last blink
volatile bool blinking = false;                // Track whether LED is blinking; represented by attribute AF_BLINK
volatile bool moduloLEDIsOn = false;           // Track whether the Modulo LED is on
uint16_t prevButtonValue = 1;                  // Track the button value...
uint16_t curButtonValue = prevButtonValue;     // ...so we know when it has changed


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

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

    int timeout = 0;
    while (af_lib_set_attribute_16(af_lib, AF_MODULO_LED, attrVal) != AF_SUCCESS) {
      delay(10);
      af_lib_loop(af_lib);
      timeout++;
      if (timeout > 500) {
        // If we haven't been successful after 5 sec (500 tries, each after 10 msec delay)
        // we assume we're in some desperate state, and reboot
        pinMode(RESET, OUTPUT);
        digitalWrite(RESET, 0);
        delay(250);
        digitalWrite(RESET, 1);
        return;
      }
    }

    moduloLEDIsOn = on;
  }
}

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_SET_RESPONSE:
      // Response to af_lib_set_attribute() for an ASR attribute
      break;

    case AF_LIB_EVENT_MCU_SET_REQ_SENT:
      // Request from af_lib_set_attribute() for an MCU attribute has been sent to ASR
      break;

    case AF_LIB_EVENT_MCU_SET_REQ_REJECTION:
      // Request from af_lib_set_attribute() for an MCU attribute was rejected by ASR
      break;

    case AF_LIB_EVENT_ASR_GET_RESPONSE:
      // Response to af_lib_get_attribute()
      break;

    case AF_LIB_EVENT_MCU_DEFAULT_NOTIFICATION:
      // Unsolicited default notification for an MCU attribute
      break;

    case AF_LIB_EVENT_ASR_NOTIFICATION:
      // Unsolicited notification of non-MCU attribute change
      switch (attributeId) {
        case AF_MODULO_LED:
          // Update the state of the LED based on the actual attribute value.
          moduloLEDIsOn = (*value == 0);
          break;

        case AF_MODULO_BUTTON:
          // curButtonValue is checked in loop(). If changed, will toggle the blinking state.
          curButtonValue = *(uint16_t*) value;
          break;

        case AF_SYSTEM_ASR_STATE:
          switch (value[0]) {
            case AF_MODULE_STATE_REBOOTED:
              initializationPending = true;   // Just rebooted, so we're not yet initialized
              attr1SyncPending = true;    // Set Attribute 1 (happens in loop())
              break;

            case AF_MODULE_STATE_LINKED:
              break;

            case AF_MODULE_STATE_UPDATING:
              break;

            case AF_MODULE_STATE_UPDATE_READY:
              // OTA has been received - reboot needed to use new code
              rebootPending = true;
              break;

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

            case AF_MODULE_STATE_RELINKED:
              break;
          }
          break;
      }
      break;

    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;
      }
      break;
  }
}

void setup() {

  Serial.begin(9600);

  while (!Serial) {;} // wait for serial port

  Serial.println("Starting sketch: afBlink");

  Serial.println("Teensy resetting Modulo");
  pinMode(RESET, OUTPUT);
  digitalWrite(RESET, 0);
  delay(250);
  digitalWrite(RESET, 1);

  // Start the sketch awaiting initialization
  initializationPending = true;

  Serial.print("Configuring communications...sketch will use SPI");
  Serial.println("SPI");
  af_transport_t* arduinoSPI = arduino_transport_create_spi(CS_PIN);
  af_lib = af_lib_create_with_unified_callback(attrEventCallback, arduinoSPI);
  arduino_spi_setup_interrupts(af_lib, digitalPinToInterrupt(INT_PIN));
}

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

  if (initializationPending) {
    // If we're awaiting initialization, don't bother checking/setting attributes
  } else {

    // If we were asked to reboot (e.g. after an OTA firmware update), make the call here in loop().
    // In order to make this fault-tolerant, we'll continue to retry if the command fails.
    if (rebootPending) {
      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 successfully; now awaiting AF_MODULE_STATE_INITIALIZED
        initializationPending = true;
      }
    }

    // After reboot, need to inform service of value of all MCU attributes (in this case just attr 1)
    if (attr1SyncPending) {
      if (af_lib_set_attribute_bool(af_lib, AF_BLINK, blinking) == AF_SUCCESS) {
        attr1SyncPending = false;
      }
    }

    // Modulo button toggles 'blinking'
    if (prevButtonValue != curButtonValue) {
      if (af_lib_set_attribute_bool(af_lib, AF_BLINK, !blinking) == AF_SUCCESS) {
        blinking = !blinking;
        prevButtonValue = curButtonValue;
      }
    }

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