mirror of
https://github.com/xoseperez/espurna.git
synced 2026-03-05 07:54:18 +01:00
Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
94 lines
2.4 KiB
C++
94 lines
2.4 KiB
C++
/*
|
|
|
|
Part of the TERMINAL MODULE
|
|
|
|
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
|
|
|
|
Heavily inspired by the Embedis design:
|
|
- https://github.com/thingSoC/embedis
|
|
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
|
|
#include "terminal_commands.h"
|
|
|
|
#include <memory>
|
|
|
|
namespace terminal {
|
|
|
|
std::unordered_map<String, Terminal::CommandFunc,
|
|
parsing::LowercaseFnv1Hash<String>,
|
|
parsing::LowercaseEquals<String>> Terminal::commands;
|
|
|
|
void Terminal::addCommand(const String& name, CommandFunc func) {
|
|
if (!func) return;
|
|
commands.emplace(std::make_pair(name, func));
|
|
}
|
|
|
|
size_t Terminal::commandsSize() {
|
|
return commands.size();
|
|
}
|
|
|
|
std::vector<String> Terminal::commandNames() {
|
|
std::vector<String> out;
|
|
out.reserve(commands.size());
|
|
for (auto& command : commands) {
|
|
out.push_back(command.first);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Terminal::Result Terminal::processLine() {
|
|
|
|
// Arduino stream API returns either `char` >= 0 or -1 on error
|
|
int c = -1;
|
|
while ((c = stream.read()) >= 0) {
|
|
if (buffer.size() >= (buffer_size - 1)) {
|
|
buffer.clear();
|
|
return Result::BufferOverflow;
|
|
}
|
|
buffer.push_back(c);
|
|
if (c == '\n') {
|
|
// in case we see \r\n, offset minus one and overwrite \r
|
|
auto end = buffer.end() - 1;
|
|
if (*(end - 1) == '\r') {
|
|
--end;
|
|
}
|
|
*end = '\0';
|
|
|
|
// parser should pick out at least one arg (command)
|
|
auto cmdline = parsing::parse_commandline(buffer.data());
|
|
buffer.clear();
|
|
if (cmdline.argc >= 1) {
|
|
auto command = commands.find(cmdline.argv[0]);
|
|
if (command == commands.end()) return Result::CommandNotFound;
|
|
(*command).second(CommandContext{std::move(cmdline.argv), cmdline.argc, stream});
|
|
return Result::Command;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we need to notify about the fixable things
|
|
if (buffer.size() && (c < 0)) {
|
|
return Result::Pending;
|
|
} else if (!buffer.size() && (c < 0)) {
|
|
return Result::NoInput;
|
|
// ... and some unexpected conditions
|
|
} else {
|
|
return Result::Error;
|
|
}
|
|
|
|
}
|
|
|
|
bool Terminal::defaultProcessFunc(Result result) {
|
|
return (result != Result::Error) && (result != Result::NoInput);
|
|
}
|
|
|
|
void Terminal::process(ProcessFunc func) {
|
|
while (func(processLine())) {
|
|
}
|
|
}
|
|
|
|
} // ns terminal
|