Robust af_lib_set_attribute*() Calls

All forms of af_lib_set_attribute() provide a return value to indicate whether the set request has been successfully enqueued. Checking this return value and re-trying can make your use of af_lib_set_attribute*() more robust. Here’s a code snippet from the afBlink example sketch that demonstrates some variations of this:

...SNIP...
bool rebootPending = false;		// True if reboot needed, e.g. if we received an OTA firmware
...SNIP...

// 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:
      break;

...SNIP...

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

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

        case AF_SYSTEM_ASR_STATE:
          switch (value[0]) {
            case AF_MODULE_STATE_REBOOTED:
              break;

            case AF_MODULE_STATE_LINKED:
              break;

            case AF_MODULE_STATE_UPDATING:
              break;

EXAMPLE 2:
            case AF_MODULE_STATE_UPDATE_READY:
              // rebootPending is checked in loop(). If true, will send reboot command.
              rebootPending = true;
              break;

            case AF_MODULE_STATE_INITIALIZED:
              break;

            default:
              break;
          }
          break;

        default:
          break;
      }
      break;

...SNIP...
  }
}

...SNIP...

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

// EXAMPLE 3:
    int timeout = 0;
    while (af_lib_set_attribute_16(af_lib, AF_MODULO_LED, attrVal, AF_LIB_SET_REASON_LOCAL_CHANGE) != 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 loop() {
  // 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, AF_LIB_SET_REASON_LOCAL_CHANGE);
    rebootPending = (retVal != AF_SUCCESS);
  }

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

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

Notes:

  • In all three examples, we check the return value from af_lib_set_attribute(), and retry if the call was not successful. Doing this is essential to robust afLib usage.

  • While retrying unsuccessful afLib calls, it is important to call af_lib_loop() to ensure that afLib gets time to handle requests. If, for example, we called set_attribute() and the call failed due to a full request queue, retrying the call would be pointless unless afLib got time to service the queue.

  • It’s important to avoid calling af_lib_loop() within attrEventCallback(); doing so can have unexpected results. But we know that to make afLib calls robust we should confirm-and-retry, and we know that we must call af_lib_loop() while we retry. So if the callback tells us we need to call set_attribute, how do we do that robustly?

    Examples #1 and #2 demonstrate a useful pattern: Code in the callback is restricted to setting a flag to indicate a af_lib_set_attribute() call is required. Then in the main loop(), the flag is checked, set_attribute() is called if indicated, and retried as needed until success. This pattern is robust, easy to read, and can reduce redundant code.

  • Example #3 shows a variation in which retrying is limited by a timeout: as above, the code retries af_lib_set_attribute(), waiting for AF_SUCCESS. But here a timeout prevents an indefinite cycle of re-trying in the face of some serious condition that is blocking us. If the timeout is exceeded, we assume that communication with afLib is fatally obstructed, so we trigger a reboot by directly manipulating the reset pin.