nRF51 IoT SDK
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
CoAP Observe

Observable Resources

The IETF draft "draft-ietf-core-observe" defines observable resources. It follows the common observe pattern which has two roles, an observer and an observable. The observer can subscribe to listen for any change in the state of an observable subject. CoAP resources representing a value can be set up to be observable, making the observing clients notified about any change in the state of the value.

smartCoAP defines both Client and Server role by flags in sdk_config.h. If Server role is enabled, any resource in the resource hierarchy can be enabled to be an observable resource. The resource needs to allow the GET method in order to succeed in registering any observer client.

The code below shows how to set up a resource as an observable resource:

uint32_t err_code;
static coap_resource_t root;
err_code = coap_resource_create(&root, "/");
APP_ERROR_CHECK(err_code);
static coap_resource_t observable_resource;
err_code = coap_resource_create(&observable_resource, "observable");
APP_ERROR_CHECK(err_code);
observable_resource.permission = (COAP_PERM_GET | COAP_PERM_OBSERVE);
observable_resource.callback = resource_callback;
observable_resource.max_age = 15;
err_code = coap_resource_child_add(&root, &observable_resource);
APP_ERROR_CHECK(err_code);

The example shows how the observable_resource is set to be shown as an observable resource. Setting this flag will also automatically trigger with the .well_known_core to generate the ;obs link format option. The configuration of max_age is set to 15 seconds, meaning that it will signal to any observer (client) that the value is valid for 15 seconds at the maximum.

CoAP Observe Server

Server register observer

The server can register up to COAP_OBSERVE_MAX_NUM_OBSERVERS number of observers in total. The configuration structure used for registering a new instance will be copied by the coap_observe module. The configuration sets the token used when subscribing to the observable resource. This token value will be used for any notification message sent to the client. It also sets the remote address to the client as well as the content format which the client and the server have agreed upon. The content format might vary between different clients. The handle returned could be kept by the application to unregister the observer later.

Note
If the handle returned during registration is kept and the instance is deleted later, the handle number might be reused by another registration later. The handle number might be the same for the outdated handle and the newly registered observer.

The code below shows how to register a new observer to the server:

...
uint32_t handle;
coap_observer_t observer;
// Set the token length.
observer.token_len = p_request->header.token_len;
// Set the token.
memcpy(observer.token, p_request->token, observer.token_len);
// Set the remote.
memcpy(&observer.remote, &p_request->remote, sizeof(coap_remote_t));
// Set the resource of interest.
observer.p_resource_of_interest = p_resource;
// Set the content format to be used for subsequent notifications.
observer.ct = ct_to_use;
err_code = coap_observe_server_register(&handle, &observer);
...

Server unregister observer

When the server wants to unregister an observer, it uses its handle value. Either, it has kept the handle in the application or looked it up by a search.

The code below shows how to unregister an observer:

...
// handle kept by the application or retrieved by search.
uint32_t handle;
...
err_code = coap_observe_server_unregister(handle);
...

Server search for observer

If the handle values are not kept by the application, it would still be possible to retrieve the handle value by searching for it based on the remote address and the resource of interest (pointer to the resource instance).

This could be useful if the client sends an unregistration message by setting its observe option to 1.

The code below shows how to search for an observer:

coap_resource_t resource;
...
uint32_t handle;
err_code = coap_observe_server_search(&handle, &remote, &resource);
if (err_code == NRF_SUCCESS)
{
// Unregister/notify etc.
}

Server iteration observers

Sometimes it is handy to iterate through all observers that are subscribed to a given resource. The target could, for example, be to unregister observers if the resource is deleted, or to send notifications to all observers if the value changes. AS the handle would not be enough to send a notification, coap_observe_server_next_get API will return instances one by one, until there are no more observers in the list associated with the given resource.

The code below shows how to iterate through all the observers subscribed to a specific resource:

coap_resorce_t resource;
...
coap_observer_t * p_prev_observer = NULL;
coap_observer_t * p_curr_observer = NULL;
while (coap_observe_server_next_get(&p_curr_observer, p_prev_observer, &resource) != (NRF_ERROR_NOT_FOUND | IOT_COAP_ERR_BASE))
{
// Do actions using p_curr_observer;
p_prev_observer = p_curr_observer;
}

Server retrieve specific observer

Based on the handle value, it would be possible to retrieve the observer instance associated with that handle. The instance has to be retrieved by keeping it in the application. Alternatively it can be retrieved by searching for it.

The code below shows how to retrieve the observer instance of a given handle:

...
// An observer has been registered, returning the handle.
uint32_t handle;
err_code = coap_observe_server_register(&handle, &observer);
...
coap_observer_t * p_observer;
err_code = coap_observe_server_get(handle, &p_observer);
// Do desired operations with p_observer.
...

Observe Client

Client register observable

The client can register up to COAP_OBSERVE_MAX_NUM_OBSERVABLES number of observable resources in total. The configuration structure used for registering a new instance will be copied by the coap_observe module.

The configuration sets the token to be used when matching the subsequent response messages from the server. It also sets the remote address of the server. The configuration also has to set the max age, in order to figure out when the server has stopped sending notifications, so that it can safely remove the observable resource. Likewise, a notification callback has to be configured so that notifications can be given to the application. The handle returned could be kept by the application to unregister the observable resource later.

Note
If the handle returned during registration is kept and the instance is deleted later, the handle number might be reused by another registration later. The handle number might be the same for the outdated handle and the newly registered observable resource.

The code below shows how to register a new observable resource to the client:

static void app_response_handler(uint32_t err_code, void * arg, coap_message_t * p_response)
{
}
...
// Assuming an observe subscription response message has been received.
coap_message_t * p_response;
...
coap_observable_t observable;
// Set the token length.
observable.token_len = p_response->header.token_len;
// Set the token.
memcpy(observable.token, p_response->token, observable.token_len);
// Set the remote.
memcpy(&observable.remote, &p_response->remote, sizeof(coap_remote_t));
// Callback to be called upon notification.
observable.response_callback = app_response_handler;
// Update max age to the newly received message.
set_max_age(&observable, p_response);
// Register the observable.
uint32_t handle;
err_code = coap_observe_client_register(&handle, &observable);
...

Client unregister observable

When the client wants to unregister an observable resource, it uses its handle value. Either, it has kept the handle in the application or looked it up by a search.

The code below shows how to unregister an observable resource:

...
// handle kept by the application or retrieved by search.
uint32_t handle;
...
err_code = coap_observe_client_unregister(handle);
...

Client search for observable

If the handle values are not kept by the application, it would still be possible to retrieve the handle value by searching for it based on the registered token.

The code below shows how to search for an observable resource:

...
// Assuming the searched resource is based on an incoming response.
coap_remote_t * p_response;
...
uint32_t handle;
err_code = coap_observe_client_search(&handle, p_response->token, p_response->header.token_len);
if (err_code == NRF_SUCCESS)
{
// Do the desired action.
}
...

Client iteration over observables

Sometimes it is handy to iterate through all the registered observable resources. The target could, for example, be to unregister an observable resource if the server has stopped sending notifications.

The code below shows how to iterate through all the observable resources registered:

...
coap_observable_t * p_prev_observable = NULL;
coap_observable_t * p_curr_observable = NULL;
uint32_t handle;
while (coap_observe_client_next_get(&p_curr_observable, &handle, p_prev_observable) != (NRF_ERROR_NOT_FOUND | IOT_COAP_ERR_BASE))
{
// Do the desired action using p_curr_observable or the handle.
...
p_prev_observable = p_curr_observable;
}
...

Server retrieve specific observable

Based on the handle value, it would be possible to retrieve the observable resource instance associated with that handle. The resource has to be retrieved by keeping it in the application. Alternatively it can be retrieved by searching for it.

The code below shows how to retrieve the observable resource instance of a given handle:

...
// An observable resource has been registered, returning the handle.
uint32_t handle;
err_code = coap_observe_client_register(&handle, &observable);
...
coap_observable_t * p_observable;
err_code = coap_observe_server_get(handle, &p_observable);
// Do the desired operations with p_observable.
...

Server Examples

Server observer registration/unregistration example

When a client sends a GET request with observe option set to 0, the server can choose to register the client as an observer to the resource value located at the URI path defined in the request. The example below demonstrates how the server can register the observer in order to send notifications if the value changes or updating the value before max age is reached. It also shows how to handle an unregistration message from the observing client.

static uint32_t observer_sequence_num = 0;
void resource_value_get(coap_content_type_t content_type, char ** pp_str)
{
if (content_type == COAP_CT_APP_JSON)
{
// Set JSON formatted data to pp_str.
}
else
{
// Set server selected format of the data to pp_str.
}
}
static void resource_callback(coap_resource_t * p_resource, coap_message_t * p_request)
{
...
// Create a response message.
uint32_t err_code = coap_message_new(&p_response, &response_config);
...
switch (p_request->header.code)
{
case COAP_CODE_GET:
{
p_response->header.code = COAP_CODE_205_CONTENT;
// Select the first common content type between the resource and the CoAP client.
err_code = coap_message_ct_match_select(&ct_to_use, p_request, p_resource);
if (err_code != NRF_SUCCESS)
{
// None of the accepted content formats are supported in this resource endpoint.
p_response->header.code = COAP_CODE_415_UNSUPPORTED_CONTENT_FORMAT;
p_response->header.type = COAP_TYPE_RST;
}
else
{
if (coap_message_opt_present(p_request, COAP_OPT_OBSERVE) == NRF_SUCCESS)
{
// Locate the option and check the value.
uint32_t observe_option = 0;
uint8_t index;
for (index = 0; index < p_request->options_count; index++)
{
if (p_request->options[index].number == COAP_OPT_OBSERVE)
{
uint32_t err_code = coap_opt_uint_decode(&observe_option,
p_request->options[index].length,
p_request->options[index].p_data);
if (err_code != NRF_SUCCESS)
{
APP_ERROR_CHECK(err_code);
}
break;
}
}
if (observe_option == 0)
{
// Register observer, and if successful, add the Observe option in the reply.
uint32_t handle;
coap_observer_t observer;
// Set the token length.
observer.token_len = p_request->header.token_len;
// Set the resource of interest.
observer.p_resource_of_interest = p_resource;
// Set the remote.
memcpy(&observer.remote, &p_request->remote, sizeof(coap_remote_t));
// Set the token.
memcpy(observer.token, p_request->token, observer.token_len);
// Set the content format to be used for subsequent notifications.
observer.ct = ct_to_use;
err_code = coap_observe_server_register(&handle, &observer);
if (err_code == NRF_SUCCESS)
{
err_code = coap_message_opt_uint_add(p_response, COAP_OPT_OBSERVE, observer_sequence_num++);
err_code = coap_message_opt_uint_add(p_response, COAP_OPT_MAX_AGE, p_resource->expire_time);
}
// If the registration of the observer could not be done, handle this as a normal message.
}
else
{
uint32_t handle;
err_code = coap_observe_server_search(&handle, &p_request->remote, p_resource);
if (err_code == NRF_SUCCESS)
{
err_code = coap_observe_server_unregister(handle);
APP_ERROR_CHECK(err_code);
}
}
}
// Set response payload to the actual LED state.
char * response_str;
// Get resource representation in the correct format for this observer. This is only used
// as an example. How to retrieve the correct content format is up to the application.
resource_value_get(ct_to_use, &response_str);
err_code = coap_message_payload_set(p_response, response_str, strlen(response_str));
APP_ERROR_CHECK(err_code);
}
break;
}
...
// Send the response, with or without the observe option.
uint32_t msg_handle;
err_code = coap_message_send(&msg_handle, p_response);
APP_ERROR_CHECK(err_code);
err_code = coap_message_delete(p_response);
APP_ERROR_CHECK(err_code);
}

Client observer unregistration example

Registration of observable resources is done by matching the response from the server with the request message in the transmission queue internally. If it is a match, it will add the observable resource to handle all subsequent response messages with this token id. On the other hand, if the messages do not arrive within the time of max age time-out, the client will not automatically remove the observable instance. This has to be handled by the application.

The example below demonstrates how the client can iterate through the observable resource and remove the instance if time-out has been reached. The function implemented below should be called continuously every second in order to obtain an approximately correct time-out interval.

static void app_observable_time_tick(void)
{
// Check if any of the observable resources has reached the max age time-out.
coap_observable_t * p_observable = NULL;
uint32_t handle;
while (coap_observe_client_next_get(&p_observable, &handle, p_observable) != (NRF_ERROR_NOT_FOUND | IOT_COAP_ERR_BASE))
{
p_observable->max_age--;
// Max age time-out reached.
if (p_observable->max_age == 0)
{
// Unregister an observable resource.
uint32_t err_code = coap_observe_client_unregister(handle);
APP_ERROR_CHECK(err_code);
}
}
}

Client registration of an observable

The illustration in Figure 1 shows the flow of sending an observe request message from a CoAP client. The message has CoAP observe option set to 0, which indicates its interest in observing the resource defined in the message.

When sending the message with coap_message_send, a call to the coap_observe_client_send_handle function is issued. This is done to peek into the message in case of a cancellation of the subscription.

Next, the message is sent to the remote server and put into the transmission queue to be matched when the response arrives. Then the message from the application can safely be deleted, as all the needed matching parameters are in the transmission queue by now.

When the response arrives, it issues the coap_transport_read callback function in order to be processed by smartCoAP. Depending on the message type of the request message NON/CON, it will look up the queue request in the transmission queue by message id (CON) or token (NON). If the response contained an Observe option, the subscription succeeded. The observable resource will be register, and the response callback from the request will be copied to the observable resource instance for the subsequent response message using the same token.

Before removing the message from the transmission queue the response callback set by the request will be called.

msc_coap_observe_client_registration_flow
Figure 1. smartCoAP client observer request flow.

Server registration of an observer

The illustration in figure 2 shows the flow in the server when receiving an observe subscription request from a client.

The message will be processed by coap_transport_read in smartCoAP. If the resource queried is found in the resource hierarchy, it will issue a callback to the resource handler.

The resource handler function is responsible for sending a response message back to the client. It adds a sequence number in the observe option as well as a max age indicating how long the representation of the value is valid.

Last, the response is sent to the application and freed by the application.

msc_coap_observe_server_registration_flow
Figure 2. smartCoAP server observer request flow.

Notifications in the client

The illustration in figure 3 shows how NON and CON notifications are handled by the smartCoAP modules.

If the server sends a NON notification response to the client, the client will receive this in the coap_transport_read function for processing. Before any callback is done, smartCoAP will check if the server has stopped sending the observe option. If so, the observable resource will be removed automatically. In any case, the response_callback function will be looked up and issued.

If the server sends a CON notification response to the client, the client will do the same as for a NON response, but in addition it will automatically send an ACK message back to the server indicating its continuous interest in the notifications from the resource.

msc_coap_observe_client_notifications
Figure 3. smartCoAP client notification flow.

Notifications in the server

The illustration in figure 4 shows how the server loops through the list of observers in order to feed them with an updated representation of the resource value.

The server will iterate through all the observers using the coap_observe_server_next_get API function and create a new message for each client. The loop will be broken when NRF_ERROR_NOT_FOUND is returned by the function. What is shown now is how to generate the correct payload for each client. The payload has to use the content format agreed during registration. How to generate the correct content format is up to the application.

msc_coap_observe_server_notifications
Figure 4. smartCoAP server notification flow.

Client unregistration of an observable

The illustration in figure 5 shows how the client can unregister itself on the remote server. The client can actively discontinue its subscription by sending a new GET request with observe option value set to 1, or it can reply with a RESET message in reply to a NON/CON notification response.

When sending out a new GET request message with observe option value set to 1, the packet is also processed by the internal coap_observe_client_send_handle function. This function will check if the message contains an observe option. If so, check whether it is 1. If the value is 1, smartCoAP will remove the observable resource instance with the token used in the request. Then the request will be sent to the server.

msc_coap_observe_client_unregistration_flow
Figure 5. smartCoAP client unregistration.

Server unregistration of an observer

The illustration in figure 6 shows how the server will unregister its observer clients.

At least every 24 hours the notification has to be sent as a CON response message. This is done in order to confirm the client's continuous interest in the observed value. If this CON message is not acknowledged, the server will immediately remove the client from the observer list.

It could also be that the client replies with a RESET message to one of the notifications sent by the server. This will also make the server remove the observer instance from its list.

msc_coap_observe_server_unregistration_flow
Figure 6. smartCoAP server unregistration.

Configuration parameters

The following configuration parameters should be defined in sdk_config.h.

COAP_ENABLE_OBSERVE_SERVER

Enable CoAP observe server role. If enabled, the coap_observe module has to be included. It will enable the module with a table to store observers and provide access to functions to register and unregister observers. The list can be traversed in order to send notifications to the observers.

Restriction Value
Possible values 0 or 1.
Dependencies COAP_OBSERVE_MAX_NUM_OBSERVERS.

COAP_OBSERVE_MAX_NUM_OBSERVERS

Maximum number of CoAP observers that a server can have active at any point of time. The maximum number of observers to be registered by a server. For each observer added, it will increase the memory consumption of one coap_observer_t struct.

Restriction Value
Minimum value 0
Maximum value 255
Recommended value 1 - 10
Dependencies COAP_ENABLE_OBSERVE_SERVER

COAP_ENABLE_OBSERVE_CLIENT

Enable CoAP observe client role. If enabled, the coap_observe module has to be included. It will enable the module with a table to store observable resources and provide access to functions to register and unregister observable resources. The observable resources list is used to match incoming notifications to an application callback function.

Restriction Value
Possible values 0 or 1.
Dependencies COAP_OBSERVE_MAX_NUM_OBSERVABLES.

COAP_OBSERVE_MAX_NUM_OBSERVABLES

Maximum number of CoAP observable resources that a client can have active at any point of time. The maximum number of observable resources to be registered by a client. For each observable resource added, it will increase the memory consumption of one coap_observable_t struct.

Restriction Value
Minimum value 0
Maximum value 255
Recommended value 1 - 10
Dependencies COAP_ENABLE_OBSERVE_CLIENT

References