162 lines
5.7 KiB
C++
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);
|
|
}
|
|
};
|
|
|