#include #include #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 &, 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 ¤t = 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 bool CommanderClient::ReadLine(T *out) { static_assert(!std::is_same::value, "64-bit not supported"); static_assert(!std::is_same::value, "64-bit not supported"); static_assert(std::is_integral::value, "integer type required"); CmdString line = ReadLine(); if (line.isEmpty()) return false; int64_t res; int i = 0; if (!std::is_unsigned::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::max() || res < std::numeric_limits::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 &, 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 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(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 &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; }