Spirik / GEM

Good Enough Menu for Arduino
GNU Lesser General Public License v3.0
239 stars 36 forks source link

error while trying to compile #37

Closed e1z0 closed 2 years ago

e1z0 commented 2 years ago
In file included from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.h:40,
                 from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.cpp:34:
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:197:25: error: section attribute not allowed for 'crlf'
  197 |     char const PROGMEM *crlf;
      |                         ^~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:283:40: error: section attribute not allowed for 's'
  283 |     void putstr_P (const char PROGMEM *s);
      |                    ~~~~~~~~~~~~~~~~~~~~^
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:340:42: error: section attribute not allowed for 'data'
  340 |     void write_P (const uint8_t PROGMEM *data, int length);
      |                   ~~~~~~~~~~~~~~~~~~~~~~~^~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:568:93: error: section attribute not allowed for 's'
  568 |     void setString_P(uint8_t posX, uint8_t posY, uint8_t justification, const char PROGMEM *s)
      |                                                                         ~~~~~~~~~~~~~~~~~~~~^
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:842:59: error: section attribute not allowed for 'sprite'
  842 |     void loadSprite_P (uint8_t id, const uint8_t PROGMEM *sprite)
      |                                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:857:47: error: section attribute not allowed for 'sprite_pixels'
  857 |                        const uint8_t PROGMEM *sprite_pixels)
      |                        ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:871:71: error: section attribute not allowed for 'sprite'
  871 |     void loadSprite_P (uint8_t id, int length, const uint8_t PROGMEM *sprite)
      |                                                ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:931:43: error: section attribute not allowed for 'sprite'
  931 |                    const uint8_t PROGMEM *sprite)
      |                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:948:59: error: section attribute not allowed for 'sprite_pixels'
  948 |                    uint8_t height, const uint8_t PROGMEM *sprite_pixels)
      |                                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:965:55: error: section attribute not allowed for 'sprite'
  965 |                    int length, const uint8_t PROGMEM *sprite)
      |                                ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:990:62: error: section attribute not allowed for 'xylist'
  990 |     void drawPolygon_P (uint8_t mode, const uint8_t PROGMEM *xylist)
      |                                       ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:1013:48: error: section attribute not allowed for 'xylist'
 1013 |     void drawPolygon_P (const uint8_t PROGMEM *xylist)
      |                         ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:1039:60: error: section attribute not allowed for 'xylist'
 1039 |     void drawLines_P (uint8_t mode, const uint8_t PROGMEM *xylist)
      |                                     ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:1062:46: error: section attribute not allowed for 'xylist'
 1062 |     void drawLines_P (const uint8_t PROGMEM *xylist)
      |                       ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
In file included from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.cpp:34:
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.h:97:43: error: section attribute not allowed for 'sprite'
   97 |     void setSplash(const uint8_t PROGMEM *sprite);       // Set custom sprite displayed as the splash screen when GEM is being initialized. Should be called before GEM::init().
      |                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.h:138:28: error: section attribute not allowed for '_splash'
  138 |     const uint8_t PROGMEM *_splash;
      |                            ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.cpp:123:44: error: section attribute not allowed for 'sprite'
  123 | void GEM::setSplash(const uint8_t PROGMEM *sprite) {
      |                     ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
exit status 1
Error compiling for board NodeMCU 1.0 (ESP-12E Module).

AltSerialGraphicLCD v1.38

Spirik commented 2 years ago

Hi there!

Is AltSerialGraphicLCD compatible with this board at all? Can you confirm, that it is possible to compile a simple sketch (say example that comes with AltSerialGraphicLCD library or your own simple sketch which will include this library without including GEM)?

Spirik commented 2 years ago

Upon further investigation it seems that indeed AltSerialGraphicLCD doesn't support NodeMCU 1.0 (ESP-12E) board.

However, I've been able to compile example for U8g2 version of GEM. So may be that is the version of GEM that will suit your hardware environment better.

e1z0 commented 2 years ago

thank you!

Spirik commented 2 years ago

No problem=)

e1z0 commented 2 years ago

I'm getting the same error when trying to compile U8g2 based example...

In file included from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.h:40,
                 from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.cpp:34:
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:197:25: error: section attribute not allowed for 'crlf'
  197 |     char const PROGMEM *crlf;
      |                         ^~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:283:40: error: section attribute not allowed for 's'
  283 |     void putstr_P (const char PROGMEM *s);
      |                    ~~~~~~~~~~~~~~~~~~~~^
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:340:42: error: section attribute not allowed for 'data'
  340 |     void write_P (const uint8_t PROGMEM *data, int length);
      |                   ~~~~~~~~~~~~~~~~~~~~~~~^~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:568:93: error: section attribute not allowed for 's'
  568 |     void setString_P(uint8_t posX, uint8_t posY, uint8_t justification, const char PROGMEM *s)
      |                                                                         ~~~~~~~~~~~~~~~~~~~~^
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:842:59: error: section attribute not allowed for 'sprite'
  842 |     void loadSprite_P (uint8_t id, const uint8_t PROGMEM *sprite)
      |                                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:857:47: error: section attribute not allowed for 'sprite_pixels'
  857 |                        const uint8_t PROGMEM *sprite_pixels)
      |                        ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:871:71: error: section attribute not allowed for 'sprite'
  871 |     void loadSprite_P (uint8_t id, int length, const uint8_t PROGMEM *sprite)
      |                                                ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:931:43: error: section attribute not allowed for 'sprite'
  931 |                    const uint8_t PROGMEM *sprite)
      |                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:948:59: error: section attribute not allowed for 'sprite_pixels'
  948 |                    uint8_t height, const uint8_t PROGMEM *sprite_pixels)
      |                                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:965:55: error: section attribute not allowed for 'sprite'
  965 |                    int length, const uint8_t PROGMEM *sprite)
      |                                ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:990:62: error: section attribute not allowed for 'xylist'
  990 |     void drawPolygon_P (uint8_t mode, const uint8_t PROGMEM *xylist)
      |                                       ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:1013:48: error: section attribute not allowed for 'xylist'
 1013 |     void drawPolygon_P (const uint8_t PROGMEM *xylist)
      |                         ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:1039:60: error: section attribute not allowed for 'xylist'
 1039 |     void drawLines_P (uint8_t mode, const uint8_t PROGMEM *xylist)
      |                                     ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/AltSerialGraphicLCD/AltSerialGraphicLCD.h:1062:46: error: section attribute not allowed for 'xylist'
 1062 |     void drawLines_P (const uint8_t PROGMEM *xylist)
      |                       ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
In file included from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.cpp:34:
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.h:97:43: error: section attribute not allowed for 'sprite'
   97 |     void setSplash(const uint8_t PROGMEM *sprite);       // Set custom sprite displayed as the splash screen when GEM is being initialized. Should be called before GEM::init().
      |                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.h:138:28: error: section attribute not allowed for '_splash'
  138 |     const uint8_t PROGMEM *_splash;
      |                            ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEM.cpp:123:44: error: section attribute not allowed for 'sprite'
  123 | void GEM::setSplash(const uint8_t PROGMEM *sprite) {
      |                     ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
exit status 1
Error compiling for board NodeMCU 1.0 (ESP-12E Module).
Spirik commented 2 years ago

That's because AltSerialGraphicLCD is still included from within GEM and is required during compilation. However, it is possible to explicitly exclude unnecessary libraries (in your case AltSerialGraphicLCD). For that you would need to edit config.h file that comes with the library. Please, see Configuration section of Readme for details.

Spirik commented 2 years ago

Try compiling U8g2 based example with the following config.h contents (with commented enable-glcd.h inclusion):

// #include "config/enable-glcd.h"         // Enable AltSerialGraphicLCD version of GEM
#include "config/enable-u8g2.h"         // Enable U8g2 version of GEM
#include "config/support-float-edit.h"  // Support for editable float and double variables (option selects support them regardless of this setting)
e1z0 commented 2 years ago

At last it works! Thank you again :)

Does it need 6 buttons or can it be configured to work with 3? Or it depends on the u8g2 library? Just found it, thanks again!

e1z0 commented 2 years ago

I see KeyDetector is the nice lib, can it be adapted to work with INPUT_PULLUP type of buttons?

Spirik commented 2 years ago

U8g2 comes with key-press detection that works with buttons wired with pullup resistors (so the LOW/GND means the button is pressed). While KeyDetector out of the box works only with buttons wired with pulldown resistors (so the HIGH means the button is pressed).

Unfortunately there is no option to change this parameter via KeyDetector library methods (or constructor). However it is very simple and straightforward library, so it's likely that it would be enough just to swap HIGH with LOW in source code here and here. You can import source code files directly into your project, if you don't want to mess with the code inside libraries folder.

I might consider adding this as an option in future releases, so thank you for suggestion! ;)

e1z0 commented 2 years ago

Yeah i've found that already and it didn't worked for me, maybe something wrong went with my hands, will try it tomorrow for sure. Thanks again!

Spirik commented 2 years ago

Sure, no prob!

As a followup, just to make sure we are not missing something here, check out KeyDetector, U8g2-based key detection and Control menu from your sketch sections in wiki.

Also there are wiring diagrams and schematics for buttons used in examples for AltSerialGraphicLCD (KeyDetector-based) and U8g2 versions.

Spirik commented 2 years ago

Just tried to swap HIGH with LOW in KeyDetector source code and it worked for me.

e1z0 commented 2 years ago

It's only showing that the up and down button is pressed when i press the up button, other buttons does not react.

/*
  Basic menu example using GEM library.

  Simple one page menu with one editable menu item associated with int variable, one with boolean variable,
  and a button, pressing of which will result in int variable value printed to Serial monitor if boolean variable is set to true.

  U8g2lib library is used to draw menu and to detect push-buttons presses.

  Additional info (including the breadboard view) available on GitHub:
  https://github.com/Spirik/GEM

  This example code is in the public domain.
*/

#include <GEM_u8g2.h>
#include "KeyDetector.h"

// Create an instance of the U8g2 library.
// Use constructor that matches your setup (see https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for details).
// This instance is used to call all the subsequent U8g2 functions (internally from GEM library,
// or manually in your sketch if it is required).
// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected

#define KEY_DOWN  1
#define KEY_UP  2
#define KEY_ENTER  3

const byte downPin = 0;
const byte upPin = 2;
const byte okPin = 10;

// Array of Key objects that will link GEM key identifiers with dedicated pins
Key keys[] = {{KEY_DOWN, downPin},{KEY_UP, upPin},{KEY_ENTER, okPin}};
// Create KeyDetector object
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key));

int PIN_CS = 15; // D8
int PIN_DC = 5; // D1
int PIN_RES = 4; // D2 // reset
int PIN_CLK = 14; // D5
int PIN_DIN = 13; // D7
int M_BUS = 1;
U8G2_PCD8544_84X48_1_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ PIN_CLK, /* data=*/ PIN_DIN, /* cs=*/ PIN_CS, /* dc=*/ PIN_DC, /* reset=*/ PIN_RES);  // Nokia 5110 Display

// Create variables that will be editable through the menu and assign them initial values
int number = -512;
boolean enablePrint = false;

// Create two menu item objects of class GEMItem, linked to number and enablePrint variables 
GEMItem menuItemInt("Number:", number);
GEMItem menuItemBool("Enable print:", enablePrint);

// Create menu button that will trigger printData() function. It will print value of our number variable
// to Serial monitor if enablePrint is true. We will write (define) this function later. However, we should
// forward-declare it in order to pass to GEMItem constructor
void printData(); // Forward declaration
GEMItem menuItemButton("Print", printData);

// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level.
// Menu can have multiple menu pages (linked to each other) with multiple menu items each
GEMPage menuPageMain("Main Menu");

// Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier
GEM_u8g2 menu(u8g2);

void setup() {
  // Serial communication setup
  Serial.begin(115200);
  pinMode(0, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);

  // U8g2 library init. Pass pin numbers the buttons are connected to.
  // The push-buttons should be wired with pullup resistors (so the LOW means that the button is pressed)
  u8g2.begin();
  // Menu init, setup and draw
  menu.init();
  setupMenu();
  menu.drawMenu();
}

void setupMenu() {
  // Add menu items to menu page
  menuPageMain.addMenuItem(menuItemInt);
  menuPageMain.addMenuItem(menuItemBool);
  menuPageMain.addMenuItem(menuItemButton);

  // Add menu page to menu and set it as current
  menu.setMenuPageCurrent(menuPageMain);
}

void loop() {
  // If menu is ready to accept button press...
  if (menu.readyForKey()) {
    // ...detect key press using U8g2 library
    // and pass pressed button to menu
    myKeyDetector.detect();

    switch (myKeyDetector.trigger) {
    case KEY_ENTER:
      Serial.println("Button ENTER pressed!");
      break;
    case KEY_UP:
      Serial.println("Button UP pressed!");
      break;
    case KEY_DOWN:
      Serial.println("Button DOWN pressed!");
      break;
  }
     menu.registerKeyPress(myKeyDetector.trigger);
  }
}

void printData() {
  // If enablePrint flag is set to true (checkbox on screen is checked)...
  if (enablePrint) {
    // ...print the number to Serial
    Serial.print("Number is: ");
    Serial.println(number);
  } else {
    Serial.println("Printing is disabled, sorry:(");
  }
}
Spirik commented 2 years ago

pinMode(0, INPUT_PULLUP) with modified KeyDetector works for me on UNO board, although I am not able to test it on U8g2 version of GEM at the moment (so I tested it on Adafruit GFX version currently in development).

I have no experience developing for ESP8266 boards (and NodeMCU 1.0 (ESP-12E) in particular) so not sure exactly, what is the deal there can be with internal pullup resistors. However, quick search shows that some pins won't work with INPUT_PULLUP as expected (notably GPIO15 (D8) and D4 (GPIO2)), see this link. So may be that is the case.

Also I made small edits to your code, e.g.:

/*
  Basic menu example using GEM library.

  Simple one page menu with one editable menu item associated with int variable, one with boolean variable,
  and a button, pressing of which will result in int variable value printed to Serial monitor if boolean variable is set to true.

  U8g2lib library is used to draw menu and to detect push-buttons presses.

  Additional info (including the breadboard view) available on GitHub:
  https://github.com/Spirik/GEM

  This example code is in the public domain.
*/

#include <GEM_u8g2.h>
#include "KeyDetector.h"

// Create an instance of the U8g2 library.
// Use constructor that matches your setup (see https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for details).
// This instance is used to call all the subsequent U8g2 functions (internally from GEM library,
// or manually in your sketch if it is required).
// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected

#define KEY_DOWN  GEM_KEY_DOWN
#define KEY_UP  GEM_KEY_UP
#define KEY_ENTER  GEM_KEY_OK

const byte downPin = 0;
const byte upPin = 2;
const byte okPin = 10;

// Array of Key objects that will link GEM key identifiers with dedicated pins
Key keys[] = {{KEY_DOWN, downPin},{KEY_UP, upPin},{KEY_ENTER, okPin}};
// Create KeyDetector object
// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key));
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 15);

int PIN_CS = 15; // D8
int PIN_DC = 5; // D1
int PIN_RES = 4; // D2 // reset
int PIN_CLK = 14; // D5
int PIN_DIN = 13; // D7
int M_BUS = 1;
U8G2_PCD8544_84X48_1_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ PIN_CLK, /* data=*/ PIN_DIN, /* cs=*/ PIN_CS, /* dc=*/ PIN_DC, /* reset=*/ PIN_RES);  // Nokia 5110 Display

// Create variables that will be editable through the menu and assign them initial values
int number = -512;
boolean enablePrint = false;

// Create two menu item objects of class GEMItem, linked to number and enablePrint variables 
GEMItem menuItemInt("Number:", number);
GEMItem menuItemBool("Enable print:", enablePrint);

// Create menu button that will trigger printData() function. It will print value of our number variable
// to Serial monitor if enablePrint is true. We will write (define) this function later. However, we should
// forward-declare it in order to pass to GEMItem constructor
void printData(); // Forward declaration
GEMItem menuItemButton("Print", printData);

// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level.
// Menu can have multiple menu pages (linked to each other) with multiple menu items each
GEMPage menuPageMain("Main Menu");

// Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier
GEM_u8g2 menu(u8g2);

void setup() {
  // Serial communication setup
  Serial.begin(115200);
  pinMode(downPin, INPUT_PULLUP);
  pinMode(upPin, INPUT_PULLUP);
  pinMode(okPin, INPUT_PULLUP);

  // U8g2 library init. Pass pin numbers the buttons are connected to.
  // The push-buttons should be wired with pullup resistors (so the LOW means that the button is pressed)
  u8g2.begin();
  // Menu init, setup and draw
  menu.init();
  setupMenu();
  menu.drawMenu();
}

void setupMenu() {
  // Add menu items to menu page
  menuPageMain.addMenuItem(menuItemInt);
  menuPageMain.addMenuItem(menuItemBool);
  menuPageMain.addMenuItem(menuItemButton);

  // Add menu page to menu and set it as current
  menu.setMenuPageCurrent(menuPageMain);
}

void loop() {
  // If menu is ready to accept button press...
  if (menu.readyForKey()) {
    // ...detect key press using U8g2 library
    // and pass pressed button to menu
    myKeyDetector.detect();

    switch (myKeyDetector.trigger) {
        case KEY_ENTER:
            Serial.println("Button ENTER pressed!");
            break;
        case KEY_UP:
            Serial.println("Button UP pressed!");
            break;
        case KEY_DOWN:
            Serial.println("Button DOWN pressed!");
            break;
    }
    menu.registerKeyPress(myKeyDetector.trigger);
  }
}

void printData() {
  // If enablePrint flag is set to true (checkbox on screen is checked)...
  if (enablePrint) {
    // ...print the number to Serial
    Serial.print("Number is: ");
    Serial.println(number);
  } else {
    Serial.println("Printing is disabled, sorry:(");
  }
}
e1z0 commented 2 years ago

I have removed problematic upPin from keys array and now down key started to work without coming back, but ok button does not work either and then i press down button the serial shows:

09:33:58.321 -> Button DOWN pressed!
09:33:58.982 -> Button ENTER pressed!

The last project i used with this hardware setup worked, i've used these functions to check button states

// gpio config
const int lightButton = 12; 
const int screenLight = 16;
int Light_state = LOW;
int button_state;
int lastbutton_state = LOW;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 30;
// down button
const int downButton = 0;
int downButtonState = 0;
int lastDownButtonState = 0;
volatile boolean down = false;
// up button
const int upButton = 2;
int upButtonState = 0;
int lastUpButtonState = 0;
volatile boolean up = false;
// select button
const int selectButton = 10;
int selectButtonState = 0;
int lastSelectButtonState = 0;
volatile boolean middle = false;
void checkIfDownButtonIsPressed()
{
  if (downButtonState != lastDownButtonState)
  {
    if (downButtonState == 0)
    {
      down = true;
    }
    delay(50);
  }
  //Serial.printf("down button state: %d\n",downButtonState);
  lastDownButtonState = downButtonState;
}

void checkIfUpButtonIsPressed()
{
  if (upButtonState != lastUpButtonState)
  {
    if (upButtonState == 0) {
      up = true;
    }
    delay(50);
  }
  //Serial.printf("up button state: %d\n", upButtonState);
  lastUpButtonState = upButtonState;
}
void checkIfSelectButtonIsPressed()
{
  if (selectButtonState != lastSelectButtonState)
  {
    if (selectButtonState == 0) {
      middle = true;
    }
    delay(50);
  }
  //Serial.printf("select button state: %d\n", selectButtonState);
  lastSelectButtonState = selectButtonState;
}

void ListenForToggleLCDLightButton()
{
  int data = digitalRead(lightButton);

  if (data != lastbutton_state) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (data != button_state) {
      button_state = data;

      if (button_state == HIGH) {
        Light_state = !Light_state;
      }
    }
  }
  digitalWrite(screenLight, Light_state);
  lastbutton_state = data;
}
void initDisplay()
{
  // enable led screen light button
  pinMode(lightButton, INPUT);
  pinMode(screenLight, OUTPUT);
    // turn off lcd by default
  digitalWrite(screenLight, LOW);
}

void loop() {
 downButtonState = digitalRead(downButton);
  selectButtonState = digitalRead(selectButton);
  upButtonState = digitalRead(upButton);
  checkIfDownButtonIsPressed();
  checkIfUpButtonIsPressed();
  checkIfSelectButtonIsPressed();
}

I actually have 4 buttons in my setup, one just for set display light and other 3 for controls, i'm almost used all gpio ports, i was thinking to change my setup using different strength resistors with one gpio port, maybe this should work better with your library?

I was using different menu system and tried to build one by myself, as i'm too new to the microcontrollers programming, it sucked all my time without any notice'able success, i found that your library is simple to use and can be adapted later for generating dynamic menus which will be needed for me in the later stage. As i'm trying to implement some remote control functionality based on the rest api standards with some 3rd party endpoints for my home. :)

Spirik commented 2 years ago

I suggest you try getting this KeyDetector example work first.

Based on the results you then can use the most successful setup in your menu project.

e1z0 commented 2 years ago

Ok lets try it :)

It's somehow used arduino installed library (although it was included with "" not with <>) and not the library inside the same folder, i've removed arduino library and it worked as you mentioned it above with modified KeyDecector.

Is there a possibility to implement custom button detection, using as example this lib: https://github.com/mickey9801/MultiButtons ? I just really need to free some gpio ports, because i'm using all of them now.

Is there a possibility to change font to a smaller size? It's too large on the 84x48 screen. I have tried:

#define disp_font u8g2_font_chikita_tr
u8g2.setFont(disp_font);

but it seems that library overrides the font selection.

Sorry for wasting your time and thank you again! :)

Spirik commented 2 years ago

You can use KeyDetector to detect multiplexed signals as well, i.e. several digital pins connected to a single analog input, similar to what MultiButtons does. You just have to specify signal threshold values that should trigger corresponding button, e.g.:

Key keys[] = {{KEY_UP, kPin, 127}, {KEY_RIGHT, kPin, 255}, {KEY_DOWN, kPin, 383}, {KEY_LEFT, kPin, 511}, {KEY_O, kPin, 639}, {KEY_X, kPin, 767}};

You can see this more complex example for more indepth theory behind it.

Spirik commented 2 years ago

To decrease font size of the menu items you can set menuItemHeight option to, say, 6. See Customization section of wiki for examples.

e1z0 commented 2 years ago

Awesome, just managed to fit it to my screen and played a little bit with menu, very nice. I just started dynamic menu generation and trying to figure out how to pass menu name to an function. For example i have menu with light switch menu items and i need to pass menu switch to the switch function so i can later pass the argument to the external api. I know it supports callbacks and it works fine, but just interesting if it's possible to pass a menu item name to a callback?

e1z0 commented 2 years ago

Tried to dynamic insert menu items on the sub menu and it crashed

Exception 3: LoadStoreError: Processor internal physical address or data error during load or store
PC: 0x4020d1e4: GEMItem::getMenuItemNext() at /Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.cpp line 308
EXCVADDR: 0x402059b5: GEM_u8g2::drawMenu() at /Users/devnull/Documents/Arduino/libraries/U8g2/src/U8g2lib.h line 185

Decoding stack results
0x40205953: GEM_u8g2::printMenuItems() at /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM_u8g2.cpp line 419
0x4020d120: U8G2::write(unsigned char const*, unsigned int) at /Users/devnull/Documents/Arduino/libraries/U8g2/src/U8g2lib.h line 345
0x40208c24: Print::write(char const*) at /Users/devnull/Library/Arduino15/packages/esp8266/hardware/esp8266/3.0.2/cores/esp8266/Print.h line 59
0x40208c48: Print::write(char const*) at /Users/devnull/Library/Arduino15/packages/esp8266/hardware/esp8266/3.0.2/cores/esp8266/Print.h line 57
0x40208da4: Print::print(char const*) at /Users/devnull/Library/Arduino15/packages/esp8266/hardware/esp8266/3.0.2/cores/esp8266/Print.cpp line 121
0x402059a6: GEM_u8g2::drawMenu() at /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM_u8g2.cpp line 237
0x40208954: HardwareSerial::write(unsigned char const*, unsigned int) at /Users/devnull/Library/Arduino15/packages/esp8266/hardware/esp8266/3.0.2/cores/esp8266/HardwareSerial.h line 193
0x40205de4: GEM_u8g2::menuItemSelect() at /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM_u8g2.cpp line 503
0x402060bd: GEM_u8g2::dispatchKeyPress() at /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM_u8g2.cpp line 927
0x402060d4: GEM_u8g2::registerKeyPress(unsigned char) at /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM_u8g2.cpp line 845
0x40204abf: loop() at /Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino line 170
0x4020a52c: loop_wrapper() at /Users/devnull/Library/Arduino15/packages/esp8266/hardware/esp8266/3.0.2/cores/esp8266/core_esp8266_main.cpp line 201

code

typedef struct {
char* real_name;
char* entity;
} Switch;

int SwitchNR = -512;

Switch Switches[] = {{"Kitchen", "light.kitchen_light" },{"Porch","light.porch_light"}};

void printData(); // Forward declaration
void loadPreset(); // another forward declaration
GEMItem menuItemButton("Print test", printData);
GEMPage menuPageMain("Meniu");
GEMPage menuLights("Lights");
GEMItem menuItemMainLights("Lights", menuLights);

void setupMenu() {
  menuPageMain.addMenuItem(menuItemButton);
  menuPageMain.addMenuItem(menuItemMainLights);
  for (int i=0; i<sizeof Switches/sizeof Switches[0]; i++) {
    char *entity = Switches[i].entity;
    char *real_name = Switches[i].real_name;
    Serial.printf("Item: %s -> %s\n",real_name,entity);
    GEMItem DynamicMenuItem(real_name,SwitchNR,loadPreset);
    menuLights.addMenuItem(DynamicMenuItem);
  }
  menuLights.setParentMenuPage(menuPageMain);
  // Add menu page to menu and set it as current
  menu.setMenuPageCurrent(menuPageMain);
}

void loadPreset() {
  // Do something, e.g. print selected value to Serial Monitor:
  Serial.print("Name option set: ");
  Serial.println(SwitchNR);
  }
Spirik commented 2 years ago

You would probably need some dynamic memory allocation in order for that to work. Or something of that nature. In C/C++ it is not quite as trivial as in some modern languages. In your code you are creating in a loop one and the same instance of a GEMItem class called DynamicMenuItem and adding it to the menu page. You probably shouldn't redefine existing object like that. And menu page can not have multiple instances of the same menu item. All menu items should be separate instances of a GEMItem class (internally they are presented as a linked list, where each previous one connected to the next via pointer, so it doesn't make sense to have one item linked to itself).

Try digging in this direction, or this, or probably some other more useful resources (I found the ones above just by googling "cpp create multiple instances of a class in a loop").

UPD: Array of GEMItem objects probably would be a good idea. If you don't know the number of menu items beforehand (say, you get them through API as well) you can try making it dynamically allocated.

e1z0 commented 2 years ago

Thank you for the explanation! :)

Spirik commented 2 years ago

No problem=)

e1z0 commented 2 years ago

I've managed to generate the dynamic menu, but do not understand how do i pass the parameter (like *entity) to the function loadPreset() ?

typedef struct {
char* real_name;
char* entity;
} Switch;
GEMItem *DynamicItems[3];
Switch Switches[] = {{"Kitchen", "light.kitchen_light" }, {"Porch", "switch.porch_button"}, {"Loft","switch.stairs_button"}};
  for (int i=0; i<sizeof Switches/sizeof Switches[0]; i++) {
    char *entity = Switches[i].entity;
    char *real_name = Switches[i].real_name;
    Serial.printf("Item: %s -> %s\n",real_name,entity);
    DynamicItems[i] = new GEMItem(real_name,loadPreset);
    menuLights.addMenuItem(*DynamicItems[i]);
  }
Spirik commented 2 years ago

There is no straightforward way of passing an argument to a callback of GEMItem. However something I can suggest to try is probably create an array of "proxy" callbacks with the length equal to the number of your dynamic menu items. Each "proxy" callback will call loadPreset() with proper argument.

void proxyCallback1();
void proxyCallback2();
void proxyCallback3();

void (*proxyCallbacks[])() = { proxyCallback1, proxyCallback2, proxyCallback3 } ;

...
// In your loop
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i]); // or (*functptr[i]) ?
...

void proxyCallback1() {
  loadPreset(1);  // You can hardcode pass required "entity" value here instead of "1"
}

void proxyCallback2() {
  loadPreset(2);
}

void proxyCallback3() {
  loadPreset(3);
}

Or probably you can create your own struct/class for storing a "proxy" callback function that will call loadPreset() with proper argument:

struct ProxyCallback {
  ProxyCallback(const char* argument_, void (*callback_)()) : argument(argument_), callback(callback_) {}
  const char* argument;         // Argument that will be passed to loadPreset()
  void (*callback)();           // Pointer to loadPreset() function
  void callbackProxy() {        // Proxy callback
    callback(argument);         // Calling loadPreset with argument
  };
};

ProxyCallbck proxyCallbacks = {{"light.kitchen_light", loadPreset}, {"switch.porch_button", loadPreset}, {"switch.stairs_button", loadPreset}};

...
// In your loop
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i].callbackProxy);

Or even make it more "dynamic" (creating instances of ProxyCallback in a loop (like you do with GEMItem):

struct ProxyCallback {
  ProxyCallback(const char* argument_, void (*callback_)()) : argument(argument_), callback(callback_) {}
  const char* argument;         // Argument that will be passed to loadPreset()
  void (*callback)();           // Pointer to loadPreset() function
  void callbackProxy() {        // Proxy callback
    callback(argument);         // Calling loadPreset with argument
  };
};

ProxyCallback *proxyCallbacks[3];

...
// In your loop
proxyCallbacks[i] = new ProxyCallback(entity, loadPreset);
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i].callbackProxy);

Please note, I haven't tried to compile any of that, but I hope that something along those line will work:) And most likely there is more elegant and proper solution to that.

e1z0 commented 2 years ago

thanks for the info! I will try :)

Spirik commented 2 years ago

Meanwhile there is new release of KeyDetector v.1.1.2 with support for buttons wired with pullup resistors. In order to activate it you would have to use full constructor when initializing an object:

KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10, 16, true);

Where the last boolean argument (set to true in the example) turns it on (default is false for pulldown mode).

e1z0 commented 2 years ago

very nice!

e1z0 commented 2 years ago

There is no straightforward way of passing an argument to a callback of GEMItem. However something I can suggest to try is probably create an array of "proxy" callbacks with the length equal to the number of your dynamic menu items. Each "proxy" callback will call loadPreset() with proper argument.

void proxyCallback1();
void proxyCallback2();
void proxyCallback3();

void (*proxyCallbacks[])() = { proxyCallback1, proxyCallback2, proxyCallback3 } ;

...
// In your loop
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i]); // or (*functptr[i]) ?
...

void proxyCallback1() {
  loadPreset(1);  // You can hardcode pass required "entity" value here instead of "1"
}

void proxyCallback2() {
  loadPreset(2);
}

void proxyCallback3() {
  loadPreset(3);
}

Or probably you can create your own struct/class for storing a "proxy" callback function that will call loadPreset() with proper argument:

struct ProxyCallback {
  ProxyCallback(const char* argument_, void (*callback_)()) : argument(argument_), callback(callback_) {}
  const char* argument;         // Argument that will be passed to loadPreset()
  void (*callback)();           // Pointer to loadPreset() function
  void callbackProxy() {        // Proxy callback
    callback(argument);         // Calling loadPreset with argument
  };
};

ProxyCallbck proxyCallbacks = {{"light.kitchen_light", loadPreset}, {"switch.porch_button", loadPreset}, {"switch.stairs_button", loadPreset}};

...
// In your loop
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i].callbackProxy);

Or even make it more "dynamic" (creating instances of ProxyCallback in a loop (like you do with GEMItem):

struct ProxyCallback {
  ProxyCallback(const char* argument_, void (*callback_)()) : argument(argument_), callback(callback_) {}
  const char* argument;         // Argument that will be passed to loadPreset()
  void (*callback)();           // Pointer to loadPreset() function
  void callbackProxy() {        // Proxy callback
    callback(argument);         // Calling loadPreset with argument
  };
};

ProxyCallback *proxyCallbacks[3];

...
// In your loop
proxyCallbacks[i] = new ProxyCallback(entity, loadPreset);
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i].callbackProxy);

Please note, I haven't tried to compile any of that, but I hope that something along those line will work:) And most likely there is more elegant and proper solution to that.

Hi, haven't touch the code for a while, now trying to implement callback using your method 3 , and it throws some errors:

/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino: In member function 'void ProxyCallback::callbackProxy()':
Example-01_Basic:70:22: error: too many arguments to function
   70 |     callback(argument);         // Calling loadPreset with argument
      |                      ^
/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino: In function 'void setupMenu()':
Example-01_Basic:151:64: error: request for member 'callbackProxy' in 'proxyCallbacks[i]', which is of pointer type 'ProxyCallback*' (maybe you meant to use '->' ?)
  151 |     DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i].callbackProxy);
      |                                                                ^~~~~~~~~~~~~
exit status 1
too many arguments to function
Spirik commented 2 years ago

Hi!

Try the following one (I've changed ProxyCallback constructor and added -> to proxyCallbacks[i]->callbackProxy):

struct ProxyCallback {
  ProxyCallback(const char* argument_, void (*callback_)(const char*)) : argument(argument_), callback(callback_) {}
  const char* argument;         // Argument that will be passed to loadPreset()
  void (*callback)();           // Pointer to loadPreset() function
  void callbackProxy() {        // Proxy callback
    callback(argument);         // Calling loadPreset with argument
  };
};

ProxyCallback *proxyCallbacks[3];

...
// In your loop
proxyCallbacks[i] = new ProxyCallback(entity, loadPreset);
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i]->callbackProxy);

If this won't work, you may also try changing constructor to:

ProxyCallback(const char* argument_, void (*callback_)(char*)) : argument(argument_), callback(callback_) {}

or

ProxyCallback(const char* argument_, void (*callback_)(const char* arg)) : argument(argument_), callback(callback_) {}

or

ProxyCallback(const char* argument_, void (*callback_)(void*)) : argument(argument_), callback(callback_) {}
e1z0 commented 2 years ago

None of above worked :(

Example-01_Basic:68:102: error: invalid conversion from 'void (*)(const char*)' to 'void (*)()' [-fpermissive]
   68 | ProxyCallback(const char* argument_, void (*callback_)(const char*)) : argument(argument_), callback(callback_) {}
      |                                                                                                      ^~~~~~~~~
      |                                                                                                      |
      |                                                                                                      void (*)(const char*)
Example-01_Basic:68:96: error: invalid conversion from 'void (*)(char*)' to 'void (*)()' [-fpermissive]
   68 | ProxyCallback(const char* argument_, void (*callback_)(char*)) : argument(argument_), callback(callback_) {}
      |                                                                                                ^~~~~~~~~
      |                                                                                                |
      |                                                                                                void (*)(char*)
Example-01_Basic:68:106: error: invalid conversion from 'void (*)(const char*)' to 'void (*)()' [-fpermissive]
   68 | ProxyCallback(const char* argument_, void (*callback_)(const char* arg)) : argument(argument_), callback(callback_) {}
      |                                                                                                          ^~~~~~~~~
      |                                                                                                          |
      |                                                                                                          void (*)(const char*)
Example-01_Basic:68:96: error: invalid conversion from 'void (*)(void*)' to 'void (*)()' [-fpermissive]
   68 | ProxyCallback(const char* argument_, void (*callback_)(void*)) : argument(argument_), callback(callback_) {}
      |                                                                                                ^~~~~~~~~
      |                                                                                                |
      |                                                                                                void (*)(void*)
Spirik commented 2 years ago

Oh yeah, you should change type of corresponding property as well, so replace void (*callback)(); in ProxyCallback struct definition with whatever version you'll write in the constructor, e.g. void (*callback)(const char*) (i.e. add const char* in parenthesis), so that their types match.

struct ProxyCallback {
  ProxyCallback(const char* argument_, void (*callback_)(const char*)) : argument(argument_), callback(callback_) {}
  const char* argument;           // Argument that will be passed to loadPreset()
  void (*callback)(const char*);  // Pointer to loadPreset() function
  void callbackProxy() {          // Proxy callback
    callback(argument);           // Calling loadPreset with argument
  };
};

ProxyCallback *proxyCallbacks[3];

...
// In your loop
proxyCallbacks[i] = new ProxyCallback(entity, loadPreset);
DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i]->callbackProxy);
e1z0 commented 2 years ago

Hey, it's in the progress, now struct seems to be ok, but i've got another error

/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino: In function 'void setupMenu()':
Example-01_Basic:156:65: error: invalid use of non-static member function 'void ProxyCallback::callbackProxy()'
  156 |     DynamicItems[i] = new GEMItem(real_name, proxyCallbacks[i]->callbackProxy);
      |                                              ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino:74:8: note: declared here
   74 |   void callbackProxy() {          // Proxy callback
      |        ^~~~~~~~~~~~~
exit status 1
invalid use of non-static member function 'void ProxyCallback::callbackProxy()'

My loadPreset function looks like:

void loadPreset(const char* argument)
Spirik commented 2 years ago

Apparently the pointer-to-function is not the same as pointer-to-member-function, so the code will require some further modification. Not sure I can help you with that at this point (and not even sure if this may or may not require GEMItem source code modifications), but have a look at this and this for better understanding of the issue. Probably the lambda function +[]{} can be a solution here. Or trying some another completely different approach. I'll let you know if I have some ideas.

Spirik commented 2 years ago

I wonder if something like that will work (without the need for ProxyCallback struct at all):

DynamicItems[i] = new GEMItem(real_name, +[]{ loadPreset(entity); });

UPD: Good news! Did a quick test, seems to be working for me.

e1z0 commented 2 years ago

Thanks a lot! But it throws another errors:

/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino: In lambda function:
Example-01_Basic:157:62: error: 'entity' is not captured
  157 |     DynamicItems[i] = new GEMItem(real_name, +[]{ loadPreset(entity); });
      |                                                              ^~~~~~
/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino:157:48: note: the lambda has no capture-default
  157 |     DynamicItems[i] = new GEMItem(real_name, +[]{ loadPreset(entity); });
      |                                                ^
/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino:150:11: note: 'char* entity' declared here
  150 |     char *entity = Switches[i].entity;
      |           ^~~~~~
exit status 1
'entity' is not captured

The lines:

150:char *entity = Switches[i].entity;
157:DynamicItems[i] = new GEMItem(real_name, +[]{ loadPreset(entity); });

Maybe i have something missing?

Spirik commented 2 years ago

Probably you need to capture entity or Switches[i] in lambda (like so [entity]{ loadPreset(entity) or [Switches[i]{ loadPreset(Switches[i].entity)). However, most likely, that won't compile either because lambda with capture can not be passed to a function that expects a function pointer... Have no solution at the moment rather than try to hardcode all necessary callbacks into an array of function pointers.

e1z0 commented 2 years ago

I've read about lambda functions and it seems that it does not work, tried with [variable] (capture), [=] (copy) and [&] (reference)

DynamicItems[i] = new GEMItem(real_name, [entity]() { loadPreset(entity); });
DynamicItems[i] = new GEMItem(real_name, [=]() { loadPreset(entity); });
DynamicItems[i] = new GEMItem(real_name, [&]() { loadPreset(entity); });

throws the same error:

/Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino: In function 'void setupMenu()':
Example-01_Basic:159:76: error: no matching function for call to 'GEMItem::GEMItem(char*&, setupMenu()::<lambda()>)'
  159 | DynamicItems[i] = new GEMItem(real_name, [entity]() { loadPreset(entity); });
      |                                                                            ^
In file included from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEMPage.h:39,
                 from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM_u8g2.h:41,
                 from /Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino:15:
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:138:5: note: candidate: 'GEMItem::GEMItem(char*, void (*)(), boolean)'
  138 |     GEMItem(char* title_, void (*buttonAction_)(), boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:138:34: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'void (*)()'
  138 |     GEMItem(char* title_, void (*buttonAction_)(), boolean readonly_ = false);
      |                           ~~~~~~~^~~~~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:130:5: note: candidate: 'GEMItem::GEMItem(char*, GEMPage*, boolean)'
  130 |     GEMItem(char* title_, GEMPage* linkedPage_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:130:36: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'GEMPage*'
  130 |     GEMItem(char* title_, GEMPage* linkedPage_, boolean readonly_ = false);
      |                           ~~~~~~~~~^~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:122:5: note: candidate: 'GEMItem::GEMItem(char*, GEMPage&, boolean)'
  122 |     GEMItem(char* title_, GEMPage& linkedPage_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:122:36: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'GEMPage&'
  122 |     GEMItem(char* title_, GEMPage& linkedPage_, boolean readonly_ = false);
      |                           ~~~~~~~~~^~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:114:5: note: candidate: 'GEMItem::GEMItem(char*, double&, boolean)'
  114 |     GEMItem(char* title_, double& linkedVariable_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:114:35: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'double&'
  114 |     GEMItem(char* title_, double& linkedVariable_, boolean readonly_ = false);
      |                           ~~~~~~~~^~~~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:113:5: note: candidate: 'GEMItem::GEMItem(char*, float&, boolean)'
  113 |     GEMItem(char* title_, float& linkedVariable_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:113:34: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'float&'
  113 |     GEMItem(char* title_, float& linkedVariable_, boolean readonly_ = false);
      |                           ~~~~~~~^~~~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:112:5: note: candidate: 'GEMItem::GEMItem(char*, boolean&, boolean)'
  112 |     GEMItem(char* title_, boolean& linkedVariable_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:112:36: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'boolean&' {aka 'bool&'}
  112 |     GEMItem(char* title_, boolean& linkedVariable_, boolean readonly_ = false);
      |                           ~~~~~~~~~^~~~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:111:5: note: candidate: 'GEMItem::GEMItem(char*, char*, boolean)'
  111 |     GEMItem(char* title_, char* linkedVariable_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:111:33: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'char*'
  111 |     GEMItem(char* title_, char* linkedVariable_, boolean readonly_ = false);
      |                           ~~~~~~^~~~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:110:5: note: candidate: 'GEMItem::GEMItem(char*, int&, boolean)'
  110 |     GEMItem(char* title_, int& linkedVariable_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:110:32: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'int&'
  110 |     GEMItem(char* title_, int& linkedVariable_, boolean readonly_ = false);
      |                           ~~~~~^~~~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:109:5: note: candidate: 'GEMItem::GEMItem(char*, byte&, boolean)'
  109 |     GEMItem(char* title_, byte& linkedVariable_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:109:33: note:   no known conversion for argument 2 from 'setupMenu()::<lambda()>' to 'byte&' {aka 'unsigned char&'}
  109 |     GEMItem(char* title_, byte& linkedVariable_, boolean readonly_ = false);
      |                           ~~~~~~^~~~~~~~~~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:100:5: note: candidate: 'GEMItem::GEMItem(char*, double&, void (*)())'
  100 |     GEMItem(char* title_, double& linkedVariable_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:100:5: note:   candidate expects 3 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:99:5: note: candidate: 'GEMItem::GEMItem(char*, float&, void (*)())'
   99 |     GEMItem(char* title_, float& linkedVariable_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:99:5: note:   candidate expects 3 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:98:5: note: candidate: 'GEMItem::GEMItem(char*, boolean&, void (*)())'
   98 |     GEMItem(char* title_, boolean& linkedVariable_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:98:5: note:   candidate expects 3 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:97:5: note: candidate: 'GEMItem::GEMItem(char*, char*, void (*)())'
   97 |     GEMItem(char* title_, char* linkedVariable_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:97:5: note:   candidate expects 3 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:96:5: note: candidate: 'GEMItem::GEMItem(char*, int&, void (*)())'
   96 |     GEMItem(char* title_, int& linkedVariable_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:96:5: note:   candidate expects 3 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:95:5: note: candidate: 'GEMItem::GEMItem(char*, byte&, void (*)())'
   95 |     GEMItem(char* title_, byte& linkedVariable_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:95:5: note:   candidate expects 3 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:88:5: note: candidate: 'GEMItem::GEMItem(char*, double&, GEMSelect&, boolean)'
   88 |     GEMItem(char* title_, double& linkedVariable_, GEMSelect& select_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:88:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:87:5: note: candidate: 'GEMItem::GEMItem(char*, float&, GEMSelect&, boolean)'
   87 |     GEMItem(char* title_, float& linkedVariable_, GEMSelect& select_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:87:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:86:5: note: candidate: 'GEMItem::GEMItem(char*, char*, GEMSelect&, boolean)'
   86 |     GEMItem(char* title_, char* linkedVariable_, GEMSelect& select_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:86:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:85:5: note: candidate: 'GEMItem::GEMItem(char*, int&, GEMSelect&, boolean)'
   85 |     GEMItem(char* title_, int& linkedVariable_, GEMSelect& select_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:85:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:84:5: note: candidate: 'GEMItem::GEMItem(char*, byte&, GEMSelect&, boolean)'
   84 |     GEMItem(char* title_, byte& linkedVariable_, GEMSelect& select_, boolean readonly_ = false);
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:84:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:74:5: note: candidate: 'GEMItem::GEMItem(char*, double&, GEMSelect&, void (*)())'
   74 |     GEMItem(char* title_, double& linkedVariable_, GEMSelect& select_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:74:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:73:5: note: candidate: 'GEMItem::GEMItem(char*, float&, GEMSelect&, void (*)())'
   73 |     GEMItem(char* title_, float& linkedVariable_, GEMSelect& select_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:73:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:72:5: note: candidate: 'GEMItem::GEMItem(char*, char*, GEMSelect&, void (*)())'
   72 |     GEMItem(char* title_, char* linkedVariable_, GEMSelect& select_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:72:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:71:5: note: candidate: 'GEMItem::GEMItem(char*, int&, GEMSelect&, void (*)())'
   71 |     GEMItem(char* title_, int& linkedVariable_, GEMSelect& select_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:71:5: note:   candidate expects 4 arguments, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:70:5: note: candidate: 'GEMItem::GEMItem(char*, byte&, GEMSelect&, void (*)())'
   70 |     GEMItem(char* title_, byte& linkedVariable_, GEMSelect& select_, void (*saveAction_)());
      |     ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:70:5: note:   candidate expects 4 arguments, 2 provided
In file included from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEMPage.h:39,
                 from /Users/devnull/Documents/Arduino/libraries/GEM/src/GEM_u8g2.h:41,
                 from /Users/devnull/Arduino/lcd_libai/GEM/examples/U8g2/Example-01_Basic/Example-01_Basic.ino:15:
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:58:7: note: candidate: 'constexpr GEMItem::GEMItem(const GEMItem&)'
   58 | class GEMItem {
      |       ^~~~~~~
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:58:7: note:   candidate expects 1 argument, 2 provided
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:58:7: note: candidate: 'constexpr GEMItem::GEMItem(GEMItem&&)'
/Users/devnull/Documents/Arduino/libraries/GEM/src/GEMItem.h:58:7: note:   candidate expects 1 argument, 2 provided
exit status 1
no matching function for call to 'GEMItem::GEMItem(char*&, setupMenu()::<lambda()>)'
Spirik commented 2 years ago

Hello, @e1z0 !

Not sure if this is still relevant, but just wanted to let you know that with the latest release of GEM user-defined callback arguments are now supported (for buttons and for menu items that represent editable variables). Thus it is now possible to implement what was discussed in this thread (dynamic creation of multiple menu items in a loop with a single callback function):

struct Switch {
  const char* title;  // Title of the button
  const char* id;     // ID of the button
};

// Array of switches
Switch switches[] = {{"Kitchen", "light.kitchen_light" }, {"Porch", "switch.porch_button"}, {"Loft","switch.stairs_button"}};

...

void setupMenu() {
  // Add buttons to menu page in a loop
  for (int i = 0; i < sizeof(switches)/sizeof(Switch); i++) {
    // Create instance of a button.
    // Pass button title, callback function, and a callback argument (button ID in this case) to constructor
    menuItemButtons[i] = new GEMItem(switches[i].title, pressButton, switches[i].id);
    // Add menu item to menu page
    menuPageMain.addMenuItem(*menuItemButtons[i]);
  }

  // Add menu page to menu and set it as current
  menu.setMenuPageCurrent(menuPageMain);
}

...

void pressButton(GEMCallbackData callbackData) {
  Serial.print("Button ID: ");
  Serial.print(callbackData.valChar); // Accessing user-defined callback argument
  Serial.print(" | Button title: ");
  Serial.println(callbackData.pMenuItem->getTitle()); // Accessing menu item title
}

Here is full gist of this example.

More info can be found in Readme and wiki.

Have a good day!