Skip to content

Porting IOWA

IOWA has been designed to be easily ported to a new platform. All functions contained in the System Abstraction Layer are specific to a platform:

  • Allocating / Freeing memory,
  • Getting the time,
  • Rebooting,
  • Sending / Receiving network messages,
  • Storing and retrieving the security keys,
  • And, so on.

FreeRTOS will be used here to explain the porting of IOWA on a platform.

FreeRTOS configuration

FreeRTOS has multiple levels to handle the memory allocation:

  • Heap 1: The basic memory allocation, does not permit a free,
  • Heap 2: Permits memory to be freed, but without coalescence adjacent free blocks,
  • Heap 3: Works in the same way as malloc and free and handles thread safety,
  • Heap 4: Includes the coalescence adjacent free blocks,
  • Heap 5: Adds the ability to span the heap across multiple non-adjacent memory areas.

Since IOWA needs to allocate and deallocate memory, the heap 2 is the minimum requirement. But the recommended heap to use with IOWA is heap 4.

Moreover, the recommended configuration of FreeRTOSConfig.h is (not all the defines are provided):

#define configUSE_16_BIT_TICKS           0
#define configUSE_MUTEXES                1
#define configSUPPORT_STATIC_ALLOCATION  0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE            ((size_t)14000)
#define configAPPLICATION_ALLOCATED_HEAP 0

#define INCLUDE_vTaskDelay 1

First step

To begin, create an empty FreeRTOS project with just the main task to start the scheduler:

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#define INFINITE_LOOP() while (1);

int main(void)
{
    // Start the scheduler
    vTaskStartScheduler();

    // We should never get here as control is now taken by the scheduler
    INFINITE_LOOP();
}

Generally, IOWA needs two threads to have a working LwM2M Client, but depending of the use case, more threads can be added:

  • The first thread is dedicated to run IOWA and let it handle the LwM2M / CoAP stacks,
  • The other threads are dedicated for the system tasks: measurement, actuator, etc.

You can refer to the part of the documentation IOWA Multithread Environment to have additional information about how IOWA behaves in a multithread environment.

Platform abstraction

Before continuing the IOWA platform functions have to be implemented:

typedef struct
{
    SemaphoreHandle_t globalMutex;
} platform_user_data_t;

void initPlatform(platform_user_data_t *platformUserDataP)
{
    platformUserDataP->globalMutex = xSemaphoreCreateMutex();
    xSemaphoreGive(platformUserDataP->globalMutex);
}

void * iowa_system_malloc(size_t size)
{
    return pvPortMalloc(size);
}

void iowa_system_free(void * pointer)
{
    vPortFree(pointer);
}

int32_t iowa_system_gettime(void)
{
    // Platform dependant
}

void iowa_system_reboot(void * userData)
{
    // Platform dependant or can be a stub function
}

void iowa_system_trace(const char * format,
                       va_list varArgs)
{
    // Platform dependant or can be a stub function
}

void * iowa_system_connection_open(iowa_connection_type_t type,
                                   char * hostname,
                                   char * port,
                                   void * userData)
{
    // Platform dependant
}

void iowa_system_connection_close(void * connP,
                                  void * userData)
{
    // Platform dependant
}

int iowa_system_connection_send(void * connP,
                                uint8_t * buffer,
                                size_t length,
                                void * userData )
{
    // Platform dependant
}

int iowa_system_connection_recv(void * connP,
                                uint8_t * buffer,
                                size_t length,
                                void * userData )
{
    // Platform dependant
}

int iowa_system_connection_select(void ** connArray,
                                  size_t connCount,
                                  int32_t timeout,
                                  void * userData )
{
    // Platform dependant
}

void iowa_system_connection_interrupt_select(void * userData)
{
    // Platform dependant
}

void iowa_system_mutex_lock(void * userData)
{
    platform_user_data_t *platformUserDataP;

    platformUserDataP = (platform_user_data_t *)userData;

    xSemaphoreTake(platformUserDataP->globalMutex, portMAX_DELAY);
}

void iowa_system_mutex_unlock(void * userData)
{
    platform_user_data_t *platformUserDataP;

    platformUserDataP = (platform_user_data_t *)userData;

    xSemaphoreGive(platformUserDataP->globalMutex);
}

Tasks management

We can now update the main function to create our two tasks:

typedef struct
{
    SemaphoreHandle_t initMutex;
    iowa_context_t contextP;
    iowa_sensor_t tempSensorId;
} iowa_user_data_t;

void iowa_task(void const* argument)
{
    // Task dedicated for IOWA operations
    iowa_user_data_t *iowaUserDataP;
    platform_user_data_t platformUserData;
    iowa_status_t result;

    initPlatform(&platformUserData);

    iowaUserDataP = (iowa_user_data_t *)argument;

    // Initialize the IOWA context
    iowaUserDataP->contextP = iowa_init(platformUserData);
    if (iowaUserDataP->contextP == NULL)
    {
        INFINITE_LOOP();
    }

    // Initialize the Client context
    result = iowa_client_configure(iowaUserDataP->contextP, "IOWA_Client_FreeRTOS", NULL, NULL);
    if (result != IOWA_COAP_NO_ERROR)
    {
        INFINITE_LOOP();
    }

    // Add a server
    result = iowa_client_add_server(iowaUserDataP->contextP, 1234, "coap://127.0.0.1:5683", 3600, 0, IOWA_SEC_NONE);
    if (result != IOWA_COAP_NO_ERROR)
    {
        INFINITE_LOOP();
    }

    // Add a sensor object
    result = iowa_client_IPSO_add_sensor(iowaUserDataP->contextP, IOWA_IPSO_TEMPERATURE, 24, "Cel", NULL, -20.0, 50.0, &(iowaUserDataP->tempSensorId));
    if (result != IOWA_COAP_NO_ERROR)
    {
        INFINITE_LOOP();
    }

    xSemaphoreGive(iowaUserData->initMutex); // Synchronization

    // Start the IOWA step
    (void)iowa_step(iowaUserDataP->contextP, -1);

    // We should never get here as control is now taken by IOWA
    INFINITE_LOOP();
}

void system_task(void const* argument)
{
    // Task dedicated for system operations
    iowa_user_data_t *iowaUserDataP;

    iowaUserDataP = (iowa_user_data_t *)argument;

    xSemaphoreTake(iowaUserData->initMutex, portMAX_DELAY); // Synchronization

    while (1)
    {
        float newValue;

        newValue = GET_TEMPERATURE_VALUE();

        (void)iowa_client_IPSO_update_value(iowaUserDataP->contextP, iowaUserDataP->tempSensorId, newValue);

        vTaskDelay(5000);
    }
}

int main(void)
{
    iowa_user_data_t iowaUserData;

    // Create the init semaphore
    iowaUserData->initMutex = xSemaphoreCreateMutex();

    // Create the tasks for IOWA
    xTaskCreate((TaskFunction_t)iowa_task, "iowa", 512, &iowaUserData, tskIDLE_PRIORITY+2, NULL);
    xTaskCreate((TaskFunction_t)system_task, "system", 256, &iowaUserData, tskIDLE_PRIORITY+1, NULL);

    // Start the scheduler
    vTaskStartScheduler();

    // We should never get here as control is now taken by the scheduler
    while (1); // Infinite loop
}

Error handling

Now everything is working, error handling have to put in place in case something went wrong:

  • Failed to initialize the IOWA stack,
  • Failed to connect to a LwM2M Server,
  • And, so on.

IOWA can report events to the Application to inform about internal states such as connection to a LwM2M, new observation started or removed, etc.

These events are reported through the callback iowa_event_callback_t, this callback is given when calling the API iowa_client_configure():

void eventCb(iowa_event_t* eventP,
             void * userData,
             iowa_context_t contextP)
{
    switch (eventP->eventType)
    {
    case IOWA_EVENT_REG_FAILED:
        // Remove then re-add the Server in case the Client wasn't able to reach it
        iowa_client_remove_server(contextP, 1234);
        iowa_client_add_server(contextP, 1234, "coap://127.0.0.1:5683", 3600, 0, IOWA_SEC_NONE);
        break;

    default:
        // Do nothing for the other events
        break;
    }
}

void iowa_task(void const* argument)
{
    ...

    result = iowa_client_configure(iowaUserDataP->contextP, "IOWA_Client_FreeRTOS", NULL, eventCb);

    ...
}