diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b689707..9b0be497 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,11 @@ target_sources(${PROJECT_NAME} PRIVATE src/core/dev/RTCTimer.cpp src/core/dev/storage/M24C32.cpp src/core/dev/Thermistor.cpp - src/core/utils/log.cpp) + src/core/utils/log.cpp + src/core/utils/terminal/menu.cpp + src/core/utils/terminal/menuItem.cpp + src/core/utils/terminal/subMenu.cpp + src/core/utils/terminal/terminal.cpp) get_directory_property(COMPDEFS COMPILE_DEFINITIONS) if(COMPDEFS MATCHES "(.*)STM32F3xx(.*)") diff --git a/include/core/utils/terminal/menu.hpp b/include/core/utils/terminal/menu.hpp new file mode 100644 index 00000000..220f7da0 --- /dev/null +++ b/include/core/utils/terminal/menu.hpp @@ -0,0 +1,135 @@ +#ifndef EVT_TERM_MENU +#define EVT_TERM_MENU + +// macro for max initial item count of a main menu +#include +#include + +namespace core::utils { + + +class Menu : public MenuItem{ +public: + /** + * constructor for sub-menu sub-class + * @param parent same as menuitem + * @param term same as menuitem + * @param option same as menuitem + * @param text same as menuitem + * @param cb exit behavior callback, leave null for nothing + * @param ctx enterence behavior callback void*(leave null for nothing) + * @param items list of items in submenu + */ + SubMenu(Menu* parent, void* term, char* option, char* text, callback_t cb, void* ctx, MenuItem** items); + + /** + * unique overridden printStr() method for sub-menus + */ + void printStr(io::UART& uart); + + /** + * print method that displays the submenu like a menu, instead of an item + */ + void printMStr(io::UART& uart); + + /** + * unique overridden equals() method for sub-menus + * true if every attribute is equivalent, checks all but items with == + * items is checked the same way as menu equivalence + * @param sub2 the other submenu to compare to + */ + bool equals(SubMenu* sub2); + + /** + * returns itemCount + */ + int getCount() { + return itemCount; + } + + /** + * returns parent + */ + void* getParent() { + return parent; + } + + /** + * replaces current item list with provided one + * @param itms items to replace current list with + */ + void setItems(MenuItem** itms); + + /** + * returns a list of all items contained in the submenu + */ + MenuItem** getItems() { + return sitems; + } + + // # + // # USE THESE TO DO ANY NEEDED SETUP/CLEANUP WHEN ENTERING/EXITING A SUB MENU: + // # + + /** + * automatic callback executor for entering + * custom behavor is stored in ctx void* + * if cb is empty will do nothing + * @param uart uart instance to use for cb + * @param args arguments for cb + */ + void enter(io::UART& uart, char** args); + + /** + * automatic callback executor for exiting + * custom behavor is stored in cb. + * if ctx is nullptr will do nothing + * @param uart uart instance to use for cb + * @param args arguments for cb + */ + void exit(io::UART& uart, char** args); + +private: + /** + * key value for item, this is used to select it in your commands + */ + char* option; + + /** + * description/name of item + */ + char* text; + + /** + * pointer to callback method for this item + */ + callback_t cb; + + /** + * context for this item, void* because it is of an abstract type + */ + void* ctx; + /** + * the total number of items that can be contained in any sub-menu + */ + int itemCount = 10; + + /** + * list of all items inside of the sub-menu + */ + MenuItem** sitems; + + /** + * submenu or menu this item is in + * if nullptr the current node is the head + */ + Menu* parent; + + /** + * terminal this is in + */ + void* term; +}; +} // namespace core::utils + +#endif \ No newline at end of file diff --git a/include/core/utils/terminal/menuItem.hpp b/include/core/utils/terminal/menuItem.hpp new file mode 100644 index 00000000..0f4bab72 --- /dev/null +++ b/include/core/utils/terminal/menuItem.hpp @@ -0,0 +1,116 @@ +#ifndef EVT_TERM_MENUITEM +#define EVT_TERM_MENUITEM +#include +#include + +// macro for max initial item count of submenus + +// struct used for callback functions, a uart instance to communicate over, +// a list of input strings(your input from the terminal), and a void* +// mostly a placeholder to ease handling void* to a function, when you fill with a function make sure the parameters are +// (UART,char**,void*) + +namespace core::utils { +class MenuItem { +public: + /** + * constructor for menu item object + * takes a string for the option key, a string for the description text, + * a void pointer to the items callback method, and a void pointer to the items context + * @param parent pointer to parent node + * @param term pointer to terminal instance this item resides within + * @param option a string representing the items "key", the char/string used to select it + * @param text a short text description/name of an item + * @param cb a void pointer to this items callback method + * @param ctx a void pointer to any context information for this menu(if none provided, is NULL) + */ + MenuItem(void* parent, void* term, char* option, char* text, callback_t cb, void* ctx); + + /** + * option acessor + */ + char* getOption() { + return option; + } + + /** + * returns parent + */ + void* getParent() { + return parent; + } + + /** + * text acessor + */ + char* getText() { + return text; + } + + /** + * callback acessor + */ + callback_t getcb() { + return cb; + } + + /** + * replace this item with another one + * @param newItem the new item + */ + void replace(MenuItem* newItem); + + /** + * context acessor + */ + void* getctx() { + return ctx; + } + + /** + * print string representation + * @param uart the uart instance to print over + */ + void printStr(core::io::UART& uart); + + /** + * checks if 2 items are equivalent + * true if every attribute is equivalent using == + * @param it2 a different menu item to compare to + */ + bool equals(MenuItem* it2); + +private: + /** + * key value for item, this is used to select it in your commands + */ + char* option; + + /** + * description/name of item + */ + char* text; + + /** + * pointer to callback method for this item + */ + callback_t cb; + + /** + * context for this item, void* because it is of an abstract type + */ + void* ctx; + + /** + * submenu or menu this item is in + */ + void* parent; + + /** + * terminal this is in + */ + void* term; +}; +} // namespace core::utils + +#endif \ No newline at end of file diff --git a/include/core/utils/terminal/terminal.hpp b/include/core/utils/terminal/terminal.hpp new file mode 100644 index 00000000..a209a18a --- /dev/null +++ b/include/core/utils/terminal/terminal.hpp @@ -0,0 +1,21 @@ +#ifndef EVT_TERM_STRUCTS +#define EVT_TERM_STRUCTS +#include + +struct TerminalInterface +{ + //fill with list full of pointers to terminalManager functions to circumvent + void** funclist; + + FunctionRunner(void** funclist): funclist(funclist) {} + + void runAll() + { + + } +}; + +using callback_t = void (*)(core::io::UART&, char** inputList, void*); + + +#endif \ No newline at end of file diff --git a/include/core/utils/terminal/terminalManager.hpp b/include/core/utils/terminal/terminalManager.hpp new file mode 100644 index 00000000..d8e78185 --- /dev/null +++ b/include/core/utils/terminal/terminalManager.hpp @@ -0,0 +1,101 @@ +#ifndef EVT_TERMINAL +#define EVT_TERMINAL + +#include +#include +#include +#include +#include +#include + +namespace core::utils { + +class TerminalManager { +public: + /** + * constructor for terminal object + * takes a uart object and sets up the command line terminal + * @param uart an instance of a UART object + * @param menu head node of terminal structure + */ + Terminal(io::UART& uart, Menu* menu); + + /** + * returns uart instance used + */ + io::UART& getUART() { + return uart; + } + + /** + * returns main menu + */ + Menu* getMenu() { + return menu; + } + + /** + * enters the given submenu + * @param uart the uart instance to use + * @param args the arguments provided, used for enter callback, first index is still option string + * @param term void pointer to the terminal this is being done inside of(optional to leave as nullptr, just here to + * be used with callbacks) + */ + void enterSub(io::UART& uart, char** args, void* term); + /** + * sets current menu/submenu to provided value + * @param sub the submenu to replace the current one with + */ + void setMenu(SubMenu* sub); + + /** + * checks if the terminal is still on the main menu + * OK FOR NOW< MAYBE MAKE IT DETECT WHEN HEAD INSTED OF USING A FLAG + */ + bool isMain() { + return m; + } + + /** + * Sends a provided message over UART + * also replaces menu with provided menu + * used to reset menu to desired state and notify user + * @param message a string message to send via UART + * @param menu the menu to replace the current menu with + */ + void update(char* message, utils::Menu menu); + + /** + * proccesses incoming UART messages + * @param holder list to store tokenized strings in, should be full of "/0" to start + */ + bool recieve(char** holder); + + /** + * processes chosen menu item and runs its callback(if exit, exits into higher menu) + * @param tag the user input key for the item + */ + void process(char* tag, char** args); + + // TERMINAL specific print function + void printTerm(); + +private: + // menu instance + Menu* menu; + + // current submenu, if there is one, nullptr otherwise + Menu* current; + + // true if currently in main menu + bool m; + + // uart instance this terminal is using + core::io::UART& uart; + + // char buffer for UART entry + char buffer[99]; +}; +} // namespace core::utils + +#endif \ No newline at end of file diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 2949ab70..10f9f486 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -23,3 +23,4 @@ add_subdirectory(rtos) add_subdirectory(thermistor) add_subdirectory(timer) add_subdirectory(spi) +add_subdirectory(terminal) diff --git a/samples/terminal/CMakeLists.txt b/samples/terminal/CMakeLists.txt new file mode 100644 index 00000000..8429e984 --- /dev/null +++ b/samples/terminal/CMakeLists.txt @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/evt-core_build.cmake) + +make_exe(terminal main.cpp) diff --git a/samples/terminal/main.cpp b/samples/terminal/main.cpp new file mode 100644 index 00000000..74de8487 --- /dev/null +++ b/samples/terminal/main.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace io = core::io; +namespace utils = core::utils; + +// example callback that prints a test message +void printCB(io::UART& uart, char** inputList, void* term) { + uart.printf("\n\rTest Message\n\r"); +} + +// example callback that echos user input(called send because e char is taken by exit) +void sendCB(io::UART& uart, char** inputList, void* term) { + uart.printf("\n\r"); + for (int i = 1; i < 10; i++) { + uart.printf(inputList[i]); + uart.printf(" "); + } +} + +// example submenu entercb +void subCB(io::UART& uart, char** inputList, void* term) { + // enters submenu with no other behavior + utils::Terminal* t = (utils::Terminal*) term; + t->enterSub(uart, inputList, term); +} + +int main() { + // core setup + core::platform::init(); + + // UART setup + io::UART& uart = io::getUART(9600); + + // item list end item + utils::MenuItem nul = utils::MenuItem(nullptr, nullptr, "null", "LIST END\n\r", nullptr, nullptr); + + // make some item lists + utils::MenuItem* items[10]; + + // this is really optional + for (int i = 0; i < 10; i++) { + items[i] = nullptr; + } + + // list of submenu items + utils::MenuItem* subs[3]; + + // make empty main menu and terminal containing it + + utils::Menu menu = utils::Menu(items); + utils::Terminal term = utils::Terminal(uart, &menu); + + // void* to both for use in thier nodes + void* m = &menu; + void* t = &term; + + // make items to fill terminal with + // a nullptr in the head parameter means it is part of the main menu + utils::MenuItem mainPrint = utils::MenuItem(nullptr, t, "p", "print test\n\r", printCB, nullptr); + utils::MenuItem mainSend = utils::MenuItem(nullptr, t, "s", "echo\n\r", sendCB, nullptr); + + /** + * submenu cb is exit cb struct, ctx is void* to enter function cb struct/function. + * these are NOT responsible for moving menus, just for operations done duirng a menu move + * this example: when we enter sub, subCB is executed, this is a cb defined in main that just runs enterCB, this is + * just the bare minimum but it ideally would also contain your custom enterence behavior when we exit, ctx is + * nullptr so only the normal submenu exit is used this could also be a void* to a cb struct/function + */ + utils::SubMenu sub = utils::SubMenu(nullptr, t, "sb", "sub menu\n\r", subCB, nullptr, subs); + void* s = ⊂ + + // more example menuitems, this time for the submenu + utils::MenuItem subPrint = utils::MenuItem(s, t, "p", "print test\n\r", printCB, nullptr); + utils::MenuItem subSend = utils::MenuItem(s, t, "s", "echo\n\r", sendCB, nullptr); + + // filling a submenu + subs[0] = &subPrint; + subs[1] = &subSend; + subs[2] = &nul; + + // set submenu to updated list + sub.setItems(subs); + + // add main menu items to main menu + items[0] = &mainPrint; + items[1] = &mainSend; + items[2] = ⊂ + items[3] = &nul; + + // set menu items to the updated list + menu.newItems(items); + + // usage instructions to print before terminal fully starts + uart.printf("Usage: flagChar argument argument | max 9 arguments\n\r"); + + // forever while loop to constantly wait for user input inside of recieve() + // create empty list of strings + char* inputList[10]; + + // empty string to store input for item selection + char* tag; + + while (true) { + // print current state of terminal + term.printTerm(); + + // recieve input + term.recieve(inputList); + + // set tag to inputted selection string + tag = inputList[0]; + + // process input, this uses input to find chosen item, and execute/enter it + term.process(tag, inputList); + } + + // this should never happen + return 0; +} \ No newline at end of file diff --git a/src/core/utils/terminal/menu.cpp b/src/core/utils/terminal/menu.cpp new file mode 100644 index 00000000..f3391411 --- /dev/null +++ b/src/core/utils/terminal/menu.cpp @@ -0,0 +1,70 @@ +#include +#include + +namespace core::utils { +SubMenu::SubMenu(void* parent, void* term, char* option, char* text, callback_t cb, void* ctx, MenuItem** sitems) + : parent(parent), term(term), option(option), text(text), cb(cb), ctx(ctx), sitems(sitems), + MenuItem(parent, term, option, text, cb, ctx) { + // constructor +} + +void SubMenu::printStr(core::io::UART& uart) { + // this is exclusively for displaying the submenu inside of a menu list + // option | text + uart.printf(option); + uart.printf("|"); + uart.printf(text); +} + +void SubMenu::printMStr(core::io::UART& uart) { + // used for printing this as the current menu state + // prints all items in list until it hits one with option string "null" + for (int c = 0; c < itemCount; c++) { + if (sitems[c]->getOption() == "null") { + return; + } + sitems[c]->printStr(uart); + } +} + +void SubMenu::setItems(MenuItem** itms) { + // replaces corresponding items in sitems with itms + // used to replace item list, or certain items by inputing an altered item list + for (int i = 0; i < itemCount; i++) { + sitems[i] = itms[i]; + } +} + +bool SubMenu::equals(SubMenu* sub2) { + // check equivalence of attributes besides items + // this section is identical to menuItem + char* option2 = sub2->getOption(); + char* text2 = sub2->getText(); + callback_t cb2 = sub2->getcb(); + void* ctx2 = sub2->getctx(); + MenuItem** items2 = sub2->getItems(); + if (option != option2 || text != text2 || cb != cb2 || ctx != ctx2) { + return false; + } + + // Check items equivalence; + for (int i = 0; i < itemCount; i++) { + if (!(sitems[i]->equals(items2[i]))) { + return false; + } + } + return true; +} + +void SubMenu::enter(io::UART& uart, char** args) { + cb(uart, args, term); +} + +void SubMenu::exit(io::UART& uart, char** args) { + // if ctx has value use it as a callback + if (ctx) { + callback_t cb = (callback_t) ctx; + cb(uart, args, term); + } +} +} // namespace core::utils \ No newline at end of file diff --git a/src/core/utils/terminal/menuItem.cpp b/src/core/utils/terminal/menuItem.cpp new file mode 100644 index 00000000..73ae15c5 --- /dev/null +++ b/src/core/utils/terminal/menuItem.cpp @@ -0,0 +1,37 @@ +#include +#include +namespace core::utils { + +MenuItem::MenuItem(void* parent, void* term, char* option, char* text, callback_t cb, void* ctx) + : parent(parent), term(term), option(option), text(text), cb(cb), ctx(ctx) { + // constructor +} + +void MenuItem::printStr(core::io::UART& uart) { + // print in form of op | text + uart.printf(option); + uart.printf("|"); + uart.printf(text); +} + +void MenuItem::replace(MenuItem* newItem) { + // replace with new items info + option = newItem->getOption(); + text = newItem->getText(); + cb = newItem->getcb(); + ctx = newItem->getctx(); +} + +bool MenuItem::equals(MenuItem* it2) { + // get option string, text, cb, and ctx of 2nd item + char* option2 = it2->getOption(); + char* text2 = it2->getText(); + callback_t cb2 = it2->getcb(); + void* ctx2 = it2->getctx(); + // if any corresponding arent equal, return false + if (option != option2 || text != text2 || cb != cb2 || ctx != ctx2) { + return false; + } + return true; +} +} // namespace core::utils \ No newline at end of file diff --git a/src/core/utils/terminal/terminalManager.cpp b/src/core/utils/terminal/terminalManager.cpp new file mode 100644 index 00000000..da2d536e --- /dev/null +++ b/src/core/utils/terminal/terminalManager.cpp @@ -0,0 +1,150 @@ +/** + * + */ +#include +#include +#include +#include +#include +#include + +namespace io = core::io; + +namespace core::utils { +Terminal::TerminalManager(io::UART& uart, utils::Menu* menu) : menu(menu), uart(uart) { + // start message + uart.printf("\n\rStarting Terminal...\n\r"); + // set main menu flag to true + m = true; +} + +void Terminal::update(char* message, utils::Menu m) { + // replace menu with provided + menu->replace(m); + // print spacer lines + for (int i = 0; i < 5; i++) { + uart.printf("\n\r"); + } + // print + uart.printf(message); +} + +void Terminal::setCurrent(SubMenu* sub) { + // sets current to input, sets main menu flag to false + current = sub; + m = false; +} + +bool Terminal::recieve(char* holder[10]) { + // fill buffer with null chars + for (int i = 0; i < 99; i++) { + buffer[i] = '\0'; + } + // fill buffer + uart.gets(buffer, 99); + + // tokenize + holder[0] = strtok(buffer, " "); + for (int i = 1; i < 10; i++) { + holder[i] = strtok(NULL, " "); + } + // return true, this is placeholder for ensuring a complete message + return true; + // for(int i = 0; i < 10; i++) + // { + // holder[i] = argN[i]; + // } +} + +// TERMINAL specific print function +void Terminal::printTerm() { + // print header and some info + uart.printf("\n\r"); + uart.printf("Terminal:\n\r"); + // uart.printf("is main: "); + // if (m) { + // uart.printf("true\n\r"); + // } else { + // uart.printf("false\n\r"); + // } + uart.printf(current->getText()); //this will eventually be changed to brint the nodes above current up to the head as well like head>between>current + uart.printf("===================\n\r"); + // print menu + if (m) { + menu->printStr(uart); + } + // or submenu + else { + current->printMStr(uart); + uart.printf("q|QUIT\n\r"); + } +} + +void Terminal::enterSub(io::UART& uart, char** args, void* term) { + utils::MenuItem** items = menu->getItems(); + char* name = args[0]; + int c = menu->getCount(); + utils::MenuItem* item; + // find chosen item + for (int i = 0; i < c; i++) { + item = items[i]; + if (strcmp(name, item->getOption()) == 0) { + // cast item to submenu and place in current + setCurrent((utils::SubMenu*) item); + break; + } + } +} + +void Terminal::process(char* tag, char** args) { + // different behavior for menu and submenu + utils::MenuItem* holder; + if (m) { + // find item in menu + utils::MenuItem** subitemsM = menu->getItems(); + + for (int i = 0; i < menu->getCount(); i++) { + char* op = subitemsM[i]->getOption(); + + if (strcmp(op, "null") == 0) { + holder = nullptr; + break; + } else if (strcmp(tag, op) == 0) { + holder = (subitemsM[i]); + break; + } + } + } else { + // find item in submenu + utils::MenuItem** subitemsC = current->getItems(); + + for (int i = 0; i < menu->getCount(); i++) { + char* op = subitemsC[i]->getOption(); + if (strcmp(op, "null") == 0) { + holder = nullptr; + break; + } + if (strcmp(tag, op) == 0) { + holder = (subitemsC[i]); + break; + } + } + } + + // check if quit flag, if quit set main menu flag to true + // this brings us back to main menu, current sill still have a value but this gets overwritten next menu move + if (strcmp(tag, "q") == 0) { + m = true; + } + + // if quit flag, kick out of process, + if (holder == nullptr || strcmp(holder->getOption(), "q") == 0) { + return; + } + + // get and run callback of chosen item + callback_t cb = holder->getcb(); + + cb(uart, args, (void*) this); +} +} // namespace core::utils