Menu, komplexe Struktur und Arrays

Hallo, ich bin neu hier und habe wenig Erfahrung mit Foren. Ich versuche ein Menu für mein (eigentlich) fertiges Projekt zu bauen. Das Menu funktioniert soweit, aber ich möchte es flexibler aufbauen. Mit C++ stehe ich auf Kriegsfuß und das 1.000 Seitige Buch beantwortet mir meine Fragen auch nicht.

Ich habe eine Kurzfassung gepostet. Diese läuft (das Hauptmenu lässt sich durchschalten). Der inaktive Abschnitt geht nicht. Ich bekomme das einfach nicht hin und bekomme folgende Fehlermeldung:

sketch_menu_v01.ino:29:37: error: too many initializers for 'menuItem'

Das Coding poste ich am Ende. Ich hoffe, man kann mir einen Denkanstoß geben. Ich stoße immer wieder auf einfache Probleme, die ich in einer anderen Programmiersprache ohne Probleme (im täglichen Job) meistere. Es ist einfach frustrierend.

Vielen Dank und Grüße

#include <Arduino.h>

#include "LiquidCrystal.cpp"
#include "Constants.h"
#include "Button.cpp"

typedef struct menuItem {
  String title;
  bool active;
  menuItem *sub1Menu[];  // level 1 submenu
};

LiquidCrystal lcd = LiquidCrystal(39, 16, 2);

Button buttonLeft(pinButtonLeft);
Button buttonMiddle(pinButtonMiddle);
Button buttonRight(pinButtonRight);

const uint8_t maxCountMenu = 4;
const uint8_t maxCountSub1 = 5;

// menuItem test[4] = { { "mode ", 1 },
//                      { "avail", 0, { { "T1", 1 },
//                                      { "T2", 0 },
//                                      { "T3", 0 },
//                                      { "T4", 0 },
//                                      { "T5", 0 } } },
//                      { "calib", 0 },
//                      { "play ", 0 } };

menuItem menu[4] = { { "mode ", 1 },
                     { "avail", 0 },
                     { "calib", 0 },
                     { "play ", 0 } };

menuItem sub1[5] = { { "T1", 1 },
                     { "T2", 0 },
                     { "T3", 0 },
                     { "T4", 0 },
                     { "T5", 0 } };

void setup() {
  Serial.begin(9600);

  buttonLeft.provide();
  buttonMiddle.provide();
  buttonRight.provide();

  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.createChars(lcd);
}

void loop() {
  uint8_t activeItem;

  if (buttonLeft.isPressed()) {
    activeItem = getActiveItem(menu, maxCountMenu);

    menu[activeItem - 1].active = false;

    if (activeItem == maxCountMenu)
      activeItem = 1;
    else
      activeItem = activeItem + 1;

    Serial.println(activeItem);
    menu[activeItem - 1].active = true;

    showMenuItem(menu[activeItem - 1].title);

    while (buttonLeft.isPressed()) {};
  };
}

void showMenuItem(String text) {
  lcd.clear();
  lcd.printBigCharacters(lcd, text, 0, 0);
};

uint8_t getActiveItem(menuItem menu[], uint8_t maxCount) {

  for (uint8_t i = 1; i <= maxCount; i++) {
    if (menu[i - 1].active == true)
      return (i);
  }
}

Im englischen Teil des Forum müssen die Beiträge und Diskussionen in englischer Sprache verfasst werden. Deswegen wurde diese Diskussion in den deutschen Teil des Forums verschoben.

mfg ein Moderator.

Wie viele „menuItem“ können Sie Ihrer Meinung nach dort speichern?

Ich möchte erreichen, dass sich dies durch die "Initialisierung" (mir fällt gerade kein passendes Wort ein) des Arrays automatisch ergibt und der entsprechende Speicher allokiert wird. Deshalb ist die Definition hier per Pointer (vorerst) variabel. Also der Speicherbedarf ergibt sich durch die konkrete Menuausprägung.

menuItem menu[] = { { "mode ", 1 },
                     { "avail", 0 },
                     { "calib", 0 },
                     { "play ", 0 } };

Damit kann ich in der Menuausprägung ohne viele Anpassungen einen Eintrag hinzufügen. Damit wird der Speicherbedarf konkret. Die Anzahl der Datensätze möchte ich dann berechnen:

  uint8_t records = sizeof(menu)/sizeof(menu[0]);
  Serial.print("Records: ");Serial.println(records); //Records: 4

Auch in C++ kann man auf Zeiger (pointer) verzichten, wenn es einem zu kompliziert ist.

struct MenuItem {int i; MenuItem* sub;};

MenuItem sub1[] { {11, NULL} };
MenuItem top[] { {1, sub1}, {2, NULL}};

void setup() {
  Serial.begin(115200);
  Serial.print(sizeof(top) / sizeof(top[0]));
  Serial.println(" Elemente auf der top-Ebene");
}

Das kompiliert.
Da hast du allerdings das Problem, dass die Größe eines "sub"-Arrays, das du nur über seine Startadresse bekommst, zur Laufzeit nicht mehr bekannt ist. ( Könntest du aber in die Struktur als Konstante mit aufnehmen.)

Wenn Sie dies tun:

typedef struct menuItem {
  String title;
  bool active;
  menuItem *sub1Menu[];  // level 1 submenu
};

sub1Menu ist ein Array unbekannter Dimension, das auf Zeiger zu Elementen vom Typ MenuItem zeigt, und Sie können die Größe dieses Arrays nicht kennen.

Sie müssten das so definieren:

Betrachten Sie diesen Code, vielleicht gibt er Ihnen einige Ideen

struct MenuItem {
  const char* title;
  bool active;
  byte subMenuCount;
  MenuItem *subMenu;
};

MenuItem sub2[] = {
  { "TT1", true,  0, nullptr },
  { "TT2", false, 0, nullptr },
  { "TT3", false, 0, nullptr },
};

MenuItem sub1[] = {
  { "T1", true, sizeof sub2 / sizeof *sub2, sub2 },
  { "T2", false, 0, nullptr },
  { "T3", false, 0, nullptr },
  { "T4", false, 0, nullptr },
  { "T5", false, 0, nullptr }
};

MenuItem menu[] = {
  { "mode ", true,   0, nullptr},
  { "avail", false,  0, nullptr },
  { "calib", false,  0, nullptr },
  { "play ", true,   sizeof sub1 / sizeof *sub1, sub1},
};


void printMenu(MenuItem* m, size_t count, byte level = 0) {
  for (size_t i = 0; i < count; i++) {
    for (byte l=0; l<level; l++) Serial.print("..");
    Serial.print(m[i].title);
    Serial.println(m[i].active ? " is active" : " is not active");
    if (m[i].subMenuCount != 0) printMenu(m[i].subMenu, m[i].subMenuCount, level + 1);
  }
}


void setup() {
  Serial.begin(115200);
  printMenu(menu, sizeof menu / sizeof * menu);
}

void loop() {}

2 Likes

Ich mache meine Menüs ohne den Aufwand.

Dazu brauche ich nur eine Numerische Variable.

Diese zeigt auf den aktiven Punkt des Menüs. Wenn ich den Menüpunkt wechsle dann wird diese Zahl z.b. erhöht (Pfeil nach unten) und das Display neu gezeichnet (wegen Scrollen).

Wird "Enter" ausgelöst, frage ich den Wert ab und reagiere darauf.

Der Vorteil dieser Methode ist. : Man brauch kein Gigantischen Variablenspeicher da alles Hard-Codiert (als Text) ist. Und der Variblenspeicher ist (besonders bei den kleinen Arduinos) sehr knapp.

Auch Untermenüs sind so Problemlos möglich.

Ach nebenbei. Die selbe Methode wende ich aktuell in meinen Projekt bei einen Touchdisplay an. Der unterschied ist nur das ich die Y-Koordinate abfrage um eine "Tastendruck" zu erkennen. Y-Reicht weil ich nur Balken als Button habe.

Fertig ist.

Gruß

Pucki

Ganz herzlichen Dank an Sie und alle anderen Beteiligten. Ich habe insbesondere durch Ihren Beitrag eine Menge Input bekommen. Die Lösung von J-M-L beinhaltet sogar das rekursive Durchlaufen des Menus. Ich habe es ausprobiert. Es funktioniert.

@pucki007
Sie haben nicht unrecht, mit dem was Sie schreiben. Ich hatte es auch ganz pragmatisch angefangen. Ich war dann aber der Meinung, dass man das etwas generischer hinbekommen müsste. Wenn man z.B. einen Punkt ins Menu einfügen möchte bzw. die Reihenfolge der Punkte ändert passt dann die Codierung nicht mehr.

Ich werde mit dem mir von Euch dankenswerterweise zur Verfügung gestellten Know-how / Denkanstößen noch ein wenige experimentieren. Leider ist C++, wie ich mir eingestehen muss, ein Buch mit sieben Siegeln.

Was ich auch noch nicht rausbekommen habe (C++) ist, ob bzw. wie man einen dynamischen Aufruf realisieren kann. Also ungefähr so:

struct MenuItem {
  const char* title;
  bool active;
  String subroutine;
  byte subMenuCount;
  MenuItem *subMenu;
};

// das ist nur stilisiert; so ähnlich kenne ich es aus einer anderen Programmiersprache
// subroutine = "startPlay";
// call (subroutine)();
//
// MenuItem menu[] = {
//   ...
//   { "play ", true,   "startPlay", sizeof sub1 / sizeof *sub1, sub1},

Also diese Aufrufe würden das Menu dann komplett flexibel machen. Aber da mein Wissen noch viel zu schwach ist werde ich mich wahrscheinlich doch mit Pragmatismus begnügen. Das widerstrebt mir zwar, aber wäre vermutlich vernünftig.

NOCMALS DANKE an ALLE FÜR EURE HILFE!

Sie können eine Funktion in C++ nicht über ihren Namen aufrufen, alle Namen werden vom Compiler entfernt.

Sie müssen Funktionszeiger studieren.

// Deklaration der Funktionsprototypen
void funktion1();
void funktion2();
void funktion3();

// Definition eines Typs für Funktionszeiger
typedef void (*Funktionszeiger)();

// Erstellung eines Arrays von Funktionszeigern
Funktionszeiger funktionen[] = {funktion1, funktion2, funktion3};

// Definition der Funktionen
void funktion1() {
  Serial.println("===> Dies ist Funktion 1");
}

void funktion2() {
  Serial.println("===> Dies ist Funktion 2");
}

void funktion3() {
  Serial.println("===> Dies ist Funktion 3");
}


void setup() {
  // Initialisierung der seriellen Kommunikation
  Serial.begin(115200);

  // Aufruf von Funktionen mit Funktionszeigern
  for (int i = 0; i < sizeof(funktionen) / sizeof(funktionen[0]); i++) {
    Serial.print("Aufruf der Funktion "); Serial.println(i + 1);
    funktionen[i](); // Aufruf der Funktion mithilfe des Funktionszeigers
  }
}

void loop() {}



1 Like

Ich habe alles wie gewünscht hinbekommen. Es funktioniert. Danke nochmal. Nun wollte ich die Logik noch in eine Klasse verlagern, wie ich es üblicherweise tue. Leider bekomme ich einen Fehler, wenn ich das Coding aus der INO in die Klasse verlagere. Es scheint wieder mit den dynamischen Arrays zu tun zu haben. Heute morgen hatte ich auch ein Problem mit meinem Versuch meine BigFont Implementierung anders aufzubauen (generischer). Habe ich aufgegeben und ist auch ein anderes Thema.

#include <Arduino.h>

class Menu {
  enum class userCmd { none,
                       availT1,
                       availT2,
                       availT3,
                       availT4,
                       availT5,
                       modFree,
                       modBiath,
                       modRndm };

  struct menuItem {
    const char* title;
    userCmd cmd;
    bool active;
    byte subMenuCount;
    menuItem* subMenu;
  };

  menuItem subTarget[] = {
    { "T1", userCmd::availT1, false, 0, nullptr },
    { "T2", userCmd::availT2, false, 0, nullptr },
    { "T3", userCmd::availT3, false, 0, nullptr },
    { "T4", userCmd::availT4, false, 0, nullptr },
    { "T5", userCmd::availT5, true, 0, nullptr }
  };

  menuItem subMode[] = {
    { "free", userCmd::modFree, false, 0, nullptr },
    { "biath", userCmd::modBiath, false, 0, nullptr },
    { "rndm", userCmd::modRndm, true, 0, nullptr }
  };

  menuItem menu[] = {
    { "mode ", userCmd::none, true, sizeof subMode / sizeof *subMode, subMode },
    { "avail", userCmd::none, false, sizeof subTarget / sizeof *subTarget, subTarget },
    { "calib", userCmd::none, false, 0, nullptr },
    { "play ", userCmd::none, false, 0, nullptr }
  };

public:
  Menu::Menu(uint8_t pin, bool reverse) {
  }
};
Menu.cpp:28:3: error: flexible array member 'Menu::subTarget' not at end of 'class Menu'
Menu.cpp:28:3: error: initializer for flexible array member 'Menu::menuItem Menu::subTarget []'
Menu.cpp:38:45: error: invalid application of 'sizeof' to incomplete type 'Menu::menuItem []'

Können Sie mir evtl. nochmal helfen / einen Tipp geben? Im Zweifel muss ich die Logik im "Hauptprogramm" lassen. Dort funktioniert es einwandfrei. Gefällt mir zwar nicht (unübersichtlich) aber ich habe leider keine Idee. Meine Suche im Internet führt zu diverse C++ Foren aber um das dort geschriebene zu verstehen, bin ich leider noch zu weit fachlich entfernt.

Warum würden Sie eine Klasse mit statischen Menüdaten erstellen, wenn nur eine Instanz benötigt wird?

So lässt es sich zumindest compilieren:

#include <Arduino.h>

class Menu {
  enum class userCmd { none, availT1, availT2, availT3, availT4, availT5, modFree, modBiath, modRndm };

  struct menuItem {
    const char *title;
    userCmd cmd;
    bool active;
    byte subMenuCount;
    menuItem *subMenu;
  };

  menuItem subTarget[5] {
      {"T1", userCmd::availT1, false, 0, nullptr},
      {"T2", userCmd::availT2, false, 0, nullptr},
      {"T3", userCmd::availT3, false, 0, nullptr},
      {"T4", userCmd::availT4, false, 0, nullptr},
      {"T5", userCmd::availT5, true,  0, nullptr}
  };

  menuItem subMode[3] {
      {"free",  userCmd::modFree,  false, 0, nullptr},
      {"biath", userCmd::modBiath, false, 0, nullptr},
      {"rndm",  userCmd::modRndm,  true,  0, nullptr}
  };

  menuItem menu[4] {
      {"mode ", userCmd::none, true,  sizeof subMode / sizeof *subMode,     subMode  },
      {"avail", userCmd::none, false, sizeof subTarget / sizeof *subTarget, subTarget},
      {"calib", userCmd::none, false, 0,                                    nullptr  },
      {"play ", userCmd::none, false, 0,                                    nullptr  }
  };

public:
  Menu(uint8_t pin, bool reverse) {}
};

Also der Sinn ist, dass ich so meine Modularisierung realisiere. D.h. wenn alles so klappt wie ich es mir vorstelle, kann ich die Klasse wiederverwenden. Kopiere die dann in mein anderes Projektverzeichnis und passe lediglich das Menu auf die neuen Anforderungen an. Ebenfalls die UserCommands auf die ich dann in meinem Hauptprogramm individuell reagiere. D.h. ich habe einen Wiederverwendungsgrad von hoffentlich 80% :wink: Außerdem kann ich in der Klasse auch Methoden ändern / hinzufügen (wenn ich eine andere Menusteuerung möchte), habe aber immer einen einheitlichen Aufbau der Logik und es ist aufgeräumt. Vielleicht verstehe ich auch Ihre Frage falsch. Falls das so ist: Sorry...

Vielen Dank. Ich probiere das aus. Hatte ich ehrlich gesagt auch schon. Ziel war eigentlich, die Anzahl offen zu lassen. Die Anzahl an Einträge wird im Sketch berechnet. Aber ja... wenn man einen Eintrag hinzufügt kann man natürlich auch 1 bei der Arraydefinition / Speicherallokierung addieren oder subtrahieren. Sollte man hinbekommen. Schöner wäre halt, wenn man sich darum nicht kümmern muss (ist ein rein theoretisches Problem und zugegebenermaßen wenig pragmatisch).

Nichts zu danken.

Ich denke dass die Klasse in der vorliegenden Form nicht besonders "widerverwendbar" ist, weil die Menüstruktur Bestandteil der Klasse ist.

Vielleicht geht es ja auch so:

enum class userCmd { none, availT1, availT2, availT3, availT4, availT5, modFree, modBiath, modRndm };

struct menuItem {
  const char *title;
  userCmd cmd;
  bool active;
  byte subMenuCount;
  const menuItem *subMenu;
};

const menuItem subTarget[] {
    {"T1", userCmd::availT1, false, 0, nullptr},
    {"T2", userCmd::availT2, false, 0, nullptr},
    {"T3", userCmd::availT3, false, 0, nullptr},
    {"T4", userCmd::availT4, false, 0, nullptr},
    {"T5", userCmd::availT5, true, 0, nullptr},
    {nullptr}
};

const menuItem subMode[] {
    {"free", userCmd::modFree, false, 0, nullptr},
    {"biath", userCmd::modBiath, false, 0, nullptr},
    {"rndm", userCmd::modRndm, true, 0, nullptr},
    {nullptr}
};

const menuItem menu[] {
    {"mode ", userCmd::none, true, sizeof subMode / sizeof *subMode, subMode},
    {"avail", userCmd::none, false, sizeof subTarget / sizeof *subTarget, subTarget},
    {"calib", userCmd::none, false, 0, nullptr},
    {"play ", userCmd::none, false, 0, nullptr},
    {nullptr}
};

class Menu {
public:
  Menu(uint8_t pin, bool reverse, const menuItem _menu[]) : menu {_menu} {}
  void print() {
    uint8_t i = 0;
    do { Serial.println(menu[i++].title); } while (menu[i].title != nullptr);
    Serial.println();
    Serial.println(menu[0].subMenu[2].title);
  }

private:
  const menuItem *menu;
};

Menu myMenu(5, false, menu);

void setup() {
  Serial.begin(115200);
  myMenu.print();
}

void loop() {}

Mein Punkt war, dass es keine Klasse sein muss, da Sie nur eine Instanz haben. Sie könnten dennoch Modularität erreichen, indem Sie einfach eine .h-Datei haben, in der Sie die Schlüsselfunktionen, die Strukturdefinition und wichtige Variablen als extern deklarieren, und eine .cpp-Datei, in der Sie die Menüs und Funktionscode definieren.

Die Klasse bietet Ihnen zwar einen gewissen Namensraumschutz, aber da sie Zugriff auf das Display, den Drehgeber oder die Tasten benötigen würde, um das Menü zu steuern, bräuchten Sie eine weitere Klasse zur Behandlung der Menüinteraktion oder Sie müssten mehr Komplexität zu Ihrer Menüklasse hinzufügen, indem Sie diese an den Konstruktor übergeben.

Die eigentliche Klasse zur Abstraktion eines Menüs wäre die Struktur menuItem.

1 Like

Es funktioniert alles so wie gewünscht und ich mit dem Erreichten mehr als glücklich. Zufrieden ist man glaube ich nie, aber da ich noch relativ neu in diesem Thema bin doch auch ein wenig stolz. Kleinigkeiten sind noch anzupassen. Das wird erledigt, wenn dieser Prototyp (Projektbestandteil) in das eigentliche Projekt eingebunden wird. Denn dann sind ohnehin entsprechende Anpassungen zu tun.

Warum schreibe ich das nun alles nochmal zusammen: ich denke, dass es eine Form der Anerkennung / Dank ist, die mir hier im Forum in Form von Tipps / Tricks sowie Codingschnippseln (teilweise sogar mehr als das) zur Verfügung gestellt wurden. Vielleicht ist der Beitrag ja auch eine Hilfe für andere oder gibt Ideen für mehr.

Mir ist auch bewusst, dass es eigene Libraries (GitHub) für die Menus gibt. Diese sind mir aber alle zu komplex und für mein Projekt überdimensioniert bzw. auch nicht so auf die Steuerung wie ich sie brauche (habe die Hardware leider gebaut, bevor ich genau wusste wie ich es programmiere ;-)) und bin nun etwas eingeschränkt. Auch eine wichtige Lektion die ich gelernt habe.

Was ich erreichen wollte:

  • Ein modulares Menu, das leicht um Einträge erweitert werden kann und auch wiederverwendbar ist
  • Das Menu kümmert sich nur um sich selbst; der Rest wird extern erledigt

Ich habe einen zentralen Ordner für meine eigenen Klassen; dort konsumiere ich diese dann für meine Projekte, indem ich eine Kopie in den Sketchordner ausführen. Änderungen sind dann an der Klasse (falls nötig) möglich; so wird es z.B. beim Menu sein; Wenn ich dabei feststelle, dass bestimmte Punkte Allgemeingültigkeit haben wird diese Logik in die zentrale „Kopiervorlage“ integriert (oder bei Fehlern lokal und zentral korrigiert). Andere Projekte sind vorerst nicht tangiert, da diese eine Kopie der alten Version innehaben (bei Schnellschüssen mache ich mir nicht alles kaputt – will sagen: in diesem konkreten Fall bzw. in der Lernphase ist Redundanz willkommen)

Ich veröffentliche hier nicht alles, sondern nur die wesentlichen Dinge, welche die Integration des Menus in das Hauptprogramm darstellen sollen. Man sieht deutlich, wie aufgeräumt (für mein Empfinden) das Hauptprogramm ist; der Rest ist „versteckt“ und kümmert mich nicht mehr wenn es einmal funktioniert; alles kann vom Hauptprogramm gesteuert / angesprochen werden (HUB mit LCD, LED, Summer, Buttons, Bluetooth und die Peripherie mit den Sensoren); alles ist, wie oben im Screenshot auszugsweise zu sehen, analog aufgebaut und in Klassen verschalt

Wie funktioniert es:

  • Es gibt 3 Buttons; linker BT steuert MenuLevel 0 (Hauptmenu); mittlerer BT MenuLevel 1 (Untermenu sofern vorhanden) und der rechte BT triggert eine Aktion, die zum Menupunkt hinterlegt ist
  • Die zwei Screenshots zeigen, wie ich in das SubMenu T1 unter AVAIL navigiert bin und dort die Aktion ausführe (in diesem Fall LED an LED aus auch zu sehen, an dem rechten Viereck im Display, was „aus“ innen leer und „an“ innen gefüllt ist

Danke und Grüße

PS

Eine Sache muss ich noch prüfen: der Sketch braucht 40% Speicher. Mein Hauptprojekt 35%. Da ich natürlich dort auch schon verschiedene Libraries inkludiert habe, sollte die Integration also nicht bei 75% Speicherverbrauch rauskommen, sondern deutlich geringer ausfallen. Aktuell ist das durch den Prototyp redundant und die Includes werden doppelt berechnet.

Constants.h

#ifndef MY_CONSTANTS_H
#define MY_CONSTANTS_H

#include <Arduino.h>

const uint8_t pinButtonLeft = 10;
const uint8_t pinButtonMiddle = 11;
const uint8_t pinButtonRight = 12;
const uint8_t pinDeviceSound = 13;
const uint8_t pinBluetoothRX = 3;
const uint8_t pinBluetoothTX = 2;
const uint8_t pinLCDData = A4;
const uint8_t pinLCDClock = A5;

enum class userCmd { none,
                     availT1,
                     availT2,
                     availT3,
                     availT4,
                     availT5,
                     modFree,
                     modBiath,
                     modRndm,
                     calib,
                     play };

#endif

Menu.cpp

#include <Arduino.h>

#include "Constants.h";

class Menu {
private:
  size_t maxCountMenu;
  
  uint8_t activeItem;
  uint8_t activeSubI;
  uint8_t lastLevel;

  struct menuItem {
    const char* title;
    userCmd cmd;
    bool active;
    byte subMenuCount;
    menuItem* subMenu;
  };

  menuItem subTarget[5] = {
    { "T1", userCmd::availT1, false, 0, nullptr },
    { "T2", userCmd::availT2, false, 0, nullptr },
    { "T3", userCmd::availT3, false, 0, nullptr },
    { "T4", userCmd::availT4, false, 0, nullptr },
    { "T5", userCmd::availT5, true, 0, nullptr }
  };

  menuItem subMode[3] = {
    { "free", userCmd::modFree, false, 0, nullptr },
    { "biath", userCmd::modBiath, false, 0, nullptr },
    { "rndm", userCmd::modRndm, true, 0, nullptr }
  };

  menuItem menu[4] = {
    { "mode ", userCmd::none, true, sizeof subMode / sizeof *subMode, subMode },
    { "avail", userCmd::none, false, sizeof subTarget / sizeof *subTarget, subTarget },
    { "calib", userCmd::calib, false, 0, nullptr },
    { "play ", userCmd::play, false, 0, nullptr }
  };

  uint8_t Menu::getActiveItem(menuItem* m, size_t count) {
    for (size_t i = 0; i < count; i++) {
      if (m[i].active == true)
        return (i);
    }
  }

public:
  Menu::Menu() {
    maxCountMenu = sizeof menu / sizeof *menu;
    activeItem = getActiveItem(menu, maxCountMenu);
    lastLevel = 0;
  }

  Menu::toggleItem(uint8_t menuLevel) {
    lastLevel = menuLevel;

    switch (lastLevel) {
      case 0:
        menu[activeItem].active = false;

        if (activeItem < maxCountMenu - 1)
          activeItem += 1;
        else
          activeItem = 0;

        menu[activeItem].active = true;

        activeSubI = 0;

        break;
      case 1:
        if (menu[activeItem].subMenuCount != 0) {
          activeSubI = getActiveItem(menu[activeItem].subMenu, menu[activeItem].subMenuCount);

          menu[activeItem].subMenu[activeSubI].active = false;

          if (activeSubI < menu[activeItem].subMenuCount - 1)
            activeSubI += 1;
          else
            activeSubI = 0;

          menu[activeItem].subMenu[activeSubI].active = true;

          break;
        }
    }
  }

  char* Menu::getItemTitle( ){
    switch(lastLevel){
      case 0:
        return(menu[activeItem].title);
        break;
      case 1:
        return(menu[activeItem].subMenu[activeSubI].title);
        break;
    }
  }

  userCmd Menu::getItemCmd(){
    switch(lastLevel){
      case 0:
        return(menu[activeItem].cmd);
        break;
      case 1:
        return(menu[activeItem].subMenu[activeSubI].cmd);
        break;
    }    
  }
};

sketch_menu_vX1.ino

#include <Arduino.h>

#include "LiquidCrystal.cpp"
#include "Constants.h"
#include "Button.cpp"
#include "Menu.cpp"
#include "Led.cpp"

LiquidCrystal lcd = LiquidCrystal(39, 16, 2);

Button buttonLeft(pinButtonLeft);
Button buttonMiddle(pinButtonMiddle);
Button buttonRight(pinButtonRight);

Led led(2, false);
Menu menu;

void setup() {
  Serial.begin(9600);

  buttonLeft.provide();
  buttonMiddle.provide();
  buttonRight.provide();

  led.provide();

  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.createChars(lcd);
}

void loop() {
  if (buttonLeft.isPressed()) {
    menu.toggleItem(0);
    showMenuItem(menu.getItemTitle());
    while (buttonLeft.isPressed()) {
    };
  };

  if (buttonMiddle.isPressed()) {
    menu.toggleItem(1);
    showMenuItem(menu.getItemTitle());
    while (buttonMiddle.isPressed()) {
    };
  }

  if (buttonRight.isPressed()) {
      handleUserCmd(menu.getItemCmd());
    while (buttonRight.isPressed()) {
    };
  }
}

void handleUserCmd(userCmd cmd) {
  switch (cmd) {
    case userCmd::availT1:
      if(led.isOn()){
        led.off();
        lcd.printSwitchState(lcd, false, 13);
      } else {
        led.on();
        lcd.printSwitchState(lcd, true, 13);
      }
      break;
  }
};

void showMenuItem(String text) {
  lcd.clear();
  lcd.printBigText(lcd, text, 0, 0);
};

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.