XDK

Developer Portal

XDK - The Sensor X-perience

Programmable sensor device and IoT prototyping platform with an open SDK for any use case.

CoAP

This section provides information and details on implementing a CoAP client or server on the XDK.

  1. General Information
    1. General Description
      1. Request for Comments Document (RFC 7252)
      2. Message Format
      3. Rest Paradigm
      4. User Datagram Protocol (UDP)
    2. CoAP API Overview
  2. Client Implementation
    1. Preparation and Includes
    2. API Reference
    3. Implementation Outline
    4. Implementation Details
  3. Server Implementation
    1. Preparation and Includes
    2. API Reference
    3. Implementation Outline
    4. Implementation Details
  4. Message Options
    1. API Reference
    2. Serializing Options
    3. Parsing Options
  5. Copper
    1. _Copper Installation_
  6. Full Code Examples
    1. Client
    2. Servers

General Information

General Description

The Constrained Application Protocol (CoAP) is an internet application protocol that has been designed to be very inexpensive, and as such, able to work on micro-controllers with very little RAM and code space. It is a powerful protocol to connect devices on the Internet of Things. Just like HTTP, it implements the REST model, where servers make resources available, which then can be accessed via a URL using methods such as GET, PUT, POST and DELETE. This makes CoAP blend in very well with other HTTP sites, especially from a developer's point of view. What makes it different from HTTP is the lightweight transport stack. It uses only few bytes for extra information in headers, and gets by with UDP on IP. Since CoAP was developed as an Internet Standards Document, namely RFC 7252, the protocol has been designed to last for decades. On top of that, it provides strong security and supports any data format of your choice, for example XML or JSON. This chapter will give a quick introduction to some of the details mentioned above, for example on the structure of a request, the header of a message, and the related REST paradigm. Readers who are familiar with these topics, or those who are just looking for the XDK specific implementation details, can safely skip to chapter 2.

Request for Comments Document (RFC 7252)

A RFC document is used to gather input on a specific concept. In this case, the RFC 7252 has gathered input on the implementation on CoAP and over time it also became the standard. This means that, if a XDK based CoAP server communicates with a Python-based Client, they will have the same understanding of CoAP. The only case where they will not behave as expected is if either client or server are not using a protocol based on RFC 7252. As such, anything the XDK implements for CoAP is conform to the standard, such as PUT, DELETE, POST, GET, Options, Confirmable/Non-Confirmable Messages, Header-Information, the UDP basis.

Message Format

As with any other protocol, the message format is uniquely defined for CoAP, and is documented in the RFC 7252 Message Format chapter. It basically consists of five parts, three of which are optional. The first mandatory part is the header information. It provides information about the message itself. The first 2 bits define the version of the protocol, the next 2 bits define the type of the message. The type is followed by 4 bits that indicate the token length. The token length is followed by an 8 bit long code, which further classifies the type of message. Those two bytes are followed by two bytes for the Message ID, which is used to detect duplicates and to match messages. The second mandatory part is just one byte to indicate that the next bytes are the payload. All in all, a minimal message only consists of five bytes. The optional Token and Options are usually not much larger. As a comparison, HTTP headers can have sizes of more than 16KB, and that does not include the payload itself. This fact makes CoAP a desirable Internet Application Protocol in the Internet of Things and for Machine-To-Machine applications.

Rest Paradigm

REST is an architectural paradigm. The important fact to know about REST is, that it is stateless. The server does not need to remember anything about previously sent messages. All the information necessary is stored in the messages themselves. This also relieves the burden on the RAM for the server. Additionally, it is client-server based. There is always a client making requests to a server and a server responding. Any device can be a client or a server. A single device can even be both of them at once. CoAP uses four core methods of the REST paradigm, which are PUT, DELETE, POST, GET, and in this guide we will mainly mention the latter two methods. The GET method is used by a client to retrieve a resource from a server. A resource is basically a URI path, like /sensors/ accelerometer/data. If a client sends a GET with that resource in mind, he can get a response which includes the data of the accelerometer of the device he sent the request to. The POST method is used by a client to create a new resource on the server. This resource is basically information that the server holds, which will be available for use by anyone, including other clients. PUT is similar to POST, but it does not create a new resource. Instead, it only updates the resource's content. DELETE will delete a resource.

User Datagram Protocol (UDP)

UDP is a minimal network protocol, that does not use transmission channels or any other form of session between two devices. It is used to send data from one point to another without any mandatory preceding communication between these two points. As such, it is the optimal protocol for CoAP, as it does not require any additional state information and as such relieves the burden on the device's RAM and minimizes the amount of information sent in a message.

CoAP API Overview

The highest levels of API in regard to CoAP are all provided by the Serval Stack. It offers functionality such as a server, a client or general utilities when implementing a CoAP based application. This ensures that any message sent and received will be a CoAP message and treated as such. Using any library outside of the CoAP context, with the aim of implementing CoAP applications, is generally not recommended because messages would need to be built manually.

fig1

In this guide, we will primarily use the API1 provided by the header files Serval_Coap.h, Serval_CoapServer.h, Serval_CoapClient.h. The first one provides general utilities, such as a parser or a serializer which are used to extract details from messages and to build them respectively. The second and third header files provide the functions necessary to use Server or Client functions.

Client Implementation

This chapter will explain how a Coap-Client can be set up using the XDK

Preparation and Includes

The application will be based on an empty XdkApplicationTemplate project, which you can open from the Welcome screen of the XDK-Workbench. The code snippets have to be placed above AppControllerEnable() one after another. As a guideline, if code-snippet 2 comes after snippet 1, then snippet 2 has to be placed above snippet 1 in the source code. Using other orders might lead to compilation errors during the build process. The implementations that are presented here usually need a connection to another device. A client implementation needs a server to send requests to, and a server needs a client to answer to. Between these two roles, there has to be a working network connection.

To establish a connection to the local Wi-Fi with your XDK, please refer to the section Wi-Fi P2P. 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.

Additionally, for the code snippets in this chapter to work, the following header-files must be included.

#include "Serval_Coap.h"
#include "Serval_CoapClient.h"
#include "Serval_Network.h"

API Reference

In general, it is recommended to develop an application based on the highest API level the XDK framework supports. The high level API for CoAP-Clients is documented in the header file Serval_CoapClient.h. Additionally, you can find a list of the main functions we will be using for the implementation of a CoAP-Client below. The following table shows the CoAP-Client functions we will use, and some additional utility functions. For a full description, please refer to the corresponding APIs.

Before the CoAP client implementation can be used, it needs to be enabled in the Serval Stack library. Due to the fact, that for other modules such as LWM2M the combined functionality of CoAp Client and Server is used, the CoAP client implementation is in default configuration disabled.

For that, go to the application.mk makefile at:

SDK > xdk110 > Common

Open the application.mk makefile and browse down to line 65 were the BCDS_SERVALSTACK_MACROS are defined and set the macro line below

-D SERVAL_ENABLE_COAP_COMBINED_SERVER_AND_CLIENT=1\

to

-D SERVAL_ENABLE_COAP_COMBINED_SERVER_AND_CLIENT=0\.

Save the changes and rebuild the SDK. This will enable the CoAP client implementation in the Serval Stack library. Please note that this modification has a direct influence on all other XDK projects, especially the ones which use LWM2M. If you consider using them again, please ensure to revert the changes you made to the macro.

CLIENT FUNCTIONALITY:

Function Description
CoapClient_initialize() This function is called to initialize the CoAP client and related modules. It must be called before any other CoAP-Client related function.
CoapClient_startInstance() This function is called to start the CoAP client instance.
CoapClient_request() This function is called to send a message as a request. As input, it receives a message, a callback for informing about the status of sending and a callback to handle a response to this message.
CoapClient_initReqMsg() This function is called to initialize a message. As input, it receives the target's IP address, its port and the pointer to a message.

UTILITY:

Function Description
CoapParser_setup() This function sets up a CoAP-Parser which will be used to receive certain information from a CoAP-Message, such as the code, the payload or the options. As input, it receives a variable of type CoapParser_T and the pointer to a message.
CoapParser_getCode() This function is called to receive the code of a message. As input, it receives a pointer to a message.
CoapParser_getPayload() This function is called to receive the payload of a message. As input, it receives a pointer to the previously set up parser, a pointer to a payload array, and the array's length.
CoapSerializer_setup() This function sets up a CoAP-Serializer, which will be used to create a message. It handles setting options, the payload, the code, etc, for a given message. As input, it receives variable of type CoapSerializer_T, the pointer to a message, and a code indicating whether a RESPONSE or a REQUEST is created.
CoapSerializer_setCode() This function is called to set the code of a message. As input, it receives the serializer, a message and the code to be set.
CoapSerializer_serializePayload() This function is called to set the payload for a message. As input, it receives the serializer, a message pointer, the payload and the payload's length.

Implementation Outline

The following code shows the general outline of what needs to be implemented. It also shows the implementation of the AppControllerEnable() and AppControllerSetup() functions, which have to be placed at the very bottom of the source code. networkSetup() implements a simple WiFi-connection. CoapClient_initialize() and CoapClient_startInstance() are described in the previous chapter API Reference. First CoapClient_initialize() needs to be called to initialize relevant Client modules in AppControllerSetup(). Afterwards the implementation to start the CoAP client instance can be proceed in the function AppControllerEnable()-

The variables for the server IP and server port are declared.

The standard port to use for CoAP is 5683. Additionally, we need the target IP and port of the server we are going to address. They will be used in the function sendClientRequest(), which we will implement later.

Then the function CoapClient_startInstance() is called to start the CoAP client instance.

static void AppControllerEnable(void * param1, uint32_t param2)
{
    // Previous implementations of AppControllerEnable here

    vTaskDelay(5000);
    Ip_Port_T serverPort = Ip_convertIntToPort((uint16_t)5683);
    Ip_Address_T ip;
    // replace the IP-string with your target's IP
    Ip_convertStringToAddr("255.255.255.255", &ip);
    CoapClient_startInstance();

    sendClientRequest(&ip, serverPort);

    // Following implementations of AppControllerEnable here
}


static void AppControllerSetup(void * param1, uint32_t param2)
{
    // Previous implementations of AppControllerSetup here

    /* Setup the necessary modules required for the application */
    networkSetup();

    CoapClient_initialize();

    // Following implementations of AppControllerSetup here
}

Implementation Details

There are four types of requests we can send. Namely GET, POST, PUT, DELETE. Choosing the type of a message is as simple as changing the predetermined code we use. Therefore, we will implement a generic set of functions to send a request, while disregarding most of the options and the payload in this chapter. To send requests, we first need to initialize a message. We start working on a message by calling the function CoapClient_initReqMsg(). It receives the IP address and port where the message gets sent to as additional input. Before we send the request, the message has to be serialized. That means, we form the message by setting the code, the options and the payload. In this case, our payload is a generic string. It will be the same for a GET and a POST request, since the use of the payload is determined by the implementation of the receiving server. For this, we later implement the function, which receives the message, the payload and the code for the specific type of request. The following code uses COAP_POST for a POST request. Alternatively, COAP_GET can be used for a GET request. Next, we need to define a callback-function, which should be used to monitor the status of sending of a message. In our case, the callback sendingCallback() will be implemented later using an empty function which does nothing. It is required to use Msg_defineCallback() to wrap our own callback correctly. The request-function will use the wrapped callback. The function CoapClient_request() receives the message we built, the sending-callback and additionally a callback for handling the response from the server. The callback for responding will also be implemented later.


void sendClientRequest(Ip_Address_T *addr_ptr, Ip_Port_T port){
  Msg_T *msg_ptr;
  char const *payload = "Hello Server";
  CoapClient_initReqMsg(addr_ptr, port, &msg_ptr);
  serializeRequest(msg_ptr, Coap_Codes[COAP_POST /*COAP_GET*/], payload);
  Callable_T *alpCallable_ptr = Msg_defineCallback(msg_ptr,
  (CallableFunc_T) sendingCallback);
  CoapClient_request(msg_ptr, alpCallable_ptr, &responseCallback);
}

The function serializeRequest() uses the CoAP-Serializer, which was mentioned in the API, to build the message. As a first step, we initialize a serializer using the CoapSerializer_setup() function. A serializer always needs the context of one specific message. In this case, the message is referenced by msg_ptr, and the type of message is a REQUEST. After initializing the serializer, we first set the code using the function CoapSerializer_setCode(). The code we use is already provided in the input variable requestCode, so we use that as the code. Also, we specify that the function does not require a confirmation by using the function CoapSerializer_setConfirmable() with false as its input. Non-Confirmable are usually faster since they produce less overhead in the messaging protocol, but they are not guaranteed to arrive at the destination.

Now it is necessary to serialize a token. Tokens are used to indicate to which request the response relates. By default, a random token will be serialized, even if we do not explicitly do so. This might lead to unexpected behavior, so we manually serialize a token. In this case, we do not specify a token, but instead tell the serializer to not insert a token. This is done by calling the function CoapSerializer_serializeToken() with a pointer to the serializer and the message pointer as input. The additional input NULL specifies the token, in this case no token is used. The last input determines the length of the token. Using 0 in combination with NULL as the token, no token will be inserted. Any value other than 0, for example 1, would create a random token of that length instead. Finally, we tell the serializer that we do not intend to serialize another option by calling the function CoapSerializer_setEndOfOptions(). Do note that the token is semantically an option, so the token should be set before this function is called.

After the options, we want to include a payload. In this case, the payload has no meaning. The payload we plan to use is only few bytes long, but in a general case, it is recommended to use a buffer for the payload. We use an uint8_t array called resource, with 30 bytes in total and copy our message into that and set the resourceLength accordingly. The content of our resource-array is now serialized as the payload, using the function CoapSerializer_serializePayload(). The function requires to know the resourceLength, as such, this variable should have a high enough value, else part of the payload will not be used.


void serializeRequest(Msg_T *msg_ptr, uint8_t requestCode, char const *payload_ptr){
  CoapSerializer_T serializer;
  CoapSerializer_setup(&serializer, msg_ptr, REQUEST);
  CoapSerializer_setCode(&serializer, msg_ptr, requestCode);
  CoapSerializer_setConfirmable(msg_ptr, false);
  CoapSerializer_serializeToken(&serializer, msg_ptr, NULL, 0);
  // call this after all options have been serialized (in this case: none)
  CoapSerializer_setEndOfOptions(&serializer, msg_ptr);
  // serialize the payload
  uint8_t resource[30] = {0};
  uint8_t resourceLength = strlen(payload_ptr);
  memcpy(resource, payload_ptr, resourceLength + 1);
  CoapSerializer_serializePayload(&serializer, msg_ptr, resource, resourceLength);
}

Finally, we need to implement the callbacks for sending the request using the function CoapClient_request(). The first callback, sendingCallback, is trivial as mentioned before. It simply returns RC_OK and suppresses warning messages concerning unused variables. The second callback, the function responseCallback(), will handle the response to our previously sent message. To parse the payload from the message, we first initialize a CoAP-Parser. The CoAP-Parser implements a few utilities, which we can use to retrieve information from a CoAP-Message. We retrieve the payload of the message using the function CoapParser_getPayload(). Since we do not use the payload in this implementation, we simply print the payload to the console. In a real-world project, the payload should be processed accordingly.


retcode_t responseCallback(CoapSession_T *coapSession, Msg_T *msg_ptr, retcode_t status){
  (void) coapSession;
  CoapParser_T parser;
  const uint8_t *payload;
  uint8_t payload_length;
  CoapParser_setup(&parser, msg_ptr);
  CoapParser_getPayload(&parser, &payload, &payload_length);
  printf("Coap Answer to Request: %s \n\r", payload);
  return status;
}
Retcode_T sendingCallback(Callable_T *callable_ptr, Retcode_T status){
  (void) callable_ptr;
  return status;
}

This concludes the client implementation, and it can be built and flashed. If the IP and port of a CoAP server is used, it will wait for a response and print the content of the response.

Server Implementation

This chapter will explain how a CoAP-Server can be set up using the XDK.

Preparation and Includes

The application will be based on an empty XdkApplicationTemplate project, which you can open from the Welcome screen of the XDK-Workbench. The code snippets have to be placed above AppControllerEnable() one after another. As a guideline, if code-snippet 2 comes after snippet 1, then snippet 2 has to be placed above snippet 1 in the source code. Using other orders might lead to compilation errors during the build process. The implementations that are presented here usually need a connection to another device. A client implementation needs a server to send requests to, and a server needs a client to answer to. Between these two roles, there has to be a working network connection.

To establish a connection to the local Wi-Fi with your XDK, please refer to the section Wi-Fi P2P. 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.

Additionally, for the code snippets in this chapter to work, the following header-files must be included.


#include "Serval_Coap.h"
#include "Serval_CoapServer.h"
#include "BCDS_NetworkConfig.h"

API Reference

In general, it is recommended to develop an application based on the highest API level the XDK framework supports. The high level API for CoAP-Servers is documented in the header file Serval_CoapServer.h. Additionally, you can find a list of the main functions we will be using for the implementation of a CoAP-Server below. The following table shows the CoAP-Server functions we will use, and some additional utility functions. For a full description, please refer to the corresponding APIs.

SERVER FUNCTIONALITY

Function Description
CoapServer_initialize() This function is called to initialize the CoAP server and related modules. It must be called before any other CoAP-Server related function.
CoapServer_startInstance() This function is called to start the CoAP server. As input, it receives a port, to which the server will listen, and a reference to a callback-function, which will be called every time the server receives a message.
CoapServer_respond() This function is called to respond to a message. As input, it receives a message, to which it responds, and a callback for informing about the status of sending.

UTILITY

Function Description
CoapParser_setup() This function sets up a CoAP-Parser which will be used to receive certain information from a CoAP-Message, such as the code, the payload or the options. As input, it receives a variable of type CoapParser_T and the pointer to a message.
CoapParser_getCode() This function is called to receive the code of a message. As input, it receives a pointer to a message.
CoapParser_getPayload() This function is called to receive the payload of a message. As input, it receives a pointer to the previously set up parser, a pointer to a payload array, and the array's length.
CoapSerializer_setup() This function sets up a CoAP-Serializer, which will be used to create a message. It handles setting options, the payload, the code, etc, for a given message. As input, it receives variable of type CoapSerializer_T, the pointer to a message, and a code indicating whether a RESPONSE or a REQUEST is created.
CoapSerializer_setCode() This function is called to set the code of a message. As input, it receives the serializer, a message and the code to be set.
CoapSerializer_serializePayload() This function is called to set the payload for a message. As input, it receives the serializer, a message pointer, the payload and the payload's length.

Implementation Outline

The following code shows the general outline of what needs to be implemented. It also shows the implementation of the AppControllerEnable() and AppControllerSetup() functions, which have to be placed at the very bottom of the source code. networkSetup() implements a simple WiFi-connection. CoapServer_initialize() and CoapServer_startInstance() are described in the previous sub-chapter API-Reference. CoapServer_initialize() will be called in AppControllerSetup() to set up the necessary modules for the CoAP server implementation.

The function CoapServer_startInstance() receives the callback function coapReceiveCallback(), which will be later introduced and implemented in the function AppControllerEnable(). The callback-function will be called whenever the message will be received. Additionally, the server's IP will be printed to the console after a brief delay.

static void AppControllerEnable(void * param1, uint32_t param2)
{
    // Previous implementations of AppControllerEnable here

    vTaskDelay(5000);
    Ip_Port_T serverPort = Ip_convertIntToPort((uint16_t)5683);
    // Port must be inserted via Ip_convertIntToPort(). Otherwise the port is not
    // recognized correctly.
    CoapServer_startInstance(serverPort,(CoapAppReqCallback_T) &coapReceiveCallback);

    NetworkConfig_IpSettings_T myIp;
    NetworkConfig_GetIpSettings(&myIp);
    vTaskDelay(5000);
    printf("The IP was retrieved: %u.%u.%u.%u \n\r",
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 3)),
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 2)),
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 1)),
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 0)));

    // Previous implementations of AppControllerEnable here
}


static void AppControllerSetup(void * param1, uint32_t param2)
{

    // Previous implementations of AppControllerSetup here

    /* Setup the necessary modules required for the application */
    networkSetup();

    CoapServer_initialize();

    // Following implementations of AppControllerSetup here
}

Implementation Details

As previously mentioned, the first thing we need to implement is the callback function, which we will name coapReceiveCallback(). As input, it receives a message pointer and an error-code and it returns an error-code. This signature should not be changed, because the function will be called internally by the stack with this given signature. After initializing an unsigned 8-bit integer, the first thing we do is parsing the message by using a function called parseCoapRequest(), which will be implemented later. Parsing means that we retrieve the content of the received message and its code.

The code of a message determines what type of message it is. It could be for example a CoAP-Post-Request or a CoAP-Get-Request. In this implementation, we only handle these two types of messages, and for each, we send a simple message which echoes the type of request. To determine what type of message it is, we simply compare the code we retrieved to the code that is specified for each of these types. The codes are stored in the array Coap_Codes and are implemented as specified by the RFC document. For sending a response, we use the function sendCoapResponse(), which will also be implemented later.


Retcode_T coapReceiveCallback(Msg_T *msg_ptr, Retcode_T status){
  uint8_t code = 0;
  parseCoapRequest(msg_ptr, &code);
  if(code == Coap_Codes[COAP_POST])
  {
    sendCoapResponse(msg_ptr, "POST received");
  }
  else if(code == Coap_Codes[COAP_GET])
  {
    sendCoapResponse(msg_ptr, "GET received");
  }
  return status;
}

To parse the code and the payload from the message, we use the function as implemented in the next code-snippet. First, we initialize a CoAP-Parser. The CoAP-Parser implements a few utilities that are used to retrieve information from a CoAP-Message. At first, we retrieve the code of the message, which determines the type of the message. Next, we retrieve the payload of the message. Since we do not use the payload in this implementation, we simply print the payload to the console. In a real-world project, the payload should be processed accordingly.


void parseCoapRequest(Msg_T *msg_ptr, uint8_t *code){
  CoapParser_T parser;
  CoapParser_setup(&parser, msg_ptr);
  *code = CoapParser_getCode(msg_ptr);
  const uint8_t* payload;
  uint8_t payloadlen;
  CoapParser_getPayload(&parser,&payload,&payloadlen);
  printf("Incoming Coap request: %s \n\r",payload);
}

Next, we implement the function that will handle building and sending a response. For this, we implement sendCoapResponse() as follows. First, the response message has to be created. To do this, we use the function createCoapResponse(), which will be implemented later. It will receive the message and will build the message using the payload we provide and the code for COAP_CONTENT.

Next, we need to define a callback-function, which should be used to monitor the status of sending a message. In our case, the callback is implemented using an empty function which only suppresses warnings for unused variables and returns RC_OK. Although it is empty, it is still required to be implemented with the exact same signature. It is also required to use Msg_defineCallback() to wrap our own callback correctly. The respond-function will use the wrapped callback. In the end, the response message will be sent using the function CoapServer_respond(). The information, where the message has to be sent to, is already included in the message itself, so it does not need to specify a target manually. The server can only respond to messages and not send its own requests.


Retcode_T sendingCallback(Callable_T *callable_ptr, Retcode_T status){
  (void) callable_ptr;
  (void) status;
  return RC_OK;
}

void sendCoapResponse(Msg_T *message, char const* payloard_ptr){
  createCoapResponse(message, payloard_ptr, Coap_Codes[COAP_CONTENT]);
  Callable_T *alpCallable_ptr = Msg_defineCallback(message,
  (CallableFunc_T) sendingCallback);
  CoapServer_respond(message, alpCallable_ptr);
}

The last and final step will be the implementation of the function createCoapResponse(). This function will use the CoAP-Serializer, which was mentioned in the API, to build the message. The message will not be built from scratch. Instead, the request, which the server received, will be used as the foundation to build the response. As a first step, we initialize a seralizer using the CoapSerializer_setup() function. A serializer always needs the context of one specific message. The message is referenced by msg_ptr, and the type of message is a RESPONSE. After initializing the serializer, we first set the code using the function CoapSerializer_setCode(). The code we use is already provided by the function createCoapResponse() in the input variable responseCode, so we use that as the code. Also, we specify that the function does not require a confirmation by using the function CoapSerializer_setConfirmable() with false as its input. Non- Confirmable are usually faster since they produce less overhead in the messaging protocol, but they are not guaranteed to arrive at the destination.

Now it is necessary to serialize a token. Tokens are used to indicate to which request the response relates. By default, a random token will be serialized, even if we do not explicitly do so. This might lead to unexpected behavior, so we manually serialize a token. In this case, we don't specify a new token, but instead reuse the token that has been included in the message that we received. This is done by calling the function CoapSerializer_reuseToken() with a pointer to the serializer and the message pointer as input. This function only works properly if the message that is used as input is the message that has been received. Finally, we tell the serializer that we do not intend to serialize another option by calling the function CoapSerializer_setEndOfOptions(). Do note that the token is semantically an option, so the token should be set before this function is called.

After the options, we want to include a payload. In this case, the payload only reflects the type of the request (GET or POST). The payload we plan to use is only few bytes long, but in a general case, it is recommended to use a buffer for the payload. We use an uint8_t array called resource, with 30 bytes in total and copy our message into that and set the resourceLength accordingly. The content of our resource-array is now serialized as the payload, using the function CoapSerializer_serializePayload(). The function requires to know the resourceLength, as such, this variable should have a high enough value, else part of the payload will not be used.


void createCoapResponse(Msg_T *msg_ptr, char const *payload_ptr, uint8_t responseCode){
  CoapSerializer_T serializer;
  CoapSerializer_setup(&serializer, msg_ptr, RESPONSE);
  CoapSerializer_setCode(&serializer, msg_ptr, responseCode);
  CoapSerializer_setConfirmable(msg_ptr, false);
  CoapSerializer_reuseToken(&serializer, msg_ptr);
  // call this after all options have been serialized
  CoapSerializer_setEndOfOptions(&serializer, msg_ptr);
  // serialize the payload
  uint8_t resource[30] = {0};
  uint8_t resourceLength = strlen(payload_ptr);
  memcpy(resource, payload_ptr, resourceLength + 1);
  CoapSerializer_serializePayload(&serializer, msg_ptr, resource, resourceLength);
}

This concludes the server implementation, and it can be built and flashed. Clients can now send requests to the IP which will be printed at runtime to the console. The server will handle requests and send responses to GET and POST requests.

Message Options

This chapter explains how options can be serialized and parsed in CoAP messages.

API Reference

The server and client implementations already shows one of the many options that can be set for a message (tokens). The options can be set for a message using the CoAP-Serializer utility and can be retrieved from a message using the CoAP-Parser utility. Options can give information such as the format of the message, the URI path of the resource that should be addressed and so forth. Server and Client can each choose for themselves, whether they want to use options or not. A message can still be processed, even while disregarding the options. The following table shows the main functions to serialize and parse options. In this guide, the options for the format of the payload and the URI for the addressed resource will be implemented. For more information on the options and how to serialize and parse them, please refer to the corresponding APIs.

Function Description
CoapSerializer_serializeOption() This function is called to set a previously defined option for a message. As input, it receives the serializer, a message pointer, and the previously defined option.
CoapSerializer_setUint16() This function is called to set a given 16-bit integer value into a given option. The bytes will be in correct order for sending. As input, it receives a pointer to the option, the value to be set in the option, and a pointer of the same type as the value that will be set.
CoapSerializer_setEndOfOptions() This function is called to insert a byte to indicate the end of options into the message. This should only be called after all the options have been set, to indicate that the following bytes represent the payload. As input, it receives the serializer and the corresponding message.
CoapParser_getOption() This function is called to receive an option from a message. As input, it receives a parser, the corresponding message, a pointer to an option structure and the number of the option that will be retrieved. Most importantly, this function will return RC_OK if the option was found and RC_COAP_END_OF_OPTIONS if the option was not found.

Serializing Options

In this chapter, two specific options will be explained and serialized.

First, we serialize the URI path to a resource. To serialize this option, we first need to instantiate a variable of type CoapOption_T. This variable will hold all the necessary information that is needed to serialize an option. The available options are stored into the predefined array Coap_Options. The specific option number for the URI path can be retrieved as Coap_Options[COAP_URI_PATH], as seen in the code snippet. This option number has to be stored in the option variable's OptionNumber field. Since we are addressing a URI path, we need to specify that path. The path string has to be stored in option variable's value field. In this code snippet, the string test is used. The first slash does not have to be specified. Using the string test will refer to the resource at "server-address:5683/test". Additionally, the length of the value has to be known. Length refers to the amount of bytes. Since a string in C consists of characters with each being the size of one byte, the string test has 4 bytes. The length has to be specified in the option variable's length field. Finally, the option is serialized by calling the function CoapSerializer_serializeOption() with the serializer, the message and the option as inputs. The following code serializes an option that will specify a URI to a resource.


// this code assumes that the Serializer has already been set up properly
// do not execute this code after CoapSerializer_setEndOfOptions has been called
CoapOption_T uriOption;
uriOption.OptionNumber = Coap_Options[COAP_URI_PATH];
uriOption.value = (uint8_t*) "test";
uriOption.length = 4;
CoapSerializer_serializeOption(&serializer, msg_ptr, &uriOption);

Next, we will serialize the format of the payload. That means that the recipient will know how the payload is supposed to be interpreted. To serialize this option, we need to instantiate a variable of type CoapOption_T again. As previously, this variable will hold all the necessary information for serialization. The specific option number for the format of the payload can be retrieved as Coap_Options[COAP_CONTENT_FORMAT], as seen in the code snippet. The option number has to be stored in the option variable's OptionNumber field. For the content format, we have to use a function we didn't require for the previously implemented option.

First, we create a variable that will hold a specific value of type uint16_t called formatValue. Then we use the function CoapSerializer_setUint16() with the option variable, the specific format Coap_ContentFormat[TEXT_PLAINCHARSET_UTF8], and the previously created variable formatValue as input. The formatValue variable will hold the output of the called function. The content of formatValue should not be changed before the option is serialized. The function setUint16 will basically format the option we specified properly into two bytes (since content formats are encoded in two bytes). This is needed in order to set the bytes into the right order for sending. As such, neither the value nor the length fields of the option need to be set manually, because setUint16 does that automatically. Finally, the option is serialized by calling the function CoapSerializer_serializeOption with the serializer, the message, and the option as inputs. The following code serializes an option that will specify the format of the payload to be plain-text UTF8 characters.


// this code assumes that the Serializer has already been set up properly
// do not execute this code after CoapSerializer_setEndOfOptions has been called
CoapOption_T formatOption;
uint16_t formatValue;
formatOption.OptionNumber = Coap_Options[COAP_CONTENT_FORMAT];
CoapSerializer_setUint16(&formatOption, Coap_ContentFormat[TEXT_PLAINCHARSET_UTF8],
&formatValue);
CoapSerializer_serializeOption(&serializer, msg_ptr, &formatOption);

Parsing Options

While a message may contain certain options, they will not be automatically interpreted by the underlying structure of the CoAP implementation on the XDK. How to interpret them has to be specified manually in the code. As a first step, they have to be retrieved from the message using the CoAP-Parser. To retrieve the format option that was serialized in chapter, a variable of type CoapOption_T has to be created first. This option variable will hold all the necessary information retrieved from a message's option. To fill the option variable, we call the function CoapParser_getOption() with the previously set up parser, the specific message, the option variable and a specific option number as input. The option number of the format can be retrieved as Coap_Options[COAP_CONTENT_FORMAT], as seen in the code snippet. Since the option was built using the function CoapSerializer_setUint16(), we now have to use the function CoapParser_getUint() to get the value properly. The function will only receive the option as input and returns the proper value from the option. We only call this function if CoapParser_getOption() returned RC_OK. Otherwise, the specific option has not been found in the message and thus not it has not been set by the sender. The following code shows how to parse the option for the content format and to print the content.


// this code assumes that the Parser has already been set up properly
CoapOption_T option;
retcode_t rc;
rc = CoapParser_getOption(&parser, msg_ptr, &option, Coap_Options[COAP_CONTENT_FORMAT]);
if(rc == RC_OK) {
  uint32_t value = CoapParser_getUint(&option);
  if(value == (uint16_t) Coap_ContentFormat[TEXT_PLAINCHARSET_UTF8]) {
    printf("Format is UTF8!\n\r");
  }
}

Copper

Copper is a Firefox Extension that provides the user with a CoAP client application for endpoint testing. The client can do basic requests, such as GET, POST, PUT, DELETE. This makes it useful for testing the server implementation in chapter 4. To install Copper, first enter  "about:addons" in the URL bar to navigate to the Firefox' add-ons menu. On the left, select Extensions (1) and then search for Copper using the search function (2) on the top right of the screen. Then press the install button (3) corresponding to Copper to install the extension.

fig2

Copper Installation

Now that the extension is installed, the extension's CoAP client interface will automatically open in the browser when CoAP server addresses are used in the URL-Bar. A CoAP server is addressed by using the URL scheme "coap://255.255.255.255:5683". Instead of the IP, a URL could be used. An example CoAP server is provided by eclipse.org. Insert "coap://californium.eclipse.org" into the URL-bar to address that server.

The picture "Copper Client Interface" provides a screenshot of the Firefox browser when connecting to the previously mentioned CoAP server. On the left, you can see the resources available on the server. Below the URL-Bar, a few messaging methods are provided. They are Ping, Discover, GET, POST, PUT, DELETE and Observe. Ping is used to check if the server is online and responding. Discover will update the list of resources available. Observe is used to tell the server that notifications for certain resources are desired (constant messages with updates concerning a resource). On the section with the title Payload, the payload of the current Incoming / Rendered / Outgoing message is shown. You can edit the payload of the message you intend to send in the Outgoing tab. The methods provided by the client are sufficient for testing simple server applications on your XDK, especially the implementation for the server in this guide.

fig3

Full Code Examples

Client

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

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

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

/* system header files */
#include <stdio.h>

/* additional interface header files */
#include "BCDS_CmdProcessor.h"
#include "FreeRTOS.h"

/* for wifi */
#include "BCDS_WlanConnect.h"
#include "BCDS_NetworkConfig.h"
#include "BCDS_ServalPal.h"
#include "BCDS_ServalPalWiFi.h"

/* for coap-client */
#include "Serval_Coap.h"
#include "Serval_CoapClient.h"
#include "Serval_Network.h"
#include "XDK_ServalPAL.h"
#include "timers.h"

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

static CmdProcessor_T * AppCmdProcessor;/**< Handle to store the main Command processor handle to be used by run-time event driven threads */
xTimerHandle coapHandle = NULL;
Ip_Address_T ip;
Ip_Port_T serverPort;

void networkSetup(void)
{
    WlanConnect_SSID_T connectSSID = (WlanConnect_SSID_T) "tek-wifi2";
    WlanConnect_PassPhrase_T connectPassPhrase = (WlanConnect_PassPhrase_T) "tek-wifi2";
    WlanConnect_Init();
    NetworkConfig_SetIpDhcp(0);
    WlanConnect_WPA(connectSSID, connectPassPhrase, NULL);
    printf("connected to WIFI network \n\r");
}

retcode_t responseCallback(CoapSession_T *coapSession, Msg_T *msg_ptr, retcode_t status)
{
    (void) coapSession;
    CoapParser_T parser;

    const uint8_t *payload;
    uint8_t payload_length;
    CoapParser_setup(&parser, msg_ptr);
    CoapParser_getPayload(&parser, &payload, &payload_length);
    printf("Coap Answer to Request: %s \n\r", payload);
    printf("responseCallback received \n\r");
    return status;
}
Retcode_T sendingCallback(Callable_T *callable_ptr, Retcode_T status)
{
    (void) callable_ptr;
    printf("sendingCallback success \n\r");
    return status;
}

void serializeRequest(Msg_T *msg_ptr, uint8_t requestCode, char const *payload_ptr)
{
    CoapSerializer_T serializer;
    CoapSerializer_setup(&serializer, msg_ptr, REQUEST);
    CoapSerializer_setCode(&serializer, msg_ptr, requestCode);
    CoapSerializer_setConfirmable(msg_ptr, false);
    CoapSerializer_serializeToken(&serializer, msg_ptr, NULL, 0);
    // call this after all options have been serialized (in this case: none)
    CoapSerializer_setEndOfOptions(&serializer, msg_ptr);
    // serialize the payload
    uint8_t resource[30] = { 0 };
    uint8_t resourceLength = strlen(payload_ptr);
    memcpy(resource, payload_ptr, resourceLength + 1);
    CoapSerializer_serializePayload(&serializer, msg_ptr, resource, resourceLength);
    printf("serializeRequest done \n\r");
}

static void sendClientRequest(void* pvParameters)
{
    BCDS_UNUSED(pvParameters);
    Msg_T *msg_ptr;
    char const *payload = "Hello Server";
    CoapClient_initReqMsg(&ip, serverPort, &msg_ptr);
    serializeRequest(msg_ptr, Coap_Codes[COAP_POST /*COAP_GET*/], payload);
    for (;; )
    {
        Callable_T *alpCallable_ptr = Msg_defineCallback(msg_ptr, (CallableFunc_T) sendingCallback);
        CoapClient_request(msg_ptr, alpCallable_ptr, &responseCallback);
        vTaskDelay(1000);
    }
}

Retcode_T coapReceiveCallback(Msg_T *msg_ptr, Retcode_T status)
{

    CoapParser_T parser;
    const uint8_t *payload;
    uint8_t payload_length;
    CoapParser_setup(&parser, msg_ptr);
    CoapParser_getPayload(&parser, &payload, &payload_length);
    printf("Coap Answer to Request: %s \n\r", payload);
    printf("coapReceiveCallback received \n\r");

    return status;
}

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

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

    if (RETCODE_OK == retcode)
    {
        retcode = ServalPAL_Enable();
    }
    vTaskDelay(5000);
    serverPort = Ip_convertIntToPort((uint16_t) 5683);
    // replace the IP-string with your target's IP
    Ip_convertStringToAddr("192.168.1.107", &ip);
    CoapClient_startInstance();
    // if no response to your request is printed, try a delay or wrap the function in a
    // timer-task here
    if (RETCODE_OK == retcode)
    {
        if (pdPASS != xTaskCreate(sendClientRequest, (const char * const ) "sendClientRequest", TASK_STACK_SIZE_APP_CONTROLLER, NULL, TASK_PRIO_APP_CONTROLLER, &coapHandle))
        {
            retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES);
        }
    }
}

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();
    if (RETCODE_OK == retcode)
    {
        retcode = ServalPAL_Setup(AppCmdProcessor);
    }

    CoapClient_initialize();

    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 */
    }
}

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

Servers

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

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

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

/* system header files */
#include <stdio.h>

/* additional interface header files */
#include "BCDS_CmdProcessor.h"
#include "FreeRTOS.h"

/* for wifi */
#include "BCDS_WlanConnect.h"
#include "BCDS_NetworkConfig.h"
#include "BCDS_ServalPal.h"
#include "BCDS_ServalPalWiFi.h"

/* for coap-server */
#include "Serval_Coap.h"
#include "Serval_CoapServer.h"

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

static CmdProcessor_T * AppCmdProcessor;

void networkSetup(void)
{

    WlanConnect_SSID_T connectSSID = (WlanConnect_SSID_T) "tek-wifi2";
    WlanConnect_PassPhrase_T connectPassPhrase =
            (WlanConnect_PassPhrase_T) "tek-wifi2";
    WlanConnect_Init();
    NetworkConfig_SetIpDhcp(0);
    WlanConnect_WPA(connectSSID, connectPassPhrase, NULL);
    printf("networkSetup success \r\n");

}

void createCoapResponse(Msg_T *msg_ptr, char const *payload_ptr, uint8_t responseCode)
{
    CoapSerializer_T serializer;
    CoapSerializer_setup(&serializer, msg_ptr, RESPONSE);
    CoapSerializer_setCode(&serializer, msg_ptr, responseCode);
    CoapSerializer_setConfirmable(msg_ptr, false);
    CoapSerializer_reuseToken(&serializer, msg_ptr);

    CoapOption_T uriOption;
    uriOption.OptionNumber = Coap_Options[COAP_URI_PATH];
    uriOption.value = (uint8_t*) "test";
    uriOption.length = 4;
    CoapSerializer_serializeOption(&serializer, msg_ptr, &uriOption);

    CoapSerializer_setEndOfOptions(&serializer, msg_ptr);
    // serialize the payload
    uint8_t resource[30] = { 0 };
    uint8_t resourceLength = strlen(payload_ptr);
    memcpy(resource, payload_ptr, resourceLength + 1);
    CoapSerializer_serializePayload(&serializer, msg_ptr, resource, resourceLength);
    printf("createCoapResponse success \r\n");

}

Retcode_T sendingCallback(Callable_T *callable_ptr, Retcode_T status)
{
    (void) callable_ptr;
    (void) status;
    printf("sendingCallback success \r\n");
    return RC_OK;
}

void sendCoapResponse(Msg_T *message, char const* payloard_ptr)
{
    createCoapResponse(message, payloard_ptr, Coap_Codes[COAP_CONTENT]);
    Callable_T *alpCallable_ptr = Msg_defineCallback(message,
            (CallableFunc_T) sendingCallback);
    CoapServer_respond(message, alpCallable_ptr);
    printf("sendCoapResponse success \r\n");

}

void parseCoapRequest(Msg_T *msg_ptr, uint8_t *code)
{
    CoapParser_T parser;
    CoapParser_setup(&parser, msg_ptr);
    *code = CoapParser_getCode(msg_ptr);
    const uint8_t* payload;
    uint8_t payloadlen;
    CoapParser_getPayload(&parser, &payload, &payloadlen);
    printf("Incoming Coap request: %s \n\r", payload);
}

Retcode_T coapReceiveCallback(Msg_T *msg_ptr, Retcode_T status)
{

    printf("Request received!\n"); //Printf to ensure that the callback function is called

    uint8_t code = 0;
    parseCoapRequest(msg_ptr, &code);
    if (code == Coap_Codes[COAP_POST])
    {
        sendCoapResponse(msg_ptr, "Hello Client, POST received");
    }
    else if (code == Coap_Codes[COAP_GET])
    {
        sendCoapResponse(msg_ptr, "Hello Client, GET Received!");
    }
    return status;
}

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

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

    vTaskDelay(5000);
    if (RETCODE_OK == retcode)
    {
        retcode = ServalPAL_Enable();
    }
    Ip_Port_T serverPort = Ip_convertIntToPort((uint16_t) 5683);

    CoapServer_startInstance(serverPort, (CoapAppReqCallback_T) &coapReceiveCallback);

    NetworkConfig_IpSettings_T myIp;
    NetworkConfig_GetIpSettings(&myIp);
    vTaskDelay(5000);
    printf("The IP was retrieved: %u.%u.%u.%u \n\r",
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 3)),
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 2)),
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 1)),
            (unsigned int) (NetworkConfig_Ipv4Byte(myIp.ipV4, 0)));
}

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();
    if (RETCODE_OK == retcode)
    {
        retcode = ServalPAL_Setup(AppCmdProcessor);
    }
    CoapServer_initialize();
    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 */
    }
}

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

Let’s stay connected

Subscribe to our newsletter to receive updates on new services, features and developer events.

Please log in to your account in order to subscribe.