Skip to content

Commit 3e14b97

Browse files
committed
[shell] Reduce memory usage and boileplate code for subcommands
1. Add lightweight CommandSet class to prevent instantiating Shell::Engine, which allows for dynamic command registration, just to create a subshell with a fixed number of subcommands. This slightly reduces the RAM usage. Additionally, the command set automatically prints the help texts if no command or "help" command has been submitted. 2. Add SubShellCommand templatized command handler to remove the boilerplate associated with creating a subshell.
1 parent ffaeaa1 commit 3e14b97

15 files changed

+293
-251
lines changed

src/lib/shell/BUILD.gn

+4
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ import("${chip_root}/src/platform/device.gni")
2020

2121
source_set("shell_core") {
2222
sources = [
23+
"Command.h",
2324
"Commands.h",
25+
"CommandSet.cpp",
26+
"CommandSet.h",
2427
"Engine.cpp",
2528
"Engine.h",
2629
"streamer.cpp",
2730
"streamer.h",
31+
"SubShellCommand.h"
2832
]
2933

3034
public_deps = [

src/lib/shell/Command.h

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <lib/support/Span.h>
20+
21+
namespace chip {
22+
namespace Shell {
23+
24+
/**
25+
* Shell command descriptor structure.
26+
*
27+
* Typically a set of commands is defined as an array of this structure and registered
28+
* at the shell root using the @c Shell::Engine::Root().RegisterCommands() method, or
29+
* used to construct a @c CommandSet.
30+
*
31+
* Usage example:
32+
*
33+
* @code
34+
* static Shell::Command cmds[] = {
35+
* { &cmd_echo, "echo", "Echo back provided inputs" },
36+
* { &cmd_exit, "exit", "Exit the shell application" },
37+
* { &cmd_help, "help", "List out all top level commands" },
38+
* { &cmd_version, "version", "Output the software version" },
39+
* };
40+
* @endcode
41+
*/
42+
struct Command
43+
{
44+
/**
45+
* Shell command handler function type.
46+
*
47+
* @param argc Number of arguments in argv.
48+
* @param argv Array of arguments in the tokenized command line to execute.
49+
*/
50+
using Handler = CHIP_ERROR (*)(int argc, char * argv[]);
51+
52+
Handler cmd_func;
53+
const char * cmd_name;
54+
const char * cmd_help;
55+
};
56+
57+
// DEPRECATED:
58+
// shell_command_t is used in many examples, so keep it for backwards compatibility
59+
using shell_command_t = const Command;
60+
61+
} // namespace Shell
62+
} // namespace chip

src/lib/shell/CommandSet.cpp

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <lib/shell/CommandSet.h>
18+
#include <lib/shell/streamer.h>
19+
#include <lib/support/CodeUtils.h>
20+
21+
namespace chip {
22+
namespace Shell {
23+
24+
CHIP_ERROR CommandSet::ExecCommand(int argc, char * argv[]) const
25+
{
26+
if (argc == 0 || strcmp(argv[0], "help") == 0)
27+
{
28+
ShowHelp();
29+
return CHIP_NO_ERROR;
30+
}
31+
32+
for (const Command & command : mCommands)
33+
{
34+
if (strcmp(argv[0], command.cmd_name) == 0)
35+
{
36+
return command.cmd_func(argc - 1, argv + 1);
37+
}
38+
}
39+
40+
return CHIP_ERROR_INVALID_ARGUMENT;
41+
}
42+
43+
void CommandSet::ShowHelp() const
44+
{
45+
for (const Command & command : mCommands)
46+
{
47+
streamer_printf(streamer_get(), " %-15s %s\r\n", command.cmd_name, command.cmd_help);
48+
}
49+
}
50+
51+
} // namespace Shell
52+
} // namespace chip

src/lib/shell/CommandSet.h

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <lib/shell/Command.h>
20+
#include <lib/support/Span.h>
21+
22+
namespace chip {
23+
namespace Shell {
24+
25+
/**
26+
* Shell command set.
27+
*
28+
* The shell command set is a thin wrapper for the span of commands.
29+
* It facilitates executing a matching shell command for the given input arguments.
30+
*/
31+
class CommandSet
32+
{
33+
public:
34+
template <size_t N>
35+
constexpr CommandSet(const Command (&commands)[N]) : mCommands(commands)
36+
{}
37+
38+
/**
39+
* Dispatch and execute the command for the given argument list.
40+
*
41+
* The first argument is used to select the command to be executed and
42+
* the remaining arguments are forwarded to the command's handler.
43+
* If no argument has been provided or the first argument is "help", then
44+
* the function prints help text for each command and returns no error.
45+
*
46+
* @param argc Number of arguments in argv.
47+
* @param argv Array of arguments in the tokenized command line to execute.
48+
*/
49+
CHIP_ERROR ExecCommand(int argc, char * argv[]) const;
50+
51+
private:
52+
void ShowHelp() const;
53+
54+
Span<const Command> mCommands;
55+
};
56+
57+
} // namespace Shell
58+
} // namespace chip

src/lib/shell/Engine.h

+1-34
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "streamer.h"
2727

2828
#include <lib/core/CHIPError.h>
29+
#include <lib/shell/Command.h>
2930

3031
#include <stdarg.h>
3132
#include <stddef.h>
@@ -49,40 +50,6 @@
4950
namespace chip {
5051
namespace Shell {
5152

52-
/**
53-
* Callback to execute an individual shell command.
54-
*
55-
* @param argc Number of arguments passed.
56-
* @param argv Array of option strings. The command name is not included.
57-
*
58-
* @return 0 on success; CHIP_ERROR[...] on failure.
59-
*/
60-
typedef CHIP_ERROR shell_command_fn(int argc, char * argv[]);
61-
62-
/**
63-
* Descriptor structure for a single command.
64-
*
65-
* Typically a set of commands are defined as an array of this structure
66-
* and passed to the `shell_register()` during application initialization.
67-
*
68-
* An example command set definition follows:
69-
*
70-
* static shell_command_t cmds[] = {
71-
* { &cmd_echo, "echo", "Echo back provided inputs" },
72-
* { &cmd_exit, "exit", "Exit the shell application" },
73-
* { &cmd_help, "help", "List out all top level commands" },
74-
* { &cmd_version, "version", "Output the software version" },
75-
* };
76-
*/
77-
struct shell_command
78-
{
79-
shell_command_fn * cmd_func;
80-
const char * cmd_name;
81-
const char * cmd_help;
82-
};
83-
84-
typedef const struct shell_command shell_command_t;
85-
8653
/**
8754
* Execution callback for a shell command.
8855
*

src/lib/shell/SubShellCommand.h

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <lib/shell/CommandSet.h>
20+
21+
namespace chip {
22+
namespace Shell {
23+
24+
/**
25+
* Templatized shell command handler that runs one of the provided subcommands.
26+
*
27+
* The array of subcommands is provided as a non-type template parameter.
28+
*
29+
* The first argument is used to select the subcommand to be executed and
30+
* the remaining arguments are forwarded to the subcommand's handler.
31+
* If no argument has been provided or the first argument is "help", then
32+
* the function prints help text for each subcommand and returns no error.
33+
*
34+
* Usage example:
35+
* @code
36+
* constexpr Command subCommands[3] = {
37+
* {handler_a, "cmd_a", "command a help text"},
38+
* {handler_b, "cmd_b", "command b help text"},
39+
* {handler_c, "cmd_c", "command c help text"},
40+
* };
41+
*
42+
* // Execute the matching subcommand
43+
* SubShellCommand<3, subCommands>(argc, argv);
44+
* @endcode
45+
*
46+
* @param argc Number of arguments in argv.
47+
* @param argv Array of arguments in the tokenized command line to execute.
48+
*/
49+
template <size_t N, const Command (&C)[N]>
50+
inline CHIP_ERROR SubShellCommand(int argc, char ** argv)
51+
{
52+
static constexpr CommandSet commandSet(C);
53+
54+
return commandSet.ExecCommand(argc, argv);
55+
}
56+
57+
} // namespace Shell
58+
} // namespace chip

src/lib/shell/commands/BLE.cpp

+7-31
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#include <platform/CHIPDeviceLayer.h>
2222
#endif
2323
#include <lib/shell/Engine.h>
24-
#include <lib/shell/commands/Help.h>
24+
#include <lib/shell/SubShellCommand.h>
2525
#include <lib/support/CHIPArgParser.hpp>
2626
#include <lib/support/CHIPMem.h>
2727
#include <lib/support/CodeUtils.h>
@@ -31,21 +31,12 @@ using chip::DeviceLayer::ConnectivityMgr;
3131
namespace chip {
3232
namespace Shell {
3333

34-
static chip::Shell::Engine sShellDeviceSubcommands;
35-
36-
CHIP_ERROR BLEHelpHandler(int argc, char ** argv)
37-
{
38-
sShellDeviceSubcommands.ForEachCommand(PrintCommandHelp, nullptr);
39-
return CHIP_NO_ERROR;
40-
}
41-
4234
CHIP_ERROR BLEAdvertiseHandler(int argc, char ** argv)
4335
{
44-
CHIP_ERROR error = CHIP_NO_ERROR;
4536
streamer_t * sout = streamer_get();
4637
bool adv_enabled;
4738

48-
VerifyOrReturnError(argc == 1, error = CHIP_ERROR_INVALID_ARGUMENT);
39+
VerifyOrReturnError(argc == 1, CHIP_ERROR_INVALID_ARGUMENT);
4940

5041
adv_enabled = ConnectivityMgr().IsBLEAdvertisingEnabled();
5142
if (strcmp(argv[0], "start") == 0)
@@ -86,33 +77,18 @@ CHIP_ERROR BLEAdvertiseHandler(int argc, char ** argv)
8677
return CHIP_ERROR_INVALID_ARGUMENT;
8778
}
8879

89-
return error;
90-
}
91-
92-
CHIP_ERROR BLEDispatch(int argc, char ** argv)
93-
{
94-
if (argc == 0)
95-
{
96-
BLEHelpHandler(argc, argv);
97-
return CHIP_NO_ERROR;
98-
}
99-
return sShellDeviceSubcommands.ExecCommand(argc, argv);
80+
return CHIP_NO_ERROR;
10081
}
10182

10283
void RegisterBLECommands()
10384
{
104-
static const shell_command_t sBLESubCommands[] = {
105-
{ &BLEHelpHandler, "help", "Usage: ble <subcommand>" },
106-
{ &BLEAdvertiseHandler, "adv", "Enable or disable advertisement. Usage: ble adv <start|stop|state>" },
85+
static constexpr Command subCommands[] = {
86+
{ &BLEAdvertiseHandler, "adv", "Manage BLE advertising. Usage: ble adv <start|stop|state>" },
10787
};
10888

109-
static const shell_command_t sBLECommand = { &BLEDispatch, "ble", "BLE transport commands" };
110-
111-
// Register `device` subcommands with the local shell dispatcher.
112-
sShellDeviceSubcommands.RegisterCommands(sBLESubCommands, ArraySize(sBLESubCommands));
89+
static constexpr Command bleCommand = { &SubShellCommand<ArraySize(subCommands), subCommands>, "ble", "Bluetooth LE commands" };
11390

114-
// Register the root `btp` command with the top-level shell.
115-
Engine::Root().RegisterCommands(&sBLECommand, 1);
91+
Engine::Root().RegisterCommands(&bleCommand, 1);
11692
}
11793

11894
} // namespace Shell

0 commit comments

Comments
 (0)