LWM2M

In times of the Internet-of-Things it becomes increasingly important to find an appropriate way to connect different devices. For now most of the current internet protocols are meant for complex use cases, like browsing the internet with a laptop, but when it comes to more specialized devices like the XDK these protocols are oversized, often. The LWM2M protocol is a new approach to solve this issue. This guide shows the basic functionality of how to use the XDK LWM2M API which provides all components that are needed to configure a LWM2M client on the XDK.

  1. API Overview
  2. General Information
    1. OMA LWM2M
    2. Project Leshan
  3. Implementation
    1. Preparation
    2. Defining Device Resources
    3. Initializing the Interface
    4. Configuring the Server
    5. Starting the Client
    6. Keeping the Client running
    7. Error Handling
  4. Full Code Example

API Overview

This chapter introduces the basics of the LWM2M API by providing condensed information of the most important features. To see the full content, the API can be found in the workbench, by opening any project in the workbench and navigating to: ./SDK/xdk110/Libraries/Serval/ServalStack/api/Serval_Lwm2m.h.

Image

The API is situated in the serval stack. For full API access the following header bus be included at the top of the implementation file (.c):

// this will include the Lwm2m interface
#include "Serval_Lwm2m.h"

The header-file Serval_Lwm2m.h contains the entire interface to the implementation of the lightweight M2M protocol as specified by the Open Mobile Alliance for machine to machine communication. Do note that the specification of the lightweight M2M protocol has not been finalized, yet. As such, the current implementation may change to meet possible changes in the specification!

Some of the functions that are made available by the interface are listed below.

FunctionDescription
Lwm2mRegistration_register()Register a LwM2M device on a certain LWM2M server.
Lwm2mRegistration_registerToAllServers()Register a LwM2M device to all specified LWM2M servers.
Lwm2mRegistration_update()Perform a registration update of a LWM2M device on a certain LWM2M server.
Lwm2mRegistration_updateToAllServers()Perform a registration update of a LWM2M device on all specified LWM2M servers.
Lwm2mRegistration_deRegister()Perform a deregistration of a LWM2M device on a certain LWM2M server.
Lwm2mRegistration_deRegisterFromAllServers()Perform a deregistration of a LWM2M device on all specified LWM2M servers.
Lwm2m_getServer()Get a reference to a particular server.
Lwm2m_setNumberOfServers()Sets the number of instantiated servers. This function should be called after the application set the server information.
Lwm2m_initialize()Initialize the Lightweight M2M module.
Lwm2m_start()Start the Lightweight M2M module.

General Information

OMA LWM2M

OMA Lightweight M2M is a protocol from the Open Mobile Alliance for M2M communication or Internet-of-Things device management that is set on top of the Constrained Application Protocol. It was designed to reduce unnecessary overhead created by other protocols like http when connecting simple, embedded devices.

The fundamental idea of LWM2M is to provide a predefined structure so that the clients only have to send the actual data. Besides the fact that this reduces the transmitted data considerably, it also helps to standardize the interfaces so every object implementing the protocol can be used the same way. Of course this only comes with certain limitations concerning flexibility, but on the other hand this makes not only a client, but also a server very lightweight, since only one model have to be implemented.

The data model basically is a container for a consecutively numbered amount of objects, which again have consecutively numbered resources. An object covers a certain use case whilst its resources provide more details in information and functionality. For now OMA is reserving different scopes for different uses:

CategoryObject ID RangeDescription
oma-label0 - 1023Objects defined by the Open Mobile Alliance
reserved1024 - 2047Reserved for future use
ext-label2048 - 10240Objects defined by a 3rd party SDO
x-label10241 - 32768Objects defined by a vendor or individual. Such an object may be either private (no DDF or Specification made available) or public. These objects are optionally private. This is indicated at the time of submission.

The higher numbers are open to custom usage, which comes in handy if the functionality, that is needed to model, is not there, yet. For a more detailed list of yet used object IDs, click here.

This guide will concentrate on the Object-ID 3 which represents the Device itself. It has 17 resources that split up as follows.

IdNameTypeOperationsMultiple InstancesMandatory
0ManufacturerStringReadSingleOptional
1Model NumberStringReadSingleOptional
2Serial NumberStringReadSingleOptional
3Firmware VersionStringReadSingleOptional
4Reboot-ExecuteSingleMandatory
5Factory Reset-ExecuteSingleOptional
6Available PowerIntegerReadMultipleOptional
7Power Source VoltageIntegerReadMultipleOptional
8Power Source CurrentIntegerReadMultipleOptional
9Battery LevelIntegerReadSingleOptional
10Memory FreeIntegerReadSingleOptional
11Error CodeIntegerReadMultipleMandatory
12Reset Error Code-ExecuteSingleOptional
13Current TimeTimeRead, WriteSingleOptional
14UTC OffsetStringRead, WriteSingleOptional
15Time zoneStringRead, WriteSingleOptional
16Supported Binding and ModesStringReadSingleMandatory

This Table shows a brief overview of the resources the object contains. For the full information including the description of the object and all properties of any resource see the specification here.

Note: There are mandatory objects like the LWM2M Security, Server and Device objects and mandatory resources within different objects. In order to prevent errors the not-implemented mandatory objects are handled internally as far as possible.

Project Leshan

The Leshan project is a server-client implementation of the LWM2M protocol in Java. Since the XDK uses C, and not Java, only the server component is needed. The Leshan project even has a standalone LWM2M server everyone can freely connect to:

The connection takes places via CoAP: https://www.eclipse.org/leshan/

Note: The Leshan sandbox is open to everyone, so be careful with the information and functionality you provide. Since it is just a playground for testing the client, custom objects are not supported, unfortunately.

Implementation

In this chapter a basic LWM2M client that connects to the Eclipse Leshan Project sandbox server, will be set up. Afterwards the XDK can be found here. The structure of the implementation is roughly as follows.

Image

The LWM2M API is providing data objects for the different LWM2M items. These objects are acting as containers for the next smaller objects. The interface allows to declare one device instance that represents the XDK. Within this device object multiple LWM2M objects can be defined which again can contain multiple LWM2M resources. The device can connect to different servers.

Preparation

This implementation is based on the XdkApplicationTemplate. The template can be opened by clicking on Help > Welcome > XDKApplicationTemplate in the workbench. By default the application starts in the AppController_Init() function which should already be at the end of the implementation file. First, all required interfaces need to be included, in addition to the ones that are already there.

// LWM2M interface
#include "Serval_Lwm2m.h"
// WiFi settings
#include "BCDS_NetworkConfig.h"
#include "BCDS_WlanConnect.h"
// socket settings
#include "PAL_initialize_ih.h"
#include "PAL_socketMonitor_ih.h"

Before any client can be implemented, the XDK has to be connected to the internet.

To establish a connection to the local Wi-Fi with your XDK, please refer to the article Wi-Fi in Connectivity. It does not only provide information on setup and configuration of Wi-Fi, but it also features a minimal network connection snippet, which can be used in any implementation.

If you use your own code for connecting to Wi-Fi, bear in mind that you have to initialize a socket. If this is not already done, use the following functions after connecting to the internet:

PAL_initialize();
PAL_socketMonitorInit();

Defining Device Resources

This paragraph shows how to configure the devices objects and resources. First the device need to be configured by using the Lwm2mDevice_T type to define the general properties of the XDK:

PropertyDescription
.nameThe name that will be used as the clients name by the LWM2M server.
.bindingThe binding function the XDK will choose. Possible values are: LWM2M_BINDING_UNDEFINED, LWM2M_BINDING_QUEUED, UDP, SMS, UDP_QUEUED, SMS_QUEUED, UDP_AND_SMS, UDP_QUEUED_AND_SMS, LWM2M_BINDING_QUEUED. For information on the possible values take a look at the type definition of Lwm2m_Binding_T.
.smsTelephone number of the device used during communication via SMS. (NULL for not available)
.numberOfObjectInstancesThe number of object instances contained in the device.
.objectInstancesThe list of object instances of the device. This list will only contain application specific objects and the device object.

The object instances list itself can be described by the type Lwm2mObjectInstance_T:

PropertyDescription
.objectIdThe LWM2M object identifier.
.objectInstanceIdThe ID of the instance.
.resourcesPointer to the resources associated with this instance.
.naxNumberOfResourcesThe maximum number of resources for this object.
.permissionsArray of permission sorted by server (first permission refers to the first server in the server array) This will be used to synthetize the Access Control List objects.

To define the resources the LWM2M_RESOURCES(resources) helper macro could be used, which will count and set the resources that are configured.

struct DeviceResource_S
{
  Lwm2mResource_T manufacturer;
};

struct DeviceResource_S deviceResources =
{
  { 0, LWM2M_STRING_RO( "Bosch Connected Devices and Solutions GmbH" ) },
};
Lwm2mObjectInstance_T objectInstances[] =
{
  {
    // object ID:
    3,
    // instance ID:
    0,
    // set resources:
    LWM2M_RESOURCES(deviceResources),
    // set permissions:
    .permissions = {0x3F, 0x0, 0x0, 0x0}
  },
};

Lwm2mDevice_T deviceResourceInfo =
{
  .name = "YOUR_DEVICE_NAME",
  .binding = UDP,
  .sms = NULL,
  .numberOfObjectInstances = 1,
  .objectInstances = objectInstances,
};

This is the most basic setup to start the client. Only the manufacturer is specified in this example. Do note that this implementation of the resources is missing the mandatory objects. In the context of this guide this will suffice, since this is just a demonstration of how to declare a resource in general. The missing resources will be intercepted, so there are still results to see on the Leshan server.

The following code example will show how to add more resources. Once there is a basic understanding it will be easy to expand a definition. Please do not copy the following code in the application right now, because it is just for showing the scheme. The same principle as for adding a new resource applies for adding new instances and objects:

struct DeviceResource_S
{
  Lwm2mResource_T manufacturer;
  Lwm2mResource_T yourSecondResource;
};

struct DeviceResource_S deviceResources =
{
  { 0, LWM2M_STRING_RO( "Bosch Connected Devices and Solutions GmbH" ) },
  { 1, LWM2M_STRING_RO( "second Resource" ) },
};

The name in the DeviceResource_S struct is just a placeholder to distinguish the resources.

Note: If the implementation doesn’t stick to the LWM2M definition of an object it might lead to an inappropriate behavior on the server. So, for example the Device Object expects resource two to be the Model Number. If a date object would be passed here, useless information might be displayed on the server.

For setting the resources data types correctly the following helper macros can be used:

MacroDescription
LWM2M_STRING_RO(string)Helper macro to initialize a string resource.
LWM2M_INTEGER(integer)Helper macro to initialize an integer resource.
LWM2M_FLOAT(floating)Helper macro to initialize a floating point resource.
LWM2M_FLOAT(floating)Helper macro to initialize a floating point resource.
LWM2M_BOOL(boolean)Helper macro to initialize a bool resource.
LWM2M_TIME(time)Helper macro to initialize a time resource.
LWM2M_DYNAMIC(dyn)Helper macro to initialize a dynamic resource.
LWM2M_DYNAMIC_ARRAY(dyn)Helper macro to initialize multiple instance resources.
LWM2M_FUNCTION(function)Helper macro to initialize a function resource.

Initializing the Interface

Now that the device itself and its objects and resources are defined, the interface can be initialized with the settings that were previously made:

// put this after the socket init
Lwm2m_initialize(&deviceResourceInfo);

Configuring the Server

Before starting the client, it is necessary to tell the client to which servers it should connect. By default, the LWM2M implementation of the XDK allows up to four servers. This maximum number is defined by the macro LWM2M_MAX_NUMBER_SERVERS.

If more than one server instance is needed, the following steps have do be done for each instance:

// put this after the LWM2M init
char* serverAddress = "coap://5.39.83.206:5683";
// get the first server instance
Lwm2mServer_T* server = Lwm2m_getServer(0);
strncpy(server->serverAddress, serverAddress, strlen(serverAddress));
server->permissions[0] = LWM2M_READ_ALLOWED;
Lwm2m_setNumberOfServers(1); // adjust this number accordingly

Note: The server instance only accepts an IP address, not an URL. The IP above is one IP from the Leshan sandbox. (Effective date 19.06.2016. If there is trouble with the IP, try to ping leshan.eclipse.org)

In this code example the server permissions were set to LWM2M_READ_ALLOWED. This includes the rights for reading and observing attributes as well as to write attributes that are specified writable by the LWM2M protocol. The following alternatives exist:

  • LWM2M_READ_ALLOWED
  • LWM2M_WRITE_ALLOWED
  • LWM2M_EXECUTE_ALLOWED
  • LWM2M_DELETE_ALLOWED
  • LWM2M_CREATE_ALLOWED
  • LWM2M_FULL_ACCESS

Starting the Client

The LWM2M interface requires a callback function that can tell the application about events. The possible events are:

  • LWM2M_EVENT_TYPE_BOOTSTRAP - Bootstrapping
  • LWM2M_EVENT_TYPE_REGISTRATION - Registration
  • LWM2M_EVENT_TYPE_REGISTRATION_UPDATE - Registration Update
  • LWM2M_EVENT_TYPE_DEREGISTRATION - Deregistration
  • LWM2M_EVENT_TYPE_WRITE - Write operation executed
  • LWM2M_EVENT_TYPE_OBJECT_CREATED - Create operation executed
  • LWM2M_EVENT_TYPE_OBJECT_DELETED - Delete operation executed
  • LWM2M_EVENT_TYPE_NEW_OBSERVER - Observe operation executed
  • LWM2M_EVENT_TYPE_NOTIFICATION - Notification operation executed
  • LWM2M_EVENT_TYPE_OBSERVATION_CANCELED - Cancel operation executed
  • LWM2M_EVENT_TYPE_NEW_SERVER_ADDED - New Server was added

The application callback has to be implemented as follows:

static void applicationCallback(Lwm2m_Event_Type_T eventType, Lwm2m_URI_Path_T
*path, Retcode_T status)
{
  // react to events
}

With this callback function, the start command can be executed:

// put this after the LWM2M server settings

// choose any free port
Ip_Port_T port = Ip_convertIntToPort(1234);
Lwm2m_start(port, &applicationCallback);
// register at first server instance
Lwm2mRegistration_register(0);

If the application is flashed to the XDK now, the client should start properly. After the application is started the client will be shown on the Eclipse Leshan site http://leshan.eclipse.org/#/clients in the client list:

Image

On the device detail page the property that was defined before is shown:

Image

Keeping the Client running

After a while it will happen that “read” can’t be executed on the client any more because of a timeout error which results of the fact that the registration was only sent once and has never been updated. To keep the client running, an implementation of a re-registration routine is needed. Therefore a timer will be created that must refresh the registration from time to time:

void RegistrationUpdate(xTimerHandle pxTimer)
{
  (void) pxTimer;
  // update registration for server at index 0
  Lwm2mRegistration_update(0);
}

// put this after the LWM2M registration

// Variables for the timer task
uint32_t timerAutoReloadOn = UINT32_C(1);
uint32_t twentySecondsDelay = UINT32_C(20000) / portTICK_RATE_MS;

xTimerHandle registrationUpdateTimerHandler = xTimerCreate(
          (const char * const ) "RegistrationUpdate",
          twentySecondsDelay,
          timerAutoReloadOn,
          (void *) NULL,
          RegistrationUpdate
);
xTimerStart(registrationUpdateTimerHandler, 0);

Error Handling

As many of the XDK API interfaces the LWM2M functions are also having a return code. In this guide the return codes have been ignored for the purpose of focusing on the understanding. Below, an example of how to evaluate the return code is shown. It is recommended to handle every API call like this:

// replace the previous RegistrationUpdate() with this:
void RegistrationUpdate(xTimerHandle pxTimer){
  (void) pxTimer;
  Retcode_T rc = RC_OK;
  rc = Lwm2mRegistration_update(0);
  if (RC_OK != rc){
    printf("send registration update failed: %i \r\n", rc);
    return;
  }
  else {
    printf("registration update was sent... \r\n");
  }
}

Full Code Example

Note: The full code example is intended for XDK-Workbench versions 3.4.0 and higher.

/*----------------------------------------------------------------------------*/

/* --------------------------------------------------------------------------- |
 * INCLUDES & DEFINES ******************************************************** |
 * -------------------------------------------------------------------------- */

#include "XdkAppInfo.h"
#undef BCDS_MODULE_ID  /* Module ID define before including Basics package*/
#define BCDS_MODULE_ID XDK_APP_MODULE_ID_APP_CONTROLLER

#include <stdio.h>
#include "BCDS_CmdProcessor.h"
#include "FreeRTOS.h"
#include "timers.h"
// LWM2M interface
#include "Serval_Lwm2m.h"

// WiFi settings
#include "BCDS_NetworkConfig.h"
#include "BCDS_WlanConnect.h"

// socket settings
#include "BCDS_ServalPal.h"
#include "BCDS_ServalPalWiFi.h"

/* --------------------------------------------------------------------------- |
 * HANDLES ******************************************************************* |
 * -------------------------------------------------------------------------- */

static CmdProcessor_T * AppCmdProcessor;/**< Handle to store the main Command processor handle to be used by run-time event driven threads */
static CmdProcessor_T CmdProcessorHandleServalPAL;

/* --------------------------------------------------------------------------- |
 * VARIABLES ***************************************************************** |
 * -------------------------------------------------------------------------- */

#define TASK_PRIORITY_SERVALPAL_CMD_PROC            UINT32_C(3)
#define TASK_STACK_SIZE_SERVALPAL_CMD_PROC          UINT32_C(600)
#define TASK_QUEUE_LEN_SERVALPAL_CMD_PROC           UINT32_C(10)

struct DeviceResource_S
{
  Lwm2mResource_T manufacturer;
};

struct DeviceResource_S deviceResources =
{
  { 0, LWM2M_STRING_RO( "Bosch Connected Devices and Solutions GmbH" ) },
};
Lwm2mObjectInstance_T objectInstances[] =
{
  {
    // object ID:
    3,
    // instance ID:
    0,
    // set resources:
    LWM2M_RESOURCES(deviceResources),
    // set permissions:
    .permissions = {0x3F, 0x0, 0x0, 0x0}
  },
};

Lwm2mDevice_T deviceResourceInfo =
{
  .name = "YOUR_DEVICE_NAME",
  .binding = UDP,
  .sms = NULL,
  .numberOfObjectInstances = 1,
  .objectInstances = objectInstances,
};

/* --------------------------------------------------------------------------- |
 * EXECUTING FUNCTIONS ******************************************************* |
 * -------------------------------------------------------------------------- */

static Retcode_T ServalPalSetup(void)
{
    Retcode_T returnValue = RETCODE_OK;
    returnValue = CmdProcessor_Initialize(&CmdProcessorHandleServalPAL, (char *)"Serval PAL", TASK_PRIORITY_SERVALPAL_CMD_PROC, TASK_STACK_SIZE_SERVALPAL_CMD_PROC, TASK_QUEUE_LEN_SERVALPAL_CMD_PROC);
    /* serval pal common init */
    if (RETCODE_OK == returnValue)
    {
        returnValue = ServalPal_Initialize(&CmdProcessorHandleServalPAL);
    }
    if (RETCODE_OK == returnValue)
    {
        returnValue = ServalPalWiFi_Init();
    }
    if (RETCODE_OK == returnValue)
    {
        ServalPalWiFi_StateChangeInfo_T stateChangeInfo = { SERVALPALWIFI_OPEN, 0 };
        returnValue = ServalPalWiFi_NotifyWiFiEvent(SERVALPALWIFI_STATE_CHANGE, &stateChangeInfo);
    }
    return returnValue;
}

static void networkSetup(void){
    WlanConnect_SSID_T connectSSID = (WlanConnect_SSID_T) "yourWifiNetworkSSID";
    WlanConnect_PassPhrase_T connectPassPhrase = (WlanConnect_PassPhrase_T) "yourWifiNetworkPW";
    WlanConnect_Init();
    NetworkConfig_SetIpDhcp(0);
    WlanConnect_WPA(connectSSID, connectPassPhrase, NULL);

    ServalPalSetup();
}

void RegistrationUpdate(xTimerHandle pxTimer){
  (void) pxTimer;
  Retcode_T rc = RC_OK;
  rc = Lwm2mRegistration_update(0);
  if (RC_OK != rc){
    printf("send registration update failed: %i \r\n",(unsigned int) rc);
    return;
  }
  else {
    printf("registration update was sent... \r\n");
  }
}

static void startRegistrationUpdate(void){
    // put this after the LWM2M registration

    // Variables for the timer task
    uint32_t timerAutoReloadOn = UINT32_C(1);
    uint32_t twentySecondsDelay = UINT32_C(20000) / portTICK_RATE_MS;

    xTimerHandle registrationUpdateTimerHandler = xTimerCreate(
              (const char * const ) "RegistrationUpdate",
              twentySecondsDelay,
              timerAutoReloadOn,
              (void *) NULL,
              RegistrationUpdate
    );
    xTimerStart(registrationUpdateTimerHandler, 0);
}

static void applicationCallback(Lwm2m_Event_Type_T eventType,
        Lwm2m_URI_Path_T *path, Retcode_T status){
  // react to events
    BCDS_UNUSED(eventType);
    BCDS_UNUSED(path);
    BCDS_UNUSED(status);
}

static void startClient(void){
    // choose any free port
    Ip_Port_T port = Ip_convertIntToPort(1234);
    Lwm2m_start(port, &applicationCallback);
    // register at first server instance
    Lwm2mRegistration_register(0);
}

static void configureServer(void){
    char* serverAddress = "coap://5.39.83.206:5683";
    // get the first server instance
    Lwm2mServer_T* server = Lwm2m_getServer(0);
    strncpy(server->serverAddress, serverAddress, strlen(serverAddress));
    server->permissions[0] = LWM2M_READ_ALLOWED;
    Lwm2m_setNumberOfServers(1); // adjust this number accordingly
}

/* --------------------------------------------------------------------------- |
 * BOOTING- AND SETUP FUNCTIONS ********************************************** |
 * -------------------------------------------------------------------------- */

static void AppControllerEnable(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);

    startClient();
    startRegistrationUpdate();
}

static void AppControllerSetup(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    /* Setup the necessary modules required for the application */
    networkSetup();
    Lwm2m_initialize(&deviceResourceInfo);
    configureServer();

    retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerEnable, NULL, UINT32_C(0));
    if (RETCODE_OK != retcode)
    {
        printf("AppControllerSetup : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0); /* To provide LED indication for the user */
    }
}

void AppController_Init(void * cmdProcessorHandle, uint32_t param2)
{
    BCDS_UNUSED(param2);

    Retcode_T retcode = RETCODE_OK;

    if (cmdProcessorHandle == NULL)
    {
        printf("AppController_Init : Command processor handle is NULL \r\n");
        retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER);
    }
    else
    {
        AppCmdProcessor = (CmdProcessor_T *) cmdProcessorHandle;
        retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerSetup, NULL, UINT32_C(0));
    }

    if (RETCODE_OK != retcode)
    {
        Retcode_RaiseError(retcode);
        assert(0); /* To provide LED indication for the user */
    }
}

/** ************************************************************************* */