MENWIZ: yet another character lcd menu wizard library

Great. I never thought about 3 button mode... :slight_smile:

at a first glance:

  • You are calling freeMemory() instead of tree.freeRam()
  • "confirm to RUN" is the default associated label in order to let the user be warned about starting action. It will be possible to toggle it on/off by next setBehaviour method in the next version
  • about the label missing ... It should'nt. I'll try to investigate better

EDIT in the quick tour doc there was the wrong reference to freeMemory. Is has been incorporated by menwiz class and changed to freeRam, as it returns the free ram and not the prog or eeprom memory. Please be sure to use the latest version of lib at GitHub - brunialti/MENWIZ: ARDUINO LCD menu library: short user code to manage complex menu structures

brunialti:
Great. I never thought about 3 button mode... :slight_smile:

at a first glance:

  • You are calling freeMemory() instead of tree.freeRam()
  • "confirm to RUN" is the default associated label in order to let the user be warned about starting action. It will be possible to toggle it on/off by next setBehaviour method in the next version
  • about the label missing ... It should'nt. I'll try to investigate better

EDIT in the quick tour doc there was the wrong reference to freeMemory. Is has been incorporated by menwiz class and changed to freeRam, as it returns the free ram and not the prog or eeprom memory. Please be sure to use the latest version of lib at GitHub - brunialti/MENWIZ: ARDUINO LCD menu library: short user code to manage complex menu structures

Only a matter of time before someone wants 5 buttons :wink: but then MENWIZ was flexible enough for me.
I'm calling freeMemory() as I've included it myself since the tree.freeRam() didn't compile.
I will beef this up then while I wait on the next version with setBehaviour.

I must say I've struggled with some other menu librarys, not so with this one :smiley:

Yeah. Someone could also ask for 2.5 buttons ...
Be sure you've got the latest version of menwiz as freeRam must compile ...
Until the first version 1.0 (soon to be delivered if I get positive feedback) I'm touching the lib and sometime I've got some regression error. Currently I'm struggling with raspi/arduino integration and I've not time enough to finalize this lib.

just got my raspi today as it goes :stuck_out_tongue:

I'll look at the freeRam, after some raspi fiddling of course

Please find the new lib version at https://github.com/brunialti/MENWIZ/downloads .
There are the new following methods:

void menwiz::writeEeprom(); //write all MENWIZ binded variables to eeprom
void menwiz::readEeprom(); //read from eeprom the previously saved variable values
void _menu::setBehaviour(byte behaviour, boolean value); //toggle action confirmation, horizontal/vertical scrolling of list

In order to let you have a better idea of the changes, please read the updated quick tour pdf doc here attached.
Due to the eeprom support it is now necessary to include EEPROM.h file in the user sketch. This introduce a small backward compatibility break.

Feedbacks are welcome

MENWIZ_0-6-0_QUICK TOUR.pdf (139 KB)

Hi, do you have an example of the setBehaviour? I'm not sure how it should be declared and against what object? tree. _menu etc?

I'm getting errors like "MW_ACTION_IMMEDIATE is not defined"

Hi, to be clear I'm using version 0.6.1
I'd defined my own button press detection as before, and emulated an exit press by fireing a custom function. While it asked for confirm to run when pressing confirm it ran. Now it doesn't run, asks for confirm, i press confirm and it goes back to the menu list

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_SR.h>
#include <buttons.h>
#include <EEPROM.h>
#include <MENWIZ.h>
#include <MemoryFree.h> 

//INTERRUPT BUTTONS
const int pin = 13;  // test for interrupt
const int buttonPin = A0; // buttons can be read here
volatile byte buttonAct = 0; // flag, follow button press
volatile int buttonValue = 0; // button pressed analogue value 
volatile int buttonPressed = 0;  // which button was pressed
volatile byte lastButtonPressed = 0; // prev button pressed
volatile byte state = LOW; // state of led on pin13
volatile byte lastState = HIGH; // unused?

/*
// DEFINE ARDUINO PINS FOR THE NAVIGATION BUTTONS
 #define UP_BOTTON_PIN       9
 #define DOWN_BOTTON_PIN     5
 #define LEFT_BOTTON_PIN     7 
 #define RIGHT_BOTTON_PIN    8
 #define CONFIRM_BOTTON_PIN  3
 #define ESCAPE_BOTTON_PIN   4
 */
// #define MW_ACTION_IMMEDIATE
menwiz tree;
// create lcd obj using LiquidCrystal lib
LiquidCrystal_SR lcd(10, 11, 12); //Data,Clk,Enable.

int  list,sp=110;

void setup(){
  Serial.begin(19200); 
  Serial.println(F("STARTED"));
  pinMode(pin, OUTPUT);
  attachInterrupt(0, readButtons, RISING);  


  _menu *r,*s1,*s2;

  
  tree.addUsrNav(navMenu);
  tree.begin(&lcd,20,4); //declare lcd object and screen size to menwiz lib
//_menu::setBehaviour(byte MW_ACTION_IMMEDIATE, false); 
  r=tree.addMenu(MW_ROOT,NULL,"Root");
  s1=tree.addMenu(MW_SUBMENU,r,"SUBMENU");
  s2=tree.addMenu(MW_VAR,s1,"Node3");
  s2->addVar(MW_LIST,&list);
  s2->addItem(MW_LIST,"Option1");
  s2->addItem(MW_LIST,"Option2");
  s2->addItem(MW_LIST,"Option3");
  s2=tree.addMenu(MW_VAR,s1,"Node4");
  s2->addVar(MW_AUTO_INT,&sp,0,120,10);
  s2=tree.addMenu(MW_VAR,s1,"Exit");
  s2->addVar(MW_ACTION,exitFunct); 

 

  // tree.navButtons(UP_BOTTON_PIN,DOWN_BOTTON_PIN,LEFT_BOTTON_PIN,RIGHT_BOTTON_PIN,ESCAPE_BOTTON_PIN,CONFIRM_BOTTON_PIN);
}
int i = 0;
void loop(){
  
  if(i > 1000){
      Serial.print(F("freeMemory() reports "));
    Serial.println( freeMemory() );
    
    i = 0;
  }else{
    i++;
  } 
  tree.draw(); 
}

void myfunc(){
  Serial.println("ACTION FIRED!");
}


void readButtons(){
  state = !state;
  digitalWrite(pin, state);
  /*   511 : none | 614 : up | 768 : down | 1023 : select   */
  lastButtonPressed = buttonPressed;

  buttonValue = analogRead(buttonPin);
  if(buttonValue >= 900) {
    buttonPressed = 3; // select

  }
  else if(buttonValue >= 700){    
    buttonPressed = 2;  // down

  }
  else if(buttonValue >= 600){    
    buttonPressed = 1;  // up

  }
  buttonAct = 1;
  Serial.print(buttonPressed);
  Serial.print(" XX ");
  Serial.println(buttonValue);

}


void exitFunct(){
  Serial.println("EXIT Called");
  buttonAct = 1;
  buttonPressed = 4;
 
}

int navMenu(){
  //MenuItem currentMenu=menu.getCurrent();
  if(buttonAct){
    buttonAct = 0;
    switch (buttonPressed){
    case 4: //simulated escape
      return MW_BTE;
      break; 
    case 3: //select     
      return MW_BTC;
      break;
    case 2: //down
      return MW_BTD;
      break;     
    case 1: //up
      return MW_BTU;
      break;     
    }
  }
  else{
    return MW_BTNULL;
  }
}

there is a misalignment between the quick tour doc and the code.
The syntax is

xxx.setBehaviour(MW_ACTION_CONFIRM,false);

Does the example compile and run ? Iin the example there is also this behaviour commented.

reading better your code, it is wrong.
The setBehaviour is a menu method that must be called after the menu declarations and for each variable object you want to affect, not before

This is a chunk of real code working

 s1=menu.addMenu(MW_VAR,r,F("TEST ACTION"));             
      s1->addVar(MW_ACTION,act);                         
      s1->setBehaviour(MW_ACTION_CONFIRM,false);

brunialti:
there is a misalignment between the quick tour doc and the code.
The syntax is

xxx.setBehaviour(MW_ACTION_CONFIRM,false);

Does the example compile and run ? Iin the example there is also this behaviour commented.

Yep it was the CONFIRM vs IMMEDIATE. If set false works as expected.
If not declared at all, asks for confirm to run but don't run when confirm is pressed (not an issue for me)
If declared as true, asks for confirm to run but don't run when confirm is pressed.

Regarding my mistake declaring referenced in your last reply. That was me trying to find a working way but using the none existent MW_ACTION_IMMEDIATE

I cannot replicate what you noticed.
I downloaded the 0.6.1. version from git hub and modified the example as following

//MENWIZ ESAMPLE
#include <Wire.h>
//INSERT ALL THE FOLLOWING 5 INCLUDES AFTER INCLUDING WIRE LIB 
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <buttons.h>
#include <MENWIZ.h>
#include <EEPROM.h>

// DEFINE ARDUINO PINS FOR THE NAVIGATION BUTTONS
#define UP_BUTTON_PIN       9
#define DOWN_BUTTON_PIN     10
#define LEFT_BUTTON_PIN     7 
#define RIGHT_BUTTON_PIN    8
#define CONFIRM_BUTTON_PIN  12
#define ESCAPE_BUTTON_PIN   11

//Create global object menu and lcd
menwiz menu;
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Addr, En, Rw, Rs, d4, d5, d6, d7, backlighpin, polarity

//instantiate global variables to bind to menu
int      tp=0;
float    f=26.0;
boolean  bb=0;
byte     b=50;

void setup(){
  _menu *r,*s1,*s2;
  _var *v; 
  int  mem;

  Serial.begin(19200);  
  
  // have a look on memory before menu creation
  Serial.println(sizeof(menwiz));
  mem=menu.freeRam();
  
  // inizialize the menu object (20 colums x 4 rows)
  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 child (submenu) node to the root menu
      s2=menu.addMenu(MW_VAR,s1,"Test list");            //add a terminal node (that is "variable"); 
          s2->addVar(MW_LIST,&tp);                       //create a variable of type "option list".. 
          s2->addItem(MW_LIST,"option 1");               //add option to the OPTION LIST
          s2->addItem(MW_LIST,"option 2");               //add option to the OPTION LIST
          s2->addItem(MW_LIST,"option 3");               //add option to the OPTION LIST
//          s2->setBehaviour(MW_SCROLL_HORIZONTAL,true); //...if you like horizontal scroll  

      s2=menu.addMenu(MW_VAR,s1,"Test float var");       //add a terminal node (that is "variable"); 
          s2->addVar(MW_AUTO_FLOAT,&f,11.00,100.00,0.5); //create a variable of type "float number"... 
                                                         //...associated to the terminal node and bind it to the app variable "f" of type float
      s2=menu.addMenu(MW_VAR,s1,"Test byte var");        //add a terminal node (that is "variable"); 
          s2->addVar(MW_AUTO_BYTE,&b,25,254,10);         //create a variable of type "byte"...
                                                         //...associated to the terminal node and bind it to the app variable "b" of typr byte
      s2=menu.addMenu(MW_VAR,s1,"Test boolean var");     //add a terminal node (that is "variable"); 
          s2->addVar(MW_BOOLEAN,&bb);                    //create a variable of type "boolean" 

    s1=menu.addMenu(MW_VAR,r,"TEST ACTION1");             //add a terminal node (that is "variable") create an "action" associated to the terminal node... 
      s1->addVar(MW_ACTION,act1);                         //the act function as default will be called when enter button is pushed
      //s1->setBehaviour(MW_ACTION_CONFIRM,false);         //...if you don't need action confirmation


    s1=menu.addMenu(MW_VAR,r,"TEST ACTION2");             //add a terminal node (that is "variable") create an "action" associated to the terminal node... 
      s1->addVar(MW_ACTION,act2);                         //the act function as default will be called when enter button is pushed
      s1->setBehaviour(MW_ACTION_CONFIRM,false);         //...if you don't need action confirmation


  //declare navigation buttons (required)
  menu.navButtons(UP_BUTTON_PIN,DOWN_BUTTON_PIN,LEFT_BUTTON_PIN,RIGHT_BUTTON_PIN,ESCAPE_BUTTON_PIN,CONFIRM_BUTTON_PIN);

  //(tip): use preallocated internal menu.sbuf buffer to save memory space!
  sprintf(menu.sbuf,"MENWIZ TEST V %s\n.Free mem. :%d\n.Used mem  :%d\n.Lap secs  :%d",menu.getVer(),menu.freeRam(),mem-menu.freeRam(),5);
  //(optional) create a splash screen (duration 5.000 millis)with some usefull infos the character \n marks end of LCD line 
  menu.addSplash((char *) menu.sbuf, 5000);
  //(optional)create a user define screen callback to activate after 10 secs (10.000 millis) since last button push 
  menu.addUsrScreen(msc,10000);
  }

void loop(){
  menu.draw(); 
  //PUT APPLICATION CODE HERE (if any)
  }

// user defined callbacks
void msc(){
  sprintf(menu.sbuf,"User screen\nUptime (s): %ld\nFree mem  : %d\n\n",millis()/1000,(int)menu.freeRam());
  menu.drawUsrScreen(menu.sbuf);
  }
  
void act1(){
  Serial.println("FIRED ACTION1");
 }

void act2(){
  Serial.println("FIRED ACTION2");
 }
 
void savevar(){
  menu.writeEeprom();
  }
  
void loadvar(){
  menu.readEeprom();
  }

The above code works as expected: the first action ask for confirmation before to write to serial, and second action do'not (EDIT but write correctly to serial).
I think something in the custom button management routine could bias the result.
I'll check.

I can only assume it is something ot do with the custom button function given that's the only difference.
The button pin defines are commented out, could that be a problem?

Here is my full code if it helps:

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_SR.h>
#include <buttons.h>
#include <EEPROM.h>
#include <MENWIZ.h>
//#include <MemoryFree.h> 

//INTERRUPT BUTTONS
const int pin = 13;  // test for interrupt
const int buttonPin = A0; // buttons can be read here
volatile byte buttonAct = 0; // flag, follow button press
volatile int buttonValue = 0; // button pressed analogue value 
volatile int buttonPressed = 0;  // which button was pressed
volatile byte lastButtonPressed = 0; // prev button pressed
volatile byte state = LOW; // state of led on pin13
volatile byte lastState = HIGH; // unused?

/*
// DEFINE ARDUINO PINS FOR THE NAVIGATION BUTTONS
 #define UP_BOTTON_PIN       9
 #define DOWN_BOTTON_PIN     5
 #define LEFT_BOTTON_PIN     7 
 #define RIGHT_BOTTON_PIN    8
 #define CONFIRM_BOTTON_PIN  3
 #define ESCAPE_BOTTON_PIN   4
 */
 #define MW_ACTION_IMMEDIATE
menwiz tree;
// create lcd obj using LiquidCrystal lib
LiquidCrystal_SR lcd(10, 11, 12); //Data,Clk,Enable.

int  list,sp=110;

void setup(){
  Serial.begin(19200); 
  Serial.println(F("STARTED"));
  pinMode(pin, OUTPUT);
  attachInterrupt(0, readButtons, RISING);  


  _menu *r,*s1,*s2;

  
  tree.addUsrNav(navMenu);
  tree.begin(&lcd,20,4); //declare lcd object and screen size to menwiz lib
 
  r=tree.addMenu(MW_ROOT,NULL,"Root");
  s1=tree.addMenu(MW_SUBMENU,r,"SUBMENU");
  s2=tree.addMenu(MW_VAR,s1,"Node3");
  s2->addVar(MW_LIST,&list);
  s2->addItem(MW_LIST,"Option1");
  s2->addItem(MW_LIST,"Option2");
  s2->addItem(MW_LIST,"Option3");
  s2=tree.addMenu(MW_VAR,s1,"Node4");
  s2->addVar(MW_AUTO_INT,&sp,0,120,10);
  s2=tree.addMenu(MW_VAR,s1,"Exit");
  s2->addVar(MW_ACTION,exitFunct); 
  s2->setBehaviour(MW_ACTION_CONFIRM,false);
 

  // tree.navButtons(UP_BOTTON_PIN,DOWN_BOTTON_PIN,LEFT_BOTTON_PIN,RIGHT_BOTTON_PIN,ESCAPE_BOTTON_PIN,CONFIRM_BOTTON_PIN);
}
int i = 0;
void loop(){
  
  if(i > 1000){
      Serial.print(F("freeRam() reports "));
    Serial.println( tree.freeRam() );
    
    i = 0;
  }else{
    i++;
  } 
  tree.draw(); 
}

void myfunc(){
  Serial.println("ACTION FIRED!");
}


void readButtons(){
  state = !state;
  digitalWrite(pin, state);
  /*   511 : none | 614 : up | 768 : down | 1023 : select   */
  lastButtonPressed = buttonPressed;

  buttonValue = analogRead(buttonPin);
  if(buttonValue >= 900) {
    buttonPressed = 3; // select

  }
  else if(buttonValue >= 700){    
    buttonPressed = 2;  // down

  }
  else if(buttonValue >= 600){    
    buttonPressed = 1;  // up

  }
  buttonAct = 1;
  Serial.print(buttonPressed);
  Serial.print(" XX ");
  Serial.println(buttonValue);

}


void exitFunct(){
  Serial.println("EXIT Called");
  buttonAct = 1;
  buttonPressed = 4;
 
}

int navMenu(){
  //MenuItem currentMenu=menu.getCurrent();
  if(buttonAct){
    buttonAct = 0;
    switch (buttonPressed){
    case 4: //simulated escape
      return MW_BTE;
      break; 
    case 3: //select     
      return MW_BTC;
      break;
    case 2: //down
      return MW_BTD;
      break;     
    case 1: //up
      return MW_BTU;
      break;     
    }
  }
  else{
    return MW_BTNULL;
  }
}

The code seems to be ok.
By the way it would be more polite to call addUsrNav after begin method.
I cannot test the code as i would need to set up the same custom analog buttons.

You can try the following on my behalf :slight_smile:
Replace the actual method addUsrNav in MENWIZ.cpp with the following

void menwiz::addUsrNav(int (*f)()){
  ERROR(0);
  btx=(_nav*)malloc(sizeof(_nav));if(btx==NULL){ERROR(900); return;}
  usrNav.fl=true;
  usrNav.fi=f;
  }

Let me know

I'll give it a try tonight and let you know. I'm not near my Arduino at the moment.

http://arduino.cc/forum/index.php?topic=109796.0 talks about the button setup if you did want to try the buttons.

Please find in https://github.com/brunialti/MENWIZ/downloads
the latest version of MENWIZ.

This version should be my final one (bug fixing apart).

Starting fron this versione the addMenu and addItem (the two methods used to build menu structure) accepts string labels wrapped by F() operator. It saves quite a lot of flash memory (but requires little changes to the sketch code of the previous versions).

Some more behaviours were added to option list object: now it is possible to display options in 2 or 3 columns.

There is still the dannix issue to check, but it seems limited to custom input devices using the MW_ACTION_CONFIRM behaviour.

Here attached the library guide.

MENWIZ_1_0_0_QUICK TOUR.pdf (148 KB)

Hi, sorry for the delay getting back to you. I've made the changes you suggested

//void menwiz::addUsrNav(int (*f)()){
//  usrNav.fl=true;
//  usrNav.fi=f;
//  }
void menwiz::addUsrNav(int (*f)()){
  ERROR(0);
  btx=(_nav*)malloc(sizeof(_nav));if(btx==NULL){ERROR(900); return;}
  usrNav.fl=true;
  usrNav.fi=f;
  }

As before,
setBehaviour false: no confirm and runs ("Exit Called in serial output")
setBehaviour true: confirm msg shown but no exit function run ("button press registered but no serial outout for "Exit called"")
no setBehaviour declaration: same as setBehaviour true

I'm going to update the lib with what you posted above and retry.

Hi, with the latest library it's the same as above. If confirm msg shown, confirm does not run the custom function.

Additionally now if setBehaviour is false, it calls the exit function but when the menu is redrawn it leaves node4 under SUBMENU which shouldn't be there, it's not at prog startup neither can you select Node4

menus is setup like so:

SUBMENU
Node3
Option 1
Option 3
Option 3
Node4
0 [110] 120
Exit

So node4 should always be the next level up. I didn't reapply the addUsrNav function changes to this library test.

Bruno,

Good to see the new 1.0 version and the new extra's.
I have to appologize for not creating yet a new bie manual.
Having heavy time issues on several projects.

Paco

@dannix

It is hard to say why. I would need to setup the analog buttons as yours, but I have not some of the components.
I'll try to make an old fashioned code inspection ... With the new lib v1.0.0 you do not need to apply the patch I sent.
Did you use the errorMessage during debug after each instruction? Did you get some message?
I'm committed to solve it before holyday :slight_smile:

@backbone

nice to read you again. If you had a practical application of Menwiz (and it works :slight_smile: ), it could be interesting share it with the communiy, even criticism,