esp32displaytest/lib/Commander/Commander.cpp
2022-03-12 12:33:52 +01:00

383 lines
8.7 KiB
C++

#include <new>
#include <HardwareSerial.h>
#include "Commander.h"
Commander *Commander::serial_instance_ = nullptr;
void Commander::EnableSerial() {
assert(!running_);
assert(serial_instance_ == nullptr);
serial_instance_ = this;
enable_serial_ = true;
}
void Commander::SetFallbackHandler(const std::function<void(const std::vector<CmdString> &,
CommanderClient *)> &handler) {
assert(!running_);
handler_ = handler;
}
void Commander::Begin() {
assert(!running_);
running_ = true;
if (enable_serial_) {
serial_client_ = new SerialCommanderClient(this);
serial_client_->Begin();
serial_instance_ = this;
}
if (enable_wifi_) {
assert(port_ > 0);
tcp_server_ = new AsyncServer(port_);
tcp_server_->onClient(Commander::WifiOnClientHandler, this);
xTaskCreate(Commander::TaskFn, "Commander", 10000, this, 1, &this->task_handle_);
}
}
void Commander::Stop() {
assert(running_);
vTaskDelete(task_handle_);
// todo: remove the serial handler, disconnect all wifi tcp clients, stop the wifi tcp server
running_ = false;
}
void Commander::WifiOnClient(AsyncClient *client) {
wifi_clients_.emplace_back(this, client);
}
void Commander::WifiOnClientHandler(void *thisarg, AsyncClient *client) {
((Commander *)thisarg)->WifiOnClient(client);
}
void Commander::EnableWifi(uint16_t port) {
assert(!running_);
enable_wifi_ = true;
port_ = port;
}
void Command::SetPrompt(const String& prompt) {
prompt_ = prompt;
}
void Commander::TaskFn(void *thisarg) {
((Commander*)thisarg)->Task();
}
[[noreturn]] void Commander::Task() {
while(true) {
ulTaskNotifyTake(pdTRUE, 1000); // todo: number of ticks, can it be made infinite?
// todo: something something queue of newly connected wifi clients, actually use a queue here maybe instead of a notification
}
}
void CommanderClient::DipatchCurrentCommandLine() {
assert(!current_line_.empty());
// todo: somehow enter into new shell if a shell command was called without arguments
const Command* shell = shell_stack_.back()->Dispatch(current_line_, this);
if (shell != nullptr) {
shell_stack_.push_back(shell);
}
current_line_.clear();
}
void CommanderClient::ProcessCommandLine() {
if (current_line_.empty()) {
current_line_.emplace_back("");
}
size_t pos = 0;
for (char c : buffer_) {
auto &current = current_line_.back();
switch (c) {
case ' ':
if (in_quotes_ || in_escape_) {
current.concat(' ');
} else {
if (current.length() > 0) {
current_line_.emplace_back("");
}
}
break;
case '"':
if (in_escape_) {
current.concat('"');
in_escape_ = false;
} else {
if (in_quotes_) {
in_quotes_ = false;
current_line_.emplace_back("");
} else {
in_quotes_ = true;
if (current.length() > 0) {
current_line_.emplace_back("");
}
}
}
break;
case '\\':
if (in_escape_) {
current.concat('\\');
in_escape_ = false;
} else {
in_escape_ = true;
}
break;
case '\n':
if (in_escape_ || in_quotes_) {
current.concat('\n');
print("> "); // line continuation prompt
if (in_escape_) {
in_escape_ = false;
}
} else {
goto line_completed;
}
break;
default:
if (c >= 0x20) { // ignore control characters etc (also ignores tab)
current.concat(c);
}
break;
}
pos += 1;
}
goto end;
line_completed:
pos += 1;
DipatchCurrentCommandLine();
end:
buffer_.remove(0, pos);
}
const String &CommanderClient::GetPrompt() {
return shell_stack_.back()->prompt_;
}
const String &CommanderClient::GetWelcomeMessage() {
return shell_stack_.back()->welcome_message_;
}
void CommanderClient::BufferAppend(const String &str) {
buffer_.concat(str);
}
void CommanderClient::BufferAppend(char ch) {
buffer_.concat(ch);
}
/*
template<typename T>
bool CommanderClient::ReadLine(T *out) {
static_assert(!std::is_same<T, int64_t>::value, "64-bit not supported");
static_assert(!std::is_same<T, uint64_t>::value, "64-bit not supported");
static_assert(std::is_integral<T>::value, "integer type required");
CmdString line = ReadLine();
if (line.isEmpty()) return false;
int64_t res;
int i = 0;
if (!std::is_unsigned<T>::value) {
bool negative = false;
if (line[0] == '-') {
i = 1;
negative = true;
if (line.length() < 2) return false;
}
char ch = line[i];
if (ch >= '0' && ch <= '9') {
res = ch - '0';
if (negative) {
res = -res;
}
} else {
return false;
};
i += 1;
}
for (; i < line.length(); i++) {
char ch = line[i];
if (ch >= '0' && ch <= '9') {
uint8_t digit = ch - '0';
res *= 10;
res += digit;
if (res > std::numeric_limits<T>::max() || res < std::numeric_limits<T>::min()) {
return false;
}
} else {
return false;
}
}
*out = res;
return true;
}*/
CmdString CommanderClient::ReadLine() {
CmdString ret;
int c = read();
while (c != '\n') {
if (c >= 0x20) { // ignore control chars, todo: make this better I guess? line editing capabilities?
write(c);
ret += (char)c;
}
c = read();
}
write('\n');
return ret;
}
SerialCommanderClient *SerialCommanderClient::single_instance_ = nullptr;
size_t SerialCommanderClient::write(uint8_t byte) { return Serial.write(byte); }
int SerialCommanderClient::available() { return Serial.available(); }
int SerialCommanderClient::read() { return Serial.read(); }
int SerialCommanderClient::peek() { return Serial.peek(); }
void SerialCommanderClient::flush() { Serial.flush(); }
void SerialCommanderClient::OnReceive() {
assert(task_handle_ != nullptr);
vTaskNotifyGiveFromISR(task_handle_, nullptr);
}
SerialCommanderClient::SerialCommanderClient(Commander *commander) : CommanderClient(commander) {
assert(single_instance_ == nullptr);
single_instance_ = this;
println(GetWelcomeMessage());
println("");
print(GetPrompt());
}
void SerialCommanderClient::Task() {
while(true) {
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10000)); // todo: number of ticks, possible to wait infinitely long?
if (in_command_) continue;
in_command_ = true;
while(Serial.available()) {
int ch = Serial.read();
if (ch < 0) {
break;
}
Serial.write(ch); // echo the typed character so the user sees it
BufferAppend((char)ch);
if (ch == '\n') {
ProcessCommandLine();
print(GetPrompt());
}
}
in_command_ = false;
}
}
void WifiCommanderClient::OnData(void *data, size_t len) {
// todo
}
size_t WifiCommanderClient::write(uint8_t byte) {
// todo: also implement a write(const char*) or something like it
}
int WifiCommanderClient::available() {
// todo
}
int WifiCommanderClient::read() {
// todo
}
int WifiCommanderClient::peek() {
// todo
}
void WifiCommanderClient::flush() {
// todo
}
void WifiCommanderClient::OnDataHandler(void *thisarg, AsyncClient *, void *data, size_t len) {
((WifiCommanderClient *)thisarg)->OnData(data, len);
}
WifiCommanderClient::WifiCommanderClient(Commander *commander, AsyncClient *client)
: CommanderClient(commander), async_client_(client) {
async_client_->onData(WifiCommanderClient::OnDataHandler, this);
}
Command *Command::RegisterCommand(const String &name,
const std::function<void(const std::vector<CmdString> &, CommanderClient *)> &handler) {
commands_.push_back(Command(name, handler, false));
return &commands_[commands_.size() - 1];
}
Command *Command::RegisterCommandWithShell(const String &name) {
commands_.push_back(Command(name, nullptr, true));
return &commands_[commands_.size() - 1];
}
const Command* Command::Dispatch(std::vector<CmdString> cmdline, CommanderClient *client) const {
if (!cmdline.empty()) {
auto &first = cmdline.front();
for (const auto &item : commands_) {
if (item.name_.equals(first)) {
if (item.shell_) {
return &item;
}
item.Dispatch(std::vector<CmdString>(cmdline.begin() + 1, cmdline.end()),
client); // todo maybe optimize this vector
return nullptr;
}
}
if (first.equals("?")) {
DefaultHelpHandler(client);
return nullptr;
}
}
if (handler_ == nullptr) {
DefaultHandler(cmdline, client);
} else {
handler_(cmdline, client);
}
}
void Command::DefaultHandler(const std::vector<CmdString> &cmdline, CommanderClient *client) const {
if (commands_.empty()) {
client->println("this command is not implemented");
} else {
if (cmdline.empty()) {
DefaultHelpHandler(client);
} else {
client->print("unknown sub-command: ");
client->println(cmdline.front());
}
}
}
void Command::DefaultHelpHandler(CommanderClient *client) const {
client->println("the following commands are available: ");
for (const auto &item : commands_) {
client->print(" ");
client->print(item.name_);
client->print(" ");
client->print(item.description_);
client->println();
}
}
void Command::SetWelcomeMessage(const String &message) {
welcome_message_ = message;
}