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:
- implement the callback itself
- define a static variable that contains the data structure used for registering the callback
- 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 >= 4) {
if(strncmp(str, "Cmyc", 4) == 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_ACKED,
};
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, "Cmyc", 4) == 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_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_STATS
THSQ_STATS
is used to indicate that it is time to update the network and health statistics. Internal use only. Customer applications are encouraged to use THSQ_PERIOD
.
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_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.