Library which creates a serial test console, it supports pages and menu items. The items are added and the pages are added as necessary when the user sets it up. This is a great too for creating an easy to maintain menu system, whether for a test sytem, or anything else.
Dependencies: Terminal
Revision 0:907d2d5e77f7, committed 2015-05-01
- Comitter:
- glansberry
- Date:
- Fri May 01 03:56:34 2015 +0000
- Child:
- 1:c6deb449c132
- Commit message:
- Planned Menu is constructed, needs callbacks
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/menuitem.cpp Fri May 01 03:56:34 2015 +0000 @@ -0,0 +1,33 @@ +#include "menuitem.h" + + +MenuItem::MenuItem() : + Name(NULL), + level(0), + type(display), + callback(NULL), + name_len(0), + data_col(0), + target_page(-1) {} + + + +MenuItem::MenuItem(const char * Name_p, callback_function callback_p, int level_p, MenuType type_p, int target_page_p) + :Name(Name_p), level(level_p), type(type_p), target_page(target_page_p){ + callback = callback_p; + name_len = strlen(Name); + + } + +MenuItem::MenuItem(const char * Name_p, int target_page_p ): + Name(Name_p), + level(0), + type(menu), + callback(NULL), + data_col(0), + target_page(target_page_p) + { + name_len = strlen(Name); + + + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/menuitem.h Fri May 01 03:56:34 2015 +0000 @@ -0,0 +1,32 @@ +#ifndef __MENUITEM_H +#define __MENUITEM_H + +#include "Terminal.h" + +extern Terminal term; + +typedef enum {menu, heading, display, control} MenuType; +typedef char * (*callback_function)(bool); // type for conciseness + +#define MAX_NAME_LEN (80-10-10) + +class MenuItem{ + public: + MenuItem(); + MenuItem(const char * Name_p, callback_function callback_p, int level, MenuType type_p, int target_page = -1); + MenuItem(const char * Name_p, int target_page); //construct a menu selection item this way + const char *Name; //reference to the name + int level; //0 if primary 1 or greater if this is a sub-menu + MenuType type; //are we displaying something or controlling something + + callback_function callback; //callback for getting the data + + int name_len; + + int data_col; //column where the data is shown + int target_page; //the page to go to if called + +}; + + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/page.cpp Fri May 01 03:56:34 2015 +0000 @@ -0,0 +1,90 @@ +#include "page.h" + + //default constructor + Page::Page() : + make_active_flag(false), + Name(NULL), + num_menuitems(0), + data_start_x(0), + page_num(0) + {} + +Page::Page(const char * Name_p): + make_active_flag(false), + Name(Name_p), + num_menuitems(0), + data_start_x(0), + max_upper_letter_cmd('A'-1), + max_lower_letter_cmd('a'-1), + max_number_cmd('1'-1), + page_num(0) +{ + term.printf("Page::Page('%s')\n",Name); + } + +MenuItem& Page::add_item(MenuItem const &item_p){ + MenuItem& retval = item[MAX_MENUITEMS-1]; //default return is last item (failure case) + if(num_menuitems < MAX_MENUITEMS-1) { + item[num_menuitems] = item_p; //make a local copy of the item + int item_data_start = item_p.name_len+ (LEVEL_INDENT * (item_p.level+1)) + DATA_GAP; + + if (item_data_start > data_start_x) data_start_x = item_data_start; //keep track of length so we can put data after it + + if(item[num_menuitems].type == heading) command_letter[num_menuitems]=0; //Headings don't get numbers + + else { + + //Assign the command character for the menu item + if(item[num_menuitems].level==1) //it gets an lower case letter + { + command_letter[num_menuitems] = ++max_lower_letter_cmd; + } + else if(item[num_menuitems].level==0) //it gets a number + { + command_letter[num_menuitems] = ++max_number_cmd; + } + else + { + command_letter[num_menuitems] = ++max_lower_letter_cmd; + } + + } + term.printf("Added menu item '%s' as index %d cmd %c\n", item[num_menuitems].Name, num_menuitems, command_letter[num_menuitems]); + retval = item[num_menuitems++]; + } + else term.printf("Failed to add menu item #%d, '%s'\n", num_menuitems, item_p.Name); + return retval; //return last item if error + } + +void Page::display(void){ + term.cls(); + term.locate(0,0); + term.printf("%s",Name); + int index; + + for (index=0; index < num_menuitems; index++){ + term.locate(LEVEL_INDENT*(item[index].level+1),index+ITEM_ROW_START); + + //don't print command letter for messages without them + if(command_letter[index] != 0) term.printf("%c) ", command_letter[index]); + + term.printf("%s", item[index].Name); + } + + index++; //add a space before the return line + term.locate(LEVEL_INDENT,index+ITEM_ROW_START); + term.printf("x) Back to previous page"); + update(); + } +// Update the data on the Page +void Page::update(void){ + + for (int index=0; index < num_menuitems; index++){ + term.locate(data_start_x,index+ITEM_ROW_START); + char const *str = (item[index].callback + ?item[index].callback(false) + :""); + term.printf("%s", str); + } + } +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/page.h Fri May 01 03:56:34 2015 +0000 @@ -0,0 +1,41 @@ +#ifndef __PAGE_H +#define __PAGE_H +#include "menuitem.h" +#include "Terminal.h" +//this class contains Pages of menu items + +extern Terminal term; +#define MAX_MENUITEMS 15 +#define LEVEL_INDENT 8 +#define DATA_GAP 4 +#define ITEM_ROW_START 3 +#define TERM_LOC_DEBUG 40,24 //x,y for printing debug info +#define TERM_LOC_INPUT 10,23 // X,Y for getting input +#define TERM_LOC_FEEDBACK 10,24 // X,Y for getting input +//class MenuItem; //forward declare + +class Page{ + private: + bool make_active_flag; + public: + Page(); + Page(const char * Name_p); + const char * Name; //reference to the name of the Page + int num_menuitems; + int data_start_x; //starting column for printing the data + MenuItem item[MAX_MENUITEMS]; + char command_letter[MAX_MENUITEMS]; //the character in front of the menuitem + char max_upper_letter_cmd; + char max_lower_letter_cmd; + char max_number_cmd; + int page_num; + bool refresh_required; + MenuItem& add_item(MenuItem const &item_p); + void display(); + void update(); + char * set_active() {make_active_flag = true; return NULL;} //flag the current page to be active + void ack_active() {make_active_flag = false;} //call to clear the set_active flag + bool check_active() {return make_active_flag;} // check to see if this page wants to be active +}; + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test_main.cpp Fri May 01 03:56:34 2015 +0000 @@ -0,0 +1,101 @@ +#ifdef TEST //define test to make this an active main +#include <mbed.h> +#include "Terminal.h" +#include "SerialTerm.h" +#include "ASCIIGraph.h" +#include "testconsole.h" +#include "page.h" +#include "menuitem.h" + +Terminal term(SERIAL_TX, SERIAL_RX); +Ticker heartbeat; + + +volatile bool tick = false; // this is the primary flag that indicates that it is time to run the control loop +static int ERROR_Overrun = 0; //this counts the number of times that the code was still running when the interrupt tried to trigger it again (ie code is running too slow) + +/*********************************************************************************************** + * \brief Interrupt for the main loop timer + * + **********************************************************************************************/ +void tick_int() { + if (!tick) tick = true; //detect if interrupt has not yet finished + else ERROR_Overrun++ ; +} + +//prototype callback for the menu system, if the command key has been pressed, then cmd is true +//otherwise it should simple return a string that gets printed +char * test_callback(bool cmd){ + + static int i; + static bool count = true; + static char buffer[20]; + + if(cmd) { + term.locate(TERM_LOC_DEBUG); + term.printf("test_callback"); + } + if(cmd) count = !count; + + if(count) i++; + + sprintf(buffer, "%d", i); + return buffer; + } + +char * null_callback(bool cmd){ + + return NULL; + } + + +int main(void){ + + term.baud(115200); + +//create the Pages for the console +// term.printf("\nsizeof page %d, menuitem %d\n",sizeof(Page), sizeof(MenuItem)); + TestConsole console("Test Program"); + + Page &pageHome = console.add_page(Page("Home")); + Page &pagePower = console.add_page(Page("Page1")); + Page &pageBattery = console.add_page(Page("Page2")); + Page &pageAccel = console.add_page(Page("Page3")); + + // pageHome.add_item(MenuItem(pagePower.Name, &pagePower.set_active(), 0, menu)); + #define CREATE_PAGE_SELECT_MENU_ITEM(x) add_item(MenuItem(x.Name, x.page_num)); + pageHome.CREATE_PAGE_SELECT_MENU_ITEM(pagePower); + pageHome.CREATE_PAGE_SELECT_MENU_ITEM(pageBattery); + pageHome.CREATE_PAGE_SELECT_MENU_ITEM(pageAccel); + + + pagePower.add_item(MenuItem("Display", NULL, 0, heading)); + pagePower.add_item(MenuItem("Eject Button Status", test_callback, 0, display)); + + pageBattery.add_item(MenuItem("Display", NULL, 0, heading)); + pageBattery.add_item(MenuItem("Battery Voltage", test_callback, 0, display)); + + pageAccel.add_item(MenuItem("Display", NULL, 0, heading)); + pageAccel.add_item(MenuItem("Battery Voltage", test_callback, 0, display)); + + + heartbeat.attach(&tick_int, 0.1); // the address of the function to be attached (tick_int) and the interval 10000uS + + + wait(2); //give the man a chance to read the messages, if any + + + while(1) { + while(tick){ //put real-time code in here + + console.tick(); + + tick=false; //keep this as the last item in the while loop + + } + + } + +} + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testconsole.cpp Fri May 01 03:56:34 2015 +0000 @@ -0,0 +1,84 @@ +#include "testconsole.h" + +TestConsole::TestConsole(const char * Name_p): + Name(Name_p), + num_pages(0), + current_page(0) +{ + + term.printf("TestConsole::TestConsole('%')\n",Name); + page_change(current_page); + page[current_page].display(); + } + +Page& TestConsole::add_page(Page const &page_p){ +term.printf("TestConsole::add_page\n"); + if(num_pages < MAX_PAGES) { + page[num_pages] = page_p; + page[num_pages].page_num = num_pages; //let the page know what number it is to help with lookups + + if(num_pages == 0) { //if this is the first page, set it active + page[num_pages].set_active(); + } + term.printf("Added page '%s'\n", page[num_pages].Name); + return page[num_pages++]; + } + + term.printf("Failed to add page'%s'\n", page_p.Name); + return page[MAX_PAGES-1]; //return 0 if no error + + } + +int TestConsole::page_change(int new_page){ + previous_page = current_page; //save a copy of the page so we can go back + current_page = new_page; + page[current_page].display(); + + page[current_page].ack_active(); + return current_page; + } + +//here, using knowledge of the page, we process commands +int TestConsole::process_cmd(char cmd){ + + + for(int index=0; index < page[current_page].num_menuitems; index++){ + if('x' == cmd) { + page_change(previous_page); + return 0; + } + + if(page[current_page].command_letter[index] == cmd) { + + //for menuitems that goto other menus, just change the page + if(page[current_page].item[index].type == menu) { + page_change(page[current_page].item[index].target_page); + return 0; + } + + //otherwise call the callaback + page[current_page].item[index].callback(true); + return 0; + } + } + + return 1; + } + +int TestConsole::tick(void){ + if (term.readable()){ //if there is a character + if(process_cmd(term.getc())){ + term.locate(TERM_LOC_FEEDBACK); + term.printf("invalid command"); + } + } + + //go through the list of pages, and see if any want to become active + for(int index=0; index < page[current_page].num_menuitems; index++){ + if(page[index].check_active()) page_change(index); + } + + + page[current_page].update(); + return 0; + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testconsole.h Fri May 01 03:56:34 2015 +0000 @@ -0,0 +1,23 @@ +#ifndef __TESTCONSOLE_H +#define __TESTCONSOLE_H + +#include "page.h" +#include "menuitem.h" + +#define MAX_PAGES 9 +class TestConsole { + public: + TestConsole(const char * Name_p); + const char * Name; //reference to the name of the Program + int num_pages; + int current_page, previous_page; + Page page[MAX_PAGES]; + Page& add_page(Page const &page_p); + int process_cmd(char cmd); + int tick(); + int page_change(int page); + + }; + + +#endif //fence \ No newline at end of file