Thingsquare callback

Thingsquare callbacks are perhaps the chief way for the application to be notified of events related to the backend connection, Thingsquare modules such as thsq_gpio, and similar things.

Purpose

Applications and drivers may implement and register callbacks that are invoked by the system on certain events and conditions. The purpose of the callback is for the application to efficiently be notified without having to poll the system.

The callback may check for the events that are important for just this specific application.

Creating a callback

Creating a callback is simple - we need to do three things:

  1. implement the callback itself
  2. define a static variable that contains the data structure used for registering the callback
  3. perform the actual regististration function call

The callback

The bare callback stub looks like below, and is defined in thsq.h. Call it anything you like, preferably something unique to make it simpler to debug.

static void
application_thsq_callback(enum thsq_reason r, const char *str, int len)
{
}

The callback data structure

Internally, the callbacks are handled as a linked list and for simplicity and efficiency reasons, the application that registers the callback keeps the list entry itself. Therefore, the application needs to define this like so,

static struct thsq_callback app_thsq_cb;

The registration procedure

Finally, to make the underlying system aware of the callback, we register it the following way,

thsq_add_callback(&app_thsq_cb, application_thsq_callback);

Putting it all together

This is what a example stub application could look like,

#include "thsq.h"
/*---------------------------------------------------------------------------*/
static void
application_thsq_callback(enum thsq_reason r, const char *str, int len)
{
  /* empty callback, we will get to populating this further down */
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
  /* register the callback */
  static struct thsq_callback app_thsq_cb;
  thsq_add_callback(&app_thsq_cb, application_thsq_callback);
}
/*---------------------------------------------------------------------------*/

Callback details

Let us have a more in-depth look at the callback itself:

/*---------------------------------------------------------------------------*/
static void
application_thsq_callback(enum thsq_reason r, const char *str, int len)
{
  /* empty callback, we will get to populating this further down */
}
/*---------------------------------------------------------------------------*/

Argument: enum thsq_reason r

The reason argument refers to why the callback was invoked. The list of reasons is defined in thsq.h. In the callback, this is the first thing that should be checked. The list of reasons are detailed below, and includes for example THSQ_COMMAND when a command from the backend was received, and THSQ_KEYVAL when a variable was updated.

Arguments: const char *str, int len

As detailed in other documentation sections, much of the data exchanges with a backend server is through passing strings. If the callback is invoked for a reason where a string is used, the str will point to this string, and len will indicate the length of the string.

Note that str can be NULL even if you expect it to be otherwise. Likewise, len might be 0. Therefore, always follow best practice and sanity check the str and len-variables before dereferencing them. The maximum length len can assume is THSQ_MAX_VALLEN as defined in thsq.h. THSQ_MAX_VALLEN is currently 128 bytes but do check against the definition and not a hard-coded value 128.

Also note that the buffer pointed to through str may contain non-printable chars, or even no printable chars at all. It depends on the event, and what data is being fed.

Callback best practices

This section details things to keep in mind, and others to avoid, when using Thingsquare callbacks in your applications and drivers.

Dos and don'ts

Register each callback only once.

Sanity check the str and len variables before using them (eg check str before de-referencing it).

Do not manipulate the struct thsq_callback you defined. The contents are for internal use only.

Do not manipulate the buffer str points at. Doing so will cause undefined behavior since other callbacks may depend on this buffer being pristine. If you need to manipulate it, copy it to a local buffer first and manipulate that.

Do not spend too much time in a callback. If an event is in effect a trigger for something computation heavy, or otherwise needs to use the CPU much, do instead post an event or poll a process. For example,

/*---------------------------------------------------------------------------*/
static void
application_thsq_callback(enum thsq_reason r, const char *str, int len)
{
  /* the "mycmd" command triggers something that takes a lot of CPU */
  if(r == THSQ_COMMAND) {
    if(str != NULL && len >= 5) {
      if(strncmp(str, "mycmd", 5) == 0) {
        process_post(&my_cpu_intensive_process, long_event_trigger, NULL);
      }
    }
  }
}
/*---------------------------------------------------------------------------*/

In a similar vein, do not cause too much network activity through the callback. If an event triggers your device along with others (eg a broadcast message or a GPIO event that also affects others), you risk overloading the network unless care is taken. If this is the case, poll a process that waits a random time before starting this activity.

How many callbacks can the system handle?

There is no specific limit on how many callbacks the system can handle, but stay sane. The Thingsquare modules themselves use callbacks, so in any given system there are somewhere around 5-10 callbacks in use. Do not abuse this and create tens of callbacks for the sake of avoiding CPU starvation. All callbacks will be invoked in turn, for all events.

Reasons

The reason argument to the callback is the main way of knowing why the callback was invoked. A callback may check against more than one reason and thus handle several cases.

This is the list of reasons. It is defined in thsq.h. We will below go through them all. Note that some are for internal use only, but included for completeness and curiositys sake.

enum thsq_reason {
  THSQ_ERROR,
  THSQ_PONG_RECEIVED,
  THSQ_KEYVAL,
  THSQ_AUTH,
  THSQ_SERVER_CONNECTED,
  THSQ_COMMAND,
  THSQ_ZONEMSG,
  THSQ_PERIOD,
  THSQ_GPIO_INPUT,
  THSQ_LIGHTING_CHANGED,
  THSQ_ACKED,
  THSQ_GATEWAY_GONE
};

Reason THSQ_KEYVAL

THSQ_KEYVAL is used when the device key-value store is updated from remotely.

The most common case is when a server backend, through the use of an app or the REST API, sets a d-variable on the device. For more information, refer to the documentation section for data exchange. It may also be the case that a d-variable is removed.

Here, the str-variable will point to a string with the name of the variable (key). To retrieve the value of the key, use thsq_get(str). Before doing so though, do check whether it exists with thsq_exists(str)

Server backend updates variable

The following is an example of when the server backend provides the device with new data for the key. This might be a new dim value for a lamp, or configuration settings, or anything you are using in your application.

/*---------------------------------------------------------------------------*/
static void
callback_thsq(enum thsq_reason r, const char *str, int len)
{
  if(r == THSQ_KEYVAL && str != NULL) {
    if(strncmp(str, "conf", 4) == 0) {
      if(thsq_exists("conf")) {
        my_driver_update_setting(thsq_get("conf"));
      }
    }
  }
}
/*---------------------------------------------------------------------------*/

Server backend removes variable

Let's expand the above example with the server removing variables. In this hypothetical example, the device should then go back to default settings, but what should happen really depends on the application at hand and is part of defining how your product should behave.

/*---------------------------------------------------------------------------*/
static void
callback_thsq(enum thsq_reason r, const char *str, int len)
{
  if(r == THSQ_KEYVAL && str != NULL) {
    if(strncmp(str, "conf", 4) == 0) {
      if(thsq_exists("conf")) {
        /* the "conf"-variable was updated */
        my_driver_update_setting(thsq_get("conf"));
      } else {
        /* the "conf"-variable was removed on the server */
        my_driver_update_setting(my_driver_get_default());
      }
    }
  }
}
/*---------------------------------------------------------------------------*/

Removing a variable on the server will also remove it on the device, unless the device sets it locally after the removal.

Reason THSQ_COMMAND

Another way to interact with the device from remote is through the use of commands. In such case, when a command is received, THSQ_COMMAND is used to indicate this. The str-variable will contain the full command string.

This is an example on how to check for, and use, a command. In this example, the command "mycommand" is sent from the server (app or REST API). The command string may also include an additional payload that comes immediately after, here eg "mycommandABC".

/*---------------------------------------------------------------------------*/
static void
callback_thsq(enum thsq_reason r, const char *str, int len)
{
  if(r == THSQ_KEYVAL && str != NULL) {
    /* NOTE: removed for brevity */
  } else if(r == THSQ_COMMAND && str != NULL) {
    if(strncmp(str, "mycommand", strlen("mycommand")) == 0) {
      /* we do something based on this command */
    }
  }
}
/*---------------------------------------------------------------------------*/

Reason THSQ_PERIOD

Every device periodically uploads sensor data and network/health statistics. The THSQ_PERIOD event is used when it is time to update sensor data and the device have the opportunity to add custom sensor data. This event is designed to make it easier for applications. It uses several mechanisms to decrease risk of radio collisions and conserve power.

Note that the periodicity with which this event is called is random within the interval [period / 2, period]. This lowers the risk of collisions.

/*---------------------------------------------------------------------------*/
static void
callback_thsq(enum thsq_reason r, const char *str, int len)
{
  if(r == THSQ_PERIOD) {
    process_poll(&my_sensor_process);
  }
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(my_sensor_process, ev, data)
{
  PROCESS_BEGIN();
  while(1) {
    /* wait until we are polled by the callback */
    PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_POLL);

    /* perform the sensor data sampling */
    thsq_sset("hum", humidity_sensor_sample());
    thsq_push();
  }
  PROCESS_END();
}
/*---------------------------------------------------------------------------*/

Reason THSQ_GPIO_INPUT

THSQ_GPIO_INPUT is used when the thsq_gpio-module detects events on the digital input pin, if available and enbled. The str-argument is always NULL.

Reason THSQ_LIGHTING_CHANGED

THSQ_LIGHTING_CHANGED is used when the thsq_lighting-module changes the light setting.

Reason THSQ_ZONEMSG

THSQ_ZONEMSG is used by the thsq_zones-module, which is under development and not available for public use yet. It triggers when the zone the device is part in receives a message for said zone. In such case, the str may point to a string indicating what triggered the zone event, or NULL if none is available. If non-null, len is the strlen() of the reason.

Reason THSQ_ACKED

THSQ_ACKED is used when in the exchange of data with the server backend. Variables may be marked as important, meaning that the server will acknowledge receipt. It does not guarantee receipt, but provides a mean for the device to know that the data has been received without performing a full connection.

Reason THSQ_ERROR

THSQ_ERROR is used when there has been an error in the connection to the backend. This is common due to the unreliable nature of wireless communication and handled automatically by the system. Internal use only.

Reason THSQ_GATEWAY_GONE

THSQ_GATEWAY_GONE is used when the a device haven't heard from a gateway in a long time. Internal use only.

Reason THSQ_PONG_RECEIVED

THSQ_PONG_RECEIVED is used as part of the communication with the backend server. Internal use only.

Reason THSQ_AUTH

THSQ_AUTH is used as part of the communication with the backend server. Internal use only.

Reason THSQ_SERVER_CONNECTED

THSQ_SERVER_CONNECTED is used as part of the communication with the backend server. Internal use only.