OpenMoCo Menu Manager - completely automated menus for Arduino

Hi all,

I've created a new release of my OpenMoCo libraries for motion control applications (available here: Dynamic Perception ), but I wanted to bring attention to a new library included in it: OMMenuMgr, which is a completely automated menuing system for the arduino.

I know there are a number of menuing libraries out there, but none of them met our needs as we have several different projects active, some of which use up to dozens of menu items. One of the key problems I needed to eliminate were duplicated code in menu handling - such as needing to determine which value is being edited, display it properly, allow temporary edits, and then save it to the final value only when the user chooses to. Using any existing menu libraries would've required a great deal of code, and provided little advantage for us when the base menu navigation was the easiest part of the project. Essentially, the only thing this menu system doesn't do for you is draw the characters on the LCD (but it does call your function to do it =)

This is a first release of the library, so obviously it has some limitations, and there may be some warts left to uncover, but for a high-level description:

The OpenMoCo Menu Manager provides a nearly complete automation of a menuing system. This class is designed to allow for rapid development of menuing systems on Arduino devices, using up to five input controls (buttons), and character displays of at least two rows. Designed to make it easy to implement menu systems, the Menu Manager requires the developer to do little more than specify the structure of the menu, and how to draw on the display.

Key features:

  • Menu structures automatically and easily stored in program memory instead of SRAM
  • Automatic handling of either analog or digital button inputs
  • Automatic management of user inputs for numeric and select list types
    -- In-place editing with ability for user to abort
    -- Write-back on user save to original variables
    -- Specify lists of display values for users to select between
    -- Control over user input precision in floats, and more
  • Ability to trigger code actions from menu items
  • One-method polling automatically handles and executes menu as-needed
  • Streamlined setup for complex and even recursive menus
  • Support for any width screen
  • Reduced memory footprint
  • No fixed limitations on menu depths, have as many sub-menus as you require
  • Flexible drawing methodology allows the use of any character-based (or even graphic) display

The Menu Manager displays a character-based menu when triggered by the user. Five user inputs are used (although only four are required): Enter, Up, Down, Back, and Forward. Forward is optional. These inputs may either be buttons tied to digital pins, or up to five buttons tied to a single analog pin using a series of resistors to present different analog values when a button is pressed.

The menu is triggered by hitting the Enter key by the user when the menu is enabled. When it is displayed, the user is presented with all available options at the current menu level, one per row, with a cursor indicating the currently selected item. The up and down keys scroll the list through all available items at the current menu level. If the user presses enter, or forward on the current item, the item will be presented as either a value edit, a new menu, or an action will be executed as defined by the user based on the type of the menu entry. The back button will cancel out of the current input, go back one menu level, or exit the menu entirely if the user is at the root menu level.

When a value is being edited, the user may adjust the value within the range specified by the developer non-destructively using the up and down buttons. The user may cancel the edit of the value by using the back button, or may save the value using either of enter or forward. When the user saves the value, it is written directly to the variable specified by the developer. All numeric types are supported, and special select lists are supported as well. For select lists, the user is presented with strings defined by the developer for the numeric values stored in the target variable, providing the ability to give the user a more complete and obvious solution.

Menu items may also be sub-menus. You can even re-use the same sub-menus under different menus with minimal impact to flash size and SRAM usage, letting you use the same menus under different contexts.

Menu items may be actions - you may wish to draw special screens and allow for use-case driven inputs. In this manner, you can trigger callbacks when menu items are triggered and disable the menu easily while interacting with the user, and then seamlessly place the user back into the menu where they left off when they are done.

Finally, the Menu Manager can be used to easily poll the user inputs without drawing the menu as needed by the developer.

The complete documentation for the menu manager is available here, which covers its capabilities and usage in great detail: Dynamic Perception

The library is available for download as part of the OpenMoCo AVR Libraries here: Dynamic Perception

I'd love your feedback!

Here is a complete example sketch showing everything you need to do to create an advanced menu, with integral input types, select list types, action handlers, and more. Note the bulk of the work is simply in declaring the menu data elements, the rest of the code is minimal and is fully functional.

/**  Example OMMenuMgr Sketch

 @author
 C. A. Church
 
 */

#include "OMMenuMgr.h"
#include <LiquidCrystal.h>

 // ======== ui setup ===========
 
    // lcd pins
const byte LCD_RS  = 17;
const byte LCD_EN  = 18;
const byte LCD_D4  = 11;
const byte LCD_D5  = 8;
const byte LCD_D6  = 7;
const byte LCD_D7  = 4;

const byte LCD_ROWS = 2;
const byte LCD_COLS = 16;

 // which input is our button
const byte BUT_PIN = 14;

  // analog button read values
const int BUTSEL_VAL  = 70;
const int BUTFWD_VAL  = 250;
const int BUTREV_VAL  = 450;
const int BUTDEC_VAL  = 655;
const int BUTINC_VAL  = 830;

const byte BUT_THRESH  = 60;

  // mapping of analog button values for menu
const int BUT_MAP[5][2] = {
                         {BUTFWD_VAL, BUTTON_FORWARD}, 
                         {BUTINC_VAL, BUTTON_INCREASE}, 
                         {BUTDEC_VAL, BUTTON_DECREASE}, 
                         {BUTREV_VAL, BUTTON_BACK}, 
                         {BUTSEL_VAL, BUTTON_SELECT} 
                    };
                            


// ====== Test Menu =========== 

 // declare some variables we want to modify
byte foo = 0;
byte sel = 0;
unsigned int bar = 1;
long baz  = 0;
float bak = 0.0;

  // Create a list of states and values for a select input
MENU_SELECT_ITEM  sel_ign = { 2, {"Ignore"} };
MENU_SELECT_ITEM  sel_on  = { 1, {"On"} };
MENU_SELECT_ITEM  sel_off = { 0, {"Off"} };

MENU_SELECT_LIST  state_list[] = { &sel_ign, &sel_on, &sel_off };
                                  
  // the special target for our state input
  
                             // TARGET VAR   LENGTH                          TARGET SELECT LIST
MENU_SELECT state_select = { &sel,           MENU_SELECT_SIZE(state_list),   MENU_TARGET(&state_list) };

  // values to use 

                    //    TYPE            MAX    MIN    TARGET 
MENU_VALUE foo_value = { TYPE_BYTE,       100,   0,     MENU_TARGET(&foo) };
MENU_VALUE bar_value = { TYPE_UINT,       10000, 100,   MENU_TARGET(&bar) };
MENU_VALUE baz_value = { TYPE_LONG,       10000, 1,     MENU_TARGET(&baz) };
MENU_VALUE bak_value = { TYPE_FLOAT_1000, 0,     0,     MENU_TARGET(&bak) };
MENU_VALUE sel_value = { TYPE_SELECT,     0,     0,     MENU_TARGET(&state_select) };

                    //        LABEL           TYPE        LENGTH    TARGET
MENU_ITEM item_checkme  = { {"Byte Edit"},    ITEM_VALUE,  0,        MENU_TARGET(&foo_value) };
MENU_ITEM item_barme    = { {"(Bar) UInt Edit"},     ITEM_VALUE,  0,        MENU_TARGET(&bar_value) };
MENU_ITEM item_bazme    = { {"Long Edit"},    ITEM_VALUE,  0,        MENU_TARGET(&baz_value) };
MENU_ITEM item_bakme    = { {"Float Edit"},   ITEM_VALUE,  0,        MENU_TARGET(&bak_value) };
MENU_ITEM item_state    = { {"Select Input"}, ITEM_VALUE,  0,        MENU_TARGET(&sel_value) };
MENU_ITEM item_testme   = { {"Test Action"},  ITEM_ACTION, 0,        MENU_TARGET(uiQwkScreen) };

                   //        List of items in menu level
MENU_LIST root_list[]   = { &item_checkme, &item_barme, &item_bazme, &item_bakme, &item_state, &item_testme };

                  // Root item is always created last, so we can add all other items to it
MENU_ITEM menu_root     = { {"Root"},        ITEM_MENU,   MENU_SIZE(root_list),    MENU_TARGET(&root_list) };

// ======== Object Initialization ==========


 // initialize LCD object
LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

 // Initialize Menu Manager
OMMenuMgr Menu(&menu_root);


// ======= Main Code Body =========

void setup() {

  lcd.begin(LCD_COLS, LCD_ROWS);
  
  uiMain();
  
  Menu.setDrawHandler(uiDraw);
  Menu.setExitHandler(uiMain);
  Menu.setAnalogButtonPin(BUT_PIN, BUT_MAP, BUT_THRESH);
  Menu.enable(true); 
  
}

void loop() {
 Menu.checkInput();
}

 // draw handler
void uiDraw(char* p_text, int p_row, int p_col, int len) {
  lcd.setCursor(p_col, p_row);
  
  for( int i = 0; i < len; i++ ) {
    if( p_text[i] < '!' || p_text[i] > '~' )
      lcd.write(' ');
    else  
      lcd.write(p_text[i]);
  }
}


 // menu exit handler / main screen draw
void uiMain() {
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Enter for Menu");
  lcd.setCursor(0, 1);
  lcd.print("Bar = "); 
  lcd.print(bar, DEC);
}

 // our special action
void uiQwkScreen() {
  lcd.clear();
  Menu.enable(false);
  
  lcd.print("Action!");
  lcd.setCursor(0, 1);
  lcd.print("Enter 2 return");
  
  while( Menu.checkInput() != BUTTON_SELECT ) {
    ; // wait!
  }
  
  Menu.enable(true);
  lcd.clear();
}

Hi

Sounds good, but i did not found any online documentation.
Regarding your statement, that none of the existing libs meets your needs, I would say, that m2tklib fullfills almost all of your requirements. Additionally m2tklib support less than 4 buttons but also more than 5 buttons, rotary encoder, input and output from Arduino serial monitor etc..

I have added your lib to my personal lib of Arduino Menu Libs:

Library Name: LCDMenu2
Download: http://arduino.cc/forum/index.php/topic,73816.0.html

Library Name: LCDMenu
Download: http://arduino.cc/forum/index.php/topic,96104.0.html

Library Name: MenuBackend
Download: http://wiring.uniandes.edu.co/source/trunk/wiring/firmware/libraries/MenuBackend/

Library Name: LCDMenu
Download: http://www.metalgecko.com/arduino/LCDMenu.zip (does not exist any more)

Library Name: Arduino_LCD_Menu
Download: GitHub - DavidAndrews/Arduino_LCD_Menu: This library creates menu systems primarily useful for 16x2 or 16x4 LCD displays.

Library Name: MENWIZ
Download: GitHub - brunialti/MENWIZ: ARDUINO LCD menu library: short user code to manage complex menu structures

Library Name: phi_prompt
Download: Phi_prompt | LiuDr Electronic Solutions LLC Official Blog

Library Name: MenuSample
Download: Arduino WiFly Driver - Browse /MenuSample at SourceForge.net

Library Name: M2tklib
Download: Google Code Archive - Long-term storage for Google Code Project Hosting.

Library Name: OpenMoCo Menu Manager
Download: http://arduino.cc/forum/index.php/topic,131614.0.html, Dynamic Perception

Oliver

Hi Oliver, thanks for the list - I hadn't seen m2tklib before, but it looks like we took a very similar approach in most regards. The online Doxygen documentation was linked in the first post, here it is again: Dynamic Perception

Thanks!

Chris

Hi

Ah, thanks for the link. I did not saw the initial link.

And yes, OpenMoCo Menu Manager and M2tklib do have a very similar approach.
I like the clean C++ approach and the description of the display handler.

Good work!

Oliver

Hi

I tried to use u8glib (Google Code Archive - Long-term storage for Google Code Project Hosting.) with OpenMoCo.

Problem 1: After download and archive extraction into the libraries folder, the Arduino IDE complains about the root directory name. I had to rename it to something without "-" and ".". I also noticed that there is exactly only one example for the complete lib. Luckily it covers the menu manager, but i would have expected some more examples within the Arduino IDE for other parts of the lib.

Problem 2: I do not know how to setup the u8glib picture loop. More specific: I need to draw the screen more than once. The draw callback handler for u8glib is easy. Assuming a font with char width 4 and height 8 it is this:

void uiDraw(char* p_text, int p_row, int p_col, int len) {
  u8g.setCursorPos(p_col*4, p_row*8);  
  for( int i = 0; i < len; i++ ) {
    if( c < '!' || c > '~' )
      u8g.write(' ');
    else  
      u8g.write(p_text[i]);
  }
}

However, the screen has to be updated several times. But i do not know how to do this. Controll is given to this lib with checkInput(), but i assume that this procedure also does the character output. Pseudocode for u8glib is this:

  1. check keys
  2. handle keys
  3. several times: redraw screen
    I looked into the code, but did not find how to do 3)

Any ideas?

Oliver

olikraus:
Problem 1: After download and archive extraction into the libraries folder, the Arduino IDE complains about the root directory name. I had to rename it to something without "-" and ".". I also noticed that there is exactly only one example for the complete lib. Luckily it covers the menu manager, but i would have expected some more examples within the Arduino IDE for other parts of the lib.

Hi Oliver,

What dashes and dots? There are no dashes or dots in any directory names for any OM libraries? The OM Menu Manager root directory name is 'OMMenuMgr'. The example exercises 100% of the OMMenuMgr functions.

If it's other libraries you're looking for information and examples on, read the doxygen docs which are in docs/html -- there are pages on the more complex libraries, which give many examples of their use, and all other libraries are fully documented with basic examples.

olikraus:
Problem 2: I do not know how to setup the u8glib picture loop. More specific: I need to draw the screen more than once. The draw callback handler for u8glib is easy. Assuming a font with char width 4 and height 8 it is this:

void uiDraw(char* p_text, int p_row, int p_col, int len) {

u8g.setCursorPos(p_col4, p_row8); 
  for( int i = 0; i < len; i++ ) {
    if( c < '!' || c > '~' )
      u8g.write(' ');
    else 
      u8g.write(p_text[i]);
  }
}




However, the screen has to be updated several times. But i do not know how to do this. Controll is given to this lib with checkInput(), but i assume that this procedure also does the character output. Pseudocode for u8glib is this:
1. check keys
2. handle keys
3. several times: redraw screen
I looked into the code, but did not find how to do 3)

Any ideas?

Oliver

I don't see from the u8g docs that it needs to be drawn constantly, but instead only to be drawn when it changes. However, if you want to re-draw constantly, then just store the draw data in global scope, and access that as it updates...

something like this:

struct {

  char text[OM_MENU_ROWS];
  int row;
  int col;
  int len;
} screenDat;

boolean screenSet = false;

...

void setup() {

  ...

  Menu.setDrawHandler(uiDraw);
  Menu.setExitHandler(uiExit);
}

void loop() {

  Menu.checkInput();

  if( screenSet )
     u8draw();

}

void uiDraw(char* p_text, int p_row, int p_col, int len) {

    // clear out buffer
  memset(screenDat.text, ' ', OM_MENU_ROWS);
  memcpy(&screenDat.text, p_text, len);

  screenDat.row = p_row;
  screenDat.col = p_col;
  screenDat.len = len;
 
  screenSet = true;

}

void uiExit() {

  screenSet = false;
}

Because of the lib name: Maybe i made a mistake here.

Regarding u8glib, i think i got your point: Implement an intermediate buffer and draw that buffer as often as required.
ok, this could be done.

Thanks,
Oliver

New update released, includes the following:

  • Ability to have Bit flag input types, showing "On" and "Off" (allow users to toggle a single bit out of a byte)
  • Automatic storage of values to EEPROM (Using OMEEPROM library included)
  • Accelerating input changes when button is held (scroll through values faster the longer buttons are held)
  • Query the library to determine if button is held, or simply pressed when using it to manage input outside of a menu
  • Navigation bug fixes
  • Greater flash size efficiency through coding optimizations

It can be downloaded here: Dynamic Perception
Updated documentation here: Dynamic Perception

!c

Hi drone

Is there a way to use my own function for the input? I have a function for my 4 analog buttons, the incremental encoder and the encoder push button. With your libary I can only use the 4 analog button and not a combination of the two... Is it possible to call the menu functions directly? I have debounced my analog buttons already.

greets mike

Hi Mike,

Sorry for the delay!

The issue is not really debouncing, but the checkInput() method does a lot more than that - you'll note that it handles escalating values the longer an input is held, dealing with returning back to menus from screens, etc. To handle a sixth input will be a bit more complex, because you probably want that input to behave like two of the other inputs. (e.g. emulate INCREASE or DECREASE based on direction of turn.) The best way to do this will be to modify the OMMenuMgr library file and make _checkAnalog() a protected virtual, and then sub-class the OMMenuMgr library, providing your new _checkAnalog() function to properly return the button type. This way, you can make your own _checkAnalog() method that returns one of BUTTON_NONE, BUTTON_FORWARD, BUTTON_BACK, BUTTON_INCREASE, BUTTON_DECREASE, BUTTON_SELECT based on how your inputs are read.

You may also want to make setAnalogButtonPins() virtual as well, so you can pass the correct information in without hard-coding it. If you're still interested in doing this, I can make these virtual changes to the core library so that you won't have to worry about upgrades later. BTW, the new home for this library is: GitHub - DynamicPerception/OMLibraries: OpenMoCo Libraries for AVR

!c

Hey there,

the lib looks promising. I stumbled upon this when i was wading through the code for chronos, not liking it a bit. But what i liked about the chronos approach was the input of data through potentiometers. it is fast and with a small piezo as audio feedback even tactile. but how to incorporate this into the openmoco menu manager?

Thinking aloud here it could even be possible to use just one analog pin for the hole setup, attaching the pot to vcc and gnd via two resistors, so two extra buttons can short the analog pin to vcc or gnd as button values.

But i'm just a moderate c programmer, still not able to handle the abstract thoughtsbehind c++, so don't expect any code from me. :frowning:

hi
i am getting error when try t compiling this example OMMenuMgr.ino.

'BUTTON_SELECT' was not declared in this scope.

any help?

shkdxb:
hi
i am getting error when try t compiling this example OMMenuMgr.ino.

'BUTTON_SELECT' was not declared in this scope.

any help?

Sounds like you did not properly install the library, or include it in your sketch? BUTTON_SELECT is an enum defined in OMMenuMgr.h

Thanks drone, i could able to solve the issue. For a strange reason, the libraries are not recognized under default library/ folder. While compiling, the error showed up for library location as my documents directory. i have copied your files to my document/ aruduino/library folder and problem solved.

But now i have one more issue. As per your documents, you have EEPROM support, but the example provided in your Github page doesn't have this line in line no 78.

MENU_VALUE foo_value = { TYPE_BYTE, 100, 0, MENU_TARGET(&foo), EEPROM_FOO };

Also i could not find the setup parameter OM_MENU_USE_EEPROM.

how can I implement EEPROM writing and reading?

I'm having a similar problem when compiling. I've installed directly into the program files\arduino\libraries folder so that each individual folder is its own library.

C:\Program Files\Arduino\libraries\OMMenuMgr/OMMenuMgr.h: In member function 'void OMMenuMgr::_eewrite(OMMenuValue*, T)':
C:\Program Files\Arduino\libraries\OMMenuMgr/OMMenuMgr.h:989: error: 'OMEEPROM' has not been declared

Hi all,
I have rum the sketch, but I am geeing lot of error as below. I thing problem with the Input Button definition, Please help me.
Regards,
Harikesh

example.ino:8:23: error: OMMenuMgr.h: No such file or directory
example:44: error: 'BUTTON_FORWARD' was not declared in this scope
example:45: error: 'BUTTON_INCREASE' was not declared in this scope
example:46: error: 'BUTTON_DECREASE' was not declared in this scope
example:47: error: 'BUTTON_BACK' was not declared in this scope
example:48: error: 'BUTTON_SELECT' was not declared in this scope
example:62: error: 'MENU_SELECT_ITEM' does not name a type
example:63: error: 'MENU_SELECT_ITEM' does not name a type
example:64: error: 'MENU_SELECT_ITEM' does not name a type
example:66: error: 'MENU_SELECT_LIST' does not name a type
example:71: error: 'MENU_SELECT' does not name a type
example:76: error: 'MENU_VALUE' does not name a type
example:77: error: 'MENU_VALUE' does not name a type
example:78: error: 'MENU_VALUE' does not name a type
example:79: error: 'MENU_VALUE' does not name a type
example:80: error: 'MENU_VALUE' does not name a type
example:83: error: 'MENU_ITEM' does not name a type
example:84: error: 'MENU_ITEM' does not name a type
example:85: error: 'MENU_ITEM' does not name a type
example:86: error: 'MENU_ITEM' does not name a type
example:87: error: 'MENU_ITEM' does not name a type
example:88: error: 'MENU_ITEM' does not name a type
example:91: error: 'MENU_LIST' does not name a type
example:94: error: 'MENU_ITEM' does not name a type
example:100: error: 'OMMenuMgr' does not name a type
example.ino: In function 'void setup()':
example:110: error: 'Menu' was not declared in this scope
example.ino: In function 'void loop()':
example:119: error: 'Menu' was not declared in this scope
example.ino: In function 'void uiQwkScreen()':
example:151: error: 'Menu' was not declared in this scope
example:157: error: 'BUTTON_SELECT' was not declared in this scope

Hi,
.h and .cpp files needs to move to upper folder. Same way om eprome also need to move in same folder where .h and .cpp are moved.
Regards,
Harikesh

Hallo,
I am trying to create Menu using this library from couple of weeks. I have tryed a lot to create Sub Menu. If any one know it, please repply me, it's not given in the example also. Please please help me out.
Harikesh Patil

Hi.

harikeshpatil:
I have tryed a lot to create Sub Menu...

You can generate the submenu by using MENU_LIST and MENU_ITEM similarly as the root menu is done. If you take the example included with the library and add these lines just above the line that begins with "MENU_LIST root_list..."

                   //  List of items in the submenu level
MENU_LIST submenu_list[] = { &item_bazme, &item_bakme };
                  // Submenu item
MENU_ITEM menu_submenu = { {"Submenu>>"},  ITEM_MENU,  MENU_SIZE(submenu_list),  MENU_TARGET(&submenu_list) };

Now add "&menu_submenu" to the root list below and remove "&item_bazme, &item_bakme " from the same list. You should now have a submenu with these two items.