command - Command Framework
Overview command Module
Typically our embedded targets are allowing debug access via a simple shell over the console (serial port). The new command shell interface is located in the util project and thus allows all bsw modules to simply define commands for this shell without adding further dependencies.
The interface ::util::command::ICommand
is dedicated for implementing such custom
commands. But there are only rare cases when a custom command needs to implement this interface
directly. Typically a custom command will derive or reuse one of the existing implementations that
can be found within the command namespace.
SimpleCommand and ParentCommand
A simple command can be easily built by using the class ::util::command::SimpleCommand
which can be constructed with all necessary parameters. No derivation is needed to implement a
simple command, the functionality of this command can easily be packed into a (member) method and
given as a delegate via a estd function object.
For groups or hierarchies of commands a ParentCommand
object can be instantiated. It can be
constructed with all necessary parameters (name and description) and allows adding child commands
that may be executed as sub commands. Arbitrary command hierarchies can be created by nesting
parent commands into each other.
Example
The following code implements a simple command named “test” that offers the two sub commands “get” and “put” that 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;
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)
{}
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
Whenever you need a small footprint command regarding RAM usage then you should use the
GroupCommand
as a base class for your command functionality. This class is the replacement for
the former ::bios::CommandInterpreter
base class.
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
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 are heavily based on the usage of the class
::util::command::CommandContext
. Context objects of this type are used for providing
access to the command line arguments and the output stream for presenting textual results.
A command will need a defined list of arguments. The CommandContext
class provides some useful
methods for scanning arguments such as identifiers, integer values, hex byte buffers or arbitrary
whitespace separated tokens. Additionally there are methods for checking certain conditions on the
arguments or whether there are pending arguments. See ::util::command::CommandContext
for a more detailed description.