319 lines
6.7 KiB
C++
319 lines
6.7 KiB
C++
#ifndef ESP32DISPLAYTEST_LIB_COMMANDER_COMMANDER_H_
|
|
#define ESP32DISPLAYTEST_LIB_COMMANDER_COMMANDER_H_
|
|
|
|
#include <functional>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <WString.h>
|
|
#include <WiFiServer.h>
|
|
#include <AsyncTCP.h>
|
|
#include <Stream.h>
|
|
|
|
|
|
class CmdString : public String {
|
|
|
|
public:
|
|
CmdString(const char *cstr = "") : String(cstr){}
|
|
CmdString(const String& str) : String(str){}
|
|
|
|
|
|
template<typename T>
|
|
bool ReadInto(T *out) const {
|
|
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");
|
|
|
|
if (isEmpty()) return false;
|
|
int64_t res;
|
|
int i = 0;
|
|
|
|
if (!std::is_unsigned<T>::value) {
|
|
bool negative = false;
|
|
if (this->charAt(0) == '-') {
|
|
i = 1;
|
|
negative = true;
|
|
if (length() < 2) return false;
|
|
}
|
|
|
|
char ch = this->charAt(i);
|
|
if (ch >= '0' && ch <= '9') {
|
|
res = ch - '0';
|
|
if (negative) {
|
|
res = -res;
|
|
}
|
|
} else {
|
|
return false;
|
|
};
|
|
i += 1;
|
|
}
|
|
|
|
for (; i < length(); i++) {
|
|
char ch = this->charAt(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;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
class Commander;
|
|
|
|
class Command;
|
|
|
|
class CommanderClient;
|
|
|
|
class SerialCommanderClient;
|
|
|
|
class WifiCommanderClient;
|
|
|
|
class Command {
|
|
friend class CommanderClient;
|
|
|
|
protected:
|
|
String name_;
|
|
String description_;
|
|
std::function<void(std::vector<CmdString>, CommanderClient *)> handler_;
|
|
std::vector<Command> commands_;
|
|
|
|
bool shell_;
|
|
|
|
String prompt_ = "> ";
|
|
String welcome_message_ = "";
|
|
|
|
Command(const String &name, std::function<void(std::vector<CmdString>, CommanderClient *)> handler, bool shell)
|
|
: handler_(std::move(handler)), shell_(shell) {
|
|
int space_index = name.indexOf(' ');
|
|
if (space_index == -1) {
|
|
name_ = name;
|
|
description_ = "";
|
|
} else {
|
|
name_ = name.substring(0, space_index);
|
|
description_ = name.substring(space_index);
|
|
}
|
|
}
|
|
|
|
const Command* Dispatch(std::vector<CmdString> cmdline, CommanderClient *client) const;
|
|
|
|
void DefaultHandler(const std::vector<CmdString> &cmdline, CommanderClient *client) const;
|
|
|
|
void DefaultHelpHandler(CommanderClient *client) const;
|
|
|
|
public:
|
|
Command *RegisterCommand(const String &name,
|
|
const std::function<void(const std::vector<CmdString> &,
|
|
CommanderClient *)> &handler);
|
|
|
|
Command *RegisterCommandWithShell(const String &name);
|
|
|
|
void SetPrompt(const String &prompt);
|
|
|
|
void SetWelcomeMessage(const String &message);
|
|
|
|
};
|
|
|
|
class Commander : public Command {
|
|
friend CommanderClient;
|
|
private:
|
|
bool enable_wifi_ = false;
|
|
bool enable_serial_ = false;
|
|
uint16_t port_ = 0;
|
|
AsyncServer *tcp_server_{};
|
|
std::vector<WifiCommanderClient> wifi_clients_;
|
|
SerialCommanderClient *serial_client_{};
|
|
|
|
bool running_ = false;
|
|
|
|
static Commander *serial_instance_;
|
|
|
|
TaskHandle_t task_handle_{};
|
|
|
|
public:
|
|
|
|
Commander() : Command("", Commander::DefaultFallbackHandler, false) {}
|
|
|
|
using Command::RegisterCommand;
|
|
using Command::RegisterCommandWithShell;
|
|
|
|
void EnableSerial();
|
|
void EnableWifi(uint16_t port);
|
|
|
|
void SetFallbackHandler(const std::function<void(const std::vector<CmdString> &,
|
|
CommanderClient *)> &handler);
|
|
|
|
void Begin();
|
|
|
|
void Stop();
|
|
|
|
private:
|
|
void WifiOnClient(AsyncClient *client);
|
|
|
|
void SerialReceive();
|
|
|
|
[[noreturn]] void Task();
|
|
|
|
static void SerialReceiveHandler();
|
|
|
|
static void WifiOnClientHandler(void *thisarg, AsyncClient *client);
|
|
|
|
[[noreturn]] static void TaskFn(void *thisarg);
|
|
|
|
static void DefaultFallbackHandler(std::vector<CmdString> cmd, CommanderClient *client) {
|
|
|
|
}
|
|
};
|
|
|
|
class CommanderClient : public Stream {
|
|
protected:
|
|
std::vector<const Command *> shell_stack_;
|
|
|
|
bool in_command_ = false;
|
|
|
|
String buffer_ = "";
|
|
|
|
std::vector<CmdString> current_line_;
|
|
bool in_quotes_ = false;
|
|
bool in_escape_ = false;
|
|
bool previous_was_cr_ = false;
|
|
|
|
TaskHandle_t task_handle_ = nullptr;
|
|
|
|
void BufferAppend(const String &str);
|
|
|
|
void BufferAppend(char ch);
|
|
|
|
void DipatchCurrentCommandLine();
|
|
|
|
void ProcessCommandLine();
|
|
|
|
const String &GetPrompt();
|
|
const String &GetWelcomeMessage();
|
|
|
|
virtual void Task() = 0;
|
|
|
|
static void TaskFn(void *thisarg) {
|
|
((CommanderClient *)thisarg)->Task();
|
|
}
|
|
|
|
public:
|
|
explicit CommanderClient(Commander *commander) {
|
|
shell_stack_.push_back(commander);
|
|
}
|
|
|
|
virtual void Begin() {
|
|
xTaskCreate(CommanderClient::TaskFn, "commander client", 10000, this, 1, &this->task_handle_);
|
|
}
|
|
|
|
CmdString ReadLine();
|
|
|
|
template<typename T>
|
|
bool 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;
|
|
}
|
|
|
|
};
|
|
|
|
class SerialCommanderClient : public CommanderClient {
|
|
|
|
static SerialCommanderClient *single_instance_;
|
|
|
|
public:
|
|
explicit SerialCommanderClient(Commander *commander);
|
|
|
|
void OnReceive();
|
|
|
|
size_t write(uint8_t byte) override;
|
|
int available() override;
|
|
int read() override;
|
|
int peek() override;
|
|
void flush() override;
|
|
|
|
void Begin() override {
|
|
CommanderClient::Begin();
|
|
Serial.onReceive(SerialCommanderClient::OnReceiveHandler);
|
|
}
|
|
|
|
[[noreturn]] void Task() override;
|
|
|
|
static void OnReceiveHandler() {
|
|
single_instance_->OnReceive();
|
|
}
|
|
};
|
|
|
|
class WifiCommanderClient : public CommanderClient {
|
|
AsyncClient *async_client_;
|
|
public:
|
|
explicit WifiCommanderClient(Commander *commander, AsyncClient *client);
|
|
|
|
void OnData(void *data, size_t len);
|
|
|
|
size_t write(uint8_t byte) override;
|
|
int available() override;
|
|
int read() override;
|
|
int peek() override;
|
|
void flush() override;
|
|
|
|
void Task() {};
|
|
|
|
private:
|
|
static void OnDataHandler(void *thisarg, AsyncClient *, void *data, size_t len);
|
|
};
|
|
|
|
#endif //ESP32DISPLAYTEST_LIB_COMMANDER_COMMANDER_H_
|