Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions doc/connectivity/networking/api/coap_client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,52 @@ RFC7959 Figure 3: Block-Wise GET with Early Negotiation).
ret = coap_client_req(&client, sock, &address, &req, -1);
Optionally, the application can register a payload callback instead of providing a payload pointer
for the CoAP upload. In such cases, the CoAP client library will call this callback when preparing
a PUT/POST request, so that the application can provide the payload in blocks, instead of having to
provide a single contiguous buffer with the entire payload. An example callback, providing the
content of the Lorem Ipsum string can look like this:

.. code-block:: c
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate to whine about this, but I dislike the fact that our code samples in the documentation do not adhere to our code style (tabs vs spaces). And it's an annoyance to do it properly, because rst file uses spaces for indentation, while our c code uses tabs.

I worked around this by using literalinclude:: directives instead of inline code, but that isn't ideal either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but that probably would need to be brought to a broader audience, it's everywhere like this from what I see, it's not only this file.

static int lorem_ipsum_cb(size_t offset, const uint8_t **payload, size_t *len,
bool *last_block, void *user_data)
{
size_t data_left;
if (offset > LOREM_IPSUM_STRLEN) {
return -EINVAL;
}
*payload = LOREM_IPSUM + offset;
data_left = LOREM_IPSUM_STRLEN - offset;
if (data_left <= *len) {
*len = data_left;
*last_block = true;
} else {
*last_block = false;
}
return 0;
}
The callback can then be registered for the PUT/POST request instead of a payload pointer:

.. code-block:: c
struct coap_client_request req = { 0 };
req.method = COAP_METHOD_PUT;
req.confirmable = true;
req.path = "lorem-ipsum";
req.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN;
req.cb = response_cb;
req.payload_cb = lore_ipsum_cb,
ret = coap_client_req(&client, sock, &address, &req, -1);
API Reference
*************

Expand Down
1 change: 1 addition & 0 deletions doc/releases/release-notes-4.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ New APIs and options
* CoAP

* :c:struct:`coap_client_response_data`
* :c:member:`coap_client_request.payload_cb`

* Sockets

Expand Down
32 changes: 32 additions & 0 deletions include/zephyr/net/coap_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,37 @@ struct coap_client_response_data {
typedef void (*coap_client_response_cb_t)(const struct coap_client_response_data *data,
void *user_data);

/**
* @typedef coap_client_payload_cb_t
* @brief Callback for providing a payload for the CoAP request.
*
* An optional callback for providing a payload for CoAP client requests. If set in
* @ref coap_client_request, the CoAP client library will call this callback when
* preparing a PUT/POST request (note that this is also true for retransmissions).
*
* When called, the library provides the application with the current payload offset
* for the transfer and the payload block size. In return, the application sets the
* payload pointer, payload size and information whether more data blocks are expected.
* Setting the @p last_block parameter to false on the initial callback call triggers
* a block transfer upload. The library will keep calling the callback until the
* @p last_block parameter is set to false.
*
* @note If block transfer is used, the application is expected to provide full blocks of
* payload. Only the final block (i.e. when @p last_block is set to true) can be shorter
* than the requested block size.
*
* @param offset Payload offset from the beginning of a blockwise transfer.
* @param payload A pointer for the buffer containing the payload block.
* @param len Requested (maximum) block size on input. The actual payload length on output.
* @param last_block A pointer to the flag indicating whether more payload blocks are expected.
* @param user_data User provided context.
*
* @return Zero on success, a negative error code to abort upload.
*/
typedef int (*coap_client_payload_cb_t)(size_t offset, const uint8_t **payload,
size_t *len, bool *last_block,
void *user_data);

/**
* @brief Representation of a CoAP client request.
*/
Expand All @@ -78,6 +109,7 @@ struct coap_client_request {
enum coap_content_format fmt; /**< Content format to be used */
const uint8_t *payload; /**< User allocated buffer for send request */
size_t len; /**< Length of the payload */
coap_client_payload_cb_t payload_cb; /**< Optional payload callback */
coap_client_response_cb_t cb; /**< Callback when response received */
const struct coap_client_option *options; /**< Extra options to be added to request */
uint8_t num_options; /**< Number of extra options */
Expand Down
12 changes: 12 additions & 0 deletions samples/net/sockets/coap_upload/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(coap_upload)

target_sources(app PRIVATE
src/main.c
)

include(${ZEPHYR_BASE}/samples/net/common/common.cmake)
12 changes: 12 additions & 0 deletions samples/net/sockets/coap_upload/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (c) 2025 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

mainmenu "CoAP Upload Sample Application"

config NET_SAMPLE_COAP_SERVER_PORT
int "CoAP server port"
default 5683
help
CoAP server port that the application should send requests to.

source "Kconfig.zephyr"
119 changes: 119 additions & 0 deletions samples/net/sockets/coap_upload/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
.. zephyr:code-sample:: coap-upload
:name: CoAP upload
:relevant-api: coap

Use the CoAP client API to upload data via a PUT request

Overview
********

This sample demonstrates the use of the CoAP client API to upload a content to a
CoAP server with PUT requests. The sample showcases various methods of uploading
a content:

* Short upload with payload pointer,
* Short upload with payload callback,
* Block transfer upload with payload pointer,
* Block transfer upload with payload callback.

The sample is compatible with the :zephyr:code-sample:`coap-server` sample, or
can run against a standalone CoAP server, for example `aiocoap`_.

Requirements
************
- :ref:`networking_with_host`, :ref:`networking_with_native_sim`
- or, a board with hardware networking (tested on nrf7002dk board with ``wifi-ipv4`` snippet)
- Network connection between the board and a host running a CoAP server

Build and Running
*****************
Build the CoAP upload sample application like this:

.. zephyr-app-commands::
:zephyr-app: samples/net/sockets/coap_upload
:board: <board to use>
:goals: build
:compact:

The easiest way to run this sample application is to build and run it as a
native_sim application. Some setup is required as described in
:ref:`networking_with_native_sim`.

Download a CoAP server application, for example `aiocoap`_ (Python).

Using ``aiocoap``:

.. code-block:: bash

python -m pip install "aiocoap[all]"
mkdir file_root
aiocoap-fileserver --write file_root

Launch :command:`net-setup.sh` in net-tools:

.. code-block:: bash

./net-setup.sh

Build and run the CoAP upload sample application for native_sim like this:

.. zephyr-app-commands::
:zephyr-app: samples/net/sockets/coap_upload
:host-os: unix
:board: native_sim
:goals: run
:compact:

Sample output
=============

.. code-block:: console

[00:00:00.000,000] <inf> net_config: Initializing network
[00:00:00.000,000] <inf> net_config: IPv4 address: 192.0.2.1
[00:00:00.110,000] <inf> net_config: IPv6 address: 2001:db8::1
[00:00:00.110,000] <inf> net_config: IPv6 address: 2001:db8::1
[00:00:00.110,000] <inf> net_samples_common: Network connectivity established and IP address assigned
[00:00:00.110,000] <inf> net_samples_common: Waiting for network...
[00:00:00.110,000] <inf> coap_upload:
[00:00:00.110,000] <inf> coap_upload: * Starting CoAP upload using IPv4
[00:00:00.110,000] <inf> coap_upload:
[00:00:00.110,000] <inf> coap_upload: ** CoAP upload short
[00:00:00.180,000] <inf> coap_upload: CoAP upload short done in 70 ms
[00:00:00.180,000] <inf> coap_upload:
[00:00:00.180,000] <inf> coap_upload: ** CoAP upload short with callback
[00:00:00.240,000] <inf> coap_upload: CoAP upload short with callback done in 60 ms
[00:00:00.240,000] <inf> coap_upload:
[00:00:00.240,000] <inf> coap_upload: ** CoAP upload blockwise
[00:00:00.300,000] <inf> coap_upload: CoAP upload blockwise ongoing, sent 128 bytes so far
[00:00:00.360,000] <inf> coap_upload: CoAP upload blockwise ongoing, sent 256 bytes so far
[00:00:00.420,000] <inf> coap_upload: CoAP upload blockwise ongoing, sent 384 bytes so far
[00:00:00.480,000] <inf> coap_upload: CoAP upload blockwise done in 240 ms
[00:00:00.480,000] <inf> coap_upload:
[00:00:00.480,000] <inf> coap_upload: ** CoAP upload blockwise with callback
[00:00:00.540,000] <inf> coap_upload: CoAP upload blockwise with callback ongoing, sent 128 bytes so far
[00:00:00.600,000] <inf> coap_upload: CoAP upload blockwise with callback ongoing, sent 256 bytes so far
[00:00:00.660,000] <inf> coap_upload: CoAP upload blockwise with callback ongoing, sent 384 bytes so far
[00:00:00.720,000] <inf> coap_upload: CoAP upload blockwise with callback done in 240 ms
[00:00:01.230,000] <inf> coap_upload:
[00:00:01.230,000] <inf> coap_upload: * Starting CoAP upload using IPv6
[00:00:01.230,000] <inf> coap_upload:
[00:00:01.230,000] <inf> coap_upload: ** CoAP upload short
[00:00:01.320,000] <inf> coap_upload: CoAP upload short done in 90 ms
[00:00:01.320,000] <inf> coap_upload:
[00:00:01.320,000] <inf> coap_upload: ** CoAP upload short with callback
[00:00:01.380,000] <inf> coap_upload: CoAP upload short with callback done in 60 ms
[00:00:01.380,000] <inf> coap_upload:
[00:00:01.380,000] <inf> coap_upload: ** CoAP upload blockwise
[00:00:01.440,000] <inf> coap_upload: CoAP upload blockwise ongoing, sent 128 bytes so far
[00:00:01.500,000] <inf> coap_upload: CoAP upload blockwise ongoing, sent 256 bytes so far
[00:00:01.560,000] <inf> coap_upload: CoAP upload blockwise ongoing, sent 384 bytes so far
[00:00:01.620,000] <inf> coap_upload: CoAP upload blockwise done in 240 ms
[00:00:01.620,000] <inf> coap_upload:
[00:00:01.620,000] <inf> coap_upload: ** CoAP upload blockwise with callback
[00:00:01.680,000] <inf> coap_upload: CoAP upload blockwise with callback ongoing, sent 128 bytes so far
[00:00:01.740,000] <inf> coap_upload: CoAP upload blockwise with callback ongoing, sent 256 bytes so far
[00:00:01.800,000] <inf> coap_upload: CoAP upload blockwise with callback ongoing, sent 384 bytes so far
[00:00:01.860,000] <inf> coap_upload: CoAP upload blockwise with callback done in 240 ms

.. _aiocoap: https://github.com/chrysn/aiocoap
29 changes: 29 additions & 0 deletions samples/net/sockets/coap_upload/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Networking
CONFIG_NETWORKING=y
CONFIG_NET_UDP=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_CONNECTION_MANAGER=y

# Logging
CONFIG_LOG=y
CONFIG_NET_LOG=y

# Kernel options
CONFIG_ENTROPY_GENERATOR=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_MAIN_STACK_SIZE=2048

# CoAP
CONFIG_COAP=y
CONFIG_COAP_CLIENT=y
CONFIG_COAP_CLIENT_BLOCK_SIZE=128
CONFIG_COAP_CLIENT_STACK_SIZE=2048

# Network application options and configuration
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
14 changes: 14 additions & 0 deletions samples/net/sockets/coap_upload/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
sample:
description: CoAP client upload example
name: socket_coap_upload
common:
harness: net
depends_on: netif
tags:
- net
- coap
tests:
sample.net.sockets.coap_upload:
platform_allow:
- native_sim
- qemu_x86
Loading