MCUmgr 处理程序

概述

MCUmgr 通过组处理程序来工作,这些处理程序标识与特定管理区域相关的一组功能;管理区域由 16 位标识值寻址。mcumgr_group_t 包含 Zephyr 中可用的管理组及其对应的组 ID 值。组 ID 包含在 SMP 头中,用于标识命令所属的组;此外还有一个 8 位命令 ID,用于标识要执行的该组功能——有关 SMP 协议和头部的详细信息,请参阅 SMP 协议规范。每个唯一 ID 只能注册一个组。

实现

MCUmgr 处理程序可以由应用程序代码或模块代码在外部添加,它们不必位于上游 Zephyr 树中才能使用。创建处理程序的第一步是创建其文件夹结构,典型的 Zephyr MCUmgr 组布局如下:

<dir>/grp/<grp_name>_mgmt/
├── CMakeLists.txt
├── Kconfig
├── include
├──── <grp_name>_mgmt.h
├──── <grp_name>_mgmt_callbacks.h
├── src
└──── <grp_name>_mgmt.c

请注意,上游 Zephyr MCUmgr 处理程序中的头文件位于 zephyr/include/zephyr/mgmt/mcumgr/grp/<grp_name>_mgmt 目录中,以便应用程序可以全局包含这些文件。

初始头文件 <grp_name>_mgmt.h

头文件的作用是提供可供 MCUmgr 处理程序本身和应用程序代码使用的定义,例如用于引用执行功能所需的命令 ID。示例文件类似于:

 1/*
 2 * Copyright (c) 2023 Nordic Semiconductor ASA
 3 *
 4 * SPDX-License-Identifier: Apache-2.0
 5 */
 6#ifndef H_EXAMPLE_MGMT_
 7#define H_EXAMPLE_MGMT_
 8
 9#ifdef __cplusplus
10extern "C" {
11#endif
12
13/**
14 * Group ID for example management group.
15 */
16#define MGMT_GROUP_ID_EXAMPLE  MGMT_GROUP_ID_PERUSER
17
18/**
19 * Command IDs for example management group.
20 */
21#define EXAMPLE_MGMT_ID_TEST   0
22#define EXAMPLE_MGMT_ID_OTHER  1
23
24/**
25 * Command result codes for example management group.
26 */
27enum example_mgmt_err_code_t {
28	/** No error, this is implied if there is no ret value in the response */
29	EXAMPLE_MGMT_ERR_OK = 0,
30
31	/** Unknown error occurred. */
32	EXAMPLE_MGMT_ERR_UNKNOWN,
33
34	/** The provided value is not wanted at this time. */
35	EXAMPLE_MGMT_ERR_NOT_WANTED,
36
37	/** The provided value was rejected by a hook. */
38	EXAMPLE_MGMT_ERR_REJECTED_BY_HOOK,
39};
40
41#ifdef __cplusplus
42}
43#endif
44
45#endif

这为 test 和 other 这 2 个命令提供了定义,并设置 SMP 版本 2 错误响应(与返回 mcumgr_err_t 的旧版 SMP 版本 1 错误响应不同,版本 2 的每个组都有唯一错误码——应始终有一个值为 0 的 OK 错误码和一个值为 1 的未知错误码。上述示例随后添加了值为 2 的 not wanted 错误码。此外,组 ID 设置为 MGMT_GROUP_ID_PERUSER,它是用户定义组的起始组 ID。请注意,组 ID 必须唯一,因此其他自定义组应使用不同的值;可以使用集中索引头文件(如上游 Zephyr 中所做的那样)来更轻松地分配组 ID。

初始头文件 <grp_name>_mgmt_callbacks.h

头文件的作用是提供可供 MCUmgr 处理程序本身和应用程序代码使用的定义,例如用于引用执行功能所需的命令 ID。示例文件类似于:

 1/*
 2 * Copyright (c) 2023 Nordic Semiconductor ASA
 3 *
 4 * SPDX-License-Identifier: Apache-2.0
 5 */
 6#ifndef H_MCUMGR_EXAMPLE_MGMT_CALLBACKS_
 7#define H_MCUMGR_EXAMPLE_MGMT_CALLBACKS_
 8#include <stdint.h>
 9#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15/* This is the event ID for the example group */
16#define MGMT_EVT_GRP_EXAMPLE MGMT_EVT_GRP_USER_CUSTOM_START
17
18/* MGMT event opcodes for example management group */
19enum example_mgmt_group_events {
20	/* Callback when the other command is received, data is example_mgmt_other_data */
21	MGMT_EVT_OP_EXAMPLE_OTHER = MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_EXAMPLE, 0),
22
23	/* Used to enable all smp_group events */
24	MGMT_EVT_OP_EXAMPLE_MGMT_ALL  = MGMT_DEF_EVT_OP_ALL(MGMT_EVT_GRP_EXAMPLE),
25};
26
27/* Structure provided in the #MGMT_EVT_OP_EXAMPLE_OTHER notification callback */
28struct example_mgmt_other_data {
29	/* Contains the user supplied value */
30	uint32_t user_value;
31};
32
33#ifdef __cplusplus
34}
35#endif
36
37#endif

这会设置一个事件,应用程序(或模块)代码可以注册该事件,以便在函数处理程序执行时收到回调;这允许更改处理程序的流程(即返回错误而不是继续执行)。事件组 ID 设置为 MGMT_EVT_GRP_USER_CUSTOM_START,它是用户定义组的起始事件 ID。请注意,事件 ID 必须唯一,因此其他自定义组应使用不同的值;可以使用集中索引头文件(如上游 Zephyr 中所做的那样)来更轻松地分配事件 ID。

初始源文件 <grp_name>_mgmt.c

此源文件的作用是处理传入的 MCUmgr 命令、提供响应,并向 MCUmgr 注册传输,以便将命令发送给它。示例文件类似于:

  1/*
  2 * Copyright (c) 2023 Nordic Semiconductor ASA
  3 *
  4 * SPDX-License-Identifier: Apache-2.0
  5 */
  6#include <zephyr/kernel.h>
  7#include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
  8#include <zephyr/mgmt/mcumgr/smp/smp.h>
  9#include <zephyr/mgmt/mcumgr/mgmt/handlers.h>
 10/* The below should be updated with the real name of the file */
 11#include <example_mgmt.h>
 12#include <zephyr/logging/log.h>
 13#include <assert.h>
 14#include <limits.h>
 15#include <string.h>
 16#include <stdio.h>
 17#include <zcbor_common.h>
 18#include <zcbor_decode.h>
 19#include <zcbor_encode.h>
 20#include <mgmt/mcumgr/util/zcbor_bulk.h>
 21
 22#if defined(CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS)
 23#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
 24#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
 25/* The below should be updated with the real name of the file */
 26#include <example_mgmt_callbacks.h>
 27#endif
 28#endif
 29
 30LOG_MODULE_REGISTER(mcumgr_example_grp, CONFIG_MCUMGR_GRP_EXAMPLE_LOG_LEVEL);
 31/* Example function with "read" command support, requires both parameters are supplied */
 32static int example_mgmt_test(struct smp_streamer *ctxt)
 33{
 34	uint32_t uint_value = 0;
 35	zcbor_state_t *zse = ctxt->writer->zs;
 36	zcbor_state_t *zsd = ctxt->reader->zs;
 37	bool ok;
 38	struct zcbor_string string_value = { 0 };
 39	size_t decoded;
 40	struct zcbor_map_decode_key_val example_test_decode[] = {
 41		ZCBOR_MAP_DECODE_KEY_DECODER("uint_key", zcbor_uint32_decode, &uint_value),
 42		ZCBOR_MAP_DECODE_KEY_DECODER("string_key", zcbor_tstr_decode, &string_value),
 43	};
 44
 45	LOG_DBG("Example test function called");
 46
 47	ok = zcbor_map_decode_bulk(zsd, example_test_decode, ARRAY_SIZE(example_test_decode),
 48				   &decoded) == 0;
 49	/* Check that both parameters were supplied and that the value of "string_key" is not
 50	 * empty
 51	 */
 52	if (!ok || string_value.len == 0 || !zcbor_map_decode_bulk_key_found(
 53			   example_test_decode, ARRAY_SIZE(example_test_decode), "uint_key")) {
 54		return MGMT_ERR_EINVAL;
 55	}
 56
 57	/* If the value of "uint_key" is over 50, return an error of "not wanted" */
 58	if (uint_value > 50) {
 59		ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_EXAMPLE, EXAMPLE_MGMT_ERR_NOT_WANTED);
 60		goto end;
 61	}
 62
 63	/* Otherwise, return an integer value of 4691 */
 64	ok = zcbor_tstr_put_lit(zse, "return_int") &&
 65	     zcbor_int32_put(zse, 4691);
 66
 67end:
 68	/* If "ok" is false, then there was an error processing the output cbor message, which
 69	 * likely indicates a lack of available memory
 70	 */
 71	return (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
 72}
 73
 74/* Example function with "write" command support */
 75static int example_mgmt_other(struct smp_streamer *ctxt)
 76{
 77	uint32_t user_value = 0;
 78	zcbor_state_t *zse = ctxt->writer->zs;
 79	zcbor_state_t *zsd = ctxt->reader->zs;
 80	bool ok;
 81	size_t decoded;
 82	struct zcbor_map_decode_key_val example_other_decode[] = {
 83		ZCBOR_MAP_DECODE_KEY_DECODER("user_value", zcbor_uint32_decode, &user_value),
 84	};
 85
 86#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
 87	struct example_mgmt_other_data other_data;
 88	enum mgmt_cb_return status;
 89	int32_t err_rc;
 90	uint16_t err_group;
 91#endif
 92
 93	LOG_DBG("Example other function called");
 94
 95	ok = zcbor_map_decode_bulk(zsd, example_other_decode, ARRAY_SIZE(example_other_decode),
 96				   &decoded) == 0;
 97
 98	/* The supplied value is optional, therefore do not return an error if it was not
 99	 * provided
100	 */
101	if (!ok) {
102		return MGMT_ERR_EINVAL;
103	}
104
105#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
106	/* Send request to application to check what to do */
107	other_data.user_value = user_value;
108	status = mgmt_callback_notify(MGMT_EVT_OP_EXAMPLE_OTHER, &other_data, sizeof(other_data),
109				      &err_rc, &err_group);
110	if (status != MGMT_CB_OK) {
111		/* If a callback returned an RC error, exit out, if it returned a group error
112		 * code, add the error code to the response and return to the calling function to
113		 * have it sent back to the client
114		 */
115		if (status == MGMT_CB_ERROR_RC) {
116			return err_rc;
117		}
118
119		ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
120		goto end;
121	}
122#endif
123	/* Return some dummy data to the client */
124	ok = zcbor_tstr_put_lit(zse, "return_string") &&
125	     zcbor_tstr_put_lit(zse, "some dummy data!");
126
127#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
128end:
129#endif
130	/* If "ok" is false, then there was an error processing the output cbor message, which
131	 * likely indicates a lack of available memory
132	 */
133	return (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
134}
135
136#ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
137/* This is a lookup function that converts from SMP version 2 group error codes to legacy
138 * MCUmgr error codes, it is only included if support for the original protocol is enabled.
139 * Note that in SMP version 2, MCUmgr error codes can still be returned, but are to be used
140 * only for general SMP/MCUmgr errors. The success/OK error code is not used in translation
141 * functions as it is automatically handled by the base SMP code.
142 */
143static int example_mgmt_translate_error_code(uint16_t err)
144{
145	int rc;
146
147	switch (err) {
148	case EXAMPLE_MGMT_ERR_NOT_WANTED:
149		rc = MGMT_ERR_ENOENT;
150		break;
151
152	case EXAMPLE_MGMT_ERR_REJECTED_BY_HOOK:
153		rc = MGMT_ERR_EBADSTATE;
154		break;
155
156	case EXAMPLE_MGMT_ERR_UNKNOWN:
157	default:
158		rc = MGMT_ERR_EUNKNOWN;
159	}
160
161	return rc;
162}
163#endif
164
165static const struct mgmt_handler example_mgmt_handlers[] = {
166	[EXAMPLE_MGMT_ID_TEST] = {
167		.mh_read = example_mgmt_test,
168		.mh_write = NULL,
169	},
170	[EXAMPLE_MGMT_ID_OTHER] = {
171		.mh_read = NULL,
172		.mh_write = example_mgmt_other,
173	},
174};
175
176static struct mgmt_group example_mgmt_group = {
177	.mg_handlers = example_mgmt_handlers,
178	.mg_handlers_count = ARRAY_SIZE(example_mgmt_handlers),
179	.mg_group_id = MGMT_GROUP_ID_EXAMPLE,
180#ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
181	.mg_translate_error = example_mgmt_translate_error_code,
182#endif
183};
184
185static void example_mgmt_register_group(void)
186{
187	/* This function is called during system init before main() is invoked, if the
188	 * handler needs to set anything up before it can be used, it should do it here.
189	 * This register the group so that clients can call the function handlers.
190	 */
191	mgmt_register_group(&example_mgmt_group);
192}
193
194MCUMGR_HANDLER_DEFINE(example_mgmt, example_mgmt_register_group);

上述代码创建了 2 个函数处理程序,其中 test 支持读请求并接受 2 个必需参数,而 other 支持写请求并接受 1 个可选参数。此函数处理程序具有可选的通信回调功能,允许代码的其他部分监听该事件并采取任何必要的措施,或者通过返回错误来阻止函数的进一步执行。有关 MCUmgr 回调功能的更多详细信息,请参阅 MCUmgr 回调。

请注意,引用自定义 MCUmgr 处理程序回调的其他代码需要同时包含基础 Zephyr 回调包含文件和自定义处理程序回调文件;包含上游 Zephyr 回调头文件时,只会包含树内 Zephyr 处理程序头文件。

初始 Kconfig

Kconfig 文件的作用是提供与所实现处理程序功能相关的选项,供用户启用或更改。示例文件类似于:

# Copyright Nordic Semiconductor ASA 2023. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# The Kconfig file is dedicated to example management group of
# of MCUmgr subsystem and provides Kconfig options to configure
# group commands behaviour and other aspects.
#
# Options defined in this file should be prefixed:
#  MCUMGR_GRP_EXAMPLE_ -- general group options;
#
# When adding Kconfig options, that control the same feature,
# try to group them together by the same stem after prefix.
if MCUMGR

menuconfig MCUMGR_GRP_EXAMPLE_APP
	bool "MCUmgr handlers for example management (app)"
	select MCUMGR_SMP_CBOR_MIN_DECODING_LEVEL_2
	default y
	help
	  Enables MCUmgr handlers for example management. This demonstrates the
	  file at application-level.

if MCUMGR_GRP_EXAMPLE_APP
config MCUMGR_GRP_EXAMPLE_OTHER_HOOK
	bool "Other hook"
	depends on MCUMGR_MGMT_NOTIFICATION_HOOKS
	help
	  Allows applications to receive callback when the "other" example
	  management function is called

module = MCUMGR_GRP_EXAMPLE
module-str = mcumgr_grp_example
source "subsys/logging/Kconfig.template.log_config"

endif

endif

source "Kconfig.zephyr"

初始 CMakeLists.txt

CMakeLists.txt 文件由构建系统使用,用于设置要编译的文件、要添加的包含目录以及指定可更改的选项。基本文件只需在启用 Kconfig 选项时包含源文件。示例文件类似于:

# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

if(CONFIG_MCUMGR_GRP_EXAMPLE_MODULE)
  zephyr_library()
  # The below should be updated with the real name of the file
  zephyr_library_sources(src/example_mgmt.c)
  zephyr_include_directories(include)
endif()

从应用程序包含

可以通过创建/编辑应用程序构建文件来添加特定于应用程序的 MCUmgr 处理程序。下面显示了示例修改。

CMakeLists.txt 示例

通过添加以下内容,应用程序 CMakeLists.txt 文件可以加载示例 MCUmgr 处理程序的 CMake 文件:

add_subdirectory(mcumgr/grp/<grp_name>)

Kconfig 示例

通过将以下内容添加到应用程序目录中的 Kconfig 文件(如果不存在则创建它),应用程序 Kconfig 文件可以包含示例 MCUmgr 处理程序的 Kconfig 文件:

rsource "mcumgr/grp/<grp_name>/Kconfig"

# Include Zephyr's Kconfig
source "Kconfig.zephyr"

从 Zephyr 模块包含

Zephyr 模块(外部项目) 可用于将自定义 MCUmgr 处理程序添加到多个不同的应用程序,而无需在每个应用程序的源代码树中重复代码;有关如何设置模块文件的详细信息,请参阅 模块 YAML 文件说明。下面显示了示例文件。

zephyr/module.yml 示例

这是一个示例文件,可用于从模块目录的根目录加载 Kconfig 和 CMake 文件,它应放置于 zephyr/module.yml:

build:
  kconfig: Kconfig
  cmake: .

Example CMakeLists.txt

这是一个示例 CMakeLists.txt 文件,它加载示例 MCUmgr 处理程序的 CMake 文件,并应放置于 CMakeLists.txt:

add_subdirectory(mcumgr/grp/<grp_name>)

Example Kconfig

这是一个示例 Kconfig 文件,它加载示例 MCUmgr 处理程序的 Kconfig 文件,并应放置于 Kconfig:

rsource "mcumgr/grp/<grp_name>/Kconfig"

演示处理程序

有一个演示项目,其中包含应用程序和 Zephyr 模块 MCUmgr 处理程序的配置,可作为创建自己的处理程序的基础,位于 tests/subsys/mgmt/mcumgr/handler_demo/。