Files
Leo-ElixirLights/components/tftpShell/include/cliWrapper.hpp
T
2025-01-01 21:15:01 +01:00

162 lines
5.7 KiB
C++

/**
* @file cliWrapper.hpp
* @author Gustice
* @brief Wrapping Command-Line-Interfaces with custom endpoints
* @details Command-Line-Interface is based on the implementation of
* https://github.com/funbiscuit/embedded-cli
*
* @copyright Copyright (c) 2024
*/
#pragma once
#include "embedded_cli.h"
#include <esp_log.h>
#include <functional>
#include <memory>
#include "simpleStreams.hpp"
/* Module configuration */
static constexpr int CLI_RX_BUFFER_SIZE = 64;
static constexpr int CLI_CMD_BUFFER_SIZE = 64; // to also accept URLs for Updates
static constexpr int CLI_HISTORY_SIZE = 16;
static constexpr int CLI_BINDING_COUNT = 8;
static constexpr std::string_view CliWelcomeString{
"** Smart-DMX-Bridge **\n"
" send 'help' for help-page\n"
" NOTE: Syslog-messages are sent to client of this session\n"};
/**
* @brief Wrapper for c-implementation of CLI
*/
class Cli {
public:
/// @brief Type for callback arguments
using CArgs = std::vector<std::string> &;
/// @brief Type for bind callback
using BindCb = std::function<void(CArgs)>;
/// @brief Type for command callback
using CommandCb = std::function<void(std::string, CArgs)>;
/// @brief Parameter structure for command-binding
struct Binding {
std::string_view name;
std::string_view help_output;
BindCb callback;
};
/// @brief Constructor
/// @param port Reference to serial port
/// @param cmd command callback
Cli(CharStream &port, CommandCb cmd)
: _port(port), _commandCb(cmd) {
EmbeddedCliConfig *config = embeddedCliDefaultConfig();
// NOTE: Note setting cliBuffer allows Cli to allocate needed buffer in correct size
// config->cliBuffer = cliBuffer;
// config->cliBufferSize = CLI_BUFFER_SIZE;
config->rxBufferSize = CLI_RX_BUFFER_SIZE;
config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
config->historyBufferSize = CLI_HISTORY_SIZE;
config->maxBindingCount = CLI_BINDING_COUNT;
cli = embeddedCliNew(config);
if (cli == NULL) {
ESP_LOGE("CLI", "Cli was not created, requires %d bytes. Check sizes!",
embeddedCliRequiredSize(config));
return;
}
cli->onCommand = genericCallback;
cli->writeChar = writeToken;
// appContext is a user-end variable and is bound to current instance
cli->appContext = this;
}
/// @brief Destructor
~Cli() {
embeddedCliFree(cli);
}
/// @brief Create command binding
bool addBinding(Binding bind) {
BindCb *cb = new BindCb{bind.callback};
CliCommandBinding cBinding{.name = bind.name.begin(),
.help = bind.help_output.begin(),
.tokenizeArgs = true,
// NOTE: this hard reference only works because we only provide
// an add-function without remove
.context = (void *)(cb),
.binding = boundCallback};
return embeddedCliAddBinding(cli, cBinding);
}
/// @brief Static method to generate c-compatible function-pointer for bound-command-callback
/// @details - receives and wraps arguments in string,
/// - determines object according to appContext
/// - calls appropriate method of appropriate object
static void boundCallback(EmbeddedCli *cli, char *args, void *context) {
if (context == nullptr) {
return;
ESP_LOGE("CLI", "No context in callback available");
}
ESP_LOGD("CLI", "Received command:");
BindCb &cb = *((BindCb *)context);
uint len = embeddedCliGetTokenCount(args);
std::vector<std::string> sArgs;
while (len-- > 0) {
const char *const arg = embeddedCliGetToken(args, 1);
ESP_LOGD("CLI", " arg: %s", arg);
sArgs.emplace_back(arg);
// shift position to end of this element to save time
args += sArgs.back().size() + 1; // needs also to be shifted by null-delimiter
}
cb(sArgs);
}
/// @brief Static method to generate c-compatible function-pointer for command-callback
/// @details - receives and wraps arguments in string,
/// - determines object according to appContext
/// - calls command-callback of appropriate object
static void genericCallback(EmbeddedCli *cli, CliCommand *command) {
std::string name(command->name);
ESP_LOGI("CLI", "Received generic command: %s", name.c_str());
embeddedCliTokenizeArgs(command->args);
std::vector<std::string> sArgs;
for (int i = 1; i <= embeddedCliGetTokenCount(command->args); ++i) {
sArgs.emplace_back(embeddedCliGetToken(command->args, i));
ESP_LOGI("CLI", " arg %d : %s", i, sArgs.back().c_str());
}
auto &commandCb = static_cast<Cli *>(cli->appContext)->_commandCb;
commandCb(name, sArgs);
}
/// @brief Process buffer and trigger commands
void process() {
auto r = _port.read();
for (auto &&c : r) {
embeddedCliReceiveChar(cli, c);
}
embeddedCliProcess(cli);
}
/// @brief publicly exposed write method
void write(std::string output) {
_port.write(output);
}
private:
/// @brief Reference to hardware port
CharStream &_port;
/// @brief Callback for generic command
CommandCb _commandCb;
EmbeddedCli *cli = nullptr;
/// @brief Write callback
static void writeToken(EmbeddedCli *embeddedCli, char c) {
static_cast<Cli *>(embeddedCli->appContext)->_port.write(c);
}
};