kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Umsetzungsbeispiel zu Fehlertoleranz: Modul zur Abschwächung der Auswirkungen bei Verlieren der roten Bahn

(EN google-translate)

(PL google-translate)

Bei der Benutzung des esp32AV zur Linienverfolgung, wird eine Bahn verfolgt, die durch eine rote Linie gekennzeichnet ist.

Abschwächungsmaßnahme: Gerät die Bahn aus dem Fokus der Kamera, so wird das Fahrzeug angehalten.

Im weiteren Verlauf kann noch unterschieden werden, ob die Bahn nur kurz verlassen wurde und das Fahrzeug einfach wieder in die zuletzt bekannte Richtung gelenkt wird, oder ob der Verlust dauerhaft ist, beispielsweise während das Fahrzeug noch nicht auf die Bahn gesetzt wurde.

Lösung

Das Problem wird behandelt, indem die im vorverarbeiteten Kamerabild sichtbare Anzahl roter Pixel prozentual erfasst und der Antrieb deaktiviert wird, sobald dabei eine gewisse Grenze über- (hier 50%), oder unterschritten (hier 1%) wird.

Softwarestand (Processing / PC-seitig), der diese Maßnahme umsetzt:

CaptureJavaRT2022_012regler_MODULAR101_PID.zip
public class Regler
{
    private float spx = 0.0;
    private Antrieb antrieb;
    private float prozent = 0.0;  
    private float regeldifferenz = 0.0;
    
    private float[] e_array = new float[10];  // e_summe = e_array[0] + e_array[1] + ....
    private int index=0;
    private float dt = 0.1f; //Anpassen an frameRate(10); in setup() !!
    private float e_alt = 0.0f;
    public Regler(Antrieb antrieb)
    {
        this.antrieb = antrieb;
    }
    
    public boolean erzeugeStellsignalAusRotbild(int[][] BILD)
    {
      //Schwerpunkt berechnen und einzeichnen
      //und Prozent an roten Pixeln ermitteln
      float gewicht = 0.0;
      int aktiv=0;
      for(int i=0;i<BILD.length;i++)
      {
         for(int k=0;k<BILD[i].length;k++)
         {
               float wert = (float)BILD[i][k];
               spx += wert*(float)k;
               gewicht+=wert;
               if(wert>0.0) aktiv++;
               
         }
      }
      if(gewicht>0.0)
         spx/=gewicht;
      prozent = 100.0*(float)aktiv/(float)(BILD.length*BILD[0].length);   
      regeldifferenz = 0.0;
      if(prozent>1.0 && prozent<50.0)
      {         
         //STELLSIGNALE BESTIMMEN UND AN DEN ANTRIEB SCHICKEN
         ...
         return true; //Erfolg
      }
      else
      {
         //ANTRIEB ABSCHLATEN, WENN ZU WENIG ROTE PIXEL
         antrieb.fahrt(0.0,0.0);
         return false; //kein Erfolg
      }
      
    }

    ...    
}

Code 0-1: Auszug aus dem Quelltext (Klasse Regler), der genau den Mitigation-Bereich enthält: Abschalten, wenn zu wenig rote Pixel.

Modularisierung

Es ist unzureichend, die Fehlertoleranz-Maßnahme irgendwo tief im Quelltext (hier in Klasse Regler) zu versenken. Die Software wird gerade dann, wenn mehr solche Maßnahmen hinzukommen schnell unübersichtlich und damit schwer zu warten.

Statt dessen soll in mehreren Schritte die Maßnahme in mehreren Schritten in zusätzlichen Klassen implementiert werden und Objekte dieser Klassen sollen im Hauptprogramm ( setup() ) gut sichtbar bei den Modulen/Klassen, die sie überwachen registriert werden. Überhaupt soll die Programmstruktur im setup()-Bereich nachvollziehbar sein und alle wichtigen Funktionalitäten dort sichtbar in Form von Objekt- bzw. Klassennamen erkennbar sein.

Was die Funktionsbereiche des Fahrzeugs betrifft, wurde dies schon umgesetzt, indem jedem Funktionsbereich eine Klasse zugeordnet ist (eigener Tab im Sketch):

Die hierarchische Struktur der entstandenen objektorientierten Klassen, kann nachfolgendem UML-Klassendiagramm entnommen werden, in dem zu sehen ist, welche Klasse Methoden von welcher anderen Klasse benutzt. Dieses A-nutzt-B-Schema wird durch eine Verbindung mit einem offenen Pfeil von Klasse A nach Klasse B visualisiert.

UML-KLassendiagramm zu CaptureJavaRT2022_012regler_MODULAR004_bereinigt.

Bild 0-1: UML-KLassendiagramm zu CaptureJavaRT2022_012regler_MODULAR004_bereinigt.

... da aber Daten zu einem großen Teil innerhalb der draw()-Methode der Hauptklasse von einem Objekt geholt und dann an eine andere weiter gereicht werden, stellt die obige Darstellung zwar die konkrete Programmstruktur korrekt dar, trifft aber nicht ganz genau die tatsächlichen logischen Zusammenhänge. Nachfolgendes Schaubild ist diesbezüglich treffender:

Datenflussdiagramm zum Gesamtsystem. Die Blöcke entsprechen in der Software einzelnen Java-Klassen.

Bild 0-2: Datenflussdiagramm zum Gesamtsystem. Die Blöcke entsprechen in der Software einzelnen Java-Klassen.


Nun sollen auch alle Fehlertoleranz-Maßnahmen auf dieser Klassen-Ebene sichtbar werden.


Zudem sollen gleiche Typen von Fehlertoleranz-Pattern in Java als so genannte Schnittstellen (Interfaces) vereinbart werden.

Um das notwendige Vorgehen dabei zu verdeutlichen, wird nun die oben implementierte Maßnahme, den Antrieb abzuschalten, wenn zu wenig Pixel der Bahn zu sehen sind umgesetzt, indem Eine Klasse "FaultObserverBahn" vom Interface-Typ "iFaultObserver" implementiert wird, von der dann ein Objekt beim Regler registriert wird und dort die beschriebene Aufgabe verrichtet.


Wichtig: Die folgenden Entwicklungsschritte ändern nichts an der bereits zuvor implementierten Funktionalität, nur die Software-Architektur ändert sich!


Erster Entwicklungsschritt: Die neuen Klassen werden ergänzt, NOCH OHNE SIE ZU BENUTZEN. Die alte Funktionsweise bleibt hier noch erhalten:

CaptureJavaRT2022_012regler_MODULAR103_MitigationRotVerloren.zip

Erst anschließend wird die alte Programmstruktur durch die neue (mit identischer Funktionalität) ersetzt:

CaptureJavaRT2022_012regler_MODULAR104_FaultObserverAktiv.zip
Übersicht zu den veränderten Programmbereichen in CaptureJavaRT2022_012regler_MODULAR104_FaultObserverAktiv:

Aktualisierter Code in setup():

UDPcomfort udpcomfort;  // define the UDP object
Antrieb antrieb;
IPCapture cam;
Bildverarbeitung bildverarbeitung;
Regler regler;

FaultObserverBahn bahnfehlerobserver;

void setup() 
{
  size(640,480);
  cam = new IPCapture(this, "http://"+IP+":81/stream", "", "");
  cam.start();
  bildverarbeitung = new Bildverarbeitung(cam);
  int[][] BILD = bildverarbeitung.holeRotbild();
  udpcomfort = new UDPcomfort(IP,PORT);
  antrieb = new Antrieb(udpcomfort); 
  bahnfehlerobserver = new FaultObserverBahn(BILD);
  regler = new Regler(antrieb,bahnfehlerobserver);  
  frameRate(10);
}

Code 0-2: Aktualisierter Code in setup().

Aktualisierter Code im Tab "Fehlertoleranz" (iFaultObserver, FaultObserverBahn):

//Spezieller Tab zur Behandlung von Störungen mit Fehlert. Pattern:

//1. Erkennen, wann Bahn verloren wird
//   Methode: Prozentanteil von rot in Bild erkennen und ggf. reagieren

public interface iFaultObserver
{
    public abstract boolean detectedError(); //true, wenn die jeweilige Störung auftritt
}

public class FaultObserverBahn implements iFaultObserver
{
    int[][] BILD;
    public FaultObserverBahn(int[][] BILD)
    {
        this.BILD = BILD;
    }
  
    public boolean detectedError()
    {
      int aktiv = 0;
      for(int i=0;i<BILD.length;i++)
      {
         for(int k=0;k<BILD[i].length;k++)
         {
               float wert = (float)BILD[i][k];
               if(wert>0.0) aktiv++;               
         }
      }
      float prozent = 100.0*(float)aktiv/(float)(BILD.length*BILD[0].length);   
      if(prozent>1.0 && prozent<50.0)
      {
           return false; //Störung tritt nicht auf! 
      }
      else
      {
           return true;
      }
    }
}

Code 0-3: Aktualisierter Code im Tab "Fehlertoleranz" (iFaultObserver, FaultObserverBahn)

Aktualisierter Code in der Klasse "Regler" (vollständig):

public class Regler
{
    private float spx = 0.0;
    private Antrieb antrieb;
    private float prozent = 0.0;  
    private float regeldifferenz = 0.0;
    
    private float[] e_array = new float[10];  // e_summe = e_array[0] + e_array[1] + ....
    private int index=0;
    private float dt = 0.1f; //Anpassen an frameRate(10); in setup() !!
    private float e_alt = 0.0f;
    private iFaultObserver bahnfehlerobserver;
    public Regler(Antrieb antrieb, iFaultObserver bahnfehlerobserver)
    {
        this.antrieb = antrieb;
        this.bahnfehlerobserver = bahnfehlerobserver;
    }
    
    public boolean erzeugeStellsignalAusRotbild(int[][] BILD)
    {
      //Schwerpunkt berechnen und einzeichnen
      //und Prozent an roten Pixeln ermitteln
      float gewicht = 0.0;
      int aktiv=0;
      for(int i=0;i<BILD.length;i++)
      {
         for(int k=0;k<BILD[i].length;k++)
         {
               float wert = (float)BILD[i][k];
               spx += wert*(float)k;
               gewicht+=wert;
               if(wert>0.0) aktiv++;
               
         }
      }
      if(gewicht>0.0)
         spx/=gewicht;
      prozent = 100.0*(float)aktiv/(float)(BILD.length*BILD[0].length);   
      regeldifferenz = 0.0;
//      if(prozent>1.0 && prozent<50.0)
      if(!bahnfehlerobserver.detectedError()) //JETZT IST DER FAULTOBSERVER AKTIV!!!
      {         
         // +/- 1 0=>nicht genug rote Pixel
         // e<0 => links stärker vor
         // e>0 => rechts stärker vor
         regeldifferenz = ((float)(BILD[0].length/2) - spx)/(float)(BILD[0].length/2);
         if(AKTIV)
         {
              float u_links = 0.0;
              float u_rechts = 0.0;

              //Implementierung P-Regler, PI-Regler, PID-Regler
              float P = PROPORTIONALE_VERSTAERKUNG;
              float I = INTEGRALE_VERSTAERKUNG;
              float D = DIFFERENTIALE_VERSTAERKUNG;
              float e = regeldifferenz;
              float eD = (e - e_alt)/dt;
              float eI = 0.0f;
              
              for(int i=0;i<e_array.length;i++)
                 eI+=e_array[i];
              eI*=dt;
              float Freg = P*e + I*eI + D*eD;
              
              e_alt = e;
              e_array[index] = e;
              index++;
              index%=e_array.length;
              
              // float Freg = P*e + I*eI + D*eD;
              //ENDE
              if(regeldifferenz<0.0)
              {
                  u_links  = VORTRIEB;
                  u_rechts = VORTRIEB - Freg;// + PROPORTIONALE_VERSTAERKUNG*(-regeldifferenz);
              }
              else if(regeldifferenz>0.0)
              {
                  u_links  = VORTRIEB + Freg;// + PROPORTIONALE_VERSTAERKUNG*(regeldifferenz);
                  u_rechts = VORTRIEB;
              }
              
              u_links*=ASYMMETRIE;
              u_rechts*=(2.0 - ASYMMETRIE);
              
              antrieb.fahrt(u_links,u_rechts);
         }
         return true; //Erfolg
      }
      else
      {
         antrieb.fahrt(0.0,0.0);
         return false; //kein Erfolg
      }
      
    }
    
    public float holeSchwerpunkt()
    {
         return spx;
    }
    
    public float getProzent()
    {
         return prozent;
    }
    public float getRegeldifferenz()
    {
         return regeldifferenz;
    }
}

Code 0-4: Aktualisierter Code in der Klasse "Regler" (vollständig).

Mit bluj erstellte Klassenstruktur (uml) zu CaptureJavaRT2022_012regler_MODULAR104_FaultObserverAktiv.

Bild 0-3: Mit bluj erstellte Klassenstruktur (uml) zu CaptureJavaRT2022_012regler_MODULAR104_FaultObserverAktiv.

UML-Klassendiagramm der Klasse Regler in CaptureJavaRT2022_012regler_MODULAR104_FaultObserverAktiv.

Bild 0-4: UML-Klassendiagramm der Klasse Regler in CaptureJavaRT2022_012regler_MODULAR104_FaultObserverAktiv.

Hinweise zu UML:

30_Informatik3/02_UML/02_UML_Klassendiagramm

Weiterführende Konzepte

Schemata, wie Fehlertoleranz-Pattern mit dem Grundsystem kooperieren könnten. a) Wartungsschnittstelle, b) Erkennen von Störungen und Reaktion über einen Recovery-Block, der einen alternativen Regler aktivieren kann, c) Ansatz zur Verwendung eines Eskalationspattern mit drei Stufen.

Bild 0-5: Schemata, wie Fehlertoleranz-Pattern mit dem Grundsystem kooperieren könnten. a) Wartungsschnittstelle, b) Erkennen von Störungen und Reaktion über einen Recovery-Block, der einen alternativen Regler aktivieren kann, c) Ansatz zur Verwendung eines Eskalationspattern mit drei Stufen.

Jeder der Blöcke könnte als eigene Klasse (in eigenem Tab) in einem Sketch umgesetzt sein.