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

Files at this revision

API Documentation at this revision

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

menuitem.cpp Show annotated file Show diff for this revision Revisions of this file
menuitem.h Show annotated file Show diff for this revision Revisions of this file
page.cpp Show annotated file Show diff for this revision Revisions of this file
page.h Show annotated file Show diff for this revision Revisions of this file
test_main.cpp Show annotated file Show diff for this revision Revisions of this file
testconsole.cpp Show annotated file Show diff for this revision Revisions of this file
testconsole.h Show annotated file Show diff for this revision Revisions of this file
--- /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