MENWIZ: yet another character lcd menu wizard library

Interesting. I was suggested to take a look. This seems to be a more developed system than some counterparts in terms of adding menu items. How are you dealing with deleting items and running out of ram with frequently adding and deleting items? Say you have a scheduler to maintain a few schedules over the day and you add and delete schedule items over time.

I also have a NOT MENU but user interface library called phi_prompt. It is completely orthogonal to your menu library. There are no classes and menus are independent from one another. I'm adding in the library to display a menu with a one-line command for beginners such as:

response=simple_menu("Main menu:\nItem 1\nItem 2\nItem3\n"); // Response ranges 0-2 and 255 if user presses escape.

Everything is done but again I don't have time to do the documentation.

About memory allocation. Good point. I wrote an allocation and garbage collector system. Unfortunately it was too large for Arduino.
You must consider that most part of memory consumption is due to literals (i.e. "strings"). You could deallocate menu object(s), but in your program the strings still got space at compile time, even without any object instantiation. I think that the actual memory constraints of Arduino UNO will be over soon. Some clones today provide more available ram to the users. I suggest to use the freeRam function to test new sketches, same as to trace error messages during debugging with getErrorMessage function. Anyway I agree, some more sofisticated memory management is required.

About multiple menus. In MENWIZ you can have multiple menu (hierarchies), each one with its own navigation device, lcd and so on.
You only need to create multiple istances of menwiz class.

The key points in MENWIZ are:

  • MUNWIZ is (near) asinchronous. I need it with real time system were I cannot suspend the sketch execution waiting for user responses.
  • MENWIZ allows to add (just one code line) splash screen and to set the elapsed time.
  • MENWIZ allows to add (just one code line) a "default" user screen. The library show automatically the default screen after a user defined delay since the last button push. It is very usefull when you have sensors and you need continuous monitoring.
  • MENWIZ allows user defined callback to be fired inside a menu (see the following example). I could even easily implement triggers to be fired when binded variables satisfy certain condictions, but I'm not sure such a feature is really usefull

How do you manage the above reqs ?

However there is not the killer solution for Menu management, and MENWIZ for sure is not (and never will be) the ultimate solution.
You have to balance the tradeoff between simple coding, power, flexibility, size, user model (sync/async) and device support.
It is not a simple task, and you know well... :slight_smile:

Here following please find an example I wrote for an other thread (Help creating menu for golf robot - #25 by brunialti - Displays - Arduino Forum), were an action is required to be fired from menu. It can be done in very few lines ...

Thanks for your interesting remarks!

p.s. sorry for my english.

//MENWIZ GOLF ROBOT EXAMPLE
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <buttons.h>
#include <MENWIZ.h>

// DEFINE ARDUINO PINS FOR THE NAVIGATION BUTTONS
#define UP_BOTTON_PIN       9
#define DOWN_BOTTON_PIN     10
#define LEFT_BOTTON_PIN     7 
#define RIGHT_BOTTON_PIN    8
#define CONFIRM_BOTTON_PIN  12
#define ESCAPE_BOTTON_PIN   11

//Create global object LCD and MENU
menwiz menu;
LiquidCrystal_I2C lcd(0x27,20,4);

//instantiate global variables to bind to menu
byte m=0;
int  fd=0,yd=0;

void setup(){
  _menu *r,*s1;

  Serial.begin(19200);  
 
  // inizialize the menu object (20 colums x 4 rows LCD)
  menu.begin(&lcd,20,4);

  //create the menu tree
  r=menu.addMenu(MW_ROOT,NULL,"GOLF ROBOT");           //create a root menu at first (required)
      s1=menu.addMenu(MW_VAR,r,"Modes");                        //add a terminal node in the menu tree (that is "variable"); 
          s1->addVar(MW_LIST,&m);                                          //create the terminal node variable of type OPTION LIST and bind it to the app variable "m"
          s1->addItem(MW_LIST,"Drive");                                  //add an option to the OPTION LIST
          s1->addItem(MW_LIST,"Punch");                                 //add an other option to the OPTION LIST
          s1->addItem(MW_LIST,"Chip");                                    //add the third option to the OPTION LIST
          s1->addItem(MW_LIST,"Putt");                                    //add the last option to the OPTION LIST
      s1=menu.addMenu(MW_VAR,r,"Putt Dist.(feets)");        //create an other "variable" menu terminal mode
          s1->addVar(MW_AUTO_INT,&fd,0,100,1);                   //int type, fd binded variable, rage 0-100, step 1
      s1=menu.addMenu(MW_VAR,r,"Other Dist. (yrds)");
          s1->addVar(MW_AUTO_INT,&yd,0,300,5);                  //int type, yd binded variable, rage 0-300, step 5
      s1=menu.addMenu(MW_VAR,r,"Fire action");                 //latest menu entry
          s1->addVar(MW_ACTION,fireAction);                          // associate an action (variable of type function) to the menu entry

  //declare navigation buttons (required)
  // equivalent shorter call: menu.navButtons(9,10,7,8,11,12);
  menu.navButtons(UP_BOTTON_PIN,DOWN_BOTTON_PIN,LEFT_BOTTON_PIN,RIGHT_BOTTON_PIN,ESCAPE_BOTTON_PIN,CONFIRM_BOTTON_PIN);
  }

void loop(){
  // NAVIGATION MANAGEMENT & DRAWING ON LCD. NOT BLOCKING
  menu.draw(); 

  //PUT APPLICATION CODE HERE
  // if any .... :-)
  }

// user defined action for fire action 
void fireAction(){
  Serial.print("FIRED ");
  switch (m){
    case 0:
      Serial.print("Drive to ");Serial.print(yd); Serial.println(" yrds");
      break;
    case 1:
      Serial.print("Punch to ");Serial.print(yd); Serial.println(" yrds");
      break;
    case 2:
      Serial.print("Chip to ");Serial.print(yd); Serial.println(" yrds");
      break;
    case 3:
      Serial.print("Put  to "); Serial.print(fd); Serial.println(" feets");
      break;
    default:
      break;
    }
  }

My concern is not "not enough memory". It is more or less possible wasting memory by using "addItem" functions.

Say if I add an item, then the menu object has to acquire memory to accommodate the new item, acquire memory to accommodate the longer list of item pointers. This means releasing the current smaller memory and allocating a larger piece of memory. The sequential adding required to initially set up a menu with a few items will do this multiple times. Every time you add, you run out of your current space and that space is more or less left as a fragment. This is all my suspicion of how you wrote your library. How exactly is adding actually done in your library?

If you run freeMem at the start and after the menu items are all added, how much memory is consumed with how many items added?

The menwiz object requires 606 bytes for a 4x20 lcd. It depends also from screen size
It allocates a buffer of the screen size when the constructor method is invocated. The object itself should'nt be allocated in ram, as it is a global object and allocated at compile time.
In the first example the whole menu took 340 bytes (difference between freeRam at first and last line of setup()).
Actualy adding is done in a mixed mode:

  • _menu items are preallocated in the menwiz class object in a fixed size array, labels are allocated dinamically.
  • each item inside _menu objects is fully dinamically allocated (but a fixed size pointer array is preallocated in each _menu object)
  • _var ("variable" associated to a terminal node of a menu tree) is preallocated inside each _menu object; it is a structure of void pointers and it requires some dinamic allocation for int, boolean ... variables

I'm experimenting which is the best model, tryng to free the heap and charge the program space.
Optimizations can be done (this is really a proof of concept lib), but do not expect it will half the memory ....
Arduino runs out of heap space very quickly ...

That's better than I thought. OOP programmers are often too easily dismissing the practice to have a fixed length array to store object pointers. I do what you do in different projects where objects are to be created at run time (there's a max of how much my fixed pointer array can hold but I think for arduino projects it's not bad). At your current stage, there should be some decent amount of memory left for developers to do their things. My library actually doesn't have a screen buffer. That saves some ram but also makes the program longer. I do have a buffer for 21 bytes, that is to assemble one item at a time and render to LCD after that and free the buffer for the next item.

Yes, I do'nt leave memory fragments at all.
Memory left depends from menu tree complexity and string length.
I trust on Arduino next gen...

brunialti:
Yes, I do'nt leave memory fragments at all.
Memory left depends from menu tree complexity and string length.
I trust on Arduino next gen...

On the other hand, I also have designed a family of phi-panels, serial LCD keypad panels that can render menus and long messages and sense buttons/multi-tap for you so your main arduino doesn't have to fight for memory. :wink:

I know, I know :slight_smile:
I'm from sw side and a real Arduino newbie.
Arduino is my hobby. Much better than sudoku... :slight_smile:

brunialti:
I know, I know :slight_smile:
I'm from sw side and a real Arduino newbie.
Arduino is my hobby. Much better than sudoku... :slight_smile:

It's good practice to make a library and people can use it and help you improve it too. I can't do sudoku, just not that sharp with 0-9.

Hi liudr and brunialti

Very interessting discussion. I have learned a lot today. I never thought of a dynamic menu system. Indeed, i never thought it could be possible. My own menu system (m2tklib) uses a complete static approch...

It would be interesseting to analyse these differences more closely. Something like this: Define a task which could be implemented by any of the menu libs and compare the results. Occupied flash, ram. Execution speed. Lib maintenance, usability, learning curve.

Unfortunately, github seems to be down at the moment... so its just a thought...

Oliver

I am not sure the comparison make sense, as the two libs seems quite different (e.g. graphic vs character), but why not?
Menwiz has three main sets of funcs. One to define the structure (add***). One to check the device (i.e. buttons by now). One to draw the menu (draw***).
In theory it could be possible to deep the third layer abstraction in order to use graphic rendering.
I would like to keep menwiz as simple as possible. At this stage it is not really a full fledged lib and the code is poor (unclean, redundant unstyled) due to the short time I spent to build it up.

MENWIZ requires LiquidCrystal_I2C

Will there be support for the standard Arduino LiquidCrytal lib?

Oliver

Menwiz uses only the following methods, that are the same in the two libs:
lcd->init();
lcd->setBacklight(HIGH);
lcd->setCursor(x, y);
lcd->noCursor();
lcd->createChar(0,z);
lcd->print();
lcd->write()
The lcd object is created in the sketch and not in the library (the labrary stores only the pointer to the object), therefore there should'nt be a problem. I did'nt test as I have not the lcd in my hand.
I made a small change in the menwiz.h file (the new version in github in minutes!). You will find the #define I2C. If you comment that line, the compiler include the standard LiquidCrystal lib instead of LiquidCrystal_I2C. I did'nt test it yet. If you can, please let me know.
Better solutions are welcome!

olikraus has library that works on both glcd and character lcds. There must be tricks to how that can be done :wink:

I am thinking about expanding to different displays too. My first step was to expand to different input devices.

A competition or cross comparison is definitely welcome! I'm sure I can win the "easy to get started" award since my library can run one one-line function calls.

Do you real want test such a young baby as menwiz against such a mature and wise counterparts ? :slight_smile:
My lib goals are: near asynchronous, simple coding, user defined screens (splash and default) and "overwrite callbacks" (input device scan, actions/triggers ...).
I have more work to do. If somebody is interested I am ready to go on.
I would be pleased if some could test the lib with standard LiquidCrytal.
I have noy 4/8 wires LCD available at the moment.

brunialti:
Do you real want test such a young baby as menwiz against such a mature and wise counterparts ? :slight_smile:
My lib goals are: near asynchronous, simple coding, user defined screens (splash and default) and "overwrite callbacks" (input device scan, actions/triggers ...).
I have more work to do. If somebody is interested I am ready to go on.
I would be pleased if some could test the lib with standard LiquidCrytal.
I have noy 4/8 wires LCD available at the moment.

Not to challenge you, just comparison as I suggested. So you are going into the dynamic and callbacks direction. You could get one of my shields to test out the library :wink:

I recently failed to support a user. It turned out that M2tklib is still too complicated (will i win the price for the most complicated lib?).

I would like to make M2tklib more simple. So I try to understand other approaches. And I like to see fresh libs like yours. For me a comparison about the programming interfaces would be useful (not about features). What would be the most easy approach for a beginner? What do others think about these different approches regading the programming API?

If we find something common which can be implemented by each of the libs, then i think i could get a better understanding of each programming model. And maybe other users can rate the programming effort for each API.

My idea was something about this: On a 16x4 (or 20x4) display 4 selectable lines. The user selects one line and the display shows the line number (0-3). No scrolling, just a cursor which is used to select one of four lines. Is this possible with MENWIZ?

Oliver

Looking at the direction you are going with the MENWIZ library, I think it would be worth while looking at the New LiquidCrystal library. Since you are using a pointer to the LCD, you would benefit from it by using a pointer to its base class LCD (a pure abstract LCD class). This would give you support for a wide range of drivers controlling Hitachi based LCDs.

Not only is it 3.5 times faster than the original LIquidCrystal library, it also gives you the flexibility of being compatible with a wide range of LCDs out there without changing a single line of code.

Just FYI, I am thinking about a slightly different approach compared with a base class approach. For input devices, I totally did base class approach and built my library from ground up to support all possible input devices. For displays, I'm thinking about using ANSI escape codes to control everything. I will need to write support for every new type of display so all my UI command to the displays will be entirely string commands. :slight_smile:

At this time MENWIZ needs only few methods of the LiquidCrystal class. It should work with any lib providing such methods. I cannot test it immediately as I have not a standard 4/8 wires lcd in my hand, but I'll do it after easter time.
I include the two different libraries depending on the #define I2C (first line in MENWIZ.h file). Another way could be difefrentiate MENWIZ.h in MENWIZ_I2C.h and MENWIZ.h.

The code needed to implememt the example Olikraus suggested is as following. Most part of the code is just to implement the screen showing selection choice.
An other usefull comparison could be done on a real request arose in this forum (see the GOLF code example here above).

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <buttons.h>
#include <MENWIZ.h>

menwiz menu;
LiquidCrystal_I2C lcd(0x27,20,4);

int choice=0;

void setup(){
  _menu *r;

  menu.begin(&lcd,20,4);
  menu.navButtons(9,10,7,8,12,11);

  r=menu.addMenu(MW_ROOT,NULL,"TEST MENU");           
    r->addVar(MW_LIST,&choice);                    
    r->addItem(MW_LIST,"Line 1");             
    r->addItem(MW_LIST,"Line 2");           
    r->addItem(MW_LIST,"Line 3");     
    r->addItem(MW_LIST,"Line 4");     
          
  menu.addUsrScreen(msc,3000);
  }

void loop(){
  menu.draw(); 
  }

//USER SCREEN SHOWED AFTER 3 SECS SINCE LAST BUTTON PUSH
void msc(){
  Serial.println(choice);
  lcd.setCursor(0, 0);
  lcd.print("Total uptime ");lcd.print(millis()/1000);
  lcd.setCursor(0, 1);
  lcd.print("Choice is ");lcd.print((int) choice);
  lcd.setCursor(0, 2);
  lcd.print("                    ");
  lcd.setCursor(0, 3);
  lcd.print("                    ");
  }