Herangehensweise für Zyklussteuerung



  • ich möchte eine Zyklussteuerung zu einem Prozess programmieren. Der Prozess umfasst 3 Stufen, die nacheinander durchlaufen werden. In jeder Stufe sollen verschiedene Pumpen Zeitgesteuert ein- oder ausgeschaltet werden, zB. Pumpe 1,2,3. Die Eingabe der Zeit soll in Stunden, Minuten, Sekunden möglich sein (hh🇲🇲ss). Nach Stufe 3 soll der Zyklus wieder von vorn beginnen. Wie würdet ihr soetwas machen?

    Ich hab mir für die 3 Stufen Radiobuttuns erstellt, zB. Stufe 1, Stufe 2 und Stufe 3. Untergliedert sind jeweils die Felder als Label Pumpe 1, Pumpe 2, Pumpe 3. Daneben sind jeweils Editfelder in den der Benutzer die Zeit eintragen kann im Format hh🇲🇲ss. 00:00:00 bedeutet dann immer "Pumpe aus". Die angegebene Zeit bezieht sich immer auf Zeit 0. So können auch 2 Pumpen gleichzeitig eingeschaltet werden.

    Würdet ihr für jedes Gerät einen Timer nehmen, der dann die Zeit hochzählt?



  • Statemachine:

    funktion callme_zyklisch()
    {
    switch(Zyklus)
     case Zyklus 1:
      {
        nächster Zyklus? => Zyklus++;
      }break;
     case Zyklus 2:
      {
        nächster Zyklus? => Zyklus++;
      }break;
     case Zyklus 3:
      {
        nächster Zyklus? => Zyklus = 1;
      }break;
    }
    

    - class Pumpe{} schreiben, die alle relevanten zustände und zeiten einer pumpe kapselt.
    - z.B. Pumpe->ZeitenListeAn[]
    - z.B. Pumpe->ZeitenListeAus[]
    - z.B. Pumpe->ZeitenListe[].ZeitAn, Pumpe->ZeitenListe[].ZeitAus...

    - in main eine Liste mit allen Pumpen => je nach Zyklus:

    for(0 bis maxPumpen)
    {
      teste Zeiten in AllePumpen[i]->zeitenlisten[] => nix, an, aus
    }
    

    in etwa so?!

    ein timer reicht der callme_zyklisch() bedient.

    viel Erfolg



  • ui ich glaub das wird eine Aufgabe...
    Ich fang mal mit der Klasse an:

    class TActuator //Klasse für Aktoren
    {
      private:
        TDateTime start;
        TDateTime stop;
        bool status; //on oder off
      public:
        void turnOn(); //einschalten
        void turnOff(); //ausschalten
    };
    

    Die Frage ist welchen Datentyp ich für die Zeit nehme. Die Zeit soll ja relativ zur Startzeit 00:00:00 sein zB. Pumpe1 soll nach 5 minuten einschalten und 10 Minuten laufen. Wie mach ich das mit der Zeit?



  • rudpower schrieb:

    ui ich glaub das wird eine Aufgabe...

    ... naja, kommt auf den Level Deiner Programmierkenntnisse an 😉

    Wenn Dich asolute Zeiten nicht interessieren, nimmst Du:

    DWORD T0 = GetTickCount();
    DWORD T_in10sec = T0 + 10000;
    
    while(T_in10sec >  GetTickCount()); // Dies wär jetzt wie 10 sec. warten
    

    GetTickCount() ist eine WinAPI Funktion und liefert die Zeit in ms seit Start des Rechners. Hinweis: der Zähler läuft nach 47 Tagen (oder so) über. Wenn der Rechner im Dauerbetrieb ist, könnte es dann zu Problemen kommen...

    TDateTime kannst Du auch als double casten:

    TDateTime jetzt =  TDateTime.CurrentDateTime();
    TDateTime in_x sec = jetzt + double(double(xSecs)/SecsPerDay);
    oder
    TDateTime in_x_stunden = jetzt + xHours/24 + double(double(xHours%24) * 3600.0/SecsPerDay);
    Quelle: http://www.bytesandmore.de/rad/index.htm
    


  • ach, und versuch mal diesen Ansatz:

    class TActuator //Klasse für Aktoren
    {
      private:
        TDateTime zyklus_start;
        ...
      protected:
        bool __fastcall GetNeedSwitch();
        ...
      public:
        void startZyklus();
        void turnOn(); //einschalten
        void turnOff(); //ausschalten
        ...
        __property bool NeedSwitch = {read = GetNeedSwitch};
    };
    
    void __fastcall TActuator::startZyklus()
    {
      zyklus_start = TDateTime.CurrentDateTime();
    }
    
    bool __fastcall TActuator::GetNeedSwitch()
    {
      if(zyklus_start != 0)
        {
          // wenn Interval abgelaufen return true;
          // sonst: return false; 
        }
       else return false;
    }
    

    Wenn neuer Zyklus beginnt:
    Actuator->startZyklus();

    Im Interval dann:
    if(Actuator->NeedSwitch) Actuator->Schalten();

    in Schalten() dann zyklus_start = 0 und diese Bedingung auch als Status verwenden, das in diesem Zyklus alles erledigt ist, oder Interval erneut starten mit startZyklus()



  • es hat sich eine wichtige Änderung ergeben, die es, glaube ich, einfacher macht. Der Zyklus bleibt im Grunde gleich. Es kann aber immer nur eine Pumpe laufen. Ausserdem werden die Pumpen nacheinander gestartet, d.h. im 1.Zyklus läuft z.B. Pumpe1 zuerst 10 Minuten lang. Danach läuft Pumpe2 1 Minute lang. Dann läuft die 3.Pumpe 10 Sekunden. Dann kommt eine Pause von 1 Stunde. Anschließen kommt der 2.Zyklus...

    Da der Prozess im Dauerbetrieb läuft funktioniert es mit GetTickCount() nicht.



  • Hallo

    Selbstverständlich kannst du trotzdem GetTickCount verwenden, da dich ja nur Zeitbereiche von 10 Minuten interessieren, und nicht von über 47 Tagen. Du must eben nur für den Fall des Überlaufs des Sekundenzähles entsprechend reagieren.

    bis bald
    akari



  • ich glaube die Verwendung der TDateTime Klasse ist hier einfacher, da ich die eingegebenen Zeiten direkt verwenden kann.

    Brauch ich hier überhaupt eine Klasse?
    Hab mal angefangen, komm aber nicht weiter:

    //---------------------------------------------------------------------------
    TDateTime zyklusStart, t1, t2, t3;
    //---------------------------------------------------------------------------
    void __fastcall TForm1::btnStartClick(TObject *Sender)
    {
      zyklusStart = Time();
      t1 = zyklusStart + StrToTime(txtPhase1P1->Text);
      t2 = zyklusStart + StrToTime(txtPhase1P2->Text);
      t3 = zyklusStart + StrToTime(txtPhase1Break->Text);
    
      if (txtPhase1P1->Text != "00:00:00")
        PumpeP1Einschalten();
      timCycle->Enabled = true;
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::timCycleTimer(TObject *Sender)
    {
      if (Time() == t1)
      {
        PumpeP1Ausschalten();
        PumpeP2Einschalten();
      }
      if (Time() == t2)
      {
        PumpeP2Ausschalten();
      }
      if (Time() == t3)
      {
        hier soll dann z.B. eine Pause sein z.B. 1 Stunde
      }
    }
    //---------------------------------------------------------------------------
    

    So funktioniert es jedenfalls nicht. Nehm ich die Statemachine mit dem Switch weiss ich nicht wie ich prüf ob er in die nächste Phase soll. Vielleicht sind die Phasen hier unwichtig. Das ist ja nur eine Darstellungssache. Im Prinzip läuft ja alles von oben nach unten ab nach vorgegebenen Zeiten.



  • TTime, respektive TDateTime sind double-Variablen!! Niemals, niemals und ich meine niemals vergleicht man Fließkommavariablen auf Gleichheit. In diesem Fall würde ich SameSecond oder MilliSecondsBetween verwenden.

    Ich würde das auch anders angehen. Ich würde die 'Aktionen' in einer Liste speichern. Für die Aktionen würde ich eine kleine Klasse bauen, die die Pumpennummer, die Aktion (an/aus) und die Dauer in Sekunden angeben.
    In der Liste würden dann die folgenden Aktionen stehen:
    Pumpe 1, an, 600
    Pumpe 1, aus, 0 // 0 = keine Verzögerung
    Pumpe 2, an, 60
    Pumpe 2, aus, 0
    Pumpe 3, an, 10
    Pumpe 3, aus, 3600

    Du startest mit dem Buttonklick den Zyklus (also Pumpe 1 einschalten) und stellst den Timer auf 600 Sekunden. Wenn der Timer auslöst, holst Du den nächsten Befehl aus der Liste (Pumpe 1 aus) und da die dort eingetragene Verzögerung 0 ist, auch gleich noch den nächsten Befehl. Die 60 dort trägst Du als Timer ein usw. Beachte, dass der Timer die Angaben in Millisekunden haben will, also die Sekunden noch mit 1000 multiplizieren. Als Liste würde sich zB eine Queue aus dem Standard anbieten.
    Ich würde die jeweils gerade abgearbeitete Aktion einfach wieder in die Queue schieben, damit das ganze kontinuierlich läuft.



  • vielen Dank für die Hilfe. Ich habe mal die Klasse so weit vorbereitet:

    class TActuator
    {
      private:
        bool action; // Aktion an oder aus;
        int pumpNumber; // Pumpennummer
        int duration; // Dauer in Sekunden
      public:
        TActuator();
        void SetAction(bool action);
        void SetPumpNumber(int pumpNumber);
        void SetDuration(int duration);
        bool GetAction();
        int GetPumpNumber();
        int GetDuration();
    };
    TActuator::TActuator() : action(0), duration(0)
    {
      //TODO: Hier Ihren Quelltext einfügen
    }
    //---------------------------------------------------------------------------
    void TActuator::SetAction(bool action)
    {
      this->action = action;
    }
    //---------------------------------------------------------------------------
    void TActuator::SetPumpNumber(int pumpNumber)
    {
      this->pumpNumber = pumpNumber;
    }
    //---------------------------------------------------------------------------
    void TActuator::SetDuration(int duration)
    {
      this->duration = duration;
    }
    //---------------------------------------------------------------------------
    bool TActuator::GetAction()
    {
      return action;
    }
    //---------------------------------------------------------------------------
    int TActuator::GetPumpNumber()
    {
      return pumpNumber;
    }
    //---------------------------------------------------------------------------
    int TActuator::GetDuration()
    {
      return duration;
    }
    

    In meiner Form hab ich die Objekte erzeugt sowie die queue definiert. Ich hoff es ist richtig so, da ich mit queue noch nie gearbeitet habe.

    class TForm1 : public TForm
    {
    __published:	// Von der IDE verwaltete Komponenten
      TButton *btnStartCycle;
      TTimer *timCycle;
      TMaskEdit *txtP1Duration;
      TMaskEdit *txtP2Duration;
      TMaskEdit *txtP3Duration;
      void __fastcall btnStartCycleClick(TObject *Sender);
    private:	// Anwender-Deklarationen
      TActuator pump1;
      TActuator pump2;
      TActuator pump3;
      queue<TActuator> actionList;
    public:		// Anwender-Deklarationen
      __fastcall TForm1(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    
    #include <vcl.h>
    #pragma hdrstop
    
    #include "unitMain.h"
    
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
      : TForm(Owner)
    {
      pump1.SetPumpNumber(1);
      pump2.SetPumpNumber(2);
      pump3.SetPumpNumber(3);
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::btnStartCycleClick(TObject *Sender)
    {
      if (txtP1Duration->Text == "00:00:00") {
        pump1.SetAction(0);
        pump1.SetDuration(0);
        actionList.push(pump1);
      }
      else {
        pump1.SetAction(1);
        pump1.SetDuration(/*hier dann die umgerechnete Zeit in ms*/);
        actionList.push(pump1);
        //usw.....
    }
    

    Ich muss noch dazu sagen, dass es immer nur eine Pause am Ende jeder "Prozessphase" gibt. Also zB.:
    Phase1:
    Pumpe1 00:10:00
    Pumpe2 00:05:00
    Pumpe3 01:20:00
    Pause 00:30:00
    Phase2:
    Pumpe1 02:50:30
    Pumpe3 00:10:00
    Pause 05:00:00
    Phase3:
    ...usw
    Die Positionen und Reihenfolge der Pumpen und die Pause bleibt gleich. Es ändern sich nur die Zeiten, die der Benutzer während der Laufzeit ändert.

    An der Stelle oben komm ich nicht weiter. Was wäre der nächste Schritt?



  • Kommt drauf an... Wie soll sich das denn verhalten? Wenn der User die Parameter ändert, soll der aktuelle Zyklus vermutlich noch zu Ende laufen?
    Man könnte zB ein Flag setzen, dass die aktuelle Liste ungültig ist und sobald die Pumpe 1 angesprochen werden soll, tauscht Du die Queue gegen eine mit den neuen Werten.

    Du könntest auch darauf verzichten, die abgearbeiteten Befehle automatisch neu in die Queue zu schieben. Wenn die Queue leer ist, befüllst Du sie einfach aus einer 'Masterqueue' neu. Und die Masterqueue erstellst Du jedesmal neu, wenn der User Änderungen durchführt.



  • habs anders gemacht. Habe einen Button "Confirm", den der User bestätigen muss. Dieser ist Enabled=false wenn der Zyklus läuft (also sobald "btnStartCycle" gedrückt wird):

    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
      : TForm(Owner)
    {
      pump1.SetPumpNumber(1);
      pump2.SetPumpNumber(2);
      pump3.SetPumpNumber(3);
    }
    //---------------------------------------------------------------------------
    // rechnet Zeit in ms um:
    ULONG TForm1::DateTimeToMs(TDateTime time)
    {
      unsigned short h, min, sek, ms;
      time.DecodeTime(&h, &min, &sek, &ms);
      return sek + min * 60 + h * 3600 + int(time) * 86400 * 1000;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnConfirmClick(TObject *Sender)
    {
      //Umwandlung der Eingaben in Sekunden:
      ULONG p1Sec = DateTimeToMs(txtP1Duration->Text);
      ULONG p2Sec = DateTimeToMs(txtP2Duration->Text);
      ULONG p3Sec = DateTimeToMs(txtP3Duration->Text);
      ULONG break1 = DateTimeToMs(txtBreakDuration->Text);
    
      // Queue füllen:
      pump1.SetDuration(p1Sec);
      pump1.SetAction(1);
      actionList.push(pump1);
    
      pump1.SetDuration(0);
      pump1.SetAction(0);
      actionList.push(pump1);
    
      pump2.SetDuration(p2Sec);
      pump2.SetAction(1);
      actionList.push(pump2);
    
      pump2.SetDuration(0);
      pump2.SetAction(0);
      actionList.push(pump2);
    
      pump3.SetDuration(p3Sec);
      pump3.SetAction(1);
      actionList.push(pump3);
    
      pump3.SetDuration(break1);
      pump3.SetAction(0);
      actionList.push(pump3);
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnStartCycleClick(TObject *Sender)
    {
      btnConfirm->Enabled = false;
      timCycle->Enabled = true;
      timCycle->Interval = pump1.GetDuration();
    
      actionList.front();
      actionList.pop();
      /* TODO : Pumpe1 starten */
    }
    //---------------------------------------------------------------------------
    

    Das würde doch gehen oder?
    Nun habe ich die Objekte, die die Zustände der Pumpen enthält, sowie die Liste mit den Einträgen. Wo trage ich nun die Befehle ein zum ein-und ausschalten der Pumpen? Soll ich einen zweiten Timer nehmen, der die member action der einzelnen Pumpenobjecte abfragt und per if/else die Pumpen ein-und ausschaltet? also zB:

    if (pump1.GetAction == true) pumpeAEinschalten(1);
    else pumpeAEinschalten(0);
    

    wofür brauch ich die Membervariable pumpNumber? Welche Pumpe seh ich doch am Objektnamen pump1, pump2,...

    ich habe mit queue noch nicht gearbeitet. Wie hol ich mir den nächsten Befehl aus der Liste?



  • so wie ich das gelesen habe komme ich mit actionList.front() an das erste Element. Wie genau komm ich nun darüber an die Daten? Mit pop lösche ich ja das Element. Wie füge ich das Objekt wieder hinten an? (actionList.push(???))



  • Hallo,

    Hier ist die beschreibung von std::queue
    http://www.cplusplus.com/reference/stl/queue/


Log in to reply