Add CoAP Resources to the Device Application#
Using the Send status / Retrieve UDP notifications technique, the devices are automatically sending messages to the Border Router (by default, another destination is possible). In a large Wi-SUN network, this may not be convenient, or the auto_send_sec
value would be set to a very high value, such that these messages act as 'keep alive' messages, used to make sure the devices are still active and connected.
An approach more fitting for large networks consists of adding the capability for the nodes to reply with the status messages to CoAP 'get' messages. With this approach, the network management machine is responsible for regularly checking the node status, potentially infrequently. This is described in the next paragraphs.
If the devices are monitoring critical sensors, such as temperature values, they can be set to send notification messages if the monitored values pass above pre-determined thresholds, or enter critical zones. This is application-specific, out of scope of the current example application.
Add CoAP to the Project#
The demonstration code is in app_coap.c
and app_coap.h
. It uses the Wi-SUN CoAP service, available in SOFTWARE COMPONENTS > Wi-SUN > Wi-SUN Services > CoAP.
Because the demonstration application uses a lot of CoAP resources, the Maximum capacity of the CoAP Resource Table
is set to 30
in /wisun_soc_empty_stability/config/sl_wisun_coap_config.h
, instead of the default of 10
.
// <o SL_WISUN_COAP_RESOURCE_HND_MAX_RESOURCES> Maximum capacity of the CoAP Resource Table
// <i> Default: 10
#define SL_WISUN_COAP_RESOURCE_HND_MAX_RESOURCES 30U
The Resource handler service socket communication buffer size
is left to its default value of 1024
, declared as SL_WISUN_COAP_RESOURCE_HND_SOCK_BUFF_SIZE
in sl_wisun_coap_config.h
. This will limit the length of the JSON text string we can send.
// <o SL_WISUN_COAP_RESOURCE_HND_SOCK_BUFF_SIZE> Resource handler service socket communication buffer size
// <i> Default: 1024
#define SL_WISUN_COAP_RESOURCE_HND_SOCK_BUFF_SIZE 1024UL
Definition of CoAP Resources#
One of the most difficult parts of coding a CoAP application is setting correct names for resources, i.e. names that feel logical for most users, and present a consistent hierarchy for those resources.
In the demonstration application, Silicon Labs used:
'info/' for parameters that will not change for a given device:
device_tag
chip
board
'info/all' returns a json string with all of the above
'status/' for parameters varying with time and not part of the statistics
running
parent (which can change if selecting a different parent)
neighbor (access to neighbor count if no payload, otherwise adding
-e <n>
returns info for neighborn
)connected (not accumulated connection time)
'status/all' returns a json string with all of the above
'statistics/' for values accumulated by the application, or retrieved from the stack
'statistic/app/'
join_states_sec
disconnected_total
connections
connected_total
availability
'statistics/app/all' returns a json string with all of the above. Adding
-e reset
resets these statistics.
'statistics/stack/'
phy (statistics from sl-wisun-statistics-phy-t. Adding
-e reset
resets these statistics)mac (statistics from sl-wisun-statistics-mac-t. Adding
-e reset
resets these statistics)fhss (statistics from sl-wisun-statistics-fhss-t. Adding
-e reset
resets these statistics)wisun (statistics from sl-wisun-statistics-wisun-t. Adding
-e reset
resets these statistics)network (statistics from sl-wisun-statistics-network-t. Adding
-e reset
resets these statistics)regulation (statistics from sl-wisun-statistics-regulation-t. Adding
-e reset
resets these statistics)No 'all' option here, because some of the resources already return very long strings. Adding all together would overflow the json string limit.
'settings/' for parameters of the application we want to change via CoAP
auto_send (returns the current
auto_send_sec
value if no payload, otherwise adding-e <s>
setsauto_send_sec
tos
))
Declare a CoAP Callback Function for each CoAP Resource#
The application needs to know which function to execute when a CoAP get
request is received. There is one function per resource. We will only document some of them. Adding more is straightforward once the first ones are clearly explained.
CoAP Callback Prototype#
Each CoAP callback function has the same return type and parameters:
return type
sl_wisun_coap_packet_t *
, a pointer to the response packetparameter
const sl_wisun_coap_packet_t *const req_packet
, the CoAP request packet, defined insl_wisun_coap.h
as:
/**
* \brief Main CoAP message struct
*/
typedef struct sn_coap_hdr_ {
uint8_t token_len; /**< 1-8 bytes. */
sn_coap_status_e coap_status; /**< Used for telling to User special cases when parsing message */
sn_coap_msg_code_e msg_code; /**< Empty: 0; Requests: 1-31; Responses: 64-191 */
sn_coap_msg_type_e msg_type; /**< Confirmable, Non-Confirmable, Acknowledgment or Reset */
sn_coap_content_format_e content_format; /**< Set to COAP_CT_NONE if not used */
uint16_t msg_id; /**< Message ID. Parser sets parsed message ID, builder sets message ID of built coap message */
uint16_t uri_path_len; /**< 0-255 bytes. Repeatable. */
uint16_t payload_len; /**< Must be set to zero if not used */
uint8_t *token_ptr; /**< Must be set to NULL if not used */
uint8_t *uri_path_ptr; /**< Must be set to NULL if not used. E.g: temp1/temp2 */
uint8_t *payload_ptr; /**< Must be set to NULL if not used */
/* Here are not so often used Options */
sn_coap_options_list_s *options_list_ptr; /**< Must be set to NULL if not used */
} sn_coap_hdr_s;
. . .
typedef sn_coap_hdr_s sl_wisun_coap_packet_t;
The above shows that there is a possible payload, corresponding to payload_ptr
with a size of payload_len
. The code can therefore check the payload to act differently. This payload corresponds to the -e <payload>
the user can add to each CoAP request.
The settings
resource is interesting to look at, since it can be used to retrieve the auto_send_sec
value and also to change it.
CoAP Reply Function#
To more easily reply to a CoAP request with a text string as the result, the app_coap_reply()
function is added to app_coap.c
:
sl_wisun_coap_packet_t * app_coap_reply(char *response_string,
const sl_wisun_coap_packet_t *const req_packet) {
sl_wisun_coap_packet_t* resp_packet = NULL;
// Prepare CoAP response packet with default response string
resp_packet = sl_wisun_coap_build_response(req_packet, COAP_MSG_CODE_RESPONSE_BAD_REQUEST);
if (resp_packet == NULL) {
return NULL;
}
resp_packet->msg_code = COAP_MSG_CODE_RESPONSE_CONTENT;
resp_packet->content_format = COAP_CT_TEXT_PLAIN;
resp_packet->payload_ptr = (uint8_t *)response_string;
resp_packet->payload_len = (uint16_t)sl_strnlen(response_string, COAP_MAX_RESPONSE_LEN);
return resp_packet;
}
The CoAP component provides the sl_wisun_coap_build_response()
function, which can be used to prepare most of the reply. Then the payload is set to the response_string
input string.
CoAP Resource Handler#
The CoAP Resource Handler is the only part of the CoAP component you need to interact with. It will then perform the following tasks whenever a CoAP request is received:
Check that there is a known resource
uri_path
matching the request'suri_path_ptr
.If yes, call the resource's
auto_response
function.If the
auto_response
function is coded to send a reply, the reply will be sent to the IPv6 address at the origin of the request.
If the request is
".well-known/core"
, return a JSON array with all resource set asdiscoverable
settings/auto_send
Example#
The settings/auto_send
resource is interesting to look at, since it can be used to retrieve the auto_send_sec
value and also to change it.
settings/auto_send
CoAP Callback Function#
sl_wisun_coap_packet_t * coap_callback_auto_send (
const sl_wisun_coap_packet_t *const req_packet) {
int sec = 0;
int res;
if (req_packet->payload_len) {
res = sscanf((char *)req_packet->payload_ptr, "%d", &sec);
if (res) { auto_send_sec = sec; }
}
snprintf(coap_response, COAP_MAX_RESPONSE_LEN, "auto_send_sec: %d", auto_send_sec);
return app_coap_reply(coap_response, req_packet); }
The one-before-last line of the function is formatting the text string corresponding to the reply's payload (req_packet->payload_ptr
), then app_coap_reply()
is called to send the reply to the sender. The reply is in the form auto_send_sec: 60
.
Before this, the request payload has been checked, and if it contains a decimal value, this is used as the new auto_send_sec
if it exists, so the reply will always contain the current value.
settings/auto_send
CoAP Resource Registration#
To have the CoAP component handle this new resource, a part of the app_coap_resources_init()
function provides a corresponding sl_wisun_coap_rhnd_resource_t
to the CoAP Resource Handler:
uint8_t app_coap_resources_init() {
sl_wisun_coap_rhnd_resource_t coap_resource = { 0 };
uint8_t count = 0;
// Add CoAP resources (one per item)
. . .
coap_resource.data.uri_path = "/settings/auto_send";
coap_resource.data.resource_type = "sec";
coap_resource.data.interface = "settings";
coap_resource.auto_response = coap_callback_auto_send;
coap_resource.discoverable = true;
assert(sl_wisun_coap_rhnd_resource_add(&coap_resource) == SL_STATUS_OK);
count++;
. . .
printf(" %d/%d CoAP resources added to CoAP Resource handler\n", count, SL_WISUN_COAP_RESOURCE_HND_MAX_RESOURCES);
return count;
}
A check has been added at the end of app_coap_resources_init()
on the number of CoAP resources registered with the CoAP Resource Handler. This allows you to check if SL_WISUN_COAP_RESOURCE_HND_MAX_RESOURCES
is correctly set, and if it needs to be increased.
CoAP Resources Initialization by the Application#
The app_coap_resources_init()
function is called once at startup from app-init.c/app_init()
, adding all CoAP resources to the CoAP Resource Handler:
void app_init(void)
{
app_coap_resources_init();
/* Creating App main thread */
. . .
With the above, from the Border Router console, you can:
Discover the available CoAP resources on the device, using
coap-client -m get -N -B 3 coap://[<device_IPv6>]:5683/.well-known/core
Retrieve a CoAP resource using
coap-client -m get -N -B 3 coap://[<device_IPv6>]:5683/<resource>
Details about the possibilities are provided in the app_coap.c/print_coap_help()
function once connected, because at this moment the device has obtained an IPv6 address:
void print_coap_help (char* device_global_ipv6_string, char* border_router_ipv6_string) {
printf("\n");
printf("To start a CoAP server on the linux Border Router:\n");
printf(" coap-server -A %s -p %d -d 10\n", border_router_ipv6_string, 5685);
printf("CoAP discovery:\n");
printf(" coap-client -m get -N -B 3 coap://[%s]:5683/.well-known/core\n", device_global_ipv6_string);
printf("CoAP GET requests:\n");
printf(" coap-client -m get -N -B 3 coap://[%s]:5683/<resource>, for the following resources:\n", device_global_ipv6_string);
sl_wisun_coap_rhnd_print_resources();
printf(" '/settings/auto_send' returns the current notification duration in seconds\n");
printf(" '/settings/auto_send' -e <d>' changes the notification duration to d seconds\n");
printf(" '/status/neighbor' returns the neighbor_count\n");
printf(" '/status/neighbor -e <n>' returns the neighbor information for neighbor at index n\n");
printf(" '/statistics/stack/<group> -e reset' clears the Stack statistics for the selected group\n");
printf(" '/statistics/app/all -e reset' clears all statistics\n");
printf("\n");
}
status/all
Example#
The status/all
resource is used to retrieve the running
, connected
, and parent
information.
status/all
CoAP Callback Function#
sl_wisun_coap_packet_t * coap_callback_all_statuses (
const sl_wisun_coap_packet_t *const req_packet) {
#define JSON_ALL_STATUSES_FORMAT_STR \
"{\n" \
" \"running\": \"%s\",\n" \
" \"connected\": \"%s\"\n" \
" \"parent\": \"%s\",\n" \
"}\n"
char running_str[40];
char connected_str[40];
snprintf(running_str , 40, dhms(now_sec()) );
snprintf(connected_str, 40, dhms(now_sec() - connection_time_sec) );
snprintf(coap_response, COAP_MAX_RESPONSE_LEN, JSON_ALL_STATUSES_FORMAT_STR,
running_str,
connected_str,
parent_tag
);
return app_coap_reply(coap_response, req_packet);
}
status/all
CoAP Resource Registration#
To have the CoAP component handle this new resource, a part of the app_coap_resources_init()
function provides a corresponding sl_wisun_coap_rhnd_resource_t
to the CoAP Resource Handler:
uint8_t app_coap_resources_init() {
sl_wisun_coap_rhnd_resource_t coap_resource = { 0 };
uint8_t count = 0;
// Add CoAP resources (one per item)
. . .
coap_resource.data.uri_path = "/status/all";
coap_resource.data.resource_type = "json";
coap_resource.data.interface = "node";
coap_resource.auto_response = coap_callback_all_statuses;
coap_resource.discoverable = true;
assert(sl_wisun_coap_rhnd_resource_add(&coap_resource) == SL_STATUS_OK);
count++;
. . .
printf(" %d/%d CoAP resources added to CoAP Resource handler\n", count, SL_WISUN_COAP_RESOURCE_HND_MAX_RESOURCES);
return count;
}
As visible above, there are very few differences between the block of code used to register "/settings/auto_send"
or "/status/all"
, so adding more is easy.
Before looking at adding CoAP to the Border Router, below is an example of the output you can get on the Border Router when monitoring multiple devices:
status/all
in use#
./coap_all /status/all
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::62a4:23ff:fe37:a51c]:5683/status/all : { "running": " 8-05:37:43", "connected": " 0:01:49:55" "parent": "2527", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::62a4:23ff:fe37:a51d]:5683/status/all : { "running": " 0-23:00:55", "connected": " 0:22:59:53" "parent": "a901", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::62a4:23ff:fe37:a527]:5683/status/all : { "running": " 8-05:37:35", "connected": " 0:02:05:35" "parent": "333a", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::62a4:23ff:fe37:a8ff]:5683/status/all : { "running": " 8-05:37:38", "connected": " 0:21:05:04" "parent": "333a", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::62a4:23ff:fe37:a901]:5683/status/all : { "running": " 1-02:44:11", "connected": " 1:02:43:16" "parent": "333a", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::b635:22ff:fe98:2191]:5683/status/all : { "running": " 1-05:50:51", "connected": " 1:05:49:47" "parent": "333a", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::b635:22ff:fe98:2527]:5683/status/all : { "running": " 1-05:50:50", "connected": " 1:05:49:37" "parent": "a8ff", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::b635:22ff:fe98:2853]:5683/status/all : { "running": " 1-05:50:53", "connected": " 0:05:10:54" "parent": "333a", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::b6e3:f9ff:fec5:8486]:5683/status/all : { "running": " 1-05:43:48", "connected": " 0:00:47:53" "parent": "333a", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::b6e3:f9ff:fec5:8493]:5683/status/all : { "running": " 1-02:45:43", "connected": " 0:10:37:17" "parent": "2853", }
coap-client -m get -N -B 3 coap://[fd00:6172:6d00::b6e3:f9ff:fec5:8503]:5683/status/all : { "running": " 1-02:43:29", "connected": " 0:07:51:09" "parent": "333a", }
NOTE: Remember that timestamping is in
dd-hh:mm:ss
format, meaning that in the example above" 8-05:37:43
means more than 8 days of connection time, corresponding to a group of 3 devices, while others have been started only a day ago, with about 3 hours between them. This shows how interesting it is to use this time stamping format.