├── .github └── workflows │ ├── aunit_tests.yml │ └── githubci.yml ├── .gitignore ├── CONFIGURATION.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── AdjustableBlink │ └── AdjustableBlink.ino ├── AlternateTokenizer │ └── AlternateTokenizer.ino ├── ArduinoTextInterface │ ├── ArduinoCommands.cpp │ ├── ArduinoCommands.h │ ├── ArduinoTextInterface.ino │ ├── MemoryUsageAVR.cpp │ ├── MemoryUsageAVR.h │ ├── memoryCommands.cpp │ ├── memoryUsageCommands.cpp │ └── memory_probe.cpp ├── EchoCommand │ └── EchoCommand.ino └── IdentifyTheSketch │ └── IdentifyTheSketch.ino ├── extras └── tests │ ├── CustomParserTest │ ├── CustomParserTest.ino │ ├── Makefile │ ├── SimulatedStream.h │ └── simpleFIFO.h │ ├── HelpTest │ ├── HelpTest.ino │ ├── Makefile │ ├── SimulatedStream.h │ └── simpleFIFO.h │ ├── Makefile │ ├── README.md │ └── simpleSerialShellTest │ ├── Makefile │ ├── SimMonitor.cpp │ ├── shellTestCommands.cpp │ ├── shellTestHelpers.h │ ├── simpleFIFO.h │ └── simpleSerialShellTest.ino ├── formatter.conf ├── keywords.txt ├── library.properties ├── releaseChecklist.md ├── releaseNotes.md └── src ├── SimpleSerialShell.cpp └── SimpleSerialShell.h /.github/workflows/aunit_tests.yml: -------------------------------------------------------------------------------- 1 | name: SimpleSerialShell unit test check 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Setup 14 | run: | 15 | cd .. 16 | #(old) git clone https://github.com/bxparks/UnixHostDuino 17 | git clone https://github.com/bxparks/EpoxyDuino 18 | git clone https://github.com/bxparks/AUnit 19 | #- name: Verify examples 20 | #run: | 21 | # make -C examples 22 | - name: Verify extras/tests 23 | run: | 24 | make -C extras/tests 25 | - name: Run tests 26 | run: | 27 | make -C extras/tests 28 | make -C extras/tests runtests 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/githubci.yml: -------------------------------------------------------------------------------- 1 | name: Arduino SimpleSerialShell Lib build permutation check 2 | 3 | # See https://learn.adafruit.com/the-well-automated-arduino-library/doxygen 4 | 5 | on: [pull_request, push, repository_dispatch] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/setup-python@v4 13 | with: 14 | python-version: '3.x' 15 | - uses: actions/checkout@v3 16 | - uses: actions/checkout@v3 17 | with: 18 | repository: adafruit/ci-arduino 19 | path: ci 20 | 21 | - name: pre-install 22 | run: bash ci/actions_install.sh 23 | 24 | - name: test platforms 25 | run: python3 ci/build_platform.py main_platforms 26 | 27 | # Make sure: 28 | # there is a "gh-pages" branch 29 | # github repository has a secret GH_REPO_TOKEN with repo access 30 | # (used to push generated docs back onto github) 31 | # 32 | # NOTE: at this time I don't think doxygen is worth it for this 33 | # library. Overkill for a "simple shell"? 34 | # 35 | # See the README.md for how to use. 36 | # 37 | #- name: doxygen 38 | # env: 39 | # GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} 40 | # PRETTYNAME : "SimpleSerialShell Library" 41 | # run: bash ci/doxy_gen_and_deploy.sh 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # (borrowed from Adafruit_NeoPixel lib) 2 | # 3 | # Our handy .gitignore for automation ease 4 | Doxyfile* 5 | doxygen_sqlite3.db 6 | html 7 | *.out 8 | *.o 9 | *.swp 10 | *.orig 11 | -------------------------------------------------------------------------------- /CONFIGURATION.md: -------------------------------------------------------------------------------- 1 | # SimpleSerialShell Configuration 2 | ## Input buffer size 3 | The predefined size of input buffer is 88 bytes (defined by the ``SIMPLE_SERIAL_SHELL_BUFSIZE`` identifier). It's ok in most cases, but when it's not enough, you can redefine it with build options. 4 | Check your IDE documentation to learn how to properly redefine build options. See the example for PlatformIO below. 5 | 6 | ### Arduino IDE: 7 | Configuring buffer size from within the Arduino IDE is not recommended. 8 | 9 | ### PlatformIO: 10 | Define a new value at your `platformio.ini` in this way: 11 | ```ini 12 | build_flags = -D SIMPLE_SERIAL_SHELL_BUFSIZE=128 13 | ``` 14 | 15 | An example for Leonardo board looks this way: 16 | ```ini 17 | leonardo.build.extra_flags={build.usb_flags} '-DSIMPLE_SERIAL_SHELL_BUFSIZE=128' 18 | ``` 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Coding Style 4 | 5 | Try and adhere to the [kernel coding style](https://www.kernel.org/doc/html/latest/process/coding-style.html) with the exception of using 4 spaces instead of 8 for indentation. 6 | 7 | The "astyle" code formatter and the library's ```formatter.conf``` configuration file do a pretty good job of correcting indentation, removing tabs etc. ("astyle" is the formatter the Arduino IDE uses when you select "Tools...Auto format".) 8 | 9 | Tip: ```git diff --check``` will check your files and flag lines with trailing whitespace, and flag lines with tabs following spaces. 10 | 11 | ## Reporting Bugs 12 | 13 | Open an issue. Please include as much code / logging / output / information as 14 | possible to help diagnose your issue. 15 | 16 | ## Proposing Changes 17 | 18 | Open a pull request using a branch based on **main**. Read the [branches 19 | section](#branches) for an overview of the workflow. 20 | 21 | If it's a major change, open an issue to gauge interest before putting in too 22 | much effort. 23 | 24 | Ensure regression tests continue to PASS. 25 | 26 | The following subsections inspired by the [git patch guidelines](https://github.com/git/git/blob/master/Documentation/SubmittingPatches). 27 | 28 | ### Commits 29 | 30 | Make separate commits for logically separate changes 31 | 32 | The title for the commit should start with the file or subsection it is 33 | changing. 34 | 35 | Give an explanation for the change(s) that is detailed enough so that people 36 | can judge if it is good thing to do, without reading the actual patch text to 37 | determine how well the code does what the explanation promises to do. 38 | 39 | If your description starts to get too long, that's a sign that you probably 40 | need to split up your commit to finer grained pieces. That being said, patches 41 | which plainly describe the things that help reviewers check the patch, and 42 | future maintainers understand the code, are the most beautiful patches. 43 | Descriptions that summarize the point in the subject well, and describe the 44 | motivation for the change, the approach taken by the change, and if relevant 45 | how this differs substantially from the prior version, are all good things to 46 | have. 47 | 48 | ### Basing your branch 49 | 50 | In general, always base your work on the oldest branch that your change is 51 | relevant to. 52 | 53 | - A bugfix should be based on **main** in general. For a bug that's not yet 54 | in **main**, find the topic that introduces the regression, and base your 55 | work on the tip of the topic. 56 | 57 | - A new feature should be based on **main** in general. If the new feature 58 | depends on a topic that is in **test**, but not in **main**, base your 59 | work on the tip of that topic. 60 | 61 | * Corrections and enhancements to a topic not yet in **main** should be based 62 | on the tip of that topic. If the topic has not been merged to **next**, it's 63 | alright to add a note to squash minor corrections into the series. 64 | 65 | * In the exceptional case that a new feature depends on several topics not in 66 | **main**, start working on **next** or **test** privately and send out 67 | patches for discussion. Before the final merge, you may have to wait until 68 | some of the dependent topics graduate to **main**, and rebase your work. 69 | 70 | To find the tip of a topic branch, run `git log --first-parent main..test` 71 | and look for the merge commit. The second parent of this commit is the tip of 72 | the topic branch. 73 | 74 | ## Branches 75 | 76 | Usage inspired by the [git branch workflow](https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt). 77 | 78 | ### main 79 | 80 | - All new features and changes should be based on this branch 81 | - Is meant to be more stable than any tagged releases, and users are encouraged 82 | to follow it 83 | - Tagged for releases 84 | 85 | ### next 86 | 87 | - Used to publish changes (both enhancements and fixes) that (1) have 88 | worthwhile goal, (2) are in a fairly good shape suitable for everyday use, 89 | (3) but have not yet demonstrated to be regression free. 90 | - Contains all new proposed changes for the next release 91 | - Users are encouraged to use it to test for regressions and bugs before they 92 | get merged for release 93 | - Periodically rebased on to **main**, usually after a tagged release 94 | 95 | ### test 96 | 97 | - Branch is used to publish other proposed changes that do not yet pass the 98 | criteria set for **next**. 99 | - Contains all seen proposed feature / enhancement requests 100 | - Most unstable branch 101 | - Regularly rebased on to **main** 102 | 103 | ### [initials]/[branch-name] 104 | 105 | - Name based on the author's initials and topic of the branch. For example 106 | *mc/fix-bug-in-foo* 107 | - Based on **main**, unless it requires code from another feature, in which 108 | case base it on that feature branch 109 | - Contains a proposed feature / enhancement / bug fix wanting to be merged 110 | - It will first get merged into **test**, then **next**, and finally **main** 111 | when it's ready 112 | 113 | ## Versioning 114 | 115 | Version numbers follow [Semantic Versioning](https://semver.org/). 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 philj404 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := SimpleSerialShell 2 | VERSION := $(shell git describe --tags --always --dirty) 3 | 4 | $(NAME)-$(VERSION).zip: 5 | git archive HEAD --prefix=$(@:.zip=)/ --format=zip -o $@ 6 | 7 | tag: 8 | git tag $(VERSION) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimpleSerialShell # [![Build Status](https://travis-ci.com/philj404/SimpleSerialShell.svg?branch=main)](https://travis-ci.com/philj404/SimpleSerialShell) 2 | An extensible command shell for Arduino. 3 | 4 | This library provides a basic "command line interface" for your Arduino through its serial interface. 5 | This can connect to the Serial Monitor, for example. 6 | 7 | Commands are of the old (1970s) Unix-style 8 | ` int aCommand(int argc, char ** argv) ` as described in Kernighan and Ritchie. 9 | 10 | ### Example: 11 | Include the library: 12 | ```cpp 13 | #include 14 | ``` 15 | 16 | Create the command to call: 17 | ```cpp 18 | int helloWorld(int argc, char **argv) {...}; 19 | 20 | ``` 21 | 22 | Start the serial connection and attach it to the shell: 23 | 24 | ```cpp 25 | setup() 26 | { 27 | Serial.begin(9600); 28 | 29 | shell.attach(Serial); // attach to any stream... 30 | ... 31 | } 32 | ``` 33 | Name the command and add it to the shell: 34 | ```cpp 35 | ... 36 | shell.addCommand(F("sayHello"),helloWorld); 37 | ... 38 | ``` 39 | 40 | Check for input periodically: 41 | ```cpp 42 | loop() 43 | { 44 | ... 45 | shell.executeIfInput(); 46 | ... 47 | } 48 | ``` 49 | 50 | ### Example Sketches 51 | * **AdjustableBlink** 52 | -- lets you read and vary the blink rate of a Blink sketch. 53 | * **AlternateTokenizer** 54 | -- Demonstrates an echo command that accepts quoted tokens that contain spaces. 55 | * **ArduinoTextInterface** 56 | -- lets you read and write digital and analog values to Arduino pins. Basically wrappers for setPinMode(), digitalRead(), digitalWrite(), analogRead(), analogWrite(), etc. 57 | It's incomplete, but enough to set and clear bits one at a time and see if you have an LED connected to the right pin. 58 | * **EchoCommand** -- "echo" example. 59 | Sending "echo Hello World!" returns "Hello World!" on the serial monitor. 60 | * **IdentifyTheSketch** -- Example provides an "id?" query which reports the filename and build date of the sketch running. 61 | Useful if you forgot what was loaded on this board. 62 | 63 | ### Alternate Tokenizers 64 | 65 | By default, shell input is tokenized using the UNIX standard strtok_r(3) function. This splits user input into space-delimited tokens. There may be applications where a more sophisticated tokenizer is desired. Quoted tokens with internal spaces, for example. The setTokenizer() method can be used to install a custom tokenizer. 66 | 67 | A demonstration of this feature can be seen [here](examples/AlternateTokenizer). 68 | 69 | ### Tips 70 | 71 | * "help" is a built-in command. It lists what is available. 72 | 73 | * If memory limitations allow, provide additional documentation for each command you register following a colon delimeter in the addCommand() method. This will make the "help" output more comprehensive and 74 | may make your device more user-friendly. (For example, use `shell.addCommand(F("echo ..."), echoCommand);` ) 75 | 76 | * RAM is limited in the ATMega world. To save space, use the F() macro, which keeps const strings in flash 77 | rather than copying them to RAM. (For example use `shell.addCommand(F("sayHello"), helloWorld);` ) 78 | 79 | * Since the shell delegates the actual communication to what it connects to 80 | (with shell.attach()), it can work with Serial, Serial2, SoftwareSerial or 81 | a custom stream. 82 | 83 | * To make it easy to switch commands to a different connection, I recommend always 84 | sending command output to the shell 85 | (rather than straight to Serial for example). For example I use `shell.println("motor is off");` 86 | 87 | ### Notes 88 | 89 | It's just a simple shell... enough that the Serial Monitor can send text 90 | commands and receive text responses. It's OK for humans with keyboards. 91 | 92 | The intent is for the Serial Monitor output to be _human-readable_ rather than _fast_. 93 | If you want fast serial DATA transfer between an Arduino and a host, 94 | you may want to look into Processing/Wiring, MIDI or other protocols 95 | (which I don't really know about). 96 | 97 | Each added command uses up a small amount of limited RAM. This may run out 98 | if you need MANY commands. (Less of a problem for newer processors.) 99 | 100 | 101 | (I haven't tested this but) you should be able to switch among multiple 102 | streams on the fly. Not sure if that's useful with limited code space. 103 | 104 | -------------------------------------------------------------------------------- /examples/AdjustableBlink/AdjustableBlink.ino: -------------------------------------------------------------------------------- 1 | // basic example testing of the command parser. 2 | #include 3 | 4 | //////////////////////////////////////////////////////////////////////////////// 5 | // Forgot what sketch was loaded to this board? 6 | // 7 | // Hint1: use the F() macro to keep const strings in FLASH and save RAM 8 | // Hint2: "Compiler " "catenates consecutive " 9 | // "strings together" 10 | // (improves readability for very long strings) 11 | // 12 | int showID(int /*argc*/ = 0, char** /*argv*/ = NULL) 13 | { 14 | shell.println(F( "Running " __FILE__ ",\nBuilt " __DATE__)); 15 | return 0; 16 | }; 17 | 18 | 19 | int togglePeriod = 1000; 20 | 21 | //////////////////////////////////////////////////////////////////////////////// 22 | // non blocking LED toggle 23 | // 24 | void toggleLED_nb(void) 25 | { 26 | static auto lastToggle = millis(); // saved between calls 27 | auto now = millis(); 28 | 29 | if (now - lastToggle > (unsigned int) (togglePeriod / 2) ) 30 | { 31 | // toggle 32 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 33 | lastToggle = now; 34 | } 35 | } 36 | 37 | //////////////////////////////////////////////////////////////////////////////// 38 | int setTogglePeriod(int argc, char **argv) 39 | { 40 | if (argc != 2) { 41 | shell.println("bad argument count"); 42 | return -1; 43 | } 44 | togglePeriod = atoi(argv[1]); 45 | shell.print("Setting LED toggle period to "); 46 | shell.print(togglePeriod); 47 | shell.println("ms"); 48 | 49 | return EXIT_SUCCESS; 50 | } 51 | 52 | //////////////////////////////////////////////////////////////////////////////// 53 | int getTogglePeriod(int /*argc*/ , char ** /*argv*/ ) 54 | { 55 | shell.print("LED toggle period is "); 56 | shell.print(togglePeriod); 57 | shell.println("ms"); 58 | 59 | return EXIT_SUCCESS; 60 | } 61 | //////////////////////////////////////////////////////////////////////////////// 62 | void setup() { 63 | // put your setup code here, to run once: 64 | 65 | pinMode(LED_BUILTIN, OUTPUT); 66 | 67 | //Serial.begin(9600); older default bit rate 68 | Serial.begin(115200); 69 | while (!Serial) { 70 | // wait for serial port to connect. Needed for native USB port only 71 | // AND you want to block until there's a connection 72 | // otherwise the shell can quietly drop output. 73 | } 74 | 75 | //example 76 | shell.attach(Serial); 77 | shell.addCommand(F("id?"), showID); 78 | 79 | //basic command 80 | //shell.addCommand(F("setTogglePeriod"), setTogglePeriod); 81 | // command with hint for help 82 | shell.addCommand(F("setTogglePeriod "), setTogglePeriod); 83 | 84 | 85 | shell.addCommand(F("getTogglePeriod"), getTogglePeriod); 86 | 87 | showID(); 88 | Serial.println(F("Ready.")); 89 | } 90 | 91 | // non blocking LED toggle 92 | extern void toggleLED_nb(void); 93 | 94 | //////////////////////////////////////////////////////////////////////////////// 95 | void loop() { 96 | // put your main code here, to run repeatedly: 97 | 98 | //shell->execute("echo Hello World"); 99 | shell.executeIfInput(); 100 | 101 | // show loop() is still running -- not waiting 102 | toggleLED_nb(); 103 | } 104 | -------------------------------------------------------------------------------- /examples/AlternateTokenizer/AlternateTokenizer.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file AlternateTokenizer.ino 3 | * @author brucemack 4 | * @version 0.1 5 | * @date 2021-12-29 6 | * 7 | * @copyright Copyright (c) 2021 8 | * 9 | * A demonstration of the SimpleSerialShell using an alternate tokenizer that 10 | * supports the use of quoted strings. 11 | * 12 | * Depends on https://github.com/philj404/SimpleSerialShell. 13 | * 14 | * Here is an example run: 15 | * 16 | * echo test 17 | * Argument 1: [test] 18 | * echo test test2 19 | * Argument 1: [test] 20 | * Argument 2: [test2] 21 | * echo test "this is a long test" 22 | * Argument 1: [test] 23 | * Argument 2: [this is a long test] 24 | */ 25 | #include 26 | 27 | /** 28 | * @brief Checks to see if a character is in a string. 29 | * 30 | * @param needle The character being searched for 31 | * @param haystack null-delimited string 32 | * @return true If found. 33 | */ 34 | static bool isIn(char needle, const char* haystack) { 35 | const char* ptr = haystack; 36 | while (*ptr != 0) { 37 | if (needle == *ptr) { 38 | return true; 39 | } 40 | ptr++; 41 | } 42 | return false; 43 | } 44 | 45 | /** 46 | * @brief The tokenizer function that conforms to the SimpleSerialShell signature 47 | * requirement. Please see strtok_r(3) for an explanation of the expected semantics. 48 | * 49 | * @param str 50 | * @param delims 51 | * @param saveptr 52 | * @return char* 53 | */ 54 | static char* tokenizer(char* str, const char* delims, char** saveptr) { 55 | 56 | // Figure out where to start scanning from 57 | char* ptr = 0; 58 | if (str == 0) { 59 | ptr = *saveptr; 60 | } else { 61 | ptr = str; 62 | } 63 | 64 | // Consume/ignore any leading delimiters 65 | while (*ptr != 0 && isIn(*ptr, delims)) { 66 | ptr++; 67 | } 68 | 69 | // At this point we either have a null or a non-delimiter 70 | // character to deal with. If there is nothing left in the 71 | // string then return 0 72 | if (*ptr == 0) { 73 | return 0; 74 | } 75 | 76 | char* result; 77 | 78 | // Check to see if this is a quoted token 79 | if (*ptr == '\"') { 80 | // Skip the opening quote 81 | ptr++; 82 | // Result is the first eligible character 83 | result = ptr; 84 | while (*ptr != 0) { 85 | if (*ptr == '\"') { 86 | // Turn the trailing delimiter into a null-termination 87 | *ptr = 0; 88 | // Skip forward for next start 89 | ptr++; 90 | break; 91 | } 92 | ptr++; 93 | } 94 | } 95 | else { 96 | // Result is the first eligible character 97 | result = ptr; 98 | while (*ptr != 0) { 99 | if (isIn(*ptr, delims)) { 100 | // Turn the trailing delimiter into a null-termination 101 | *ptr = 0; 102 | // Skip forward for next start 103 | ptr++; 104 | break; 105 | } 106 | ptr++; 107 | } 108 | } 109 | 110 | // We will start on the next character when we return 111 | *saveptr = ptr; 112 | return result; 113 | } 114 | 115 | /** 116 | * @brief Installed into the shell as a command processor. Prints out the 117 | * arguments passed to the function. 118 | * 119 | * @param argc 120 | * @param argv 121 | * @return int 122 | */ 123 | int echo(int argc, char **argv) { 124 | for (int i = 1; i < argc; i++) { 125 | shell.print("Argument "); 126 | shell.print(i); 127 | shell.print(": ["); 128 | shell.print(argv[i]); 129 | shell.println("]"); 130 | } 131 | return 0; 132 | } 133 | 134 | void setup() { 135 | Serial.begin(115200); 136 | shell.attach(Serial); 137 | shell.addCommand(F("echo [text tokens]"), echo); 138 | shell.setTokenizer(tokenizer); 139 | } 140 | 141 | void loop() { 142 | shell.executeIfInput(); 143 | } 144 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/ArduinoCommands.cpp: -------------------------------------------------------------------------------- 1 | // ArduinoCommands.cpp 2 | // support for controlling Arduino pins through a serial command line interface 3 | // 4 | // 5 | //////////////////////////////////////////////////////////////////////////////// 6 | #include 7 | #include 8 | #include "ArduinoCommands.h" 9 | //#include 10 | 11 | // see Arduino.h and pins_arduino.h for useful declarations. 12 | 13 | //////////////////////////////////////////////////////////////////////////////// 14 | int badArgCount( char * cmdName ) 15 | { 16 | shell.print(cmdName); 17 | shell.println(F(": bad arg count")); 18 | return -1; 19 | } 20 | 21 | //////////////////////////////////////////////////////////////////////////////// 22 | //////////////////////////////////////////////////////////////////////////////// 23 | // int <--> symbolic translations 24 | // declare it in PROGMEM so strings are not copied to RAM 25 | // 26 | // NOTE: PROGMEM access to values in this struct 27 | // is not as simple as it would be in RAM. 28 | // But const strings in RAM can take significant space. 29 | // 30 | struct lookupVals 31 | { 32 | #ifdef AVR 33 | const char * PROGMEM name_pp; 34 | #else 35 | const char * name_pp; 36 | #endif 37 | int val; 38 | __FlashStringHelper * getNamePtr(void) const 39 | { 40 | auto nameInProgmem = &(name_pp); 41 | __FlashStringHelper * fshName 42 | = (__FlashStringHelper *)pgm_read_ptr(nameInProgmem); 43 | return fshName; 44 | } 45 | String getName(void) const 46 | { 47 | auto fshName = getNamePtr(); 48 | return String(fshName); 49 | }; 50 | int getValue(void) const 51 | { 52 | auto valInProgmem = &(val); 53 | return pgm_read_word(valInProgmem); 54 | } 55 | }; 56 | 57 | //////////////////////////////////////////////////////////////////////////////// 58 | int lookup(const char * aName, const lookupVals entries[]) 59 | { 60 | int i = 0; 61 | String name(aName); 62 | //Serial << F("looking up \"") << name << F("\"") << endl; 63 | for (; entries[i].getNamePtr(); i++) 64 | { 65 | //Serial << F(" i = ") << i 66 | // << F(" comparing against ") << entries[i].getName() << endl; 67 | if (name.equalsIgnoreCase(entries[i].getName())) { 68 | auto aVal = entries[i].getValue(); 69 | //Serial << F("found entry: ") << aVal << endl; 70 | return aVal; 71 | break; 72 | } 73 | } 74 | auto v = entries[0].getValue(); 75 | //Serial << "giving up; using" << v << endl; 76 | return v; 77 | } 78 | 79 | //////////////////////////////////////////////////////////////////////////////// 80 | String reverseLookup(int aVal, const lookupVals entries[]) 81 | { 82 | //Serial << F("Looking up ") << aVal << endl; 83 | for (int i = 0; entries[i].getNamePtr(); i++) 84 | { 85 | //Serial << F(" i = ") << i 86 | // << F(" comparing against ") << entries[i].getName() << endl; 87 | if (aVal == entries[i].getValue()) { 88 | return entries[i].getName(); 89 | break; 90 | } 91 | } 92 | return String(F("(unrecognized value)")); 93 | } 94 | 95 | //////////////////////////////////////////////////////////////////////////////// 96 | static const char input_s[] PROGMEM = "input"; 97 | static const char output_s[] PROGMEM = "output"; 98 | static const char pullup_s[] PROGMEM = "pullup"; 99 | static const lookupVals modes[] PROGMEM = { 100 | {input_s, INPUT}, 101 | {output_s, OUTPUT}, 102 | {pullup_s, INPUT_PULLUP}, 103 | {NULL, INPUT} // end of list 104 | }; 105 | 106 | int setPinMode(int argc, char **argv) 107 | { 108 | if (argc == 3) 109 | { 110 | auto pin = atoi(argv[1]); 111 | auto mode = lookup(argv[2], modes); 112 | 113 | pinMode(pin, mode); 114 | return EXIT_SUCCESS; 115 | } 116 | 117 | return badArgCount(argv[0]); 118 | } 119 | 120 | //////////////////////////////////////////////////////////////////////////////// 121 | int analogRead(int argc, char **argv) 122 | { 123 | if (argc == 2) 124 | { 125 | int pin = atoi(argv[1]); 126 | if (pin < 0 || pin > (int) NUM_ANALOG_INPUTS) 127 | { 128 | shell.print(F("pin ")); 129 | shell.print(pin); 130 | shell.println(F(" does not look like an analog pin")); 131 | } 132 | auto val = analogRead(pin); 133 | shell.println(val); 134 | return EXIT_SUCCESS; 135 | } 136 | 137 | return badArgCount(argv[0]); 138 | } 139 | 140 | #ifndef ARDUINO_ARCH_ESP32 141 | //////////////////////////////////////////////////////////////////////////////// 142 | int analogWrite(int argc, char **argv) 143 | { 144 | if (argc == 3) 145 | { 146 | int pin = atoi(argv[1]); 147 | if (!digitalPinHasPWM(pin)) 148 | { 149 | shell.print(F("pin ")); 150 | shell.print(pin); 151 | shell.println(F(" does not look like an analog output")); 152 | } 153 | int val = atoi(argv[2]); 154 | 155 | analogWrite(pin, val); 156 | return EXIT_SUCCESS; 157 | } 158 | 159 | return badArgCount(argv[0]); 160 | } 161 | #endif 162 | 163 | //////////////////////////////////////////////////////////////////////////////// 164 | const char low_s[] PROGMEM = "low"; 165 | const char high_s[] PROGMEM = "high"; 166 | const char zero_s[] PROGMEM = "0"; 167 | const char one_s[] PROGMEM = "1"; 168 | const lookupVals digLevels[] PROGMEM = { 169 | {low_s, LOW}, 170 | {high_s, HIGH}, 171 | {zero_s, LOW}, 172 | {one_s, HIGH}, 173 | {NULL, LOW} // end of list 174 | }; 175 | //////////////////////////////////////////////////////////////////////////////// 176 | int digitalWrite(int argc, char **argv) 177 | { 178 | if (argc == 3) 179 | { 180 | int pin = atoi(argv[1]); 181 | if (pin < 0 || pin >= (int) NUM_DIGITAL_PINS) 182 | { 183 | shell.print(F("pin ")); 184 | shell.print(pin); 185 | shell.println(F(" does not look like a digital pin")); 186 | } 187 | auto level = lookup(argv[2], digLevels); 188 | 189 | digitalWrite(pin, level); 190 | return EXIT_SUCCESS; 191 | } 192 | 193 | return badArgCount(argv[0]); 194 | } 195 | 196 | //////////////////////////////////////////////////////////////////////////////// 197 | int digitalRead(int argc, char **argv) 198 | { 199 | if (argc == 2) 200 | { 201 | int pin = atoi(argv[1]); 202 | if (pin < 0 || pin >= (int) NUM_DIGITAL_PINS) 203 | { 204 | shell.print(F("pin ")); 205 | shell.print(pin); 206 | shell.println(F(" does not look like a digital pin")); 207 | } 208 | auto val = digitalRead(pin); 209 | shell.print(val); 210 | shell.print(F(" ")); 211 | auto valName = reverseLookup(val, digLevels); 212 | shell.println(valName); 213 | 214 | return EXIT_SUCCESS; 215 | } 216 | 217 | return badArgCount(argv[0]); 218 | } 219 | 220 | 221 | #ifndef ARDUINO_ARCH_ESP32 222 | //////////////////////////////////////////////////////////////////////////////// 223 | int doTone(int argc, char **argv) 224 | { 225 | if (argc < 3 || argc > 4) 226 | { 227 | return badArgCount(argv[0]); 228 | } 229 | int pin = atoi(argv[1]); 230 | int freq = atoi(argv[2]); 231 | if (argc == 3) 232 | { 233 | tone(pin, freq); 234 | } else { 235 | int duration = atoi(argv[4]); 236 | tone(pin, freq, duration); 237 | } 238 | 239 | return EXIT_SUCCESS; 240 | } 241 | #endif 242 | 243 | #ifndef ARDUINO_ARCH_ESP32 244 | //////////////////////////////////////////////////////////////////////////////// 245 | int doNoTone(int argc, char **argv) 246 | { 247 | if (argc != 2) 248 | { 249 | return badArgCount(argv[0]); 250 | } 251 | int pin = atoi(argv[1]); 252 | noTone(pin); 253 | 254 | return EXIT_SUCCESS; 255 | } 256 | #endif 257 | 258 | #define WITH_HELPINFO 259 | #ifdef WITH_HELPINFO 260 | #define ADD_COMMAND(name, argumentHints, function)\ 261 | shell.addCommand(F(name " " argumentHints),function) 262 | #else 263 | // if there is no room for a help hint, 264 | // shorten the string to just the command 265 | #define ADD_COMMAND(name, argumentHints, function)\ 266 | shell.addCommand(F(name),function) 267 | #endif 268 | 269 | //////////////////////////////////////////////////////////////////////////////// 270 | int addArduinoCommands(SimpleSerialShell & shell) 271 | { 272 | //shell.addCommand(F("setpinmode" ), setPinMode); 273 | //shell.addCommand(F("setpinmode pinNumber {input|output|pullup}"), setPinMode); 274 | // 275 | ADD_COMMAND("setpinmode", "pinNumber {input|output|pullup}", setPinMode); 276 | ADD_COMMAND("digitalwrite", "pinNumber {low|high|0|1}", digitalWrite); 277 | ADD_COMMAND("digitalread", "pinNumber", digitalRead); 278 | ADD_COMMAND("analogread", "pinNumber", analogRead); 279 | 280 | #ifndef ARDUINO_ARCH_ESP32 281 | ADD_COMMAND("analogwrite", "pinNumber value", analogWrite); 282 | ADD_COMMAND("tone", "pinNumber freqHz [durationMillisec]", doTone); 283 | ADD_COMMAND("notone", "pinNumber", doNoTone); 284 | #endif 285 | 286 | return EXIT_SUCCESS; 287 | } 288 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/ArduinoCommands.h: -------------------------------------------------------------------------------- 1 | // An external reference to the Arduino command wrappers. 2 | // 3 | #include 4 | 5 | extern int addArduinoCommands(SimpleSerialShell &shell); 6 | extern int addMemoryCommands(SimpleSerialShell &shell); 7 | 8 | #ifdef AVR 9 | extern int addStackHeapCommands(SimpleSerialShell &shell); 10 | extern int addMemoryUsageCommands(SimpleSerialShell &shell); 11 | #endif 12 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/ArduinoTextInterface.ino: -------------------------------------------------------------------------------- 1 | // basic example testing of the command parser. 2 | #include 3 | #include "ArduinoCommands.h" 4 | 5 | ///////////////////////////////////////////////////////////////////////////////////// 6 | // Forgot what sketch was loaded to this board? 7 | // 8 | // Hint1: use the F() macro to keep const strings in FLASH and save RAM 9 | // Hint2: "Compiler " "catenates consecutive " 10 | // "strings together" 11 | // (improves readability for very long strings) 12 | // 13 | int showID(int /*argc*/ = 0, char** /*argv*/ = NULL) 14 | { 15 | shell.println(F( "Running " __FILE__ ", Built " __DATE__)); 16 | return 0; 17 | }; 18 | 19 | //////////////////////////////////////////////////////////////////////////////// 20 | void setup() { 21 | // put your setup code here, to run once: 22 | 23 | pinMode(LED_BUILTIN, OUTPUT); 24 | 25 | //Serial.begin(9600); older default bit rate 26 | Serial.begin(115200); 27 | while (!Serial) { 28 | // wait for serial port to connect. Needed for native USB port only 29 | // AND you want to block until there's a connection 30 | // otherwise the shell can quietly drop output. 31 | } 32 | delay(1000); 33 | 34 | //example 35 | shell.attach(Serial); 36 | //shell.addCommand(F("echo"), echo); 37 | shell.addCommand(F("id?"), showID); 38 | 39 | addArduinoCommands(shell); 40 | addMemoryCommands(shell); 41 | #ifdef AVR 42 | addStackHeapCommands(shell); 43 | addMemoryUsageCommands(shell); //MemoryUsage (modified library -- no dependency) 44 | #endif 45 | 46 | showID(); 47 | Serial.println(F("Ready.")); 48 | } 49 | 50 | //////////////////////////////////////////////////////////////////////////////// 51 | void loop() { 52 | // put your main code here, to run repeatedly: 53 | 54 | //shell->execute("echo Hello World"); 55 | shell.executeIfInput(); 56 | 57 | // loop() is still running... 58 | } 59 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/MemoryUsageAVR.cpp: -------------------------------------------------------------------------------- 1 | // (Originally from the MemoryUsage library by Thierry Paris) 2 | // modified to return numberic values, and use streams other than Serial 3 | 4 | #include "Arduino.h" 5 | 6 | #include "MemoryUsageAVR.h" 7 | #include 8 | 9 | #ifdef __AVR__ 10 | using namespace MU_AVR; 11 | 12 | /// Thanks to adafruit : https://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory 13 | 14 | int MU_AVR::maxStackSize = getStackSize(); 15 | int MU_AVR::numStackComputeCalls = 0; 16 | 17 | // print 1, 2 or 3 items on a line 18 | void PRINT (const __FlashStringHelper *a, int b = -1, const __FlashStringHelper * c = NULL) 19 | { 20 | shell.print(a); 21 | if (b != -1) { 22 | shell.print(b); 23 | shell.print(F(" (0x")); 24 | shell.print(b,HEX); 25 | shell.print(F(")")); 26 | if (c) { 27 | shell.print(c); 28 | } 29 | } 30 | shell.println(); 31 | } 32 | 33 | /// Modified function from http://www.avr-developers.com/mm/memoryusage.html 34 | void MU_AVR::SRamDisplay(void) 35 | { 36 | int data_size = (int)&__data_end - getDataStart(); // vars initialized on start 37 | int bss_size = (int)&__bss_end - (int)&__data_end;// vars uninitialized on start 38 | int heap_end = getHeapEnd(); 39 | //int heap_end = (int)SP - (int)&__malloc_margin; 40 | int heap_size = heap_end - (int)&__bss_end; 41 | int stack_size = getStackSize(); 42 | int available = getRamSize(); 43 | 44 | available -= data_size + bss_size + heap_size + stack_size; 45 | 46 | PRINT( F( "+----------------+ " ), getDataStart(), F(" (__data_start)")); 47 | PRINT( F( "+ data +" )); 48 | PRINT( F( "+ variables + size = " ), data_size); 49 | PRINT( F( "+----------------+ " ), (int)&__data_end, F(" (__data_end / __bss_start)")); 50 | PRINT( F( "+ bss +" )); 51 | PRINT( F( "+ variables + size = " ), bss_size); 52 | PRINT( F( "+----------------+ " ), (int)&__bss_end, F(" (__bss_end / __heap_start)")); 53 | PRINT( F( "+ heap + size = " ), heap_size); 54 | PRINT( F( "+----------------+ " ), (int)heap_end, F(" (__brkval if not 0, or __heap_start)")); 55 | PRINT( F( "+ +" )); 56 | PRINT( F( "+ +" )); 57 | PRINT( F( "+ FREE RAM + size = " ), available); 58 | PRINT( F( "+ +" )); 59 | PRINT( F( "+ +" )); 60 | PRINT( F( "+----------------+ " ), (int)SP, F(" (SP)")); 61 | PRINT( F( "+ stack + size = " ), stack_size); 62 | PRINT( F( "+----------------+ " ), (int)RAMEND, F(" (RAMEND / __stack)")); 63 | shell.println(); 64 | shell.println(); 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/MemoryUsageAVR.h: -------------------------------------------------------------------------------- 1 | // (Originally from the MemoryUsage library by Thierry Paris 2 | // (see https://github.com/Locoduino/MemoryUsage) 3 | // modified to return numberic values, and use streams other than Serial 4 | // 5 | // MemoryUsageAVR.h 6 | 7 | #ifdef __AVR__ 8 | 9 | #ifndef __MemoryUsageAVR_h__ 10 | #define __MemoryUsageAVR_h__ 11 | 12 | #include 13 | 14 | extern uint8_t _end; 15 | extern uint8_t __stack; 16 | extern uint8_t *__brkval; 17 | extern uint8_t *__data_start; 18 | extern uint8_t *__data_end; 19 | extern uint8_t *__heap_start; 20 | extern uint8_t *__heap_end; 21 | extern uint8_t *__bss_start; 22 | extern uint8_t *__bss_end; 23 | 24 | /////////////////////////////////////////////////////////////////////////////////////////////// 25 | // 26 | // Access Memory addresses 27 | // These are inline functions to minimize how much they affect the stack. 28 | // 29 | namespace MU_AVR 30 | { 31 | // start of RAM: 32 | // ... initialized data 33 | // ... then bss (uninitialized data) 34 | // ... then heap 35 | // ... then FREE RAM 36 | // ... then stack 37 | // ... then end of RAM 38 | 39 | inline unsigned int getDataStart(void) 40 | { 41 | return (unsigned int)&__data_start; // start RAM 42 | } 43 | 44 | inline unsigned int getHeapStart(void) 45 | { 46 | return (unsigned int)&__heap_start; 47 | } 48 | 49 | inline unsigned int getHeapEnd(void) 50 | { 51 | unsigned int heapEnd = getHeapStart(); 52 | if (__brkval) 53 | heapEnd = (unsigned int)__brkval; 54 | return heapEnd; 55 | } 56 | 57 | inline unsigned int getHeapSize(void) 58 | { 59 | return getHeapEnd() - getHeapStart(); 60 | } 61 | 62 | extern int numStackComputeCalls; 63 | extern int maxStackSize; 64 | 65 | inline unsigned int getStackStart(void) 66 | { 67 | // next UNUSED entry 68 | // active stack above here. Stack grows DOWN from RAMEND 69 | return (unsigned int)SP; 70 | } 71 | 72 | inline unsigned int getFreeRam(void) 73 | { 74 | return getStackStart() - getHeapEnd(); 75 | } 76 | 77 | inline unsigned int getMemoryEnd(void) 78 | { 79 | return (unsigned int)RAMEND; // last byte! (not the byte after) 80 | } 81 | 82 | inline unsigned int getStackSize(void) 83 | { 84 | return getMemoryEnd() - getStackStart(); 85 | } 86 | 87 | inline unsigned int getMaxStackSize(void) 88 | { 89 | return maxStackSize; 90 | } 91 | 92 | inline unsigned int getRamSize(void) 93 | { 94 | return (getMemoryEnd() + 1) - getDataStart(); 95 | } 96 | 97 | /// Displays the 'map' of the current state of the Arduino's SRAM memory on the console. 98 | void SRamDisplay(void); 99 | 100 | /// Must be called to update the current maximum size of the stack, at each function beginning. 101 | inline void stackCompute(void) 102 | { 103 | int currentSize = getStackSize(); 104 | maxStackSize = max(currentSize, maxStackSize); 105 | numStackComputeCalls++; 106 | //Serial.println(F("COMPUTING STACK...")); 107 | } 108 | #define STACK_COMPUTE stackCompute(); 109 | 110 | }; // end namespace MU_AVR 111 | 112 | #endif 113 | #else // __AVR__ 114 | #define STACK_COMPUTE /* do nothing; MemoryUsageAVR library emulation not included */ 115 | #endif 116 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/memoryCommands.cpp: -------------------------------------------------------------------------------- 1 | // memoryCommands.cpp 2 | // Basic support for reading Arduino RAM/PROGMEM/EEPROM data 3 | // through a serial command line interface 4 | // 5 | //////////////////////////////////////////////////////////////////////////////// 6 | #include 7 | #include 8 | #include "ArduinoCommands.h" 9 | #ifdef AVR 10 | #include 11 | #endif 12 | 13 | static int checkSyntax(void) 14 | { 15 | shell.println(F("check syntax")); 16 | return -18; 17 | } 18 | 19 | //////////////////////////////////////////////////////////////////////////////// 20 | long parseAsHex(const char *aValue) 21 | { 22 | long value = -1; // parse failure 23 | bool done = false; 24 | for (int i = 0; ((i < 8) && !done); i++) { 25 | int nextDigit = -1; 26 | char nextChar = tolower(aValue[i]); 27 | switch (nextChar) { 28 | case '0' ... '9': 29 | nextDigit = nextChar - '0'; 30 | break; 31 | case 'a' ... 'f': 32 | nextDigit = nextChar - 'a' + 0xa; 33 | break; 34 | default: 35 | done = true; 36 | break; 37 | } 38 | if (nextDigit > -1) { 39 | if (value < 0) { 40 | value = nextDigit; // first digit 41 | } else { 42 | value <<= 4; // make room 43 | value |= nextDigit; 44 | } 45 | } 46 | } 47 | return value; 48 | } 49 | 50 | //////////////////////////////////////////////////////////////////////////////// 51 | void prettyPrintChars(int lineNo, const char *chars, int numChars) 52 | { 53 | shell.print(lineNo, HEX); 54 | for (int j = 0; j < numChars; j++) { // hex values 55 | unsigned char b = chars[j]; 56 | shell.print(F(" ")); 57 | if(b < 0x10) { 58 | shell.print(F("0")); 59 | } 60 | shell.print(b, HEX); 61 | } 62 | shell.print(F(" ")); 63 | for (int j = 0; j < numChars; j++) { // text values 64 | char b = chars[j]; 65 | bool printIt = isPrintable(b); 66 | shell.print(printIt ? b : '.'); 67 | } 68 | shell.println(); 69 | } 70 | 71 | static const int rowSize = 0x10; 72 | 73 | //////////////////////////////////////////////////////////////////////////////// 74 | // displaying any kind of memory is pretty similar, even if access is different 75 | // 76 | int dumpAMemory(int argc, char **argv) 77 | { 78 | const char dumperNameStarts = tolower(argv[0][0]); 79 | bool dumpingRAM = (dumperNameStarts == 'r'); 80 | #ifdef AVR 81 | bool dumpingEEPROM = (dumperNameStarts == 'e'); 82 | #endif 83 | bool dumpingPROGMEM = (dumperNameStarts == 'p'); 84 | 85 | int begin = 0; 86 | if (argc > 1) { 87 | begin = parseAsHex(argv[1]); // start address 88 | if (begin < 0) { 89 | return checkSyntax(); 90 | } 91 | } 92 | 93 | int end = begin + rowSize; 94 | #ifdef AVR 95 | if (dumpingEEPROM) { 96 | end = (unsigned) EEPROM.length(); // default do all EEPROM 97 | } 98 | #endif 99 | if (argc > 2) { 100 | int numBytes = parseAsHex(argv[2]); 101 | if (numBytes < 0) { 102 | return checkSyntax(); 103 | } 104 | end = begin + numBytes; 105 | } 106 | 107 | char aLine[rowSize]; 108 | for (int i = begin; i < end; i += rowSize) { 109 | for (int j = 0; j < rowSize; j++) { 110 | char b = 0; 111 | if (dumpingPROGMEM) { 112 | b = pgm_read_byte( (void *) (i + j)); 113 | } 114 | if (dumpingRAM) { 115 | const char *allRam = (char *) i; 116 | b = allRam[j]; 117 | } 118 | #ifdef AVR 119 | if (dumpingEEPROM) { 120 | b = EEPROM.read(i + j); 121 | } 122 | #endif 123 | aLine[j] = b; 124 | } 125 | prettyPrintChars(i, aLine, rowSize); 126 | } 127 | return 0; 128 | } 129 | 130 | //////////////////////////////////////////////////////////////////////////////// 131 | int addMemoryCommands(SimpleSerialShell & shell) 132 | { 133 | #ifdef AVR 134 | shell.addCommand(F("eeprom? [ []]"), dumpAMemory); 135 | #endif 136 | shell.addCommand(F("ram? [ []]"), dumpAMemory); 137 | shell.addCommand(F("progmem? [ []]"), dumpAMemory); 138 | return 0; 139 | } 140 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/memoryUsageCommands.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ArduinoCommands.h" 3 | 4 | #if defined(AVR) 5 | 6 | #include "MemoryUsageAVR.h" 7 | #include 8 | 9 | using namespace MU_AVR; 10 | 11 | //////////////////////////////////////////////////////////////////////////////// 12 | // probe memory usage 13 | // NOTE this function call needs/uses a little more memory/stack 14 | // 15 | int memoryUsageProbe(int argc = 0, char **argv = NULL) 16 | { 17 | STACK_COMPUTE // call as needed to probe current stack depth 18 | 19 | bool all = false; 20 | bool graphical = false; 21 | bool heap = false; 22 | bool stack = false; 23 | bool free = false; 24 | 25 | if (argc < 2) 26 | { // default 27 | graphical = true; 28 | } 29 | else 30 | { 31 | for (char *flagPtr = argv[1]; *flagPtr != 0; flagPtr++) 32 | { 33 | switch (*flagPtr) 34 | { 35 | case 'a': // all 36 | all = true; 37 | break; 38 | case 'h': // heap 39 | heap = true; 40 | break; 41 | case 'g': // graphical 42 | graphical = true; 43 | break; 44 | case 's': 45 | stack = true; 46 | break; 47 | case 'f': 48 | free = true; 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | } 55 | 56 | // helpers to print a line of args to shell 57 | #define PRINT2(a, b) shell.print((a)); shell.println((b)); 58 | #define PRINT2HEX(a, b) shell.print((a)); shell.print((b)); shell.print(F("/0x")); shell.println((b),HEX); 59 | 60 | if (all) 61 | { 62 | PRINT2HEX(F("Data start: "), getDataStart()); 63 | } 64 | if (heap || all) 65 | { 66 | PRINT2HEX(F("Heap start: "), getHeapStart()); 67 | PRINT2HEX(F("Heap end: "), getHeapEnd()); 68 | PRINT2(F("Heap size: "), getHeapSize()); 69 | } 70 | if (stack || all) 71 | { 72 | PRINT2HEX(F("Stack start: "), getStackStart()); 73 | PRINT2HEX(F("Stack end: "), getMemoryEnd()); 74 | PRINT2(F("Stack size: "), getStackSize()); 75 | PRINT2(F("Stack Max Size (at STACK_COMPUTE): "), maxStackSize); 76 | PRINT2(F(" num STACK_COMPUTE checkpoints: "), numStackComputeCalls); 77 | } 78 | if (free || all) 79 | { 80 | PRINT2(F("Free Ram Size: "), getFreeRam()); 81 | } 82 | if (graphical || all) 83 | { 84 | SRamDisplay(); 85 | } 86 | return 0; 87 | }; 88 | 89 | int addMemoryUsageCommands(SimpleSerialShell &shell) 90 | { 91 | STACK_COMPUTE 92 | shell.addCommand(F("memory?"), memoryUsageProbe); 93 | return 0; 94 | } 95 | #else 96 | // otherwise not using the MemoryUsage library. 97 | #endif 98 | -------------------------------------------------------------------------------- /examples/ArduinoTextInterface/memory_probe.cpp: -------------------------------------------------------------------------------- 1 | // memory_probe.cpp 2 | // 3 | // Storage measurement 4 | // 5 | // See also: 6 | // https://playground.arduino.cc/Code/AvailableMemory/ 7 | // https://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory 8 | // https://github.com/mpflaga/Arduino-MemoryFree 9 | // https://github.com/Locoduino/MemoryUsage 10 | #ifdef AVR 11 | #include 12 | #include "ArduinoCommands.h" 13 | 14 | #ifdef __arm__ 15 | // should use uinstd.h to define sbrk but Due causes a conflict 16 | extern "C" char* sbrk(int incr); 17 | #else // __ARM__ 18 | //extern char *__brkval; 19 | extern uint8_t *__brkval; 20 | #endif // __arm__ 21 | 22 | //extern unsigned int __heap_start; 23 | extern uint8_t * __heap_start; 24 | //extern void *__brkval; 25 | 26 | /* 27 | The free list structure as maintained by the 28 | avr-libc memory allocation routines. 29 | 30 | Warning: this __freelist struct matches the real one ONLY BY COINCIDENCE 31 | Keep it in sync! 32 | */ 33 | struct __freelist { 34 | size_t sz; 35 | struct __freelist *nx; 36 | }; 37 | 38 | /* The head of the free list structure */ 39 | extern struct __freelist *__flp; 40 | 41 | //#include "MemoryFree.h" 42 | 43 | /* Calculates the size of the free list */ 44 | // may be fragmented/unusable... 45 | int freeListSize() { 46 | struct __freelist* current; 47 | int total = 0; 48 | int numFragments = 0; 49 | for (current = __flp; current; current = current->nx) { 50 | total += 2; /* Add two bytes for the memory block's header */ 51 | total += (int) current->sz; 52 | numFragments++; 53 | } 54 | if (numFragments) { 55 | Serial.print(F("unrecovered fragments: ")); 56 | Serial.println(numFragments); 57 | } 58 | if (total) { 59 | Serial.print(F("(fragmented) free list space: ")); 60 | Serial.println(total); 61 | } 62 | return total; 63 | } 64 | 65 | int memorySimpleProbe(int, char **) 66 | { 67 | int free_memory; // also at top of stack 68 | if ((int)__brkval == 0) { 69 | shell.println(F("no malloc()/heap in use yet.")); 70 | free_memory = ((int)&free_memory) - ((int)&__heap_start); 71 | } else { 72 | //Serial.println(F("including size of free list.")); 73 | free_memory = ((int)&free_memory) - ((int)__brkval); 74 | shell.print(F("Unfragmented memory (usable for stack): ")); 75 | shell.println(free_memory); 76 | //Serial.print(F("free list space: ")); 77 | //Serial.println(freeListSize()); 78 | free_memory += freeListSize(); 79 | } 80 | shell.print(F("Total free memory ")); 81 | shell.println(free_memory); 82 | 83 | return free_memory; 84 | } 85 | 86 | int addStackHeapCommands(SimpleSerialShell &shell) 87 | { 88 | shell.addCommand(F("stackheap?"), memorySimpleProbe); 89 | return 0; 90 | } 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /examples/EchoCommand/EchoCommand.ino: -------------------------------------------------------------------------------- 1 | // basic example testing of the command parser. 2 | #include 3 | 4 | //////////////////////////////////////////////////////////////////////////////// 5 | // Forgot what sketch was loaded to this board? 6 | // 7 | // Hint1: use the F() macro to keep const strings in FLASH and save RAM 8 | // Hint2: "Compiler " "catenates consecutive " 9 | // "strings together" 10 | // (improves readability for very long strings) 11 | // 12 | int showID(int /*argc*/ = 0, char** /*argv*/ = NULL) 13 | { 14 | shell.println(F( "Running " __FILE__ ", Built " __DATE__)); 15 | return 0; 16 | }; 17 | 18 | 19 | //////////////////////////////////////////////////////////////////////////////// 20 | // echo 21 | // simple version of Unix style 'echo' 22 | // 23 | // example: "echo Hello world" 24 | // prints: "Hello world\n" to attached stream (Serial, Serial1 etc.). 25 | // 26 | //////////////////////////////////////////////////////////////////////////////// 27 | // 28 | int echo(int argc, char **argv) 29 | { 30 | auto lastArg = argc - 1; 31 | for ( int i = 1; i < argc; i++) { 32 | 33 | shell.print(argv[i]); 34 | 35 | if (i < lastArg) 36 | shell.print(F(" ")); 37 | } 38 | shell.println(); 39 | 40 | return EXIT_SUCCESS; 41 | } 42 | 43 | 44 | //////////////////////////////////////////////////////////////////////////////// 45 | // non blocking LED toggle 46 | // 47 | void toggleLED_nb(void) 48 | { 49 | static auto lastToggle = millis(); // saved between calls 50 | auto now = millis(); 51 | 52 | if (now - lastToggle > 1000) 53 | { 54 | // toggle 55 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 56 | lastToggle = now; 57 | } 58 | } 59 | 60 | //////////////////////////////////////////////////////////////////////////////// 61 | void setup() { 62 | // put your setup code here, to run once: 63 | 64 | pinMode(LED_BUILTIN, OUTPUT); 65 | 66 | //Serial.begin(9600); older default bit rate 67 | Serial.begin(115200); 68 | while (!Serial) { 69 | // wait for serial port to connect. Needed for native USB port only 70 | // AND you want to block until there's a connection 71 | // otherwise the shell can quietly drop output. 72 | } 73 | 74 | //example 75 | shell.attach(Serial); 76 | shell.addCommand(F("id?"), showID); 77 | 78 | // adds a basic "echo" command 79 | shell.addCommand(F("echo"), echo); 80 | 81 | // adds an "echo" command with argument hints for help 82 | // nice for complex commands 83 | shell.addCommand(F("echoWithHints ..."), echo); 84 | 85 | showID(); 86 | Serial.println(F("Ready.")); 87 | } 88 | 89 | // non blocking LED toggle 90 | extern void toggleLED_nb(void); 91 | 92 | //////////////////////////////////////////////////////////////////////////////// 93 | void loop() { 94 | // put your main code here, to run repeatedly: 95 | 96 | //shell->execute("echo Hello World"); 97 | shell.executeIfInput(); 98 | 99 | // show loop() is still running -- not waiting 100 | toggleLED_nb(); 101 | } 102 | -------------------------------------------------------------------------------- /examples/IdentifyTheSketch/IdentifyTheSketch.ino: -------------------------------------------------------------------------------- 1 | // basic example testing of the command parser. 2 | #include 3 | 4 | ///////////////////////////////////////////////////////////////////////////////////// 5 | // Forgot what sketch was loaded to this board? 6 | // 7 | // Hint1: use the F() macro to keep const strings in FLASH and save RAM 8 | // Hint2: "Compiler " "catenates consecutive " 9 | // "strings together" 10 | // (improves readability for very long strings) 11 | // 12 | int showID(int /*argc*/ = 0, char** /*argv*/ = NULL) 13 | { 14 | shell.println(F( "Running " __FILE__ ", Built " __DATE__)); 15 | return 0; 16 | }; 17 | 18 | 19 | //////////////////////////////////////////////////////////////////////////////// 20 | void setup() { 21 | // put your setup code here, to run once: 22 | 23 | Serial.begin(115200); 24 | while (!Serial) { 25 | // wait for serial port to connect. Needed for native USB port only 26 | // AND you want to block until there's a connection 27 | // otherwise the shell can quietly drop output. 28 | } 29 | 30 | //example 31 | shell.attach(Serial); 32 | shell.addCommand(F("id?"), showID); 33 | 34 | showID(); 35 | shell.println(F("Ready.")); 36 | shell.println(F("Type 'help' for help")); 37 | } 38 | 39 | //////////////////////////////////////////////////////////////////////////////// 40 | void loop() { 41 | // put your main code here, to run repeatedly: 42 | 43 | shell.executeIfInput(); 44 | } 45 | -------------------------------------------------------------------------------- /extras/tests/CustomParserTest/CustomParserTest.ino: -------------------------------------------------------------------------------- 1 | // CustomParserTest.ino 2 | // 3 | #include 4 | 5 | // fake it for UnixHostDuino emulation 6 | #if defined(UNIX_HOST_DUINO) 7 | # ifndef ARDUINO 8 | # define ARDUINO 100 9 | # endif 10 | #endif 11 | 12 | // These tests depend on the Arduino "AUnit" library 13 | #include 14 | #include "SimulatedStream.h" 15 | #include 16 | 17 | using namespace aunit; 18 | 19 | // some platforms ouput line endings differently. 20 | #define NEW_LINE "\r\n" 21 | //#define NEW_LINE "\n" 22 | #define COMMAND_PROMPT NEW_LINE "> " 23 | 24 | // A mock of the Arduino Serial stream 25 | static SimulatedStream<128> terminal; 26 | 27 | void prepForTests(void) 28 | { 29 | terminal.init(); 30 | shell.resetBuffer(); 31 | } 32 | 33 | ////////////////////////////////////////////////////////////////////////////// 34 | // test fixture to ensure clean initial and final conditions 35 | // 36 | class CustomParserTest: public TestOnce { 37 | protected: 38 | void setup() override { 39 | TestOnce::setup(); 40 | prepForTests(); 41 | } 42 | 43 | void teardown() override { 44 | prepForTests(); 45 | TestOnce::teardown(); 46 | } 47 | }; 48 | 49 | ////////////////////////////////////////////////////////////////////////////// 50 | // A demonstration of the alternate tokenizer feature. 51 | 52 | /** 53 | * @brief Echoes the parameters of the function with comma-delimiting. 54 | * @return int 0 On success, non-zero on error. 55 | */ 56 | int echo(int argc, char **argv) { 57 | auto lastArg = argc - 1; 58 | for ( int i = 1; i < argc; i++) { 59 | shell.print(argv[i]); 60 | if (i < lastArg) 61 | shell.print(F(" ")); 62 | } 63 | shell.println(); 64 | return 0; 65 | } 66 | 67 | /** 68 | * @brief An alternate tokenizer that uses a comma-delimiter after the 69 | * initial token has been identified. 70 | */ 71 | char* commaTokenizer(char* str, const char*, char** saveptr) { 72 | // The first time we tokenize on space so that we can 73 | // pick up the command. Subsequent calls we tokenize on 74 | // comma. 75 | if (str != 0) { 76 | return strtok_r(str, " ", saveptr); 77 | } else { 78 | return strtok_r(str, ",", saveptr); 79 | } 80 | } 81 | 82 | testF(CustomParserTest, altTokenizer) { 83 | 84 | const char* testCommand = "echo test1,test2,test3"; 85 | 86 | // Basic sanity check using the current semantics 87 | terminal.pressKeys(testCommand); 88 | assertFalse(shell.executeIfInput()); 89 | // Flush the user-entry 90 | terminal.getline(); 91 | terminal.pressKey('\r'); 92 | assertTrue(shell.executeIfInput()); 93 | // Everything comes back as a single token 94 | assertEqual(terminal.getline(), (NEW_LINE "test1,test2,test3" COMMAND_PROMPT)); 95 | 96 | // Now plug in a new tokenizer that looks for commas instead of spaces. 97 | // This is the main point of this unit test. 98 | shell.setTokenizer(commaTokenizer); 99 | 100 | // Send in the same command 101 | terminal.pressKeys(testCommand); 102 | assertFalse(shell.executeIfInput()); 103 | // Flush the user-entry 104 | terminal.getline(); 105 | terminal.pressKey('\r'); 106 | assertTrue(shell.executeIfInput()); 107 | // Notice now that the three tokens were separated 108 | assertEqual(terminal.getline(), (NEW_LINE "test1 test2 test3" COMMAND_PROMPT)); 109 | } 110 | 111 | ////////////////////////////////////////////////////////////////////////////// 112 | // ... so which sketch is this? 113 | int showID(int /*argc*/ = 0, char ** /*argv*/ = NULL) 114 | { 115 | Serial.println(); 116 | Serial.println(F( "Running " __FILE__ ", Built " __DATE__)); 117 | return 0; 118 | }; 119 | 120 | 121 | ////////////////////////////////////////////////////////////////////////////// 122 | void setup() { 123 | ::delay(1000); // wait for stability on some boards to prevent garbage Serial 124 | Serial.begin(115200); // ESP8266 default of 74880 not supported on Linux 125 | while (!Serial); // for the Arduino Leonardo/Micro only 126 | showID(); 127 | 128 | shell.addCommand(F("echo"), echo); 129 | shell.attach(terminal); 130 | } 131 | 132 | ////////////////////////////////////////////////////////////////////////////// 133 | void loop() { 134 | // Should get: 135 | // TestRunner summary: 136 | // passed, failed, skipped, timed out, out of test(s). 137 | aunit::TestRunner::run(); 138 | } 139 | -------------------------------------------------------------------------------- /extras/tests/CustomParserTest/Makefile: -------------------------------------------------------------------------------- 1 | # See https://github.com/bxparks/EpoxyDuino for documentation about this 2 | # Makefile to compile and run Arduino programs natively on Linux or MacOS. 3 | # 4 | # NOTE: 5 | # Brian Parks has renamed the UnixHostDunio library to EpoxyDuino so at some 6 | # point this will need ot be changed to: 7 | # include ../../../../EpoxyDuino/UnixHostDuino.mk 8 | # 9 | APP_NAME := CustomParserTest 10 | ARDUINO_LIBS := AUnit SimpleSerialShell 11 | CPPFLAGS += -Werror 12 | include ../../../../EpoxyDuino/UnixHostDuino.mk 13 | #(old) include ../../../../UnixHostDuino/UnixHostDuino.mk 14 | -------------------------------------------------------------------------------- /extras/tests/CustomParserTest/SimulatedStream.h: -------------------------------------------------------------------------------- 1 | #ifndef _TestStream_h 2 | #define _TestStream_h 3 | 4 | #include 5 | #include "simpleFIFO.h" 6 | 7 | /** 8 | * @brief A simple implementation fo the Stream interface. Useful for 9 | * creating mock Serial objects for unit testing. 10 | * 11 | * A header-only class for ease of re-use across tests cases. 12 | */ 13 | template 14 | class SimulatedStream : public Stream { 15 | public: 16 | 17 | SimulatedStream() { } 18 | virtual ~SimulatedStream() { } 19 | 20 | void init() { 21 | _outputBuffer.flush(); 22 | _inputBuffer.flush(); 23 | } 24 | 25 | String getline() { 26 | return getOutput(); 27 | } 28 | 29 | /** 30 | * @brief Returns the accumulated output and then flushes. 31 | */ 32 | String getOutput() { 33 | String result; 34 | result.reserve(MAX_BUFSIZE); 35 | while (_outputBuffer.count() > 0) { 36 | result.concat(_outputBuffer.dequeue()); 37 | } 38 | return result; 39 | } 40 | 41 | /** 42 | * @brief Simulate the entry of multiple inbound characters. 43 | * 44 | * @param keys 45 | */ 46 | void pressKeys(const char* keys) { 47 | for (unsigned int i = 0; keys[i]; i++) { 48 | pressKey(keys[i]); 49 | } 50 | } 51 | 52 | /** 53 | * @brief Simulates an inbound character (ex: a serial keypress) 54 | * 55 | * @param key 56 | */ 57 | void pressKey(char key) { 58 | _inputBuffer.enqueue(key); 59 | } 60 | 61 | // ------------------------------------------------------------------------ 62 | // Implementation of the Stream interface. 63 | 64 | virtual size_t write(uint8_t c) { 65 | _outputBuffer.enqueue(c); 66 | return 1; 67 | } 68 | 69 | virtual int available() { 70 | return _inputBuffer.count(); 71 | } 72 | 73 | virtual int read() { 74 | return available() ? _inputBuffer.dequeue() : -1; 75 | } 76 | 77 | virtual int peek() { 78 | return available() ? _inputBuffer.peek() : -1; 79 | } 80 | 81 | virtual void flush() { 82 | } 83 | 84 | private: 85 | 86 | SimpleFIFO _inputBuffer; 87 | SimpleFIFO _outputBuffer; 88 | }; 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /extras/tests/CustomParserTest/simpleFIFO.h: -------------------------------------------------------------------------------- 1 | // see https://github.com/rambo/SimpleFIFO 2 | #ifndef SimpleFIFO_h 3 | #define SimpleFIFO_h 4 | #include 5 | #ifndef SIMPLEFIFO_SIZE_TYPE 6 | #ifndef SIMPLEFIFO_LARGE 7 | #define SIMPLEFIFO_SIZE_TYPE uint8_t 8 | #else 9 | #define SIMPLEFIFO_SIZE_TYPE uint16_t 10 | #endif 11 | #endif 12 | /* 13 | || 14 | || @file SimpleFIFO.h 15 | || @version 1.2 16 | || @author Alexander Brevig 17 | || @contact alexanderbrevig@gmail.com 18 | || 19 | || @description 20 | || | A simple FIFO class, mostly for primitive types but can be used with classes if assignment to int is allowed 21 | || | This FIFO is not dynamic, so be sure to choose an appropriate size for it 22 | || # 23 | || 24 | || @license 25 | || | Copyright (c) 2010 Alexander Brevig 26 | || | This library is free software; you can redistribute it and/or 27 | || | modify it under the terms of the GNU Lesser General Public 28 | || | License as published by the Free Software Foundation; version 29 | || | 2.1 of the License. 30 | || | 31 | || | This library is distributed in the hope that it will be useful, 32 | || | but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | || | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 34 | || | Lesser General Public License for more details. 35 | || | 36 | || | You should have received a copy of the GNU Lesser General Public 37 | || | License along with this library; if not, write to the Free Software 38 | || | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 39 | || # 40 | || 41 | */ 42 | template 43 | class SimpleFIFO { 44 | public: 45 | const SIMPLEFIFO_SIZE_TYPE size; //speculative feature, in case it's needed 46 | 47 | SimpleFIFO(); 48 | 49 | T dequeue(); //get next element 50 | bool enqueue( T element ); //add an element 51 | T peek() const; //get the next element without releasing it from the FIFO 52 | void flush(); //[1.1] reset to default state 53 | 54 | //how many elements are currently in the FIFO? 55 | SIMPLEFIFO_SIZE_TYPE count() { 56 | return numberOfElements; 57 | } 58 | 59 | private: 60 | #ifndef SimpleFIFO_NONVOLATILE 61 | volatile SIMPLEFIFO_SIZE_TYPE numberOfElements; 62 | volatile SIMPLEFIFO_SIZE_TYPE nextIn; 63 | volatile SIMPLEFIFO_SIZE_TYPE nextOut; 64 | volatile T raw[rawSize]; 65 | #else 66 | SIMPLEFIFO_SIZE_TYPE numberOfElements; 67 | SIMPLEFIFO_SIZE_TYPE nextIn; 68 | SIMPLEFIFO_SIZE_TYPE nextOut; 69 | T raw[rawSize]; 70 | #endif 71 | }; 72 | 73 | template 74 | SimpleFIFO::SimpleFIFO() : size(rawSize) { 75 | flush(); 76 | } 77 | template 78 | bool SimpleFIFO::enqueue( T element ) { 79 | if ( count() >= rawSize ) { 80 | return false; 81 | } 82 | numberOfElements++; 83 | nextIn %= size; 84 | raw[nextIn] = element; 85 | nextIn++; //advance to next index 86 | return true; 87 | } 88 | template 89 | T SimpleFIFO::dequeue() { 90 | numberOfElements--; 91 | nextOut %= size; 92 | return raw[ nextOut++]; 93 | } 94 | template 95 | T SimpleFIFO::peek() const { 96 | return raw[ nextOut % size]; 97 | } 98 | template 99 | void SimpleFIFO::flush() { 100 | nextIn = nextOut = numberOfElements = 0; 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /extras/tests/HelpTest/HelpTest.ino: -------------------------------------------------------------------------------- 1 | // HelpTest.ino 2 | // 3 | #include 4 | 5 | // fake it for UnixHostDuino emulation 6 | #if defined(UNIX_HOST_DUINO) 7 | # ifndef ARDUINO 8 | # define ARDUINO 100 9 | # endif 10 | #endif 11 | 12 | // These tests depend on the Arduino "AUnit" library 13 | #include 14 | #include "SimulatedStream.h" 15 | #include 16 | 17 | // Some platforms ouput line endings differently. 18 | #define NEW_LINE "\r\n" 19 | #define HELP_PREAMBLE "Commands available are:" 20 | #define TWO_SPACE " " 21 | #define COMMAND_PROMPT NEW_LINE "> " 22 | 23 | // A mock of the Arduino Serial stream 24 | static SimulatedStream<128> terminal; 25 | 26 | void prepForTests(void) 27 | { 28 | shell.resetBuffer(); 29 | } 30 | 31 | ////////////////////////////////////////////////////////////////////////////// 32 | // test fixture to ensure clean initial and final conditions 33 | // 34 | class HelpTest: public aunit::TestOnce { 35 | protected: 36 | void setup() override { 37 | TestOnce::setup(); 38 | prepForTests(); 39 | } 40 | 41 | void teardown() override { 42 | prepForTests(); 43 | TestOnce::teardown(); 44 | } 45 | }; 46 | 47 | int echo(int, char **) 48 | { 49 | return 0; 50 | } 51 | 52 | // Define a new command, keeping the documentation close by. 53 | 54 | #define rangeCommandNameAndDocs F("range ") 55 | 56 | int rangeCommand(int, char **) 57 | { 58 | // simulates setting range (lower, upper) 59 | return 0; 60 | } 61 | 62 | ////////////////////////////////////////////////////////////////////////////// 63 | // The goal of this test is to validate that the help messages 64 | // return as expected. 65 | testF(HelpTest, helpTest) 66 | { 67 | const char* testCommand = "help\r"; 68 | terminal.pressKeys(testCommand); 69 | assertTrue(shell.executeIfInput()); 70 | 71 | // The begining part of the response is the echo of the user's input. 72 | assertEqual(terminal.getline(), 73 | ("help" NEW_LINE 74 | HELP_PREAMBLE NEW_LINE 75 | TWO_SPACE "echo" NEW_LINE 76 | TWO_SPACE "help" NEW_LINE 77 | TWO_SPACE "range " COMMAND_PROMPT)); 78 | } 79 | 80 | ////////////////////////////////////////////////////////////////////////////// 81 | // The goal of this test is to check a bug (#28) that was introduced when using 82 | // a command that starts off with another. 83 | testF(HelpTest, helpTest2) 84 | { 85 | // Notice that this command text is a superset of the "range" command 86 | terminal.pressKeys("ranger 1 2\r"); 87 | assertTrue(shell.executeIfInput()); 88 | 89 | // The begining part of the response is the echo of the user's input. 90 | assertEqual(terminal.getline(), 91 | ("ranger 1 2" NEW_LINE 92 | "\"ranger\": -1: command not found" 93 | COMMAND_PROMPT)); 94 | 95 | // Notice that this command text is a subset of the "range" command 96 | terminal.pressKeys("rang 1 2\r"); 97 | assertTrue(shell.executeIfInput()); 98 | 99 | // The begining part of the response is the echo of the user's input. 100 | assertEqual(terminal.getline(), 101 | ("rang 1 2" NEW_LINE 102 | "\"rang\": -1: command not found" 103 | COMMAND_PROMPT)); 104 | 105 | // Now check the normal command 106 | terminal.pressKeys("range 1 2\r"); 107 | assertTrue(shell.executeIfInput()); 108 | 109 | // The begining part of the response is the echo of the user's input. 110 | assertEqual(terminal.getline(), 111 | ("range 1 2" 112 | COMMAND_PROMPT)); 113 | 114 | // Now check the normal command, except show that the comparison is 115 | // case-insensitive (per original implementation) 116 | terminal.pressKeys("Range 1 2\r"); 117 | assertTrue(shell.executeIfInput()); 118 | 119 | // The begining part of the response is the echo of the user's input. 120 | assertEqual(terminal.getline(), 121 | ("Range 1 2" 122 | COMMAND_PROMPT)); 123 | } 124 | 125 | ////////////////////////////////////////////////////////////////////////////// 126 | // ... so which sketch is this? 127 | int showID(int /*argc*/ = 0, char ** /*argv*/ = NULL) 128 | { 129 | Serial.println(); 130 | Serial.println(F( "Running " __FILE__ ", Built " __DATE__)); 131 | return 0; 132 | }; 133 | 134 | 135 | ////////////////////////////////////////////////////////////////////////////// 136 | void setup() { 137 | ::delay(1000); // wait for stability on some boards to prevent garbage Serial 138 | Serial.begin(115200); // ESP8266 default of 74880 not supported on Linux 139 | while (!Serial); // for the Arduino Leonardo/Micro only 140 | showID(); 141 | 142 | shell.addCommand(F("echo"), echo); 143 | shell.addCommand(rangeCommandNameAndDocs, rangeCommand); 144 | shell.attach(terminal); 145 | } 146 | 147 | ////////////////////////////////////////////////////////////////////////////// 148 | void loop() { 149 | // Should get: 150 | // TestRunner summary: 151 | // passed, failed, skipped, timed out, out of test(s). 152 | aunit::TestRunner::run(); 153 | } 154 | -------------------------------------------------------------------------------- /extras/tests/HelpTest/Makefile: -------------------------------------------------------------------------------- 1 | # See https://github.com/bxparks/EpoxyDuino for documentation about this 2 | # Makefile to compile and run Arduino programs natively on Linux or MacOS. 3 | # 4 | APP_NAME := HelpTest 5 | ARDUINO_LIBS := AUnit SimpleSerialShell 6 | CPPFLAGS += -Werror 7 | include ../../../../EpoxyDuino/UnixHostDuino.mk 8 | -------------------------------------------------------------------------------- /extras/tests/HelpTest/SimulatedStream.h: -------------------------------------------------------------------------------- 1 | #ifndef _TestStream_h 2 | #define _TestStream_h 3 | 4 | #include 5 | #include "simpleFIFO.h" 6 | 7 | /** 8 | * @brief A simple implementation fo the Stream interface. Useful for 9 | * creating mock Serial objects for unit testing. 10 | * 11 | * A header-only class for ease of re-use across tests cases. 12 | */ 13 | template 14 | class SimulatedStream : public Stream { 15 | public: 16 | 17 | SimulatedStream() { } 18 | virtual ~SimulatedStream() { } 19 | 20 | void init() { 21 | _outputBuffer.flush(); 22 | _inputBuffer.flush(); 23 | } 24 | 25 | String getline() { 26 | return getOutput(); 27 | } 28 | 29 | /** 30 | * @brief Returns the accumulated output and then flushes. 31 | */ 32 | String getOutput() { 33 | String result; 34 | result.reserve(MAX_BUFSIZE); 35 | while (_outputBuffer.count() > 0) { 36 | result.concat(_outputBuffer.dequeue()); 37 | } 38 | return result; 39 | } 40 | 41 | /** 42 | * @brief Simulate the entry of multiple inbound characters. 43 | * 44 | * @param keys 45 | */ 46 | void pressKeys(const char* keys) { 47 | for (unsigned int i = 0; keys[i]; i++) { 48 | pressKey(keys[i]); 49 | } 50 | } 51 | 52 | /** 53 | * @brief Simulates an inbound character (ex: a serial keypress) 54 | * 55 | * @param key 56 | */ 57 | void pressKey(char key) { 58 | _inputBuffer.enqueue(key); 59 | } 60 | 61 | // ------------------------------------------------------------------------ 62 | // Implementation of the Stream interface. 63 | 64 | virtual size_t write(uint8_t c) { 65 | _outputBuffer.enqueue(c); 66 | return 1; 67 | } 68 | 69 | virtual int available() { 70 | return _inputBuffer.count(); 71 | } 72 | 73 | virtual int read() { 74 | return available() ? _inputBuffer.dequeue() : -1; 75 | } 76 | 77 | virtual int peek() { 78 | return available() ? _inputBuffer.peek() : -1; 79 | } 80 | 81 | virtual void flush() { 82 | } 83 | 84 | private: 85 | 86 | SimpleFIFO _inputBuffer; 87 | SimpleFIFO _outputBuffer; 88 | }; 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /extras/tests/HelpTest/simpleFIFO.h: -------------------------------------------------------------------------------- 1 | // see https://github.com/rambo/SimpleFIFO 2 | #ifndef SimpleFIFO_h 3 | #define SimpleFIFO_h 4 | #include 5 | #ifndef SIMPLEFIFO_SIZE_TYPE 6 | #ifndef SIMPLEFIFO_LARGE 7 | #define SIMPLEFIFO_SIZE_TYPE uint8_t 8 | #else 9 | #define SIMPLEFIFO_SIZE_TYPE uint16_t 10 | #endif 11 | #endif 12 | /* 13 | || 14 | || @file SimpleFIFO.h 15 | || @version 1.2 16 | || @author Alexander Brevig 17 | || @contact alexanderbrevig@gmail.com 18 | || 19 | || @description 20 | || | A simple FIFO class, mostly for primitive types but can be used with classes if assignment to int is allowed 21 | || | This FIFO is not dynamic, so be sure to choose an appropriate size for it 22 | || # 23 | || 24 | || @license 25 | || | Copyright (c) 2010 Alexander Brevig 26 | || | This library is free software; you can redistribute it and/or 27 | || | modify it under the terms of the GNU Lesser General Public 28 | || | License as published by the Free Software Foundation; version 29 | || | 2.1 of the License. 30 | || | 31 | || | This library is distributed in the hope that it will be useful, 32 | || | but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | || | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 34 | || | Lesser General Public License for more details. 35 | || | 36 | || | You should have received a copy of the GNU Lesser General Public 37 | || | License along with this library; if not, write to the Free Software 38 | || | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 39 | || # 40 | || 41 | */ 42 | template 43 | class SimpleFIFO { 44 | public: 45 | const SIMPLEFIFO_SIZE_TYPE size; //speculative feature, in case it's needed 46 | 47 | SimpleFIFO(); 48 | 49 | T dequeue(); //get next element 50 | bool enqueue( T element ); //add an element 51 | T peek() const; //get the next element without releasing it from the FIFO 52 | void flush(); //[1.1] reset to default state 53 | 54 | //how many elements are currently in the FIFO? 55 | SIMPLEFIFO_SIZE_TYPE count() { 56 | return numberOfElements; 57 | } 58 | 59 | private: 60 | #ifndef SimpleFIFO_NONVOLATILE 61 | volatile SIMPLEFIFO_SIZE_TYPE numberOfElements; 62 | volatile SIMPLEFIFO_SIZE_TYPE nextIn; 63 | volatile SIMPLEFIFO_SIZE_TYPE nextOut; 64 | volatile T raw[rawSize]; 65 | #else 66 | SIMPLEFIFO_SIZE_TYPE numberOfElements; 67 | SIMPLEFIFO_SIZE_TYPE nextIn; 68 | SIMPLEFIFO_SIZE_TYPE nextOut; 69 | T raw[rawSize]; 70 | #endif 71 | }; 72 | 73 | template 74 | SimpleFIFO::SimpleFIFO() : size(rawSize) { 75 | flush(); 76 | } 77 | template 78 | bool SimpleFIFO::enqueue( T element ) { 79 | if ( count() >= rawSize ) { 80 | return false; 81 | } 82 | numberOfElements++; 83 | nextIn %= size; 84 | raw[nextIn] = element; 85 | nextIn++; //advance to next index 86 | return true; 87 | } 88 | template 89 | T SimpleFIFO::dequeue() { 90 | numberOfElements--; 91 | nextOut %= size; 92 | return raw[ nextOut++]; 93 | } 94 | template 95 | T SimpleFIFO::peek() const { 96 | return raw[ nextOut % size]; 97 | } 98 | template 99 | void SimpleFIFO::flush() { 100 | nextIn = nextOut = numberOfElements = 0; 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /extras/tests/Makefile: -------------------------------------------------------------------------------- 1 | tests: 2 | set -e; \ 3 | for i in *Test/Makefile; do \ 4 | echo '==== Making:' $$(dirname $$i); \ 5 | make -C $$(dirname $$i) -j; \ 6 | done 7 | 8 | runtests: 9 | set -e; \ 10 | for i in *Test/Makefile; do \ 11 | echo '==== Running:' $$(dirname $$i); \ 12 | $$(dirname $$i)/$$(dirname $$i).out; \ 13 | done 14 | 15 | clean: 16 | set -e; \ 17 | for i in *Test/Makefile; do \ 18 | echo '==== Cleaning:' $$(dirname $$i); \ 19 | make -C $$(dirname $$i) clean; \ 20 | done 21 | -------------------------------------------------------------------------------- /extras/tests/README.md: -------------------------------------------------------------------------------- 1 | Notes on Unit Tests 2 | =================== 3 | * Be sure to clone the EpoxyDuino project (https://github.com/bxparks/EpoxyDuino) adjacent 4 | to your SimpleSerialShell project. 5 | * Be sure to clone the AUnit project (https://github.com/bxparks/AUnit) adjacent 6 | to your SimpleSerialShell project. This is a dependency for unit testing. 7 | 8 | For example: 9 | ``` 10 | git clone https://github.com/bxparks/EpoxyDuino.git 11 | git clone https://github.com/bxparks/AUnit.git 12 | ``` 13 | 14 | 15 | -------------------------------------------------------------------------------- /extras/tests/simpleSerialShellTest/Makefile: -------------------------------------------------------------------------------- 1 | # See https://github.com/bxparks/EpoxyDuino for documentation about this 2 | # Makefile to compile and run Arduino programs natively on Linux or MacOS. 3 | # 4 | # NOTE: 5 | # Brian Parks has renamed the UnixHostDunio library to EpoxyDuino so at some 6 | # point this will need ot be changed to: 7 | # include ../../../../EpoxyDuino/UnixHostDuino.mk 8 | # 9 | APP_NAME := simpleSerialShellTest 10 | ARDUINO_LIBS := AUnit SimpleSerialShell 11 | CPPFLAGS += -Werror 12 | include ../../../../EpoxyDuino/UnixHostDuino.mk 13 | #(old) include ../../../../UnixHostDuino/UnixHostDuino.mk 14 | -------------------------------------------------------------------------------- /extras/tests/simpleSerialShellTest/SimMonitor.cpp: -------------------------------------------------------------------------------- 1 | // SimMonitor.cpp 2 | // 3 | #include 4 | //#include 5 | #include "shellTestHelpers.h" 6 | 7 | //////////////////////////////////////////////////////////////////////////////// 8 | // 9 | SimMonitor::SimMonitor(void) { 10 | init(); 11 | } 12 | 13 | void SimMonitor::init(void) { 14 | keyboardBuffer.flush(); 15 | displayBuffer.flush(); 16 | } 17 | 18 | //////////////////////////////////////////////////////////////////////////////// 19 | String SimMonitor::getline(void) { 20 | String theLine; 21 | theLine.reserve(BUFSIZE); 22 | while (displayBuffer.count() > 0) { 23 | theLine += displayBuffer.dequeue(); 24 | } 25 | return theLine; 26 | } 27 | 28 | //////////////////////////////////////////////////////////////////////////////// 29 | // get a character sent to the display 30 | int SimMonitor::getOutput(void) { 31 | int theKey = -1; 32 | if (displayBuffer.count() > 0) { 33 | theKey = displayBuffer.dequeue(); 34 | } 35 | return theKey; 36 | } 37 | 38 | //////////////////////////////////////////////////////////////////////////////// 39 | // simulate a keypress 40 | size_t SimMonitor::pressKey(char c) { 41 | return keyboardBuffer.enqueue(c); 42 | } 43 | 44 | //////////////////////////////////////////////////////////////////////////////// 45 | // simulate a bunch of keypresses 46 | size_t SimMonitor::pressKeys(const char * keys) { 47 | size_t numSent = 0; 48 | 49 | for (int i = 0; keys[i]; i++) { 50 | numSent += pressKey(keys[i]); 51 | } 52 | 53 | return numSent; 54 | } 55 | 56 | 57 | //////////////////////////////////////////////////////////////////////////////// 58 | //////////////////////////////////////////////////////////////////////////////// 59 | // stream interface 60 | size_t SimMonitor::write(uint8_t aByte) // write to "display" 61 | { 62 | // carriage return should reset line? 63 | return displayBuffer.enqueue(aByte); 64 | } 65 | 66 | int SimMonitor::available() // any keypresses? 67 | { 68 | return (keyboardBuffer.count() > 0); 69 | } 70 | 71 | int SimMonitor::read() // read keyboard input 72 | { 73 | return available() ? keyboardBuffer.dequeue() : -1; 74 | } 75 | 76 | int SimMonitor::peek() 77 | { 78 | return available() ? keyboardBuffer.peek() : -1; 79 | } 80 | 81 | void SimMonitor::flush() 82 | { 83 | init(); 84 | } 85 | -------------------------------------------------------------------------------- /extras/tests/simpleSerialShellTest/shellTestCommands.cpp: -------------------------------------------------------------------------------- 1 | // shellTestCommands.cpp 2 | // 3 | #include 4 | #include 5 | #include "shellTestHelpers.h" 6 | 7 | //////////////////////////////////////////////////////////////////////////////// 8 | // echo 9 | // simple version of Unix style 'echo' 10 | // 11 | // example: "echo Hello world" 12 | // prints: "Hello world\n" to attached stream (Serial, Serial1 etc.). 13 | // 14 | //////////////////////////////////////////////////////////////////////////////// 15 | // 16 | int echo(int argc, char **argv) 17 | { 18 | auto lastArg = argc - 1; 19 | for ( int i = 1; i < argc; i++) { 20 | 21 | shell.print(argv[i]); 22 | 23 | if (i < lastArg) 24 | shell.print(F(" ")); 25 | } 26 | shell.println(); 27 | 28 | return EXIT_SUCCESS; 29 | } 30 | 31 | //////////////////////////////////////////////////////////////////////////////// 32 | // calculate the sum of the arguments 33 | // "sum 5 3" returns 8. 34 | // 35 | int sum(int argc, char **argv) 36 | { 37 | int aSum = 0; 38 | 39 | for ( int i = 1; i < argc; i++) { 40 | aSum += atoi(argv[i]); 41 | } 42 | 43 | return aSum; 44 | } 45 | 46 | 47 | void addTestCommands(void) { 48 | shell.addCommand(F("echo"), echo); 49 | shell.addCommand(F("sum"), sum); 50 | } 51 | -------------------------------------------------------------------------------- /extras/tests/simpleSerialShellTest/shellTestHelpers.h: -------------------------------------------------------------------------------- 1 | // shellTestHelpers.h 2 | // 3 | //#include 4 | #include 5 | #include "simpleFIFO.h" 6 | 7 | // SimMonitor simulates a serial terminal 8 | class SimMonitor: public Stream { 9 | 10 | public: 11 | SimMonitor(void); 12 | void init(void); 13 | String getline(void); // get the line sent to display 14 | int getOutput(void); // get display output char, or -1 if none 15 | 16 | size_t pressKeys(const char * s); // send a line 17 | size_t pressKey(char c); // simulate a keypress 18 | 19 | // stream emulation 20 | virtual size_t write(uint8_t); 21 | virtual int available(); 22 | virtual int read(); 23 | virtual int peek(); 24 | virtual void flush(); // esp32 needs an implementation 25 | 26 | private: 27 | static const int BUFSIZE = 80; 28 | SimpleFIFO keyboardBuffer; 29 | SimpleFIFO displayBuffer; 30 | }; 31 | 32 | void addTestCommands(void); 33 | -------------------------------------------------------------------------------- /extras/tests/simpleSerialShellTest/simpleFIFO.h: -------------------------------------------------------------------------------- 1 | // see https://github.com/rambo/SimpleFIFO 2 | #ifndef SimpleFIFO_h 3 | #define SimpleFIFO_h 4 | #include 5 | #ifndef SIMPLEFIFO_SIZE_TYPE 6 | #ifndef SIMPLEFIFO_LARGE 7 | #define SIMPLEFIFO_SIZE_TYPE uint8_t 8 | #else 9 | #define SIMPLEFIFO_SIZE_TYPE uint16_t 10 | #endif 11 | #endif 12 | /* 13 | || 14 | || @file SimpleFIFO.h 15 | || @version 1.2 16 | || @author Alexander Brevig 17 | || @contact alexanderbrevig@gmail.com 18 | || 19 | || @description 20 | || | A simple FIFO class, mostly for primitive types but can be used with classes if assignment to int is allowed 21 | || | This FIFO is not dynamic, so be sure to choose an appropriate size for it 22 | || # 23 | || 24 | || @license 25 | || | Copyright (c) 2010 Alexander Brevig 26 | || | This library is free software; you can redistribute it and/or 27 | || | modify it under the terms of the GNU Lesser General Public 28 | || | License as published by the Free Software Foundation; version 29 | || | 2.1 of the License. 30 | || | 31 | || | This library is distributed in the hope that it will be useful, 32 | || | but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | || | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 34 | || | Lesser General Public License for more details. 35 | || | 36 | || | You should have received a copy of the GNU Lesser General Public 37 | || | License along with this library; if not, write to the Free Software 38 | || | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 39 | || # 40 | || 41 | */ 42 | template 43 | class SimpleFIFO { 44 | public: 45 | const SIMPLEFIFO_SIZE_TYPE size; //speculative feature, in case it's needed 46 | 47 | SimpleFIFO(); 48 | 49 | T dequeue(); //get next element 50 | bool enqueue( T element ); //add an element 51 | T peek() const; //get the next element without releasing it from the FIFO 52 | void flush(); //[1.1] reset to default state 53 | 54 | //how many elements are currently in the FIFO? 55 | SIMPLEFIFO_SIZE_TYPE count() { 56 | return numberOfElements; 57 | } 58 | 59 | private: 60 | #ifndef SimpleFIFO_NONVOLATILE 61 | volatile SIMPLEFIFO_SIZE_TYPE numberOfElements; 62 | volatile SIMPLEFIFO_SIZE_TYPE nextIn; 63 | volatile SIMPLEFIFO_SIZE_TYPE nextOut; 64 | volatile T raw[rawSize]; 65 | #else 66 | SIMPLEFIFO_SIZE_TYPE numberOfElements; 67 | SIMPLEFIFO_SIZE_TYPE nextIn; 68 | SIMPLEFIFO_SIZE_TYPE nextOut; 69 | T raw[rawSize]; 70 | #endif 71 | }; 72 | 73 | template 74 | SimpleFIFO::SimpleFIFO() : size(rawSize) { 75 | flush(); 76 | } 77 | template 78 | bool SimpleFIFO::enqueue( T element ) { 79 | if ( count() >= rawSize ) { 80 | return false; 81 | } 82 | numberOfElements++; 83 | nextIn %= size; 84 | raw[nextIn] = element; 85 | nextIn++; //advance to next index 86 | return true; 87 | } 88 | template 89 | T SimpleFIFO::dequeue() { 90 | numberOfElements--; 91 | nextOut %= size; 92 | return raw[ nextOut++]; 93 | } 94 | template 95 | T SimpleFIFO::peek() const { 96 | return raw[ nextOut % size]; 97 | } 98 | template 99 | void SimpleFIFO::flush() { 100 | nextIn = nextOut = numberOfElements = 0; 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /extras/tests/simpleSerialShellTest/simpleSerialShellTest.ino: -------------------------------------------------------------------------------- 1 | // 2 | // simpleSerialShellTest.ino 3 | // 4 | // Confirm serial shell behaves as expected. 5 | 6 | // UnixHostDuino emulation needs this include 7 | // (it's not picked up "for free" by Arduino IDE) 8 | // 9 | #include 10 | 11 | // fake it for UnixHostDuino emulation 12 | #if defined(UNIX_HOST_DUINO) 13 | # ifndef ARDUINO 14 | # define ARDUINO 100 15 | # endif 16 | #endif 17 | 18 | // 19 | // also, you need to provide your own forward references 20 | 21 | // These tests depend on the Arduino "AUnit" library 22 | #include 23 | #include 24 | //#include 25 | #include "shellTestHelpers.h" 26 | 27 | using namespace aunit; 28 | 29 | SimMonitor terminal; 30 | 31 | #define END_LINE "\r\n" 32 | #define COMMAND_PROMPT END_LINE "> " 33 | 34 | void prepForTests(void) 35 | { 36 | terminal.init(); 37 | shell.resetBuffer(); 38 | } 39 | 40 | ////////////////////////////////////////////////////////////////////////////// 41 | // test fixture to ensure clean initial and final conditions 42 | // 43 | class ShellTest: public TestOnce { 44 | protected: 45 | void setup() override { 46 | TestOnce::setup(); 47 | prepForTests(); 48 | } 49 | 50 | void teardown() override { 51 | prepForTests(); 52 | TestOnce::teardown(); 53 | } 54 | }; 55 | 56 | ////////////////////////////////////////////////////////////////////////////// 57 | // shell.execute( string ) works? 58 | testF(ShellTest, execute) { 59 | 60 | int response = shell.execute("echo hello world"); 61 | assertEqual(response, 0); 62 | int errNo = shell.lastErrNo(); 63 | assertEqual(errNo, 0); // OK or no errors 64 | 65 | assertEqual(terminal.getline(), "hello world" END_LINE); // no prompt 66 | 67 | response = shell.execute("sum 1 2 3"); 68 | assertEqual(response, 6); 69 | errNo = shell.lastErrNo(); 70 | assertEqual(errNo, 6); // sum(...) returned 6 71 | }; 72 | 73 | ////////////////////////////////////////////////////////////////////////////// 74 | // shell.execute( string ) fails for missing command? 75 | testF(ShellTest, missingCommand) { 76 | 77 | int response = shell.execute("echoNOT hello world"); 78 | assertEqual(response, -1); 79 | int errNo = shell.lastErrNo(); 80 | assertEqual(errNo, -1); // OK or no errors 81 | 82 | assertEqual(terminal.getline(), "\"echoNOT\": -1: command not found" END_LINE); 83 | }; 84 | 85 | ////////////////////////////////////////////////////////////////////////////// 86 | // shell.executeIfInput() from stream works? 87 | testF(ShellTest, executeIfInput) { 88 | 89 | const char* echoCmd = "echo hello world"; 90 | bool response = false; 91 | int errNo = 0; 92 | 93 | for (int i = 0; echoCmd[i] != '\0'; i++) { 94 | auto aKey = echoCmd[i]; 95 | terminal.pressKey(echoCmd[i]); 96 | response = shell.executeIfInput(); 97 | assertFalse(response); 98 | 99 | char echoed = (char) terminal.getOutput(); // typed keys echoed back? 100 | assertEqual(echoed, aKey); 101 | } 102 | 103 | terminal.pressKey('\r'); 104 | response = shell.executeIfInput(); 105 | assertTrue(response); 106 | errNo = shell.lastErrNo(); 107 | assertEqual(errNo, 0); // OK or no errors 108 | 109 | assertEqual(terminal.getline(), END_LINE "hello world" COMMAND_PROMPT); 110 | }; 111 | 112 | ////////////////////////////////////////////////////////////////////////////// 113 | // erase input line? 114 | testF(ShellTest, cancelLine) { 115 | 116 | const char* badCmd = "BADCOMMAND BADCOMMAND"; 117 | bool response = false; 118 | int errNo = 0; 119 | 120 | terminal.pressKeys(badCmd); 121 | response = shell.executeIfInput(); 122 | assertFalse(response); 123 | String aLine = terminal.getline(); // flush output 124 | 125 | terminal.pressKey(0x15); // CTRL('U') 126 | response = shell.executeIfInput(); 127 | aLine = terminal.getline(); 128 | assertEqual(aLine, "XXX" END_LINE); 129 | 130 | const char* echoCmd = "echo aWord"; 131 | terminal.pressKeys(echoCmd); 132 | response = shell.executeIfInput(); 133 | aLine = terminal.getline(); // flush output 134 | terminal.pressKey('\r'); 135 | response = shell.executeIfInput(); 136 | 137 | assertTrue(response); 138 | errNo = shell.lastErrNo(); 139 | assertEqual(errNo, 0); // OK or no errors 140 | 141 | assertEqual(terminal.getline(), END_LINE "aWord" COMMAND_PROMPT); 142 | }; 143 | 144 | ////////////////////////////////////////////////////////////////////////////// 145 | // retype input line 146 | testF(ShellTest, retypeLine) { 147 | 148 | const char* echoCmd = "echo howdy doodie"; 149 | bool response = false; 150 | int errNo = 0; 151 | 152 | terminal.pressKeys(echoCmd); 153 | response = shell.executeIfInput(); 154 | assertFalse(response); 155 | String aLine = terminal.getline(); // flush output 156 | 157 | terminal.pressKey(0x12); // CTRL('R') retype line 158 | response = shell.executeIfInput(); 159 | aLine = terminal.getline(); 160 | assertEqual(aLine, END_LINE "echo howdy doodie"); 161 | 162 | terminal.pressKey('\r'); 163 | response = shell.executeIfInput(); 164 | 165 | assertTrue(response); 166 | errNo = shell.lastErrNo(); 167 | assertEqual(errNo, 0); // OK or no errors 168 | 169 | assertEqual(terminal.getline(), END_LINE "howdy doodie" COMMAND_PROMPT); 170 | }; 171 | 172 | ////////////////////////////////////////////////////////////////////////////// 173 | // shell backspace from stream works? 174 | testF(ShellTest, backspace) { 175 | 176 | char eraseChar = '\b'; 177 | const char* echoCmd = "ech3"; 178 | bool response = false; 179 | int errNo = 0; 180 | 181 | for (int i = 0; echoCmd[i] != '\0'; i++) { 182 | char aKey = echoCmd[i]; 183 | terminal.pressKey(aKey); 184 | response = shell.executeIfInput(); 185 | assertFalse(response); 186 | 187 | char echoed = (char) terminal.getOutput(); // typed keys echoed back? 188 | assertEqual(echoed, aKey); 189 | } 190 | 191 | terminal.pressKey(eraseChar); 192 | response = shell.executeIfInput(); 193 | char echoed = (char) terminal.getOutput(); 194 | assertEqual((int) echoed, (int) '\b'); // back up 195 | echoed = (char) terminal.getOutput(); 196 | assertEqual( echoed, ' '); // erase 'typo' char 197 | echoed = (char) terminal.getOutput(); 198 | assertEqual((int) echoed, (int) '\b'); // back up (cursor on whitespace) 199 | 200 | const char* echoCmdFinish = "o hello world"; 201 | for (int i = 0; echoCmdFinish[i] != '\0'; i++) { 202 | auto aKey = echoCmdFinish[i]; 203 | terminal.pressKey(aKey); 204 | response = shell.executeIfInput(); 205 | assertFalse(response); 206 | 207 | char echoed = (char) terminal.getOutput(); // typed keys echoed back? 208 | assertEqual(echoed, aKey); 209 | } 210 | 211 | terminal.pressKey('\r'); 212 | response = shell.executeIfInput(); 213 | assertTrue(response); 214 | errNo = shell.lastErrNo(); 215 | assertEqual(errNo, 0); // OK or no errors 216 | 217 | assertEqual(terminal.getline(), END_LINE "hello world" COMMAND_PROMPT); 218 | } 219 | 220 | ////////////////////////////////////////////////////////////////////////////// 221 | // shell DEL key from stream works? 222 | testF(ShellTest, delete) { 223 | 224 | char eraseChar = (char) 127; // DEL key 225 | const char* echoCmd = "ech3"; 226 | bool response = false; 227 | int errNo = 0; 228 | 229 | terminal.pressKeys(echoCmd); 230 | response = shell.executeIfInput(); 231 | assertFalse(response); 232 | auto echoed = terminal.getline(); // typed keys echoed back? 233 | assertEqual(echoCmd, echoed); 234 | 235 | terminal.pressKey(eraseChar); 236 | response = shell.executeIfInput(); 237 | echoed = terminal.getline(); 238 | assertEqual("\b \b", echoed); // back up, erase typo, back up 239 | 240 | const char* echoCmdFinish = "o hello world"; 241 | 242 | terminal.pressKeys(echoCmdFinish); 243 | response = shell.executeIfInput(); 244 | assertEqual(terminal.getline(), echoCmdFinish); 245 | 246 | terminal.pressKey('\r'); 247 | response = shell.executeIfInput(); 248 | assertTrue(response); 249 | errNo = shell.lastErrNo(); 250 | assertEqual(errNo, 0); // OK or no errors 251 | 252 | assertEqual(terminal.getline(), END_LINE "hello world" COMMAND_PROMPT); 253 | } 254 | 255 | ////////////////////////////////////////////////////////////////////////////// 256 | // ... so which sketch is this? 257 | int showID(int /*argc*/ = 0, char ** /*argv*/ = NULL) 258 | { 259 | Serial.println(); 260 | Serial.println(F( "Running " __FILE__ ", Built " __DATE__)); 261 | return 0; 262 | }; 263 | 264 | ////////////////////////////////////////////////////////////////////////////// 265 | void setup() { 266 | ::delay(1000); // wait for stability on some boards to prevent garbage Serial 267 | Serial.begin(115200); // ESP8266 default of 74880 not supported on Linux 268 | while (!Serial); // for the Arduino Leonardo/Micro only 269 | showID(); 270 | shell.attach(terminal); 271 | shell.addCommand(F("id?"), showID); 272 | 273 | addTestCommands(); 274 | } 275 | 276 | ////////////////////////////////////////////////////////////////////////////// 277 | void loop() { 278 | // Should get: 279 | // TestRunner summary: 280 | // passed, failed, skipped, timed out, out of test(s). 281 | aunit::TestRunner::run(); 282 | } 283 | -------------------------------------------------------------------------------- /formatter.conf: -------------------------------------------------------------------------------- 1 | # This configuration file contains a selection of the available options provided by the formatting tool "Artistic Style" 2 | # http://astyle.sourceforge.net/astyle.html 3 | # 4 | # If you wish to change them, don't edit this file. 5 | # Instead, copy it in the same folder of file "preferences.txt" and modify the copy. This way, you won't lose your custom formatter settings when upgrading the IDE 6 | # If you don't know where file preferences.txt is stored, open the IDE, File -> Preferences and you'll find a link 7 | # 8 | # STANDALONE USAGE: 9 | # astyle --options=formatter.conf 10 | 11 | mode=c 12 | 13 | # 2 spaces indentation 14 | #indent=spaces=2 15 | indent=spaces=4 16 | 17 | # also indent macros 18 | indent-preprocessor 19 | 20 | # indent classes, switches (and cases), comments starting at column 1 21 | indent-classes 22 | indent-switches 23 | indent-cases 24 | indent-col1-comments 25 | 26 | # put a space around operators 27 | pad-oper 28 | 29 | # put a space after if/for/while 30 | pad-header 31 | 32 | # if you like one-liners, keep them 33 | keep-one-line-statements 34 | 35 | #remove-comment-prefix 36 | 37 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For SimpleSerialShell 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | CommandFunction KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | addCommand KEYWORD2 16 | attachToStream KEYWORD2 17 | executeIfInput KEYWORD2 18 | execute KEYWORD2 19 | lastErrNo KEYWORD2 20 | printHelp KEYWORD2 21 | resetBuffer KEYWORD2 22 | 23 | ####################################### 24 | # Instances (KEYWORD2) 25 | ####################################### 26 | shell KEYWORD2 27 | 28 | ####################################### 29 | # Constants (LITERAL1) 30 | ####################################### 31 | 32 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SimpleSerialShell 2 | version=1.0.0 3 | 4 | author=Phil Jansen 5 | maintainer=Phil Jansen 6 | sentence=Simple serial text shell for sending commands to Arduino board. 7 | paragraph=Text Commands are parsed from an attached Stream (for example 'Serial' connected to the IDE's Serial Monitor). Shell commands have the "int hello(int argc, char **argv)" function signature, so you can pass in arguments. 8 | category=Communication 9 | url=https://github.com/philj404/SimpleSerialShell 10 | architectures=* 11 | includes=SimpleSerialShell.h 12 | -------------------------------------------------------------------------------- /releaseChecklist.md: -------------------------------------------------------------------------------- 1 | Release checklist: 2 | 3 | ## "test" phase 4 | Confirm code changes all work together. 5 | 6 | - [ ] GitHub's automated build succeeds on all platforms (even platforms you don't have). 7 | - This library is intended to work with many microcontrollers. Your change should work for all of them. 8 | - If not, document what variants should expect. 9 | 10 | - [ ] Unit tests pass. 11 | - [ ] Have you added tests to confirm new features continue to work (so nobody accidentally breaks your feature)? 12 | 13 | - [ ] Usage notes upated for new behaviors 14 | - [ ] Does **README.md** need to describe new behavior? 15 | - [ ] Any other documents need an upate? 16 | 17 | ## "next" phase 18 | Prepare for release 19 | 20 | - [ ] Updated version number in **library.properties** file 21 | - Is this a major release? 22 | 23 | - [ ] Updated **releaseNotes.md** 24 | - What is the reason for this release? 25 | - Is it obvious whether a developer needs this update, or can safely ignore it? 26 | 27 | ## "release" phase 28 | Make the release visible to others 29 | 30 | - [ ] Code merged/squashed to **main** 31 | - Clones of this library check out **main** by default. 32 | 33 | - [ ] Commit tagged with the new version number 34 | - Arduino libraries only pick up tagged releases with a matching **library.properties** version, AND in the commit history for the **main** branch. 35 | 36 | - [ ] Confirm the new release is visible 37 | - (It may take a day or so for the library managers to discover the new release) 38 | - [ ] It should be visible in the Arduino IDE. 39 | - Eventually it should be visible in the PlatformIO IDE. (hopefully this update is automatic) 40 | -------------------------------------------------------------------------------- /releaseNotes.md: -------------------------------------------------------------------------------- 1 | Release notes 2 | 3 | ### v1.0.0 4 | * save another ~8 bytes RAM for ATmega processors 5 | * declare a stable 1.0 release 6 | 7 | ### v0.9.2 8 | * #28 bug - use full command name (no partial matches) 9 | * added example memory dump commands 10 | 11 | ### v0.9.1 12 | * Ensure shell is a Singleton 13 | 14 | ### v0.9.0 15 | * Support argument hints when adding commands (for "help" command) (brucemack) 16 | * Add "> " command prompt to show shell is alive (MAY BREAK TESTS of text output) 17 | * Unit tests build/run/PASS with Windows/Arduino IDE + Arduino Nano 18 | 19 | ### v0.2.3 20 | * Support custom tokenizers, for example to quote text with spaces. (brucemack) 21 | * Clean up miscellaneous warnings 22 | 23 | ### v0.2.2 24 | examples/ArduinoTextInterface: 25 | * Provide memory dump commands for RAM, ROM and EEPROM address spaces. Very useful for finding/removing constant strings in RAM. 26 | * Provide memory usage info for RAM consumption (AVR architecture). Shows global, heap, stack and free memory measurements. 27 | 28 | ### v0.2.1 29 | (for PlatformIO builds) Support changing the default buffer size with a #define. (eg321) 30 | 31 | ### v0.2.0 32 | Run unit tests and confirm they PASS on github code push. 33 | More constistent code formatting (with astyle) 34 | 35 | ### v0.1.1 36 | Confirm library can build for various platforms on github code push. 37 | (suggested by KiraVerSace) 38 | 39 | ### v0.1.0 40 | Initial release 41 | 42 | -------------------------------------------------------------------------------- /src/SimpleSerialShell.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | //////////////////////////////////////////////////////////////////////////////// 5 | /*! 6 | * @file SimpleSerialShell.cpp 7 | * 8 | * Implementation for the shell. 9 | * 10 | */ 11 | 12 | // The static instance of the singleton 13 | SimpleSerialShell SimpleSerialShell::theShell; 14 | 15 | // A reference to the singleton shell in the global namespace. There is an 16 | // extern definition of this in SimpleSherialShell.h, so all users of the 17 | // class will have visibilty to this reference. 18 | SimpleSerialShell& shell = SimpleSerialShell::theShell; 19 | 20 | // 21 | SimpleSerialShell::Command * SimpleSerialShell::firstCommand = NULL; 22 | 23 | //////////////////////////////////////////////////////////////////////////////// 24 | /*! 25 | * @brief associates a named command with the function to call. 26 | */ 27 | class SimpleSerialShell::Command { 28 | public: 29 | Command(const __FlashStringHelper * n, CommandFunction f): 30 | nameAndDocs(n), myFunc(f) {}; 31 | 32 | int execute(int argc, char **argv) 33 | { 34 | return myFunc(argc, argv); 35 | }; 36 | 37 | // Comparison used for sort commands 38 | int compare(const Command * other) const 39 | { 40 | const String otherNameString(other->nameAndDocs); 41 | return compareName(otherNameString.c_str()); 42 | }; 43 | 44 | int compareName(const char * aName) const 45 | { 46 | // Look for the command delimiter and make sure we don't 47 | // consider anything beyond it in the comparison. There 48 | // may be more documentation in the string. 49 | // 50 | // Note for future consideration: The temporary String here could 51 | // be eliminated here by leveraging strlen_P, pgm_read_byte, and 52 | // strncasecmp_P. That will take a bit of research since 53 | // the header file may have a different name on ESP2886/ESP32. 54 | String work(nameAndDocs); 55 | int delim = work.indexOf(' '); 56 | if (delim >= 0) { 57 | work.remove(delim); 58 | } 59 | return strncasecmp(work.c_str(), aName, SIMPLE_SERIAL_SHELL_BUFSIZE); 60 | }; 61 | 62 | /** 63 | * @brief Writes the documentation associated with this command. 64 | * 65 | * @param str Stream to write into. 66 | */ 67 | void renderDocumentation(Stream& str) const 68 | { 69 | str.print(F(" ")); 70 | str.print(nameAndDocs); 71 | str.println(); 72 | } 73 | 74 | Command * next; 75 | 76 | private: 77 | 78 | const __FlashStringHelper * const nameAndDocs; 79 | const CommandFunction myFunc; 80 | }; 81 | 82 | //////////////////////////////////////////////////////////////////////////////// 83 | SimpleSerialShell::SimpleSerialShell() 84 | : shellConnection(NULL), 85 | m_lastErrNo(EXIT_SUCCESS), 86 | tokenizer(strtok_r) 87 | { 88 | resetBuffer(); 89 | 90 | // simple help. 91 | addCommand(F("help"), SimpleSerialShell::printHelp); 92 | }; 93 | 94 | ////////////////////////////////////////////////////////////////////////////// 95 | void SimpleSerialShell::addCommand( 96 | const __FlashStringHelper * name, CommandFunction f) 97 | { 98 | auto * newCmd = new Command(name, f); 99 | 100 | // insert in list alphabetically 101 | // from stackoverflow... 102 | 103 | Command* temp2 = firstCommand; 104 | Command** temp3 = &firstCommand; 105 | while (temp2 != NULL && (newCmd->compare(temp2) > 0) ) 106 | { 107 | temp3 = &temp2->next; 108 | temp2 = temp2->next; 109 | } 110 | *temp3 = newCmd; 111 | newCmd->next = temp2; 112 | } 113 | 114 | ////////////////////////////////////////////////////////////////////////////// 115 | bool SimpleSerialShell::executeIfInput(void) 116 | { 117 | bool bufferReady = prepInput(); 118 | bool didSomething = false; 119 | 120 | if (bufferReady) { 121 | didSomething = true; 122 | execute(); 123 | print(F("> ")); // provide command prompt feedback 124 | } 125 | 126 | return didSomething; 127 | } 128 | 129 | ////////////////////////////////////////////////////////////////////////////// 130 | void SimpleSerialShell::attach(Stream & requester) 131 | { 132 | shellConnection = &requester; 133 | } 134 | 135 | ////////////////////////////////////////////////////////////////////////////// 136 | // Arduino serial monitor appears to 'cook' lines before sending them 137 | // to output, so some of this is overkill. 138 | // 139 | // But for serial terminals, backspace would be useful. 140 | // 141 | bool SimpleSerialShell::prepInput(void) 142 | { 143 | bool bufferReady = false; // assume not ready 144 | bool moreData = true; 145 | 146 | do { 147 | int c = read(); 148 | switch (c) 149 | { 150 | case -1: // No character present; don't do anything. 151 | moreData = false; 152 | break; 153 | case 0: // throw away NUL characters 154 | break; 155 | 156 | // Line editing characters 157 | case 127: // DEL delete key 158 | case '\b': // CTRL(H) backspace 159 | // Destructive backspace: remove last character 160 | if (inptr > 0) { 161 | print(F("\b \b")); // remove char in raw UI 162 | linebuffer[--inptr] = 0; 163 | } 164 | break; 165 | 166 | case 0x12: //CTRL('R') 167 | //Ctrl-R retypes the line 168 | print(F("\r\n")); 169 | print(linebuffer); 170 | break; 171 | 172 | case 0x15: //CTRL('U') 173 | //Ctrl-U deletes the entire line and starts over. 174 | println(F("XXX")); 175 | resetBuffer(); 176 | break; 177 | 178 | case ';': // BLE monitor apps don't let you add '\r' to a string, 179 | // so ';' ends a command 180 | 181 | case '\r': //CTRL('M') carriage return (or "Enter" key) 182 | // raw input only sends "return" for the keypress 183 | // line is complete 184 | println(); // Echo newline too. 185 | bufferReady = true; 186 | break; 187 | 188 | case '\n': //CTRL('J') linefeed 189 | // ignore newline as 'raw' terminals may not send it. 190 | // Serial Monitor sends a "\r\n" pair by default 191 | break; 192 | 193 | default: 194 | // Otherwise, echo the character and append it to the buffer 195 | linebuffer[inptr++] = c; 196 | write(c); 197 | if (inptr >= SIMPLE_SERIAL_SHELL_BUFSIZE-1) { 198 | bufferReady = true; // flush to avoid overflow 199 | } 200 | break; 201 | } 202 | } while (moreData && !bufferReady); 203 | 204 | return bufferReady; 205 | } 206 | 207 | ////////////////////////////////////////////////////////////////////////////// 208 | int SimpleSerialShell::execute(const char commandString[]) 209 | { 210 | // overwrites anything in linebuffer; hope you don't need it! 211 | strncpy(linebuffer, commandString, SIMPLE_SERIAL_SHELL_BUFSIZE); 212 | return execute(); 213 | } 214 | 215 | ////////////////////////////////////////////////////////////////////////////// 216 | int SimpleSerialShell::execute(void) 217 | { 218 | char * argv[MAXARGS] = {0}; 219 | linebuffer[SIMPLE_SERIAL_SHELL_BUFSIZE - 1] = '\0'; // play it safe 220 | int argc = 0; 221 | 222 | char * rest = NULL; 223 | const char * whitespace = " \t\r\n"; // not PROGMEM/simple TokenizerFunction 224 | char * commandName = tokenizer(linebuffer, whitespace, &rest); 225 | 226 | if (!commandName) 227 | { 228 | // empty line; no arguments found. 229 | println(F("OK")); 230 | resetBuffer(); 231 | return EXIT_SUCCESS; 232 | } 233 | argv[argc++] = commandName; 234 | 235 | for ( ; argc < MAXARGS; ) 236 | { 237 | char * anArg = tokenizer(0, whitespace, &rest); 238 | if (anArg) { 239 | argv[argc++] = anArg; 240 | } else { 241 | // no more arguments 242 | return execute(argc, argv); 243 | } 244 | } 245 | 246 | return report(F("Too many arguments to parse"), -1); 247 | } 248 | 249 | ////////////////////////////////////////////////////////////////////////////// 250 | int SimpleSerialShell::execute(int argc, char **argv) 251 | { 252 | m_lastErrNo = 0; 253 | for ( Command * aCmd = firstCommand; aCmd != NULL; aCmd = aCmd->next) { 254 | if (aCmd->compareName(argv[0]) == 0) { 255 | m_lastErrNo = aCmd->execute(argc, argv); 256 | resetBuffer(); 257 | return m_lastErrNo; 258 | } 259 | } 260 | print(F("\"")); 261 | print(argv[0]); 262 | print(F("\": ")); 263 | 264 | return report(F("command not found"), -1); 265 | } 266 | 267 | ////////////////////////////////////////////////////////////////////////////// 268 | int SimpleSerialShell::lastErrNo(void) 269 | { 270 | return m_lastErrNo; 271 | } 272 | 273 | ////////////////////////////////////////////////////////////////////////////// 274 | int SimpleSerialShell::report(const __FlashStringHelper * constMsg, int errorCode) 275 | { 276 | if (errorCode != EXIT_SUCCESS) 277 | { 278 | String message(constMsg); 279 | print(errorCode); 280 | if (message[0] != '\0') { 281 | print(F(": ")); 282 | println(message); 283 | } 284 | } 285 | resetBuffer(); 286 | m_lastErrNo = errorCode; 287 | return errorCode; 288 | } 289 | ////////////////////////////////////////////////////////////////////////////// 290 | void SimpleSerialShell::resetBuffer(void) 291 | { 292 | memset(linebuffer, 0, sizeof(linebuffer)); 293 | inptr = 0; 294 | } 295 | 296 | ////////////////////////////////////////////////////////////////////////////// 297 | // SimpleSerialShell::printHelp() is a static method. 298 | // printHelp() can access the linked list of commands. 299 | // 300 | int SimpleSerialShell::printHelp(int /*argc*/, char ** /*argv*/) 301 | { 302 | shell.println(F("Commands available are:")); 303 | auto aCmd = firstCommand; // first in list of commands. 304 | while (aCmd) 305 | { 306 | aCmd->renderDocumentation(shell); 307 | aCmd = aCmd->next; 308 | } 309 | return 0; // OK or "no errors" 310 | } 311 | 312 | /////////////////////////////////////////////////////////////// 313 | // i/o stream indirection/delegation 314 | // 315 | size_t SimpleSerialShell::write(uint8_t aByte) 316 | { 317 | return shellConnection ? 318 | shellConnection->write(aByte) 319 | : 0; 320 | } 321 | 322 | int SimpleSerialShell::available() 323 | { 324 | return shellConnection ? shellConnection->available() : 0; 325 | } 326 | 327 | int SimpleSerialShell::read() 328 | { 329 | return shellConnection ? shellConnection->read() : 0; 330 | } 331 | 332 | int SimpleSerialShell::peek() 333 | { 334 | return shellConnection ? shellConnection->peek() : 0; 335 | } 336 | 337 | void SimpleSerialShell::flush() 338 | { 339 | if (shellConnection) 340 | shellConnection->flush(); 341 | } 342 | 343 | void SimpleSerialShell::setTokenizer(TokenizerFunction f) 344 | { 345 | tokenizer = f; 346 | } 347 | -------------------------------------------------------------------------------- /src/SimpleSerialShell.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SIMPLE_SERIAL_SHELL_H 3 | #define SIMPLE_SERIAL_SHELL_H 4 | 5 | #ifndef SIMPLE_SERIAL_SHELL_BUFSIZE 6 | #define SIMPLE_SERIAL_SHELL_BUFSIZE 88 7 | #endif 8 | 9 | //////////////////////////////////////////////////////////////////////////////// 10 | /*! 11 | * @file SimpleSerialShell.h 12 | * 13 | * @section dependencies Dependencies 14 | * 15 | * Depends on Stream. The shell is an instance of Stream so anthing that 16 | * works with a Stream should also work with the shell. 17 | * 18 | * @section author Phil Jansen 19 | */ 20 | class SimpleSerialShell : public Stream { 21 | public: 22 | 23 | // The singleton instance of the shell 24 | static SimpleSerialShell theShell; 25 | 26 | // Unix-style (from 1970!) 27 | // functions must have a signature like: "int hello(int argc, char ** argv)" 28 | typedef int (*CommandFunction)(int, char ** ); 29 | 30 | /** 31 | * @brief Registers a command with the shell processor. 32 | * 33 | * @param name Command name, with optional documentation. The 34 | * command must be delimited from the rest of the documentation 35 | * using a space, which implies that the command itself cannot 36 | * contain space characters. Anything after the initial space 37 | * delimiter will be treated as documentation for display in 38 | * the help message. 39 | * @param f The command function that will be called when the command 40 | * is entered into the shell. 41 | */ 42 | void addCommand(const __FlashStringHelper * name, CommandFunction f); 43 | 44 | void attach(Stream & shellSource); 45 | 46 | // check for a complete command and run it if available 47 | // non blocking 48 | bool executeIfInput(void); // returns true when command attempted 49 | int lastErrNo(void); 50 | 51 | int execute( const char aCommandString[]); // shell.execute("echo hello world"); 52 | 53 | static int printHelp(int argc, char **argv); 54 | 55 | void resetBuffer(void); 56 | 57 | // this shell delegates communication to/from the attached stream 58 | // (which sent the command) 59 | // Note changing streams may intermix serial data 60 | // 61 | virtual size_t write(uint8_t); 62 | virtual int available(); 63 | virtual int read(); 64 | virtual int peek(); 65 | virtual void flush(); // esp32 needs an implementation 66 | 67 | // The function signature of the tokening function. This is based 68 | // on the parameters of the strtok_r(3) function. 69 | // 70 | // Please keep these things in mind when creating your own tokenizer: 71 | // * The str parameter is called the first time with the string to be 72 | // tokenized, and then 0 is passed on subsequent calls. 73 | // * The list of allowable delimiters can be changed on subsequent calls. 74 | // * The str parameter is modified! 75 | // * saveptr should be treated as an opaque pointer. Its meaning 76 | // is implementation-specific. 77 | // 78 | typedef char* (*TokenizerFunction)(char* str, const char* delim, char** saveptr); 79 | 80 | // Call this to change the tokenizer function used internally. By 81 | // default strtok_r() is used, so the use of this funcition is 82 | // optional. 83 | void setTokenizer(TokenizerFunction f); 84 | 85 | private: 86 | 87 | SimpleSerialShell(void); 88 | 89 | Stream * shellConnection; 90 | int m_lastErrNo; 91 | int execute(void); 92 | int execute(int argc, char** argv); 93 | 94 | bool prepInput(void); 95 | 96 | int report(const __FlashStringHelper * message, int errorCode); 97 | static const char MAXARGS = 10; 98 | char linebuffer[SIMPLE_SERIAL_SHELL_BUFSIZE]; 99 | int inptr; 100 | 101 | class Command; 102 | static Command * firstCommand; 103 | 104 | TokenizerFunction tokenizer; 105 | }; 106 | 107 | //////////////////////////////////////////////////////////////////////////////// 108 | extern SimpleSerialShell& shell; 109 | 110 | //example commands which would be easy to add to the shell: 111 | //extern int helloWorld(int argc, char **argv); 112 | //extern int echo(int argc, char **argv); 113 | 114 | #endif /* SIMPLE_SERIAL_SHELL_H */ 115 | --------------------------------------------------------------------------------