COAP_OBSERVE(3)
โจ NAME โจ
okay so this is COAP_OBSERVE(3), straight out of the libcoap Manual, and it's all about working with CoAP observe ๐
The bestie functions in this group are:
coap_resource_set_get_observablecoap_resource_notify_observerscoap_cancel_observecoap_session_set_no_observe_cancel
๐ฆ SYNOPSIS ๐ฆ
#include <coap3/coap.h>
void coap_resource_set_get_observable(coap_resource_t *resource, int mode);
int coap_resource_notify_observers(coap_resource_t *resource, const coap_string_t *query);
int coap_cancel_observe(coap_session_t *session, coap_binary_t *token,
coap_pdu_type_t message_type);
void coap_session_set_no_observe_cancel(coap_session_t *session);
For specific (D)TLS library support, link with -lcoap-3-notls, -lcoap-3-gnutls, -lcoap-3-openssl, -lcoap-3-mbedtls, -lcoap-3-wolfssl or -lcoap-3-tinydtls. Otherwise, link with -lcoap-3 to get the default (D)TLS library support.
๐ DESCRIPTION ๐
RFC7641 extends the CoAP protocol so it can monitor the state of a resource over time. Iconic behavior, honestly.
This lets clients โobserveโ resources with a defined query โ like, retrieve a representation of a resource and keep that representation updated by the server over a period of time. Obsessed with the continuity.
The server has to flag a resource as โobservableโ, and then the client has to request in a GET request that it wants to observe this resource by using the COAP_OPTION_OBSERVE Option with a value of COAP_OBSERVE_ESTABLISH. Optionally, the client can specify query options for the resource, or use a FETCH request instead of a GET to define a query (RFC8132).
To remove the โobserveโ subscription, the client has to issue a GET (or FETCH) request with the COAP_OPTION_OBSERVE Option with a value of COAP_OBSERVE_CANCEL, using the same token and other options used for making the initial โobserveโ request. Alternatively, โobserveโ can be cancelled using coap_cancel_observe() instead.
The underlying library adds in and removes โsubscribersโ to โobserveโ the resource as appropriate in the server side logic.
NOTE: COAP_RESOURCE_MAX_SUBSCRIBER may have been defined to limit the number of subscribers to a resource when libcoap was built.
Within the server application, it needs to determine that there is a change of state of the resource under observation, and then cause the CoAP library layer to initiate a โfake GET/FETCH requestโ so that an observe GET/FETCH response gets sent back to all the clients that are observing the resource. The appropriate GET/FETCH handler within the server application is called to fill in the response packet with the appropriate information. This โfake GET/FETCH requestโ is triggered by a call to coap_resource_notify_observers().
The call to coap_io_process() in the main server application i/o loop will do all the necessary processing of sending any outstanding โfake GET/FETCH requestsโ.
Whenever the server sends a copy of the state of the โobservedโ resource to the client, it will use the same token used by the client when the client requested the โobserveโ (or the last token used for a FETCH that spans multiple blocks). The client will receive this observe response in the handler defined by coap_register_response_handler(3) (with the token updated to the initial token used by the client application for a large FETCH). It is the responsibility of the client application to match the supplied token and update the appropriate internal information.
๐ฅ FUNCTIONS ๐ฅ
๐ธ Function: coap_resource_set_get_observable() ๐ธ
The coap_resource_set_get_observable() function enables or disables the observable status of the resource by the setting of mode. If mode is 1, then the resource is observable. If mode is 0, then the resource is no longer observable.
NOTE: It is not possible for the Unknown Resource, created by coap_resource_unknown_init(3), to be observable as the Uri-Path is not known when libcoap creates a โfake GET/FETCH requestโ. The Unknown Resource PUT handler must create a new resource and mark the resource as โobservableโ if a specific resource needs to be observable. The application must then manage the deletion of the resource at the appropriate time.
NOTE: The type (confirmable or non-confirmable) of the triggered observe GET response is determined not by the initial GET/FETCH request, but independently by the server as per"RFC7641 3.5. Transmission". This is controlled by the flags (one ofCOAP_RESOURCE_FLAGS_NOTIFY_NON,COAP_RESOURCE_FLAGS_NOTIFY_NON_ALWAYSorCOAP_RESOURCE_FLAGS_NOTIFY_CON) used when creating the resource usingcoap_resource_init(3).
NOTE: Furthermore, the server must send at least one โobserveโ response as confirmable, when generally sending non-confirmable, at least every 24 hours as per"RFC7641 4.5. Transmission". Libcoap automatically handles this by sending every fifth (COAP_OBS_MAX_NON) response as a confirmable response for detection that the client is still responding unlessCOAP_RESOURCE_FLAGS_NOTIFY_NON_ALWAYSis set, which is a"RFC7641 4.5. Transmission"violation, where non-confirmable โobserveโ responses are always sent as required by some higher layer protocols.
๐ Function: coap_resource_notify_observers() ๐
The coap_resource_notify_observers() function needs to be called whenever the server application determines that there has been a change to the state of the resource. The query parameter is obsolete and ignored.
๐ Function: coap_cancel_observe() ๐
The coap_cancel_observe() function can be used by the client to cancel an observe request that is being tracked. This will cause the appropriate PDU to be sent to the server to cancel the observation, based on the session and token used to set up the observe, and the PDU is of type message_type (use COAP_MESSAGE_NON or COAP_MESSAGE_CON).
๐ง Function: coap_session_set_no_observe_cancel() ๐ง
The coap_session_set_no_observe_cancel() function can be called by the client to disable calling coap_cancel_observe() when the session is being closed down / freed off. coap_cancel_observe() can still be called directly by the client application.
๐ RETURN VALUES ๐
coap_resource_notify_observers()returns0if not observable or no observers,1on success.coap_cancel_observe()returns0on failure,1on success.
๐ธ EXAMPLES ๐ธ
Okay bestie, this is the part where it all comes together. Two whole examples, both iconic, both doing the absolute most ๐
โจ Simple Time Server โจ
So the first one is a whole tiny server that serves the time and also lets people observe it (like subscribing to a TikTok account but for seconds-since-epoch). The way this works is: one generic GET handler routes stuff, a specific handler does the actual time business, resources get initialized, and then the main loop just vibes and notifies observers once a second. No cap ๐
#include <coap3/coap.h>
#include <stdio.h>
coap_resource_t *time_resource = NULL;
/* specific GET "time" handler, called from hnd_get_generic() */
static void
hnd_get_time(coap_resource_t *resource, coap_session_t *session,
const coap_pdu_t *request, const coap_string_t *query,
coap_pdu_t *response) {
unsigned char buf[40];
size_t len;
time_t now;
(void)resource;
(void)session;
/* ... Additional analysis code for resource, request pdu etc. ... */
/* After analysis, generate a suitable response */
/* Note that token, if set, is already in the response pdu */
now = time(NULL);
if (query != NULL && coap_string_equal(query, coap_make_str_const("secs"))) {
/* Output secs since Jan 1 1970 */
len = snprintf((char *)buf, sizeof(buf), "%lu", now);
} else {
/* Output human-readable time */
struct tm *tmp;
tmp = gmtime(&now);
if (!tmp) {
/* If 'now' is not valid */
coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
return;
}
len = strftime((char *)buf, sizeof(buf), "%b %d %H:%M:%S", tmp);
}
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
/*
* Invoke coap_add_data_large_response() to do all the hard work.
*
* Define the format - COAP_MEDIATYPE_TEXT_PLAIN - to add in
* Define how long this response is valid for (secs) - 1 - to add in.
* ETAG Option added internally with unique value as param set to 0
*
* OBSERVE Option added internally if needed within the function
* BLOCK2 Option added internally if output too large
* SIZE2 Option added internally
*/
coap_add_data_large_response(resource, session, request, response,
query, COAP_MEDIATYPE_TEXT_PLAIN, 1, 0,
len,
buf, NULL, NULL);
}
/* Generic GET handler */
static void
hnd_get_generic(coap_resource_t *resource, coap_session_t *session,
const coap_pdu_t *request, const coap_string_t *query,
coap_pdu_t *response) {
coap_str_const_t *uri_path = coap_resource_get_uri_path(resource);
if (!uri_path) {
/* Unexpected Failure */
coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST);
return;
}
/* Is this the "time" resource" ? */
if (coap_string_equal(uri_path, coap_make_str_const("time"))) {
hnd_get_time(resource, session, request, query, response);
return;
}
/* Other resources code */
/* Failure response */
coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST);
}
/* Initialize generic GET handler */
static void
init_resources(coap_context_t *ctx) {
coap_resource_t *r;
/* Create a resource to return return or update time */
r = coap_resource_init(coap_make_str_const("time"),
COAP_RESOURCE_FLAGS_NOTIFY_CON);
/* We are using a generic GET handler here */
coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_generic);
coap_resource_set_get_observable(r, 1);
coap_add_resource(ctx, r);
time_resource = r;
}
int
main(int argc, char *argv[]) {
coap_context_t *ctx = NULL;
coap_endpoint_t *ep = NULL;
coap_address_t addr;
unsigned wait_ms;
struct timeval tv_last = {0, 0};
/* Initialize libcoap library */
coap_startup();
/* Remove (void) definition if variable is used */
(void)argc;
(void)argv;
memset(&tv_last, 0, sizeof(tv_last));
/* Create the libcoap context */
ctx = coap_new_context(NULL);
if (!ctx) {
exit(1);
}
/* See coap_block(3) */
coap_context_set_block_mode(ctx,
COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY);
coap_address_init(&addr);
addr.addr.sa.sa_family = AF_INET;
addr.addr.sin.sin_port = ntohs(COAP_DEFAULT_PORT);
ep = coap_new_endpoint(ctx, &addr, COAP_PROTO_UDP);
/* Other Set up Code */
init_resources(ctx);
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
while (1) {
int result = coap_io_process(ctx, wait_ms);
if (result < 0) {
break;
} else if (result && (unsigned)result < wait_ms) {
/* decrement if there is a result wait time returned */
wait_ms -= result;
} else {
/*
* result == 0, or result >= wait_ms
* (wait_ms could have decremented to a small value, below
* the granularity of the timer in coap_io_process() and hence
* result == 0)
*/
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
}
if (time_resource) {
struct timeval tv_now;
if (gettimeofday(&tv_now, NULL) == 0) {
if (tv_last.tv_sec != tv_now.tv_sec) {
/* Happens once per second */
tv_last = tv_now;
coap_resource_notify_observers(time_resource, NULL);
}
/* need to wait until next second starts if wait_ms is too large */
unsigned next_sec_ms = 1000 - (tv_now.tv_usec / 1000);
if (next_sec_ms && next_sec_ms < wait_ms)
wait_ms = next_sec_ms;
}
}
}
coap_free_context(ctx);
coap_cleanup();
exit(0);
}
Like, the coap_add_data_large_response() call is genuinely doing all the heavy lifting and honestly? Respect. It handles the ETAG, the OBSERVE, the BLOCK2, the SIZE2 โ she's booked and busy ๐ซถ
๐ฆ Client Observe Request Setup ๐ฆ
Now for the client side, where we set up a request that says "hey bestie, I'd like to observe this resource, pls and thank you". Requests are usually sent confirmable, and there's a token so we can match up unsolicited or delayed responses later โ very main character of us ๐
#include <coap3/coap.h>
/* Usually, requests are sent confirmable */
static unsigned char msgtype = COAP_MESSAGE_CON;
static unsigned int token = 0;
static coap_pdu_t *
coap_new_request(coap_context_t *context, coap_session_t *session,
char request_code, coap_optlist_t **options,
unsigned char *data, size_t length, int observe) {
coap_pdu_t *pdu;
/* Remove (void) definition if variable is used */
(void)context;
/* Create the pdu with the appropriate options */
pdu = coap_pdu_init(msgtype, request_code, coap_new_message_id(session),
coap_session_max_pdu_size(session));
if (!pdu)
return NULL;
/*
* Create uniqueness token for this request for handling unsolicited /
* delayed responses
*/
token++;
if (!coap_add_token(pdu, sizeof(token), (unsigned char *)&token)) {
coap_log_debug("cannot add token to request\n");
goto error;
}
if (request_code == COAP_REQUEST_GET && observe) {
/* Indicate that we want to observe this resource */
if (!coap_insert_optlist(options,
coap_new_optlist(COAP_OPTION_OBSERVE,
COAP_OBSERVE_ESTABLISH, NULL)))
goto error;
}
/* ... Other code / options etc. ... */
/* Add in all the options (after internal sorting) to the pdu */
if (!coap_add_optlist_pdu(pdu, options))
goto error;
if (data && length) {
/* Add in the specified data */
if (!coap_add_data(pdu, length, data))
goto error;
}
return pdu;
error:
coap_delete_pdu(pdu);
return NULL;
}
Note the goto error cleanup pattern โ very demure, very mindful. Nothing leaks on her watch ๐
๐ SEE ALSO ๐
Go read these next, they're all part of the same friend group:
coap_block(3)coap_context(3)coap_handler(3)coap_init(3)coap_pdu_setup(3)coap_resource(3)coap_session(3)
๐ฅ FURTHER INFORMATION ๐ฅ
If you want the lore, bestie, go read the source material:
- "RFC7252: The Constrained Application Protocol (CoAP)"
- "RFC7641: Observing Resources in the Constrained Application Protocol (CoAP)"
- "RFC8132: PATCH and FETCH Methods for the Constrained Application Protocol (CoAP)"
for further information. That's the assigned reading, no skipping ๐ง
๐ BUGS ๐
Found a bug? Not cute, but okay, let's handle it:
- Please raise an issue on GitHub at
https://github.com/obgm/libcoap/issuesto report any bugs. - Please raise a Pull Request at
https://github.com/obgm/libcoap/pullsfor any fixes.
Like genuinely, they want your PRs. Slay ๐
๐ AUTHORS ๐
The libcoap project <libcoap-developers@lists.sourceforge.net>
That's the whole page bestie, you ate that ๐ Mwah ๐
Source iscoap_observeversion4.3.5, dated08/24/2026โ the page is literally calledCOAP_OBSERVE(3). Iconic. Periodt. โจ๐ธ
๐ฆ where this page comes from
- ubuntu stonking: libcoap3t64 4.3.5-4ubuntu1 ยท BSD-2-Clause
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms