Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

93 lines
2.4 KiB

Terminal: change command-line parser (#2247) 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.
4 years ago
  1. /*
  2. Part of the TERMINAL MODULE
  3. Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  4. Heavily inspired by the Embedis design:
  5. - https://github.com/thingSoC/embedis
  6. */
  7. #include <Arduino.h>
  8. #include "terminal_commands.h"
  9. #include <memory>
  10. namespace terminal {
  11. std::unordered_map<String, Terminal::CommandFunc,
  12. parsing::LowercaseFnv1Hash<String>,
  13. parsing::LowercaseEquals<String>> Terminal::commands;
  14. void Terminal::addCommand(const String& name, CommandFunc func) {
  15. if (!func) return;
  16. commands.emplace(std::make_pair(name, func));
  17. }
  18. size_t Terminal::commandsSize() {
  19. return commands.size();
  20. }
  21. std::vector<String> Terminal::commandNames() {
  22. std::vector<String> out;
  23. out.reserve(commands.size());
  24. for (auto& command : commands) {
  25. out.push_back(command.first);
  26. }
  27. return out;
  28. }
  29. Terminal::Result Terminal::processLine() {
  30. // Arduino stream API returns either `char` >= 0 or -1 on error
  31. int c = -1;
  32. while ((c = stream.read()) >= 0) {
  33. if (buffer.size() >= (buffer_size - 1)) {
  34. buffer.clear();
  35. return Result::BufferOverflow;
  36. }
  37. buffer.push_back(c);
  38. if (c == '\n') {
  39. // in case we see \r\n, offset minus one and overwrite \r
  40. auto end = buffer.end() - 1;
  41. if (*(end - 1) == '\r') {
  42. --end;
  43. }
  44. *end = '\0';
  45. // parser should pick out at least one arg (command)
  46. auto cmdline = parsing::parse_commandline(buffer.data());
  47. buffer.clear();
  48. if (cmdline.argc >= 1) {
  49. auto command = commands.find(cmdline.argv[0]);
  50. if (command == commands.end()) return Result::CommandNotFound;
  51. (*command).second(CommandContext{std::move(cmdline.argv), cmdline.argc, stream});
  52. return Result::Command;
  53. }
  54. }
  55. }
  56. // we need to notify about the fixable things
  57. if (buffer.size() && (c < 0)) {
  58. return Result::Pending;
  59. } else if (!buffer.size() && (c < 0)) {
  60. return Result::NoInput;
  61. // ... and some unexpected conditions
  62. } else {
  63. return Result::Error;
  64. }
  65. }
  66. bool Terminal::defaultProcessFunc(Result result) {
  67. return (result != Result::Error) && (result != Result::NoInput);
  68. }
  69. void Terminal::process(ProcessFunc func) {
  70. while (func(processLine())) {
  71. }
  72. }
  73. } // ns terminal