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()
if(CONFIG_MCUMGR_GRP_EXAMPLE_APP)
target_sources(app PRIVATE example_as_module/src/example_mgmt.c)
zephyr_include_directories(example_as_module/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/。