util::command
Overview
Typically, our embedded targets allow debug access through a simple shell over the console (serial port). The command shell interface is part of the util module, allowing all BSW modules to define commands for this shell without introducing additional dependencies.
The util::command::ICommand
interface is designed for implementing custom
commands. However, it is rare for a custom command to implement this interface
directly. Typically, a custom command derives from or reuses one of the existing
implementations available within the command namespace.
SimpleCommand and ParentCommand
A simple command can be easily created using the util::command::SimpleCommand
class,
which can be constructed with all necessary parameters. No derivation is
required to implement a simple command. Its functionality can be encapsulated in a
(member) function and passed as a delegate via an estd::function
object.
For groups or hierarchies of commands a util::command::ParentCommand
object can be instantiated.
It is constructed with the necessary parameters (name and description) and allows child commands
to be added, which can be executed as subcommands. Arbitrary command hierarchies can be created by
nesting parent commands within each other.
Example
The following code implements a simple command named “test” that includes two subcommands: “get” and “put”, which allow read and write access to a contained value.
The header file TestCommand.h
could look like:
#include "util/command/ParentCommand.h"
#include "util/command/SimpleCommand.h"
#include "util/format/SharedStringWriter.h"
namespace test
{
class TestCommand: public ::util::command::ParentCommand
{
public:
TestCommand();
private:
void get(::util::command::CommandContext& context);
void put(::util::command::CommandContext& context);
::util::command::SimpleCommand _get;
::util::command::SimpleCommand _put;
uint32_t _value;
};
} // namespace test
and the corresponding source file:
#include "TestCommand.h"
#include "util/format/SharedStringWriter.h"
namespace test
{
using namespace ::util::command;
using namespace ::util::format;
using ExecuteFunction = ::estd::function<void(CommandContext& context)>;
TestCommand::TestCommand()
: ParentCommand("test", "Contains simple test commands.")
, _get(
"get",
"Get the test value.",
SimpleCommand::ExecuteFunction::create<TestCommand, &TestCommand::get>(*this),
this
)
, _put("put",
"Put the test value.",
SimpleCommand::ExecuteFunction::create<TestCommand, &TestCommand::put>(*this),
this
)
, _value(0)
{
addCommand(_get);
addCommand(_put);
}
void
TestCommand::get(CommandContext& context)
{
if (context.checkEol())
{
SharedStringWriter(context).printf("Value is %d", _value);
}
}
void
TestCommand::put(CommandContext& context)
{
uint32_t value = context.scanIntToken<uint32_t>();
if (context.checkEol())
{
_value = value;
}
}
} // namespace test
GroupCommand
If minimizing RAM usage is a priority, you should use the GroupCommand
class
as the base for your command functionality. This class replaces the bios::CommandInterpreter
base class. The
util::command::GroupCommand
serves as an alternative to the
util::command::ParentCommand
class but supports only a two-level hierarchy (parent-child).
Example
The file TestCommand.h
counter part to the ParentCommand
/SimpleCommand
implementation of
TestCommand could then look like:
#include "util/command/GroupCommand.h"
#include "util/format/SharedStringWriter.h"
namespace test
{
class TestCommand : public GroupCommand
{
public:
TestCommand() = default;
protected:
DECLARE_COMMAND_GROUP_GET_INFO
void executeCommand(::util::command::CommandContext& context, uint8_t idx) override;
private:
enum Id
{
ID_GET,
ID_PUT
};
uint32_t _value = 0;
};
} // namespace test
with the corresponding source file:
#include "TestCommand.h"
#include "util/format/SharedStringWriter.h"
namespace test
{
using namespace ::util::command;
using namespace ::util::format;
DEFINE_COMMAND_GROUP_GET_INFO_BEGIN(TestCommand, "test", "Contains simple test commands.")
COMMAND_GROUP_COMMAND(ID_GET, "get", "Get the test value.")
COMMAND_GROUP_COMMAND(ID_PUT, "put", "Put the test value.")
DEFINE_COMMAND_GROUP_GET_INFO_END
// Implement the virtual function in the class:
void TestCommand::executeCommand(CommandContext& context, uint8_t idx)
{
switch (idx)
{
case ID_GET:
if (context.checkEol())
{
SharedStringWriter(context).printf("Value is %d", _value);
}
break;
case ID_PUT:
{
uint32_t value = context.scanIntToken<uint32_t>();
if (context.checkEol())
{
_value = value;
}
}
break;
default: break;
}
}
} // namespace test
CommandContext
The default implementations heavily rely on the
util::command::CommandContext
class. Context objects of this type provide
access to command-line arguments and an output stream for presenting textual results.
Commands require a defined list of arguments. The util::command::CommandContext
class offers helpful
methods for parsing arguments, such as identifiers, integer values, hex byte buffers, or arbitrary
whitespace-separated tokens. Additionally, it includes methods for checking conditions on the
arguments or determining if there are pending arguments. Refer to util::command::CommandContext
for a more detailed description.