Avionics
Core avionics package for CURE flight computers
Loading...
Searching...
No Matches
UARTCommandHandler.cpp
Go to the documentation of this file.
2#include "ArduinoHAL.h"
3#include <algorithm>
4
5constexpr int kCommandCharsAsciiEnd = 31; // ASCII control characters end at 31, so we can ignore those in input
6
7
8CommandLine::CommandLine(Stream* uartStream) : uart_(uartStream), defaultUart_(uartStream) {
9}
10
12 if (newUart == nullptr) {
13 return;
14 }
15 uart_ = newUart;
16}
17
19 uart_ = defaultUart_;
20}
21
23 help();
24 uart_->print(kShellPrompt);
25}
26
27static bool isBackspace_(char receivedChar) {
28 return receivedChar == static_cast<char>(AsciiKey::Backspace) ||
29 receivedChar == static_cast<char>(AsciiKey::Delete);
30}
31
32static bool isNewline_(char receivedChar) {
33 return receivedChar == '\n' || receivedChar == '\r';
34}
35
36namespace {
37// Small helper: split on spaces/tabs (no quoting support)
38// Parses a line into a command and arguments, e.g. "foo bar baz" -> cmd="foo", args={"bar", "baz"}
39void tokenizeWhitespace(const std::string& line,
40 std::string& outCmd,
41 std::queue<std::string>& outArgs)
42{
43 outCmd.clear();
44 while (!outArgs.empty()) {
45 outArgs.pop();
46 }
47
48 std::string token;
49 auto flush = [&]() {
50 if (token.empty()) {return;}
51 if (outCmd.empty()) {outCmd = token;}
52 else {outArgs.push(token);}
53 token.clear();
54 };
55
56 for (char ch : line) {
57 if (ch == ' ' || ch == '\t') {flush();}
58 else {token += ch;}
59 }
60 flush();
61}
62} // namespace
63
64void CommandLine::readInput() { // NOLINT(readability-function-cognitive-complexity)
65 bool consumedInputThisCall = false;
66
67 while (uart_->available() > 0) {
68 consumedInputThisCall = true;
69 const char receivedChar = static_cast<char>(uart_->read());
70
71 if (isBackspace_(receivedChar)) {
72 lastWasCR_ = false;
73 handleBackspace_();
74 } else if (isNewline_(receivedChar)) {
75 if (lastWasCR_ && receivedChar == '\n') {
76 lastWasCR_ = false; // Treat CRLF as a single newline event.
77 continue;
78 }
79 lastWasCR_ = (receivedChar == '\r');
80 handleNewline_();
81 } else {
82 lastWasCR_ = false;
83 handleChar_(receivedChar);
84 }
85 }
86
87 if (consumedInputThisCall) {
88 lastInteractionTimestamp_ = static_cast<uint32_t>(millis());
89 }
90}
91
92void CommandLine::handleBackspace_() {
93 if (fullLine_.empty()) {return;}
94 fullLine_.pop_back();
95 uart_->print("\b \b"); // erase last char on terminal
96}
97
98void CommandLine::handleNewline_() {
99 uart_->println();
100
101 std::string line = fullLine_;
102 fullLine_.clear();
103
104 trimSpaces(line);
105 if (line.empty()) {
106 uart_->print(kShellPrompt);
107 return;
108 }
109
110 std::string cmd = {""};
111 std::queue<std::string> args = {}; //NOLINT(cppcoreguidelines-init-variables)
112 tokenizeWhitespace(line, cmd, args);
113
114 if (!cmd.empty()) {
115 executeCommand(cmd, args);
116 }
117
118 uart_->print(kShellPrompt);
119}
120
121void CommandLine::handleChar_(char receivedChar) {
122 // Optional: ignore other control chars (keep tab if you want)
123 if (static_cast<unsigned char>(receivedChar) <= kCommandCharsAsciiEnd && receivedChar != '\t') {return;}
124
125 if (fullLine_.length() >= kUartBufferSize - 1) {
126 uart_->println();
127 uart_->println("Buffer overflow, input ignored.");
128 fullLine_.clear();
129 uart_->print(kShellPrompt);
130 return;
131 }
132
133 fullLine_ += receivedChar;
134 uart_->print(receivedChar);
135}
136
137
138// Add a command with its long name, short name, and function pointer
139void CommandLine::addCommand(const std::string& longName, const std::string& shortName, std::function<void(std::queue<std::string>, std::string&)> funcPtr) { //NOLINT(readability-convert-member-functions-to-static)
140 Command newCommand{ longName, shortName, funcPtr };
141 commands_.push_back(newCommand);
142}
143
144// Execute a command based on its long name or short name
145void CommandLine::executeCommand(const std::string& command, std::queue<std::string> arguments) {
146 // Check if the user entered "help" or "?"
147 if (command == "help" || command == "?") {
148 help();
149 return;
150 }
151
152 std::string response;
153 for (const auto& cmd : commands_) {
154 if (cmd.longName == command || cmd.shortName == command) {
155
156 cmd.funcPtr(arguments, response);
157 return;
158 }
159 }
160
161 // Print the name of the command that was not found, and suggest using "help" to see available commands.
162 uart_->println("Command not found: " + String(command.c_str()));
163 uart_->println("Type 'help' or '?' to see available commands.");
164}
165
166// Help function to list all commands with their long and short names
167void CommandLine::help(){
168 if (commands_.empty()) {
169 uart_->println("No commands available.");
170 uart_->println("help<?>");
171 return;
172 }
173
174 for (const auto& cmd : commands_) {
175 uart_->println(String(cmd.longName.c_str()) + "<" + String(cmd.shortName.c_str()) + ">");
176 }
177 uart_->println("help<?>");
178
179}
180
181void CommandLine::trimSpaces(std::string& str) { //NOLINT(readability-convert-member-functions-to-static)
182 // Remove leading spaces
183 size_t start = str.find_first_not_of(" "); //NOLINT(cppcoreguidelines-init-variables)
184 // If there's any non-space character
185 if (start != std::string::npos) {
186 // Remove trailing spaces
187 size_t end = str.find_last_not_of(" "); //NOLINT(cppcoreguidelines-init-variables)
188 str = str.substr(start, end - start + 1);
189 } else {
190 // If there are only spaces, clear the string
191 str.clear();
192 }
193}
constexpr int kCommandCharsAsciiEnd
constexpr int kUartBufferSize
constexpr const char * kShellPrompt
CommandLine(Stream *uartStream)
void switchUART(Stream *newUart)
void executeCommand(const std::string &command, std::queue< std::string > arguments)
void addCommand(const std::string &longName, const std::string &shortName, std::function< void(std::queue< std::string > argumentQueue, std::string &)> funcPtr)
void println(const T &message)