Go Down

Topic: MENWIZ: yet another character lcd menu wizard library (Read 85519 times) previous topic - next topic

brunialti

Apr 03, 2012, 05:00 pm Last Edit: Apr 29, 2014, 10:13 pm by brunialti Reason: 1
EDIT: latest MENWIZ version 1.3.2 beta  is now available on https://github.com/brunialti/MENWIZ_1_3_2
       please sign as follower in order to be notified about new versions!

I had to implement a sketch for sensor management with LCD to set some parameters and to show the mesure in real time.
I abstracted the menu management and i wrote a new library called MENWIZ (menu wizard).

The pros are:
- non blocking: that is the library does not take mu control while working, usefull if your sketch need to work while you are changing the settings
- simple to use (few primitives and compact code)
- user defined splash screen (optional): if you need to show something at startup. The time is user defined.
- user defined callback default screen (optional) activated after a user defined elapsed time since last button push. The menu gently leave the screen to the user, untill any button is pushed.  
- automatic user defined variables binding: that is all parameters changes inside the menus affects also the user defined binded variables
- user defined navigation callback routine (so you can use any navigation device you like, inside the framework)
- actions: callback routines to be executed from menu

Warnings
- MENWIZ requires LiquidCrystal_I2C and Buttons libraries, enclosed with the package
- I tested MENWIZ mainly with 4x20 I2C lcd screens. It *should* work also with other lcds (e.g 16x2)), but i did'nt testvit extensively
- Menu labels require memory. Be carefull not to run out of  memory (a simple memory-check function is included inthe lib: use it!)

Latest version of MENWIZ:
https://github.com/brunialti/MENWIZ/downloads

Any feed back is welcome. Should you be interested I can mantain and expand.
Keep in mind that I want to keep it simple.

The TEST_LIB zip file contains the library used to test the MENWIZ lib.
It should be standard, neverthless should you have some problem, try using it

robtillaart

Please change the title to lowercase, uppercase is considered shouting ...
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)


robtillaart

No need to apologize, thank you for "not shouting" ;)

I like new libraries, this is what makes open source great !

Do you have some sample sketches as show case?



Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

brunialti

#4
Apr 03, 2012, 08:49 pm Last Edit: Apr 03, 2012, 09:05 pm by brunialti Reason: 1
Here following the example:


Code: [Select]
#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 tp=2;
int  gp=26;
boolean wr=0;

void setup(){
 char b[84];
 _menu *r,*s1,*s2;
 _var *v;
 int  mem;

 Serial.begin(19200);  
 // have a look on memory before menu creation
 mem=menu.freeRam();
 
 // 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,"MAIN MENU");           //create a root menu at first (required)
   s1=menu.addMenu(MW_SUBMENU,r,"MEASURE SUBMENU");   //add a submenu node to the root menu
     s2=menu.addMenu(MW_VAR,s1,"Choose T scale");//add a terminal node in the menu tree (that is "variable");
         s2->addVar(MW_LIST,&tp);                    //create the terminal node variable of type OPTION LIST and bind it to the app variable "tp"
         s2->addItem(MW_LIST,"Celsius");             //add an option to the OPTION LIST
         s2->addItem(MW_LIST,"Farenheit");           //add an other option to the OPTION LIST
         s2->addItem(MW_LIST,"Raw sensor data");     //add the third and last option to the OPTION LIST
     s2=menu.addMenu(MW_VAR,s1,"Set T1 trigger");
         s2->addVar(MW_AUTO_INT,&gp,10,100,2);
     s2=menu.addMenu(MW_VAR,s1,"Enable warmer");
         s2->addVar(MW_BOOLEAN,&wr);

 //(optional) create a splash screen (lap 6 seconds) with some usefull infos
 //the character # marks end of line
 //(tip): use preallocated internal menu.sbuf buffer to save memory space!
 sprintf(menu.sbuf,"MENWIZ TEST V %s#Free memory   : %d#Menu mem alloc: %d#Splash lap sec: %d",menu.getVer(),menu.freeRam(),mem-menu.freeRam(),6);
 menu.addSplash((char *) menu.sbuf, 6000);

 //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);
 // create a user define screen callback to activate after 7 secs since last button push
 menu.addUsrScreen(msc,7000);
 }

void loop(){
 // NAVIGATION MANAGEMENT & DRAWING ON LCD. NOT BLOCKING
 menu.draw();
 //PUT APPLICATION CODE HERE
 }

// user defined default screen
void msc(){
 lcd.setCursor(0,0);
 lcd.print("                    ");
 lcd.setCursor(0,1);
 lcd.print("Test user screen");
 lcd.setCursor(0,2);
 lcd.print(millis()/1000);  lcd.print("        ");
 lcd.setCursor(0,3);
 lcd.print("                    ");
 }

liudr

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.

brunialti

#6
Apr 04, 2012, 05:20 pm Last Edit: Apr 07, 2012, 11:35 am by brunialti Reason: 1
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... :-)

Here following please find an example I wrote for an other thread (http://arduino.cc/forum/index.php/topic,98875.msg748675.html#new), 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.

Code: [Select]
//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;
   }
 }






liudr

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?

brunialti

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 ...



liudr

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.

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...

liudr


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.  ;)

brunialti

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

liudr


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


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.

olikraus

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







Go Up