samples: net: openthread: Add a sample using OpenThread CoAP API

In order to show how to use OpenThread and CoAP, add an application.
This example could be build to run a client or server.
The server could expose LEDs and buttons and client could get their
state and set the LEDs state.

The network is created automatically using the network key predefined
in the config. The goal is to make the example simple by removing
the commisionning and joinning process.

If a client application has a button, it could use it to toggle the first
LED on any boards running the server application.

Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
This commit is contained in:
Alexandre Bailon 2024-11-02 18:23:23 +01:00 committed by Benjamin Cabé
parent 0e743abb1d
commit dc898b47c8
13 changed files with 1121 additions and 0 deletions

View file

@ -13,4 +13,5 @@ supported:
- spi - spi
- watchdog - watchdog
- hwinfo - hwinfo
- netif:openthread
vendor: ti vendor: ti

View file

@ -0,0 +1,20 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ot_coap)
target_include_directories(app PRIVATE src)
target_sources(app PRIVATE
src/main.c
src/coap_utils.c
)
target_sources_ifdef(CONFIG_OT_COAP_SAMPLE_LED app PRIVATE src/led.c)
target_sources_ifdef(CONFIG_OT_COAP_SAMPLE_SW app PRIVATE src/button.c)
include(${ZEPHYR_BASE}/samples/net/common/common.cmake)
set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/)

View file

@ -0,0 +1,23 @@
# Config options for OpenThread Border CoAP sample app
# Copyright (c) 2024 Alexandre Bailon
# SPDX-License-Identifier: Apache-2.0
mainmenu "OpenThread CoAP Sample"
menu "Application configuration"
config OT_COAP_SAMPLE_SERVER
bool "Build the sample CoAP server application"
config OT_COAP_SAMPLE_LED
bool "Enable LED support"
default y
config OT_COAP_SAMPLE_SW
bool "Enable switch support"
default y
endmenu
source "Kconfig.zephyr"

View file

@ -0,0 +1,178 @@
:orphan:
.. zephyr:code-sample:: ot-coap
:name: OpenThread CoAP client and server application
:relevant-api: openthread
Build a Full Thread Device (FTD) CoAP server and client.
Overview
********
This sample demonstrates how to use OpenThread CoAP API.
It can be built to work as a server or as a client.
By running a client and server on two boards, a local Thread network can be created.
To create the network, OpenThread uses the network key provided with Kconfig.
Once the boards have been flashed, the network will be
automatically created and configured.
Once the network is operational, then the client could start interacting with
the server.
Every time the user presses the button, the LED on server should toggle.
The source code for this sample application can be found at:
:zephyr_file:`samples/net/openthread/coap`.
.. note::
This sample uses the OpenThread CoAP API whereas Zephyr has its own CoAP API.
So, why are we using the OpenThread CoAP API here ?
* OpenThread uses it internaly to implement many of its services.
* OpenThread CoAP API has a more direct access to radio.
So by using OpenThread CoAP API instead of Zephyr one,
we could expect less overhead although this makes the application less portable.
Building and Running
********************
Build the OpenThread FTD CoAP server sample application like this:
.. zephyr-app-commands::
:zephyr-app: samples/net/openthread/coap
:board: <board to use>
:west-args: -T sample.net.openthread.ftd.coap.server
:goals: build
:compact:
Build the OpenThread FTD CoAP client sample application like this:
.. zephyr-app-commands::
:zephyr-app: samples/net/openthread/coap
:board: <board to use>
:west-args: -T sample.net.openthread.ftd.coap.client
:goals: build
:compact:
Example building CoAP server for the cc1352p7 launchpad:
.. zephyr-app-commands::
:zephyr-app: samples/net/openthread/coap
:host-os: unix
:board: cc1352p7_lp
:west-args: -T sample.net.openthread.ftd.coap.server
:goals: build flash
:compact:
Example building CoAP client for the cc1352p7 launchpad:
.. zephyr-app-commands::
:zephyr-app: samples/net/openthread/coap
:host-os: unix
:board: cc1352p7_lp
:west-args: -T sample.net.openthread.ftd.coap.client
:goals: build flash
:compact:
Checking Thread network state
*****************************
Open a console on both server and client boards then check the sate:
.. code-block::
server:~$ ot state
router
Done
A valid state could be child, router or leader.
Once Thread network is operational, you could start using client.
Controlling server board's LED using a button on client board
*************************************************************
There is nothing to do once Thread network is operational.
Just press the button sw0 on the client and you should see led0 toggling.
The client uses a broadcast address to request CoAP server to toggle the LED.
It does not know the address of the server so if there is a second server
on the network, then the LED of the second board will toggle too.
Controlling server board's LED from a computer
**********************************************
Although we use OpenThread CoAP API, we could interact with any CoAP client
or server available on network. In this example, we are going to control the
LED from a computer that is not in the Thread network.
This requires an `OpenThread Border Router`_ with NAT64 support enabled on the same network.
First, check that the server (or the client) is connected to the otbr and
can use NAT64:
.. code-block::
server:~$ ot netdata show
router
Done
Prefixes:
fd6f:cb3a:802:1::/64 paos low dc00
Routes:
fc00::/7 sa med dc00
fd6f:cb3a:802:2:0:0::/96 sn low dc00
Services:
44970 01 14000500000e10 s dc00 0
44970 5d fd78b9ce54779c6eb5484d062c3b5b22d120 s dc00 1
Contexts:
fd6f:cb3a:802:1::/64 1 c
Commissioning:
11426 - - -
Done
Prefixes show the IPv6 prefies that could be used by device outside the
Thread network to contact devices on Thread network.
We should have an IPv6 address using the prefix:
.. code-block::
server:~$ ot ipaddr
fd78:b9ce:5477:9c6e:0:ff:fe00:a800
fd6f:cb3a:802:1:f0ec:c1e2:c1bb:744
fd78:b9ce:5477:9c6e:75b8:386c:1f79:1013
fe80:0:0:0:50d1:bed5:6e6e:ad75
Done
fd6f:cb3a:802:1:f0ec:c1e2:c1bb:744 is the IPv6 address that could be used
to contact the CoAP server outside of the Thread network.
We could also check that we could access internet from Thread network:
.. code-block::
server:~$ ot ping 8.8.8.8
Pinging synthesized IPv6 address: fd6f:cb3a:802:2:0:0:808:808
16 bytes from fd6f:cb3a:802:2:0:0:808:808: icmp_seq=1 hlim=114 time=36ms
1 packets transmitted, 1 packets received. Packet loss = 0.0%. Round-trip min/avg/max = 36/36.0/36 ms.
Done
If everything is working, then, we could start controlling the LED from a computer.
To do that, let's use aiocoap-client, a tool written in python.
First, install it:
.. code-block::
pip install aiocoap
Then, send a request to the server to toggle the LED:
.. code-block::
aiocoap-client -m PUT --payload '{"led_id":0,"state":2}' coap://[fd6f:cb3a:802:1:f0ec:c1e2:c1bb:744]/led
The LED state should have changed.
.. _OpenThread Border Router: https://openthread.io/codelabs/openthread-border-router-nat64

View file

@ -0,0 +1,28 @@
CONFIG_NETWORKING=y
CONFIG_NET_L2_OPENTHREAD=y
CONFIG_OPENTHREAD_COAP=y
CONFIG_OPENTHREAD_SLAAC=y
CONFIG_JSON_LIBRARY=y
# Logging
CONFIG_LOG=y
CONFIG_LOG_MAX_LEVEL=1
CONFIG_LOG_MODE_MINIMAL=n
CONFIG_BOOT_BANNER=y
CONFIG_LOG_BACKEND_UART=y
# Kernel options
CONFIG_MAIN_STACK_SIZE=2560
CONFIG_INIT_STACKS=y
# Add features required for FTD CLI
CONFIG_SHELL=y
CONFIG_OPENTHREAD_SHELL=y
CONFIG_OPENTHREAD_PING_SENDER=y
# Network config
CONFIG_OPENTHREAD_PANID=4660
CONFIG_OPENTHREAD_XPANID="11:11:11:11:22:22:22:22"
CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff"
CONFIG_OPENTHREAD_CHANNEL=15
CONFIG_OPENTHREAD_NETWORK_NAME="OpenThreadDemo"

View file

@ -0,0 +1,21 @@
common:
harness: net
tags:
- net
- openthread
depends_on: openthread
min_flash: 140
sample:
description: Runs the OpenThread stack as FTD with CoAP
name: OpenThread FTD CoAP
tests:
sample.net.openthread.ftd.coap.client:
build_only: true
platform_allow:
- cc1352p7_lp
sample.net.openthread.ftd.coap.server:
build_only: true
platform_allow:
- cc1352p7_lp
extra_configs:
- CONFIG_OT_COAP_SAMPLE_SERVER=y

View file

@ -0,0 +1,180 @@
/*
* Copyright (c) 2024 Alexandre Bailon
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(coap);
#include "coap_utils.h"
#include "button.h"
struct btn_rsc_data {
const struct gpio_dt_spec gpio;
};
struct btn_rsc_ctx {
struct btn_rsc_data *btn;
int count;
};
static const struct json_obj_descr json_btn_state_descr[] = {
JSON_OBJ_DESCR_PRIM(struct json_btn_state, btn_id, JSON_TOK_NUMBER),
JSON_OBJ_DESCR_PRIM(struct json_btn_state, state, JSON_TOK_NUMBER),
};
static const struct json_obj_descr json_btn_get_descr[] = {
JSON_OBJ_DESCR_PRIM(struct json_btn_get, device_id, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJ_ARRAY(struct json_btn_get, btns, JSON_MAX_BTN, count,
json_btn_state_descr, ARRAY_SIZE(json_btn_state_descr)),
};
static K_SEM_DEFINE(btn_get_sem, 0, 1);
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
static int btn_handler_get(void *ctx, otMessage *msg, const otMessageInfo *msg_info)
{
uint8_t buf[COAP_MAX_BUF_SIZE];
struct btn_rsc_ctx *btn_ctx = ctx;
struct json_btn_get btn_data = {
.device_id = coap_device_id(),
};
for (int i = 0; i < btn_ctx->count; i++) {
btn_data.btns[i].btn_id = i;
btn_data.btns[i].state = gpio_pin_get_dt(&btn_ctx->btn[i].gpio);
}
btn_data.count = btn_ctx->count;
json_obj_encode_buf(json_btn_get_descr, ARRAY_SIZE(json_btn_get_descr), &btn_data, buf,
COAP_MAX_BUF_SIZE);
return coap_resp_send(msg, msg_info, buf, strlen(buf) + 1);
}
static void btn_handler(void *ctx, otMessage *msg, const otMessageInfo *msg_info)
{
coap_req_handler(ctx, msg, msg_info, NULL, btn_handler_get);
}
#define DEFINE_BTN_CTX(node_id) \
{ \
.gpio = GPIO_DT_SPEC_GET(node_id, gpios), \
},
#define DEFINE_BTNS_CTX(inst, compat, ...) DT_FOREACH_CHILD(DT_INST(inst, compat), DEFINE_BTN_CTX)
static struct btn_rsc_data btn_rsc_data[] = {
DT_COMPAT_FOREACH_STATUS_OKAY_VARGS(gpio_keys, DEFINE_BTNS_CTX)};
static struct btn_rsc_ctx btn_rsc_ctx = {
.btn = btn_rsc_data,
.count = ARRAY_SIZE(btn_rsc_data),
};
static otCoapResource btn_rsc = {
.mUriPath = BTN_URI,
.mHandler = btn_handler,
.mContext = &btn_rsc_ctx,
.mNext = NULL,
};
static int button_init_rsc(otCoapResource *rsc)
{
int ret = 0;
struct btn_rsc_ctx *btn_ctx = rsc->mContext;
const struct gpio_dt_spec *gpio;
LOG_INF("Initializing the buttons");
for (int i = 0; i < btn_ctx->count; i++) {
gpio = &btn_ctx->btn[i].gpio;
ret = button_init(gpio);
if (ret) {
break;
}
}
return ret;
}
void coap_btn_reg_rsc(void)
{
otInstance *ot = openthread_get_default_instance();
button_init_rsc(&btn_rsc);
LOG_INF("Registering button rsc");
otCoapAddResource(ot, &btn_rsc);
}
#endif /* CONFIG_OT_COAP_SAMPLE_SERVER */
int button_init(const struct gpio_dt_spec *gpio)
{
int ret;
if (!gpio_is_ready_dt(gpio)) {
LOG_ERR("Error: button device %s is not ready\n", gpio->port->name);
return -ENODEV;
}
ret = gpio_pin_configure_dt(gpio, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Error %d: failed to configure %s pin %d\n", ret, gpio->port->name,
gpio->pin);
return ret;
}
ret = gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
LOG_ERR("Error %d: failed to configure interrupt on %s pin %d\n", ret,
gpio->port->name, gpio->pin);
return ret;
}
return 0;
}
static void coap_btn_get_state_cb(void *ctx, otMessage *msg, const otMessageInfo *msg_info,
otError error)
{
uint8_t buf[COAP_MAX_BUF_SIZE];
int len = COAP_MAX_BUF_SIZE;
struct json_btn_get *btn = (struct json_btn_get *)ctx;
int ret;
ret = coap_get_data(msg, buf, &len);
if (ret) {
btn->count = 0;
goto exit;
}
json_obj_parse(buf, len, json_btn_get_descr, ARRAY_SIZE(json_btn_get_descr), btn);
exit:
k_sem_give(&btn_get_sem);
}
int coap_btn_get_state(const char *addr, int btn_id, int *state)
{
struct json_btn_get btn;
int ret;
ret = coap_get_req_send(addr, BTN_URI, NULL, 0, coap_btn_get_state_cb, &btn);
if (ret) {
return ret;
}
ret = k_sem_take(&btn_get_sem, K_FOREVER);
if (ret) {
return ret;
}
if (btn_id >= btn.count) {
return -ENODEV;
}
*state = btn.btns[btn_id].state;
return ret;
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 Alexandre Bailon
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef COAP_BUTTON_H
#define COAP_BUTTON_H
#include <zephyr/drivers/gpio.h>
#include <zephyr/data/json.h>
#define BTN_URI "sw"
#define BTN_MSG_STATE_OFF 0
#define BTN_MSG_STATE_ON 1
#define JSON_MAX_BTN 4
struct json_btn_state {
int btn_id;
int state;
};
struct json_btn_get {
const char *device_id;
struct json_btn_state btns[JSON_MAX_BTN];
int count;
};
int button_init(const struct gpio_dt_spec *gpio);
int coap_btn_get_state(const char *addr, int led_id, int *state);
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
void coap_btn_reg_rsc(void);
#endif
#endif /* COAP_BUTTON_H */

View file

@ -0,0 +1,284 @@
/*
* Copyright (c) 2024 Alexandre Bailon
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(coap);
#include "coap_utils.h"
static uint8_t coap_buf[COAP_MAX_BUF_SIZE];
static uint8_t coap_dev_id[COAP_DEVICE_ID_SIZE];
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
static void coap_default_handler(void *context, otMessage *message,
const otMessageInfo *message_info)
{
ARG_UNUSED(context);
ARG_UNUSED(message);
ARG_UNUSED(message_info);
LOG_INF("Received CoAP message that does not match any request "
"or resource");
}
#endif /* CONFIG_OT_COAP_SAMPLE_SERVER */
static int coap_req_send(const char *addr, const char *uri, uint8_t *buf, int len,
otCoapResponseHandler handler, void *ctx, otCoapCode code)
{
otInstance *ot;
otMessage *msg;
otMessageInfo msg_info;
otError err;
int ret;
ot = openthread_get_default_instance();
if (!ot) {
LOG_ERR("Failed to get an OpenThread instance");
return -ENODEV;
}
memset(&msg_info, 0, sizeof(msg_info));
otIp6AddressFromString(addr, &msg_info.mPeerAddr);
msg_info.mPeerPort = OT_DEFAULT_COAP_PORT;
msg = otCoapNewMessage(ot, NULL);
if (!msg) {
LOG_ERR("Failed to allocate a new CoAP message");
return -ENOMEM;
}
otCoapMessageInit(msg, OT_COAP_TYPE_CONFIRMABLE, code);
err = otCoapMessageAppendUriPathOptions(msg, uri);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to append uri-path: %s", otThreadErrorToString(err));
ret = -EBADMSG;
goto err;
}
err = otCoapMessageSetPayloadMarker(msg);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to set payload marker: %s", otThreadErrorToString(err));
ret = -EBADMSG;
goto err;
}
err = otMessageAppend(msg, buf, len);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to set append payload to response: %s", otThreadErrorToString(err));
ret = -EBADMSG;
goto err;
}
err = otCoapSendRequest(ot, msg, &msg_info, handler, ctx);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to send the request: %s", otThreadErrorToString(err));
ret = -EIO; /* Find a better error code */
goto err;
}
return 0;
err:
otMessageFree(msg);
return ret;
}
int coap_put_req_send(const char *addr, const char *uri, uint8_t *buf, int len,
otCoapResponseHandler handler, void *ctx)
{
return coap_req_send(addr, uri, buf, len, handler, ctx, OT_COAP_CODE_PUT);
}
int coap_get_req_send(const char *addr, const char *uri, uint8_t *buf, int len,
otCoapResponseHandler handler, void *ctx)
{
return coap_req_send(addr, uri, buf, len, handler, ctx, OT_COAP_CODE_GET);
}
int coap_resp_send(otMessage *req, const otMessageInfo *req_info, uint8_t *buf, int len)
{
otInstance *ot;
otMessage *resp;
otCoapCode resp_code;
otCoapType resp_type;
otError err;
int ret;
ot = openthread_get_default_instance();
if (!ot) {
LOG_ERR("Failed to get an OpenThread instance");
return -ENODEV;
}
resp = otCoapNewMessage(ot, NULL);
if (!resp) {
LOG_ERR("Failed to allocate a new CoAP message");
return -ENOMEM;
}
switch (otCoapMessageGetType(req)) {
case OT_COAP_TYPE_CONFIRMABLE:
resp_type = OT_COAP_TYPE_ACKNOWLEDGMENT;
break;
case OT_COAP_TYPE_NON_CONFIRMABLE:
resp_type = OT_COAP_TYPE_NON_CONFIRMABLE;
break;
default:
LOG_ERR("Invalid message type");
ret = -EINVAL;
goto err;
}
switch (otCoapMessageGetCode(req)) {
case OT_COAP_CODE_GET:
resp_code = OT_COAP_CODE_CONTENT;
break;
case OT_COAP_CODE_PUT:
resp_code = OT_COAP_CODE_CHANGED;
break;
default:
LOG_ERR("Invalid message code");
ret = -EINVAL;
goto err;
}
err = otCoapMessageInitResponse(resp, req, resp_type, resp_code);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to initialize the response: %s", otThreadErrorToString(err));
ret = -EBADMSG;
goto err;
}
err = otCoapMessageSetPayloadMarker(resp);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to set payload marker: %s", otThreadErrorToString(err));
ret = -EBADMSG;
goto err;
}
err = otMessageAppend(resp, buf, len);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to set append payload to response: %s", otThreadErrorToString(err));
ret = -EBADMSG;
goto err;
}
err = otCoapSendResponse(ot, resp, req_info);
if (err != OT_ERROR_NONE) {
LOG_ERR("Failed to send the response: %s", otThreadErrorToString(err));
ret = -EIO;
goto err;
}
return 0;
err:
otMessageFree(resp);
return ret;
}
int coap_req_handler(void *ctx, otMessage *msg, const otMessageInfo *msg_info,
coap_req_handler_put put_fn, coap_req_handler_get get_fn)
{
otCoapCode msg_code = otCoapMessageGetCode(msg);
otCoapType msg_type = otCoapMessageGetType(msg);
int ret;
if (msg_type != OT_COAP_TYPE_CONFIRMABLE && msg_type != OT_COAP_TYPE_NON_CONFIRMABLE) {
return -EINVAL;
}
if (msg_code == OT_COAP_CODE_PUT && put_fn) {
int len = otMessageGetLength(msg) - otMessageGetOffset(msg);
otMessageRead(msg, otMessageGetOffset(msg), coap_buf, len);
ret = put_fn(ctx, coap_buf, len);
if (ret) {
return ret;
}
if (msg_type == OT_COAP_TYPE_CONFIRMABLE) {
ret = get_fn(ctx, msg, msg_info);
}
return ret;
}
if (msg_code == OT_COAP_CODE_GET) {
return get_fn(ctx, msg, msg_info);
}
return -EINVAL;
}
const char *coap_device_id(void)
{
otInstance *ot = openthread_get_default_instance();
otExtAddress eui64;
int i;
if (coap_dev_id[0] != '\0') {
return coap_dev_id;
}
otPlatRadioGetIeeeEui64(ot, eui64.m8);
for (i = 0; i < 8; i++) {
if (i * 2 >= COAP_DEVICE_ID_SIZE) {
i = COAP_DEVICE_ID_SIZE - 1;
break;
}
sprintf(coap_dev_id + i * 2, "%02x", eui64.m8[i]);
}
coap_dev_id[i * 2] = '\0';
return coap_dev_id;
}
int coap_get_data(otMessage *msg, void *buf, int *len)
{
int coap_len = otMessageGetLength(msg) - otMessageGetOffset(msg);
if (coap_len > *len) {
return -ENOMEM;
}
*len = coap_len;
otMessageRead(msg, otMessageGetOffset(msg), buf, coap_len);
return 0;
}
int coap_init(void)
{
otError err;
otInstance *ot;
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
LOG_INF("Initializing OpenThread CoAP server");
#else /* CONFIG_OT_COAP_SAMPLE_SERVER */
LOG_INF("Initializing OpenThread CoAP client");
#endif /* CONFIG_OT_COAP_SAMPLE_SERVER */
ot = openthread_get_default_instance();
if (!ot) {
LOG_ERR("Failed to get an OpenThread instance");
return -ENODEV;
}
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
otCoapSetDefaultHandler(ot, coap_default_handler, NULL);
#endif /* CONFIG_OT_COAP_SAMPLE_SERVER */
err = otCoapStart(ot, OT_DEFAULT_COAP_PORT);
if (err != OT_ERROR_NONE) {
LOG_ERR("Cannot start CoAP: %s", otThreadErrorToString(err));
return -EBADMSG;
}
return 0;
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2024 Alexandre Bailon
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef COAP_UTILS_H
#define COAP_UTILS_H
#include <zephyr/net/openthread.h>
#include <openthread/coap.h>
#define COAP_MAX_BUF_SIZE 128
#define COAP_DEVICE_ID_SIZE 25
typedef int (*coap_req_handler_put)(void *ctx, uint8_t *buf, int size);
typedef int (*coap_req_handler_get)(void *ctx, otMessage *msg, const otMessageInfo *msg_info);
int coap_init(void);
int coap_req_handler(void *ctx, otMessage *msg, const otMessageInfo *msg_info,
coap_req_handler_put put_fn, coap_req_handler_get get_fn);
int coap_resp_send(otMessage *req, const otMessageInfo *req_info, uint8_t *buf, int len);
int coap_put_req_send(const char *addr, const char *uri, uint8_t *buf, int len,
otCoapResponseHandler handler, void *ctx);
int coap_get_req_send(const char *addr, const char *uri, uint8_t *buf, int len,
otCoapResponseHandler handler, void *ctx);
const char *coap_device_id(void);
int coap_get_data(otMessage *msg, void *buf, int *len);
#endif /* COAP_UTILS_H */

View file

@ -0,0 +1,218 @@
/*
* Copyright (c) 2024 Alexandre Bailon
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(coap);
#include "coap_utils.h"
#include "led.h"
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
struct led_rsc_data {
const struct gpio_dt_spec gpio;
int state;
};
struct led_rsc_ctx {
struct led_rsc_data *led;
int count;
};
#endif
static const struct json_obj_descr json_led_state_descr[] = {
JSON_OBJ_DESCR_PRIM(struct json_led_state, led_id, JSON_TOK_NUMBER),
JSON_OBJ_DESCR_PRIM(struct json_led_state, state, JSON_TOK_NUMBER),
};
static const struct json_obj_descr json_led_get_descr[] = {
JSON_OBJ_DESCR_PRIM(struct json_led_get, device_id, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJ_ARRAY(struct json_led_get, leds, JSON_MAX_LED, count,
json_led_state_descr, ARRAY_SIZE(json_led_state_descr)),
};
K_SEM_DEFINE(led_get_sem, 0, 1);
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
static int led_init(otCoapResource *rsc)
{
struct led_rsc_ctx *led_ctx = rsc->mContext;
int ret;
LOG_INF("Initializing the LED");
for (int i = 0; i < led_ctx->count; i++) {
struct led_rsc_data *led = &led_ctx->led[i];
if (!gpio_is_ready_dt(&led->gpio)) {
return -ENODEV;
}
ret = gpio_pin_configure_dt(&led->gpio, GPIO_OUTPUT);
if (ret) {
LOG_ERR("Failed to configure the GPIO");
return ret;
}
}
return 0;
}
static int led_handler_put(void *ctx, uint8_t *buf, int size)
{
struct json_led_state led_data;
struct led_rsc_ctx *led_ctx = ctx;
struct led_rsc_data *led;
int ret = -EINVAL;
json_obj_parse(buf, size, json_led_state_descr, ARRAY_SIZE(json_led_state_descr),
&led_data);
if (led_data.led_id >= led_ctx->count) {
LOG_ERR("Invalid led id: %x", led_data.led_id);
return -EINVAL;
}
led = &led_ctx->led[led_data.led_id];
switch (led_data.state) {
case LED_MSG_STATE_ON:
ret = gpio_pin_set_dt(&led->gpio, 1);
led->state = 1;
break;
case LED_MSG_STATE_OFF:
ret = gpio_pin_set_dt(&led->gpio, 0);
led->state = 0;
break;
case LED_MSG_STATE_TOGGLE:
led->state = 1 - led->state;
ret = gpio_pin_set_dt(&led->gpio, led->state);
break;
default:
LOG_ERR("Set an unsupported LED state: %x", led_data.state);
}
return ret;
}
static int led_handler_get(void *ctx, otMessage *msg, const otMessageInfo *msg_info)
{
uint8_t buf[COAP_MAX_BUF_SIZE];
struct led_rsc_ctx *led_ctx = ctx;
struct json_led_get led_data = {
.device_id = coap_device_id(),
};
for (int i = 0; i < led_ctx->count; i++) {
led_data.leds[i].led_id = i;
led_data.leds[i].state = led_ctx->led[i].state;
}
led_data.count = led_ctx->count;
json_obj_encode_buf(json_led_get_descr, ARRAY_SIZE(json_led_get_descr), &led_data, buf,
COAP_MAX_BUF_SIZE);
return coap_resp_send(msg, msg_info, buf, strlen(buf) + 1);
}
static void led_handler(void *ctx, otMessage *msg, const otMessageInfo *msg_info)
{
coap_req_handler(ctx, msg, msg_info, led_handler_put, led_handler_get);
}
#define DEFINE_LED_CTX(node_id) \
{ \
.gpio = GPIO_DT_SPEC_GET(node_id, gpios), \
.state = 0, \
},
#define DEFINE_LEDS_CTX(inst, compat, ...) DT_FOREACH_CHILD(DT_INST(inst, compat), DEFINE_LED_CTX)
static struct led_rsc_data led_rsc_data[] = {
DT_COMPAT_FOREACH_STATUS_OKAY_VARGS(gpio_leds, DEFINE_LEDS_CTX)};
static struct led_rsc_ctx led_rsc_ctx = {
.led = led_rsc_data,
.count = ARRAY_SIZE(led_rsc_data),
};
static otCoapResource led_rsc = {
.mUriPath = LED_URI,
.mHandler = led_handler,
.mContext = &led_rsc_ctx,
.mNext = NULL,
};
void coap_led_reg_rsc(void)
{
otInstance *ot = openthread_get_default_instance();
LOG_INF("Registering LED rsc");
led_init(&led_rsc);
otCoapAddResource(ot, &led_rsc);
}
#endif /* CONFIG_OT_COAP_SAMPLE_SERVER */
static void coap_led_send_req_cb(void *ctx, otMessage *msg, const otMessageInfo *msg_info,
otError error)
{
}
int coap_led_set_state(const char *addr, int led_id, int state)
{
uint8_t buf[COAP_MAX_BUF_SIZE];
struct json_led_state led_data = {
.led_id = led_id,
.state = state,
};
json_obj_encode_buf(json_led_state_descr, ARRAY_SIZE(json_led_state_descr), &led_data, buf,
COAP_MAX_BUF_SIZE);
return coap_put_req_send(addr, LED_URI, buf, strlen(buf) + 1, coap_led_send_req_cb, NULL);
}
static void coap_led_get_state_cb(void *ctx, otMessage *msg, const otMessageInfo *msg_info,
otError error)
{
uint8_t buf[COAP_MAX_BUF_SIZE];
int len = COAP_MAX_BUF_SIZE;
struct json_led_get *led = (struct json_led_get *)ctx;
int ret;
ret = coap_get_data(msg, buf, &len);
if (ret) {
led->count = 0;
goto exit;
}
json_obj_parse(buf, len, json_led_get_descr, ARRAY_SIZE(json_led_get_descr), led);
exit:
k_sem_give(&led_get_sem);
}
int coap_led_get_state(const char *addr, int led_id, int *state)
{
struct json_led_get led;
int ret;
ret = coap_get_req_send(addr, LED_URI, NULL, 0, coap_led_get_state_cb, &led);
if (ret) {
return ret;
}
ret = k_sem_take(&led_get_sem, K_FOREVER);
if (ret) {
return ret;
}
if (led_id >= led.count) {
return -ENODEV;
}
*state = led.leds[led_id].state;
return ret;
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 Alexandre Bailon
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef COAP_LED_H
#define COAP_LED_H
#include <zephyr/data/json.h>
#define LED_URI "led"
#define LED_MSG_STATE_OFF 0
#define LED_MSG_STATE_ON 1
#define LED_MSG_STATE_TOGGLE 2
#define JSON_MAX_LED 4
struct json_led_state {
int led_id;
int state;
};
struct json_led_get {
const char *device_id;
struct json_led_state leds[JSON_MAX_LED];
int count;
};
int coap_led_set_state(const char *addr, int led_id, int state);
int coap_led_get_state(const char *addr, int led_id, int *state);
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
void coap_led_reg_rsc(void);
#endif /* CONFIG_OT_COAP_SAMPLE_SERVER */
#endif /* COAP_LED_H */

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 Alexandre Bailon
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(coap);
#include <coap_utils.h>
#ifdef CONFIG_OT_COAP_SAMPLE_LED
#include "led.h"
#endif /* CONFIG_OT_COAP_SAMPLE_LED */
#ifdef CONFIG_OT_COAP_SAMPLE_SW
#include <zephyr/drivers/gpio.h>
#include "button.h"
/*
* Get button configuration from the devicetree sw0 alias. This is mandatory.
*/
#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS_OKAY(SW0_NODE)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
static struct gpio_callback button_cb_data;
void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
coap_led_set_state("ff03::1", 0, LED_MSG_STATE_TOGGLE);
}
#endif /* CONFIG_OT_COAP_SAMPLE_SW */
int main(void)
{
int ret;
#ifdef CONFIG_OT_COAP_SAMPLE_SERVER
#ifdef CONFIG_OT_COAP_SAMPLE_LED
coap_led_reg_rsc();
#endif /* CONFIG_OT_COAP_SAMPLE_LED */
#ifdef CONFIG_OT_COAP_SAMPLE_SW
coap_btn_reg_rsc();
#endif /* CONFIG_OT_COAP_SAMPLE_SW */
#endif /* CONFIG_OT_COAP_SAMPLE_SERVER */
ret = coap_init();
if (ret) {
return ret;
}
#ifdef CONFIG_OT_COAP_SAMPLE_SW
button_init(&button);
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
gpio_add_callback_dt(&button, &button_cb_data);
#endif /*CONFIG_OT_COAP_SAMPLE_SW */
return 0;
}