Code Aufräumen/Verbessern, Speicherplatz freigeben

Damit man die Zustände von einem Ablauf trennen kann und den Ablauf auch getrennt warten kann.
Es gibt ja auch die Möglichkeit nicht nur in einer fixen Abfolge die einzelnen Status durchzugehen.

Hallo,

sollte man eine Statemaschine nicht als eine Einheit betrachten? Man schreibt sich ja die Statemaschine so wie man sie benötigt. Das die hier im Bsp. der Reihe nach durchspringt ist ja nur das Bsp. Wenn man eine Statemaschine von außerhalb steuert ist es in meinen Augen keine gute Statemaschine mehr. Weil was bringt die Auftrennung? 2 Ministeuerungen. Eine ändert den Zustand und die andere reagiert darauf. Dann kann man das auch gleich zusammenziehen. Ansonsten hätte man 2 Steuerungen die man pflegen muss. Ich sehe das schon etwas kritisch. Die Frage lautet doch wofür benötigt man extern den Zustand was man nicht intern auch erledigen kann?

Mal gesponnen. Ich will mir den aktuellen Zustand auf einem Display anzeigen lassen. Dann rufe ich im entsprechend aktuellen case eine Displayfunktion auf. Ich meine beim debuggen schreibe ich serielle Ausgabe in das entsprechende case rein.

Wenn man unbedingt die Zustandsänderung und die Auswertung darauf in 2 switch case trennen möchte, dann kann man das innerhalb der einen Statemaschine Funktion tun. 2 switch case hintereinander. Nur wie gesagt, halte ich für nicht notwendig.

Am Besten ich lasse euch mal machen. Vielleicht liege ich ja hier doch falsch. :eyes:

Es gibt verschiedene Arten Zustandsmaschinen zu implementieren. Mit jeweils Vorteilen und Nachteilen.

Häufig werden Funktionszeiger verwendet, auch wenn manche Leute davon abraten. Aber nicht alles ist eine sicherheitsrelevante Anwendung.
Man kann Zustandsübergangstabellen aus Funktionszeigern, Zuständen und Ereignissen ins Programm schreiben.
Oder man verwaltet nur den aktuellen Zustand direkt als Funktionszeiger (statt ein enum zu speichern).
Man kann auch jede Funktion selbst ihre nachfolgende Funktion als Rückgabewert liefern lassen. Interessant, aber sehr unübersichtlich.

Es gibt auch objektorientierte FSMs.

Das mit switch/case ist für solche einfachen Sachen aber völlig in Ordnung. Und man macht wahrscheinlich weniger Fehler als mit komplexeren Konstrukten.

Immer wenn man 2 Sachen so verbindet, sind sie verbunden.

Angenommen man möchte sich den Zustand zu Kontrollzwecken anzeigen lassen.....
Dann:
Es ist die Aufgabe des Benutzerinterfaces, den Zustand zu zeigen.
Das Interfache weiß WO und WIE es angezeigt werden soll.
Die Anzeige liegt in seinem Zuständigkeitsbereich.

Es darf nach außen wie eine Einheit erscheinen.
Bei der Konstruktion/Design muss man das Problem schon aufteilen/fragmentieren.
Ins besondere haben wir ja etliche Zustände und Transformationen, von einem Zustand in den anderen.

Nähkästchen:
Meine Anwendungen bestehen oft aus dutzenden von Automaten.
Das hat einige Vorteile:
Das macht sie schlanker.
Damit leichter zu testen
Leichter zu schreiben
Einfacher zu warten
Leichter wiederzuverwenden

Alles in einen großen Klumpen gegossen, nimmt mir (alle?) diese Vorteile.
In meine Automaten findet man darum auch nur ganz flache Entscheidungsstrukturen (ganz ohne komme auch ich nicht aus)

Keiner dieser Automaten enthält Ausgaben, es sei denn es ist genau seine Aufgabe, Ein und Ausgaben zu tätigen. Aber dann macht dieser Automat auch nur dieses. Nix anderes.

Nur unter harten Sachzwängen mache ich da Gefangene/Kompromisse.

Hallo,

Funktionszeiger als enum Ersatz? Hatte ich auch schon probiert, der Nutzen war jedoch bei mir nicht da. Es war nur alles komplizierter. Es gibt verschiedene Ansätze. Ja. Soweit gehe ich mit. :wink:

Nur sowas doppelt gemoppeltes hier vermeidet man doch ganz sicher, also ich ganz bestimmt. :wink: So habe ich die Auftrennung verstanden die im Raum steht. Ich kann dabei ganz ernsthaft keinen Vorteil erkennen außer mehr Fehlerpotential.

void stateMaschine(Program &state)
{
  switch (state)
  {
    case Program::cube :       state = Program::dice;       break;
    case Program::dice :       state = Program::fadeOnSite; break;
    case Program::fadeOnSite : state = Program::disco;      break;
    case Program::disco :      state = Program::cube;       break;
  }
}

void stateAuswertung(const Program state)
{
  switch (state)
  {
    case Program::cube :       /* mach sinnvolles */  break;
    case Program::dice :       /* mach sinnvolles */  break;
    case Program::fadeOnSite : /* mach sinnvolles */  break;
    case Program::disco :      /* mach sinnvolles */  break;
  }
}

Da mach ich dann schon das hier komplett. Möglichst in sich geschlossen.

void stateMaschine(Program &state)
{
  switch (state)
  {
    case Program::cube :        /* mach sinnvolles */ 
                                state = Program::dice;        break;
    case Program::dice :        /* mach sinnvolles */ 
                                state = Program::fadeOnSite;  break;
    case Program::fadeOnSite :  /* mach sinnvolles */ 
                                state = Program::disco;       break;
    case Program::disco :       /* mach sinnvolles */ 
                                state = Program::cube;        break;
  }
}

Das wäre der entsprechende Funktionsaufruf im case. Die komplette Displayausgabe schreibe ich ja nicht komplett ins case. Es wird die Funktion dafür an Ort und Stelle aufgerufen.

Wir können darüber bestimmt ewig reden. :grinning: Am Ende müßte man mehrere Ansätze vergleichen die jeweils für einen ganz bestimmten Zweck geschrieben wurden. Damit man die Unterschiede der Ansätze auch versteht. Warum wieso weshalb. Warum das so hier ein Vorteil ist und dort nicht usw.. Ihr wisst schon.

kann ja eh jeder machen wie er will.

Dennoch ein Beispiel von mir.
Bisher haben wir die 4 Status nur Reihum durchgeschaltet.
Aber jetzt möchte ich z.b. in der Nacht nur zwischen Dice und Fade wechseln weil die anderen beiden zu hell sind.

Oder weil Arduino Freunde das sind, schnell mal ein Demo durchklicken, aber nicht mehr ins Disco fallen, weil Disco relativ lang dauert.

Die Abläufe unterscheiden sich, rufen die einzelnen Muster in anderer Reihenfolge auf. Die Muster bleiben aber immer gleich.
Da hab ich die Ablaufsteuerung lieber separat. Oder auch wenn ich mit Timer oder Button-Events arbeite, kommt mir eine Trennung entgegen.

1 Like

Hallo,

okay, jetzt verstehe ich den Sinn. Alles klar. Horizont erweitert. :wink:

Hab mich etwas mit Strukturen und Klassen beschäftigt. Schöne Sache, aber, ich denke eine Klasse hilft mir in meinem Code nicht wirklich.

Die Programme an sich, passen nicht in eine Klasse, denn jedes Programm ist zu unterschiedlich bzw. macht unterschiedlich Dinge. Die Parameter kann man (FadeInOut, Demoaktiv) könnte man zusammenfassen. Da würde eine Struktur reichen.

Nur wie mache das dann mit dem, ich sage mal "Handler"? Wenn ich eine Struktur erstelle, wo die Parameter gespeichert werden, gebe ich den Namen. Beispiel:

struct Handler {
  bool fadeinout;
  bool demoaktiv;
  Handler(bool b, bool c){
    fadeinout = b;
    demoaktiv = c;
  }
};
                 // (Fade, Demo);
Handler Cube        (false, false);
Handler Dice        (false, false);
Handler FadeOnSite  (false, false);
Handler Disco       (true, true);
Handler Rainbow     (true, true);
Handler Flash       (false, true);
Handler Snake       (false, true);
Handler Rain        (false, true);
Handler Heratbeat   (false, true);

Nur habe ich jetzt noch keine Programmreihenfolge mit Nummern. Also kann ich nicht einfach wie bisher mit program++ zum nächsten Programm springen. Ich müsste also zusätzlich irgendwo angeben, zu welchen Programm gesprungen werden muss.

Eine weitere Möglichkeit wäre die Programmnummer mit in der Struktur zu speichern. Bsp:

struct Handler {
  byte nummer;
  bool fadeinout;
  bool demoaktiv;
  Handler(byte a, bool b, bool c){
    nummer = a;
    fadeinout = b;
    demoaktiv = c;
  }
};
                 // (Nr, Fade, Demo);
Handler Cube        (0, false, false);
Handler Dice        (1, false, false);
Handler FadeOnSite  (2, false, false);
Handler Disco       (3, true, true);
Handler Rainbow     (4, true, true);
Handler Flash       (5, false, true);
Handler Snake       (6, false, true);
Handler Rain        (7, false, true);
Handler Heratbeat   (8, false, true);

Jetzt könnte ich wieder einen Zähler nutzen, und das Programm anspringen was mit dem Zähler übereinstimmt. Jetzt würde ich aber pro Programm wieder ein Byte mehr verbrauchen, was dem Speicher sparen nicht entgegen kommt. (Auch spart generell die Klasse oder die Struktur kein Speicher, denn die Daten müssen ja dennoch gespeichert werden. Es fördert aber die Übersicht)

Und nochmal zu enum. Es wurde ja empfohlen, die Zahlen beim Switch Casedurch die Programmnamen zu enumieren. Es soll die Lesbarkeit fördern. Das ist schon richtig, wenn an vielen Orten im Code die Programmnummer im Zusammenhang mit dem Programm stehen würde. Aber, die Nummern sind nur im Switch Case zu sehen. Und bei jeder Zahl steht direkt dazu das auszuführende Programm im Code. Sprich man könnte die Zuweisung nicht "verwechseln". Das dort zu ändern bringt mir irgendwie nur Nachteile.

Aber ein was gutes hat es. Ich habe mich damit mal beschäftigt :smiley:

Du könntest auch ein Array aus den Strukturen benutzen.
Dein struct mit Construktor ist übrigens schon eine Klasse.

Gruß Tommy

Hast du auf die schnelle ein kurzes Beispiel? (Ich glaub ich hab schon wieder Knoten im Hirn)

Ok. Ich dachte der Unterschied besteht nur, das in Klassen auch Methoden verwendet werden. Und natürlich das unterschiedliche Schlüsselwort.

Kurz ungetestet:

struct Handler {
  bool fadeinout;
  bool demoaktiv;
  Handler(bool b, bool c){
    fadeinout = b;
    demoaktiv = c;
  }
};

Handler hArray = {{false,false},{false,false}};

Wenn ich mal die ersten Beiden nehme. Wobei Klassen/Strukturen die mehrfach nur die gleichen Werte haben in meinen Augen wenig Sinn machen.

Gruß Tommy

Eine struct ist eine Klasse, bei der alle Klassenmember public sind. C++-Buch könnte helfen...

Warum gibt es dann eigentlich überhaupt struct? Wenn man bei einer class nichts angibt (public, privat, protected), dann ist dort doch auch alles public. Irgendwie erschleicht mir dann nicht der Sinn von struct.

Rückwärtskompatibilität mit C und etwas Schreibfaulheit :grinning_face_with_smiling_eyes:

1 Like

Falsch, dann ist alles private.

Gruß Tommy

Ok, dann war die Erklärung woanders falsch.

Du hattest die Eckige Klammer oben bei Array Beispiel vergessen. Zumindest gab es bei mir Fehler.

Handler hArray[] = {{false,false},{false,false}};

Edit: Man sollte halt nicht immer der ersten Stelle im Netz glauben schenken.
Die erste Quelle sagte, bei class ist Default Public. Andere Quellen sagen Privat ist Default. Das wäre auch ein nennenswerter Unterschied zwischen struct und class. Denn bei struct soll Default Public sein

Richtig erkannt.

Gruß Tommy

Wie schon geschrieben, mit einem Feld aus Strukturen:

#include <Streaming.h>

enum {
  cube,
  dice,
  fadeOnSite,
  disco,
  rainbow,
  flash,
  snake,
  rain,
  heratbeat
};

struct Handler {
  bool fadeinout;
  bool demoaktiv;
  Handler(bool b, bool c) {
    fadeinout = b;
    demoaktiv = c;
  }
};
Handler handler[] = {
  // Fade, Demo
  {false, false},  // Cube
  {false, false},  // Dice
  {false, false},  // FadeOnSite
  {true, true},    // Disco
  {true, true},    // Rainbow
  {false, true},   // Flash
  {false, true},   // Snake
  {false, true},   // Rain
  {false, true},   // Heratbeat
};

void setup (void)
{
  Serial.begin(9600);
  Serial.println("\nStart ...");
  for (byte index = 0; index < sizeof(handler) / sizeof(handler[0]); index++)
  {
    Serial << index << F("\tFade: ") << handler[index].fadeinout << F("\tDemo: ") << handler[index].demoaktiv << '\n';
  }
  Serial << "\nRainbow" << F("\tFade: ") << handler[rainbow].fadeinout << F("\tDemo: ") << handler[rainbow].demoaktiv << '\n';
}
void loop() {}
1 Like

Danke, hat mir sehr geholfen.

sizeof(handler) / sizeof(handler[0])

Coole Lösung. Egal wie viele, ich sag mal "Argumente", das Array hat, es wird immer wieder auf "die Anzahl der Programme" begrenzt. Nice

Elemente des Feldes, Feldelemente

Ist leider nicht von mir, haben sich andere schlaue Menschen ausgedacht.

Manchmal lande ich einen Treffer :relaxed:

Zu einer Klasse/Struktur können auch Methoden gehören, die dann gleich auf die zugehörigen Daten zugreifen können. Die Methode handler[rainbow].fade() könnte Dir beispielsweise den Wert true zurückliefern.

Ich habe nicht das komplette Thema durchgelesen, möglicherweise wäre aber die Methode handler[rainbow].animation(rainbow) mit switch/case oder auch handler[rainbow].animation_rainbow eine Anregung.