Moba Tools + Eieruhr timer

Hallo Leute,

könnt ihr mir Bitte kurz weiterhelfen ?

Ich möchte gerne, dass sich nach dem Ablauf des Timers, also nach 2 Sek. der Zustand der Variable von “oben” auf “neu” geändert wird.

Hierzu benutze ich den EggTimer von den Moba Tools.

Der Zustand ändert sich aber auch nach mehreren Sekunden nicht und bleibt auf “oben”.

Was muss ich korrigieren ?

LG
Christian

#include <MobaTools.h>

EggTimer StoppUhr;

String Z_Schlitten;


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

Z_Schlitten = "oben";

}

void loop()
{
   Serial.print("Zustand Schlitten= ");
   Serial.println(Z_Schlitten);
 //  delay(200);

    

  StoppUhr.setTime(2000);

   if ( not StoppUhr.running() ) {

   Z_Schlitten = "neu"; 
  
   }
   
}

Du startest die Zeit in jedem Loopdurchgang neu ... deshalb läuft sie nie ab.
Du hast auch nicht geschrieben, wann die Zeit den starten soll.
So wie dein Testsketch aussieht, würde ich das da erstmal im setup machen. Dann wird dein String halt 2Sek nach dem Programmstart einmalig geändert.

P.S. Du solltest 'EggTimer' in 'MoToTimer' ändern. 'EggTimer' ist der alte Name. Es sei denn, Du hast noch eine sehr alte Version der Lib ( dann solltest Du updaten :wink: ).

2., gleichen Topic gelöscht.
Grüße Uwe

Hallo Franz Peter,

Danke für deine Hilfe, so wie Du es beschrieben hast funktioniert es nun :slight_smile: .

Als nächstes werde ich dann versuchen den Timer in den richtigen Programablauf zu integrieren, dort muss dieser jedoch im loop Teil stehen.
Ich hoffe das klappt dann doch irgendwie ::slight_smile:

LG
Christian

christian_auer_11:
Ich hoffe das klappt dann doch irgendwie ::slight_smile:

Eine Schrittkette (=endlicher Automat, =finite state machine) könnte Dir helfen.

Hallo Agmue,

Ja mit der Schrittkette hast Du völlig recht, aber da habe ich 0—Plan wie das funktionieren könnte.
Wenn das mit dem MoToTimer funktioniert, denke ich, bin ich aus dem gröbsten herausen.

LG
Christian

christian_auer_11:
Ja mit der Schrittkette hast Du völlig recht, aber da habe ich 0—Plan wie das funktionieren könnte.

Ich auch erst nicht, aber das läßt sich glücklicherweise ändern. Suche im Forum nach "agmue anleitung" und Du findest möglicherweise hilfreiche Texte und Links.

christian_auer_11:
Wenn das mit dem MoToTimer funktioniert, denke ich, bin ich aus dem gröbsten herausen.

Nö, aber das merkst Du schon noch rechtzeitig ;D

Hallo Agmue,

ja das mit dem Timer im Loop hat wie von dir prognostiziert nicht geklappt :slight_smile:

Ich habe jetzt mal aus einem Forumsbeispiel einen für mich abgewandelten Fall probiert,
dieser funktioniert aber noch nicht (es rührt sich gar nichts)?

Was muss ich im Code ergänzen bzw korrigieren, damit dieser einfache Zustandsautomat funktioniert ?

Ich habe 2 Zustände deffiniert.
Im 1 Zustand sollte Stepper 1 => 1/2 Umdrehung machen und wenn dies abgeschloßen ist, sollte auf Zustand 2 gewechselt werden, indem Stepper 2 => 1/2 Umdrehung macht, und dann wieder in den Zustand 1 wechselt.

Hier der Code:

#include <MobaTools.h>


MoToStepper S1(1600, STEPDIR);          //bipolarer Schrittmotor Längsschlitten 
MoToStepper S3(1600, STEPDIR);          //bipolarer Schrittmotor Vertikalschlitten


byte   Zustand;    // aktueller Zustand 


void setup()
{

  S1.attach( 53, 51 );                    // STEPpin, DIRpin
  S1.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S1.setRampLen( 10 );                   // Beschleunigung (motorspezifisch)

  S3.attach( 52, 50 );                    // STEPpin, DIRpin
  S3.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S3.setRampLen( 0 );                   // Beschleunigung (motorspezifisch)



}

void loop()
{
  
switch ( Zustand ) {
  
 case 1:
 S1.writeSteps(800);
 Zustand = 2;
 break;
 
 case 2:
 S3.writeSteps(800);
  Zustand = 1;
 break;
 
 }
}

und bei diesem Code Fahren beide Stepper gleichzeitig jedoch nur ein mal.

#include <MobaTools.h>


MoToStepper S1(1600, STEPDIR);          //bipolarer Schrittmotor Längsschlitten 
MoToStepper S3(1600, STEPDIR);          //bipolarer Schrittmotor Vertikalschlitten

enum ZUSTAENDE {A, B};
byte   Zustand = A;    // aktueller Zustand 


void setup()
{

  S1.attach( 53, 51 );                    // STEPpin, DIRpin
  S1.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S1.setRampLen( 10 );                   // Beschleunigung (motorspezifisch)

  S3.attach( 52, 50 );                    // STEPpin, DIRpin
  S3.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S3.setRampLen( 0 );                   // Beschleunigung (motorspezifisch)



}

void loop()
{
  
switch ( Zustand ) {
  
 case A:
 S1.writeSteps(800);
 Zustand = B;
 break;
 
 case B:
 S3.writeSteps(800);
  Zustand = A;
 break;
 
 }
}

LG
Christian

byte   Zustand;    // aktueller Zustand

Welchen Anfangswert hat die Variable?

S1.writeSteps(800);

Ist das eine relative oder obsolute Positionierung?

Hallo Agmue

agmue:

byte   Zustand;    // aktueller Zustand

Welchen Anfangswert hat die Variable?

Den Anfangswert habe ich im zweiten Code auf A eingestellt.
Da Habe ich dein Beispiel mit dem Kohlekran gefunden, und gesehen dass auch ein Anfang—Zustand eingestellt werden sollte.

Ich verstehe aber nicht warum im 2 Code beide Zustände zur gleichen Zeit, und nur ein einziges Mal abgefahren werden?

agmue:

S1.writeSteps(800);

Ist das eine relative oder obsolute Positionierung?

Sollte relativ sein, von der aktuellen Position 1/2 Umdrehung weiter, also keine Referenzfahrt notwendig.

Hallo Christian

christian_auer_11:
Sollte relativ sein, von der aktuellen Position 1/2 Umdrehung weiter, also keine Referenzfahrt notwendig.

Ist aber absolut. writeSteps(800) positioniert den Motor zur Position 800 Steps vom Referenzpunkt entfernt. Und wenn er diese Position erreicht hat, kannst Du das aufrufen so oft Du willst - er wird sich nicht mehr bewegen, da er ja schon am Ziel ist.
Für eine relative Bewegung um 800 Schritte musst Du doSteps(800) verwenden.

christian_auer_11:
Ich verstehe aber nicht warum im 2 Code beide Zustände zur gleichen Zeit, und nur ein einziges Mal abgefahren werden?

Die Aufrufe der MobaTools sind nicht blockierend. Du startest mit writeSteps ( oder auch mit doSteps ) die Bewegung, aber der Sketch bleibt nicht da stehen und wartet bis sie abgeschlossen ist. Der Sketch läuft sofort weiter. Da Du nie wartest bis der Motor am Ziel ist, schaltet der Arduino mit Maximalgeschwindigkeit zwischen den beiden Zuständen hin und her und lässt beide Motore laufen.

agmue:

byte   Zustand;    // aktueller Zustand

Welchen Anfangswert hat die Variable?

Das bezieht sich auf den ersten Code.

Globale Variablen erhalten den Wert 0, in switch/case gibt es aber kein case 0:.

christian_auer_11:
Was muss ich im Code ergänzen bzw korrigieren, damit dieser einfache Zustandsautomat funktioniert ?

byte   Zustand = 1;    // aktueller Zustand

Hallo Franz Peter,
Hallo Agmue,

vielen Dank für eure beiden Rückmeldungen.

Ich habe nun beide Anmerkungen von euch in meinem Code eingearbeitet.
Wie im Post von Franz Peter schon angemerkt, drehen sich nun beide Schrittmotoren quasi solange Strom vorhanden ist.

#include <MobaTools.h>


MoToStepper S1(1600, STEPDIR);          //bipolarer Schrittmotor Längsschlitten => Verfahrweg: 0m bis 3m, stoppt ca. alle 10cm bei Klinge durch Kap. Sensor
MoToStepper S3(1600, STEPDIR);          //bipolarer Schrittmotor Vertikalschlitten

byte   Zustand = 1;    // aktueller Zustand


void setup()
{

  S1.attach( 53, 51 );                    // STEPpin, DIRpin
  S1.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S1.setRampLen( 10 );                   // Beschleunigung (motorspezifisch)

  S3.attach( 52, 50 );                    // STEPpin, DIRpin
  S3.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S3.setRampLen( 0 );                   // Beschleunigung (motorspezifisch)



}

void loop()
{
  
switch ( Zustand ) {
  
 case 1:
 S1.doSteps(800);
 Zustand = 2;
 break;
 
 case 2:
 S3.doSteps(800);
  Zustand = 1;
 break;
 
 }
}

Darufhin habe ich gehofft, das mir in einem “switch / case” Scenario der MoToTimer aus der Patsche hilft, aber denkste … :confused:

Der Gedanke wäre gewessen, dass im case 1: eine Stoppuhr mit 3 Sek. Laufzeit aufgezogen wird, und solange die Stoppuhr läuft, die Anweisung in case 1: abgearbeitet wird.
Erst nachdem die Stoppur abgelaufen ist sollte in case 2: gehüpft werden.

Leider wird auch in einem switch / case Scenario alles in maximalgeschwindigkeit abgearbeitet, so auch die Stoppuhr. Von den 3000 ms werden max 3ms abgearbeitet bis die Stoppuhr wieder neu aufgezogen wird.

 case 1:
 
 StoppUhr.setTime(3000);
 if ( StoppUhr.running() ) { 
 S1.doSteps(800);
 }
 else {
 Zustand = 2;
 }
 break;
 
 case 2:
 S3.doSteps(800);
  Zustand = 1;
 break;

Für mich als Laien bzw. so ähnlich läuft es doch auch beim Beispiel mit dem Wechselblinker ab, nur das halt dort abgefragt wurde ob die Stopuhr steht, und dann ziehe wieder die Stoppuhr auf und hüpfe in den nächsten Zustand.

Was ist für einen Anfänger wirklich die einfachste Möglichkeit dem Arduino beizubringen, dass zuerst der case 1: abzuarbeiten ist, und erst wenn case 1: abgearbeitet ist, wird in den case 2: gesprungen usw.

LG
Christian

christian_auer_11:
... die einfachste Möglichkeit ...

Du willst es nicht lesen, ich nicht schreiben, aber das ist einfach. Gerade im Augenblick nicht für Dich, schon klar, aber generell schon.

Was willst Du eigentlich machen?

Hallo Agmue,

naja mit nicht lesen wollen (falls Du deine Agmue Anleitung meinst) hat das nicht viel zu tun…

Wie soll denn ein Anfänger so ein wirres Zeug verstehen ?? (Auszug aus dem Beispiel mit dem Kohlekran)

  if (MicroVerzoegerung(zeit)) {
    bitfolge = schrittfolge[schritt];
    for (byte j = 0; j < sizeof(pinMotor); j++) {
      digitalWrite(pinMotor[j], (bitfolge >> j) & 1);
    }
    if (abs(sollSchritte) - schrittZaehler < beschleunigungSchritte && beschleunigungSchritte > 0) {
      zeit -= beschleunigungIntervall;
    }
    if (schrittZaehler < verzoegerungSchritte) {
      zeit += verzoegerungIntervall;
    }
    if (schrittZaehler > 0) {
      schrittZaehler--;
    }
    if (sollSchritte > 0) {
      schritt--;
    }
    if (sollSchritte < 0) {
      schritt++;
    }
    if (schritt < 0) {
      schritt = sizeof(schrittfolge);
    } else {
      schritt = schritt % sizeof(schrittfolge);
    }
  }
  if (schrittZaehler == 0) {
    for (byte i = 0; i < sizeof(pinMotor); i++) {
      digitalWrite(pinMotor[i], LOW);
    }
    return true;
  }
  return false;

oder Auszug aus deiner Verlinkung ein “Automat ohne delay”

if(millis()-millisMem < d[0]+d[1]+d[2]+d[3])

ich kann Dir nur sagen das ist für einen Anfänger NICHT geeignet.

Mir ist schon klar, dass das für Dich als “alten Hasen” so leicht zu verstehen ist wie 1+1 zusammenzählen,
für mich als Anfänger ist das aber Raketentechnologie, und somit nicht so einfach zu verstehen.

Da anscheinend viele Wege (auch bei einem Zustandsautomaten) nach Rom führen, wollte ich mich nach einer wirklich einfachen Variante erkundigen, wie ich von case 1: nach case 2: hüpfen kann.

Mein Beispiel was ich hier Anfangen wollte, könnte meiner Meinung nach wirklich als Anfängerbeispiel durchgehen, inwelchem es nur 2 Zustände gibt.
Diese hätte mir helfen sollen in die Thematik mit dem Zustandsautomaten reinzuwachsen.

Ich hätte gerne an meinem Schleifautomaten weitergearbeitet, welchen wir schon einmal hier
https://forum.arduino.cc/index.php?topic=695136.45” besprochen hatten.
Das conclusio dieser Besprechung war, ohne Schrittkette wird das nicht funktionieren.

Zwischenzeitlich habe ich schon mit der Programmierung begonnen, und bin dort hängengeblieben wo ich mit dem Schrittmotor S1 von der Klinge wegfahren möchte um das Sensor Signal zu unterbrechen, sodass S1 wieder selbstsändig zur nächsten Klinge weiterfahren kann.
Hierfür hätte ich zwar eine Lösung mit delay() wobei dies halt nicht sehr sauber ist.
Funktionieren würde es bis jetzt schon mal, ich werde die Zustände allerdings noch etwas optimieren.

Bei meinem Code muss ich noch den S2 ergänzen, sowie S4 und S5, was theoretisch auch wieder mit einfachen if abfragen funktionieren sollte.
Die Schrittkette hätte denn bisherigen Programmablauf etwas aufhübschen / vereinfachen sollen, sodass nicht alles in einer Wurst geschrieben ist.

#define MAX8BUTTONS                                                 
#include <MobaTools.h>

const int sensorpin = 22;              //Kapazitiver Näherungssensor

MoToStepper S1(1600, STEPDIR);          //bipolarer Schrittmotor Längsschlitten => Verfahrweg: 0m bis 3m, stoppt ca. alle 10cm bei Klinge durch Kap. Sensor
MoToStepper S3(1600, STEPDIR);          //bipolarer Schrittmotor Vertikalschlitten

enum { Taster1, Taster2, Taster3, Taster4} ;       //4x Endschalter
const byte tasterPins[] = { 44, 45, 46, 47 };                             
MoToButtons taster( tasterPins, sizeof(tasterPins), 20, 500 );   

int Z_Taster1;  // Zustand Taster1 (HIGH, LOW) 
int Z_Taster2;
int Z_Taster3;
int Z_Taster4;
String Z_Schlitten;

void setup()
{
  Serial.begin (115200);
 
  pinMode(sensorpin, INPUT);
 
  S1.attach( 53, 51 );                    // STEPpin, DIRpin
  S1.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S1.setRampLen( 10 );                   // Beschleunigung (motorspezifisch)

  S3.attach( 52, 50 );                    // STEPpin, DIRpin
  S3.setSpeed( 300 );                    // = 80 U/Min (motorspezifisch)
  S3.setRampLen( 0 );                   // Beschleunigung (motorspezifisch)

Z_Taster1 = LOW;
Z_Taster2 = LOW;
Z_Taster3 = LOW;
Z_Taster4 = LOW;
Z_Schlitten = "oben";

}

void loop()
{

//**************************Einlesen der Taster und Sensoren sowie vorvereitung des Seriellen Monitors *****************************
   
  taster.processButtons();                          // Taster einlesen und bearbeiten
 
  int sensorstate = digitalRead(sensorpin);

  Serial.print("Zustand Taster 1= ");
   Serial.println(Z_Taster1);
  Serial.print("Zustand Taster 2= ");
   Serial.println(Z_Taster2);
  Serial.print("Zustand Taster 3= ");
   Serial.println(Z_Taster3);
  Serial.print("Zustand Taster 4= ");
   Serial.println(Z_Taster4);

    Serial.print("Zustand Schlitten= ");
   Serial.println(Z_Schlitten);
  delay(200);
/*




  if (sensorstate == 1)
   {
    Serial.println("Klinge nicht erkannt  =  1");   // Klinge nicht erkannt
   }
  else
   {
    Serial.println(Z_Taster1);
    }
  delay(500);
  
Serial.print("Zustand Taster 1= ");
Serial.println(Z_Taster1);

/*    if (taster.state(Taster1) == HIGH)
   {
    Serial.println("Taster1 gedrückt");         // Klinge nicht erkannt
   }
   
   if (taster.state(Taster1) == LOW)
   {
    Serial.println("Taster1 nicht gedrückt");   // Klinge nicht erkannt
   }
  delay(100);
 */
 
//**************************Programablauf *****************************       

    

  if (taster.pressed(Taster1)) {
    if (Z_Taster1 == LOW) Z_Taster1 = HIGH; else Z_Taster1 = LOW;  
  }
  if (taster.pressed(Taster2)) {
    if (Z_Taster2 == LOW) Z_Taster2 = HIGH; else Z_Taster2 = LOW;  
  }
  if (taster.pressed(Taster3)) {
    if (Z_Taster3 == LOW) Z_Taster3 = HIGH; else Z_Taster3 = LOW;  
  }
  if (taster.pressed(Taster4)) {
    if (Z_Taster4 == LOW) Z_Taster4 = HIGH; else Z_Taster4 = LOW;  
  }

  
 


   if ( sensorstate == 1 && Z_Taster1 == LOW && Z_Taster2 == LOW)             // S1 fährt zu nächswter Klinge
      {                                                           
      S1.rotate(1); 
      S3.rotate(0);
      } else {
       S1.rotate(0);
       S3.rotate(0);
   
   }   

 
 
  if ( sensorstate == 0 && Z_Taster4 == LOW && Z_Schlitten == "oben")       // S1 stoppt bei Klinge, S3 beginnt nach unten zu drehen
  {
     S1.rotate(0);
     S3.rotate(1);
  }



  if ( sensorstate == 0 && Z_Taster4 == HIGH  && Z_Schlitten == "oben")       // S3 fährt gegen Endschalter unten und pausiert dort für 3 Sek  
  {
     S1.rotate(0);
     S3.rotate(0);
     delay(3000); 

         Z_Schlitten = "unten";
  }

  if ( sensorstate == 0 && Z_Schlitten == "unten")       // S3 fährt nach oben
  {
     S1.rotate(0);
     S3.rotate(-1);
 
  }

    if ( sensorstate == 0 && Z_Taster3 == HIGH)       // S3 fährt gegen Endschalter oben und stoppt 
  {
     S1.rotate(0);
     S3.rotate(0);
     Z_Schlitten = "oben_angekommen";
  }

     if ( sensorstate == 0 && Z_Schlitten == "oben_angekommen")       // S1 fährt im Uhrzeigersinn weiter um aus dem Sensorsignal zu kommen
  {
     S1.doSteps(800); 
     S3.rotate(0);
     delay(3000);
     
     Z_Schlitten = "oben";

     Z_Taster3 = LOW;  
     Z_Taster4 = LOW;
     
  }


  
 
}

LG
Christian

christian_auer_11:
Wie soll denn ein Anfänger so ein wirres Zeug verstehen ?? (Auszug aus dem Beispiel mit dem Kohlekran)

Sorry, aber das finde ich gelinde gesagt extrem frech.
Auch als Anfänger kannst du dazu lernen und dir Mühe geben, es zu verstehen.

Was du nicht verstehst, darfst du hier gern nachfragen.
Ob nach dieser Aussage noch jemand helfen will, steht auf einem anderen Blatt.

Hallo Christian,
Du musst dir das mit den Zuständen noch mal klarmachen, und was deine Statemachine denn machen soll. Wenn deine Motore abwechselnd drehen sollen, brauchst Du da schon mehr Zustände:
1 - starte Motor 1 - dann zu 2
2 - Warte bis Motor 1 sein Ziel erreicht hat, dann zu 3
3 - Starte Motor 2 - dann zu 4
4 - Warte bis Motor 2 sein Ziel erreicht hat, dann wieder zu 1

Das ergibt dann für deine Statemachine:

 switch ( Zustand ) {
    case 1:
      // Starte Motor 1 - dann zu 2
      S1.doSteps(800);
      Zustand = 2;
      break;
    case 2:
      // Prüfe ob Motor 1 sein Ziel erreicht hat, wenn ja zu 3
      if ( !S1.moving() ) Zustand = 3;
      break;
    case 3:
      //  Starte Motor 2 - dann zu 4
      S3.doSteps(800);
      Zustand = 4;
      break;
    case 4:
      // Prüfe ob Motor 2 sein Ziel erreicht hat, wenn ja wieder zu 1
      if ( !S3.moving() ) Zustand = 1;
      break;
  }

Wenn Du einen Timer einsetzt läuft es ganz ähnlich. Du musst den Ablauf einfach in mehr kleine Schritte zerlegen.
Du musst dir auch klarmachen, dass der Sketch in den Zuständen 2 und 4, wo er auf den Motor wartet, nicht stehen bleibt!. Der loop läuft weiterhin immer im Kreis, und bei jedem Durchlauf wird nachgeschaut, ob der Motor jetzt am Ziel angekommen ist.

Hallo Franz Peter,

1000 Dank ;D für deine Erklärung, so ergibt das wirklich einen Sinn.

Werde den neuen Code gleich morgen Abend nach der Arbeit testen :slight_smile:

LG
Christian

Hallo Christian,
schön, wenn Du mit der Erklärung zurecht kommst.

Du kannst übrigens schon auch mit 2 Zuständen auskommen, wenn Du nach dem 'klassischen' Automatenprinzip vorgehst: In jedem Zustand wird auf (mindestens) ein Ereignis oder einen Status gewartet. Wenn der eintritt, dann wird eine Aktion ( in deinem Fall das Starten des Motors ) ausgelöst und der Zustand gewechselt. Dort wird dann wieder auf ein (anderes) Ereignis gewartet, bevor eine Aktion und ein Zustandswechsel ausgeführt wird. Dadurch ist dann auch sichergestellt, dass die Aktion nur einmalig ausgeführt wird, und nicht bei jedem loop-Durchlauf.

Für deine Fall sieht das dann so aus:

 switch ( Zustand ) {
    case 1:
      // Warte bis Motor 2 sein Zeil erreicht hat, dann
      // Motor 1 starten und zu 2
      if ( !S3.moving() ) {
        // Starte Motor 1 - dann zu 2
        S1.doSteps(800);
        Zustand = 2;
      }
      break;
    case 2:
      // Warte bis Motor 1 sein Zeil erreicht hat, dann
      // Motor 2 starten und zu 1
      if ( !S1.moving() ) {
        S3.doSteps(800);
        Zustand = 1;
      }
      break;
  }

christian_auer_11:
naja mit nicht lesen wollen (falls Du deine Agmue Anleitung meinst) hat das nicht viel zu tun....

Ich meinte nicht das Lesen der Anleitung, sondern den Satz, es sei generell betrachtet "einfach".

Sorry wenn ich mich mißverständlich ausgedrückt haben sollte!

christian_auer_11:
Wie soll denn ein Anfänger so ein wirres Zeug verstehen ?? (Auszug aus dem Beispiel mit dem Kohlekran)

Eine Anleitung richtet sich an eine bestimmte Zielgruppe, für die es verständlich sein sollte. Da ich Dich nicht kenne, mußt Du selbst herausfinden, welche Anleitungen für Dich geeignet sind und welche nicht. Der µC fand es nicht wirr sondern logisch, denn der Kohlekran dreht, wie er soll.

Ich behaupte nicht, meine Anleitungen seien gut, aber ich versuche damit, anderen auf die Sprünge zu helfen. Gelegendlich gelingt das sogar ::slight_smile:

Zur Schrittkette hat MicroBahner schon was geschrieben, dem kann ich nichts hinzufügen.