├── .arduino-ci.yaml ├── .bumpversion.cfg ├── .gitignore ├── .vscode └── tasks.json ├── Gemfile ├── ManagedSerialDevice.h ├── library.json ├── library.properties ├── license.md ├── readme.md ├── run_tests.sh ├── src ├── ManagedSerialDevice.cpp └── ManagedSerialDevice.h └── test └── instantiate.cpp /.arduino-ci.yaml: -------------------------------------------------------------------------------- 1 | compile: 2 | libraries: 3 | - "Regexp" 4 | platforms: 5 | - uno 6 | 7 | unittest: 8 | libraries: 9 | - "Regexp" 10 | platforms: 11 | - uno 12 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:library.json] 7 | 8 | [bumpversion:file:library.properties] 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | Gemfile.lock 3 | *.tar.xz 4 | *.bin 5 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Run all tests", 8 | "type": "shell", 9 | "command": "./run_tests.sh", 10 | "group": { 11 | "kind": "test", 12 | "isDefault": true 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'arduino_ci' 3 | -------------------------------------------------------------------------------- /ManagedSerialDevice.h: -------------------------------------------------------------------------------- 1 | #include "src/ManagedSerialDevice.h" 2 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arduino-managed-serial-device", 3 | "keywords": "stream, duplex, managed, serial, commands", 4 | "description": "Easily and asynchronously interact with a serial device requiring call-and-response style commands.", 5 | "homepage": "https://github.com/coddingtonbear/arduino-managed-serial-device", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/coddingtonbear/arduino-managed-serial-device.git" 9 | }, 10 | "version": "1.2.0", 11 | "authors": { 12 | "name": "Adam Coddington", 13 | "url": "https://coddingtonbear.net/" 14 | }, 15 | "exclude": [ 16 | "test" 17 | ], 18 | "frameworks": "arduino", 19 | "platforms": "*" 20 | } 21 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=arduino-managed-serial-device 2 | version=1.2.0 3 | author=Adam Coddington 4 | maintainer=Adam Coddington 5 | sentence=Easily and asynchronously with a serial device requiring call-and-response style commands. 6 | paragraph=Examples of devices that this is useful for are any modems using AT-commands like the SIM800, SIM7000, and ESP8266. 7 | category=Communication 8 | url=https://github.com/coddingtonbear/arduino-managed-serial-device 9 | architectures=* 10 | depends=Regexp 11 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 CoddingtonBear 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Arduino Managed Serial Device 2 | 3 | *Note* This library was formerly less-descriptively named "Arduino Async Duplex" 4 | 5 | This library allows you to asynchronously interact with any device 6 | having a call-and-response style serial command interface. 7 | 8 | If you've ever used one of the many serial-controlled devices that exist, 9 | you're familiar with the frustration that is waiting for a response from 10 | a long-running command. Between sending your command and receiving a 11 | response (or worse -- that command timing out), your program is halted, 12 | and your microcontroller is wasiting valuable cycles. This library 13 | aims to fix that problem by allowing you to queue commands that will 14 | be asynchronously sent to your device without blocking your 15 | microcontroller loop. 16 | 17 | ## Requirements 18 | 19 | * std::functional: This is available in the standard library for most 20 | non-AVR Arduino cores, but should you be attempting to use this 21 | on an AVR microcontroller (e.g. an atmega328p), you may find what you 22 | need in this repository: https://github.com/SGSSGene/StandardCplusplus 23 | * Regexp (https://github.com/nickgammon/Regexp) 24 | 25 | ## Examples 26 | 27 | The following examples are based upon interactions with a SIM7000 LTE modem. 28 | 29 | ### Simple 30 | 31 | Sending a command is as easy as queueing it: 32 | 33 | ```c++ 34 | #include 35 | #include 36 | 37 | ManagedSerialDevice handler = ManagedSerialDevice(); 38 | 39 | void setup() { 40 | handler.begin(&Serial1); 41 | 42 | handler.execute("AT"); 43 | } 44 | 45 | void loop() { 46 | handler.loop(); 47 | } 48 | ``` 49 | 50 | But that isn't much more useful than just writing to the stream directly; 51 | for more useful applications, keep reading. 52 | 53 | ### Independent 54 | 55 | When executing multiple independent commands, you can follow the below 56 | pattern: 57 | 58 | ```c++ 59 | #include 60 | #include 61 | 62 | ManagedSerialDevice handler = ManagedSerialDevice(); 63 | 64 | void setup() { 65 | handler.begin(&Serial); 66 | 67 | handler.execute( 68 | "AT+CCLK?", 69 | "+CCLK:.*\n", 70 | ); 71 | handler.execute( 72 | "AT+CIPSTATUS", 73 | "STATE:.*\n" 74 | ); 75 | } 76 | 77 | void loop() { 78 | handler.loop(); 79 | } 80 | ``` 81 | 82 | This pattern will work great for independent commands like the above, but 83 | for a few reasons this isn't the recommended pattern to follow for 84 | sequential steps that are dependent upon one another: 85 | 86 | 1. If one of these commands' expectations are not met (i.e. `AT+CIPSTART` 87 | in the examples below returns `ERROR` instead of `OK`), the subsequent 88 | commands will still be executed. 89 | 2. No guarantee is made that these will be executed sequentially. More 90 | commands could be queued and inserted between the above commands if 91 | another function queues a high-priority (`ManagedSerialDevice::Timing::NEXT`) 92 | command. 93 | 3. There are a limited number of independent queue slots (by default: 5, 94 | but this value can be adjusted by changing `COMMAND_QUEUE_SIZE`). 95 | 96 | 97 | ### Sequential (Nested Callbacks) 98 | 99 | This is a simplified overview of connecting to a TCP server using 100 | a SIM7000 LTE modem. 101 | 102 | 1. Send `AT+CIPSTART...`; wait for `OK` followed by the line ending. 103 | 2. Send `AT+CIPSEND...`; wait for a `>` to be printed. 104 | 3. Send the data you want to send followed by CTRL+Z (`\x1a`). 105 | 106 | The below commands will be executed sequentially and should any command's 107 | expectations not be met, subsequent commands will not be executed. 108 | 109 | ```c++ 110 | #include 111 | #include 112 | 113 | ManagedSerialDevice handler = ManagedSerialDevice(); 114 | 115 | void setup() { 116 | handler.begin(&Serial); 117 | 118 | handler.execute( 119 | "AT+CIPSTART=\"TCP\",\"mywebsite.com\",\"80\"", // Command 120 | "OK\r\n", // Expectation regex 121 | [&handler](MatchState ms) -> void { 122 | Serial.println("Connected"); 123 | 124 | handler.execute( 125 | "AT+CIPSEND", 126 | ">", 127 | NEXT, 128 | [&handler](MatchState ms) -> void { 129 | handler.execute( 130 | "abc\r\n\x1a" 131 | "SEND OK\r\n" 132 | NEXT, 133 | ); 134 | } 135 | ) 136 | } 137 | ); 138 | } 139 | 140 | 141 | void loop() { 142 | handler.loop(); 143 | } 144 | ``` 145 | 146 | ### Sequential (Chained) 147 | 148 | Most of the time, you probably just want to run a few commands in sequence, 149 | and the callback structure above may become tedious. For sequential commands 150 | that should be chained together (like above: aborting subsequent steps 151 | should any command's expectations not be met), there is a simpler way 152 | of handling these: 153 | 154 | ```c++ 155 | #include 156 | #include 157 | 158 | ManagedSerialDevice handler = ManagedSerialDevice(); 159 | 160 | void setup() { 161 | handler.begin(&Serial); 162 | 163 | ManagedSerialDevice::Command commands[] = { 164 | ManagedSerialDevice::Command( 165 | "AT+CIPSTART=\"TCP\",\"mywebsite.com\",\"80\"", // Command 166 | "OK\r\n", // Expectation regex 167 | [](MatchState ms){ 168 | Serial.println("Connected"); 169 | } 170 | ), 171 | ManagedSerialDevice::Command( 172 | "AT+CIPSEND", 173 | ">", 174 | ), 175 | ManagedSerialDevice::Command( 176 | "abc\r\n\x1a" 177 | "SEND OK\r\n" 178 | ) 179 | } 180 | handler.executeChain(commands, 3); 181 | } 182 | 183 | 184 | void loop() { 185 | handler.loop(); 186 | } 187 | ``` 188 | 189 | The above is identical in function to the "Nested Callbacks" example earlier, 190 | but using this pattern allows you define callbacks that are automatically 191 | prepended to any callback that might have originally been defined for every 192 | member of the chain: 193 | 194 | ```c++ 195 | #include 196 | #include 197 | 198 | ManagedSerialDevice handler = ManagedSerialDevice(); 199 | 200 | void setup() { 201 | handler.begin(&Serial); 202 | 203 | ManagedSerialDevice::Command commands[] = { 204 | ManagedSerialDevice::Command( 205 | "AT+CIPSTART=\"TCP\",\"mywebsite.com\",\"80\"", // Command 206 | "OK\r\n", // Expectation regex 207 | [](MatchState ms){ 208 | Serial.println("Connected"); 209 | } 210 | ), 211 | ManagedSerialDevice::Command( 212 | "AT+CIPSEND", 213 | ">", 214 | ), 215 | ManagedSerialDevice::Command( 216 | "abc\r\n\x1a" 217 | "SEND OK\r\n" 218 | ) 219 | } 220 | handler.executeChain( 221 | commands, 222 | 3, 223 | [](MatchState ms) { // Common success function 224 | // This will cause a message to be printed 225 | // after the completion of every command in the chain 226 | Serial1.println("Success!"); 227 | }, 228 | [](Command*) { // Common failure function 229 | // This will cause a message to be printed 230 | // after any member of the chain fails; this 231 | // might be a good place to put retry logic! 232 | Serial1.println("Failed!"); 233 | } 234 | ); 235 | } 236 | 237 | 238 | void loop() { 239 | handler.loop(); 240 | } 241 | ``` 242 | 243 | ### Capture Groups 244 | 245 | The below example will execute the relevant commands and, when the response 246 | is received, set local variables using captured data. 247 | 248 | ```c++ 249 | #include 250 | #include 251 | 252 | ManagedSerialDevice handler = ManagedSerialDevice(); 253 | 254 | void setup() { 255 | handler.begin(&Serial); 256 | 257 | // Get the current timestamp 258 | time_t currentTime; 259 | handler.execute( 260 | "AT+CCLK?", 261 | "+CCLK: \"([%d]+)/([%d]+)/([%d]+),([%d]+):([%d]+):([%d]+)([\\+\\-])([%d]+)\"", 262 | [¤tTime](MatchState ms) { 263 | char year_str[3]; 264 | char month_str[3]; 265 | char day_str[3]; 266 | char hour_str[3]; 267 | char minute_str[3]; 268 | char second_str[3]; 269 | char zone_dir_str[2]; 270 | char zone_str[3]; 271 | 272 | ms.GetCapture(year_str, 0); 273 | ms.GetCapture(month_str, 1); 274 | ms.GetCapture(day_str, 2); 275 | ms.GetCapture(hour_str, 3); 276 | ms.GetCapture(minute_str, 4); 277 | ms.GetCapture(second_str, 5); 278 | ms.GetCapture(zone_dir_str, 6); 279 | ms.GetCapture(zone_str, 7); 280 | 281 | tmElements_t timeEts; 282 | timeEts.Hour = atoi(hour_str); 283 | timeEts.Minute = atoi(minute_str); 284 | timeEts.Second = atoi(second_str); 285 | timeEts.Day = atoi(day_str); 286 | timeEts.Month = atoi(month_str); 287 | timeEts.Year = (2000 + atoi(year_str)) - 1970; 288 | 289 | currentTime = makeTime(timeEts); 290 | } 291 | ); 292 | 293 | char connectionStatus[10]; 294 | handler.execute( 295 | "AT+CIPSTATUS", 296 | "STATE: (.*)\n" 297 | [&connectionStatus](MatchState ms) { 298 | ms.GetCapture(connectionStatus, 0); 299 | } 300 | ); 301 | } 302 | 303 | void loop() { 304 | handler.loop(); 305 | } 306 | ``` 307 | 308 | ### Failure Handling 309 | 310 | You can pass a second function parameter to be executed should the request 311 | timeout. If you would like to retry the command (and subsequent commands 312 | chained with it), you are able to do so. 313 | 314 | ```c++ 315 | #include 316 | #include 317 | 318 | ManagedSerialDevice handler = ManagedSerialDevice(); 319 | 320 | void setup() { 321 | handler.begin(&Serial); 322 | handler.execute( 323 | "AT+CIPSTART=\"TCP\",\"mywebsite.com\",\"80\"", // Command 324 | "OK\r\n", // Expectation regex 325 | [](MatchState ms) -> void { 326 | Serial.println("Connected"); 327 | }, 328 | [&handler](ManagedSerialDevice::Command* cmd) -> void { // Run this function on failure 329 | Serial.println("Connection failed; retrying"); 330 | 331 | // Retry this immediately 332 | handler.execute(cmd, ManagedSerialDevice::Timing::NEXT); 333 | } 334 | ); 335 | } 336 | 337 | void loop() { 338 | handler.loop(); 339 | } 340 | ``` 341 | 342 | If you only want to print to the console that an error occurred, you can 343 | use the `ManagedSerialDevice::printFailure` helper: 344 | 345 | ```c++ 346 | #include 347 | #include 348 | 349 | ManagedSerialDevice handler = ManagedSerialDevice(); 350 | 351 | void setup() { 352 | handler.begin(&Serial); 353 | handler.execute( 354 | "AT+CIPSTART=\"TCP\",\"mywebsite.com\",\"80\"", // Command 355 | "OK\r\n", // Expectation regex 356 | [](MatchState ms) -> void { 357 | Serial.println("Connected"); 358 | }, 359 | ManagedSerialDevice::printFailure(&Serial1), // Will print "Command 'AT+CIPSTART...' failed." 360 | ); 361 | } 362 | 363 | void loop() { 364 | handler.loop(); 365 | } 366 | ``` 367 | 368 | ### Timeouts 369 | 370 | By default, commands time out after 2.5s (see `COMMAND_TIMEOUT`); sometimes 371 | you may need to run a command that needs extra time to complete: 372 | 373 | ```c++ 374 | #include 375 | #include 376 | 377 | ManagedSerialDevice handler = ManagedSerialDevice(); 378 | 379 | void setup() { 380 | handler.begin(&Serial); 381 | handler.execute( 382 | "AT+CIPSTART=\"TCP\",\"mywebsite.com\",\"80\"", // Command 383 | "OK\r\n", // Expectation regex 384 | NULL, 385 | NULL, 386 | 10000 // Extended Timeout 387 | ); 388 | } 389 | 390 | void loop() { 391 | handler.loop(); 392 | } 393 | ``` 394 | 395 | ### Delaying 396 | 397 | Occasionally, especially when chaining commands, you may need to ensure 398 | that a subsequent command isn't executed immediately; in these cases, 399 | you are able to set a delay: 400 | 401 | ```c++ 402 | #include 403 | #include 404 | 405 | ManagedSerialDevice handler = ManagedSerialDevice(); 406 | 407 | void setup() { 408 | handler.begin(&Serial); 409 | 410 | ManagedSerialDevice::Command commands[] = { 411 | ManagedSerialDevice::Command( 412 | "AT+CIPSTART=\"TCP\",\"mywebsite.com\",\"80\"", // Command 413 | "OK\r\n", // Expectation regex 414 | [](MatchState ms){ 415 | Serial.println("Connected"); 416 | } 417 | ), 418 | ManagedSerialDevice::Command( 419 | "AT+CIPSEND", 420 | ">", 421 | NULL, 422 | NULL, 423 | COMMAND_TIMEOUT, 424 | 1000 // Wait for 1s before running this command 425 | ), 426 | ManagedSerialDevice::Command( 427 | "abc\r\n\x1a" 428 | "SEND OK\r\n" 429 | ) 430 | } 431 | handler.executeChain(commands, 3); 432 | } 433 | 434 | 435 | void loop() { 436 | handler.loop(); 437 | } 438 | ``` 439 | 440 | # Note from the author 441 | 442 | I'm not a particularly great C++ programmer, and all of the projects 443 | I work on using this language are ones I work on for fun in my free time. 444 | It's very likely that you, dear reader, will have a better understanding 445 | of either C++ or programming for microcontrollers in general and will find 446 | ways of improving this that I either wouldn't have the skills to pull off 447 | on my own, or wouldn't even have the awareness of to know how much better 448 | things could be. If any of those situations occur, please either reach out 449 | on freenode -- I'm `coddingtonbear` there, too, search the issues list 450 | on Github or create a new issue if one doesn't exist, or, better yet, 451 | post a pull request making things better. Cheers! 452 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # It looks like the unittest library itself leaks 4 | export ASAN_OPTIONS=detect_leaks=0 5 | 6 | bundle install 7 | bundle exec ensure_arduino_installation.rb 8 | 9 | git clone https://github.com/coddingtonbear/Regexp 10 | mv Regexp ~/Arduino/libraries 11 | rm -rf Regexp 12 | 13 | bundle exec arduino_ci_remote.rb 14 | -------------------------------------------------------------------------------- /src/ManagedSerialDevice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #undef min 5 | #undef max 6 | #include 7 | 8 | #include "ManagedSerialDevice.h" 9 | 10 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG_COUT 11 | #include 12 | #endif 13 | 14 | ManagedSerialDevice::Command::Command() {} 15 | 16 | ManagedSerialDevice::Command::Command( 17 | const char* _cmd, 18 | const char* _expect, 19 | std::function _success, 20 | std::function _failure, 21 | uint16_t _timeout, 22 | uint32_t _delay 23 | ) { 24 | strncpy(command, _cmd, MAX_COMMAND_LENGTH - 1); 25 | command[MAX_COMMAND_LENGTH - 1] = '\0'; 26 | strncpy(expectation, _expect, MAX_EXPECTATION_LENGTH - 1); 27 | command[MAX_EXPECTATION_LENGTH - 1] = '\0'; 28 | success = _success; 29 | failure = _failure; 30 | timeout = _timeout; 31 | delay = _delay; 32 | } 33 | 34 | ManagedSerialDevice::Hook::Hook() {} 35 | 36 | ManagedSerialDevice::Hook::Hook( 37 | const char* _expect, 38 | std::function _success 39 | ) { 40 | strncpy(expectation, _expect, MAX_EXPECTATION_LENGTH - 1); 41 | success = _success; 42 | } 43 | 44 | ManagedSerialDevice::ManagedSerialDevice(){} 45 | 46 | bool ManagedSerialDevice::begin(Stream* _stream, Stream* _errorStream) { 47 | stream = _stream; 48 | errorStream = _errorStream; 49 | began = true; 50 | 51 | // Sublcasses may perform additional actions here that may not 52 | // be successful; in this base class' case, this isn't variable 53 | return true; 54 | } 55 | 56 | bool ManagedSerialDevice::wait(uint32_t _timeout, std::function feed_watchdog) { 57 | uint32_t started = millis(); 58 | 59 | while(queueLength > 0) { 60 | if(_timeout && (millis() > started + _timeout)) { 61 | // Wait timeout 62 | return false; 63 | } 64 | if(feed_watchdog) { 65 | feed_watchdog(); 66 | } 67 | loop(); 68 | } 69 | return true; 70 | } 71 | 72 | bool ManagedSerialDevice::abort() { 73 | if(queueLength > 0) { 74 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 75 | debugMessage("\t"); 76 | #endif 77 | 78 | shiftLeft(); 79 | clearInputBuffer(); 80 | processing=false; 81 | 82 | return true; 83 | } else { 84 | return false; 85 | } 86 | } 87 | 88 | bool ManagedSerialDevice::execute( 89 | const char *_command, 90 | const char *_expectation, 91 | ManagedSerialDevice::Timing _timing, 92 | std::function _success, 93 | std::function _failure, 94 | uint16_t _timeout, 95 | uint32_t _delay 96 | ) { 97 | if(queueLength == COMMAND_QUEUE_SIZE) { 98 | return false; 99 | } 100 | 101 | uint8_t position = 0; 102 | if(_timing == ANY) { 103 | position = queueLength; 104 | queueLength++; 105 | } else { 106 | shiftRight(); 107 | } 108 | 109 | if(strlen(_command) > MAX_COMMAND_LENGTH - 1) { 110 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 111 | debugMessage("\t"); 112 | #endif 113 | return false; 114 | } 115 | if(strlen(_expectation) > MAX_EXPECTATION_LENGTH - 1) { 116 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 117 | debugMessage("\t"); 118 | #endif 119 | return false; 120 | } 121 | 122 | strcpy(commandQueue[position].command, _command); 123 | strcpy(commandQueue[position].expectation, _expectation); 124 | commandQueue[position].success = _success; 125 | commandQueue[position].failure = _failure; 126 | commandQueue[position].timeout = _timeout; 127 | 128 | // Once queued, the delay signifies the point in time at 129 | // which this task can begin being processed 130 | commandQueue[position].delay = _delay + millis(); 131 | 132 | return true; 133 | } 134 | 135 | bool ManagedSerialDevice::execute( 136 | const char *_command, 137 | const char *_expectation, 138 | std::function _success, 139 | std::function _failure, 140 | uint16_t _timeout, 141 | uint32_t _delay 142 | ) { 143 | return ManagedSerialDevice::execute( 144 | _command, 145 | _expectation, 146 | ManagedSerialDevice::Timing::ANY, 147 | _success, 148 | _failure, 149 | _timeout, 150 | _delay 151 | ); 152 | } 153 | 154 | bool ManagedSerialDevice::execute( 155 | const Command* cmd, 156 | Timing _timing 157 | ) { 158 | return ManagedSerialDevice::execute( 159 | cmd->command, 160 | cmd->expectation, 161 | _timing, 162 | cmd->success, 163 | cmd->failure, 164 | cmd->timeout 165 | ); 166 | } 167 | 168 | bool ManagedSerialDevice::executeChain( 169 | const Command* cmdArray, 170 | uint16_t count, 171 | Timing _timing, 172 | std::function _success, 173 | std::function _failure 174 | ) { 175 | if(count < 2) { 176 | return false; 177 | } 178 | 179 | Command scratch; 180 | Command chain = cmdArray[count - 1]; 181 | prependCallback(&chain, _success, _failure); 182 | 183 | for(int16_t i = count - 2; i >= 0; i--) { 184 | copyCommand( 185 | &scratch, 186 | &cmdArray[i] 187 | ); 188 | prependCallback(&scratch, _success, _failure); 189 | createChain( 190 | &scratch, 191 | &chain 192 | ); 193 | copyCommand( 194 | &chain, 195 | &scratch 196 | ); 197 | } 198 | return ManagedSerialDevice::execute( 199 | &chain, 200 | _timing 201 | ); 202 | } 203 | 204 | bool ManagedSerialDevice::executeChain( 205 | const Command* cmdArray, 206 | uint16_t count, 207 | std::function _success, 208 | std::function _failure 209 | ) { 210 | return ManagedSerialDevice::executeChain( 211 | cmdArray, 212 | count, 213 | ManagedSerialDevice::Timing::ANY, 214 | _success, 215 | _failure 216 | ); 217 | } 218 | 219 | void ManagedSerialDevice::createChain(Command* dest, const Command* toChain) { 220 | Command chained; 221 | copyCommand(&chained, toChain); 222 | 223 | std::function originalSuccess = dest->success; 224 | dest->success = [this, chained, originalSuccess](MatchState ms){ 225 | if(originalSuccess) { 226 | originalSuccess(ms); 227 | } 228 | execute( 229 | &chained, 230 | Timing::NEXT 231 | ); 232 | }; 233 | } 234 | 235 | void ManagedSerialDevice::copyCommand(Command* dest, const Command* src) { 236 | strcpy(dest->command, src->command); 237 | strcpy(dest->expectation, src->expectation); 238 | dest->success = src->success; 239 | dest->failure = src->failure; 240 | dest->timeout = src->timeout; 241 | dest->delay = src->delay; 242 | } 243 | 244 | void ManagedSerialDevice::prependCallback( 245 | Command* cmd, 246 | std::function _success, 247 | std::function _failure 248 | ) { 249 | if(_success) { 250 | std::function originalFn = cmd->success; 251 | cmd->success = [_success, originalFn](MatchState ms){ 252 | _success(ms); 253 | if(originalFn) { 254 | originalFn(ms); 255 | } 256 | }; 257 | } 258 | if(_failure) { 259 | std::function originalFn = cmd->failure; 260 | cmd->failure = [_failure, originalFn](Command* cmd){ 261 | _failure(cmd); 262 | if(originalFn) { 263 | originalFn(cmd); 264 | } 265 | }; 266 | } 267 | } 268 | 269 | void ManagedSerialDevice::clearInputBuffer() { 270 | inputBuffer[0] = '\0'; 271 | bufferPos = 0; 272 | nextLogLineStart = 0; 273 | } 274 | 275 | void ManagedSerialDevice::getLatestLine(char* buffer, uint16_t length) { 276 | strncpy(buffer, &inputBuffer[nextLogLineStart], length - 1); 277 | if(strlen(&inputBuffer[nextLogLineStart]) >= length) { 278 | buffer[length - 1] = '\0'; 279 | } 280 | } 281 | 282 | void ManagedSerialDevice::newLineReceived() { 283 | nextLogLineStart = bufferPos + 1; 284 | 285 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 286 | #ifndef MANAGED_SERIAL_DEVICE_DEBUG_VERBOSE 287 | char* line = &inputBuffer[nextLogLineStart]; 288 | debugMessage( 289 | "\t = (" + String(bufferPos) + ") \"" + line + "\"" 290 | ); 291 | #endif 292 | #endif 293 | } 294 | 295 | void ManagedSerialDevice::commandSent(char*) { 296 | } 297 | 298 | void ManagedSerialDevice::loop(){ 299 | if(!began) { 300 | return; 301 | } 302 | if(processing && (millis() > timeout)) { 303 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 304 | String nonMatching = String(inputBuffer); 305 | nonMatching.trim(); 306 | debugMessage( 307 | "\t<-- " + nonMatching 308 | ); 309 | debugMessage("\t"); 310 | #endif 311 | 312 | Command failedCommand; 313 | copyCommand(&failedCommand, &commandQueue[0]); 314 | std::function fn = failedCommand.failure; 315 | if(fn) { 316 | // Clear delay settings before handing to error 317 | // handler callback to prevent erroneously delaying 318 | // for forty years if the error handler tries to retry 319 | failedCommand.delay = 0; 320 | fn(&failedCommand); 321 | } 322 | 323 | shiftLeft(); 324 | clearInputBuffer(); 325 | processing=false; 326 | } 327 | while(stream->available()) { 328 | bool foundNewline = false; 329 | uint8_t received = stream->read(); 330 | if(received != '\0') { 331 | if(received == '\n') { 332 | // If we've found a line ending, we should plan to run 333 | // any registered hooks so they can check for unsolicited 334 | // data that might be useful. 335 | foundNewline = true; 336 | 337 | newLineReceived(); 338 | } 339 | if(bufferPos + 1 == INPUT_BUFFER_LENGTH) { 340 | for(int32_t i = INPUT_BUFFER_LENGTH - 1; i > 0; i--) { 341 | inputBuffer[i-1] = inputBuffer[i]; 342 | } 343 | bufferPos--; 344 | } 345 | inputBuffer[bufferPos++] = received; 346 | inputBuffer[bufferPos] = '\0'; 347 | } 348 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 349 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG_VERBOSE 350 | debugMessage( 351 | "\t = (" + String(bufferPos) + ") \"" + String(inputBuffer) + "\"" 352 | ); 353 | #endif 354 | #endif 355 | 356 | if(processing) { 357 | MatchState ms; 358 | ms.Target(inputBuffer); 359 | char result = ms.Match(commandQueue[0].expectation); 360 | if(result) { 361 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 362 | String src = String(ms.src); 363 | src.trim(); 364 | debugMessage( 365 | "\t<-- " + src 366 | ); 367 | debugMessage( 368 | "\t" 369 | ); 370 | #endif 371 | 372 | processing=false; 373 | 374 | std::function fn = commandQueue[0].success; 375 | shiftLeft(); 376 | if(fn) { 377 | fn(ms); 378 | } 379 | stripMatchFromInputBuffer(ms); 380 | } 381 | } 382 | 383 | if(foundNewline) { 384 | runHooks(); 385 | } 386 | } 387 | if(!processing && queueLength > 0 && commandQueue[0].delay <= millis()) { 388 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 389 | debugMessage("\t--> " + String(commandQueue[0].command)); 390 | #endif 391 | clearInputBuffer(); 392 | 393 | stream->println(commandQueue[0].command); 394 | stream->flush(); 395 | commandSent(commandQueue[0].command); 396 | processing = true; 397 | timeout = millis() + commandQueue[0].timeout; 398 | } 399 | } 400 | 401 | bool ManagedSerialDevice::registerHook( 402 | const char *_expectation, 403 | std::function _success 404 | ) { 405 | if(hookCount == MAX_HOOK_COUNT) { 406 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 407 | debugMessage("\t"); 408 | #endif 409 | return false; 410 | } 411 | if(strlen(_expectation) + 1 > MAX_EXPECTATION_LENGTH) { 412 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 413 | debugMessage("\t"); 414 | #endif 415 | return false; 416 | } 417 | strcpy(hooks[hookCount].expectation, _expectation); 418 | hooks[hookCount].success = _success; 419 | hookCount++; 420 | 421 | return true; 422 | } 423 | 424 | void ManagedSerialDevice::runHooks() { 425 | for(uint8_t i = 0; i < hookCount; i++) { 426 | Hook hook = hooks[i]; 427 | 428 | MatchState ms; 429 | ms.Target(inputBuffer); 430 | 431 | char result = ms.Match(hook.expectation); 432 | if(result) { 433 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 434 | String src = String(ms.src); 435 | src.trim(); 436 | debugMessage( 437 | "\t<-- " + src 438 | ); 439 | debugMessage( 440 | "\t" 441 | ); 442 | #endif 443 | hook.success(ms); 444 | } 445 | } 446 | } 447 | 448 | uint8_t ManagedSerialDevice::getQueueLength() { 449 | return queueLength; 450 | } 451 | 452 | void ManagedSerialDevice::getResponse(char* buffer, uint16_t length) { 453 | strncpy(buffer, inputBuffer, length); 454 | } 455 | 456 | void ManagedSerialDevice::shiftRight() { 457 | for(int8_t i = 0; i < queueLength - 1; i++) { 458 | copyCommand(&commandQueue[i+1], &commandQueue[i]); 459 | } 460 | queueLength++; 461 | } 462 | 463 | void ManagedSerialDevice::shiftLeft() { 464 | for(int8_t i = queueLength - 1; i > 0; i--) { 465 | copyCommand(&commandQueue[i-1], &commandQueue[i]); 466 | } 467 | queueLength--; 468 | } 469 | 470 | std::function ManagedSerialDevice::printFailure(Stream* stream) { 471 | return [stream](ManagedSerialDevice::Command* cmd) { 472 | stream->println( 473 | "Command '" + String(cmd->command) + "' failed." 474 | ); 475 | }; 476 | } 477 | 478 | void ManagedSerialDevice::stripMatchFromInputBuffer(MatchState ms) { 479 | uint16_t offset = ms.MatchStart + ms.MatchLength; 480 | for(uint16_t i = offset; i < INPUT_BUFFER_LENGTH; i++) { 481 | inputBuffer[i - offset] = inputBuffer[i]; 482 | bufferPos = i - offset; 483 | // If we reached the end of the capture, we 484 | // do not need to copy anything further 485 | if(inputBuffer[i] == '\0') { 486 | break; 487 | } 488 | } 489 | } 490 | 491 | void ManagedSerialDevice::emitErrorMessage(const char *msg) { 492 | if(errorStream != NULL) { 493 | errorStream->println(msg); 494 | errorStream->flush(); 495 | } 496 | } 497 | 498 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 499 | void ManagedSerialDevice::debugMessage(String msg) { 500 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG_COUT 501 | std::cout << msg; 502 | std::cout << "\n"; 503 | #endif 504 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG_STREAM 505 | emitErrorMessage(msg.c_str()); 506 | #endif 507 | } 508 | 509 | void ManagedSerialDevice::debugMessage(const char *msg) { 510 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG_COUT 511 | std::cout << msg; 512 | std::cout << "\n"; 513 | #endif 514 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG_STREAM 515 | emitErrorMessage(msg); 516 | #endif 517 | } 518 | #endif 519 | 520 | inline int ManagedSerialDevice::available() { 521 | return stream->available(); 522 | } 523 | 524 | inline size_t ManagedSerialDevice::write(uint8_t bt) { 525 | return stream->write(bt); 526 | } 527 | 528 | inline int ManagedSerialDevice::read() { 529 | return stream->read(); 530 | } 531 | 532 | inline int ManagedSerialDevice::peek() { 533 | return stream->peek(); 534 | } 535 | 536 | inline void ManagedSerialDevice::flush() { 537 | return stream->flush(); 538 | } 539 | -------------------------------------------------------------------------------- /src/ManagedSerialDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #undef min 7 | #undef max 8 | #include 9 | 10 | #define COMMAND_QUEUE_SIZE 5 11 | #define INPUT_BUFFER_LENGTH 256 12 | #define MAX_COMMAND_LENGTH 64 13 | #define MAX_EXPECTATION_LENGTH 128 14 | #define COMMAND_TIMEOUT 2500 15 | #define MAX_HOOK_COUNT 10 16 | 17 | //#define MANAGED_SERIAL_DEVICE_DEBUG 18 | //#define MANAGED_SERIAL_DEVICE_DEBUG_VERBOSE 19 | //#define MANAGED_SERIAL_DEVICE_DEBUG_COUT 20 | //#define MANAGED_SERIAL_DEVICE_DEBUG_STREAM 21 | 22 | class ManagedSerialDevice: public Stream { 23 | public: 24 | enum Timing{ 25 | NEXT, 26 | ANY 27 | }; 28 | struct Command { 29 | char command[MAX_COMMAND_LENGTH]; 30 | char expectation[MAX_EXPECTATION_LENGTH]; 31 | std::function success; 32 | std::function failure; 33 | uint16_t timeout; 34 | uint32_t delay; 35 | 36 | Command(); 37 | Command( 38 | const char* _cmd, 39 | const char* _expect, 40 | std::function _success = NULL, 41 | std::function _failure = NULL, 42 | uint16_t _timeout = COMMAND_TIMEOUT, 43 | uint32_t _delay = 0 44 | ); 45 | }; 46 | struct Hook { 47 | char expectation[MAX_EXPECTATION_LENGTH]; 48 | std::function success; 49 | 50 | Hook(); 51 | Hook( 52 | const char* _expect, 53 | std::function _success 54 | ); 55 | }; 56 | 57 | ManagedSerialDevice(); 58 | 59 | bool begin(Stream*, Stream* _errorStream=NULL); 60 | bool wait(uint32_t timeout, std::function _feed_watchdog=NULL); 61 | bool abort(); 62 | bool execute( 63 | const char *_command, 64 | const char *_expectation, 65 | Timing _timing, 66 | std::function _success = NULL, 67 | std::function _failure = NULL, 68 | uint16_t _timeout = COMMAND_TIMEOUT, 69 | uint32_t _delay = 0 70 | ); 71 | bool execute( 72 | const char *_command, 73 | const char *_expectation = "", 74 | std::function _success = NULL, 75 | std::function _failure = NULL, 76 | uint16_t _timeout = COMMAND_TIMEOUT, 77 | uint32_t _delay = 0 78 | ); 79 | bool execute( 80 | const Command*, 81 | Timing _timing = Timing::ANY 82 | ); 83 | bool executeChain( 84 | const Command*, 85 | uint16_t count, 86 | Timing _timing, 87 | std::function _success = NULL, 88 | std::function _failure = NULL 89 | ); 90 | bool executeChain( 91 | const Command*, 92 | uint16_t count, 93 | std::function _success = NULL, 94 | std::function _failure = NULL 95 | ); 96 | 97 | bool registerHook( 98 | const char *_expectation, 99 | std::function _success 100 | ); 101 | 102 | void loop(); 103 | 104 | uint8_t getQueueLength(); 105 | void getResponse(char*, uint16_t); 106 | 107 | // Helper functions 108 | std::function printFailure(Stream*); 109 | void stripMatchFromInputBuffer(MatchState ms); 110 | 111 | // Stream 112 | int available(); 113 | size_t write(uint8_t); 114 | int read(); 115 | int peek(); 116 | void flush(); 117 | protected: 118 | Command commandQueue[COMMAND_QUEUE_SIZE]; 119 | uint8_t queueLength = 0; 120 | 121 | void shiftRight(); 122 | void shiftLeft(); 123 | 124 | void getLatestLine(char*, uint16_t length); 125 | virtual void newLineReceived(); 126 | virtual void commandSent(char*); 127 | 128 | void clearInputBuffer(); 129 | void copyCommand(Command*, const Command*); 130 | void createChain(Command*, const Command*); 131 | void prependCallback( 132 | Command*, 133 | std::function _success = NULL, 134 | std::function _failure = NULL 135 | ); 136 | 137 | uint16_t nextLogLineStart = 0; 138 | 139 | char inputBuffer[INPUT_BUFFER_LENGTH]; 140 | uint16_t bufferPos = 0; 141 | uint32_t timeout = 0; 142 | 143 | bool began = false; 144 | bool processing = false; 145 | 146 | Hook hooks[MAX_HOOK_COUNT]; 147 | uint8_t hookCount = 0; 148 | virtual void runHooks(); 149 | 150 | virtual void emitErrorMessage(const char*); 151 | #ifdef MANAGED_SERIAL_DEVICE_DEBUG 152 | virtual void debugMessage(String); 153 | virtual void debugMessage(const char*); 154 | #endif 155 | Stream* errorStream; 156 | Stream* stream; 157 | }; 158 | -------------------------------------------------------------------------------- /test/instantiate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../src/ManagedSerialDevice.h" 5 | 6 | 7 | class TestingManagedSerialDevice: public ManagedSerialDevice { 8 | public: 9 | 10 | virtual void newLineReceived() { 11 | char buffer[8]; 12 | getLatestLine(buffer, 8); 13 | String line = String(buffer); 14 | line.trim(); 15 | nextLogLineStart = bufferPos; 16 | 17 | testLines[testLineLength++] = line; 18 | 19 | ManagedSerialDevice::newLineReceived(); 20 | } 21 | 22 | uint8_t testLineLength = 0; 23 | String testLines[10]; 24 | }; 25 | 26 | unittest(simple) { 27 | GodmodeState* state = GODMODE(); 28 | state->resetPorts(); 29 | 30 | bool callbackExecuted = false; 31 | 32 | ManagedSerialDevice handler = ManagedSerialDevice(); 33 | handler.begin(&Serial); 34 | handler.execute("TEST"); 35 | handler.loop(); 36 | 37 | assertEqual( 38 | "TEST\r\n", 39 | state->serialPort[0].dataOut 40 | ); 41 | } 42 | 43 | unittest(can_resolve_expectation) { 44 | GodmodeState* state = GODMODE(); 45 | state->resetPorts(); 46 | 47 | bool callbackExecuted = false; 48 | 49 | ManagedSerialDevice handler = ManagedSerialDevice(); 50 | handler.begin(&Serial); 51 | handler.execute( 52 | "TEST", 53 | "OK", 54 | ManagedSerialDevice::NEXT, 55 | [&callbackExecuted](MatchState ms) { 56 | callbackExecuted = true; 57 | } 58 | ); 59 | handler.loop(); 60 | 61 | assertEqual( 62 | "TEST\r\n", 63 | state->serialPort[0].dataOut 64 | ); 65 | assertFalse(callbackExecuted); 66 | 67 | state->serialPort[0].dataIn = "OK"; 68 | handler.loop(); 69 | 70 | assertTrue(callbackExecuted); 71 | } 72 | 73 | unittest(can_fail_expectation) { 74 | GodmodeState* state = GODMODE(); 75 | state->resetPorts(); 76 | 77 | bool callbackExecuted = false; 78 | bool failureCallbackExecuted = false; 79 | 80 | ManagedSerialDevice handler = ManagedSerialDevice(); 81 | handler.begin(&Serial); 82 | handler.execute( 83 | "TEST", 84 | "OK", 85 | ManagedSerialDevice::NEXT, 86 | [&callbackExecuted](MatchState ms) { 87 | callbackExecuted = true; 88 | }, 89 | [&failureCallbackExecuted](ManagedSerialDevice::Command* cmd) { 90 | failureCallbackExecuted = true; 91 | }, 92 | 0 93 | ); 94 | handler.loop(); 95 | 96 | assertEqual( 97 | "TEST\r\n", 98 | state->serialPort[0].dataOut 99 | ); 100 | 101 | state->micros = state->micros + 100000; 102 | 103 | handler.loop(); 104 | assertFalse(callbackExecuted); 105 | assertTrue(failureCallbackExecuted); 106 | } 107 | 108 | unittest(can_fail_expectation_with_display) { 109 | GodmodeState* state = GODMODE(); 110 | state->resetPorts(); 111 | 112 | bool callbackExecuted = false; 113 | bool failureCallbackExecuted = false; 114 | 115 | ManagedSerialDevice handler = ManagedSerialDevice(); 116 | handler.begin(&Serial); 117 | handler.execute( 118 | "TEST", 119 | "OK", 120 | ManagedSerialDevice::NEXT, 121 | [&callbackExecuted](MatchState ms) { 122 | callbackExecuted = true; 123 | }, 124 | handler.printFailure(&Serial), 125 | 0 126 | ); 127 | handler.loop(); 128 | state->micros = state->micros + 100000; 129 | handler.loop(); 130 | assertEqual( 131 | "TEST\r\nCommand 'TEST' failed.\r\n", 132 | state->serialPort[0].dataOut 133 | ); 134 | } 135 | 136 | unittest(can_execute_from_object) { 137 | GodmodeState* state = GODMODE(); 138 | state->resetPorts(); 139 | 140 | ManagedSerialDevice::Command cmd = ManagedSerialDevice::Command( 141 | "TEST", 142 | "XYZ" 143 | ); 144 | ManagedSerialDevice handler = ManagedSerialDevice(); 145 | handler.begin(&Serial); 146 | handler.execute(&cmd); 147 | 148 | handler.loop(); 149 | 150 | assertEqual( 151 | "TEST\r\n", 152 | state->serialPort[0].dataOut 153 | ); 154 | } 155 | 156 | unittest(can_execute_chain) { 157 | GodmodeState* state = GODMODE(); 158 | state->resetPorts(); 159 | 160 | uint8_t appendedCallbackCalls = 0; 161 | 162 | ManagedSerialDevice::Command commands[] = { 163 | ManagedSerialDevice::Command("TEST", "OK"), 164 | ManagedSerialDevice::Command("TEST2", "OK"), 165 | ManagedSerialDevice::Command("TEST3", "OK") 166 | }; 167 | ManagedSerialDevice handler = ManagedSerialDevice(); 168 | handler.begin(&Serial); 169 | handler.executeChain( 170 | commands, 171 | 3, 172 | ManagedSerialDevice::Timing::ANY, 173 | [&appendedCallbackCalls](MatchState ms) { 174 | appendedCallbackCalls++; 175 | } 176 | ); 177 | 178 | handler.loop(); 179 | assertEqual( 180 | "TEST\r\n", 181 | state->serialPort[0].dataOut 182 | ); 183 | state->serialPort[0].dataIn = "OK"; 184 | state->serialPort[0].dataOut = ""; 185 | 186 | handler.loop(); 187 | assertEqual(1, appendedCallbackCalls); 188 | assertEqual( 189 | "TEST2\r\n", 190 | state->serialPort[0].dataOut 191 | ); 192 | state->serialPort[0].dataIn = "OK"; 193 | state->serialPort[0].dataOut = ""; 194 | 195 | handler.loop(); 196 | assertEqual(2, appendedCallbackCalls); 197 | assertEqual( 198 | "TEST3\r\n", 199 | state->serialPort[0].dataOut 200 | ); 201 | state->serialPort[0].dataIn = "OK"; 202 | state->serialPort[0].dataOut = ""; 203 | 204 | handler.loop(); 205 | assertEqual(3, appendedCallbackCalls); 206 | } 207 | 208 | unittest(can_return_match_groups) { 209 | GodmodeState* state = GODMODE(); 210 | state->resetPorts(); 211 | 212 | char matchGroupResult[10] = {'\0'}; 213 | 214 | ManagedSerialDevice handler = ManagedSerialDevice(); 215 | handler.begin(&Serial); 216 | handler.execute( 217 | "TEST", 218 | "OK%[([%d]+)%]", 219 | ManagedSerialDevice::NEXT, 220 | [&matchGroupResult](MatchState ms) { 221 | char buffer[5]; 222 | ms.GetCapture(buffer, 0); 223 | strcpy(matchGroupResult, buffer); 224 | } 225 | ); 226 | handler.loop(); 227 | 228 | assertEqual( 229 | "TEST\r\n", 230 | state->serialPort[0].dataOut 231 | ); 232 | state->serialPort[0].dataIn = "OK[10]"; 233 | handler.loop(); 234 | 235 | assertEqual("10", matchGroupResult); 236 | } 237 | 238 | unittest(task_overflows_non_dangerous) { 239 | GodmodeState* state = GODMODE(); 240 | state->resetPorts(); 241 | 242 | ManagedSerialDevice handler = ManagedSerialDevice(); 243 | handler.begin(&Serial); 244 | 245 | for(uint8_t i = 0; i < COMMAND_QUEUE_SIZE + 1; i++) { 246 | bool result = handler.execute("TEST"); 247 | 248 | if(i < COMMAND_QUEUE_SIZE) { 249 | assertTrue(result); 250 | } else { 251 | assertFalse(result); 252 | } 253 | } 254 | 255 | assertEqual(COMMAND_QUEUE_SIZE, handler.getQueueLength()); 256 | } 257 | 258 | unittest(can_register_and_run_hooks) { 259 | GodmodeState* state = GODMODE(); 260 | state->resetPorts(); 261 | 262 | bool hookExecuted = false; 263 | 264 | ManagedSerialDevice handler = ManagedSerialDevice(); 265 | handler.begin(&Serial); 266 | 267 | handler.registerHook( 268 | "%*PSUTTZ(.*)\r\n", 269 | [&hookExecuted](MatchState ms) { 270 | hookExecuted = true; 271 | } 272 | ); 273 | 274 | assertFalse(hookExecuted); 275 | 276 | state->serialPort[0].dataIn = "SOMETHING"; 277 | handler.loop(); 278 | assertFalse(hookExecuted); 279 | 280 | state->serialPort[0].dataIn = "\r\n"; 281 | handler.loop(); 282 | assertFalse(hookExecuted); 283 | 284 | state->serialPort[0].dataIn = "*PSUTTZ: 18/11/04,22:38:07\",\"-32\",0"; 285 | handler.loop(); 286 | assertFalse(hookExecuted); 287 | 288 | state->serialPort[0].dataIn = "\r\n"; 289 | handler.loop(); 290 | assertTrue(hookExecuted); 291 | } 292 | 293 | unittest(properly_identifies_newlines) { 294 | GodmodeState* state = GODMODE(); 295 | state->resetPorts(); 296 | 297 | TestingManagedSerialDevice handler = TestingManagedSerialDevice(); 298 | handler.begin(&Serial); 299 | 300 | state->serialPort[0].dataIn = "alpha"; 301 | handler.loop(); 302 | assertEqual( 303 | 0, 304 | handler.testLineLength 305 | ); 306 | state->serialPort[0].dataIn = "\n"; 307 | handler.loop(); 308 | assertEqual( 309 | 1, 310 | handler.testLineLength 311 | ); 312 | assertEqual( 313 | "alpha", 314 | handler.testLines[0] 315 | ); 316 | 317 | state->serialPort[0].dataIn = "beta"; 318 | handler.loop(); 319 | assertEqual( 320 | 1, 321 | handler.testLineLength 322 | ); 323 | state->serialPort[0].dataIn = "\n"; 324 | handler.loop(); 325 | assertEqual( 326 | 2, 327 | handler.testLineLength 328 | ); 329 | assertEqual( 330 | "beta", 331 | handler.testLines[1] 332 | ); 333 | 334 | state->serialPort[0].dataIn = "0123456789\n"; 335 | handler.loop(); 336 | assertEqual( 337 | 3, 338 | handler.testLineLength 339 | ); 340 | assertEqual( 341 | "0123456", 342 | handler.testLines[2] 343 | ); 344 | } 345 | 346 | unittest_main() 347 | --------------------------------------------------------------------------------