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