Skip to content

Commit

Permalink
[shell] Reduce memory usage and boileplate code for subcommands
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Damian-Nordic committed May 15, 2024
1 parent f4e02d4 commit 47b8d45
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 252 deletions.
4 changes: 4 additions & 0 deletions src/lib/shell/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ import("${chip_root}/src/platform/device.gni")

source_set("shell_core") {
sources = [
"Command.h",
"CommandSet.cpp",
"CommandSet.h",
"Commands.h",
"Engine.cpp",
"Engine.h",
"SubShellCommand.h",
"streamer.cpp",
"streamer.h",
]
Expand Down
62 changes: 62 additions & 0 deletions src/lib/shell/Command.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <lib/support/Span.h>

namespace chip {
namespace Shell {

/**
* Shell command descriptor structure.
*
* Typically a set of commands is defined as an array of this structure and registered
* at the shell root using the @c Shell::Engine::Root().RegisterCommands() method, or
* used to construct a @c CommandSet.
*
* Usage example:
*
* @code
* static Shell::Command cmds[] = {
* { &cmd_echo, "echo", "Echo back provided inputs" },
* { &cmd_exit, "exit", "Exit the shell application" },
* { &cmd_help, "help", "List out all top level commands" },
* { &cmd_version, "version", "Output the software version" },
* };
* @endcode
*/
struct Command
{
/**
* Shell command handler function type.
*
* @param argc Number of arguments in argv.
* @param argv Array of arguments in the tokenized command line to execute.
*/
using Handler = CHIP_ERROR (*)(int argc, char * argv[]);

Handler cmd_func;
const char * cmd_name;
const char * cmd_help;
};

// DEPRECATED:
// shell_command_t is used in many examples, so keep it for backwards compatibility
using shell_command_t = const Command;

} // namespace Shell
} // namespace chip
52 changes: 52 additions & 0 deletions src/lib/shell/CommandSet.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <lib/shell/CommandSet.h>
#include <lib/shell/streamer.h>
#include <lib/support/CodeUtils.h>

namespace chip {
namespace Shell {

CHIP_ERROR CommandSet::ExecCommand(int argc, char * argv[]) const
{
if (argc == 0 || strcmp(argv[0], "help") == 0)
{
ShowHelp();
return CHIP_NO_ERROR;
}

for (const Command & command : mCommands)
{
if (strcmp(argv[0], command.cmd_name) == 0)
{
return command.cmd_func(argc - 1, argv + 1);
}
}

return CHIP_ERROR_INVALID_ARGUMENT;
}

void CommandSet::ShowHelp() const
{
for (const Command & command : mCommands)
{
streamer_printf(streamer_get(), " %-15s %s\r\n", command.cmd_name, command.cmd_help);
}
}

} // namespace Shell
} // namespace chip
58 changes: 58 additions & 0 deletions src/lib/shell/CommandSet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2024 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <lib/shell/Command.h>
#include <lib/support/Span.h>

namespace chip {
namespace Shell {

/**
* Shell command set.
*
* The shell command set is a thin wrapper for the span of commands.
* It facilitates executing a matching shell command for the given input arguments.
*/
class CommandSet
{
public:
template <size_t N>
constexpr CommandSet(const Command (&commands)[N]) : mCommands(commands)
{}

/**
* Dispatch and execute the command for the given argument list.
*
* The first argument is used to select the command to be executed and
* the remaining arguments are forwarded to the command's handler.
* If no argument has been provided or the first argument is "help", then
* the function prints help text for each command and returns no error.
*
* @param argc Number of arguments in argv.
* @param argv Array of arguments in the tokenized command line to execute.
*/
CHIP_ERROR ExecCommand(int argc, char * argv[]) const;

private:
void ShowHelp() const;

Span<const Command> mCommands;
};

} // namespace Shell
} // namespace chip
35 changes: 1 addition & 34 deletions src/lib/shell/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "streamer.h"

#include <lib/core/CHIPError.h>
#include <lib/shell/Command.h>

#include <stdarg.h>
#include <stddef.h>
Expand All @@ -49,40 +50,6 @@
namespace chip {
namespace Shell {

/**
* Callback to execute an individual shell command.
*
* @param argc Number of arguments passed.
* @param argv Array of option strings. The command name is not included.
*
* @return 0 on success; CHIP_ERROR[...] on failure.
*/
typedef CHIP_ERROR shell_command_fn(int argc, char * argv[]);

/**
* Descriptor structure for a single command.
*
* Typically a set of commands are defined as an array of this structure
* and passed to the `shell_register()` during application initialization.
*
* An example command set definition follows:
*
* static shell_command_t cmds[] = {
* { &cmd_echo, "echo", "Echo back provided inputs" },
* { &cmd_exit, "exit", "Exit the shell application" },
* { &cmd_help, "help", "List out all top level commands" },
* { &cmd_version, "version", "Output the software version" },
* };
*/
struct shell_command
{
shell_command_fn * cmd_func;
const char * cmd_name;
const char * cmd_help;
};

typedef const struct shell_command shell_command_t;

/**
* Execution callback for a shell command.
*
Expand Down
58 changes: 58 additions & 0 deletions src/lib/shell/SubShellCommand.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2024 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <lib/shell/CommandSet.h>

namespace chip {
namespace Shell {

/**
* Templatized shell command handler that runs one of the provided subcommands.
*
* The array of subcommands is provided as a non-type template parameter.
*
* The first argument is used to select the subcommand to be executed and
* the remaining arguments are forwarded to the subcommand's handler.
* If no argument has been provided or the first argument is "help", then
* the function prints help text for each subcommand and returns no error.
*
* Usage example:
* @code
* constexpr Command subCommands[3] = {
* {handler_a, "cmd_a", "command a help text"},
* {handler_b, "cmd_b", "command b help text"},
* {handler_c, "cmd_c", "command c help text"},
* };
*
* // Execute the matching subcommand
* SubShellCommand<3, subCommands>(argc, argv);
* @endcode
*
* @param argc Number of arguments in argv.
* @param argv Array of arguments in the tokenized command line to execute.
*/
template <size_t N, const Command (&C)[N]>
inline CHIP_ERROR SubShellCommand(int argc, char ** argv)
{
static constexpr CommandSet commandSet(C);

return commandSet.ExecCommand(argc, argv);
}

} // namespace Shell
} // namespace chip
38 changes: 7 additions & 31 deletions src/lib/shell/commands/BLE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include <platform/CHIPDeviceLayer.h>
#endif
#include <lib/shell/Engine.h>
#include <lib/shell/commands/Help.h>
#include <lib/shell/SubShellCommand.h>
#include <lib/support/CHIPArgParser.hpp>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
Expand All @@ -31,21 +31,12 @@ using chip::DeviceLayer::ConnectivityMgr;
namespace chip {
namespace Shell {

static chip::Shell::Engine sShellDeviceSubcommands;

CHIP_ERROR BLEHelpHandler(int argc, char ** argv)
{
sShellDeviceSubcommands.ForEachCommand(PrintCommandHelp, nullptr);
return CHIP_NO_ERROR;
}

CHIP_ERROR BLEAdvertiseHandler(int argc, char ** argv)
{
CHIP_ERROR error = CHIP_NO_ERROR;
streamer_t * sout = streamer_get();
bool adv_enabled;

VerifyOrReturnError(argc == 1, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(argc == 1, CHIP_ERROR_INVALID_ARGUMENT);

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

return error;
}

CHIP_ERROR BLEDispatch(int argc, char ** argv)
{
if (argc == 0)
{
BLEHelpHandler(argc, argv);
return CHIP_NO_ERROR;
}
return sShellDeviceSubcommands.ExecCommand(argc, argv);
return CHIP_NO_ERROR;
}

void RegisterBLECommands()
{
static const shell_command_t sBLESubCommands[] = {
{ &BLEHelpHandler, "help", "Usage: ble <subcommand>" },
{ &BLEAdvertiseHandler, "adv", "Enable or disable advertisement. Usage: ble adv <start|stop|state>" },
static constexpr Command subCommands[] = {
{ &BLEAdvertiseHandler, "adv", "Manage BLE advertising. Usage: ble adv <start|stop|state>" },
};

static const shell_command_t sBLECommand = { &BLEDispatch, "ble", "BLE transport commands" };

// Register `device` subcommands with the local shell dispatcher.
sShellDeviceSubcommands.RegisterCommands(sBLESubCommands, ArraySize(sBLESubCommands));
static constexpr Command bleCommand = { &SubShellCommand<ArraySize(subCommands), subCommands>, "ble", "Bluetooth LE commands" };

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

} // namespace Shell
Expand Down
Loading

0 comments on commit 47b8d45

Please sign in to comment.