├── .gitignore ├── .gitattributes ├── extras ├── Assets │ ├── DocuImages │ │ ├── banner.png │ │ ├── banner.xcf │ │ ├── support.png │ │ ├── support.xcf │ │ └── support_me_on_kofi_red.png │ ├── DocuPages │ │ ├── examples_page.md │ │ ├── bug_report_page.md │ │ ├── faq_page.md │ │ ├── developer_zone.md │ │ ├── installation_page.md │ │ └── introduction_page.md │ ├── custom_header.css │ ├── header.html │ ├── DoxygenLayout.xml │ └── logo.svg └── trie_generator │ ├── trie_generator.html │ ├── styles.css │ ├── script.js │ └── trie.js ├── .gitmodules ├── library.properties ├── .vscode └── settings.json ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── documentation_problem.md │ └── bug_report.md └── workflows │ └── compile-examples.yml ├── LICENSE ├── src ├── Commander-Caller-Interface.cpp ├── Commander-DefaultSettings.hpp ├── Commander-Caller-Interface.hpp ├── Commander-IO.cpp ├── Commander-IO.hpp ├── Commander-Autocomplete.hpp ├── Commander-Autocomplete.cpp ├── Commander-Arguments.hpp ├── Commander-API.hpp ├── Commander-Arguments.cpp └── Commander-Database.hpp ├── CONTRIBUTING.md ├── examples └── 001_commander_basic │ └── 001_commander_basic.ino ├── keywords.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | docs/* -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /extras/Assets/DocuImages/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani007200964/Commander-API/HEAD/extras/Assets/DocuImages/banner.png -------------------------------------------------------------------------------- /extras/Assets/DocuImages/banner.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani007200964/Commander-API/HEAD/extras/Assets/DocuImages/banner.xcf -------------------------------------------------------------------------------- /extras/Assets/DocuImages/support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani007200964/Commander-API/HEAD/extras/Assets/DocuImages/support.png -------------------------------------------------------------------------------- /extras/Assets/DocuImages/support.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani007200964/Commander-API/HEAD/extras/Assets/DocuImages/support.xcf -------------------------------------------------------------------------------- /extras/Assets/DocuImages/support_me_on_kofi_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani007200964/Commander-API/HEAD/extras/Assets/DocuImages/support_me_on_kofi_red.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/Style/doxygen-awesome-css"] 2 | path = docs/Style/doxygen-awesome-css 3 | url = https://github.com/jothepro/doxygen-awesome-css.git 4 | [submodule "extras/doxygen-awesome-css"] 5 | path = extras/doxygen-awesome-css 6 | url = https://github.com/jothepro/doxygen-awesome-css.git 7 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Commander-API 2 | version=3.0.0 3 | author=Daniel Hajnal 4 | maintainer=Daniel Hajnal 5 | sentence=This library can be used to create a simple command parser. 6 | paragraph=It simply parses text based commands and find the corresponding functions for them. 7 | category=Data Processing 8 | url=https://github.com/dani007200964/Commander-API 9 | architectures=* 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.example": "cpp", 4 | "type_traits": "cpp", 5 | "xtr1common": "cpp", 6 | "compare": "cpp", 7 | "utility": "cpp", 8 | "xutility": "cpp", 9 | "cmath": "cpp", 10 | "initializer_list": "cpp", 11 | "xstring": "cpp", 12 | "xlocale": "cpp", 13 | "ios": "cpp", 14 | "xmemory": "cpp", 15 | "system_error": "cpp", 16 | "typeinfo": "cpp" 17 | }, 18 | "C_Cpp.errorSquiggles": "disabled" 19 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for creating a pull request to contribute to Commander! 2 | Before you open the request please review the following guidelines 3 | to help it be more easily integrated: 4 | 5 | * Describe the scope of your change. What the change does and what parts of the code were modified. 6 | * Describe any known limitations with your change. 7 | * Please run any tests or examples that can exercise your modified code. 8 | 9 | Thank you again for contributing! I will try to test and integrate the change as soon as I can. 10 | 11 | __After reviewing the guidelines above you can delete this text from the pull request.__ 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature request 3 | about: Propose a new feature 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_problem.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📜 Documentation problem 3 | about: Report a problem in the documentation 4 | title: '' 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the problem** 11 | A clear and concise description of what the problem is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. The problem is on page '...' 16 | 2. The problem is on section '...' 17 | 3. In the documentation there is '...' instead of '...' 18 | 4. '...' is missing from '...' 19 | 5. I don't understand this step or section: '...' 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /extras/Assets/DocuPages/examples_page.md: -------------------------------------------------------------------------------- 1 | @page examples_page Examples 2 | 3 | ## Commander-API & Shellminator: A Perfect Duo for Embedded Terminals 4 | 5 | Commander-API has been developed alongside Shellminator for many years. Together, these two software packages allow you to create an interactive terminal on low-power processors. While both projects work independently, documenting the same features twice would have been redundant. That’s why the documentation for Commander-API can be found within the Shellminator documentation. 6 | 7 |
8 | 9 | | Previous | Next | 10 | |:------------------|-----------------------------:| 11 | |[Install](@ref installation_page) | [FAQ](@ref faq_page) | 12 | 13 |
-------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Report a bug in the software 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. The used board is: '...' 16 | 2. The used microcontroller is: '....' 17 | 3. The used environment and it's version is: '....' 18 | 4. The used OS on your computer is: '....' 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /extras/Assets/custom_header.css: -------------------------------------------------------------------------------- 1 | @keyframes pulse-bg { 2 | 0% { 3 | background-color: #007bff; 4 | box-shadow: 0 0 10px rgba(0, 123, 255, 0.4); 5 | } 6 | 50% { 7 | background-color: #3399ff; 8 | box-shadow: 0 0 25px rgba(0, 123, 255, 0.6); 9 | } 10 | 100% { 11 | background-color: #007bff; 12 | box-shadow: 0 0 10px rgba(0, 123, 255, 0.4); 13 | } 14 | } 15 | 16 | .terminalPulse-btn { 17 | font-size: 18px; 18 | font-weight: bold; 19 | text-align: center; 20 | padding: 20px 40px; 21 | display: block; 22 | border-radius: 8px; 23 | animation: pulse-bg 5s infinite alternate ease-in-out; 24 | text-decoration: none; 25 | color: white; 26 | width: 100%- 50px; 27 | border: 3px solid transparent; 28 | background-clip: padding-box; 29 | } 30 | 31 | .kofi_image { 32 | padding: 20px 40px; 33 | display: block; 34 | width: 200px; 35 | } 36 | 37 | .kofi_button { 38 | display: block; 39 | padding: 20px 40px; 40 | width: 100%- 50px; 41 | text-align: center; 42 | } -------------------------------------------------------------------------------- /extras/Assets/DocuPages/bug_report_page.md: -------------------------------------------------------------------------------- 1 | @page bug_report_page Bug Report 2 | 3 | Bugs can happen in any product, and software is no exception. If you come across an issue, it's best to report it as soon as possible so we can fix it. The first step is to create a new issue on 4 | [GitHub](https://github.com/dani007200964/Commander-API/issues/new?template=bug_report.md). 5 | 6 | When filling out the issue description, try to be as detailed as possible. The more relevant information you provide, the faster and more efficiently we can investigate and resolve the problem. You don’t need to share your entire project’s source code—just narrow it down to the part that’s causing the issue. If you can include a minimal code snippet that reproduces the problem, that would be incredibly helpful! 7 | 8 | Also, don’t forget to mention your setup: which environment you're working in, what microcontroller you’re using, and which compiler you’re working with. Try to document the issue in a way that would make it easy for someone else (or even yourself, later) to reproduce it based on your description. 9 | 10 | **Thanks for helping us improve!** -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel Hajnal 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. -------------------------------------------------------------------------------- /extras/trie_generator/trie_generator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Trie Generator 7 | 8 | 9 | 10 |
11 |

Trie Generator

12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Commander-Caller-Interface.cpp: -------------------------------------------------------------------------------- 1 | #include "Commander-Caller-Interface.hpp" 2 | 3 | CommandCaller::CommandCaller(){ 4 | 5 | } 6 | 7 | void CommandCaller::setChannel( Stream* channel_p ){ 8 | channel = channel_p; 9 | } 10 | 11 | Stream* CommandCaller::getChannel(){ 12 | return channel; 13 | } 14 | 15 | void CommandCaller::clearChannel(){ 16 | channel = NULL; 17 | } 18 | 19 | 20 | 21 | int CommandCaller::available(){ 22 | 23 | if( channel == NULL ){ 24 | return -1; 25 | } 26 | 27 | return channel -> available(); 28 | 29 | } 30 | 31 | int CommandCaller::read(){ 32 | 33 | if( channel == NULL ){ 34 | return -1; 35 | } 36 | 37 | return channel -> read(); 38 | 39 | } 40 | 41 | /// Flush the channel. 42 | void CommandCaller::flush(){ 43 | // Honestly I don't know what to do. 44 | // Arduino flush methods are weird. 45 | if( channel == NULL ){ 46 | return; 47 | } 48 | channel -> flush(); 49 | } 50 | 51 | int CommandCaller::peek(){ 52 | 53 | if( channel == NULL ){ 54 | return -1; 55 | } 56 | return channel -> peek(); 57 | 58 | } 59 | 60 | size_t CommandCaller::write( uint8_t data ){ 61 | if( channel == NULL ){ 62 | return -1; 63 | } 64 | return channel -> write( data ); 65 | } 66 | 67 | size_t CommandCaller::write( const uint8_t *data, size_t size ){ 68 | if( channel == NULL ){ 69 | return -1; 70 | } 71 | return channel -> write( data, size ); 72 | } 73 | -------------------------------------------------------------------------------- /extras/trie_generator/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | background-color: #2f2f2f; 4 | color: #ccc; 5 | margin: 0; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | height: 100vh; 10 | } 11 | 12 | .container { 13 | text-align: center; 14 | background-color: #3e3e3e; 15 | padding: 20px; 16 | border-radius: 10px; 17 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); 18 | width: 90%; 19 | max-width: 600px; 20 | } 21 | 22 | .output-container { 23 | text-align: left;; 24 | } 25 | 26 | h1 { 27 | margin-bottom: 20px; 28 | color: #fff; 29 | } 30 | 31 | .input-container { 32 | position: relative; 33 | } 34 | 35 | textarea { 36 | width: 100%; 37 | height: 150px; 38 | padding: 10px; 39 | margin-bottom: 20px; 40 | border: 1px solid #555; 41 | border-radius: 5px; 42 | resize: none; 43 | font-size: 16px; 44 | background-color: #555; 45 | color: #eee; 46 | } 47 | 48 | button { 49 | display: inline-block; 50 | padding: 10px 20px; 51 | font-size: 16px; 52 | color: #fff; 53 | background-color: #28a745; 54 | border: none; 55 | border-radius: 5px; 56 | cursor: pointer; 57 | } 58 | 59 | button#helpBtn { 60 | background-color: #ff5722; 61 | margin-top: 10px; 62 | } 63 | 64 | button#copyBtn { 65 | background-color: #007bff; 66 | margin-top: 10px; 67 | } 68 | 69 | button:hover { 70 | opacity: 0.9; 71 | } 72 | 73 | .output 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How Can I Contribute? 2 | 3 | ### Reporting Bugs 4 | 5 | Bugs are tracked as GitHub issues. If you find a problem, [create an issue](https://github.com/dani007200964/Commander-API/issues/new?assignees=&labels=bug&template=bug_report.md&title=) and provide the following information: 6 | * Use a clear and descriptive title for the issue to identify the problem. 7 | * Describe the exact steps which reproduce the problem in as many details as possible. 8 | * Provide specific examples to demonstrate the steps. 9 | * Provide a detailed description about the configuration which caused the problem. 10 | 11 | ### Suggesting Enhancements 12 | 13 | The best way to ask for help or propose a new idea is to [create a new issue](https://github.com/dani007200964/Commander-API/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=). 14 | Before creating enhancement suggestions, please check the enhancements related issues, as you might find out that you don't need to create one. 15 | 16 | * In the enhancement suggestion, please describe your idea as clear as possible. 17 | * Use a clear and descriptive title for the issue to identify the suggestion. 18 | * Provide a step-by-step description of the suggested enhancement in as many details as possible. 19 | * Describe the current behavior and explain which behavior you expected to see instead and why. 20 | 21 | ### Pull Requests 22 | 23 | 1. Check that your modifications works as expected. 24 | 2. If it's a new feature please provide an example with it. 25 | 3. Fill the template. 26 | -------------------------------------------------------------------------------- /extras/Assets/DocuPages/faq_page.md: -------------------------------------------------------------------------------- 1 | @page faq_page Frequently Asked Questions 2 | @tableofcontents 3 | 4 | It's always great when questions arise in connection with such a project. Based on the experiences of recent years, we've gathered a few questions that many have asked, and we've tried to provide short, concise answers for each. If you need more information beyond these, feel free to ask on the [GitHub Discussions](https://github.com/dani007200964/Commander-API/discussions) platform. 5 | 6 | 💡 *Can I use Commander-API without Shellminator to save resources?* 7 | Absolutely! The `basic.ino` example demonstrates this. However, keep in mind that without Shellminator, you lose features like interactive editing and command history. If you really need to save resources, you can go this route—but only if a reduced user experience isn’t a concern. 8 | 9 | 💡 *Does it work on other platforms without the Arduino environment?* 10 | Yes! We’ve been using it with STM32 for a long time without any issues. There’s even a guide in the Shellminator documentation on how to integrate it into an STM32 environment. 11 | 12 | 💡 *Why does dynamic memory run out so quickly on AVR controllers?* 13 | This is a complex issue. AVR controllers don’t have a unified memory bus, meaning they can’t directly access data from flash memory. As a result, even `const char*` strings end up in dynamic memory. There are two ways to handle this: keep command descriptions short or use AVR-specific memory handling techniques. 14 | 15 | 💡 *Does it only work with Serial?* 16 | Nope! You can use any channel derived from the `Stream` class. With Shellminator, you can even use TCP, WebSocket, or BLE for communication. -------------------------------------------------------------------------------- /examples/001_commander_basic/001_commander_basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on Aug 10 2020 3 | * 4 | * Copyright (c) 2023 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2025.02.22 8 | */ 9 | 10 | #include "Commander-API.hpp" 11 | 12 | #define COMMAND_SIZE 30 13 | 14 | // We have to create an object from Commander class. 15 | Commander commander; 16 | 17 | // Command callbacks 18 | bool cat_func( char *args, CommandCaller* caller ); 19 | bool dog_func( char *args, CommandCaller* caller ); 20 | 21 | // Command tree 22 | Commander::systemCommand_t API_tree[] = { 23 | systemCommand( "cat", "Description for cat command.", cat_func ), 24 | systemCommand( "dog", "Description for dog command.", dog_func ) 25 | }; 26 | 27 | // This buffer is used to store command from Serial port. 28 | char commandBuffer[ COMMAND_SIZE ]; 29 | 30 | // System init section. 31 | void setup(){ 32 | Serial.begin(115200); 33 | 34 | commander.attachTree( API_tree ); 35 | commander.init(); 36 | 37 | Serial.println(); 38 | Serial.println( "---- Init Finished ----" ); 39 | Serial.println(); 40 | 41 | Serial.println( "Type something" ); 42 | Serial.print( "$: " ); 43 | } 44 | 45 | // Infinite loop. 46 | void loop(){ 47 | commander.update( commandBuffer, COMMAND_SIZE, &Serial ); 48 | } 49 | 50 | /// This is an example function for the cat command 51 | bool cat_func(char *args, CommandCaller* caller ){ 52 | caller -> print( "Hello from cat function!\r\n" ); 53 | return true; 54 | } 55 | 56 | /// This is an example function for the dog command 57 | bool dog_func(char *args, CommandCaller* caller ){ 58 | caller -> print( "Hello from dog function!\r\n" ); 59 | return true; 60 | } -------------------------------------------------------------------------------- /extras/Assets/DocuPages/developer_zone.md: -------------------------------------------------------------------------------- 1 | @page developer_zone_page Developer Zone 2 | 3 | @tableofcontents 4 | 5 | This section is for those fearless terminal ninjas who want to contribute to improving this system. First of all, we want to say a huge **thank you** for taking the time to make this software better and better! 6 | 7 | To ensure that this shared adventure is a fun and productive challenge for everyone, we have a few simple guidelines: 8 | 9 | - **Be respectful and constructive** – Everyone starts somewhere, and we value all contributions, whether big or small. Keep discussions friendly and helpful. 10 | - **Clear and concise communication** – When reporting issues or submitting code, provide useful details that make it easy for others to understand and help. 11 | - **Follow the coding style** – Consistency is key. Try to adhere to existing conventions in the project to keep the codebase clean and maintainable. 12 | - **Write meaningful commit messages** – A good commit message helps others understand *why* a change was made, not just *what* changed. 13 | - **Be open to feedback** – Code reviews aren’t about criticism; they’re about making the project stronger. Let’s help each other improve! 14 | - **Keep it fun!** – At the end of the day, we’re all here to learn, build cool things, and collaborate. Let’s make this a positive space for everyone. 15 | 16 | ### Necessary Tools 17 | 18 | Now that we’ve got that covered, let’s dive in! To start developing, you’ll need a few extra tools: 19 | - [Git](https://git-scm.com/) - Any Git client works, but we recommend [GitHub Desktop](https://desktop.github.com/download/) 20 | - [Visual Studio Code](https://code.visualstudio.com/) - We love this code editor 21 | - [Doxygen](https://www.doxygen.nl/) - This builds the whole html documentation. You will need at least __version 1.9.5__ -------------------------------------------------------------------------------- /.github/workflows/compile-examples.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # - push 3 | # - pull_request 4 | - workflow_dispatch 5 | 6 | jobs: 7 | compile-examples: 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | fqbn: 13 | - arduino:avr:uno 14 | - arduino:avr:nano 15 | - arduino:avr:mega 16 | - arduino:avr:leonardo 17 | - arduino:avr:micro 18 | - arduino:avr:mini 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: arduino/compile-sketches@v1 23 | with: 24 | fqbn: ${{ matrix.fqbn }} 25 | 26 | sketch-paths: | 27 | - examples/Commander_simple 28 | - examples/Commander_simple_progmem 29 | 30 | compile-examples-for-esp32: 31 | runs-on: ubuntu-latest 32 | 33 | strategy: 34 | matrix: 35 | fqbn: 36 | - esp32:esp32:esp32 37 | - esp32:esp32:esp32s3 38 | - esp32:esp32:esp32c3 39 | 40 | steps: 41 | - uses: actions/checkout@v3 42 | - uses: arduino/compile-sketches@v1 43 | with: 44 | fqbn: ${{ matrix.fqbn }} 45 | 46 | platforms: | 47 | - name: esp32:ESP32 48 | source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 49 | 50 | sketch-paths: | 51 | - examples/Commander_simple_ESP32 52 | 53 | compile-examples-for-esp8266: 54 | runs-on: ubuntu-latest 55 | 56 | strategy: 57 | matrix: 58 | fqbn: 59 | - esp8266:esp8266:nodemcuv2 60 | 61 | steps: 62 | - uses: actions/checkout@v3 63 | - uses: arduino/compile-sketches@v1 64 | with: 65 | fqbn: ${{ matrix.fqbn }} 66 | 67 | platforms: | 68 | - name: esp8266:ESP8266 69 | source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json 70 | 71 | 72 | sketch-paths: | 73 | - examples/Commander_simple_ESP8266 74 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Colors for Commander-API 3 | ####################################### 4 | # Class 5 | ####################################### 6 | 7 | Commander KEYWORD1 8 | Argument KEYWORD1 9 | AutoComplete KEYWORD1 10 | CommandCaller KEYWORD1 11 | commanderPipeChannel KEYWORD1 12 | 13 | ####################################### 14 | # Methods and Functions 15 | ####################################### 16 | 17 | systemCommand KEYWORD2 18 | attachTree KEYWORD2 19 | systemCommand_P KEYWORD2 20 | systemVariableFloat KEYWORD2 21 | systemVariableInt KEYWORD2 22 | systemVariableString KEYWORD2 23 | attachVariables KEYWORD2 24 | enablePipeModule KEYWORD2 25 | attachTreeFunction KEYWORD2 26 | attachVariablesFunction KEYWORD2 27 | getSystemVariable KEYWORD2 28 | printSystemVariable KEYWORD2 29 | printSystemVariables KEYWORD2 30 | init KEYWORD2 31 | execute KEYWORD2 32 | attachDebugChannel KEYWORD2 33 | setDebugLevel KEYWORD2 34 | printHelp KEYWORD2 35 | enablePipeModuleFunc KEYWORD2 36 | enableFormatting KEYWORD2 37 | disableFormatting KEYWORD2 38 | printArgumentError KEYWORD2 39 | inString KEYWORD2 40 | update KEYWORD2 41 | floatToString KEYWORD2 42 | commandExists KEYWORD2 43 | generateHint KEYWORD2 44 | getHint KEYWORD2 45 | parseInt KEYWORD2 46 | parseFloat KEYWORD2 47 | parseStringFunction KEYWORD2 48 | find KEYWORD2 49 | isFound KEYWORD2 50 | getSystemVariable KEYWORD2 51 | 52 | 53 | ####################################### 54 | # Structures 55 | ####################################### 56 | 57 | debugLevel_t KEYWORD2 58 | API_t KEYWORD2 59 | systemCommand_t KEYWORD2 60 | systemVariableType_t KEYWORD2 61 | systemVariablePointer_t KEYWORD2 62 | systemVariableData_t KEYWORD2 63 | systemVariable_t KEYWORD2 64 | 65 | ####################################### 66 | # Constants 67 | ####################################### 68 | 69 | COMMANDER_MAX_COMMAND_SIZE LITERAL1 70 | COMMAND_PRINTF_BUFF_LEN LITERAL1 -------------------------------------------------------------------------------- /extras/trie_generator/script.js: -------------------------------------------------------------------------------- 1 | require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.31.1/min/vs' }}); 2 | require(['vs/editor/editor.main'], function() { 3 | const generateBtn = document.getElementById('generateBtn'); 4 | const helpBtn = document.getElementById('helpBtn'); 5 | const inputText = document.getElementById('inputText'); 6 | 7 | // Initialize Monaco Editor 8 | const editor = monaco.editor.create(document.getElementById('outputText'), { 9 | value: '', 10 | language: 'cpp', 11 | theme: 'vs-dark', 12 | readOnly: true, 13 | lineNumbers: "on", 14 | minimap: { 15 | enabled: true 16 | }, 17 | scrollBeyondLastLine: false, 18 | automaticLayout: true 19 | }); 20 | 21 | generateBtn.addEventListener('click', function() { 22 | const input = inputText.value; 23 | const generatedOutput = generateTrie(input); 24 | editor.setValue(generatedOutput); 25 | }); 26 | 27 | copyBtn.addEventListener('click', function() { 28 | const generatedOutput = editor.getValue(); 29 | navigator.clipboard.writeText(generatedOutput).then(() => { 30 | alert('Copied to clipboard'); 31 | }); 32 | }); 33 | 34 | helpBtn.addEventListener('click', function() { 35 | alert('Enter text into the input box. Click "Generate" to create the Trie. Use the "Copy" button to copy the generated output.'); 36 | }); 37 | 38 | function generateTrie(input) { 39 | 40 | var separateLines = input.split( '\n' ); 41 | 42 | for( let i = 0; i < separateLines.length; i++ ){ 43 | separateLines[ i ] = ' ' + separateLines[ i ].trim(); 44 | } 45 | 46 | //console.log( separateLines ); 47 | 48 | let trie = new Trie(); 49 | 50 | for( let i = 0; i < separateLines.length; i++ ){ 51 | trie.insert( separateLines[ i ] ); 52 | } 53 | 54 | console.log( trie ); 55 | 56 | trie.print(); 57 | 58 | // Replace this with your actual Trie generation logic 59 | return trie.print(); 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /extras/Assets/DocuPages/installation_page.md: -------------------------------------------------------------------------------- 1 | @page installation_page Installation 2 | 3 | @tableofcontents 4 | 5 | Before we get started with anything, we need to install this software package into our preferred development environment. We've gathered the most popular development environments and prepared a short guide for you to ensure a smooth start to this adventure. 6 | 7 | ## Terminal Emulator Software 8 | 9 | ## Arduino Installation 10 | 11 | Let's start with the installation guide for the Arduino environment, as we believe this is the simplest one of all. First, you will need the 12 | [Arduino IDE](https://www.arduino.cc/en/software). This software is the heart of the Arduino ecosystem. Next, you'll need an Arduino board that has enough resources to meet your goals. 13 | 14 | To help you find the most suitable board, we've created a table for you to easily choose one. 15 | 16 | If you're just getting acquainted with this software, we recommend choosing a slightly more modern microcontroller, such as the Raspberry Pi Pico or the ESP32. We suggest this because these platforms have much more memory than, for example, an older Arduino Nano. This extra memory allows you to try out all the functions without needing to optimize right away. We're offering this friendly advice to help you avoid any additional distractions while you're learning. 17 | 18 | Once you've installed the Arduino IDE, the next task is to install Commander-API. You can find detailed instructions 19 | [here](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library/). 20 | Please make sure to always strive for using the latest version to ensure you have the most stable version on your computer. 21 | 22 | ## Arduino Installation Manually 23 | 24 | There are, unfortunately, times when the Library Manager is not available. This can happen for various reasons, such as wanting to install the library offline, or working in a corporate environment where the paths used by the Arduino IDE for installation are blocked. But don't worry! The libraries can still be installed in these situations, please follow the instructions from [here](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries/). 25 | 26 |
27 | 28 | | Previous | Next | 29 | |:------------------|-----------------------------:| 30 | | | [Examples](@ref examples_page) | 31 | 32 |
-------------------------------------------------------------------------------- /src/Commander-DefaultSettings.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 18 2020 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.02.06 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | 35 | #ifndef COMMANDER_API_SRC_COMMANDER_SETTINGS_HPP_ 36 | #define COMMANDER_API_SRC_COMMANDER_SETTINGS_HPP_ 37 | 38 | #ifdef __has_include 39 | #if __has_include ("Commander-Settings.hpp") 40 | #include "Commander-Settings.hpp" 41 | #endif 42 | #endif 43 | 44 | #ifdef ESP32 45 | 46 | #ifndef COMMANDER_USE_WIFI_CLIENT_RESPONSE 47 | #define COMMANDER_USE_WIFI_CLIENT_RESPONSE 48 | #endif 49 | 50 | #ifndef COMMANDER_ENABLE_PIPE_MODULE 51 | #define COMMANDER_ENABLE_PIPE_MODULE 52 | #endif 53 | 54 | #ifndef COMMANDER_MAX_COMMAND_SIZE 55 | #define COMMANDER_MAX_COMMAND_SIZE 50 56 | #endif 57 | 58 | #endif 59 | 60 | #ifdef ESP8266 61 | 62 | #ifndef COMMANDER_USE_WIFI_CLIENT_RESPONSE 63 | #define COMMANDER_USE_WIFI_CLIENT_RESPONSE 64 | #endif 65 | 66 | #ifndef COMMANDER_ENABLE_PIPE_MODULE 67 | #define COMMANDER_ENABLE_PIPE_MODULE 68 | #endif 69 | 70 | #ifndef COMMANDER_MAX_COMMAND_SIZE 71 | #define COMMANDER_MAX_COMMAND_SIZE 50 72 | #endif 73 | 74 | #endif 75 | 76 | // Enable the Pipe module by default 77 | #define COMMANDER_ENABLE_PIPE_MODULE 78 | 79 | /// Maximum length of the terminal command. 80 | #ifndef COMMANDER_MAX_COMMAND_SIZE 81 | #define COMMANDER_MAX_COMMAND_SIZE 40 82 | #endif 83 | 84 | /// This macro can be used create a buffer for piping. 85 | /// @note The pipe buffer has to be a char type and the size of the buffer has to be as big as this define. 86 | #define COMMANDER_PIPE_BUFFER_SIZE ( COMMANDER_MAX_COMMAND_SIZE + 1 ) 87 | 88 | #endif /* COMMANDER_API_SRC_COMMANDER_SETTINGS_HPP_ */ 89 | -------------------------------------------------------------------------------- /src/Commander-Caller-Interface.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 18 2020 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.02.06 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | #ifndef __COMMANDER_CALLER_INTERFACE_HPP__ 35 | #define __COMMANDER_CALLER_INTERFACE_HPP__ 36 | 37 | #include "Stream.h" 38 | 39 | class CommandCaller : public Stream{ 40 | 41 | public: 42 | CommandCaller(); 43 | 44 | /// Available bytes in the channel. 45 | /// 46 | /// @returns The available bytes in the channel. 47 | int available() override; 48 | 49 | /// Read one byte form the channel. 50 | /// 51 | /// @returns Read and return one byte form the channel. The byte will be removed from the channel. 52 | int read() override; 53 | 54 | /// Peek the firtst byte from the channel. 55 | /// 56 | /// @returns Read and return one byte form the channel. The byte will NOT be removed from the channel. 57 | int peek() override; 58 | 59 | /// Flush the channel. 60 | void flush() override; 61 | 62 | /// Write one byte to the channel. 63 | /// 64 | /// @param b The value that has to be written to the channel. 65 | /// @returns The number of bytes that has been successfully written to the channel. Because it is the base class, it returns 0. 66 | size_t write( uint8_t b ) override; 67 | 68 | size_t write( const uint8_t *buffer, size_t size ) override; 69 | 70 | void setChannel( Stream* channel_p ); 71 | Stream* getChannel(); 72 | void clearChannel(); 73 | 74 | virtual void beep(){} 75 | virtual void setBannerText( const char* text_p ){} 76 | virtual void setPathText( const char* text_p ){} 77 | virtual void clear(){} 78 | 79 | private: 80 | Stream* channel = NULL; 81 | 82 | }; 83 | 84 | #endif -------------------------------------------------------------------------------- /src/Commander-IO.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 18 2020 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.02.06 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | // Todo: This implementation is horrible, we need a circular buffer! 35 | 36 | #include "Commander-IO.hpp" 37 | 38 | int commanderPipeChannel::available(){ 39 | 40 | if( writePointer == readPointer ){ 41 | return 0; 42 | } 43 | 44 | return writePointer - readPointer; 45 | 46 | } 47 | 48 | int commanderPipeChannel::read(){ 49 | 50 | int ret; 51 | 52 | if( writePointer == readPointer ){ 53 | return -1; 54 | } 55 | 56 | else{ 57 | if( readPointer >= COMMANDER_MAX_COMMAND_SIZE ){ 58 | return -1; 59 | } 60 | 61 | ret = (uint8_t)buffer[ readPointer ]; 62 | readPointer++; 63 | 64 | } 65 | 66 | return ret; 67 | 68 | } 69 | 70 | /// Flush the channel. 71 | void commanderPipeChannel::flush(){ 72 | // Honestly I don't know what to do. 73 | // Arduino flush methods are weird. 74 | } 75 | 76 | int commanderPipeChannel::peek(){ 77 | 78 | if( writePointer == readPointer ){ 79 | return -1; 80 | } 81 | 82 | return (uint8_t)buffer[ readPointer ]; 83 | 84 | } 85 | 86 | size_t commanderPipeChannel::write( uint8_t data ){ 87 | 88 | if( writePointer >= COMMANDER_MAX_COMMAND_SIZE ){ 89 | return 0; 90 | } 91 | 92 | buffer[ writePointer ] = data; 93 | writePointer++; 94 | 95 | return 1; 96 | 97 | } 98 | 99 | size_t commanderPipeChannel::write( const uint8_t *data, size_t size ){ 100 | 101 | uint32_t i; 102 | 103 | for( i = 0; i < size; i++ ){ 104 | 105 | if( writePointer >= COMMANDER_MAX_COMMAND_SIZE ){ 106 | return i; 107 | } 108 | 109 | buffer[ writePointer ] = data[ i ]; 110 | writePointer++; 111 | 112 | } 113 | 114 | return size; 115 | 116 | } 117 | -------------------------------------------------------------------------------- /extras/Assets/DocuPages/introduction_page.md: -------------------------------------------------------------------------------- 1 | @mainpage Introduction 2 | 3 | ## A Lightweight Command Interpreter for Microcontrollers 4 | 5 | Commander-API is a command interpreter designed for microcontrollers with limited resources. It runs on almost any microcontroller, whether old or new. 6 | 7 | ### So, what’s it good for? 8 | Essentially, it gives you a functionality similar to a command-line interface on a desktop computer. You can create commands and easily link them to specific functions in your code. The main motivation behind this project was to eliminate the need to constantly recompile and upload your code during development. Instead, you can define diagnostic and testing commands that are dynamically available at runtime. 9 | 10 | But that’s not all! Commander-API also supports arguments, allowing you to create parameterized commands. This makes your development workflow much more flexible and powerful. 11 | 12 | In addition to handling commands and arguments, Commander-API can also manage environment variables. This lets you store and retrieve settings dynamically, making your system even more flexible and adaptable. 13 | 14 | ## Just a few lines of code and it is done! 15 | 16 | ```cpp 17 | #include "Commander-API.hpp" 18 | 19 | #define COMMAND_SIZE 30 20 | 21 | // We have to create an object from Commander class. 22 | Commander commander; 23 | 24 | // Command callbacks 25 | bool cat_func( char *args, CommandCaller* caller ); 26 | bool dog_func( char *args, CommandCaller* caller ); 27 | 28 | // Command tree 29 | Commander::systemCommand_t API_tree[] = { 30 | systemCommand( "cat", "Description for cat command.", cat_func ), 31 | systemCommand( "dog", "Description for dog command.", dog_func ) 32 | }; 33 | 34 | // This buffer is used to store command from Serial port. 35 | char commandBuffer[ COMMAND_SIZE ]; 36 | 37 | // System init section. 38 | void setup(){ 39 | Serial.begin(115200); 40 | 41 | commander.attachTree( API_tree ); 42 | commander.init(); 43 | 44 | Serial.println(); 45 | Serial.println( "---- Init Finished ----" ); 46 | Serial.println(); 47 | 48 | Serial.println( "Type something" ); 49 | Serial.print( "$: " ); 50 | } 51 | 52 | // Infinite loop. 53 | void loop(){ 54 | commander.update( commandBuffer, COMMAND_SIZE, &Serial ); 55 | } 56 | 57 | /// This is an example function for the cat command 58 | bool cat_func(char *args, CommandCaller* caller ){ 59 | caller -> print( "Hello from cat function!\r\n" ); 60 | return true; 61 | } 62 | 63 | /// This is an example function for the dog command 64 | bool dog_func(char *args, CommandCaller* caller ){ 65 | caller -> print( "Hello from dog function!\r\n" ); 66 | return true; 67 | } 68 | ``` 69 | 70 | Right? It doesn't seem that complicated, does it? It was very important to us to keep things simple. 71 | We really wanted beginner programmer padawans to be able to create amazing things easily with Commander. 72 | That's why we made sure the API is as simple and straightforward as possible. 73 | 74 | 75 |
76 | 77 | | Previous | Next | 78 | |:------------------|-----------------------------:| 79 | | | [Install](@ref installation_page) | 80 | 81 |
-------------------------------------------------------------------------------- /src/Commander-IO.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 18 2020 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.02.06 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | 35 | #ifndef COMMANDER_API_SRC_COMMANDER_IO_HPP_ 36 | #define COMMANDER_API_SRC_COMMANDER_IO_HPP_ 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | #include "Commander-DefaultSettings.hpp" 43 | 44 | #include "Stream.h" 45 | 46 | #ifdef ARDUINO 47 | #include "Arduino.h" 48 | #endif 49 | 50 | #ifdef COMMANDER_USE_WIFI_CLIENT_RESPONSE 51 | #ifdef ESP8266 52 | #include 53 | #endif 54 | 55 | #ifdef ESP32 56 | #include 57 | #endif 58 | #endif 59 | 60 | class commanderPipeChannel : public Stream{ 61 | 62 | public: 63 | 64 | /// Available bytes in the channel. 65 | /// 66 | /// @returns The available bytes in the channel. 67 | int available() override; 68 | 69 | /// Read one byte form the channel. 70 | /// 71 | /// @returns Read and return one byte form the channel. The byte will be removed from the channel. 72 | int read() override; 73 | 74 | /// Peek the firtst byte from the channel. 75 | /// 76 | /// @returns Read and return one byte form the channel. The byte will NOT be removed from the channel. 77 | int peek() override; 78 | 79 | /// Flush the channel. 80 | void flush() override; 81 | 82 | /// Write one byte to the channel. 83 | /// 84 | /// @param b The value that has to be written to the channel. 85 | /// @returns The number of bytes that has been successfully written to the channel. Because it is the base class, it returns 0. 86 | size_t write( uint8_t b ) override; 87 | 88 | size_t write( const uint8_t *buffer, size_t size ) override; 89 | 90 | private: 91 | uint8_t buffer[ COMMANDER_MAX_COMMAND_SIZE + 1 ]; 92 | uint32_t readPointer = 0; 93 | uint32_t writePointer = 0; 94 | 95 | friend class commanderPipeChannelUT; 96 | 97 | }; 98 | 99 | 100 | #endif /* COMMANDER_API_SRC_COMMANDER_IO_HPP_ */ 101 | -------------------------------------------------------------------------------- /src/Commander-Autocomplete.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 18 2020 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.02.06 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | #ifndef __COMMANDER_AUTOCOMPLETE_HPP__ 35 | #define __COMMANDER_AUTOCOMPLETE_HPP__ 36 | 37 | #include 38 | #include 39 | 40 | /* 41 | #include 42 | #include 43 | #include 44 | */ 45 | 46 | #define AUTOCOMPLETE_RESULT_SIZE 10 47 | 48 | class AutoComplete{ 49 | 50 | public: 51 | 52 | typedef struct{ 53 | char c; 54 | uint8_t word_end; 55 | uint8_t num_of_childs; 56 | uint16_t* child_indexes; 57 | } TrieElement_t; 58 | 59 | AutoComplete( TrieElement_t* trie_p ); 60 | 61 | void searchRecursive( int current_index, char* key, int key_size ); 62 | int searchLastMatchingNode( const char* key ); 63 | 64 | int generateHint( const char *key_p, char *buffer_p, int buffer_size_p ); 65 | const char* getResult( int index, bool only_remaining_chars = false ); 66 | 67 | private: 68 | TrieElement_t* trie = NULL; 69 | char *buffer; 70 | int buffer_size; 71 | int input_data_size; 72 | 73 | int work_buffer_size; 74 | int result_buffer_start; 75 | 76 | int result_buffer_counter; 77 | static const char empty_result; 78 | const char* result_pointers[ AUTOCOMPLETE_RESULT_SIZE ]; 79 | int result_counter; 80 | }; 81 | 82 | 83 | #endif 84 | 85 | 86 | /* Test data 87 | https://media.geeksforgeeks.org/wp-content/uploads/20220828232752/Triedatastructure1.png 88 | 89 | AutoComplete::TrieElement_t trie[ 9 ]; 90 | uint16_t trie_indexes_element_0[] = { 1, 2 }; 91 | uint16_t trie_indexes_element_1[] = { 3 }; 92 | uint16_t trie_indexes_element_2[] = { 4, 5 }; 93 | uint16_t trie_indexes_element_3[] = { 6, 7 }; 94 | uint16_t trie_indexes_element_4[] = { 8 }; 95 | 96 | AutoComplete test( trie ); 97 | 98 | trie[ 0 ].c = '\0'; 99 | trie[ 0 ].word_end = false; 100 | trie[ 0 ].num_of_childs = 2; 101 | trie[ 0 ].child_indexes = trie_indexes_element_0; 102 | 103 | trie[ 1 ].c = 'a'; 104 | trie[ 1 ].word_end = false; 105 | trie[ 1 ].num_of_childs = 1; 106 | trie[ 1 ].child_indexes = trie_indexes_element_1; 107 | 108 | trie[ 2 ].c = 'd'; 109 | trie[ 2 ].word_end = false; 110 | trie[ 2 ].num_of_childs = 2; 111 | trie[ 2 ].child_indexes = trie_indexes_element_2; 112 | 113 | trie[ 3 ].c = 'n'; 114 | trie[ 3 ].word_end = false; 115 | trie[ 3 ].num_of_childs = 2; 116 | trie[ 3 ].child_indexes = trie_indexes_element_3; 117 | 118 | trie[ 4 ].c = 'a'; 119 | trie[ 4 ].word_end = false; 120 | trie[ 4 ].num_of_childs = 1; 121 | trie[ 4 ].child_indexes = trie_indexes_element_4; 122 | 123 | trie[ 5 ].c = 'o'; 124 | trie[ 5 ].word_end = true; 125 | trie[ 5 ].num_of_childs = 0; 126 | trie[ 5 ].child_indexes = NULL; 127 | 128 | trie[ 6 ].c = 'd'; 129 | trie[ 6 ].word_end = true; 130 | trie[ 6 ].num_of_childs = 0; 131 | trie[ 6 ].child_indexes = NULL; 132 | 133 | trie[ 7 ].c = 't'; 134 | trie[ 7 ].word_end = true; 135 | trie[ 7 ].num_of_childs = 0; 136 | trie[ 7 ].child_indexes = NULL; 137 | 138 | trie[ 8 ].c = 'd'; 139 | trie[ 8 ].word_end = true; 140 | trie[ 8 ].num_of_childs = 0; 141 | trie[ 8 ].child_indexes = NULL; 142 | 143 | 144 | 145 | 146 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Commander-API](extras/Assets/DocuImages/banner.png)](https://www.commanderapi.org/html/index.html) 2 | 3 | # 🚀 About 4 | 5 | Commander-API is an easy-to-use command parser library designed for microcontroller environments. Thanks to its low resource requirements, it runs on almost any microcontroller. It’s **Arduino-compatible out of the box**, making it accessible for hobbyists and beginner programmers alike. 6 | 7 | # 🔄 What’s New? 8 | 9 | ### Argument Handling 10 | We’re really proud of this feature! Now you can easily and efficiently add arguments to your commands. The argument parser extracts the parameters you need from the raw argument string and even **automatically parses them into strings, floats, and integers** for convenience. 11 | 12 | ### 🌍 Environment Variables 13 | Access and modify variables from the command parser in the C++ domain directly! This makes it much easier to tweak system parameters at runtime. Hopefully, tuning your **PID controller** just got a whole lot easier. 😉 14 | 15 | ### 🔧 Major Refactoring 16 | We know "refactoring" isn’t the most exciting word—especially when it affects backward compatibility—but we believe in improving the project. We put a **huge amount of effort** into optimizing the internal structure (including a new **template-based database**—more on that later) to make Commander-API **faster, cleaner, and more scalable**. 17 | 18 | ### 📂 Database Overhaul 19 | This was one of our biggest tasks! The command storage system, previously tied to Commander class, has been modularized for **broader use cases** (like environment variables). We also removed **recursion** entirely—even during initialization—so **embedded developers can breathe a sigh of relief**. 20 | 21 | ### ⌨️ Auto-Complete (Experimental) 22 | We’re working on it! Early results are promising, but for now, **we don’t recommend using it yet**. Stay tuned! 23 | 24 | ### 📦 Abstraction Layers 25 | As the project grew, we realized it was time to **increase abstraction** for better integration into complex systems. For example, that’s why we introduced the **CommandCaller interface**, making it easier to integrate Commander-API into larger projects. 26 | 27 | ### 📡 Pipe Module (Almost Ready!) 28 | We’re finalizing a **refactored version** of the Pipe module, which should be **more stable, easier to configure, and more flexible** than before. We just need a bit more time to document it—stay tuned for updates! 29 | 30 | ### 📖 Better Documentation 31 | We finally invested the time to create **a user-friendly, well-organized, and example-rich documentation**. It’s available as part of the **Shellminator documentation**, so check it out! 32 | 33 | ### ❌ Removal of Built-in Commands 34 | We had to say goodbye to built-in system commands. 😢 The reason? It was nearly impossible to create a **one-size-fits-all** solution for every microcontroller. The implementation became messy, and it wasn’t fun to look at. **The good news:** We have a new approach in mind, and we’re actively working on a solution! 35 | 36 | # 📚 Documentation 37 | 38 | Commander-API has its own [technical documentation](https://www.commanderapi.org/html/index.html), but [most of the examples are found in the Shellminator documentation](https://www.shellminator.org/html/index.html). The Commander-specific docs are for those who want to **dive deep** into how it works under the hood. If you’re impatient, you might even find some **spoilers** about upcoming features! 39 | 40 | # 🤝 Feedback and Contributions 41 | 42 | We've poured **a lot of time and effort** into making this project **stable and easy to use**, but no software is perfect. Bugs happen. Features can improve. And that’s where you come in! 43 | 44 | If you have **feature suggestions, bug reports, or ideas for enhancements**, we’d love to hear from you. Your feedback makes Commander-API better for everyone! 45 | 46 | **Ways to contribute:** 47 | - 🐛 [Submit an issue](https://github.com/dani007200964/Commander-API/issues/new?template=bug_report.md) 48 | - 💬 [Join discussions](https://github.com/dani007200964/Commander-API/discussions) 49 | - 🛠 [Share your use cases](https://github.com/dani007200964/Commander-API/discussions/categories/show-and-tell) 50 | 51 | Every contribution, big or small, helps us grow! 52 | 53 | # ❤️ Support 54 | 55 | If this project has been helpful to you and you’d like to **buy us a coffee**, we’d really appreciate it! ☕ Every little bit helps keep this small team motivated. 56 | [![Support](extras/Assets/DocuImages/support.png)](https://ko-fi.com/danielhajnal) 57 | 58 | # 🌐 Useful Links 59 | 60 | - [Discussions](https://github.com/dani007200964/Commander-API/discussions) – Join the conversation and share your ideas! 61 | - [Technical Documentation](https://www.commanderapi.org/html/index.html) – Deep dive into Commander-API. 62 | - [Shellminator](https://www.shellminator.org/html/index.html) – Companion project with additional resources. 63 | - [Discord](https://discord.gg/GhMGqhBS) – Part of the Shellminator Discord server. Come chat with us! 64 | 65 | # 🗨️ Contact 66 | 67 | Have a question? Need help? Just want to say hi? 68 | - 🗨️ [Join the discussions](https://github.com/dani007200964/Commander-API/discussions) 69 | - 🎧 [Find us on Discord](https://discord.gg/GhMGqhBS) 70 | 71 | We’d love to hear from you! 🚀 72 | 73 | # 📃 License 74 | 75 | Commander-API is licensed under the **MIT License**. 76 | 77 | © Daniel Hajnal 78 | 79 | ✉️ hajnal.daniel96@gmail.com -------------------------------------------------------------------------------- /extras/Assets/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | $treeview 28 | $search 29 | $mathjax 30 | $darkmode 31 | 32 | $extrastylesheet 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
54 |
$projectname $projectnumber 55 |
56 |
$projectbrief
57 |
62 |
$projectbrief
63 |
$searchbox
$searchbox
81 |
82 | 83 | 84 | 85 | 109 | -------------------------------------------------------------------------------- /src/Commander-Autocomplete.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 18 2020 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.02.06 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | #include "Commander-Autocomplete.hpp" 35 | 36 | const char AutoComplete::empty_result = '\0'; 37 | 38 | 39 | AutoComplete::AutoComplete(TrieElement_t *trie_p){ 40 | int i; 41 | trie = trie_p; 42 | for( i = 0; i < AUTOCOMPLETE_RESULT_SIZE; i++ ){ 43 | result_pointers[ i ] = &empty_result; 44 | } 45 | } 46 | 47 | void AutoComplete::searchRecursive(int current_index, char *key, int key_size ){ 48 | 49 | int i; 50 | int search_index; 51 | char search_char; 52 | 53 | //wprintf( L"current_index: %d\r\n", current_index ); 54 | //wprintf( L"key: %.*s\r\n", key_size, key ); 55 | 56 | // Check if we ran out of buffer memory. If so, 57 | // it is pointless to do anything else. 58 | if( result_buffer_counter >= buffer_size ){ 59 | return; 60 | } 61 | 62 | if( result_counter >= AUTOCOMPLETE_RESULT_SIZE ){ 63 | // Check if we has any result pointers left 64 | return; 65 | } 66 | 67 | // We have to check if we found a word_end. 68 | // If so, we have to copy all data from the working part 69 | // of the buffer to the next result part. 70 | if( trie[ current_index ].word_end ){ 71 | 72 | for( i = 0; i < key_size; i++ ){ 73 | buffer[ result_buffer_counter ] = key[ i ]; 74 | result_buffer_counter++; 75 | 76 | if( result_buffer_counter >= buffer_size ){ 77 | result_counter--; 78 | return; 79 | } 80 | } 81 | 82 | // Terminate the result string. 83 | buffer[ result_buffer_counter ] = '\0'; 84 | result_buffer_counter++; 85 | 86 | if( result_buffer_counter >= buffer_size ){ 87 | return; 88 | } 89 | 90 | result_counter++; 91 | if( result_counter >= AUTOCOMPLETE_RESULT_SIZE ){ 92 | // Check if we has any result pointers left 93 | return; 94 | } 95 | result_pointers[ result_counter ] = &buffer[ result_buffer_counter ]; 96 | 97 | } 98 | 99 | 100 | for( i = 0; i < trie[ current_index ].num_of_childs; i++ ){ 101 | search_index = trie[ current_index ].child_indexes[ i ]; 102 | search_char = trie[ search_index ].c; 103 | if( key_size >= work_buffer_size ){ 104 | continue; 105 | } 106 | key[ key_size ] = search_char; 107 | searchRecursive( search_index, key, key_size + 1 ); 108 | } 109 | 110 | } 111 | 112 | int AutoComplete::searchLastMatchingNode( const char* key ){ 113 | 114 | int i; 115 | int next_index; 116 | int current_index = 0; 117 | int search_index; 118 | int search_char; 119 | 120 | if( *key == '\0' ){ 121 | return -1; 122 | } 123 | 124 | while( true ){ 125 | 126 | next_index = -1; 127 | for( i = 0; i < trie[ current_index ].num_of_childs; i++ ){ 128 | 129 | search_index = trie[ current_index ].child_indexes[ i ]; 130 | search_char = trie[ search_index ].c; 131 | 132 | if( *key == search_char ){ 133 | next_index = search_index; 134 | break; 135 | } 136 | 137 | } 138 | 139 | if( next_index < 0 ){ 140 | return -1; 141 | } 142 | 143 | key++; 144 | if( *key == '\0' ){ 145 | return next_index; 146 | } 147 | 148 | current_index = next_index; 149 | 150 | } 151 | 152 | return -1; 153 | 154 | } 155 | 156 | int AutoComplete::generateHint( const char *key_p, char *buffer_p, int buffer_size_p ){ 157 | int last_index; 158 | 159 | buffer = buffer_p; 160 | buffer_size = buffer_size_p; 161 | 162 | work_buffer_size = buffer_size / 3; 163 | result_buffer_start = work_buffer_size + 1; 164 | result_buffer_counter = result_buffer_start; 165 | 166 | result_counter = 0; 167 | result_pointers[ result_counter ] = &buffer[ result_buffer_start ]; 168 | 169 | last_index = searchLastMatchingNode( key_p ); 170 | if( last_index < 0 ){ 171 | return 0; 172 | } 173 | 174 | strncpy( buffer, key_p, work_buffer_size - 1 ); 175 | buffer[ work_buffer_size - 1 ] = '\0'; 176 | 177 | input_data_size = strlen( buffer ); 178 | 179 | //wprintf( L"buffer: %s\r\n", buffer ); 180 | //wprintf( L"last_index: %d\r\n", last_index ); 181 | 182 | searchRecursive( last_index, buffer, strlen( buffer ) ); 183 | 184 | if( result_counter < 0 ){ 185 | result_counter = 0; 186 | } 187 | 188 | return result_counter; 189 | } 190 | 191 | const char* AutoComplete::getResult( int index, bool only_remaining_chars ){ 192 | int offset = 0; 193 | 194 | if( index >= result_counter ){ 195 | return &empty_result; 196 | } 197 | 198 | if( only_remaining_chars ){ 199 | offset = input_data_size; 200 | } 201 | 202 | return result_pointers[ index ] + offset; 203 | } 204 | 205 | -------------------------------------------------------------------------------- /extras/trie_generator/trie.js: -------------------------------------------------------------------------------- 1 | /* 2 | class TrieNode { 3 | constructor() { 4 | this.children = {}; 5 | this.isEndOfWord = false; 6 | this.index = -1; 7 | } 8 | } 9 | 10 | class Trie { 11 | constructor() { 12 | this.root = new TrieNode(); 13 | this.elementCounter = 0; 14 | this.text = {}; 15 | } 16 | 17 | insert(word) { 18 | let node = this.root; 19 | for (let i = 0; i < word.length; i++) { 20 | let char = word[i]; 21 | if (!node.children[char]) { 22 | node.children[char] = new TrieNode(); 23 | } 24 | node = node.children[char]; 25 | } 26 | node.isEndOfWord = true; 27 | } 28 | 29 | index_rec( start ){ 30 | start.index = this.elementCounter; 31 | this.elementCounter++; 32 | for( let child in start.children ){ 33 | this.index_rec( start.children[ child ] ); 34 | } 35 | } 36 | 37 | index(){ 38 | this.elementCounter = 0; 39 | this.index_rec( this.root ); 40 | } 41 | 42 | print_rec( start ){ 43 | //console.log( this.elementCounter, start.isEndOfWord, start.children, Object.keys(start.children).length ); 44 | //if( !this.text[ start.index ] ){ 45 | // this.text[ start.index ] = 46 | //} 47 | //this.elementCounter++; 48 | for( let child in start.children ){ 49 | console.log( start.children[ child ] ); 50 | //this.text[ start.children[ child ].index ] = start.children[ child ]; 51 | this.print_rec( start.children[ child ] ); 52 | } 53 | } 54 | 55 | print(){ 56 | this.index(); 57 | //console.log( this.root ); 58 | this.text = new Array( this.elementCounter ).fill( 'a' ); 59 | //console.log( this.text ); 60 | //console.log( Object.keys( this.root.children ) ); 61 | this.print_rec( this.root ); 62 | //console.log( this.text ); 63 | } 64 | 65 | } 66 | 67 | */ 68 | 69 | class TrieNode { 70 | constructor() { 71 | this.character = '\0'; 72 | this.children = {}; 73 | this.isEndOfWord = false; 74 | this.index = -1; 75 | } 76 | } 77 | 78 | class Trie { 79 | constructor() { 80 | this.root = new TrieNode(); 81 | this.elementCounter = 0; 82 | this.array_text = {}; 83 | this.index_text = {}; 84 | this.name = "trie"; 85 | } 86 | 87 | setName( name_p ){ 88 | this.name = name_p; 89 | } 90 | 91 | insert(word) { 92 | let node = this.root; 93 | for (let i = 0; i < word.length; i++) { 94 | let char = word[i]; 95 | if (!node.children[char]) { 96 | node.children[char] = new TrieNode(); 97 | } 98 | node = node.children[char]; 99 | node.character = char; 100 | } 101 | node.isEndOfWord = true; 102 | } 103 | 104 | index_rec( start ){ 105 | start.index = this.elementCounter; 106 | this.elementCounter++; 107 | for( let child in start.children ){ 108 | this.index_rec( start.children[ child ] ); 109 | } 110 | } 111 | 112 | index(){ 113 | this.elementCounter = 0; 114 | this.index_rec( this.root ); 115 | } 116 | 117 | 118 | print_rec( start ){ 119 | //console.log( this.elementCounter, start.isEndOfWord, start.children, Object.keys(start.children).length ); 120 | //if( !this.text[ start.index ] ){ 121 | // this.text[ start.index ] = 122 | //} 123 | //this.elementCounter++; 124 | let char_local = start.character; 125 | if( char_local == '\u0000' ){ 126 | char_local = "\\0"; 127 | } 128 | let array_text_local = "{ \'" + char_local + "\', "; 129 | array_text_local += start.isEndOfWord + ", "; 130 | array_text_local += Object.keys( start.children ).length + ", "; 131 | 132 | if( Object.keys( start.children ).length > 0 ){ 133 | let index_array_name = this.name + "_element_" + start.index + "_indexes"; 134 | let index_text_local = "uint16_t " + index_array_name + "[] = { "; 135 | for( let child in start.children ){ 136 | index_text_local += start.children[ child ].index + ", "; 137 | } 138 | index_text_local = index_text_local.substring( 0, index_text_local.length - 2 ); 139 | index_text_local += " };"; 140 | if( !this.index_text[ start.index ] ){ 141 | this.index_text[ start.index ] = new String( index_text_local ); 142 | } 143 | array_text_local += index_array_name + " }"; 144 | } 145 | else{ 146 | array_text_local += "NULL }"; 147 | } 148 | 149 | if( !this.array_text[ start.index ] ){ 150 | this.array_text[ start.index ] = new String( array_text_local ); 151 | } 152 | //this.array_text[ start.index ] = start.character; 153 | for( let child in start.children ){ 154 | console.log( start.children[ child ] ); 155 | //this.text[ start.children[ child ].index ] = start.children[ child ]; 156 | this.print_rec( start.children[ child ] ); 157 | } 158 | } 159 | 160 | print(){ 161 | this.index(); 162 | //console.log( this.root ); 163 | // this.array_text = new Array( this.elementCounter ).fill( 'a' ); 164 | // this.array_text 165 | //console.log( this.text ); 166 | //console.log( Object.keys( this.root.children ) ); 167 | this.print_rec( this.root ); 168 | //console.log( this.array_text ); 169 | //console.log( this.index_text ); 170 | 171 | let output_text = "// Generated with Commander Trie Generator\n"; 172 | output_text += "// Trie name: " + this.name + "\n"; 173 | 174 | for( let text in this.index_text ){ 175 | output_text += this.index_text[ text ] + "\n"; 176 | } 177 | 178 | output_text += "\nAutoComplete::TrieElement_t " + this.name + "[] = {\n"; 179 | 180 | for( let text in this.array_text ){ 181 | output_text += "\t" + this.array_text[ text ] + ",\n"; 182 | } 183 | 184 | output_text = output_text.substring( 0, output_text.length - 2 ); 185 | output_text += "\n};\n"; 186 | 187 | console.log( output_text ); 188 | return output_text; 189 | 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /extras/Assets/DoxygenLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /src/Commander-Arguments.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 25 2022 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.06.25 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | 35 | #ifndef COMMANDER_ARGUMENTS_HPP_ 36 | #define COMMANDER_ARGUMENTS_HPP_ 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include "Commander-API.hpp" 43 | 44 | /// Try to parse the defined string argument. 45 | /// 46 | /// @note Please use this macro instead of parseStringFunction. It helps to avoid buffer overflow. 47 | /// @param buffer Character array to store the extracted string data. 48 | /// @returns It will return true if the parsing was successful. 49 | #define parseString( buffer ) parseStringFunction( buffer, sizeof( buffer ) / sizeof( buffer[ 0 ] ) ) 50 | 51 | /// Argument class. 52 | /// 53 | /// This class is made to parse simply __int__, __float__ and __string__ 54 | /// arguments from a source character array with the syntax style of the getopt library. 55 | /// Sadly getopt can not be used with an embedded system, like an AVR. It is 56 | /// too resource hungry. Also the C version of getopt does not support long argument 57 | /// names. This is a lightweight argument parser and it can be used for 4 kinds of arguments: 58 | /// * Place Dependent Argument - A place dependent argument is usually not optional. 59 | /// Also it has a fixed place in the argument string. 60 | /// It can be indexed like a regular array element from 0 to N-1 where N is the number of total arguments. 61 | /// The argument string must be start with the fixed position regular arguments. 62 | /// The first place dependent argument can be 63 | /// optional. It is easy to detect if it exists or not. But it is a good practice 64 | /// to allow __only the first__ place dependent argument to be optional. 65 | /// * Optional Argument - These are optional settings for the command. 66 | /// They must be identified by a name. The name can be short or long. 67 | /// short: __-h__ long: __--help__. The short argument name always starts with 68 | /// a dash character( '-' ). The long argument name always starts with two dash 69 | /// characters( '--' ). After the dashes, the name comes. In case of a short argument 70 | /// the name is always one characters long( this is why it is called short name ). 71 | /// Both long and short names are case sensitive. After the name, at least one 72 | /// white space character is required between the name and the value. Supported white space 73 | /// characters are space or tabulator( ' ' or '\t' ). 74 | /// * Place Independent Argument - The syntax is the same as the optional argument. Only difference 75 | /// is, that this is required by the user code, the value is not optional. 76 | /// * Flag - The flag is basically an optional argument without a value. The naming syntax is 77 | /// the same. Only difference is, that the flag does not have a value. It can be used to indicate 78 | /// something. 79 | /// 80 | /// This library supports 3 kinds of values: 81 | /// * __int__ - A regular int value. It is parsed by sscanf. It does not require an external buffer to parse 82 | /// the data. The parsed value is stored internally after parsing. 83 | /// * __float__ - A regular float value. It is parsed by atof. It does not require an external buffer to parse 84 | /// the data. The parsed value is stored internally after parsing. Exponent notation is supported, 85 | /// for more information check [here](https://en.cppreference.com/w/c/string/byte/atof). 86 | /// * string - The string value parsing requires an external buffer to store the parsed string. 87 | /// If the string value contains white space characters, the value must be encapsulated with brackets 88 | /// like this: --text "this is a text with white space characters" 89 | /// @note Be careful with long strings on low power devices like Arduino Uno / Nano with AVR chips. 90 | class Argument{ 91 | 92 | public: 93 | 94 | /// Constructor for place dependent argument. 95 | /// 96 | /// Use this constructor to create a place dependent argument object. 97 | /// @param source_p The source argument string. 98 | /// This character array must contain the whole argument string. 99 | /// It must be terminated with string end character( '\0' ). 100 | /// @param place_p The place of the argument in the argument string. It is valid from 101 | /// 0 - N-1, where N is the number of white space separated tokens in the argument string. 102 | Argument( const char* source_p, int place_p ); 103 | 104 | /// Constructor for optional or place independent argument with only short name. 105 | /// 106 | /// Use this constructor to create an optional or place independent argument object. 107 | /// This version only supports short argument names. 108 | /// @note If your system does not have a relatively high amount of dynamic memory, 109 | /// please use this version. It uses much less memory than, the long name version. 110 | /// It is perfect for UNO / NANO with AVR chipset. 111 | /// @param source_p The source argument string. 112 | /// This character array must contain the whole argument string. 113 | /// It must be terminated with string end character( '\0' ). 114 | /// @param shortName_p This is a simple character. It is valid from a-z or A-Z. 115 | /// @note Be careful __shortName_p__ is case sensitive! 116 | Argument( const char* source_p, char shortName_p ); 117 | 118 | /// Constructor for optional or place independent argument with short and long name. 119 | /// 120 | /// Use this constructor to create an optional or place independent argument object. 121 | /// This version supports both short and long names. 122 | /// @note Only use this version if your system has a relatively high amount of dynamic memory. 123 | /// My recommendation is at least 10k-bytes of RAM if you need to use this( ESP8266, ESP32, STM32... ). 124 | /// @param source_p The source argument string. 125 | /// This character array must contain the whole argument string. 126 | /// It must be terminated with string end character( '\0' ). 127 | /// @param shortName_p This is a simple character. It is valid from a-z or A-Z. 128 | /// @param longName_p Pointer to a character array, that contains the long name. 129 | /// @note Be careful both __shortName_p__ and __longName_p__ are case sensitive! 130 | Argument( const char* source_p, char shortName_p, const char* longName_p ); 131 | 132 | /// Override return behaviour for int. 133 | /// 134 | /// The return behaviour is overriden in such a way, that it will 135 | /// return the parsed integer number if the __parseInt__ function 136 | /// was successfully parsed the input argument string. Otherwise it 137 | /// will return __0__. 138 | operator int(); 139 | 140 | /// Override return behaviour for float. 141 | /// 142 | /// The return behaviour is overriden in such a way, that it will 143 | /// return the parsed float number if the __parseFloat__ function 144 | /// was successfully parsed the input argument string. Otherwise it 145 | /// will return __0.0__. 146 | operator float(); 147 | 148 | /// Override return behaviour for char array. 149 | /// 150 | /// The return behaviour is overriden in such a way, that it will 151 | /// return a pointer to the parsed string if the __parseString__ function 152 | /// was successfully parsed the input argument string. Otherwise it 153 | /// will return a pointer to an empty string( "" ). 154 | operator char*(); 155 | 156 | /// Override return behaviour for boolean. 157 | /// 158 | /// The return behaviour is overriden in such a way, that it will 159 | /// return __true__ value if the parsing process for the specified type 160 | /// was successful. Otherwise it will return false. 161 | operator bool(); 162 | 163 | bool parseInt(); 164 | bool parseFloat(); 165 | bool parseStringFunction( char* buffer, int bufferSize ); 166 | bool find(); 167 | bool isFound(); 168 | Commander::systemVariable_t* getSystemVariable(); 169 | 170 | private: 171 | 172 | // Because it can only return one thing, 173 | // an union is made to save som precious memory. 174 | union returnData { 175 | int i; // For int return data. 176 | float f; // For float return data. 177 | char* c; // For string return data. 178 | }; 179 | 180 | typedef bool boolField; 181 | 182 | struct boolFields{ 183 | 184 | boolField parsed : 1; 185 | boolField found : 1; 186 | 187 | }__attribute__((packed)); 188 | 189 | // Pointer to the source buffer. 190 | const char* source = NULL; 191 | 192 | // Size of the source buffer. 193 | // It will use strlen to identify 194 | int sourceSize = -1; 195 | 196 | // If fixed place argument is used, this will store its place. 197 | int place = -1; 198 | 199 | // If short named argument is used, this will store its short name character. 200 | char shortName = '\0'; 201 | 202 | // If long named argument is used, this will store pointer to its long name. 203 | const char* longName = NULL; 204 | 205 | // Pointer to a system variable if it is found. 206 | // If not, it will be NULL. 207 | Commander::systemVariable_t* systemVariable = NULL; 208 | 209 | 210 | boolFields bFields; 211 | 212 | union returnData ret; 213 | //int intResult; 214 | //int floatResult; 215 | //char* outStringBuffer = NULL; 216 | static const char failedString; 217 | 218 | /// Find the start position of a short named argument. 219 | /// 220 | /// This function will return the position( index ) of the 221 | /// start location of a short named argument, if the argument 222 | /// is found in the source string. 223 | /// @returns The index of the argument. If the argument is not found, 224 | /// it will return a negative number. 225 | /// @note If the short argument name is found, it will return the index 226 | /// of the first character of the argument. If the first character 227 | /// is a string terminator, it will return its index as well. It is 228 | /// necessary to find arguments without argument data( flags ). 229 | int findShortName(); 230 | 231 | /// Find the start position of a long named argument. 232 | /// 233 | /// This function will return the position( index ) of the 234 | /// start location of a long named argument, if the argument 235 | /// is found in the source string. 236 | /// @returns The index of the argument. If the argument is not found, 237 | /// it will return a negative number. 238 | /// @note If the short argument name is found, it will return the index 239 | /// of the first character of the argument. If the first character 240 | /// is a string terminator, it will return its index as well. It is 241 | /// necessary to find arguments without argument data( flags ). 242 | int findLongName(); 243 | 244 | /// Find the start position of a place dependent argument. 245 | /// 246 | /// This function will return the position( index ) of the 247 | /// start location of a place dependent argument, if the argument 248 | /// is found in the source string. 249 | /// @returns The index of the argument. If the argument is not found, 250 | /// it will return a negative number. 251 | int findByPlace(); 252 | 253 | int findStart(); 254 | 255 | bool inString( int index ); 256 | 257 | int substring( char* str1, char* str2 ); 258 | 259 | // For unit testing. 260 | friend class ArgumentUT; 261 | 262 | }; 263 | 264 | #endif -------------------------------------------------------------------------------- /src/Commander-API.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on June 18 2020 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2022.02.06 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | 35 | #ifndef COMMANDER_API_SRC_COMMANDER_HPP_ 36 | #define COMMANDER_API_SRC_COMMANDER_HPP_ 37 | 38 | /// Version string 39 | #define COMMANDER_API_VERSION (const char*)"3.0.0" 40 | 41 | #include "stdint.h" 42 | #include "string.h" 43 | 44 | #include "Commander-DefaultSettings.hpp" 45 | #include "Commander-IO.hpp" 46 | 47 | #include "Stream.h" 48 | 49 | #include "Commander-Database.hpp" 50 | 51 | #include "Commander-Caller-Interface.hpp" 52 | #include "Commander-Autocomplete.hpp" 53 | 54 | /// Arduino detection 55 | #ifdef ARDUINO 56 | #include "Arduino.h" 57 | #endif 58 | 59 | // Clever solution to handle constant string data. 60 | // Thank you ondras12345! 61 | #ifndef __CONST_TXT__ 62 | #if defined(ARDUINO) && defined(__AVR__) 63 | #define __CONST_TXT__(s) F(s) 64 | #else 65 | #define __CONST_TXT__(s) (const char*)(s) 66 | #endif 67 | #endif 68 | 69 | #ifdef ESP8266 70 | #include 71 | #endif 72 | 73 | #ifdef ESP32 74 | #include 75 | #endif 76 | 77 | #ifdef __AVR__ 78 | #include 79 | #endif 80 | 81 | /// This macro simplifies the API element creation. 82 | /// 83 | /// With this macro you can fill the API tree structure easily. 84 | /// @param name The name of the command. The best practice is to use a const char array. 85 | /// @param desc The description of the command. The best practice is to use a const char array. 86 | /// @param func This function will be called, when this command gets executed. 87 | #define systemCommand( name, desc, func ) { 0, NULL, NULL, (const char*)name, { (const char*)desc, func, NULL } } 88 | //#define apiElement( name, desc, func ) { 0, NULL, NULL, (const char*)name, (const char*)desc, func } 89 | 90 | #define systemCommandWithHelp( name, desc, func, help ) { 0, NULL, NULL, (const char*)name, { (const char*)desc, func, help } } 91 | 92 | #ifdef __AVR__ 93 | 94 | /// This macro simplifies the API element creation for PROGMEM implementation. 95 | /// 96 | /// With this macro you can fill the API tree structure easily. 97 | /// It is used for PROGMEM implementation. 98 | /// @param element One element in the API tree. 99 | /// @param name The name of the command. The best practice is to use a const char array. 100 | /// @param desc The description of the command. The best practice is to use a const char array. 101 | /// @param func_arg This function will be called, when this command gets executed. 102 | #define systemCommand_P( element, name_p, desc_progmem, func_p ) { element.place = 0; \ 103 | element.left = NULL; \ 104 | element.right = NULL; \ 105 | element.name = name_p; \ 106 | element.data.desc = NULL; \ 107 | element.data.desc_P = __CONST_TXT__( desc_progmem ); \ 108 | element.data.func = func_p; \ 109 | } 110 | 111 | #else 112 | #define systemCommand_P( element, name_p, desc_progmem, func_p ) { element.place = 0; \ 113 | element.left = NULL; \ 114 | element.right = NULL; \ 115 | element.name = name_p; \ 116 | element.data.desc = __CONST_TXT__( desc_progmem ); \ 117 | element.data.func = func_p; \ 118 | } 119 | 120 | 121 | #endif 122 | 123 | /// This macro simplifies the attachment of the API-tree. 124 | /// 125 | /// With this macro you can attach the API-tree to the 126 | /// class easier and faster than with attachTreeFunction. 127 | /// @note Please use this macro instead of attachTreeFunction, because it is safer. It always calculates the correct size for the tree. 128 | #define attachTree( name ) attachTreeFunction( name, sizeof( name ) / sizeof( name[ 0 ] ) ) 129 | 130 | /// This macro simplifies the float type system variable creation. 131 | /// 132 | /// With this macro you can fill the system variable table easily. 133 | /// @param name The name of the float variable. 134 | /// @note Do not use & operator before the name. Just use the name, the macro will handle the rest. 135 | #define systemVariableFloat( name ) { 0, NULL, NULL, #name, { Commander::VARIABLE_FLOAT, { (float*)&name } } } 136 | 137 | /// This macro simplifies the int type system variable creation. 138 | /// 139 | /// With this macro you can fill the system variable table easily. 140 | /// @param name The name of the int variable. 141 | /// @note Do not use & operator before the name. Just use the name, the macro will handle the rest. 142 | #define systemVariableInt( name ) { 0, NULL, NULL, #name, { Commander::VARIABLE_INT, { (float*)&name } } } // It is an union, this is why (float*) casting required. 143 | 144 | /// This macro simplifies the char* or const char* type system variable creation. 145 | /// 146 | /// With this macro you can fill the system variable table easily. 147 | /// @param name The name of the char* or const char* variable. 148 | /// @note Do not use & operator before the name. Just use the name, the macro will handle the rest. Also, no const casting is needed. 149 | #define systemVariableString( name ) { 0, NULL, NULL, #name, { Commander::VARIABLE_STRING, { (float*)name } } } // It is an union, this is why (float*) casting required. 150 | 151 | /// This macro simplifies the attachment of the system variable table. 152 | /// 153 | /// With this macro you can attach the system variable table 154 | /// to the class easier and faster than with attachVariablesFunction. 155 | /// @param name The name of the variable table. The type has to be Commander::SystemVariable_t[]. 156 | /// @note Please use this macro instead of attachVariablesFunction, because it is safer. It always calculates the correct size for the table. 157 | #define attachVariables( name ) attachVariablesFunction( name, sizeof( name ) / sizeof( name[ 0 ] ) ) 158 | 159 | /// This macro simplifies the pipe system setup. 160 | /// 161 | /// With this macro you can setup the pipe system easily. 162 | /// @param buffer Pointer to a char buffer 163 | /// @param pipeChannel This is a pointer to a commanderPipeChannel object. 164 | /// @warning The buffer size has to be exactly COMMANDER_PIPE_BUFFER_SIZE! 165 | /// @note Please use this macro instead of enablePipeModuleFunc, because it is safer. It always calculates the correct size for the pipeBuffer. 166 | #define enablePipeModule( pipeBuffer, pipeChannel ) enablePipeModuleFunc( pipeBuffer, sizeof( pipeBuffer ) / sizeof( pipeBuffer[ 0 ] ), pipeChannel ) 167 | 168 | /// Commander class. 169 | /// 170 | /// This class can be used to create a command parser. 171 | /// Its job is to parse a command from a character 172 | /// array( string ) and find the matching function for 173 | /// that command from a database. This database consists 174 | /// an array of API_t-type elements, and these elements 175 | /// are store the relevant information for that specific 176 | /// command. 177 | class Commander{ 178 | 179 | public: 180 | 181 | /// Library version string. 182 | static const char *version; 183 | 184 | typedef enum{ 185 | DEBUG_OFF = 0, 186 | DEBUG_ERROR = 1, 187 | DEBUG_DEBUG = 2, 188 | DEBUG_VERBOSE = 3 189 | }debugLevel_t; 190 | 191 | /// Structure for command data. 192 | /// 193 | /// Every command will get a structure like this. 194 | /// This structure is used to store your commands 195 | /// in a balanced binary tree. 196 | typedef struct API_t{ 197 | 198 | const char *desc; ///< Description of the command 199 | 200 | bool(*func)( char*, CommandCaller* caller ); ///< Function pointer to the command function 201 | 202 | AutoComplete* help; 203 | 204 | #ifdef __AVR__ 205 | __FlashStringHelper *desc_P; ///< Description of the command( stored in PROGMEM ) 206 | #endif 207 | 208 | }API_t; 209 | 210 | typedef CommanderDatabase::dataRecord_t systemCommand_t; 211 | 212 | /// Enum for system variable type. 213 | enum systemVariableType_t{ 214 | VARIABLE_FLOAT, ///< Used for float data. 215 | VARIABLE_INT, ///< Used for int data. 216 | VARIABLE_STRING ///< Used for string data. 217 | }; 218 | 219 | typedef union{ 220 | 221 | float* floatData; ///< In case of float type, it will store the address of the actual variable. Otherwise it has to be NULL! 222 | int* intData; ///< In case of int type, it will store the address of the actual variable. Otherwise it has to be NULL! 223 | char* strData; ///< In case of char* type, it will store the address of the actual variable. Otherwise it has to be NULL! 224 | 225 | } systemVariablePointer_t; 226 | 227 | typedef struct{ 228 | systemVariableType_t type; 229 | systemVariablePointer_t data; 230 | } systemVariableData_t; 231 | 232 | typedef CommanderDatabase::dataRecord_t systemVariable_t; 233 | 234 | /// Attach API-tree to the object. 235 | /// 236 | /// With this function you can attach the API-tree 237 | /// structure array to the object. This array contains 238 | /// the data for each command. 239 | /// @note There is a macro to simplify this process. Please use the attachTree macro because it is safer! 240 | void attachTreeFunction( systemCommand_t* API_tree_p, uint32_t API_tree_size_p ); 241 | 242 | /// Attach system variables list to the object. 243 | /// 244 | /// With this function you can attach the system 245 | /// variables list to the object. This array contains 246 | /// the data for each variable. 247 | /// @note There is a macro to simplify this process. Please use the attachVariables macro because it is safer! 248 | static void attachVariablesFunction( systemVariable_t* variables_p, uint32_t variables_size_p ); 249 | 250 | /// Get the instance if a system variable by its name. 251 | /// 252 | /// With this function you can get the address of a system variable 253 | /// structure element by the name if the system variable. 254 | /// @param name The name of the system variable. 255 | /// @returns If the variable is found, it will return the address of the control structure. Otherwise it will return NULL. 256 | static systemVariable_t* getSystemVariable( const char* name ); 257 | 258 | /// Print the value of a system variable. 259 | /// 260 | /// This function will print the value of a system variable to a specified Stream. 261 | /// The variable is accessed by its name. 262 | /// @param channel_p Pointer to a Stream object. The data will be printed to this Stream. 263 | /// @param name The name of the system variable. 264 | /// @param decimalPlaces Optionally, you can specify how many how many decimal places will be needed in case of float data. 265 | static void printSystemVariable( Stream* channel_p, const char* name, int decimalPlaces = 2 ); 266 | 267 | /// Print all the available system variables. 268 | /// 269 | /// This function will print all the available system variables to a specified Stream. 270 | /// The variable name and its value will be printed. 271 | /// @param channel_p Pointer to a Stream object. The data will be printed to this Stream. 272 | static void printSystemVariables( Stream* channel_p ); 273 | 274 | /// Initializer. 275 | /// 276 | /// This function initializes the object and its internal parts. 277 | /// Firstly it makes the API-tree alphabetically ordered, then 278 | /// creates a balanced binary tree from it. It is necessary to 279 | /// speed up the search process. 280 | bool init(); 281 | 282 | /// Execution function for Stream response. 283 | /// 284 | /// This function tries to execute a command. 285 | /// It uses the Stream response channel, so 286 | /// the messages from the command handler 287 | /// will be passed to the selected Stream 288 | /// object. 289 | /// @param cmd This is a command string and it will be executed. 290 | /// @param caller_p This can be a pointer to anything. The reason why it is implemented, is because 291 | /// the Shellminator project needed a simple way to communicate with the caller 292 | /// terminal from the called command function. It is optional, the default value for it is NULL. 293 | bool execute( const char *cmd, Stream* channel_p = NULL, CommandCaller* caller_p = NULL ); 294 | 295 | /// Attach a debug channel to the object. 296 | /// 297 | /// This function attaches a Serial channel 298 | /// for debug messages. It also enables 299 | /// the debug functionality. It can be useful 300 | /// during the init phase. 301 | /// @param resp Pointer to a Stream object. The output data will be printed to this Stream. 302 | /// @note If you call this function the debug messages will be enabled automatically. 303 | void attachDebugChannel( Stream *resp ); 304 | 305 | static void setDebugLevel( debugLevel_t debugLevel_p ); 306 | 307 | /// Print the help text to a specified Stream. 308 | /// 309 | /// It prints all the available commands in 310 | /// alphabetical order. If the description 311 | /// argument is set to true, it also prints 312 | /// the description data for all commands. 313 | /// @param out Pointer to a Stream object. The output data will be printed to this Stream. 314 | /// @param description If this flag is set to true, the description data will be printed as well. 315 | /// @param style If this flag is set to true, the output text will be colorized. It will use VT100 316 | /// escape sequences, and because of it, the output data only will be pretty on VT100 317 | /// compatible frontends. 318 | void printHelp( Stream* out, bool description, bool style = false ); 319 | 320 | /// Enable pipe functionality. 321 | /// 322 | /// With this function you can enable piping. The syntax for piping is more less the same as 323 | /// with a Linux system. Sadly it is a bit resource hungry, because it requires a pipe buffer 324 | /// and a pipe Stream to make it work. For this reason the required amount buffer memory is 325 | /// at least three times as much as without the pipe module enabled. 326 | /// @param buffer Pointer to a char buffer. 327 | /// @param bufferSize The size of the buffer. 328 | /// @param pipeChannel_p This is a pointer to a commanderPipeChannel object. 329 | /// @returns If the pipe module is enabled it will return true. Otherwise most probably the buffer size is not set correctly. 330 | /// @warning The buffer size has to be exactly COMMANDER_PIPE_BUFFER_SIZE! 331 | /// @note There is a macro to simplify this process. Please use the enablePipeModule macro because it is safer! 332 | bool enablePipeModuleFunc( char* buffer, int bufferSize, commanderPipeChannel* pipeChannel_p ); 333 | 334 | /// Enable formatting. 335 | /// 336 | /// If you want to get fancy, you can enable coloring on the output text. 337 | /// @note It will only work with VT100 compatible host interface like PuTTY. 338 | /// On Arduino terminal monitor it will result some garbage text. 339 | /// @note Sadly Arduino terminal monitor not support VT100 yet @emoji :disappointed: 340 | void enableFormatting(); 341 | 342 | /// Disable formatting. 343 | /// 344 | /// You can disable formatting with this function. 345 | void disableFormatting(); 346 | 347 | /// Print 'Argument error!' to a specified Serial channel. 348 | /// 349 | /// This is a relatively common message, when arguments are used in a command. 350 | /// To avoid duplication and save som precious memory, this function is dedicated 351 | /// to print this message. 352 | /// @param channel_p Pointer to the Stream object where the message will be printed. 353 | static void printArgumentError( Stream* channel_p ); 354 | 355 | /// Detect if character is in the string. 356 | /// 357 | /// You can detect if the index of a character is in a 'string' in a character array. 358 | /// For example if the source looks like this: 359 | /// @code{unparsed} -p "hello" bello' @endcode 360 | /// And the index is 3 it will return true, because the h character is within two " characters. 361 | /// If the index us 11 it will return false, because the b character is not in a string. 362 | /// @param source Pointer to a source string. It has to be terminated! 363 | /// @param index The index of the investigated character. 364 | /// @returns As explained above. 365 | static bool inString( const char* source, int index ); 366 | 367 | /// Create a very very simple command lime. 368 | /// 369 | /// With this function you can create a very basic command line. 370 | /// A buffer is required to store the incoming characters somewhere. 371 | /// This function has to be called periodically to make it work. 372 | /// @param buffer Pointer to a buffer. 373 | /// @param bufferSize The size of the buffer. I recommend at least 20 character long buffer. 374 | /// @param channel_p Pointer to a Stream object like Serial. 375 | void update( char* buffer, int bufferSize, Stream* channel_p ); 376 | 377 | // 2 decimal point. 378 | static int floatToString( float number, char* buffer, int bufferSize ); 379 | 380 | bool commandExists( const char* cmd, systemCommand_t** cmd_ptr = NULL ); 381 | int generateHint( const char *fraction, char *buffer_p, int buffer_size_p ); 382 | AutoComplete* lastHint = NULL; 383 | const char* lastCommandHint = NULL; 384 | int lastCommandHintOffset = 0; 385 | const char* getHint( int index, bool only_remaining_chars = false ); 386 | 387 | private: 388 | 389 | /// Starting address of the API-tree. 390 | ///CommanderDatabase::dataRecord_t* API_tree = NULL; 391 | 392 | /// Number of elements in the API-tree. 393 | ///uint32_t API_tree_size = 0; 394 | 395 | CommanderDatabase regularCommands; 396 | 397 | 398 | static CommanderDatabase systemVariables; 399 | 400 | /// Internal command buffer. The command data 401 | /// has to be copied to this buffer. It is necessary 402 | /// because the execute function has to modify the 403 | /// content of the command. 404 | char tempBuff[ COMMANDER_MAX_COMMAND_SIZE + 1 ]; 405 | 406 | /// This variable will store the address of the input data. 407 | /// It is required to track back the original command in case 408 | /// of an error. 409 | const char* originalCommandData; 410 | 411 | bool formatting = false; 412 | 413 | /// Flag to enable or disable debug messages. 414 | static debugLevel_t debugLevel; 415 | 416 | /// Pointer to response class. By default it 417 | /// points to the default debug response handler. 418 | Stream *dbgResponse = NULL; 419 | 420 | /// Command execution. 421 | /// 422 | /// This function executes a command. Before calling this 423 | /// function, the response pointer and it's channel has to 424 | /// be configured correctly. 425 | bool executeCommand( const char *cmd ); 426 | 427 | /// Search for a character in a string. 428 | /// @param str Pointer to a character array where the search will be. 429 | /// @param c This character will be searched in the string. 430 | /// @param ignoreString If this flag is set to true, the searched character within a string will be ignored. 431 | /// For example if the searched character is 'A' and the input str is 'dog "Apple" Arduino' 432 | /// 12 will be returned, which is the start of the Arduino word. 433 | /// @returns If the character found in the string, the position of the first occurrence will be returned. 434 | /// If it can not be found, a negative number will be returned. 435 | int hasChar( const char* str, char c, bool ignoreString = true ); 436 | 437 | /// Search for a character in a string. 438 | /// @param str Pointer to a character array where the search will be. 439 | /// @param c This character will be searched in the string. 440 | /// @param number Which occurrence of c is searched. 441 | /// @param ignoreString If this flag is set to true, the searched character within a string will be ignored. 442 | /// For example if the searched character is 'A' and the input str is 'dog "Apple" Arduino' 443 | /// 12 will be returned, which is the start of the Arduino word. 444 | /// @returns If the character found in the string in the number'th place, the position of the first occurrence 445 | /// will be returned. If it can not be found, a negative number will be returned. 446 | int hasChar( const char* str, char c, int number, bool ignoreString = true ); 447 | 448 | void printBrokenPipe(); 449 | 450 | /// Channel for the internal piping. 451 | commanderPipeChannel* pipeChannel = NULL; 452 | 453 | /// If piping happens the output of the first command will be copied to this buffer. 454 | /// This way it can be passed to the second command and so on. 455 | /// @note If this variable points to a valid address, the piping module will be enabled in the executeCommand function. 456 | char* pipeArgBuffer = NULL; 457 | 458 | /// This function tracks the current pipe in case of a piped command. 459 | /// It can be used to backtrack a broken pipe position. 460 | int pipeCounter; 461 | 462 | /// This variable tracks the next free elements index in the update functions buffer. 463 | int updateBufferCounter = 0; 464 | 465 | static const char empty_string; 466 | 467 | /// For unit testing. 468 | friend class CommanderUT; 469 | 470 | CommandCaller defaultCommandCaller; 471 | CommandCaller *caller = NULL; 472 | 473 | }; 474 | 475 | 476 | 477 | #endif /* COMMANDER_API_SRC_COMMANDER_HPP_ */ 478 | -------------------------------------------------------------------------------- /src/Commander-Arguments.cpp: -------------------------------------------------------------------------------- 1 | #include "Commander-Arguments.hpp" 2 | 3 | Argument::Argument( const char* source_p, int place_p ){ 4 | 5 | // Save the source and set its size to an invalid value by default. 6 | // if the source is valid, the size will be calculated accordingly. 7 | source = source_p; 8 | sourceSize = -1; 9 | 10 | // Save the place to an internal variable. 11 | place = place_p; 12 | 13 | // In this case the short name is irrelevant, 14 | // set it to string terminator. 15 | shortName = '\0'; 16 | 17 | // In this case the long name is irrelevant, 18 | // set it to NULL. 19 | longName = NULL; 20 | 21 | // By default set te parsed and found flags to false. 22 | bFields.parsed = false; 23 | bFields.found = false; 24 | 25 | // If the source is defined correctly, 26 | // calculate its size. 27 | if( source ){ 28 | sourceSize = strlen( source ); 29 | } 30 | 31 | } 32 | 33 | Argument::Argument( const char* source_p, char shortName_p ){ 34 | 35 | // Save the source and set its size to an invalid value by default. 36 | // if the source is valid, the size will be calculated accordingly. 37 | source = source_p; 38 | sourceSize = -1; 39 | 40 | // In this case the place is irrelevant, 41 | // set it to -1. 42 | place = -1; 43 | 44 | // Save the short name character to internal variable. 45 | shortName = shortName_p; 46 | 47 | // long name is not defined with this constructor, 48 | // set it to NULL. 49 | longName = NULL; 50 | 51 | // By default set te parsed and found flags to false. 52 | bFields.parsed = false; 53 | bFields.found = false; 54 | 55 | // If the source is defined correctly, 56 | // calculate its size. 57 | if( source ){ 58 | sourceSize = strlen( source ); 59 | } 60 | 61 | } 62 | 63 | Argument::Argument( const char* source_p, char shortName_p, const char* longName_p ){ 64 | 65 | // Save the source and set its size to an invalid value by default. 66 | // if the source is valid, the size will be calculated accordingly. 67 | source = source_p; 68 | sourceSize = -1; 69 | 70 | // In this case the place is irrelevant, 71 | // set it to -1. 72 | place = -1; 73 | 74 | // Save the short name character to internal variable. 75 | shortName = shortName_p; 76 | 77 | // Save the pointer to the long name. 78 | longName = longName_p; 79 | 80 | // By default set te parsed and found flags to false. 81 | bFields.parsed = false; 82 | bFields.found = false; 83 | 84 | // If the source is defined correctly, 85 | // calculate its size. 86 | if( source ){ 87 | sourceSize = strlen( source ); 88 | } 89 | 90 | } 91 | 92 | int Argument::findShortName(){ 93 | 94 | // Tracks the current index of the investigated character. 95 | int index = 0; 96 | int searchIndex = 0; 97 | 98 | // Array, that contains the searched argument name. 99 | char expected[ 3 ] = { '-', '\0', '\0' }; 100 | 101 | // Check if the source is not set correctly. 102 | if( source == NULL ){ 103 | return -1; 104 | } 105 | 106 | // Only alphabetical characters are valid. 107 | if( !( ( ( shortName >= 'A' ) && ( shortName <= 'Z' ) ) || ( ( shortName >= 'a' ) && ( shortName <= 'z' ) ) ) ){ 108 | return -1; 109 | } 110 | 111 | // The alphabetical identifier is added to the expected string. 112 | expected[ 1 ] = shortName; 113 | 114 | while( true ){ 115 | 116 | // Try to find the argument name in the source array. 117 | index = substring( expected, (char*)&source[ searchIndex ] ); 118 | 119 | // Check if we found the short name in the source. 120 | // If not, return with invalid address. 121 | if( index < 0 ){ 122 | 123 | return -1; 124 | 125 | } 126 | 127 | if( !inString( index + searchIndex ) ){ 128 | searchIndex += 2; 129 | // Check and handle buffer overflow. 130 | if( ( index + searchIndex ) > sourceSize ){ 131 | return -1; 132 | } 133 | 134 | if( ( source[ index + searchIndex ] == ' ' ) || ( source[ index + searchIndex ] == '\t' ) || ( source[ index + searchIndex ] == '\0' ) ){ 135 | break; 136 | } 137 | } 138 | 139 | searchIndex += 2; 140 | // Check and handle buffer overflow. 141 | if( ( index + searchIndex ) > sourceSize ){ 142 | return -1; 143 | } 144 | 145 | } 146 | 147 | index = index + searchIndex; 148 | 149 | // Set the found flag 150 | // found = true; 151 | bFields.found = true; 152 | 153 | // Check and handle buffer overflow. 154 | if( index > sourceSize ){ 155 | return -1; 156 | } 157 | 158 | // Remove white space characters. 159 | while( ( source[ index ] == ' ' ) || ( source[ index ] == '\t' ) ){ 160 | 161 | index++; 162 | 163 | // Check and handle buffer overflow. 164 | if( index > sourceSize ){ 165 | return -1; 166 | } 167 | 168 | } 169 | 170 | // Return with the start index of the data. 171 | return index; 172 | 173 | } 174 | 175 | int Argument::findLongName(){ 176 | 177 | // Tracks the current index of the investigated character. 178 | int index = 0; 179 | 180 | // The long argument name looks like this: --name 181 | // It can be splitted to two parts: -- , name 182 | // The first part is is fix, it always look like this: -- 183 | // The second part is the actual name of the argument, 184 | // it is stored in longName. 185 | // According to this we will try to find the start index of 186 | // the two parts. 187 | int indexFirst; 188 | int indexSecond; 189 | 190 | // Expected string array for the first part. 191 | char expected[ 3 ] = { '-', '-', '\0' }; 192 | 193 | // Check if the source is not set correclty. 194 | if( source == NULL ){ 195 | return -1; 196 | } 197 | 198 | // Check if the longName is not set correclty. 199 | if( longName == NULL ){ 200 | return -1; 201 | } 202 | 203 | // Try to find a valid combination for the first and second part. 204 | // The exit event is when we run out of the bounds of the source array. 205 | while( index <= sourceSize ){ 206 | 207 | // This is tricky. We have to offset the return value of substring wiht the index; 208 | // Also, in every iteration we have to offset the start address of the sorce array with index. 209 | indexFirst = index + substring( expected, (char*)&source[ index ] ); 210 | indexSecond = index + substring( (char*)longName, (char*)&source[ index ] ); 211 | 212 | // If the '--' string can not be found in the rest of the source array, 213 | // we have to quit with a negative number. 214 | if( ( indexFirst - index ) < 0 ){ 215 | return -1; 216 | } 217 | 218 | // We have to check if we are in a string. 219 | if( inString( indexFirst ) ){ 220 | //If it is the case we have to search further. 221 | index++; 222 | continue; 223 | } 224 | 225 | // If the first and the second part is next to each other, 226 | // the second part must be 2 characters behind the first part. 227 | if( ( indexSecond - indexFirst ) != 2 ){ 228 | // If it is not the case, we have to search further. 229 | // We increment the index, and start a next iteration. 230 | index++; 231 | continue; 232 | } 233 | 234 | // If we are here, that means we found a valid combination of 235 | // the two parts. Still, we have to check the next character after the 236 | // second part. 237 | // For example: 238 | // --name 239 | // --nameeee 240 | // These strings will both come here because the compensation happens from left to right. 241 | // We have to detect the end of the token as well. 242 | index = indexSecond + strlen( longName ); 243 | 244 | // Check and handle buffer overflow. 245 | if( index > sourceSize ){ 246 | return -1; 247 | } 248 | 249 | // Check if we found string terminator at the end of the long name. 250 | if( source[ index ] == '\0' ){ 251 | // Set the found flag 252 | // found = true; 253 | bFields.found = true; 254 | return index; 255 | } 256 | 257 | // We need at least one white space character after the name. 258 | if( !( ( source[ index ] == ' ' ) || ( source[ index ] == '\t' ) ) ){ 259 | // If it is not the case, we have to search further. 260 | // We increment the index, and start a next iteration from here. 261 | index++; 262 | continue; 263 | } 264 | 265 | // If we are here, that means, we have a white 266 | // space character after the found long name. 267 | // Set the found flag 268 | // found = true; 269 | bFields.found = true; 270 | 271 | // 'Remove' white space characters. Basically, we skip every white space 272 | // characters to find the real index of the first character of the argument. 273 | while( ( source[ index ] == ' ' ) || ( source[ index ] == '\t' ) ){ 274 | 275 | index++; 276 | 277 | // Check if we found string terminator. 278 | if( source[ index ] == '\0' ){ 279 | // Set the found flag 280 | // found = true; 281 | bFields.found = true; 282 | return index; 283 | } 284 | 285 | // Check and handle buffer overflow. 286 | if( index > sourceSize ){ 287 | return -1; 288 | } 289 | 290 | } 291 | 292 | // Return with the start index of the data. 293 | return index; 294 | 295 | } 296 | 297 | // If we are here, that means, w did not found the long name. 298 | // In this case, we have to return wit negative number. 299 | return -1; 300 | 301 | } 302 | 303 | int Argument::findByPlace(){ 304 | 305 | int placeCounter = 0; 306 | int index = 0; 307 | int startIndex = 0;; 308 | 309 | bool prevWhiteSpace = false; 310 | bool whiteSpace; 311 | 312 | // Check if the place is invalid. 313 | if( place < 0 ){ 314 | return -1; 315 | } 316 | 317 | // Check if the source is not set correclty. 318 | if( source == NULL ){ 319 | return -1; 320 | } 321 | 322 | // Remove white space characters. 323 | while( ( source[ startIndex ] == ' ' ) || ( source[ startIndex ] == '\t' ) ){ 324 | 325 | startIndex++; 326 | 327 | // Check and handle buffer overflow. 328 | if( startIndex >= sourceSize ){ 329 | return -1; 330 | } 331 | 332 | } 333 | 334 | // Iterate through all the characters in the source buffer. 335 | for( index = startIndex; index < sourceSize; index++ ){ 336 | 337 | // Check if we found the right place. 338 | if( placeCounter == place ){ 339 | 340 | // Remove white space characters. 341 | while( ( source[ index ] == ' ' ) || ( source[ index ] == '\t' ) ){ 342 | 343 | index++; 344 | 345 | // Check and handle buffer overflow. 346 | if( index >= sourceSize ){ 347 | return -1; 348 | } 349 | 350 | } 351 | 352 | // Return with the start index of the data. 353 | return index; 354 | 355 | } 356 | 357 | // Detect if we changing from non whitespace to whitespace character. 358 | whiteSpace = ( source[ index ] == ' ' ) || ( source[ index ] == '\t' ); 359 | whiteSpace &= !inString( index ); 360 | 361 | if( ( prevWhiteSpace == false ) && ( whiteSpace == true ) ){ 362 | 363 | // It means we have to increment the place counter. 364 | placeCounter++; 365 | } 366 | 367 | // Store the current state for the next iteration. 368 | prevWhiteSpace = whiteSpace; 369 | 370 | 371 | } 372 | 373 | return -1; 374 | 375 | } 376 | 377 | int Argument::findStart(){ 378 | 379 | // This will hold the return value. 380 | int startIndex; 381 | 382 | // By default set it to NULL. It will be changed to a valid address if 383 | // the argument is a system variable. 384 | systemVariable = NULL; 385 | 386 | // Try to find the long name. 387 | startIndex = findLongName(); 388 | 389 | // If it fails the next step is to find the short name. 390 | if( startIndex < 0 ){ 391 | 392 | // Try to find the short name. 393 | startIndex = findShortName(); 394 | 395 | // If it fails the next step is to find by place. 396 | if( startIndex < 0 ){ 397 | 398 | // Try to find by place. 399 | startIndex = findByPlace(); 400 | 401 | // If it fails we can't do anything. 402 | if( startIndex < 0 ){ 403 | return -1; 404 | } 405 | 406 | } 407 | 408 | } 409 | 410 | // Check if it is a system variable. 411 | if( source[ startIndex ] == '$' ){ 412 | systemVariable = Commander::getSystemVariable( &source[ startIndex + 1 ] ); 413 | } 414 | 415 | return startIndex; 416 | 417 | } 418 | 419 | bool Argument::parseInt(){ 420 | 421 | // It will store the start address of the data for the current argument. 422 | int startIndex; 423 | 424 | // It will store the status of the string to integer conversation. 425 | int status = 0; 426 | 427 | // Clear the parsed flag. 428 | // parsed = false; 429 | bFields.parsed = false; 430 | 431 | // Try to find the argument. 432 | startIndex = findStart(); 433 | 434 | // Handle if it is not found. 435 | if( startIndex < 0 ){ 436 | return false; 437 | } 438 | 439 | if( systemVariable != NULL ){ 440 | 441 | if( ( systemVariable -> data.type ) == Commander::VARIABLE_INT ){ 442 | ret.i = *( systemVariable -> data.data.intData ); 443 | status = 1; 444 | } 445 | 446 | else if( ( systemVariable -> data.type ) == Commander::VARIABLE_FLOAT ){ 447 | ret.i = (int)*( systemVariable -> data.data.floatData ); 448 | status = 1; 449 | } 450 | 451 | } 452 | 453 | else{ 454 | 455 | // Try to convert string to integer number. 456 | status = sscanf( &source[ startIndex ], "%d", &ret.i ); 457 | 458 | } 459 | 460 | // Check if the conversation was succesful. 461 | // Store the result to the parsed variable. 462 | // parsed = status == 1; 463 | bFields.parsed = status == 1; 464 | 465 | // Return with the result. 466 | return bFields.parsed; 467 | 468 | } 469 | 470 | bool Argument::parseFloat(){ 471 | 472 | // It will store the start address of the data for the current argument. 473 | int startIndex; 474 | 475 | // Clear the parsed flag. 476 | // parsed = false; 477 | bFields.parsed = false; 478 | 479 | // Try to find the argument. 480 | startIndex = findStart(); 481 | 482 | // Handle if it is not found. 483 | if( startIndex < 0 ){ 484 | return false; 485 | } 486 | 487 | if( systemVariable != NULL ){ 488 | 489 | if( ( systemVariable -> data.type ) == Commander::VARIABLE_FLOAT ){ 490 | ret.f = *( systemVariable -> data.data.floatData ); 491 | bFields.parsed = true; 492 | } 493 | 494 | else if( ( systemVariable -> data.type ) == Commander::VARIABLE_INT ){ 495 | ret.f = (float)*( systemVariable -> data.data.intData ); 496 | bFields.parsed = true; 497 | } 498 | 499 | } 500 | 501 | else{ 502 | 503 | // The first character must be a number, or + or - character 504 | if( ( ( source[ startIndex ] >= '0' ) && ( source[ startIndex ] <= '9' ) ) || ( source[ startIndex ] == '+' ) || ( source[ startIndex ] == '-' ) ){ 505 | // Try to convert string to integer number. 506 | ret.f = (float)atof( &source[ startIndex ] ); 507 | 508 | // Check if the conversation was successful. 509 | // Store the result to the parsed variable. 510 | bFields.parsed = true; 511 | } 512 | 513 | } 514 | 515 | // Return with the result. 516 | return bFields.parsed; 517 | 518 | } 519 | 520 | bool Argument::parseStringFunction( char* buffer, int bufferSize ){ 521 | 522 | // It will store the start address of the data for the current argument. 523 | int startIndex; 524 | 525 | // Points to the next free element in the buffer. 526 | int bufferIndex; 527 | 528 | // It indicates quotation mark bounded string. 529 | bool quotation = false; 530 | 531 | // Next character that is rad from the source buffer. 532 | char c; 533 | 534 | // Return status for snprintf; 535 | int status; 536 | 537 | // Clear the parsed flag. 538 | // parsed = false; 539 | bFields.parsed = false; 540 | 541 | // Set an invalid address to the outStringBuffer by default. 542 | // outStringBuffer = NULL; 543 | ret.c = NULL; 544 | 545 | // Check for invalid buffer size. 546 | if( bufferSize < 1 ){ 547 | return false; 548 | } 549 | 550 | // Try to find the argument. 551 | startIndex = findStart(); 552 | 553 | // Handle if it is not found. 554 | if( startIndex < 0 ){ 555 | return false; 556 | } 557 | 558 | // The first character of the string should not be a '-' 559 | // Character. 560 | if( source[ startIndex ] == '-' ){ 561 | return false; 562 | } 563 | 564 | // If a system variable is requested, but it is not found, we have a problem. 565 | if( ( source[ startIndex ] == '$' ) && systemVariable == NULL ){ 566 | return false; 567 | } 568 | 569 | if( systemVariable != NULL ){ 570 | 571 | if( ( systemVariable -> data.type ) == Commander::VARIABLE_STRING ){ 572 | strncpy( buffer, systemVariable -> data.data.strData, bufferSize ); 573 | buffer[ bufferSize - 1 ] = '\0'; 574 | ret.c = buffer; 575 | bFields.parsed = true; 576 | return true; 577 | } 578 | 579 | else if( ( systemVariable -> data.type ) == Commander::VARIABLE_INT ){ 580 | status = snprintf( buffer, bufferSize, "%d", *systemVariable -> data.data.intData ); 581 | buffer[ bufferSize - 1 ] = '\0'; 582 | if( ( status > 0 ) && ( status < bufferSize ) ){ 583 | ret.c = buffer; 584 | bFields.parsed = true; 585 | return true; 586 | } 587 | else{ 588 | return false; 589 | } 590 | } 591 | 592 | else if( ( systemVariable -> data.type ) == Commander::VARIABLE_FLOAT ){ 593 | status = Commander::floatToString( *systemVariable -> data.data.floatData, buffer, bufferSize ); 594 | buffer[ bufferSize - 1 ] = '\0'; 595 | if( ( status > 0 ) && ( status < bufferSize ) ){ 596 | ret.c = buffer; 597 | bFields.parsed = true; 598 | return true; 599 | } 600 | else{ 601 | return false; 602 | } 603 | } 604 | 605 | } 606 | 607 | if( source[ startIndex ] == '\"' ){ 608 | quotation = true; 609 | startIndex++; 610 | 611 | // Check and handle buffer overflow. 612 | if( startIndex >= sourceSize ){ 613 | return false; 614 | } 615 | 616 | } 617 | 618 | 619 | for( bufferIndex = 0; bufferIndex < bufferSize; bufferIndex++ ){ 620 | 621 | // Check and handle buffer overflow. 622 | if( ( startIndex + bufferIndex ) > sourceSize ){ 623 | return false; 624 | } 625 | 626 | // Read the next character from the source buffer. 627 | c = source[ startIndex + bufferIndex ]; 628 | 629 | // First case is when the input string is bounded with " characters. 630 | if( quotation ){ 631 | 632 | if( c != '\"' ){ 633 | buffer[ bufferIndex ] = c; 634 | } 635 | 636 | else{ 637 | buffer[ bufferIndex ] = '\0'; 638 | //outStringBuffer = buffer; 639 | //parsed = true; 640 | bFields.parsed = true; 641 | ret.c = buffer; 642 | return true; 643 | } 644 | 645 | } 646 | 647 | // Second case is when the input string is ended with a white space character or string terminator. 648 | else{ 649 | 650 | if( ( c == ' ' ) || ( c == '\t' ) || ( c == '\0' ) ){ 651 | buffer[ bufferIndex ] = '\0'; 652 | // outStringBuffer = buffer; 653 | ret.c = buffer; 654 | //parsed = true; 655 | bFields.parsed = true; 656 | return true; 657 | } 658 | 659 | else{ 660 | buffer[ bufferIndex ] = c; 661 | } 662 | 663 | } 664 | 665 | } 666 | 667 | // If we are here, that means we did not found the end of the string. 668 | // It probably means, that the buffer is too short, or the " bounded 669 | // string is not terminated. 670 | return false; 671 | 672 | } 673 | 674 | bool Argument::find(){ 675 | 676 | // Reset the found flag. 677 | // found = false; 678 | bFields.found = false; 679 | 680 | findLongName(); 681 | findShortName(); 682 | 683 | return bFields.found; 684 | 685 | } 686 | 687 | Argument::operator int(){ 688 | 689 | //if( parsed ){ 690 | if( bFields.parsed ){ 691 | return ret.i; 692 | } 693 | return 0; 694 | 695 | } 696 | 697 | Argument::operator float(){ 698 | 699 | //if( parsed ){ 700 | if( bFields.parsed ){ 701 | return ret.f; 702 | } 703 | return 0.0; 704 | 705 | } 706 | 707 | Argument::operator bool(){ 708 | 709 | //return parsed; 710 | return bFields.parsed; 711 | 712 | } 713 | 714 | Argument::operator char*(){ 715 | 716 | //if( ( !parsed ) || ( ret.c == NULL ) ){ 717 | if( ( !bFields.parsed ) || ( ret.c == NULL ) ){ 718 | 719 | return (char*)&failedString; 720 | 721 | } 722 | 723 | return ret.c; 724 | 725 | } 726 | 727 | bool Argument::isFound(){ 728 | //return found; 729 | return bFields.found; 730 | } 731 | 732 | int Argument::substring( char* str1, char* str2 ){ 733 | 734 | // https://www.geeksforgeeks.org/check-string-substring-another/ 735 | 736 | int i; 737 | int j; 738 | 739 | int m = strlen( str1 ); 740 | int n = strlen( str2 ); 741 | 742 | for( i = 0; i <= ( n - m ); i++ ){ 743 | 744 | for( j = 0; j < m; j++ ){ 745 | 746 | if( str2[ i + j ] != str1[ j ] ){ 747 | break; 748 | } 749 | 750 | } 751 | 752 | if( j == m ){ 753 | 754 | return i; 755 | 756 | } 757 | 758 | } 759 | 760 | return -1; 761 | 762 | } 763 | 764 | bool Argument::inString( int index ){ 765 | 766 | int i; 767 | bool ret = false; 768 | 769 | // Check if the source is not set correclty. 770 | if( source == NULL ){ 771 | return false; 772 | } 773 | 774 | if( index < 0 ){ 775 | return false; 776 | } 777 | 778 | for( i = 0; i < index; i++ ){ 779 | 780 | if( source[ i ] == '\"' ){ 781 | ret = !ret; 782 | } 783 | 784 | } 785 | 786 | return ret; 787 | 788 | } 789 | 790 | Commander::systemVariable_t* Argument::getSystemVariable(){ 791 | return systemVariable; 792 | } 793 | 794 | const char Argument::failedString = '\0'; 795 | -------------------------------------------------------------------------------- /src/Commander-Database.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on Sept 9 2023 3 | * 4 | * Copyright (c) 2020 - Daniel Hajnal 5 | * hajnal.daniel96@gmail.com 6 | * This file is part of the Commander-API project. 7 | * Modified 2023.09.09 8 | */ 9 | 10 | /* 11 | MIT License 12 | 13 | Copyright (c) 2020 Daniel Hajnal 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | */ 33 | 34 | 35 | #ifndef COMMANDER_DATABASE_HPP_ 36 | #define COMMANDER_DATABASE_HPP_ 37 | 38 | #include 39 | #include "Stream.h" 40 | 41 | // Clever solution to handle constant string data. 42 | // Thank you ondras12345! 43 | #ifndef __CONST_TXT__ 44 | #if defined(ARDUINO) && defined(__AVR__) 45 | #define __CONST_TXT__(s) F(s) 46 | #else 47 | #define __CONST_TXT__(s) (const char*)(s) 48 | #endif 49 | #endif 50 | 51 | template< typename T > 52 | /// Database class 53 | /// 54 | /// This class is designed to create a database for any type of data. 55 | /// Every element has to have a unique name, which is case sensitive. 56 | /// The elements can be accessed by their names. 57 | class CommanderDatabase{ 58 | 59 | public: 60 | 61 | /// Debug level definitions. 62 | typedef enum{ 63 | DEBUG_OFF = 0, ///< All debug messages will be turned off. 64 | DEBUG_ERROR = 1, ///< Only error messages will be printed. 65 | DEBUG_DEBUG = 2, ///< Some debug data and the error messages will be printed. 66 | DEBUG_VERBOSE = 3 ///< All debug messages will be turned on. 67 | }debugLevel_t; 68 | 69 | /// Data record element. 70 | struct dataRecord_t{ 71 | 72 | uint16_t place; ///< This will store the alphabetical place of the element in the tree 73 | dataRecord_t *left; ///< Left element in the binary tree branch 74 | dataRecord_t *right; ///< Right element in the binary tree branch 75 | const char *name; ///< Name of the element 76 | 77 | T data; ///< Stored data. It comes from the template, so it can be any type of data. 78 | 79 | }; 80 | 81 | /// Empty constructor. 82 | CommanderDatabase(); 83 | 84 | /// Constructor. 85 | /// 86 | /// To construct a working object, this must be used. 87 | /// @param dataTree_p Pointer to the data array. The element type must be 88 | /// a configured dataRecord_t. For example: 89 | /// `CommanderDatabase::dataRecord_t testData[ 10 ]` 90 | /// will create a data array for integer type with 10 elements. 91 | /// @param dataTreeSize_p The size of the data array in elements. 92 | CommanderDatabase( dataRecord_t* dataTree_p, uint16_t dataTreeSize_p ); 93 | 94 | /// Redirect debug messages. 95 | /// 96 | /// You can specify an output stream, where the debug 97 | /// messages will be printed. By default it is not 98 | /// specified and no debug messages will be printed. 99 | /// @param debugChannel_p Pointer to a stream, where the debug messages will be printed. 100 | /// @note You have to call this function before init, if you need any debug data. 101 | void attachDebugChannel( Stream* debugChannel_p ); 102 | 103 | /// Set the debug level. 104 | /// 105 | /// You can set, how detailed debug is needed. 106 | /// @param debugLevel_p Please check @ref debugLevel_t for more information. 107 | /// @note You have to specify a Stream for the debug messages with 108 | /// @ref attachDebugChannel. 109 | void setDebugLevel( debugLevel_t debugLevel_p ); 110 | 111 | /// Init function. 112 | /// 113 | /// This function must be called in the init section of your system. 114 | /// It will generate a balanced binary tree structure from the data tree. 115 | /// @returns If anything goes well, it will return true. If something wrong 116 | /// happens during the tree generation, it will return false. 117 | bool init(); 118 | 119 | /// Overload for indexing operator 120 | /// 121 | /// The indexing operator is overloaded and it can be used 122 | /// with an integer to get a pointer to the data tree element. 123 | /// The indexing is protected against wrong addressing. 124 | /// @param i Index of the element in the tree. If the index is 125 | /// incorrect, the returned value will be NULL. 126 | /// @returns The address of the indexed element. 127 | dataRecord_t* operator [] ( int i ); 128 | 129 | /// Overload for indexing operator 130 | /// 131 | /// The indexing operator is overloaded and it can be used 132 | /// with a string to get a pointer to an element by its 133 | /// name parameter. If it is not found, NULL will be returned. 134 | /// @param name The name of the searched element. 135 | /// @returns The address of the element which has the 136 | /// same name as the name parameter. 137 | dataRecord_t* operator [] ( const char* name ); 138 | 139 | /// Find array index by element place. 140 | /// 141 | /// This function returns the array index( from 1 to tree size ) 142 | /// of an element by its place variable. It can be handy because the 143 | /// actual place not represents the real index in the binary tree. 144 | /// @param place Place of the searched element. 145 | /// @returns The array index of the element if the place is found. 146 | /// If the place is invalid or not found, the returned value 147 | /// will be 0 to avoid bad addressing. 148 | uint16_t findIndexByPlace( uint16_t place ); 149 | 150 | /// Get tree size 151 | /// 152 | /// You can get the size of the tree wit this function. 153 | /// @returns The size of the tree in elements. 154 | uint16_t getSize(); 155 | 156 | int completeFragment( const char* fragment, char* buffer, int buffer_size ); 157 | 158 | protected: 159 | /// Starting address of the API-tree. 160 | dataRecord_t* dataTree = NULL; 161 | 162 | /// Number of elements in the API-tree. 163 | uint16_t dataTreeSize = 0; 164 | 165 | /// Pointer to a Stream object, which will be used as debug output. 166 | Stream* debugChannel = NULL; 167 | 168 | /// Debug level. 169 | debugLevel_t debugLevel = DEBUG_OFF; 170 | 171 | /// If initialised correctly, it will be true. 172 | bool initFlag = false; 173 | 174 | /// Function pointer to an internal strcmp like function. 175 | /// It uses the regular version by default. 176 | int( CommanderDatabase::*strcmpElementElement )( dataRecord_t* element1, dataRecord_t* element2 ); 177 | 178 | /// Function pointer to an internal strcmp like function. 179 | /// It uses the regular version by default. 180 | int( CommanderDatabase::*strcmpElementCharArray )( dataRecord_t* element1, const char* element2 ); 181 | 182 | /// Function pointer to an internal strcmp like function. 183 | /// It uses the regular version by default. 184 | int( CommanderDatabase::*strncmpElementElement )( dataRecord_t* element1, dataRecord_t* element2, size_t num ); 185 | 186 | /// Function pointer to an internal strncmp like function. 187 | /// It uses the regular version by default. 188 | int( CommanderDatabase::*strncmpElementCharArray )( dataRecord_t* element1, const char* element2, size_t num ); 189 | 190 | int strcmpElementElementRegular( dataRecord_t* element1, dataRecord_t* element2 ); 191 | 192 | int strcmpElementCharArrayRegular( dataRecord_t* element1, const char* element2 ); 193 | 194 | int strncmpElementElementRegular( dataRecord_t* element1, dataRecord_t* element2, size_t num ); 195 | 196 | int strncmpElementCharArrayRegular( dataRecord_t* element1, const char* element2, size_t num ); 197 | 198 | void swapElements( uint16_t a, uint16_t b ); 199 | 200 | bool optimizeDataTree(); 201 | 202 | /// Custom strcmp function. 203 | /// 204 | /// It works like the original strcmp, but it terminates 205 | /// to space character as well. 206 | int strcmpDB( const char *p1, const char* p2 ); 207 | 208 | int strncmpDB( const char *p1, const char* p2, size_t num ); 209 | 210 | friend class CommanderDatabaseUT; 211 | 212 | }; 213 | 214 | template< typename T > 215 | CommanderDatabase< T >::CommanderDatabase(){ 216 | 217 | dataTree = NULL; 218 | dataTreeSize = 0; 219 | debugChannel = NULL; 220 | debugLevel = DEBUG_OFF; 221 | initFlag = false; 222 | strcmpElementElement = &CommanderDatabase::strcmpElementElementRegular; 223 | strcmpElementCharArray = &CommanderDatabase::strcmpElementCharArrayRegular; 224 | 225 | strncmpElementElement = &CommanderDatabase::strncmpElementElementRegular; 226 | strncmpElementCharArray = &CommanderDatabase::strncmpElementCharArrayRegular; 227 | } 228 | 229 | template< typename T > 230 | CommanderDatabase< T >::CommanderDatabase( struct dataRecord_t* dataTree_p, uint16_t dataTreeSize_p ){ 231 | 232 | dataTree = dataTree_p; 233 | dataTreeSize = dataTreeSize_p; 234 | debugChannel = NULL; 235 | debugLevel = DEBUG_OFF; 236 | initFlag = false; 237 | strcmpElementElement = &CommanderDatabase::strcmpElementElementRegular; 238 | strcmpElementCharArray = &CommanderDatabase::strcmpElementCharArrayRegular; 239 | 240 | strncmpElementElement = &CommanderDatabase::strncmpElementElementRegular; 241 | strncmpElementCharArray = &CommanderDatabase::strncmpElementCharArrayRegular; 242 | } 243 | 244 | template< typename T > 245 | void CommanderDatabase< T >::attachDebugChannel( Stream* debugChannel_p ){ 246 | debugChannel = debugChannel_p; 247 | } 248 | 249 | template< typename T > 250 | void CommanderDatabase< T >::setDebugLevel( debugLevel_t debugLevel_p ){ 251 | debugLevel = debugLevel_p; 252 | } 253 | 254 | template< typename T > 255 | int CommanderDatabase< T >::strcmpElementElementRegular( dataRecord_t* element1, dataRecord_t* element2 ){ 256 | return strcmpDB( element1 -> name, element2 -> name ); 257 | } 258 | 259 | template< typename T > 260 | int CommanderDatabase< T >::strcmpElementCharArrayRegular( dataRecord_t* element1, const char* element2 ){ 261 | return strcmpDB( element1 -> name, element2 ); 262 | } 263 | 264 | template< typename T > 265 | int CommanderDatabase< T >::strncmpElementElementRegular( dataRecord_t* element1, dataRecord_t* element2, size_t num ){ 266 | return strncmpDB( element1 -> name, element2 -> name, num ); 267 | } 268 | 269 | template< typename T > 270 | int CommanderDatabase< T >::strncmpElementCharArrayRegular( dataRecord_t* element1, const char* element2, size_t num ){ 271 | return strncmpDB( element1 -> name, element2, num ); 272 | } 273 | 274 | template< typename T > 275 | uint16_t CommanderDatabase< T >::findIndexByPlace( uint16_t place ){ 276 | 277 | // Generic counter variable 278 | uint16_t i; 279 | 280 | // Go through all commands 281 | for( i = 0; i < dataTreeSize; i++ ){ 282 | 283 | // Check that if we found the desired command 284 | if( dataTree[ i ].place == place ){ 285 | 286 | // If we found it, return the index of it. 287 | return i; 288 | 289 | } 290 | 291 | } 292 | 293 | return 0; 294 | 295 | } 296 | 297 | 298 | template< typename T > 299 | void CommanderDatabase< T >::swapElements( uint16_t a, uint16_t b ){ 300 | 301 | // Buffer that will temporary hold an element. 302 | // This is required for a swap. 303 | dataRecord_t buffer; 304 | 305 | // Protect against overflow. 306 | if( a >= dataTreeSize ){ 307 | return; 308 | } 309 | 310 | // Protect against overflow. 311 | if( b >= dataTreeSize ){ 312 | return; 313 | } 314 | 315 | buffer = dataTree[ a ]; 316 | dataTree[ a ] = dataTree[ b ]; 317 | dataTree[ b ] = buffer; 318 | 319 | } 320 | 321 | template< typename T > 322 | bool CommanderDatabase< T >::optimizeDataTree(){ 323 | 324 | // Generic counter. 325 | uint16_t i; 326 | 327 | // Generic counter. 328 | uint16_t j; 329 | 330 | // This variable will be used to find the 331 | // depth of the required binary tree. 332 | uint16_t levelMask; 333 | 334 | // This variable will store how many bits needs 335 | // to be shifted for the specific levels in the tree. 336 | uint8_t shiftIndex; 337 | 338 | // This variable will track how many possible leafs are 339 | // in the current level. 340 | uint16_t levelElements; 341 | 342 | // The index of the actual element( leaf ) will be calculated 343 | // to this variable. 344 | uint16_t elementIndex; 345 | 346 | // This variable tracks how many elements are processed. 347 | uint16_t elementCounter; 348 | 349 | // Stores the next elements address in the tree 350 | dataRecord_t *next; 351 | 352 | // Stores the previous elements address in the tree 353 | dataRecord_t *prev; 354 | 355 | // It will store string comparison result 356 | int comp_res; 357 | 358 | // Create a mask for the MSB only( in 16-bit domain ). 359 | levelMask = 0x8000; 360 | for( i = 0; i < 32; i++ ){ 361 | 362 | // Check if we found the firs bit, that is not zero. 363 | if( ( dataTreeSize & levelMask ) != 0 ){ 364 | break; 365 | } 366 | 367 | // Shift to right one bit. 368 | levelMask >>= 1; 369 | 370 | } 371 | 372 | // Set the siftIndex to 1 by default. 373 | shiftIndex = 1; 374 | 375 | // Find how many bits are used for the tree and 376 | // store the MSB index to the shiftIndex variable. 377 | for( i = 0; i < 32; i++ ){ 378 | 379 | // Check if we found the firs bit, that is not zero. 380 | if( ( levelMask & ( (uint16_t)1 << i ) ) != 0 ){ 381 | break; 382 | } 383 | 384 | shiftIndex++; 385 | 386 | } 387 | 388 | levelElements = 1; 389 | elementCounter = 0; 390 | 391 | for( i = levelMask; i != 0; i >>= 1 ){ 392 | 393 | // i variable will be the actual start index for the current level. 394 | for( j = 0; j < levelElements; j++ ){ 395 | 396 | elementIndex = i | ( j << shiftIndex ); 397 | 398 | if( ( debugChannel != NULL ) && ( debugLevel == DEBUG_VERBOSE ) ){ 399 | debugChannel -> print( __CONST_TXT__( " level: " ) ); 400 | debugChannel -> print( i, BIN ); 401 | debugChannel -> print( __CONST_TXT__( " idx: " ) ); 402 | debugChannel -> print( elementIndex ); 403 | } 404 | 405 | if( elementIndex > dataTreeSize ){ 406 | if( ( debugChannel != NULL ) && ( debugLevel == DEBUG_VERBOSE ) ){ 407 | debugChannel -> println( __CONST_TXT__( " skip" ) ); 408 | } 409 | continue; 410 | } 411 | 412 | swapElements( elementCounter, findIndexByPlace( elementIndex ) ); 413 | 414 | // Create the connection for the parent leaf. 415 | if( elementCounter > 0 ){ 416 | 417 | // The first element is the starting node. 418 | prev = &dataTree[ 0 ]; 419 | 420 | // Compare the next leafs name with the new items name. 421 | comp_res = ( this ->* strcmpElementElement )( prev, &dataTree[ elementCounter ] ); 422 | 423 | // Check if we found a match. 424 | if( comp_res == 0 ){ 425 | // This is a problem, because each element has to be unique. 426 | return false; 427 | } 428 | 429 | // Otherwise check which leaf will be the next. 430 | (comp_res > 0) ? (next = (prev->left)) : ( next = (prev->right)); 431 | 432 | while( next != NULL ){ 433 | 434 | prev = next; 435 | 436 | // Compare the next leafs name with the new items name. 437 | comp_res = ( this ->* strcmpElementElement )( prev, &dataTree[ elementCounter ] ); 438 | 439 | // Check if we found a match. 440 | if( comp_res == 0 ){ 441 | // This is a problem, because each element has to be unique. 442 | return false; 443 | } 444 | 445 | // Otherwise check which leaf will be the next. 446 | (comp_res > 0) ? (next = (prev->left)) : ( next = (prev->right)); 447 | 448 | } 449 | 450 | // Check which side will be used to store the new item. 451 | if( comp_res > 0 ){ 452 | prev -> left = &dataTree[ elementCounter ]; 453 | } 454 | 455 | else if( comp_res < 0 ){ 456 | prev -> right = &dataTree[ elementCounter ]; 457 | } 458 | 459 | // Check if we found a match. 460 | else{ 461 | // This is a problem, because each element has to be unique. 462 | return false; 463 | } 464 | 465 | if( ( debugChannel != NULL ) && ( debugLevel == DEBUG_VERBOSE ) ){ 466 | debugChannel -> print( __CONST_TXT__( " parent: " ) ); 467 | debugChannel -> println( prev -> place ); 468 | } 469 | 470 | //( comp_res > 0 ) ? ( ( prev->left ) = &API_tree[ elementCounter ] ) : ( ( prev->right ) = &API_tree[ elementCounter ] ); 471 | 472 | } 473 | 474 | else if( ( debugChannel != NULL ) && ( debugLevel == DEBUG_VERBOSE ) ){ 475 | debugChannel -> println( __CONST_TXT__(" ROOT") ); 476 | } 477 | 478 | elementCounter++; 479 | 480 | } 481 | 482 | levelElements <<= 1; 483 | shiftIndex--; 484 | 485 | } 486 | 487 | return true; 488 | 489 | } 490 | 491 | template< typename T > 492 | bool CommanderDatabase< T >::init(){ 493 | 494 | // Generic counter variables. 495 | uint32_t i; 496 | uint32_t j; 497 | 498 | // Temporary variable, used to flip elements. 499 | dataRecord_t temp; 500 | 501 | bool swapped; 502 | int compareResult; 503 | 504 | if( ( debugChannel != NULL ) && ( debugLevel >= DEBUG_DEBUG ) ){ 505 | debugChannel -> println( __CONST_TXT__( "Database init start" ) ); 506 | debugChannel -> println( __CONST_TXT__( "Creating alphabetical order..." ) ); 507 | } 508 | 509 | // Simple bubble sort. 510 | // We need to sort the elements into an alphabetical order. 511 | for( i = 0; i < (uint16_t)( dataTreeSize - 1 ); i++ ){ 512 | swapped = false; 513 | for( j = 0; j < (uint16_t)( dataTreeSize - i - 1 ); j++ ){ 514 | 515 | compareResult = ( this ->* strcmpElementElement )( &dataTree[ j ], &dataTree[ j + 1 ] ); 516 | 517 | // Every element has to have an unique name. 518 | if( compareResult == 0 ){ 519 | return false; 520 | } 521 | 522 | if( compareResult < 0 ){ 523 | 524 | temp = dataTree[ j ]; 525 | dataTree[ j ] = dataTree[ j + 1 ]; 526 | dataTree[ j + 1 ] = temp; 527 | swapped = true; 528 | 529 | } 530 | 531 | } 532 | 533 | if( !swapped ){ 534 | break; 535 | } 536 | 537 | } 538 | 539 | // Fill the place variable in the elements with 540 | // correct alphabetical place. The place has to 541 | // be started from index of 1! 542 | for( i = 0; i < dataTreeSize; i++ ){ 543 | 544 | dataTree[ i ].place = i + 1; 545 | if( ( debugChannel != NULL ) && ( debugLevel == DEBUG_VERBOSE ) ){ 546 | debugChannel -> print( __CONST_TXT__( " " ) ); 547 | debugChannel -> print( i ); 548 | debugChannel -> print( __CONST_TXT__( ".\tname: " ) ); 549 | // todo AVR support! 550 | debugChannel -> println( dataTree[ i ].name ); 551 | } 552 | 553 | } 554 | 555 | if( ( debugChannel != NULL ) && ( debugLevel >= DEBUG_DEBUG ) ){ 556 | debugChannel -> println( __CONST_TXT__( "Prepare leaf connections..." ) ); 557 | } 558 | 559 | // Prepare all leaf connections. 560 | for( i = 0; i < dataTreeSize; i++ ){ 561 | dataTree[ i ].left = NULL; 562 | dataTree[ i ].right = NULL; 563 | } 564 | 565 | // Optimize the tree to make it balanced. 566 | // It is necessary to speed up the command 567 | // search phase. 568 | if( ( debugChannel != NULL ) && ( debugLevel >= DEBUG_DEBUG ) ){ 569 | debugChannel -> println( __CONST_TXT__( "Create balanced binary structure... " ) ); 570 | } 571 | 572 | initFlag = optimizeDataTree(); 573 | 574 | return initFlag; 575 | 576 | } 577 | 578 | 579 | template< typename T > 580 | typename CommanderDatabase< T >::dataRecord_t* CommanderDatabase< T >::operator [] ( int i ){ 581 | 582 | // Detect wrong addressing. 583 | if( ( i < 0 ) || ( i >= (int)dataTreeSize ) ){ 584 | return NULL; 585 | } 586 | 587 | return &dataTree[ i ]; 588 | 589 | } 590 | 591 | template< typename T > 592 | typename CommanderDatabase< T >::dataRecord_t* CommanderDatabase< T >::operator [] ( const char* name ){ 593 | 594 | // Stores the next elements address in the tree 595 | dataRecord_t *next; 596 | 597 | // Stores the previous elements address in the tree 598 | dataRecord_t *prev; 599 | 600 | // It will store string compersation result 601 | int comp_res; 602 | 603 | if( initFlag == false ){ 604 | return NULL; 605 | } 606 | 607 | // Thee roo node will be the first element. 608 | prev = &dataTree[ 0 ]; 609 | 610 | comp_res = ( this ->* strcmpElementCharArray )( prev, name ); 611 | 612 | (comp_res > 0) ? (next = (prev->left)) : ( next = (prev->right)); 613 | 614 | // Go through the binary tree until you find a match, or until you find the 615 | // end of the tree. 616 | while( ( comp_res != 0 ) && ( next != NULL ) ){ 617 | 618 | prev = next; 619 | comp_res = ( this ->* strcmpElementCharArray )( prev, name ); 620 | (comp_res > 0) ? (next = (prev->left)) : ( next = (prev->right)); 621 | 622 | } 623 | 624 | // If comp_res variable has a zero in it, that means in the last iteration 625 | // we had a match. 626 | if( comp_res == 0 ){ 627 | 628 | return prev; 629 | 630 | } 631 | 632 | // If we did not found the command we return NULL. 633 | return NULL; 634 | 635 | } 636 | 637 | template< typename T > 638 | uint16_t CommanderDatabase< T >::getSize(){ 639 | return dataTreeSize; 640 | } 641 | 642 | template< typename T > 643 | int CommanderDatabase< T >::strcmpDB( const char *p1, const char* p2 ){ 644 | const unsigned char *s1 = (const unsigned char *) p1; 645 | const unsigned char *s2 = (const unsigned char *) p2; 646 | 647 | unsigned char c1; 648 | unsigned char c2; 649 | 650 | do{ 651 | c1 = (unsigned char) *s1; 652 | c2 = (unsigned char) *s2; 653 | 654 | s1++; 655 | s2++; 656 | 657 | if( ( c1 == ' ' ) || ( c2 == ' ' ) ){ 658 | c1 = '\0'; 659 | c2 = '\0'; 660 | } 661 | 662 | if( c1 == '\0' ){ 663 | return c2 - c1; 664 | } 665 | 666 | } while( c1 == c2 ); 667 | 668 | return c2 - c1; 669 | 670 | } 671 | 672 | template< typename T > 673 | int CommanderDatabase< T >::strncmpDB( const char *p1, const char* p2, size_t num ){ 674 | const unsigned char *s1 = (const unsigned char *) p1; 675 | const unsigned char *s2 = (const unsigned char *) p2; 676 | 677 | unsigned char c1; 678 | unsigned char c2; 679 | 680 | size_t counter = 0; 681 | 682 | do{ 683 | c1 = (unsigned char) *s1; 684 | c2 = (unsigned char) *s2; 685 | 686 | s1++; 687 | s2++; 688 | 689 | if( ( c1 == ' ' ) || ( c2 == ' ' ) ){ 690 | c1 = '\0'; 691 | c2 = '\0'; 692 | } 693 | 694 | if( c1 == '\0' ){ 695 | return c2 - c1; 696 | } 697 | 698 | counter++; 699 | if( counter >= num ){ 700 | return c2 - c1; 701 | } 702 | 703 | } while( c1 == c2 ); 704 | 705 | return c2 - c1; 706 | 707 | } 708 | 709 | template< typename T > 710 | int CommanderDatabase< T >::completeFragment( const char* fragment, char* buffer, int buffer_size ){ 711 | 712 | // Stores the next elements address in the tree 713 | dataRecord_t *next; 714 | 715 | // Stores the previous elements address in the tree 716 | dataRecord_t *prev; 717 | 718 | // It will store string compersation result 719 | int comp_res; 720 | 721 | int fragment_len; 722 | 723 | if( initFlag == false ){ 724 | return 0; 725 | } 726 | 727 | if( fragment[ 0 ] == '\0' ){ 728 | return 0; 729 | } 730 | 731 | fragment_len = strlen( fragment ); 732 | 733 | // Thee root node will be the first element. 734 | prev = &dataTree[ 0 ]; 735 | 736 | comp_res = ( this ->* strncmpElementCharArray )( prev, fragment, fragment_len ); 737 | if( comp_res == 0 ){ 738 | strncpy( buffer, prev -> name, buffer_size ); 739 | return 1; 740 | } 741 | 742 | comp_res = ( this ->* strcmpElementCharArray )( prev, fragment ); 743 | (comp_res > 0) ? (next = (prev->left)) : ( next = (prev->right)); 744 | 745 | // Go through the binary tree until you find a match, or until you find the 746 | // end of the tree. 747 | while( ( comp_res != 0 ) && ( next != NULL ) ){ 748 | 749 | prev = next; 750 | 751 | comp_res = ( this ->* strncmpElementCharArray )( prev, fragment, fragment_len ); 752 | if( comp_res == 0 ){ 753 | strncpy( buffer, prev -> name, buffer_size ); 754 | return 1; 755 | } 756 | 757 | comp_res = ( this ->* strcmpElementCharArray )( prev, fragment ); 758 | (comp_res > 0) ? (next = (prev->left)) : ( next = (prev->right)); 759 | 760 | } 761 | 762 | // If comp_res variable has a zero in it, that means in the last iteration 763 | // we had a match. 764 | if( comp_res == 0 ){ 765 | 766 | strncpy( buffer, prev -> name, buffer_size ); 767 | return 1; 768 | 769 | } 770 | 771 | // If we did not found the command we return NULL. 772 | return 0; 773 | 774 | 775 | 776 | } 777 | 778 | 779 | 780 | #endif -------------------------------------------------------------------------------- /extras/Assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 39 | 56 | 73 | 90 | 107 | 124 | 141 | 158 | 175 | 192 | 209 | 226 | 243 | 250 | 251 | 278 | 280 | 281 | 283 | image/svg+xml 284 | 286 | 287 | 288 | 289 | 293 | 301 | 308 | 315 | 322 | 329 | 333 | 341 | 346 | 351 | 359 | 363 | 367 | 373 | 381 | 389 | 397 | 405 | 407 | 413 | 431 | 449 | 467 | 468 | 486 | 504 | 522 | C 533 | 534 | 535 | --------------------------------------------------------------------------------