Code delayfrei umbauen

Hallo,

immer wieder finde ich im Netz tolle Codes. Nach ersten Tests folgt jedoch oftmals die Ernüchterung, wenn ich den Code dann in bestehende Sketche von mir einbauen möchte, da sie lästige delay-Anweisungen enthalten und somit alles blockieren. >:(

Habt ihr mir Tipps, wie man bestehende Codes delayfrei umbaut?

Mein momentanes Problem ist das hier:

#include <SPI.h>
#include <Ethernet.h>

// this must be unique
byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x59, 0x67 };  
// change network settings to yours
IPAddress ip( 192, 168, 2, 2 );    
IPAddress gateway( 192, 168, 2, 1 );
IPAddress subnet( 255, 255, 255, 0 );

char server[] = "smtpcorp.com";
int port = 2525;

EthernetClient client;

void setup()
{
  Serial.begin(115200);
  pinMode(4,OUTPUT);
  digitalWrite(4,HIGH);
  Ethernet.begin(mac, ip, gateway, gateway, subnet); 
  delay(2000);
  Serial.println(F("Ready. Press 'e' to send."));
}

void loop()
{
  byte inChar;

  inChar = Serial.read();

  if(inChar == 'e')
  {
      if(sendEmail()) Serial.println(F("Email sent"));
      else Serial.println(F("Email failed"));
  }
}

byte sendEmail()
{
  byte thisByte = 0;
  byte respCode;

  if(client.connect(server,port) == 1) {
    Serial.println(F("connected"));
  } else {
    Serial.println(F("connection failed"));
    return 0;
  }

  if(!eRcv()) return 0;

  Serial.println(F("Sending hello"));
// replace 1.2.3.4 with your Arduino's ip
  client.println("EHLO 1.2.3.4");
  if(!eRcv()) return 0;

  Serial.println(F("Sending auth login"));
  client.println("auth login");
  if(!eRcv()) return 0;

  Serial.println(F("Sending User"));
// Change to your base64 encoded user
  client.println("xxxx");

  if(!eRcv()) return 0;

  Serial.println(F("Sending Password"));
// change to your base64 encoded password
  client.println("yyyy");

  if(!eRcv()) return 0;

// change to your email address (sender)
  Serial.println(F("Sending From"));
  client.println("MAIL From: <me@mydomain.com>");
  if(!eRcv()) return 0;

// change to recipient address
  Serial.println(F("Sending To"));
  client.println("RCPT To: <you@yourdomain.com>");
  if(!eRcv()) return 0;

  Serial.println(F("Sending DATA"));
  client.println("DATA");
  if(!eRcv()) return 0;

  Serial.println(F("Sending email"));

// change to recipient address
  client.println("To: You <you@yourdomain.com>");

// change to your address
  client.println("From: Me <me@mydomain.com>");

  client.println("Subject: Arduino email test\r\n");

  client.println("This is from my Arduino!");

  client.println(".");

  if(!eRcv()) return 0;

  Serial.println(F("Sending QUIT"));
  client.println("QUIT");
  if(!eRcv()) return 0;

  client.stop();

  Serial.println(F("disconnected"));

  return 1;
}

byte eRcv()
{
  byte respCode;
  byte thisByte;
  int loopCount = 0;

  while(!client.available()) {
    delay(1);
    loopCount++;

    // if nothing received for 10 seconds, timeout
    if(loopCount > 10000) {
      client.stop();
      Serial.println(F("\r\nTimeout"));
      return 0;
    }
  }

  respCode = client.peek();

  while(client.available())
  {  
    thisByte = client.read();    
    Serial.write(thisByte);
  }

  if(respCode >= '4')
  {
    efail();
    return 0;  
  }

  return 1;
}


void efail()
{
  byte thisByte = 0;
  int loopCount = 0;

  client.println(F("QUIT"));

  while(!client.available()) {
    delay(1);
    loopCount++;

    // if nothing received for 10 seconds, timeout
    if(loopCount > 10000) {
      client.stop();
      Serial.println(F("\r\nTimeout"));
      return;
    }
  }

  while(client.available())
  {  
    thisByte = client.read();    
    Serial.write(thisByte);
  }

  client.stop();

  Serial.println(F("disconnected"));
}

Gruß Chris

Also das Delay im Setup brauchst du ja damit die Ethernet Verbindung Zeit hat sich aufzubauen. Ansonsten ist doch alles ok, hab kein weiteres Delay gefunden.

Gruß

Das Problem ist hier:

      if(sendEmail()) Serial.println(F("Email sent"));

else Serial.println(F("Email failed"));

Leider gibt es keinen 1:1 Ersatz delay() gegen millis().
Und das Problem ist auch genaugenommen weniger das delay(1), als die 10000-Schleife aussenrum.

Statt dessen muss jede Funktion ( wie hier sendEmail() ) ausser OK / Fehler auch "noch in Arbeit" zurückmelden. Und muss wieder aufgerufen werden können, um zu prüfen, ob es inzwischen weiter gegangen ist. So wäre zumindest mein Ansatz.

Die "tollen Codes" zeigen dir nur das Prinzip wie etwas generell geht. Wie man das dann mit dem "tollen BlinkWithoutDelay" kombiniert, ist eine andere Sache.

Was macht die Funktion? Hast du an der schon rumgespielt? Ich sehe den Einsatz von respCode und thisByte nicht.
Eine Funktionsbeschreibung wäre ebenfalls nicht verkehrt.

byte eRcv()
{
  byte respCode;
  byte thisByte;
  int loopCount = 0;


  while(!client.available()) {
    delay(1);
    loopCount++;

    // if nothing received for 10 seconds, timeout
    if(loopCount > 10000) {
      client.stop();
      Serial.println(F("\r\nTimeout"));
      return 0;
    }
  }

Die Funktion kann man besser aufbauen,

/* Funktion liefert den Wert 1, wenn dieser sich die letzen 10000 nicht zurückgemeldet hat */
int8_t eRcv()
{
static uint16_t error;
const uint16_t max_error = 10000;
// Abfrage, ob Client erreichbar ist, wenn nicht, Wert inkrementieren
if(!client.available()) error++;
else error = 0;

if(error >= max_error) return 1;
else return 0;
}

In der Loop rufst du dann diese Funktion jede ms einmal auf. Sollte die Funktion 1 zurückliefern, stoppst du die Verbindung.

So wie ich das sehe, ist das senden einer Mail (mal wieder) eine Aufgabe für einen endlichen Automaten b.z.w. einer Schrittkette. Und im Zuge des Umbaus auf einen solchen Automaten, kann man dann auch alle Schleifen und Delays entfernen.

Was meinst Du mit "Schrittkette"?
Switch/case?

Gruß Chris

Chris72622:
Was meinst Du mit "Schrittkette"?
Switch/case?

Durchaus!
Gibt aber noch mehr Möglichkeiten.
Funktionspointer, Sprungtabellen(Tabelle mit Funktionspointern)
Eigentlich meine ich die Idee, das Konzept, Schrittkette.
Wie man das dann im einzelnen realisiert, ist fast egal.

Die SPS Welt ist in solchen Dingen immer für einen Seitenblick gut!
(die Leute kämpfen fast nur mit diesem Problem)
http://www.et.tu-dresden.de/ifa/uploads/media/P01-07_Ablaufsteuerungen_R1109.pdf

Interessant. :slight_smile:

Ich denke, ich werde auch dieses Problem (mal wieder) mit der famosen FSM Bibliothek vom Brevig angehen.

Gruß Chris

Ich bekomme es nicht hin.

Habe versucht den von mir im Eröffnungsposting geposteten Code von hinten aufzurollen.

Hierfür wollte ich zunächst einmal davon ausgehen, dass die Funktionen eRcv und efail bei Aufruf mehrmals durchlaufen werden.

Ich begann also so:

boolean eRcv()
{
  static byte count = 1;  // Zähler für switch/case
  static int i = 0;       // Zähler für millis()-Schleifen
  static unsigned long enterTime; // Hält Zeitpunkt fest

  static byte respCode;
  byte thisByte;

  switch (count)
  {
    case 1:
      enterTime = millis();
      count++;

    case 2:
      if (!client.available())
      {
        if (millis() - enterTime >= i)
        {
          i++;
          if (i >= 10000)
          {
            client.stop();
            Serial.println(F("\r\nTimeout"));
            count = 1;
            i = 0;
            return 0;
          }
        }
      }
      else
      {
        i = 0;
        count++;
      }
      break;

    case 3:
      respCode = client.peek();
      count++;

    case 4:
      while (client.available())
      {
        thisByte = client.read();
        Serial.write(thisByte);
      }
      count++;

    case 5:
      if (respCode >= '4')
      {
        count++;
      }
      else
      {
        count = 7;
      }
      break;

    case 6:
      if (efail() == true)  // efail-Funktion muss mehrmals durchlaufen werden
      {
        count = 1;
        i = 0;
        return 0;
      }
      break;

    case 7:
      count = 1;
      i = 0;
      return 1;
  }
}





boolean efail() // Gibt eine 1 zurück, sobald vom Client ein Byte zurückkam oder 10 Sekunden verstrichen sind
{
  static byte count = 1;  // Zähler für switch/case
  static int i = 0;       // Zähler für millis()-Schleifen
  static unsigned long enterTime; // Hält Zeitpunkt fest

  byte thisByte = 0;
  int loopCount = 0;

  switch (count)
  {
    case 1:
      enterTime = millis();
      client.println(F("QUIT"));
      count++;
    case 2:
      if (!client.available())
      {
        if (millis() - enterTime >= i)
        {
          i++;
          if (i >= 10000)
          {
            client.stop();
            Serial.println(F("\r\nTimeout"));
            count = 1;
            i = 0;
            return 1;
          }
        }
      }
      else
      {
        while (client.available())
        {
          thisByte = client.read();
          Serial.write(thisByte);
        }
        client.stop();
        Serial.println(F("disconnected"));
        count = 1;
        i = 0;
        return 1;
      }
  }
}

Leider musste ich nun feststellen, dass diese Vorgehensweise doch die falsche zu sein scheint.

Vielleicht hat ja noch jmd. ne Idee.

Ansonsten würde ich das Ganze mit einem Flussdiagramm auseinderrupfen und wieder von ganz vorne anfangen.

Gruß Chris

Da fehlen bei manchen Cases die breaks. Oder willst du fall-through in den nächsten Case absichtlich verwenden? Ich glaube eher nicht.

Das ist so gewollt. Wenn es nur das wäre.. :confused:

Gruß Chris

Ah, jetzt sehe ich es wie du das gedacht hast. :slight_smile:
Ist sehr nett, aber eigentlich nicht zwingend nötig. Damit spart man nur ein paar Aufrufe der Funktion. Ansonsten hat man break -> Funktion beenden -> beim nächsten Aufruf kommt sowie das nächste Case dran.

Bau vielleicht mal mehr Debug Ausgaben ein, damit du siehst in welcher Reihenfolge der Code durchlaufen wird. Du lassen sich auch mit millis() verzögern wenn nötig. So lässt sich das schlecht sagen.

Hallo,

hab mich durchgebissen und den kompletten Code umgebaut, so dass er nun delayfrei funktioniert.

Ich würde die Experten nun gerne darum bitten, mal über Code zu schauen.

Gruß Chris

without_delay.ino (8.17 KB)