Projekt Überwachung/Schalter/Termin Planung/ Abfall Kalender / Servo / Relais / LCD Menü über Encoder /offenes Projekt/ zum Nachbau

Du hast das in #95 bereits richtig ausgedrückt. Der Simulator als zusätzliches Werkzeug ist als Ergänzung durchaus sinnvoll. Wenn Quellcode aber nur noch durch Links dorthin ersichtlich ist und nicht mehr direkt im Forum zur Diskussion steht, dann sehe ich (und wohl auch Andere) das für das Forum als kontraproduktiv an.

Gruß Tommy

@Tommy56 das ist doch nicht so @ec2021 postet doch immer den Link und kurz danach den ganzen Code Nochmal Hier .
Schau mal weiter oben . #78 z.b. das ist noch aktuell . Und da ich nur mal versucht habe ein menü Punkt zu tauschen in der Simulation . macht es ja kein Sinn das gleiche noch mal zu posten .

Das war

a) die schnellste Möglichkeit :innocent: und
b) die flexibelste Möglichkeit, weil es so dem Entwickler überlassen bleibt, was ein Haupt- bzw. ein Submenü sein soll und von wo es wie aufgerufen wird.

Man könnte die Menühierarchie in der Klasse abbilden, das ist jedoch aufwendiger (kostet mehr Zeit zum Umsetzen und Testen). Unter Umständen würde man, um schneller etwas liefern zu können, zunächst auch Einschränkungen einbauen müssen, um Fehlanwendungen zu vermeiden (derzeit auch nur eine Annahme meinerseits ...).

Genau genommen gibt es momentan keine "Haupt" bzw. "Sub-"Menüs sondern Menüs, deren Aufrufabfolge durch die State Machine gesteuert wird (daher -> flexibel). Würde man das in die Klasse verlegen, müsste man das dort regeln ...

Ich Finde das so ganz gut @ec2021 hab ja mal ein menüPunkt getauscht zum Test und war Übersichtlich finde ich .
Wir Sollten vieleicht immer mal ein Bild vom Aufbau mit Posten und den Code damit das jeder Nachbauen und Nachvollziehen kann . LG

edit @ec2021 passt das mit den Menü Punkten so ?
hab das nicht so fein hinbekommen wie du mit den punkten vorn dran :slight_smile:

Bin Deiner Meinung. Es darf nicht dahin kommen, dass wir Forumsmitglieder ausgrenzen. Daher würde ich auf Dauer die Integration eines Simulators in Arduino.cc sehr begrüßen. Die Vorteile einer integrierten Lösung wären m.E. nicht zu unterschätzen. Der Sprung von digitaler Abbildung zum realen Aufbau ist bei vielen Anwendungen kaum oder wenig kritisch, aber sicher auch nicht bei allen.

  • Start Bildschirm

    • mit Uhrzeit Datum Zeile 1
    • Temperatur Entfernung Überwachung Ultraschall Zeile 2
    • Lauf anzeige Tages Terminen und bevorstehende Geburtstage (1 Tage vorher ) zeile 3
  • Anzeige Abfall Kalender Immer nur den Nächsten Termin ( Einen Tag zuvor Warnung )

    • Zum Beispiel Blinken „ Morgen Schwarze Tonne „
  • Wenn Sich Jemand Nähert soll Das Display Blinken und ein Akkustiches Signal kommen zeile 4

    • beim ersten Drücken von SW LCD Löschen und Menü aufrufen
  • Menü

    • Wochenplan
    • submenü
      • Wochenplan zum Herunterrollen
      • zurück Hauptmenü
  • Ultraschall

    • Submenü
      • Warnung an
      • Warnung aus
      • (Wichtig ist hier zu wissen das ist eine Türüberwachung und soll ein Ton ausgeben (MP3) das sich jemand nähert Damit ich das mitbekomme . Bach Feierabend möchte ich das aber abschalten .
  • Servo Schliesser

    • Submenü
      • Tür verriegeln
      • Tür Öffnen
  • Maschinen Wache

    • Submenü
      • Wache einschalten
      • Wache Ausschalten
      • ( hier kommt nur der Lichtsensor ran . Sollte bei Wache eingeschaltet, der Lichtsensor merken das es dunkel ist , muss sofort eine Akustische Warnung kommen und LCD Löschen. Dort muss dann stehen Maschine Fertig . Mit bestätigen kann es wieder zurück zum Startbildschirm gehen )
  • Gas Warner

    • Submenü
      • Anschalten Ton und Warnung im Display
      • Ausschalten Ton und Warnung im Display
      • ( Wird nur abgeschaltet beim Tausch der Gasflaschen )
  • Geburtstage

    • Submenü
      • diese Woche
      • nächste Woche
  • Uhrzeit

    • submenu
      • Stunden einstellen
      • Minuten einstellen
      • Sekunden einstellen
  • Alkohol Tester

    • Starten
    • beenden

Ich schicke Dir per privater Nachricht eine PDFScreenshot-Anleitung, wie das geht ...

1 Like

genau so stelle ich mir das menü vor . Bei dem Servo wäre die frage ob man beim offen oder schliessen eine je Position speichern könnte . mhh Da würden unter Servo noch 2 punkte kommen .
Wie ich mir das vorstelle :
Menü :
*Servo

  • submenü

  • servo öffnen

  • servo schliessen

  • servo öffnen Pos speichern

  • servo schliessen Pos speichern

man geht ins menü position schliessen speichern . dreht am endcoder soweit bis man sagt perfekt und drückt 1x den SW somit wäre diese Positon gespeichert . und beim Öffen eben das gleiche .
Ich muss mich mal schlau machen ob sowas geht .
@ec2021 schon fast :slight_smile:
denke das wäre Hilfreich , zumal wenn jemand das gerät für sich nutzt , ja nicht jeder die gleiche schliessanlage hat .
lg

Das sollte auf jeden Fall möglich sein.

hab mich erst mal mit ä ö ü ... auseinander gesetzt :slight_smile:
RotMenue HauptMenue(clkPin,dtPin, swPin, "Hauptmen\365 "); // \365 für das ü
\341 ä
\357 ö
bin mir nicht sicher ob das bei allen Displays so sein wird aber beim HD44780-kompatiblen sollte das gehen

RotMenue HauptMenue(clkPin,dtPin, swPin, "Hauptmen\365 ");
RotMenue GreenLedMenue(clkPin,dtPin, swPin, "Control Green");

kann man eigentlich zur Laufzeit die Texte wieder ändern?
Das würde ermöglichen dass man in jeder Zeile auch aktuelle Daten einbauen kann

vgs

Momentan kann man zur Laufzeit neue Menüpunkte erstellen, die in der internen Liste hinten angehängt werden. Ändern ist bisher nicht vorgesehen, wäre aber "leicht" anzupassen ...

Dazu müsste ich die Einträge im jeweiligen Textarray über den Index (laufende Menüpunkt-Nummer) oder eine Textsuche (setzt für Eindeutigkeit voraus, dass pro RotMenue-Instanz jeder Eintrag nur einmal initiiert wird) zugänglich machen.

Könnte ich umsetzen, wenn es gewünscht ist ... :wink:

finde ich braucht man nicht. Aber einen wie auch immer zusammengesetzen String aktualisieren damit man Zeilen haben kann
CO2-Alarm ab xxxx ppm und die Zahl ist dann einstellbar das wäre cool.
tja und wenn es nur einen Drehknopf gibt dann muss ja die Eingabe auch darüber erfolgen.
Dann braucht man einen Modus bei dem man den Drehencoder abfragen kann
im Uhrzeigersinn gedreht Zahlenwert erhöhen
gegen den Uhrzeigersinn gedreht Zahlen erniedrigen.

Jetzt bin ich mir nicht sicher ob das ganz schön Klimmzüge gibt wenn man da zwischen
Vollautomatisch Menüzeilen wechseln und Zahlenwert ändern hin- und herschalten will.

Wenn ja dann wäre ich für eine eindeutige Trennung zwischen Encoder auswerten und Menüzeilen wechseln.

vgs

Das braucht es auf jeden Fall. Die Trennung von Menüeabfragen lässt sich tatsächlich im Sketch selbst organisieren. Wenn diese Funktion nicht aufgerufen wird, entzieht man dem Encoder die Kontrolle über das aktuelle Menü:

void ActMenue(RotMenue &aMenue){
 if (aMenue.StateHasChanged() || aMenue.Changed()) {
   Serial.print(aMenue.ActMenueNo());
   Serial.print("\t");
   Serial.println(aMenue.ActMenueName());
   WriteLcd(aMenue.ActMenueName(),aMenue.ActMenueTitle());
  }
  if (aMenue.confirmed()) {
    Serial.println(" Confirmed Menue\t"+aMenue.ActMenueName());
    state = aMenue.ActMenueState();
  }
}

In der State Machine genügt es also, einen State einzurichten, in dem diese Routine nicht aufgerufen wird, um den Encoder und Taster getrennt verwenden zu können. Man muss dann allerdings einen Weg offenhalten, um zurück gehen zu können. Das wäre im Normalfall die Bestätigung mit dem Taster; dort würde man (beispielsweise) Servo-Positionen speichern und dann als "Letztes" state = MAIN; aufrufen. Schwupps läuft das Hauptmenü wieder. Oder (wahrscheinlicher!) man ruft den state für das Menü auf, wo man zuvor hergekommen ist.

Siehe z.B. SUBGREENON; dort könnte man eine andere Encoder-Abfrage einbauen, die meinetwegen die Helligkeit der Led steuert. Der state = SUBGREENON würde dann dort erst mit einem Tastendruck auf state = SUBGREEN gesetzt:

void StateMachine(){
   switch(state){
     case MAIN : ActMenue(HauptMenue);
       break;
     case SUBGREEN : ActMenue(GreenLedMenue);
        break;
     case SUBGREENON : digitalWrite(GreenLedPin,HIGH);
               state = SUBGREEN;
        break;
     case SUBGREENOFF : digitalWrite(GreenLedPin,LOW);
               state = SUBGREEN;
        break;

Ja und dass finde ich sind dann Klimmzüge mal mitlaufen lassen mal nicht.

Da fände ich besser die Encoderabfrage ist eine Sache und die liefert immer einen "Vorgabewert" ich wurde einen Tick links / rechts gedreht und dieser Wert wird dann wenn man die Menü-Anzeige verändern will der Menü-Routine zugeführt.
Und sonst wird er "LeftTurn" / "RightTurn" dem Herauf/Herunterzählen einer Variable zugeführt.

vgs

Ja, ich bin schuld.
Bedenke: Ich habe die Hardware nicht gehabt und Du auch nicht.
Und alle Codes sind hier geblieben.

Das ist kein Hexenwerk.
Haben wir an anderer Stelle auch gemacht.
Angefangen von Grundeinstellungen über Datum / Uhrzeit bis hin zu Farbwerten von RGB-Led.s Serielle Datenstring vom Computer im Adruino Mega einlesen und aufteilen - #403 by my_xy_projekt

Ist kein Hexenwerk.

Will mal jemand was probieren?
Er ist momentan auf einem 20x4 Display eingestellt, aber wenn man die Zeilen und Spalten anpasst, wird alles andere im Code automtisch angepasst.
Wer Fehler findet, kann gerne was dazu schreiben.

Ich verwende GitHub - CarlosSiles67/Rotary: Arduino rotary encoder library
Hoffentlich habe ich es genügend kommentiert.
Die RotaryFunktion erkläre ich ggfls. wenn Fragen sind.
Hinweis: Die Pins müssen nicht 2 und 3 sein - das Ding läuft ohne Interrupts

// LCD
#include <LiquidCrystal_I2C.h>
const byte lcdZeilen = 4;
const byte lcdSpalten = 20;
char zeilenBuffer[lcdSpalten] = {' '};
LiquidCrystal_I2C lcd(0x27, lcdSpalten, lcdZeilen);

// RotaryEncoder
#define ENABLE_PULLUPS
#include <rotary.h>
Rotary rotary = Rotary(2, 3, 7); // Rotary(Encoder Pin 1, Encoder Pin 2, Button Pin)
const uint8_t bounce = 10;

void setup()
{
  delay(500);
  Serial.begin(115200);
  Serial.println(F("Start..."));
  lcd.begin();
  lcd.backlight();
  hauptmenu(true);
}
void loop()
{
  hauptmenu();
}
void hauptmenu()
{hauptmenu(false);}
void hauptmenu(bool neu)
{
  const char texte[][6] = {"Menu1", "Menu2", "Menu3", "Menu4", "Menu5", "Menu6", "Menu7", "Menu8"};
  const byte menuepunkte = sizeof(texte) / sizeof(texte[0]);  // Rechenwert Anzahl der Einträge
  const char isauswahl[] = {'>', '#'};                        // Positionsanzeige für LCD
  static uint8_t myRotate = 0;                                // Aktuelle RotaryPosition
  static uint8_t lcdID = 0;                                   // Jeder Bildschirm wird errechnet
  static uint8_t lastRotate = 0;                              // letzte RotaryFunktion zum Vergleichen
  static bool displayNew = false;                             // Merker für neu
  if (neu) {lcdID = -1; lastRotate = -1;}
  myRotate = getRotary (myRotate, 0, menuepunkte - 1, true);  // Gibt den Counter zurück
  /*
    Serial.print(myRotate % lcdZeilen); Serial.print(' ');
    Serial.print(lastMenu); Serial.print(' ');
    Serial.println();
  */
  if (lcdID != myRotate / lcdZeilen)                          // Ist der Inhalt der Anzeige passend zum Counter?
  {
    lcdID = myRotate / lcdZeilen;                             // Ermittelt die lcdID an der Anzahl der MenuEinträge
    for (byte b = 0; b < lcdZeilen; b++)                      // schreibt Menueinträge
    {
      char buf[lcdSpalten] = {'\0'};                          // Zwischenpuffer für Leerzeichen
      if (b + lcdZeilen * lcdID < menuepunkte)                // um zu vermeiden, das über Ende von texte[] gelesen wird
      {
        lcd.setCursor(0, b);
        for (byte c = 0; c < lcdSpalten - sizeof(texte[b]) - 2; c++) // baut einen buffer  bis ende der Zeile
        {buf[c] = ' ';}
        lcd.print("  ");
        lcd.print(texte[b + lcdZeilen * lcdID]);
        lcd.print(buf);
      }
    }
  }
  if (lastRotate != myRotate)                                // Für die Auswahlanzeige
  {
    for (byte b = 0; b < lcdZeilen; b++)                     // durchlaufe alle LCD-Zeilen
    {
      if (b + lcdZeilen * lcdID < menuepunkte)               // solange sich da ein Menupunkt findet
      {
        lcd.setCursor(0, b);                                 // spalte 0 - Zeile b
        byte c = myRotate;                                   // Ab hier Festlegung in welcher Zeile
        if (myRotate >= lcdZeilen)                           // und was ausgegeben wird
          c = (myRotate % lcdZeilen);
        if (c == b)
          lcd.print(isauswahl[0]);
        else
          lcd.print(isauswahl[1]);
      }
    }
    lastRotate = myRotate;
  }
  if (rotary.buttonPressedReleased(bounce))
  {
    Serial.println(myRotate);
  }
}


// Diese Funktionen sind für den RotaryCounter!
uint8_t getRotary(int16_t rotaryWert, const uint8_t rotaryMin, const uint8_t rotaryMax)
{return getRotary(rotaryWert, rotaryMin, rotaryMax, false);}
uint8_t getRotary(int16_t rotaryWert, const uint8_t rotaryMin, const uint8_t rotaryMax, const bool atEnd)
{
  // *INDENT-OFF*
  uint8_t val = rotary.process();
  //Serial.println(val);
  if (val == rotary.clockwise())
  {
    rotaryWert ++;
    Serial.println('+');
  }
  else if (val == rotary.counterClockwise())
  {
    rotaryWert --;
    Serial.println('-');
  }
  // Ab hier wird festgelegt, wie sich verhalten wird, wenn Ende erreicht
  if (rotaryWert > rotaryMax) { if (atEnd) rotaryWert = rotaryMin; else rotaryWert = rotaryMax;}
  else if (rotaryWert < rotaryMin) {if (atEnd) rotaryWert = rotaryMax; else rotaryWert = rotaryMin;}
  return (rotaryWert);
  // *INDENT-ON*
}

Bei genauer Betrachtung ist das nur ein anderer Blick auf die gleiche Sache ... In jedem Fall muss man die Encoder-Werte routen. Ob man sie außerhalb der Klasse ermittelt und dann wahlweise der Menüklasse weiterreicht oder ob man die Funktion der Klasse nur dann aufruft, wenn man sie benötigt, ist letztlich eher eine individuelle Sicht ... :wink:

Ich kann die Klasse auf Wunsch wie folgt anpassen:

  • Encoder-/Taster-Routine "herausoperieren"
  • Dafür Funktionen "Forward", "Back", "Confirm" aufnehmen, die dann von außen aufgerufen werden.
  • Die Möglichkeit zur Änderung von Menü-Titel und einzelner Menüpunkte (Name, Status) einbringen

Würde Euch das helfen?

P.S.: Ich habe die ersten beiden Schritte mal ausprobiert. Damit wird man m.E. nicht glücklich... Man muss wesentlich mehr außerhalb der Klasse mitbeachten, als wenn man die gekapselte Funktion verwendet. Ich werde stattdessen mal

a) die Änderungsmöglichkeit für Titel und Menüpunkte einbringen
b) das Beispiel um eine getrennte Encoder-Abfragefunktion erweitern, so dass man sehen kann, wie das am Besten geht.

so war das nicht gemeint :slight_smile: "lach " ich wollte damit sagen man kann es nutzen aber eben auch nicht für alles und immer

hab das gerade mal versucht .
also dsd LCD Begin geht bei mir nie ich muss immer lcd init nutzen .
und dann kommt :
In function getRotary': undefined reference to Rotary::process()'
undefined reference to Rotary::clockwise()' undefined reference to Rotary::counterClockwise()'
/tmp/ccUGVtaj.ltrans0.ltrans.o: In function hauptmenu': undefined reference to Rotary::buttonPressedReleased(short)'
/tmp/ccUGVtaj.ltrans0.ltrans.o: In function global constructors keyed to 65535_0_ClassRotMenue.cpp.o.2293': <artificial>:(.text.startup+0x98): undefined reference to Rotary::Rotary(char, char, char)'
collect2: error: ld returned 1 exit status
Error during build: exit status 1

die Lib ist aber eingeladen
LG

Ja das ist bei manchen so.
Das mit dem Rotary schau ich gleich mal.

Da hat sich wohl was verändert.
Rotary.zip (6,4 KB)
Ich häng mal die von mir benutzte ran.

sieht wie meine aus und kommt auch das gleiche an ferhlern .