A Modular Approach to IoT Application Development - Part 2: Software Design

Contributed By Digi-Key's North American Editors

Editor’s Note: IoT applications bring a particularly tight convergence between hardware and software components, requiring developers to account for myriad details in each domain. This two-part series looks at a single platform that uses a modular approach designed to facilitate that convergence. Part 1: Hardware Choices looked at how the platform’s hardware options simplify implementation of IoT devices. Part 2, here, examines the platform’s software architecture and its role in speeding end-to-end development of IoT applications.

End-to-end IoT application development presents engineers with an extensive set of requirements stretching from IoT hardware to cloud-based resources. Developers find themselves handling myriad integration challenges as they combine diverse hardware and software components into a functional system.

With the Samsung ARTIK platform, however, developers can take advantage of a unified approach to design and deploy complex IoT applications more quickly.

For software development of IoT applications, the ARTIK platform is comprehensive. It melds two major Samsung software environments – TizenRT and the ARTIK cloud. Within the underlying IoT hardware layer, ARTIK devices such as the Samsung ARTIK 053 module use TizenRT, an open-source real-time version of the company’s widely used Linux-based Tizen operating system (OS) for smartphones, wearables, and home entertainment systems. Created specifically for IoT devices, TizenRT is designed to operate within the limited resources available in IoT designs. For example, developers can tune a TizenRT configuration to squeeze its image footprint down to only 40 Kbytes.

For cloud services, Samsung’s ARTIK is based on a concept called Samsung Architecture for Multimodal Interactions (SAMI), which provides a framework for abstracting cloud-based mechanisms into basic data structures and an application programming interface (API). In implementing the SAMI concept, the ARTIK cloud simplifies cloud interactions into a series of simple API calls using a variety of protocols including REST, WebSockets, MQTT, and CoAP.

The combination of ARTIK modular hardware and this multilayer software environment results in a simplified approach to IoT development. As noted in Part 1: Hardware Choices, ARTIK development boards implement a complete system for IoT terminal nodes and gateway designs. Consequently, developers can bring up a full IoT system simply by plugging an ARTIK board into a Windows-based development system. In turn, software engineers can begin implementing code using free tools including Samsung’s own ARTIK integrated development environment (IDE), GCC C/C++ compilers, or the OpenOCD on-chip debugger and programming environment.

Completing the connection to the ARTIK cloud is just as easy. In the ARTIK environment, developers describe the attributes of their hardware devices in a data package called a device manifest. To connect their ARTIK hardware modules to the cloud, they simply load the corresponding device manifest into their ARTIK cloud dashboard. In turn they receive a cloud API access token. After this short procedure, software developers can quickly begin developing their IoT applications using the ARTIK API.

Modular API

Samsung organizes its ARTIK API as a set of modules corresponding to different hardware functions or middleware services. For example, separate modules abstract the low-level interactions involved in accessing ADC, GPIO, PWM, and other hardware related functions. Other modules support specific wireless protocols such as Wi-Fi and Bluetooth or cloud connectivity protocols such as HTTP or MQTT for interacting with the ARTIK cloud.

To simplify development using the many available modules, the API itself helps eliminate run-time module errors. Here, the API provides a request function that frees developers from keeping track of module location or its load status. Rather than testing for the module as required with some environments, developers simply request the module through a common name such as “wifi”. For example, the call:

artik_wifi_module *wifi = (artik_wifi_module *)artik_request_api_module("wifi");

loads the wifi module and returns a reference (*wifi). Later in the code, when that module is no longer required, developers can unload the module with the call:

artik_release_api_module(wifi);

Each module in turn builds on low-level services that abstract hardware-based elements such as analog-to-digital converters or protocol-based components such as MQTT services. Within a module, transactions that read or write data to hardware-based elements or protocol-based components rely on a collection of associated structures.

For example, developers use a simple configuration structure (Listing 1) to access module routines for ADC operations (Listing 2) such as accessing an ADC (adc->request) and reading its sampled values (adc->get_value()).

   typedef void *artik_adc_handle;

 

   typedef struct {

       int pin_num;

       char *name;

       void *user_data;

 

   } artik_adc_config;

 

   typedef struct {

       artik_error(*request) (artik_adc_handle * handle,

                      artik_adc_config * config);

       artik_error(*release) (artik_adc_handle handle);

       artik_error(*get_value) (artik_adc_handle handle, int *value);

   } artik_adc_module;

 

   extern artik_adc_module adc_module;

Listing 1: The ARTIK API abstracts hardware such as the ARTIK 053 module’s ADC into a pair of structures (artik_adc_config and artik_adc_module) used by API module functions. (Code source: Samsung Semiconductor)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <artik_module.h>

#include <artik_adc.h>

#define CHECK_RET(x)    { if (x != S_OK) goto exit; }

static artik_adc_config config = { 0, "adc", NULL };

artik_error adc_test_value(void)

{

    artik_adc_handle handle;

    artik_error ret = S_OK;

    artik_adc_module *adc = (artik_adc_module *)artik_request_api_module("adc");

    int val = -1;

    int i = 0;

    fprintf(stdout, "TEST: %s\n", __func__);

    if (adc == INVALID_MODULE) {

        fprintf(stderr, "TEST: %s - Failed to request module\n", __func__);

        return E_NOT_SUPPORTED;

    }

    ret = adc->request(&handle, &config);

    if (ret != S_OK) {

        fprintf(stderr, "TEST: %s - Failed to request adc (err=%d)\n", __func__, ret);

        goto exit;

    }

    for (i = 0; i < 5; i++) {

        ret = adc->get_value(handle, &val);

        if (ret != S_OK) {

            fprintf(stderr, "TEST: %s - Failed to get adc value (err=%d)\n", __func__, ret);

            goto exit;

        }

        fprintf(stdout, "TEST: %s - Value = %d\n", __func__, val);

        usleep(1000*1000);

    }

    adc->release(handle);

exit:

    fprintf(stdout, "TEST: %s %s\n", __func__, (ret == S_OK) ? "succeeded" : "failed");

    ret = artik_release_api_module(adc);

    if (ret != S_OK) {

        fprintf(stderr, "TEST: %s - Failed to release module\n", __func__);

        return ret;

    }

    return S_OK;

}

int main(void)

{

    artik_error ret = S_OK;

    ret = adc_test_value();

    CHECK_RET(ret);

exit:

    return (ret == S_OK) ? 0 : -1;

Listing 2: The ARTIK ecosystem provides an extensive set of code samples such as this ADC test routine, which demonstrates simple calls to the adc module for accessing (adc->request), reading data (adc->get_value) , and releasing an ADC channel (adc->release) represented by an associated structure (config) and instance (handle). (Code source: Samsung Semiconductor)

For asynchronous operations, the ARTIK architecture relies on callbacks to provide completion services. The low-level loop module in the ARTIK API provides services that manage callback functionality for setting timeouts, creating repeated callbacks, setting up a watch, and more (Listing 3). Developers can examine ARTIK sample code that demonstrates the functional architecture and illustrates specific design patterns for operations such as MQTT transactions (Listing 4).

typedef struct {

    void (*run)(void);

    void (*quit)(void);

    artik_error(*add_timeout_callback)(int *timeout_id, unsigned int msec,

            timeout_callback func, void *user_data);

    artik_error(*remove_timeout_callback)(int timeout_id);

    artik_error(*add_periodic_callback)(int *periodic_id, unsigned int msec,

            periodic_callback func, void *user_data);

    artik_error(*remove_periodic_callback)(int periodic_id);

    artik_error(*add_fd_watch)(int fd, enum watch_io io, watch_callback func, void *user_data, int *watch_id);

    artik_error(*remove_fd_watch)(int watch_id);

    artik_error(*add_idle_callback)(int *idle_id, idle_callback func, void *user_data);

    artik_error(*remove_idle_callback)(int idle_id);

} artik_loop_module;

Listing 3: Along with device- and service-oriented modules, the ARTIK API provides a number of utility modules such as the loop module, which provides the structure shown here for managing sophisticated callback services. (Code source: Samsung Semiconductor)

void on_publish(artik_mqtt_config *client_config, void *user_data, int result)

{

    fprintf(stdout, "message published (%d)\n", result);

}

    .

    .

    .

 

    mqtt = (artik_mqtt_module *)artik_request_api_module("mqtt");

    loop = (artik_loop_module *)artik_request_api_module("loop");

 

    memset(&subscribe_msg, 0, sizeof(artik_mqtt_msg));

    snprintf(sub_topic, sizeof(sub_topic), "/v1.1/actions/%s", device_id);

    subscribe_msg.topic = sub_topic;

    subscribe_msg.qos = 0;

 

    memset(&config, 0, sizeof(artik_mqtt_config));

    config.client_id = "sub_client";

    config.block = true;

    config.user_name = device_id;

    config.pwd = token;

 

    /* TLS configuration  */

    memset(&ssl, 0, sizeof(artik_ssl_config));

    ssl.verify_cert = ARTIK_SSL_VERIFY_REQUIRED;

    ssl.ca_cert.data = (char *)akc_root_ca;

    ssl.ca_cert.len = strlen(akc_root_ca);

    config.tls = &ssl;

 

    /* Connect to server */

    mqtt->create_client(&client, &config);

    mqtt->set_connect(client, on_connect_subscribe, &subscribe_msg);

    mqtt->set_disconnect(client, on_disconnect, mqtt);

    mqtt->set_publish(client, on_publish, mqtt);

    mqtt->set_message(client, on_message_disconnect, mqtt);

 

    mqtt->connect(client, "api.artik.cloud", broker_port);

 

    loop->run();

 

    artik_release_api_module(mqtt);

    artik_release_api_module(loop);

    .

    .

    .

Listing 4: Developers can gain experience with ARTIK design patterns by examining sample code such as this snippet, which demonstrates use of callbacks for asynchronous operations such as MQTT transactions (only one callback, on_publish, is shown here). (Code source: Samsung Semiconductor)

Security architecture

As noted in Listing 4, each API call requires a valid device id and access token (config.user_name and config.pwd, respectively, in Listing 4). The ARTIK platform deploys a particularly robust series of security policies built on underlying hardware-based security mechanisms.

At its most fundamental level, ARTIK security builds on Samsung’s trusted execution environment (TEE). TEE is based on the underlying ARM TrustZone hardware architecture built into ARM® Cortex®-A processors, such as those used in Samsung’s ARTIK 5 series of hardware modules. In the TrustZone model, trusted software runs in a separate execution environment. Using hardware-based virtualization mechanisms, this approach isolates trusted software that needs to access to all system resources from general software applications.

Within the TEE partition, Samsung stores critical data in a flash-based secure storage system or in a hardware component called a secure element. Samsung builds a secure element into each ARTIK hardware module, providing a consistent tamper resistant mechanism for storing private keys securely across different ARTIK hardware modules. At the application level, developers use a separate security API module to access this security partition and its underlying hardware components (Figure 1).

Image of modules in the Samsung ARTIK family leverage ARM’s TrustZone security architecture

Figure 1: Modules in the ARTIK family leverage ARM’s TrustZone security architecture and provide hardware-based security mechanisms such as secure storage used to protect critical data such as private crypto keys. (Image source: Samsung Semiconductor)

The ARTIK platform builds on these device level security features to secure end-to-end transactions between ARTIK hardware modules and the ARTIK cloud. When creating a new device, developers can generate and upload to the cloud a certificate authority (CA) certificate used to authenticate that device and others of the same type. Within the device itself, developers can store a client certificate and private key in the hardware module’s secure element. When users later register that device with the ARTIK cloud, the ARTIK platform uses the certificates to recognize an authorized device. During normal ongoing operations, ARTIK uses the private client key stored in secure storage for mutual authentication for TLS (transport layer security) transactions (Listing 5).

int send_data_to_artik(int *data)

{

 

       int ret, sockfd, i;

       bool redirected;

       struct sockaddr_in server;

       struct wget_s ws;

       struct http_client_tls_t *client_tls = (struct http_client_tls_t *)malloc(sizeof(struct http_client_tls_t));

       struct http_client_ssl_config_t ssl_config;

 

       /*

        * Set Up ssl config

        */

       ssl_config.root_ca = (char *)c_ca_crt_rsa;

       ssl_config.root_ca_len = sizeof(c_ca_crt_rsa);

       ssl_config.dev_cert = (char *)c_srv_crt_rsa;

       ssl_config.dev_cert_len = sizeof(c_srv_crt_rsa);

       ssl_config.private_key = (char *)c_srv_key_rsa;

       ssl_config.private_key_len = sizeof(c_srv_key_rsa);

 

       char r_message[1024], w_message[1024];

       char body[1024];

       char body_main[512];

       char data_string[256];

       char req[300], body1[50], body2[150];

       int req_len, body_len;

 

       ws.buffer = r_message;

       ws.buflen = 1024;

 

       do {

              ws.httpstatus = HTTPSTATUS_NONE;

              ws.offset = 0;

              ws.datend = 0;

              ws.ndx = 0;

              .

              .

              .

              sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

              .

              .

              .

              /*

               * Try to connect server

               */

              ret = connect(sockfd, (struct sockaddr *)&server,

                           sizeof(struct sockaddr_in));

              if (ret < 0) {

                     /* ERROR: Connection Failed */

                     ndbg("ERROR: connect failed: %d\n", ret);

                     free(client_tls);

                     return ERROR;

              }

 

              client_tls->client_fd = sockfd;

              .

              .

              .

              /*

               * Assemble TLS header, body, footer

               */

              sprintf(req, "POST /v1.1/messages HTTP/1.1\r\nhost:api.artik.cloud\r\nContent-Type: application/json\r\nAuthorization: Bearer %s\r\nContent-Length:", CONFIG_ARTIK_CLOUD_BEARER);

              sprintf(body1, "{\"data\": {");

              sprintf(body2, "},\"sdid\": \"%s\",\"type\": \"message\"}", CONFIG_ARTIK_CLOUD_DID);

              body_main[0] = '\0';

 

              for (i = 0; i < field_count; i++) {

                     if (i == field_count - 1)

                           sprintf(data_string, "\"%s\":%d", field_name[i], data[i]);

                     else

                           sprintf(data_string, "\"%s\":%d,", field_name[i], data[i]);

                     strcat(body_main, data_string);

              }

              /*

               * Checking the body_main to send

               */

              ndbg("sending data : %s\n", body_main);

              sprintf(body, "%s%s%s", body1, body_main, body2);

 

              /*

               * Get Contents Length

               */

              req_len = strlen(req);

              body_len = strlen(body);

 

              sprintf(w_message, "%s%d\r\n\r\n%s", req, body_len, body);

 

              ndbg("send_message : %s\n", w_message);

 

              ret = mbedtls_ssl_write(&(client_tls->tls_ssl), (unsigned char*)w_message, strlen(w_message));

 

              if (ret < 0) {

                     /* Error Occur */

                     ndbg("ERROR: send failed: %d\n", ret);

                     goto errout;

              } else {

                     /* Send Success */

                     ndbg("SEND SUCCESS: send %d bytes\n", ret);

              }

              .

              .

              .

Listing 5: This snippet demonstrates how developers can set up SSL (secure sockets layer) configuration, TLS (transport layer security) session initialization, and secure data transmission (mbedtls_ssl_write). (Code source: Samsung Semiconductor)

As shown in Listing 5, Samsung’s software samples demonstrate key design patterns for streaming data to the cloud using TLS-based communications. By leveraging those software samples, developers can quickly build applications able to send data from ARTIK hardware modules to the ARTIK cloud, using a routine such as send_data_to_artik() to hide the details of mutual authentication and secure data exchange (Listing 6).

int artik_demo_run(int argc, char *argv[])

{

       int ret;

       int field_value[field_count];

 

       /*

        * Initialize Artik Network

        */

       wifiAutoConnect();

 

       while (1) {

              printf("Sending ... ");

              /*

               * Get Rssi of network using dm_conn_get_rssi api

               */

              ret = dm_conn_get_rssi(&field_value[0]);

              printf("%s[%d] : %d / ", field_name[0], ret, field_value[0]);

              /*

               * Get Tx Power of network using dm_conn_get_tx_power api

               */

              ret = dm_conn_get_tx_power(&field_value[1]);

              printf("%s[%d] : %d / ", field_name[1], ret, field_value[1]);

              /*

               * Get Channel of network using dm_conn_get_channel api

               */

              ret = dm_conn_get_channel(&field_value[2]);

              printf("%s[%d] : %d\n", field_name[2], ret, field_value[2]);

 

              /*

               * Sending Data to Artik Cloud

               */

              send_data_to_artik(field_value);

 

              sleep(3);

       }

}

Listing 6: This Samsung code sample shows the basic design pattern for streaming data (in this case, received signal strength indicator (RSSI) and transmitter (Tx) power readings) to the ARTIK cloud using the send_data_to_artik() routine described in Listing 5. (Code source: Samsung Semiconductor)

For some IoT applications, however, even the relative ease of IoT device deployment with the ARTIK platform can prove limiting. For example, cloud application developers often find themselves waiting for IoT device deployment and generation of data streams needed to drive their applications.

Instead of waiting, software developers would typically build test data sets to exercise their applications, but the task of building realistic test data streams could rival development of the application itself. However, with the ARTIK cloud, developers can more easily begin developing their high-level cloud-based application well before they have production data from final IoT hardware.

Simulated data streams

Samsung provides a Java-based command-line device simulator that can generate test data and transmit properly formatted data packages to the ARTIK cloud. Using the simulator in interactive mode, developers can execute basic commands to gain experience with ARTIK cloud interactions. Used in its automated mode, however, the device simulator can generate random data or provide a template that developers can complete using their own test data. For example, after starting the command-line device simulator, developers can create a test package, called a “scenario,” for a particular device with device ID “device_id” as follows:

create scenario device_id mytest

This creates a JSON file containing a basic template (Listing 7).

{

    "sdid": "device_id",

    "deviceToken": "",

    "data": {},

    "config": {},

    "api": "POST",

    "period": 1000

}

Listing 7: Using the ARTIK Java-based command-line device simulator, developers can automatically generate a properly formatted template for sending data to the ARTIK cloud. (Code source: Samsung Semiconductor)

Developers can fill out the data block with appropriate key/value pairs in JSON format to complete the test package (Listing 8).

    "data": {

        "someNumber":0,

        "someText":"",

        "heartRate":0,

        "stepCount":0

        "randomWord":"",

        "content":"constant stuff"

    }

Listing 8: Developers can expand the basic simulator-generated data template by providing a data block with key:value pairs appropriate to their application. (Code source: Samsung Semiconductor)

A configuration block (config in Listing 7) defines how the simulator should modify the data block in the template in sending a series of data messages to the ARTIK cloud.

For example, the config block shown in Listing 9 specifies that someNumber should cycle through a series of values sequentially from 1 to 10 and the other values should change in a specified fashion. Using the device simulator and this simple data generator, developers can test cloud-based applications with realistic data streams early in the development process.

.... "config": {

      "someNumber":{"function":"cycle", "type":"Integer", "value":[1,2,3,4,5,6,7,8,9,10]},

      "someText":{"function":"cycle", "type":"String", "value":["text","css","json"]},

      "heartRate":{"function":"random", "type":"Integer", "min":0,"max":200},

      "stepCount":{"function":"increment", "type":"Integer", "min":1000, "max":10000, "increment":2, "period":5000},

      "randomWord":{"function":"random", "type":"String"}

  } ....

Listing 9: By adding a configuration block config to the simulator-generated template, developers can use the device simulator to send a series of data packages to the ARTIK cloud, enabling developers to test cloud-based applications with realistic data well before actual IoT device hardware is available. (Code source: Samsung Semiconductor)

For developers, the ARTIK platform simplifies the task of delivering data securely to the cloud during development and final deployment. For any IoT application, however, the key to differentiation lies with the ability of developers to add value to that data through cloud-based processing, analytics, predictive mechanisms, and more. To support IoT applications at the cloud level, the ARTIK cloud complements its own rich set of services with a broad array of third-party services designed for any high-throughput data-driven application and for IoT applications in particular.

After they have built their ARTIK cloud-based application, developers can even tie their ARTIK data streams into existing applications built on other cloud platforms. Here, ARTIK cloud connectors define a set of services used to stream data between cloud platforms, allowing developers to connect ARTIK cloud data streams to third-party services such as Amazon Web Services Kinesis for performing complex analytics on high-throughput data streams.

Conclusion

IoT applications can present widely diverse requirements for connectivity and cloud services. In the past, IoT developers had little choice but to integrate software from different hardware platforms and cloud services, delaying progress on the final application itself. In contrast, the Samsung ARTIK ecosystem combines multiple hardware modules (see Part 1: Hardware Choices), software libraries, and cloud services into a unified platform.

Using this platform, developers can build sophisticated applications that build on consistent hardware features and software capabilities needed to meet increasingly complex IoT requirements.

Disclaimer: The opinions, beliefs, and viewpoints expressed by the various authors and/or forum participants on this website do not necessarily reflect the opinions, beliefs, and viewpoints of Digi-Key Electronics or official policies of Digi-Key Electronics.

About this publisher

Digi-Key's North American Editors