Skip to content

Using the firmware SDK

The firmware SDK allows developing custom user applications running directly on the hardware and communicating both with the REST API and the hardware itself.

User applications are developed in the C programming language and compiled together with the Thingsquare firmware to form a firmware image that is either downloaded directly onto the flash memory of the SoC or over the air via the firmware update procedure.

User applications can range from extremely simple that do only a minimal hardware setup to much more complex that includes application logic.

User applications run on top of the Thingsquare firmware and operating system (OS), and uses custom drivers and the Thingsquare SDK API to interact with the hardware to define what a product is and feels like to the user.

The structure of a user application

At the heart of a user application is a C function called app() that is called by the Thingsquare OS at system boot. The app() function is responsible for starting any user applications that should run on the device and may also set parameters for the underlying operating system.

Let's begin by looking at the smallest possible user application:

#include "thsq.h"
/*---------------------------------------------------------------------------*/
void
app(void)
{
}
/*---------------------------------------------------------------------------*/

In this user application, the app() function does nothing at all. It has to be there, because it is always called by the OS, but in this case it does not do anything. This means that the system will boot up and work as normal, but will not provide any user-specific functionality.

We can add a little bit of functionality by adding a printout on the serial port:

#include "thsq.h"
/*---------------------------------------------------------------------------*/
void
app(void)
{
  printf("Hello, world\n");
}
/*---------------------------------------------------------------------------*/

If this user application is running on a hardware platform with a UART connection, this application will print out Hello, world when the device is turned on or reset.

Adding a callback

The OS communicates with the user application using callback functions. The callback functions are called when an event occurs. Events include a new variable being set from the REST API, a periodic timer triggering, a digital input pin triggering, or a local command being received from another device in the same zone. The user application may register any number of callbacks.

A callback is a C function that takes three arguments: the reason for the callback, a pointer to data that is passed along with the callback, and the length of the data. The value of the data depends on the reason for the callback.

A callback is registered with the thsq_add_callback() function. The following user application adds a callback and prints out a message when a variable is set with the REST API:

#include "thsq.h"
/*---------------------------------------------------------------------------*/
static void
callback(enum thsq_reason r, const char *str, int len)
{
  if(r == THSQ_KEYVAL) {
    printf("Received key %.*s\n", len, str);
  }
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
  static struct thsq_callback cb;
  thsq_add_callback(&cb, callback);
}
/*---------------------------------------------------------------------------*/

This user application will print out the name of the variable that is received, if the device is configured with a UART with output capabilities.

The callback mechanism is a powerful way to interact with the REST API and the in-mesh mechanisms provided by the Thingsquare OS.

Variables

The user application may set variables that can be accessed by the REST API.

There are two types of variables in the Thingsquare system: d variables and s variables. d variables can be accessed by both the device and via the API, whereas s variables are accessed only via the API. The device may set both d and s variables. Both d and s variables can be set by the REST API, but only d variables will be sent to the device.

Typically, s variables are used for communicating data from the device to the REST API, such as sensor values, and d variables are used for communicating data from the REST API to the device, such as configuration parameters.

Setting variables

To set an s variable from a device, we use the thsq_sset() function, followed by a call to thsq_push():

#include "thsq.h"
/*---------------------------------------------------------------------------*/
static void
callback(enum thsq_reason r, const char *str, int len)
{
  if(r == THSQ_KEYVAL) {
    printf("Received key %.*s\n", len, str);
    thsq_sset("myvariable", 42);
    thsq_push();
  }
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
  static struct thsq_callback cb;
  thsq_add_callback(&cb, callback);
}
/*---------------------------------------------------------------------------*/

See thsq.h for the full set of variable functions.

This will set the s variable with the name myvariable to the integer value 42 whenever a new d variable is received by the device. The thsq_push() function is needed to push the new variable to the backend. Multiple variables can be set before a call to thsq_push(). The system will then collate them into a single message to the backend to save communication bandwidth.

The thsq_sset() variable sets a variable to an integer value. To set a variable to a string, we use the thsq_sset_str() function. We can change the code above to thsq_sset_str("myvariable", "my text"); to set the string my text instead of the value 42.

Even though the typical use case is that device set s variables, d variables can be set from the device too. To do this, we use the thsq_dset() and thsq_dset_str() functions.

Reading variables

Devices can read d variables that are set either by the device itself or via the REST API. A d variable is read with the thsq_get() and thsq_get_to_buf() functions.

#include "thsq.h"
/*---------------------------------------------------------------------------*/
static void
callback(enum thsq_reason r, const char *str, int len)
{
  if(r == THSQ_KEYVAL) {
    printf("Received key %.*s\n", len, str);
    printf("Value of variable myvar is %d\n", thsq_get("myvar"));
  }
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
  static struct thsq_callback cb;
  thsq_add_callback(&cb, callback);
}
/*---------------------------------------------------------------------------*/

This application will print out the value of the d variable myvar whenever the device receives a new d variable. This example assumes that the variable myvar has an integer value. If the value is a string, we need to use the thsq_get_to_buf() function to extract the value.

#include "thsq.h"
/*---------------------------------------------------------------------------*/
static void
callback(enum thsq_reason r, const char *str, int len)
{
  char strbuf[THSQ_MAX_VALLEN];
  if(r == THSQ_KEYVAL) {
    printf("Received key %.*s\n", len, str);

    thsq_get_to_buf("myvar", strbuf, sizeof(strbuf));
    printf("Value of variable myvar is %s\n", strbuf);
  }
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
  static struct thsq_callback cb;
  thsq_add_callback(&cb, callback);
}
/*---------------------------------------------------------------------------*/

This user application reads out the value of the variable myvar into the char buffer called str and prints it out to the UART.

Device power saving modes

In the Thingsquare system, a device may be in three different modes: feather, which is the default mode for a device, deadleaf, which is a power-saving mode, and connected, in which the device has a secure connection with the backend.

To set the default mode for a device, use the thsq_set_default_mode() function. In most cases, the device power mode should be feather which is the default and no action is necessary, like below.

#include "thsq.h"
/*---------------------------------------------------------------------------*/
void
app(void)
{
}
/*---------------------------------------------------------------------------*/

For ultra-low power applications, the device may set its default mode to deadleaf. This will make the device sleep most of the time and will save power, but will also make the device slower to update via the REST API, as the device will only respond to input when it decides to wake up. By default, devices in deadleaf mode will wake up once every 60 seconds. The wake up rate is configurable via the REST API. To set the device to deadleaf mode by default, we use the thsq_set_default_mode() function:

#include "thsq.h"
/*---------------------------------------------------------------------------*/
void
app(void)
{
  thsq_set_default_mode("deadleaf");
}
/*---------------------------------------------------------------------------*/

Thingsquare modules

The Thingsquare system provides a set of default modules to help with digital input/output (GPIO), analog-to-digital conversion (ADC), pulse-width modulation (PWM), serial input/output via the REST API, time-synchronized scheduled lighting events, power configuration, and control messages from smartphones in the local network.

To initialize the modules, the user application calls their respective initialization functions according to the needs of the application:

  • thsq_gpio_init() to start the GPIO, ADC, PWM module
  • thsq_lighting_init() to start the lighting and scheduling module
  • thsq_serialline_init() to start the serial input/output module
  • thsq_localcontrol_init() to start the module that responds to local control messages from nearby smartphones

Configuring custom hardware

Custom hardware has custom hardware configuration in terms of UART pins, LED pins, and GPIO pins. The Thingsquare firmware SDK supports setting custom pins via runtime function calls for both the UART, LED and GPIO pins.

Setting UART pins

The UART is initialized early in the OS boot procedure. A user application may intercept the initialization of the UART with a special init_uart() function.

The simplest user application UART configuration is to simply avoid to initialize the UART:

#include "thsq.h"
/*---------------------------------------------------------------------------*/
void
init_uart(void)
{
  /* Do nothing */
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
}
/*---------------------------------------------------------------------------*/

This will cause the device to not initialize the UART and any attempts at UART output will be discarded.

To apply a custom configuration of the UART pins, the init_uart() function must call the hardware driver to set up the UART. This is different on different platforms. For the TI CC13xx / CC26xx-based platforms, the UART configuration is done as follows.

#include "thsq.h"
#include "ti-lib.h"
/*---------------------------------------------------------------------------*/
void
init_uart(void)
{
  cc26xx_uart_init(IOID_1, IOID_2, IOID_UNUSED, IOID_UNUSED);
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
}
/*---------------------------------------------------------------------------*/

This will set up the UART with pin 1 as the UART TX pin, pin 2 as the UART RX pin, and no pins for RTS and CTS.

Setting LED pins

The LEDs are used by the OS to indicate the state of the system. The default behavior for mesh devices is as follows: the red LED blinks when the device is not yet bonded with a wireless mesh network. The green LED blinks when the device is waiting to be connected to the mesh routing tree or the backend server. When the device is in steady-state, the LEDs are all shut off. An access point is slightly different: it will keep an LED on when in steady-state.

To configure which pins are used for the LEDs, the user application can call the leds_arch_set_pins() function from a function called init_leds().

#include "thsq.h"
#include "ti-lib.h"
/*---------------------------------------------------------------------------*/
void
init_leds(void)
{
  leds_arch_set_pins(IOID_6, IOID_7, IOID_UNUSED);
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
}
/*---------------------------------------------------------------------------*/

This will set pin 6 to be the red LED, pin 7 to be the green LED, and the blue LED to be unavailable.

Using processes and timers

Under the hood, the Thingsquare OS is built on the Contiki IoT OS. Developers familiar with Contiki will already be familiar with the Contiki process model and timers. These can be used to drive activities in a Thingsquare user application.

There are two types of timers that can be used: ctimers and etimers. ctimers call a callback function when the timer fires and etimers post a process event when the timer fires. A ctimer can be used without a process context whereas an etimer requires a process.

To set up a ctimer, simply call the ctimer_set() function:

#include "thsq.h"
/*---------------------------------------------------------------------------*/
static void
timer_callback(void *ptr)
{
  printf("Hello later\n");
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
  static struct ctimer c;
  ctimer_set(&c, CLOCK_SECOND, timer_callback, NULL);
}
/*---------------------------------------------------------------------------*/

This sets up a timer to fire one second after the device boots up.

To set up an etimer, a process context is needed. This can be done as follows.

#include "thsq.h"
/*---------------------------------------------------------------------------*/
PROCESS(example_process, "Example process");
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(example_process, ev, data)
{
  static struct etimer et;
  PROCESS_BEGIN();

  while(1) {
    etimer_set(&et, CLOCK_SECOND);
    PROCESS_WAIT_UNTIL(etimer_expired(&et));
    printf("Hello periodic\n");
  }

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
void
app(void)
{
  process_start(&example_process, NULL);
}
/*---------------------------------------------------------------------------*/

This sets up a process called example_process that sets an etimer and waits for it to fire. When it fires, it prints out a message, and repeats the process via the while(1) loop.