1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-14 17:13:50 +01:00

examples/unicoap_message: add demo of message APIs

This commit is contained in:
Carl Seifert 2025-07-07 17:07:51 +02:00
parent 0ab0a68804
commit 0db81c5de7
4 changed files with 299 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# Default Makefile, for unicoap message APIs
# name of your application
APPLICATION = unicoap_message
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../../../..
USEMODULE += unicoap
# This module is needed to get the RFC 7252 PDU parser
USEMODULE += unicoap_driver_rfc7252_pdu
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,8 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
atmega8 \
#

View File

@ -0,0 +1,14 @@
# Demo of `unicoap` Message APIs
This application provides an example of how to create messages and how to set and get option values.
The example also demonstrates how to parse and serialize CoAP messages using the RFC 7252 PDU format.
This example corresponds to the [sample code _Using Message APIs_ on doc.riot-os.org](https://doc.riot-os.org/group__net__unicoap__message__example.html).
## Running the Example
To try this example on your host, run:
```sh
BOARD=native make flash term
```
This will compile and run the application.

View File

@ -0,0 +1,252 @@
/*
* Copyright (C) 2024-2025 Carl Seifert
* Copyright (C) 2024-2025 TU Dresden
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*/
/**
* @file
* @ingroup examples
* @brief Demonstration of `unicoap` message, options, and parsing APIs
* @author Carl Seifert <carl.seifert1@mailbox.tu-dresden.de>
*/
#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include "assert.h"
#include "msg.h"
#include "modules.h"
#include "architecture.h"
#include "net/unicoap.h"
static void _example_handle_message(const unicoap_message_t* message)
{
/* Use unicoap_message_is_request, unicoap_message_is_response, unicoap_message_is_signal
* to determine what class of message you are dealing with.
* In this case, we expect a request. */
assert(unicoap_message_is_request(message->code));
/* The unicoap message type supports different typed views of the CoAP message code:
* message->method, message->status, and message->signal.
*
* Now, we want to get a string from the method: GET, PUT, POST, etc.
* unicoap_string_from_* methods generate a null-terminated constant C string
* from typed values. */
const char* method_name = unicoap_string_from_method(message->method);
/* Also possible, more general: unicoap_string_from_code(uint8_t code). */
printf("PDU is a CoAP %s request"
" with payload=<%" PRIuSIZE " bytes>\n",
method_name, message->payload_size);
/* Options ----------------- */
/* If you need to dump options quickly, use this function: */
unicoap_options_dump_all(message->options);
/* Content-Format is an option that can occur no more than once. */
unicoap_content_format_t format = 0;
if (unicoap_options_get_content_format(message->options, &format) < 0) {
puts("Error: could not read Content-Format!");
return;
}
if (format != UNICOAP_FORMAT_JSON) {
puts("Error: was expecting JSON request!");
return;
}
/* Uri-Query can occur more than once. Hence, unicoap provides multiple convenience
* accessors. */
const char* query = NULL;
/* The first getter provides a view into the PDU buffer. The returned string
* is thus not null-terminated. */
ssize_t res = unicoap_options_get_first_uri_query(message->options, &query);
if (res < 0) {
/* The getter also fails in cases where no option was found */
if (res == -ENOENT) {
puts("Message has no Uri-Query option");
}
printf("Error: could read first Uri-Query option");
}
printf("First URI query: '%.*s'\n", (int)res, query);
/* You can also get a query by name: */
res = unicoap_options_get_first_uri_query_by_name(message->options, "color", &query);
if (res < 0) {
/* The getter also fails in cases where no option was found */
if (res == -ENOENT) {
puts("Message has no 'color' query");
}
printf("Error: could read first 'color' query");
}
/* If you do want to access all values of a repeatable option,
* unicoap offers two ways:
* a, unicoap can generate a contiguous string from repeatable options: ?a=1&b=2&c=3
* b, you iterate over all options using the unicoap option iterator */
char query_string[50] = { 0 };
res = unicoap_options_copy_uri_queries(message->options, query_string, sizeof(query_string));
if (res < 0) {
puts("Error: could not generate URI query string");
}
/* - or - */
unicoap_options_iterator_t iterator;
unicoap_options_iterator_init(&iterator, message->options);
while ((res = unicoap_options_get_next_uri_query(&iterator, &query)) >= 0) {
printf("- URI query: '%.*s'\n", (int)res, query);
}
/* You can also use the option iterator to iterate over all options */
unicoap_options_iterator_init(&iterator, message->options);
unicoap_option_number_t number;
const uint8_t* value = NULL;
while ((res = unicoap_options_get_next(&iterator, &number, &value)) >= 0) {
const char* name = unicoap_string_from_option_number(number);
printf("- option %s nr=%i contains %" PRIuSIZE " bytes\n", name, number, res);
}
}
static void _example_parse_pdu(void)
{
ssize_t res = 0;
/*
{
"type": "Confirmable",
"code": "POST",
"id": 65201,
"token": 0,
"options": [
"Uri-Path: actuators",
"Uri-Path: leds",
"Content-Format: application/json",
"Uri-Query: color=g",
"Accept: application/json"
]
}
*/
const uint8_t pdu[] = {
0x40, 0x02, 0xfe, 0xb1, 0xb9, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x04,
0x6c, 0x65, 0x64, 0x73, 0x11, 0x32, 0x37, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3d, 0x67, 0x21,
0x32, 0xff, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x6f, 0x6e
};
/* Allocate a helper structure */
unicoap_parser_result_t parsed = { 0 };
/* Parse using helper function, _result version initializes options structure for us. */
if ((res = unicoap_pdu_parse_rfc7252_result((uint8_t*)pdu, sizeof(pdu), &parsed)) < 0) {
puts("Error: parsing failed");
return;
}
/* To read the header, use the message properties: */
printf("CoAP message has token=<%i bytes>\n", parsed.properties.token_length);
/* If the message was sent over CoAP over UDP or DTLS, it has the
* RFC 7252 CoAP header. This is how you access these properties: */
printf("CoAP over UDP/DTLS has id=%i type=%s\n",
parsed.properties.rfc7252.id,
unicoap_string_from_rfc7252_type(parsed.properties.rfc7252.type));
_example_handle_message(&parsed.message);
}
static void _example_serialize_message(const unicoap_message_t* message)
{
/* Well, first we need a buffer. */
uint8_t pdu[200];
/* The header format varies depending on the transport. Let's use
* CoAP over UDP or CoAP over DTLS, i.e., the RFC 7252 format.
* Also, let's not use a token (very constrained nodes may only handle a request at a time
* and thus use an empty token value). */
unicoap_message_properties_t properties = {
.token = NULL,
.token_length = 0,
.rfc7252 = {
.id = 0xABCD,
.type = UNICOAP_TYPE_NON
}
};
ssize_t res = unicoap_pdu_build_rfc7252(pdu, sizeof(pdu), message, &properties);
if (res < 0) {
if (res == -ENOBUFS) {
puts("Error: PDU buffer too small");
}
puts("Error: could not serialize message");
return;
}
printf("The final PDU has a size of %" PRIuSIZE " bytes.\n", res);
}
static void _example_create_message(void)
{
/* If you want options, you need to allocate an options object and provide
* a capacity for the options buffer. The object is stack-allocated. The
* macro just saves you the boilerplate of allocating a buffer and initializing
* the options struct. */
UNICOAP_OPTIONS_ALLOC(options, 100);
const char payload[] = "Dear thermostat, please adjust your target temperature to 22.5 °C";
unicoap_message_t message;
unicoap_request_init_string_with_options(&message, UNICOAP_METHOD_POST, payload, &options);
/* Options ----------------- */
/* To set the path, use the following method. There are two versions for null-terminated
* strings and for ones that aren't. */
int res = unicoap_options_add_uri_path_string(&options, "/thermostat/temperature");
if (res < 0) {
if (res == -ENOBUFS) {
puts("Error: options buffer too small");
}
puts("Error: could not add URI path");
}
/* Repeatable options like Uri-Path can also be added individually.
* This results in /thermostat/temperature/target */
res = unicoap_options_add_uri_path_component_string(&options, "target");
if (res < 0) {
puts("Error: could not add path component");
}
/* Uri-Query is a repeatable option, and can thus also be added individually. */
res = unicoap_options_add_uri_queries_string(&options, "unit=C&cool=yes");
if (res < 0) {
puts("Error: could not add URI query");
}
/* Simple options like Content-Format can be set as follows: */
res = unicoap_options_set_content_format(&options, UNICOAP_FORMAT_TEXT);
if (res < 0) {
puts("Error: could not set Content-Format");
}
/* Let's see if everything has been added: */
unicoap_options_dump_all(&options);
_example_serialize_message(&message);
}
int main(void)
{
puts("Parsing a sample message =============");
_example_parse_pdu();
puts("Creating a sample message ============");
_example_create_message();
}