blob: 1666880f2fc26f0ff580ab7cd56920e1e2857955 [file] [log] [blame]
.. _coap_server_interface:
CoAP server
###########
.. contents::
:local:
:depth: 2
Overview
********
Zephyr comes with a batteries-included CoAP server, which uses services to listen for CoAP
requests. The CoAP services handle communication over sockets and pass requests to registered
CoAP resources.
Setup
*****
Some configuration is required to make sure services can be started using the CoAP server. The
:kconfig:option:`CONFIG_COAP_SERVER` option should be enabled in your project:
.. code-block:: cfg
:caption: ``prj.conf``
CONFIG_COAP_SERVER=y
All services are added to a predefined linker section and all resources for each service also get
their respective linker sections. If you would have a service ``my_service`` it has to be
prefixed with ``coap_resource_`` and added to a linker file:
.. code-block:: c
:caption: ``sections-ram.ld``
#include <zephyr/linker/iterable_sections.h>
ITERABLE_SECTION_RAM(coap_resource_my_service, 4)
Add this linker file to your application using CMake:
.. code-block:: cmake
:caption: ``CMakeLists.txt``
zephyr_linker_sources(DATA_SECTIONS sections-ram.ld)
You can now define your service as part of the application:
.. code-block:: c
#include <zephyr/net/coap_service.h>
static const uint16_t my_service_port = 5683;
COAP_SERVICE_DEFINE(my_service, "0.0.0.0", &my_service_port, COAP_SERVICE_AUTOSTART);
.. note::
Services defined with the ``COAP_SERVICE_AUTOSTART`` flag will be started together with the CoAP
server thread. Services can be manually started and stopped with ``coap_service_start`` and
``coap_service_stop`` respectively.
Sample Usage
************
The following is an example of a CoAP resource registered with our service:
.. code-block:: c
#include <zephyr/net/coap_service.h>
static int my_get(struct coap_resource *resource, struct coap_packet *request,
struct sockaddr *addr, socklen_t addr_len)
{
static const char *msg = "Hello, world!";
uint8_t data[CONFIG_COAP_SERVER_MESSAGE_SIZE];
struct coap_packet response;
uint16_t id;
uint8_t token[COAP_TOKEN_MAX_LEN];
uint8_t tkl, type;
type = coap_header_get_type(request);
id = coap_header_get_id(request);
tkl = coap_header_get_token(request, token);
/* Determine response type */
type = (type == COAP_TYPE_CON) ? COAP_TYPE_ACK : COAP_TYPE_NON_CON;
coap_packet_init(&response, data, sizeof(data), COAP_VERSION_1, type, tkl, token,
COAP_RESPONSE_CODE_CONTENT, id);
/* Set content format */
coap_append_option_int(&response, COAP_OPTION_CONTENT_FORMAT,
COAP_CONTENT_FORMAT_TEXT_PLAIN);
/* Append payload */
coap_packet_append_payload_marker(&response);
coap_packet_append_payload(&response, (uint8_t *)msg, sizeof(msg));
/* Send to response back to the client */
return coap_resource_send(resource, &response, addr, addr_len, NULL);
}
static int my_put(struct coap_resource *resource, struct coap_packet *request,
struct sockaddr *addr, socklen_t addr_len)
{
/* ... Handle the incoming request ... */
/* Return a CoAP response code as a shortcut for an empty ACK message */
return COAP_RESPONSE_CODE_CHANGED;
}
static const char * const my_resource_path[] = { "test", NULL };
COAP_RESOURCE_DEFINE(my_resource, my_service, {
.path = my_resource_path,
.get = my_get,
.put = my_put,
});
.. note::
As demonstrated in the example above, a CoAP resource handler can return response codes to let
the server respond with an empty ACK response.
Observable resources
********************
The CoAP server provides logic for parsing observe requests and stores these using the runtime data
of CoAP services. An example using a temperature sensor can look like:
.. code-block:: c
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/net/coap_service.h>
static void notify_observers(struct k_work *work);
K_WORK_DELAYABLE_DEFINE(temp_work, notify_observers);
static int send_temperature(struct coap_resource *resource,
const struct sockaddr *addr, socklen_t addr_len,
uint16_t age, uint16_t id, const uint8_t *token, uint8_t tkl,
bool is_response)
{
const struct device *dev = DEVICE_DT_GET(DT_ALIAS(ambient_temp0));
uint8_t data[CONFIG_COAP_SERVER_MESSAGE_SIZE];
struct coap_packet response;
char payload[14];
struct sensor_value value;
double temp;
uint8_t type;
/* Determine response type */
type = is_response ? COAP_TYPE_ACK : COAP_TYPE_CON;
if (!is_response) {
id = coap_next_id();
}
coap_packet_init(&response, data, sizeof(data), COAP_VERSION_1, type, tkl, token,
COAP_RESPONSE_CODE_CONTENT, id);
if (age >= 2U) {
coap_append_option_int(&response, COAP_OPTION_OBSERVE, age);
}
/* Set content format */
coap_append_option_int(&response, COAP_OPTION_CONTENT_FORMAT,
COAP_CONTENT_FORMAT_TEXT_PLAIN);
/* Get the sensor date */
sensor_sample_fetch_chan(dev, SENSOR_CHAN_AMBIENT_TEMP);
sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &value);
temp = sensor_value_to_double(&value);
snprintk(payload, sizeof(payload), "%0.2f°C", temp);
/* Append payload */
coap_packet_append_payload_marker(&response);
coap_packet_append_payload(&response, (uint8_t *)payload, strlen(payload));
return coap_resource_send(resource, &response, addr, addr_len, NULL);
}
static int temp_get(struct coap_resource *resource, struct coap_packet *request,
struct sockaddr *addr, socklen_t addr_len)
{
uint8_t token[COAP_TOKEN_MAX_LEN];
uint16_t id;
uint8_t tkl;
int r;
/* Let the CoAP server parse the request and add/remove observers if needed */
r = coap_resource_parse_observe(resource, request, addr);
id = coap_header_get_id(request);
tkl = coap_header_get_token(request, token);
return send_temperature(resource, addr, addr_len, r == 0 ? resource->age : 0,
id, token, tkl, true);
}
static void temp_notify(struct coap_resource *resource, struct coap_observer *observer)
{
send_temperature(resource, &observer->addr, sizeof(observer->addr), resource->age, 0,
observer->token, observer->tkl, false);
}
static const char * const temp_resource_path[] = { "sensors", "temp1", NULL };
COAP_RESOURCE_DEFINE(temp_resource, my_service, {
.path = temp_resource_path,
.get = temp_get,
.notify = temp_notify,
});
static void notify_observers(struct k_work *work)
{
if (sys_slist_is_empty(&temp_resource.observers)) {
return;
}
coap_resource_notify(&temp_resource);
k_work_reschedule(&temp_work, K_SECONDS(1));
}
CoAP Events
***********
By enabling :kconfig:option:`CONFIG_NET_MGMT_EVENT` the user can register for CoAP events. The
following example simply prints when an event occurs.
.. code-block:: c
#include <zephyr/sys/printk.h>
#include <zephyr/net/coap_mgmt.h>
#include <zephyr/net/coap_service.h>
#define COAP_EVENTS_SET (NET_EVENT_COAP_OBSERVER_ADDED | NET_EVENT_COAP_OBSERVER_REMOVED | \
NET_EVENT_COAP_SERVICE_STARTED | NET_EVENT_COAP_SERVICE_STOPPED)
void coap_event_handler(uint32_t mgmt_event, struct net_if *iface,
void *info, size_t info_length, void *user_data)
{
switch (mgmt_event) {
case NET_EVENT_COAP_OBSERVER_ADDED:
printk("CoAP observer added");
break;
case NET_EVENT_COAP_OBSERVER_REMOVED:
printk("CoAP observer removed");
break;
case NET_EVENT_COAP_SERVICE_STARTED:
if (info != NULL && info_length == sizeof(struct net_event_coap_service)) {
struct net_event_coap_service *net_event = info;
printk("CoAP service %s started", net_event->service->name);
} else {
printk("CoAP service started");
}
break;
case NET_EVENT_COAP_SERVICE_STOPPED:
if (info != NULL && info_length == sizeof(struct net_event_coap_service)) {
struct net_event_coap_service *net_event = info;
printk("CoAP service %s stopped", net_event->service->name);
} else {
printk("CoAP service stopped");
}
break;
}
}
NET_MGMT_REGISTER_EVENT_HANDLER(coap_events, COAP_EVENTS_SET, coap_event_handler, NULL);
CoRE Link Format
****************
The :kconfig:option:`CONFIG_COAP_SERVER_WELL_KNOWN_CORE` option enables handling the
``.well-known/core`` GET requests by the server. This allows clients to get a list of hypermedia
links to other resources hosted in that server.
API Reference
*************
.. doxygengroup:: coap_service
.. doxygengroup:: coap_mgmt