Adaption an Multi-MCU - Neuprogrammierung MATLAB® GUI: Unterschied zwischen den Versionen

Aus Technische Beeinflussbarkeit der Geschmacksache Kaffee
Zur Navigation springen Zur Suche springen
Zeile 768: Zeile 768:
   app.zeile_char = char(zeile);
   app.zeile_char = char(zeile);


Mittels einer if-Abfrage wird überprüft, welches der zuvor definierten Kürzel (ssr, bas, mwp) die Antwort enthält und ob diese Platine nicht bereits initialisiert wurde. Die eigentliche Initialisierung wird gestartet, welches dem Benutzer an einer Meldung in der Statusleiste signalisiert wird. (Im folgenden wird dies am Beispiel der SSR dokumentiert. BAS- und MWP-Platine sind analog angelegt). Die zuvor angelegte allgemeine MCU wird nun als ssr_platine abgespeichert und ihr eine neue, SSR-spezifische Verarbeitungsfunktion zugewiesen.  
Mittels einer if-Abfrage wird überprüft, welches der zuvor definierten Kürzel (ssr, bas, mwp) die Antwort enthält und ob diese Platine nicht bereits initialisiert wurde. Die eigentliche Initialisierung wird gestartet, welches dem Benutzer an einer Meldung in der Statusleiste signalisiert wird. (Hier wird dies am Beispiel der SSR dokumentiert. BAS- und MWP-Platine sind analog angelegt). Die zuvor angelegte allgemeine MCU wird als ssr_platine abgespeichert und ihr eine neue, SSR-spezifische Verarbeitungsfunktion zugewiesen.  


   if contains(app.zeile_char,'ssr') && app.ssr_init == 0  
   if contains(app.zeile_char,'ssr') && app.ssr_init == 0  

Version vom 25. Juli 2023, 13:42 Uhr

Melina Scherf, 25.07.2023, offene Themen

  • Messwertpuffr -> Vernünftige Reihenfolge
  • Initalisierung bageschlossen zum Schluss erscheinen
  • Durchrollieren
  • Messwerte erst bei Knopfdruck starten
  • Fehlerfall >>> Überschreibung?
  • Signaölwort M/B!
  • Fehlerfall in Msicherkurve wenn 0
  • Anzeige von Messwerten letzten fehlen
  • Zuordnung Ventile
  • Knöpfe funktionsfähig
  • VrbindungImport ident vor Terminator?
  • Anzeige der Istwerte
  • Heizung nicht funktionsfähig
  • Herizung/ Pumpe stellen() keine Visualisierung
  • Stellfunktionen für Bypass und Brühgruppendrossel
  • Optimierung Status/Bildwechsel der neuen Ventilschaltung
  • Regler und funktionsschaltung ändern
  • neue Aktorenstellung
  • Vorschlag init_mcus()
  • Fehlerpuffer
  • Antwort durchrollieren
  • Kommandopuffer
  • connected


Melina Scherf, 23.07.2023, Ventilschaltung automatischer Wechsel

                if app.manualOverwriteAktiv == 1
                    if app.flag_Y01 == 0
                    % wenn das Ventil geschlossen ist -> Öffnen
                          app.Y01.Icon = '2WegeRechts.png'; % Ändern des Bildes im Reiter "manueller Modus" zu geöffnet
                          app.flag_Y01 = 1;                 % Ändern der Statusinformation, wie das Ventil steht
                    else
                   % wenn das Ventil geöffnet ist -> Schließen
                         app.Y01.Icon = '2WegeGeschlossen.png'; % Ändern des Bildes im Reiter "manueller Modus" zu geschlossen
                         app.flag_Y01 = 0;                      % Ändern der Statusinformation, wie das Ventil steht
                    end
                else
                     if ismember('Y01', varargin)
                        % wenn der String "Y01" der Funktion übergeben wurde (in varargin enthalten ist)
                        % hierbei muss die Funktion ismember benutzt werden, da es sich bei varargin um ein cell array handelt
   
                         app.Y01.Icon = '2WegeRechts.png';      % Ändern des Bildes im Reiter manueller Moduszu geöffnet (da im Funktionsaufruf enthalten)
                         app.flag_Y01 = 1;                      % Ändern der Statusinformation über Ventilstellung
                     else
                         app.Y01.Icon = '2WegeGeschlossen.png'; % Ändern des Bildes im Reiter manueller Moduszu geschlossen (da nicht im Funktionsaufruf enthalten)
                         app.flag_Y01 = 0;                      % Ändern der Statusinformation über Ventilstellung
                     end
                end

Button pushed: app.ventile_stellen("'Y01'"); % Aufruf der Funktion zur Schließung/Öffnung des Ventils

Melina Scherf, 14.06.2023, Ventilschaltung (Python)

Der MicroPython-Code für die zuvor beschriebene Funktionsweise der Ventilschaltung wurde erstellt und auf der Seite zum Schalten Magnetventile SSR-Platine Multi-MCU erklärt.

Melina Scherf, 07.06.2023, Ventilschaltung (MATLAB)

Um die Schaltung der Aktoren übersichtlicher zu gestalten, sollen direkt in den Programmen (automatischer Modus) oder bei Wertänderung (manueller Modus) Funktionen aufgerufen werden.
Die Schaltung der Ventile erfolgt aktuell zunächst über die Festlegung der Sollwerte:

    app.ventileSollIntern=[0 1 0 1 0 0 0 0 0 0]; 

Dieser Vektor wird anschließend im SOLL/IST-Vergleich verarbeitet. Dies ist in der Handhabung sehr umständlich, da zu einem die Zuordnung der Ventile schwer fällt und zum anderen die Position nicht mehr zum aktuellen Stand der Maschine passt, was zukünftig verhindert werden soll. Aktuelle Schaltung: [Y01, Y03, Y04, Y05, Y13, Y06, Y07, Y08, Y09, Y00]
Daher wurde die neue Funktion ventile_stellen() programmiert, die eine unbekannte Anzahl an Eingaben verarbeiten kann. Ein möglicher Aufruf könnte wie folgt aussehen:

    app.ventile_stellen("Y01", "Y13", "Y07");

Die Eingaben werden in der Funktion in dem Vektor zu_schaltende_ventile gespeichert. Im automatischen Modus werden nun alle Ventile, die in dieser Funktion genannt werden, auf 1 gesetzt, alle nicht genannten auf 0. Dazu werden die zu schaltenden Ventile mit der Funktion ventile_schalten_automatisch() an die SSR-Platine übergeben:

    writeline(app.ssr_platine, sprintf(['ventile_schalten_automatisch(', repmat('%s, ', 1, numel(app.zu_schaltende_ventile)-1), '%s)'], app.zu_schaltende_ventile));

Im automatischen Modus werden nur die Werte der eingegebenen Ventile invertiert (ON -> OFF oder OFF -> ON) und dafür in der Funktion ventile_schalten_manuell() übergeben:

    writeline(app.ssr_platine, sprintf(['ventile_schalten_manuell(', repmat('%s, ', 1, numel(app.zu_schaltende_ventile)-1), '%s)'], app.zu_schaltende_ventile));

Theoretisch würde es im manuellen Modus reichen nur einen Wert zu übergeben, denn manuell kann bisher nur ein Ventil gleichzeitig geschaltet werden. Um aber zukünftigen Komplikationen aus dem Weg zu gehen und für zukünftige Bearbeiter eine bessere Übersichtlichkeit und Anpassungsmöglichkeit zu garantieren, wurde sich für den Vektor entschieden.

Für Dosierventil, Pumpe und Heizung sollen die bestehenden Funktionen zusammengelegt werden, sodass die Visualisierung und Stellung der Aktoren in je einer Funktion erfolgt. (noch zu machen)


           app.SSR_Status.Text = char('def_ventile = ventile_schalten_neu.init()'); % Anzeige im Statusfeld der SSR
           writeline(app.ssr_platine, 'def_ventile = ventile_schalten_neu.init()'); % Abspeichern der Ventilnamen mit zugehörigem Pin
           % Es ist eine Unterscheidung des Schaltens der Ventile aus dem automtischen und aus dem manuellen Modus vorzunehmen.
           % Im automatischen Modus werden alle im Funktionsaufruf genannten Ventile geöffnet und alle nicht genannten geschlossen.
           % Dies kann jedoch nicht auf den manuellen Modus übertragen werden, da dort, durch Klicken auf ein Bild in der Graphikoberfläche, nur der Wert EINES Ventiles geändert werden soll
           if app.manualOverwriteAktiv == 1
               % wenn der Manual Overwrite aktiviert ist
                 app.SSR_Status.Text = sprintf(['ventile_schalten_neu.ventile_schalten_manuell(def_ventile, ', repmat('%s, ', 1, numel(varargin{:})-1), '%s)'], varargin{:});       % Anzeige im Statusfeld SSR
                 writeline(app.ssr_platine, sprintf(['ventile_schalten_neu.ventile_schalten_manuell(def_ventile, ', repmat('%s, ', 1, numel(varargin{:})-1), '%s)'], varargin{:})); % Befehl an MicroPython
                   % Aussehen des Befehls an Python: ventile_schalten_neu.ventile_schalten_manuell(def_ventile, xxx)
                   % An der Stelle xxx werden alle der Funktion übergebenenen Ventile als String (in  mit Komma abgetrennt) an MicroPython geschickt
                   % Da nicht festgelegt wurde, wieviele Ventile übergeben werden, wird mittels der repmat Funktion die unbekannte Anzahl eingefügt. 
                   % Für weitere, ausführlichere Erklärungen der Funktion siehe Wiki: http://www.institut-fuer-kaffeetechnologie.de/Wiki/index.php?title=Adaption_an_Multi-MCU_-_Neuprogrammierung_MATLAB%C2%AE_GUI
                % Das Ändern der Statusinformation über die Stellung des Ventils sowie die Bildänderung im Reiter Manueller Modus erfolgen bereits in der Callback-Funktion 


           else
               % Wenn der Manual Overwrite nicht aktiv ist
               app.SSR_Status.Text = sprintf(['ventile_schalten_neu.ventile_schalten_manuell(def_ventile, ', repmat('%s, ', 1, numel(varargin{:})-1), '%s)'], varargin{:});           % Anzeige in Statusfeld SSR
               writeline(app.ssr_platine, sprintf(['ventile_schalten_neu.ventile_schalten_automatisch(def_ventile, ', repmat('%s, ', 1, numel(varargin{:})-1), '%s)'], varargin{:})); % Befehl an Python
                 
                % Ändern der Statusinformation über die Stellung des Ventils sowie Bildänderung im Reiter manueller Modus 
                % Somit kann bei automatischen Programmabläufen optisch die Schaltung der Ventile auf der Oberfläche nachvollzogen werden
           end

Madita vom Stein, 06.06.2023

Beschreibung Prozessbild manueller Modus der GUI

Prozessbild manueller Modus der GUI
gerahmt

Auf dem Prozessbild sind die wesentlichen Zusammenhänge der im Mai 2023 aktuellen Labormaschine dargestellt.

Das Magnetventil Y01 ist das Ventil für den Festwasseranschluss.
Es wird eine Fluid-O-tech Pumpe verwendet. Parallel gibt es einen steuerbaren Bypass, der über ein Drosselventil mit Schrittmotor hergestellt wird.
Über das Magnetventil Y03 (Entschichtungsventil) wird der Boiler durch Umpumpen entschichtet oder durch Abpumpen entleert. Schmutzwasser kann über Y05 (Entwässerungsventil) abgeführt werden und über Y04 (Boilerbefüllung) wird der Boiler befüllt.
Der Kaffee- bzw. Teewasserbezug wird über Y06 (Bezugsventil) geschaltet. Es führt ein ungedrosselter Wasserstrang durch die Wasserwendel im Boiler zum Mischer. Ein weiterer Wasserstrang, der durch ein Dosierventil gedrosselt wird, führt ebenfalls zum Mischer. Der Mischer selbst ist derzeit als T-Stück realisiert.
Das 3/2-Wegeventil Y07 (Mischventil) führt im nicht-geschalteten Zustand in die Abtropfwanne.
Das Magnetventil Y09 (Umschaltventil) schaltet zwischen Kaffeebezug und Teewasserbezug um. Es steht im nicht-geschalteten Zustand auf Kaffeebezug.
Vor der Brühgruppe befindet sich eine Drossel mit Schrittmotor.
Über das Ventil Y08 (Rückspülventil) wird die halbautomatisierte Rückspülreinigung der Brühgruppe realisiert.
Der Dampfhahn ist als 3/2-Wege-Magnetventil Y13 ausgeführt. Im nicht-geschalteten Zustand wird das Kondenswasser zwischen Dampfhahn und -lanze in die Abtropfschale abgeleitet.
Der Stahlboiler der Labormaschine weist 3,6 Liter Gesamtvolumen auf und wird auf 2,4 Liter befüllt. Mit einem 1800 Watt leistungsgeregeltem Heizelement wurden 12 Minuten Aufheizzeit ermittelt.


Melina Scherf, 26.05.2023, Ventilschaltung

Die Schaltung der Ventile wurde zusätzlich angepasst. Zuvor wurde eine Abfrage der IST- und SOLL-Stellwerte der Ventile durchgeführt und bei Abweichungen ein Vektor an die STM32 geschickt, die die Ventile bei 1 auf ON und bei 0 auf OFF geschaltet hat.
Der Vergleich bleibt vorläufig bestehen, nun wird aber bei abweichenden Werten direkt eine Funktion "Yxx__on" bzw. "Yxx_off" aufgerufen. In dieser erfolgt die Ansprache der Ventile auf der Multi-MCU über einen writeline-Befehl, der die Funktionen ventil_on(pin) bzw. ventil_off(pin) auf den Platinen aufruft. Die Definition der Pins wurde zuvor in der Initialisierung in einem Vektor gespeichert, dessen Werte über Indizes händisch zugeordnet und aufgerufen werden:

    writeline(app.ssr_platine, 'a = ventil_schalten.init()'); (Initialisierung)
    writeline(app.ssr_platine, 'ventil_schalten.ventil_on(a[0])'); (Funktionsaufruf auf MCU)

Je nach Stellung des Ventils wird das Icon in der Oberfläche angepasst.
Beachtet werden muss jedoch, dass bisher die ON-Funktion die Ventile OFF schaltet und umgekehrt. Dieses Problem soll zukünftig über eine Neuprogrammierung der Ventilschaltung behoben werden.

       function Y01_off(app)
           app.SSR_Status.Text = char('ventil_schalten.ventil_off(a[0])'); % Anzeige des Befehls in der Oberfläche im Statusfeld der SSR            
           writeline(app.ssr_platine, 'ventil_schalten.ventil_off(a[0])'); % Schließen des Ventil Y01 mit dem Pin an 1. Stelle des Vektors a (in ssr_initalisieren() definiert)
       end
       function Y01_on(app)
           app.SSR_Status.Text = char('ventil_schalten.ventil_on(a[0])'); % Anzeige des Befehls in der Oberfläche im Statusfeld der SSR             
           writeline(app.ssr_platine, 'ventil_schalten.ventil_on(a[0])'); % Öffnen des Ventil Y01 mit dem Pin an 1. Stelle des Vektors a (in ssr_initalisieren() definiert)
           
       end

SOLL-IST

           if ~app.ventileIstIntern(1) == app.ventileSollIntern(1)
               % wenn Soll-Ist-Wert abweichend
               if app.ventileSollIntern(1)
                   % wenn Ventil offen sein sollte
                    app.ventileIstIntern(1) = 1;      % Speichern des neuen Istzustandes
                    app.Y01_on();                     % Funktion zum Öffnen des Ventils
                    app.Y01.Icon = '2WegeRechts.png'; % Ändern des Bildes der Ventilstellung im Reiter "manueller Modus"
                    app.flag_Y01 = 1;                 % Statusindikator auf offen                
                    
               else
                   % wenn Ventil geschlossen sein sollte
                   app.ventileIstIntern(1) = 0;           % Speichern des neuen Istzustandes
                   app.Y01_off();                         % Funktion zum Schließen des Ventils
                   app.Y01.Icon = '2WegeGeschlossen.png'; % Ändern des Bildes der Ventilstellung im Reiter "manueller Modus"
                   app.flag_Y01 = 0;                      % Statusindikator auf geschlossen 
               end
      
           end

Melina Scherf, 25.05.2023, connected()

connected()

Die neu erstellten Platinen und die geschriebene Software wurden erstmals zusammengeführt. Die Initialisierung erfolgt in den meisten Fällen problemlos. Um die Fehlerfälle zu beseitigen wurde in die Initialisierungsfunktion eine Abfrage nach dem Verbindungsstatus eingefügt. Hierbei wird am Ende der Initialisierung die connected()-Funktion aufgerufen, die lediglich das Wort "connected" bei Ausführung rücksendet. Wird diese Wort von der GUI erkannt wird der Verbindungsstatus auf 1 gesetzt. Sollte diese Antwort nicht erkannt werden, wird die Initialisierungsroutine nochmals abgerufen.

 if app.ssr_connected == 0

Initalisierungszeug

writeline(app.ssr_platine, 'import verbunden'); writeline(app.ssr_platine, 'verbunden.connected()');


if contains(app.zeile_char, 'connected') app.ssr_connected = 1; app.Verbinden_Button.Enable = 0; app.SSR_Status.Text = ('SSR-Platine Initialiserung abgeschlossen');

end


end

-> wieder rausgenommen, durch zukünftige Projektgruppen zu bearbeiten (Frage ob nötig)

Melina Scherf, 24.05.2023, Initalisierung BAS, SSR, MWP; Aktorenstellung: Pumpe, Heizung, Dosierventil

Erstmalige Zusammenführung


Dazu wurden die Initialisierungsfunktionen der beiden Platinen um die Schrittmotorinitialisierung erweitert wurden, die jedoch zu großen Teilen aus der bestehenden GUI übernommen wurden.
Es wurden zudem kleinere Anpassungen in den writeline-befehlen vorgenommen, diese betreffen jedoch nur Groß- und Kleinschreibung.

SSR function ssr_initalisieren(app, ~,~)

           % Initalisierungsfunktion der SSR-Platine
           % Import aller benötigten Pythonprogamme + mechanische Initalisierung
           % (aufgerufen aus init_mcus())
         
             % SCHRITTMOTOREN
           
                   app.zeile_char_ssr = char('import Schrittmotor');           % Anzeige des Befehls in der GUI-Oberfläche (Statusfeld SSR)
                   writeline(app.ssr_platine, 'import Schrittmotor');          % Import des Schrittmotor-Python-Programmes
                       
                   app.zeile_char_ssr = char('seq = Schrittmotor.sequenz()');  % Anzeige des Befehls in der GUI-Oberfläche
                   writeline(app.ssr_platine, 'seq = Schrittmotor.sequenz()'); % Steuersequenz für die Schrittmotoren festlegen


                 %Dosierventil 
                   app.zeile_char_ssr = char('pins = Schrittmotor.Dosierventil_Pins()');  
                   writeline(app.ssr_platine, 'pins = Schrittmotor.Dosierventil_Pins()'); % Festlegung der benötigten Pins des Dosierventils
   
                   app.zeile_char_ssr = char('dosierventil = Schrittmotor.setup(pins)'); 
                   writeline(app.ssr_platine, 'dosierventil = Schrittmotor.setup(pins)'); % Festlegung der Ansprache des Dosierventiles
   
                   app.zeile_char_ssr = char('dosierventil_pos = -1'); 
                   writeline(app.ssr_platine, 'dosierventil_pos = -1'); % Initialisierungsposition in der Schrittmotorsequenz


                 %mechanische Initialisierung
                   writeline(app.ssr_platine, 'dosierventil_pos = Schrittmotor.forwardStep(800, dosierventil_pos, seq, dosierventil, "endDosier")');
                   pause(1);
                   writeline(app.ssr_platine, 'dosierventil_pos = Schrittmotor.backwardStep(800, dosierventil_pos, seq, dosierventil, "endDosier")');
                   pause(1);
                   % Befehle zum Vorwärts- und Rückwärtsstellen des Dosierventils
                   % in diesem Fall 800 vorwärts sowie 800 rückwärts Halbschritte
                   % Rückgabe des Signalwortes "endDosier" nach Abschluss des Vorganges 
             % VENTILE
                   
                 % ALT (getestet)
                   writeline(app.ssr_platine, 'import ventil_schalten');     % Import des Python-Programmes
                   writeline(app.ssr_platine, 'a = ventil_schalten.init()'); % Speicherung eines Vektors mit den Pins der Ventile
                   app.Y01_off();                                            % Aufruf der Funktionen zur Schließung aller Ventile -> sicherer Zustand
                   app.Y03_off();
                   app.Y04_off();
                   app.Y05_off();
                   app.Y13_off();
                   app.Y06_off();
                   app.Y07_off();
                   app.Y08_off();
                   app.Y09_off();


BAS

           % SCHRITTMOTOREN
               app.zeile_char_bas = char('import Schrittmotor');           % Anzeige des Befehls in der GUI-Oberfläche (Statusfeld BAS)
               writeline(app.bas_platine, 'import Schrittmotor');          % Import des Schrittmotor-Python-Programmes   
               app.zeile_char_bas = char('seq = Schrittmotor.sequenz()');  % Anzeige des Befehls in der GUI-Oberfläche
               writeline(app.bas_platine, 'seq = Schrittmotor.sequenz()'); % Steuersequenz für die Schrittmotoren festlegen 


               %Brühgruppendrossel
               app.zeile_char_bas = char('pins = Schrittmotor.Bruehgruppendrossel_Pins()'); 
               writeline(app.bas_platine, 'pins = Schrittmotor.Bruehgruppendrossel_Pins()'); % Festlegung der benötigten Pins der Brühgruppendrossel
               app.zeile_char_bas = char('drossel = Schrittmotor.setup(pins)');           
               writeline(app.bas_platine, 'drossel = Schrittmotor.setup(pins)'); % Festlegung der Ansprache des Dosierventiles      
               app.zeile_char_bas = char('drossel_pos = -1'); 
               writeline(app.bas_platine, 'drossel_pos = -1');  % Initialisierungsposition in der Schrittmotorsequenz
               %Bypass
               app.zeile_char_bas = char('pins = Schrittmotor.Bypass_Pins()'); 
               writeline(app.bas_platine, 'pins = Schrittmotor.Bypass_Pins()');  % Festlegung der benötigten Pins des Bypasses
               app.zeile_char_bas = char('Bypass = Schrittmotor.setup(pins)'); 
               writeline(app.bas_platine, 'Bypass = Schrittmotor.setup(pins)');  % Festlegung der Ansprache des Bypasses
               app.zeile_char_bas = char('Bypass_pos = -1'); 
               writeline(app.bas_platine, 'Bypass_pos = -1');  % Initialisierungsposition in der Schrittmotorsequenz
               %mechanische Initialisierung
               writeline(app.bas_platine, 'drossel_pos = Schrittmotor.forwardStep(800, drossel_pos, seq, drossel, endDrossel)');
               pause(1);
               writeline(app.bas_platine, 'drossel_pos = Schrittmotor.backwardStep(800, drossel_pos, seq, drossel, endDrossel)');
               pause(1);
               writeline(app.bas_platine, 'Bypass_pos = Schrittmotor.forwardStep(800, Bypass_pos, seq, Bypass, "endBypass")');
               pause(1);
               writeline(app.bas_platine, 'Bypass_pos = Schrittmotor.backwardStep(800, Bypass_pos, seq, Bypass, "endBypass")');
               pause(1);
              % Befehle zum Vorwärts- und Rückwärtsstellen des Bypasses/ der Brühgruppedrossel
                   % in diesem Fall 800 vorwärts sowie 800 rückwärts Halbschritte
                   % Rückgabe des Signalwortes "endDrossel" bzw. "endBypass" nach Abschluss des Vorganges 
            
            %PUMPE
               app.zeile_char_bas = char('import Pumpensteuerung'); 
               writeline(app.bas_platine, 'import Pumpensteuerung'); %Import des Python-Programms zur Pumpensteuerung

Aktorenstellung Pumpe, Heizung, Dosierventil

Die Funktionen zum Stellen der Pumpe, Heizung und Dosierventil wurden angepasst und werden wie zuvor über den SOLL/IST-Vergleich getriggert.

  • Pumpe: Der Pumpe wird lediglich der Sollwert übergeben:
    writeline(app.bas_platine, sprintf('pumpe(%d)', app.pumpeSollIntern));
  • Heizung: Ein- und Ausschalten ähnlich Ventilschaltung: -> FALSCH
    writeline(app.ssr_platine, 'ventil_on(3)'); (Einschalten)
    writeline(app.ssr_platine, 'ventil_off(4)'); (Ausschalten)
  • Dosierventil:
    writeline(app.ssr_platine, ['dosierventil_pos = schrittmotor.forwardStep(' num2str(dosierventilStellwert*2) ', dosierventil_pos, seq, dosierventil, "endDosier")']); (Vorwärts)
    writeline(app.ssr_platine, ['dosierventil_pos = schrittmotor.backwardStep(' num2str(dosierventilStellwert*(-2)) ', dosierventil_pos, seq, dosierventil, "endDosier")']); (Rückwärts)


MWP

           app.zeile_char_mwp = char('import Messung');   % Anzeige des Befehls in der GUI-Oberfläche (Statusfeld MWP)    
           writeline(app.mwp_platine, 'import Messung');  % Import des Messung-Python-Programmes

Melina Scherf, 20.05.2023, Verarbeitung BAS, SSR

Die RPI-Platine entfällt, sodass deren Funktionen zur Schrittmotorsteuerung auf die BAS- und SSR-Platine verteilt werden.

  • Dosierventil -> SSR
  • Drossel -> BAS
  • Bypass -> BAS

Verarbeitungsfunktion BAS

           zeile = readline(app.bas_platine);        % Einlesen der Antwort der BAS in lokale Variable
           app.zeile_char_bas = char(zeile);         % Abspeicherung der Antwort in globale Variable
           app.BAS_Status.Text = app.zeile_char_bas; % Anzeige der Antwort in Statusfeld der BAS
           
           %SCHRITTMOTOREN
           
                   if contains(app.zeile_char_bas,'endDrossel')
                       % wenn diese Antwort das Signalwort "endDrossel" enthält
                       % dieses wird nach Beendigung des Schrittmotor-Stellens gesendet   
       
                       writeline(app.bas_platine, 'Schrittmotor.setStepperOff(drossel)'); % Stromlos-Schaltung des Schrittmotors
                       app.zeile_char_bas = char('Schrittmotor.setStepperOff(drossel)');
                   end
       
                   if contains(app.zeile_char_bas,'endBypass')
                       % wenn diese Antwort das Signalwort "endBypass" enthält
                       % dieses wird nach Beendigung des Schrittmotor-Stellens gesendet 
       
                       writeline(app.bas_platine, 'Schrittmotor.setStepperOff(Bypass)'); % Stromlos-Schaltung des Schrittmotors
                       app.zeile_char_ssr = char('Schrittmotor.setStepperOff(Bypass)');
                   end

Verarbeitungsfunktion SSR

           zeile = readline(app.ssr_platine);        % Einlesen der Antwort der SSR in lokale Variable
           app.zeile_char_ssr = char(zeile);         % Abspeicherung der Antwort in globale Variable
           app.SSR_Status.Text = app.zeile_char_ssr; % Anzeige der Antwort in Statusfeld der SSR
           
           if contains(app.zeile_char_ssr,'endDosier') 
               % wenn diese Antwort das Signalwort "endDosier" enthält
               % dieses wird nach Beendigung des Schrittmotor-Stellens gesendet
               writeline(app.ssr_platine, 'Schrittmotor.setStepperOff(dosierventil)'); % Stromlos-Schaltung des Schrittmotors
               app.zeile_char_ssr = char('Schrittmotor.setStepperOff(dosierventil)'); 
           end


Melina Scherf, 18.05.2023, Verarbeitungsfunktion BAS (Messwerte)

Verarbeitungsfunktion BAS Messwerte -> Initalisierung vorwerst über simulierte Messwerte

Die simulierte Messwerterfassung wurde in gleicher Weise wie für die MWP für die BAS-Platine erstellt. Beide Platinen-Verarbeitungsfunktionen greifen dabei auf dieselbe Messwert-Verarbeitungsfunktion zu, speichern die Messwerte aber zunächst in unterschiedlichen lokalen Vektoren. Hinzu kommt, dass einkommende Messwerte der BAS-Platine zusätzlich an einem "F!" zu erkennen sind.
Um dies in der Praxis zu testen wurde je die letzte Zeile des von Armin Rohnen erstellten Programm zur simulierten Messdatenerfassung angepasst:
MWP:

  print('M!',T_Boiler, T_Eingang, T_Mischer, P_Boiler, Leitwert, T_Bruehgruppe, P_Bruehgruppe) 

BAS:

  print('F!', Fuellstand , Durchfluss, Pulse)

Melina Scherf, 16.05.2023, Messwerte anzeigen

               aktuelle_uhrzeit = datestr(now,'HH:MM:SS.FFF'); % Aktuelle Uhrzeit ermitteln
               uhrzeit_sekunden = str2num(aktuelle_uhrzeit(1:2))*3600 + str2num(aktuelle_uhrzeit(4:5))*60 + str2num(aktuelle_uhrzeit(7:end)); % Umrechnung in Sekunden
               if app.letzte_anzeige == 0 || (uhrzeit_sekunden - app.letzte_anzeige) > 1
                   % wenn es sich um die erste Anzeige handelt (letzte_anzeige == 0)
                   % oder die letzte Anzeigenaktualisierung länger als 1 Sekunde her ist
                   
                   %Anzeige der Messwerte an der zugehörigen Stelle auf der GUI-Oberfläche
                   % 1. Temperatur Boiler
                       app.Boilertemperatur.Text = num2str(round(app.messwert_puffer(end,1),1));
                           
                   % 2. Frischwassertemperatur
                       app.Frischwassertemperatur.Text = num2str(round(app.messwert_puffer(end,2),1));
                   % 3. Mischwassertemp
                       app.Mischwassertemperatur.Text = num2str(round(app.messwert_puffer(end,3),2));
     
                   % 4. Boilerdruck 
                       app.Boilerdruck.Text = num2str(round(app.messwert_puffer(end,4)));
   
                   % 5. Wasserleitfähigkeit
                       app.Leitungsdruck.Text = num2str(round(app.messwert_puffer(end,5),1));
   
                   % 6. Füllstand
                       app.Boilerfuellstand.Text = num2str(app.messwert_puffer(end,6)); 
   
                   % 7. Durchfluss
                       app.Durchflussrate.Text = num2str(round(app.messwert_puffer(end,7),2));
   
                   % 8. Pulse Flowmeter


                   % 9. Temperatur hinter der Wasserwendel/ vor der Brühgruppe
                       app.BruehgruppenTemp.Text = num2str(round(app.messwert_puffer(end,9),2));
   
                   % 10. Druck in der Brühgruppe
                       app.BruehgruppenDruck.Text = num2str(round(app.messwert_puffer(end,10)));
   
                   % 11. Pumpensteuerspannung
                       
                   % 12. Bypass-Stellung
   
                   % 13. Dosierventilstellung
   
                   % 14. Brühgruppendrossel
   
                   % 15. Heizleistung PWM
                   app.letzte_anzeige = uhrzeit_sekunden; % Abspeichern der letzten Anzeige zur aktuellen Uhrzeit
               end

Melina Scherf, 15.05.2023, Datensicherung

Datenspeicherung
So werden von der Messwertplatine sieben und von der Basisplatine drei Messwerte gesendet, die nach der festgelegten Reihenfolge (s.u.) in der GUI gespeichert werden. Um diese auch sichtbar zu machen, wurde die neu erstellte Oberfläche integriert, welche automatische jede Sekunde die aktualisierten Messwerte an den zugeordneten Positionen anzeigt. 
Es wurde zudem die automatische (nach vorgegebener Zeit in der Oberfläche) und die manuelle Datensicherung (per Knopfdruck) programmiert. Die Dateien enthalten den aktuellen Messwertpuffer und werden mit Zeitstempel und dem Zusatz "automatisch" oder "manuell" angelegt. Bei jedem Schließen der App wird eine Sicherung mit dem Namen "last_data" erstellt, die bei jedem Neustart geladen wird.
     function datensicherung(app, ~, ~)
           % Sicherung aller Messwerte und Eingaben der Oberfläche im Start-Reiter
           % automatische und manuelle Datensicherung können beide mittels dieser Funktion erfolgen
   
               app.Allgemeiner_Status.Text = char('Datensicherung gestartet'); % Anzeige im allgemeinen Statusfeld
               
               % Abspeicherung der Eingaben aus dem Start-Reiter
               meta.Datum = app.Datum.Value;
               meta.Bearbeiter = app.Bearbeiter.Value;
               meta.Messzyklus = app.Messzyklus.Value;
               meta.Bemerkung1 = app.Bemerkung1.Value;
               meta.Bemerkung2 = app.Bemerkung2.Value;
               meta.Bemerkung3 = app.Bemerkung3.Value;
               meta.Kaffeesorte = app.Kaffeesorte.Value;
               meta.Mahlgrad = app.Mahlgrad.Value;
               meta.Kaffeemenge = app.Kaffeemenge.Value;
               meta.Tamperdruck = app.Tamperdruck.Value;
   
               % Überschriften der jeweiligen Messwerte
               meta.messpunkt(1).Value = 'Boilertemperaturwert'; 
               meta.messpunkt(2).Value = 'Frischwassertemperatur';
               meta.messpunkt(3).Value = 'Mischwassertemperatur'; 
               meta.messpunkt(4).Value = 'Boilerdruck';
               meta.messpunkt(5).Value = 'Leitfähigkeit';
               meta.messpunkt(6).Value = 'Füllstand';
               meta.messpunkt(7).Value = 'Durchfluss';
               meta.messpunkt(8).Value = 'Pulse vom Flowmeter';
               meta.messpunkt(9).Value = 'Brühgruppenwassertemperatur';
               meta.messpunkt(10).Value = 'Brühgruppendruck';
               meta.messpunkt(11).Value = 'Pumpenansteuerung';
               meta.messpunkt(12).Value = 'Pumpenbypass';
               meta.messpunkt(13).Value = 'Dosierventilstellung';
               meta.messpunkt(14).Value = 'Brühgruppendrossel';
               meta.messpunkt(15).Value = 'Heizleistung PWM';
               
               % Speicherung der Messwerte mit zugehörigem Zeitstempel
               messdaten.data = app.messwert_puffer;
               messdaten.time = app.zeitstempel_messung;
               
               % es soll unterschieden werden, ob es sich um eine manuelle oder automatische Datensicherung handelt (Kennzeichnung im Dateinamen)
               % dazu wurden die Variablen automatische_datensicherung und manuelle_datensicherung angelegt, die bei den Aurufen der Funktion auf 1 gesetzt werden 
               % sollte weder eine automatische noch manuelle Datensicherung erwünscht sein, die Funkion aber aufgerufen wird, handelt es sich um die Sicherung bei
               % Schließung der App
          
               if app.automatische_datensicherung == 1
                   % wenn es sich um eine automatische Datensicherung handelt
                   app.automatische_datensicherung = 0;          % Rücksetzen auf 0
                   t = char(datetime);                           % Abspeichern der aktuellen Uhrzeit               
                   datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_automatisch']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_automatisch"
                                                                                                              % Achtung im Dateinamen kein ':' möglich!
                   folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; % Festlegen des Speicherortes
                   save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) 
                   save([save_data], 'meta', 'messdaten');       % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort

               elseif app.manuelle_datensicherung == 1
                   % wenn es sich um eine manuelle Datensicherung handelt
                   app.manuelle_datensicherung = 0;              % Rücksetzen auf 0
                   t = char(datetime);                           % Abspeichern der aktuellen Uhrzeit
                   datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_manuell']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_manuell"
                                                                                                          %Achtung im Dateinamen kein ':' möglich!                    
                   folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; %Festlegen des Speicherortes
                   save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) 
                   save([save_data], 'meta', 'messdaten');       % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort


               else
                   % Schließung der App
                   % Die Eingaben im Reiter Start, die hier gespeichert wurden, werden beim nächsten Start der App wieder geladen
                   folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\last_data'; % Festlegen des Speicherortes
                   datei = ['last_data'];                        % Festlegen des Dateinamens
                   save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) 
                   save([save_data], 'meta', 'messdaten');       % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort


               end 
               
               app.Allgemeiner_Status.Text = char('Datensicherung abgeschlossen'); % Anzeige im allgemeinen Statusfeld

Melina Scherf, 14.05.2023, Messwertverarbeitung

Die Messwerte der Messwertplatine werden nun im Messwert-Puffer gespeichert und können von anderen functions verarbeitet werden.
Dazu werden die Messwerte der MWP in einem lokalen Vektor gespeichert und dieser anhand der Indizes dem globalen Messwertpuffer zusammen mit dem Zeitstempel der Messung zugeordnet und umgerechnet. Die Zuordnung erfolgt auf Basis der bestehenden Logik, wobei es allerdings zu Unregelmäßigkeiten kommt, diese könnten zukünftig aufgearbeitet werden:

  1. Boilertemperatur -> MWP
  2. Frischwassertemperatur -> MWP
  3. Mischwassertemp -> MWP
  4. Boilerdruck -> MWP
  5. Wasserleitfähigkeit -> MWP
  6. Füllstand -> BAS
  7. Durchfluss -> BAS
  8. Pulse Flowmeter -> BAS
  9. Temperatur hinter der Wasserwendel/ vor der Brühgruppe -> MWP
  10. Druck in der Brühgruppe -> MWP
  11. Pumpensteuerspannung
  12. Bypass-Stellung
  13. Dosierventilstellung
  14. Brühgruppendrossel
  15. Heizleistung PWM


           if app.verarbeitung_aktiv == 0
               % wenn gerade keine Verarbeitungsroutine durchgeführt wird, kann eine neue gestartet werden
               app.verarbeitung_aktiv = 1; % Variable auf 1 setzen, um erneute Verarbeitung zu verhindern
               messwerte_mwp = [0,0,0,0,0,0,0,0]; % Anlegen von lokalen Vektoren mit 0 als Messwerte
               messwerte_bas = [0,0,0,0,0,0];
               % im Fehlerfall von einer Platine werden nur Nullen an den entsprechenden Stellen der Oberfläche angezeigt
               % ohne diese Vektoren, keine Verarbeitung über eine Verarbeitungsfunktion, wenn eine Platine Fehlerfall hat, möglich
               if strcmp(app.zeile_char_mwp(1:1),'M')
                   % wenn gesendete Antwort der MWP an erster Stelle Signalwort enthielt (kein Fehlerfall auf MWP)
                  messwerte_mwp =str2num(app.zeile_char_mwp_neu); % 0-Vekor mit gesendeten Messwerten überschreiben 
               end   
               if strcmp(app.zeile_char_bas(1:1),'B')
                   % wenn gesendete Antwort der BAS an erster Stelle Signalwort enthielt (kein Fehlerfall auf BAS)
                  messwerte_bas =str2num(app.zeile_char_bas_neu); % 0-Vekor mit gesendeten Messwerten überschreiben 
               end


               %ERSTELLUNG DER ZEITPUNKTE
               aktuelle_uhrzeit = datestr(now,'HH:MM:SS.FFF');    % Aktuelle Uhrzeit ermitteln
               uhrzeit_sekunden = str2num(aktuelle_uhrzeit(1:2))*3600 + str2num(aktuelle_uhrzeit(4:5))*60 + str2num(aktuelle_uhrzeit(7:end)); % Umrechnung in Sekunden
                                                          
               if app.uhrzeit_erste_messung == 0                  % Erkennung der erstmalige Messwertverarbeitung
                   app.uhrzeit_erste_messung = uhrzeit_sekunden;  % Abspeichern des Startzeitpunktes der Messung
                   app.zeitstempel_messung(1,1) = 0;              % Startzeitpunkt an erste Stelle des Vktor mit den Zeitstempel der Messungen speichern
               else
                   app.zeitstempel_messung(end+1,1) = uhrzeit_sekunden - app.uhrzeit_erste_messung; %Hinzufügen eines weiteren Zeitstempels am Ende des Vektors            
                                                                                                    % vergangene Zeit zwischen aktueller und erster Messung
               end
               
               %VERARBEITUNG MESSWERTE
               pos_zeitstempel = length(app.zeitstempel_messung); % Anzahl der durchgeführten Messungen ermitteln = letztes Element
               
               % Abspeichern der Messwerte in letzter Zeile des Messwert-Puffer (pos_zeitstempel) und in zugewiesener Spalte 
               %1. Boilertemperatur
                   app.messwert_puffer(pos_zeitstempel,1) = app.boilerkalibrierkurve(round(messwerte_mwp(1)*3300/4096)); 
                   % Umwandlung des Messwertes zu einem Boilertemperaturwert, durch Extraktion eines Spannungswert aus dem ADC-Wert und dann nachschauen in einer Tabelle.      
               % 2. Frischwassertemperatur
                   app.messwert_puffer(pos_zeitstempel,2)=(messwerte_mwp(2)*1.00416-1.68)*140/4096; 
                   % Umwandlung des Messwertes zu einer Frischwassertemperatur inkl. linearer Kalibrierkurve (ADC)
               % 3. Mischwassertemp
                   app.messwert_puffer(pos_zeitstempel,3)=app.mischerkalibrierkurve(round(messwerte_mwp(3)*3300/4096)); 
                   % Umwandlung des Messwertes zur Mischwassertemperatur,durch extraktion eines Spannungswert aus dem ADC-Wert und dann nachschauen in einer Tabelle.   
               % 4. Boilerdruck
                   app.messwert_puffer(pos_zeitstempel,4)=4500*((messwerte_mwp(4)*1.00605-4.72)/4096)-500; 
                   % Umwandlung des Messwertes zum Boilerdruck. Dieser hat einen 0-Offset von 500mV zzgl. linearer Kalibrierkurve (ADC).
               % 5. Wasserleitfähigkeit
                   app.messwert_puffer(pos_zeitstempel,5)=((messwerte_mwp(5)*1.00382-1.78)*19.8/4096)+0.2;  
                   % Umwandlung des Messwertes zur Wasserleitfähigkeit, dieser hat einen 0-Offset von 0.2mS/cm zzgl. linearer Kalibrierkurve (ADC)
               % 6. Füllstand -> BAS
                   app.messwert_puffer(pos_zeitstempel,6)=messwerte_bas(1);
               % 7. Durchfluss
                   app.messwert_puffer(pos_zeitstempel,7)=messwerte_bas(5);
               % 8. Pulse Flowmeter -> BAS
                   app.messwert_puffer(pos_zeitstempel,8)=messwerte_bas(6);
                   app.flowCounter = app.flowCounter + messwerte_bas(6); 
                   % Kumulierung der Interruptmenge zur bestimmung der Bezugsmenge
               % 9. Temperatur hinter der Wasserwendel/ vor der Brühgruppen
                   app.messwert_puffer(pos_zeitstempel,9)=app.mischerkalibrierkurve(round(messwerte_mwp(6)*3300/4096));
                   % Umwandlung des Messwertes zu einer Brühgruppen oder Wasserwendeltemperatur je nach Einbau       
                   app.messPuffer(end,10)=20000*((werte(10))/4096)-2000; % gültig für AVS Römer Drucksensor
               % 10. Druck in der Brühgruppe.
                   app.messwert_puffer(pos_zeitstempel,10)=15000*((messwerte_mwp(7))/4096)-1500; 
                   % gültig für SEEED Drucksensor, Umwandlung des Messwertes zum Brühgruppendruck. Dieser hat einen 0-Offset von 500mV und es wird nur der Bereich bis 4,5V verwendet
               % 11. Brühgruppentemperatur (neu??) ANPASSEN FRAGE
                   % app.messwert_puffer(pos_zeitstempel,11)= messwerte_mwp(7); 
               % 11. Pumpensteuerspannung -> BAS
                   app.messwert_puffer(pos_zeitstempel,11) = app.Pumpenleistung.Value;
               % 12. Bypass-Stellung -> RPI
                   app.messwert_puffer(pos_zeitstempel,12) = app.Pumpenbypass.Value; 
               % 13. Dosierventilstellung -> RPI
                   app.messwert_puffer(pos_zeitstempel,13) = app.Dosierventil.Value;
               % 14. Brühgruppendrossel -> RPI
                   app.messwert_puffer(pos_zeitstempel,14) = app.Bruehgruppendrossel.Value;
               % 15. Heizleistung PWM
                   app.messwert_puffer(pos_zeitstempel,15) = 0; 
  
   
               if length(app.zeitstempel_messung)>app.puffer_laenge 
                   % wenn Anzahl die Messungen die definierte Pufferlänge übersteigt
                   % Kürzung auf puffer_laenge
                   app.messwert_puffer = app.messwert_puffer(end - app.puffer_laenge:end,:);         % Löschen aller Zeilen von messwert_puffer bis auf die letzten "puffer_laenge"-Zeilen
                   app.zeitstempel_messung = app.zeitstempel_messung(end - app.puffer_laenge:end,1); % Löschen aller Zeilen von zeitstempel_messung bis auf die letzten "puffer_laenge"-Zeilen
               end
               app.messwerte_anzeigen();       % Funktionsaufruf zur Visualisierung der Messwerte in der GUI-Oberfläche      
               app.reglerundfunktionsaufruf(); % Funktionsaufruf, die Regler und Funktionen bearbeitet  
   
               if app.zeitpunkt_letzte_sicherung == 0
                   % wenn noch keine Sicherung erfolgte 
                   app.datensicherung(app);                           % Aufruf der Funktion zur Datensicherung
                   app.zeitpunkt_letzte_sicherung = uhrzeit_sekunden; % Zeitpunkt der letzten Sicherung auf aktuellen Zeitpunkt
               elseif (uhrzeit_sekunden - app.zeitpunkt_letzte_sicherung) > app.Datensicherung.Value
                   %wenn die vergangene Zeit zwischen letzter Sicherung und aktuellem Zeitpunkt eingegebenen Wert im Start-Reiter überschreitet
                   app.automatische_datensicherung = 1;               % Variable, die anzeigt, dass es sich um eine automatische Datensicherung handelt 
                   app.datensicherung(app);                           % Aufruf der Funktion zur Datensicherung
                   app.zeitpunkt_letzte_sicherung = uhrzeit_sekunden; % Zeitpunkt der letzten Sicherung auf aktuellen Zeitpunkt
                   
               end
               app.verarbeitung_aktiv = 0; % Verarbeitung abgeschlossen, neue Verarbeitungsroutine kann beginnen
           end

Melina Scherf, 13.05.2023, Verarbeitungsfunktion MWP

In der MATLAB® GUI wurde die Verarbeitungsfunktion der Messwert-Platine erstellt. Dabei handelt es sich um die Funktion, die bei gesendetem Terminator der MWP aufgerufen wird. Diese kann einkommende Messwerte (Erkennungszeichen "M!") von Fehlern unterscheiden. Der Benutzer kann anhand der Error-Lampe sicherstellen, ob es zu Fehlern kommt, diese würde in diesem Fall rot leuchten. Werden Messwerte erkannt wird eine weitere Funktion zur Verarbeitung der Messwerte aufgerufen.

Zunächst werden die einkommenden Antworten der MWP mittels des readline-Befehls eingelesen, zunächst lokal und anschließend global abgespeichert. Um dies dem Bediener zu verdeutlichen werden die Antworten in der Statusleiste der MWP angezeigt.

  zeile = readline(app.mwp_platine);        
  app.zeile_char_mwp = char(zeile);         
  app.MWP_Status.Text = app.zeile_char_mwp; 
                       

Um die Messwertverarbeitung nur bei einer Messwertübermittlung zuzulassen, wird mittels einer if-Abfrage die gesendete Antwort überprüft - und gegebenenfalls aufbereitet - ob es sich um eine solche handelt. Dafür wird zunächst eine Variable auf den Wert 0 gesetzt, welche nur 1 wird und eine Verarbeitung erlaubt, wenn eine Übermittlung erkannt wurde.

  app.verarbeitung_moeglich_mwp = 0;

Es wird die Länge der einkommenden Antwort ermittelt, anhand dieser kann unterschieden werden, ob es sich um Messwerte handelt.

  lenstr = strlength(app.zeile_char_mwp); 
     % strlength(string): Ermittelt die Länge von string

Es folgt die Abfrage, ob dieser String länger als 2 Zeichen ist. Sollte dies der Fall sein wird überprüft, ob an den ersten 3 Stellen der Antwort die Zeichen ">>>" stehen, diese werden ANPASSEN. Wird dieser Fall erkannt, werden diese Zeichen entfernt, die Antwortlänge um 3 gekürzt und die Antwort weiter überprüft.

  if lenstr>2
     if strcmp(app.zeile_char_mwp(1:3),'>>>') 
        % strcmp(s1, s2): vergleicht die beiden Strings s1 und s2 miteinander
        % app.zeile_char_mwp(1:3): nur die ersten drei Stellen
        app.zeile_char_mwp = app.zeile_char_mwp(4:end); 
           % app.zeile_char_mwp(4:end): alle Zeichen ab der 4. Stelle   
        lenstr = lenstr-3;                                
     end                                                                       
  end            

Anschließend wird überprüft, ob die (übrig gebliebene) Antwort noch mehr als ein Zeichen enthält. Sollte auch dies der Fall sein, wird nach dem Signalzeichen der Messwertübermittlung der MWP gesucht M!. Dieses Signalzeichen wird Python-seitig vor jeder Messwertübermittlung geprintet. Wird es erkannt, handelt es sich um eine Messwertübermittlung und die Variable verarbeitung_moeglich_mwp kann auf den Wert 1 gesetzt werden. Die gesendete Antwort wird in einer neuen Variable gespeichert, um zu verhindern, dass eine zwischenzeitig gesendete und eingelesene Antwort diese überschreibt

  if lenstr > 1
     if strcmp(app.zeile_char_mwp(1:1),'M')
        app.verarbeitung_moeglich_mwp = 1;             
        app.zeile_char_mwp_neu =app.zeile_char_mwp(3:end); 
                                                                              
     end
  end

Nun wird abgefragt, ob es sich um eine Messwertübermittlung handelte und verarbeitung_moeglich_mwp den Wert 1 besitzt. Wenn zusätzlich bereits alle Platinen initialisiert wurden kann die Funktion zur Messwertverarbeitung aufgerufen werden und die Error-Lampe grün leuchten, um dies auch dem Benutzer zu signalisieren. Sollte die Antwort anders aussehen als abgefragt, bleibt verarbeitung_moeglich_mwp bei 0 und die Error-Lampe leuchtet rot.

  if app.verarbeitung_moeglich_mwp == 1 
     if (app.ssr_init == 1 && app.bas_init == 1 && app.mwp_init == 1)
        app.messwerte_verarbeiten();
        app.Error_Lampe.Color = 'g'; 
     end
  else
     app.Error_Lampe.Color = 'r'; 
  end

Melina Scherf, 07.05.2023, Simulierte Messwerte

Um die Verarbeitungsfunktion der MWP zu testen, wurde ein von Armin Rohnen erstelltes Programm zur simulierten Messdatenerfassung eingepflegt. Dieses stellt quasi eine Initialisierung der MWP dar. Das Programm sendet kontinuierlich 10 Messwerte und kann über einen Knopf gestartet sowie beendet werden. Die Initialisierung beginnt mit dem Import der nötigen Python-Programme.

  writeline(app.mwp_platine, "from machine import Timer");   
     % Import der Funktion Timer aus der Python-Bibliothek machine     
  writeline(app.mwp_platine, "from messwerte import messwerte");
    % Import der Funktion messwerte aus der Python-Bibliothek messwerte 

Anschließend wird ein Timer definiert, der bestimmt, in welcher Frequenz (in Sekunden) die Messwerte gesendet werden sollen.

  writeline(app.mwp_platine, "messwert_timer = Timer(mode=Timer.PERIODIC, freq=1, callback=messwerte)");

Melina Scherf, 06.05.2023, Verbinden

Die Initialisierung aller vier MCUs ist nun möglich.

Callback Verbinden Button pushed
Es wird durch Betätigen des Verbinden-Buttons in der angelegten Testumgebung eine Callback-Funktion ausgelöst. In dieser wird zunächst überprüft, ob zuvor noch keine Verbindung aufgebaut wurde (die Variable verbindungsstatus den Wert 0 besitzt). Sollte dies der Fall sein, wird der Variable verbindungsstatus der Wert 1 zugewiesen und es kann mit der eigentlichen Verbindung begonnen werden. Dem Benutzer wird dies durch die grün werdende Error-Lampe auf der Oberfläche sowie einen Informationstext in der allgemeinen Statuszeile angezeigt.

  if app.verbindungsstatus == 0
       app.verbindungsstatus = 1;   
       app.Error_Lampe.Color = 'g';
          % .Color: Farbe einer Lampe
          % 'g': Kürzel der Farbe grün  
       app.Allgemeiner_Status.Text = char('MCUs werden initalisiert...'); 
          % .Text: Text des Labels
          % char(): Umwandlung in ein character array, welches im Label angezeigt werden kann ("Vektor für Buchstaben")

Es wird eine Liste mit allen belegten Ports angelegt und der erste Port ausgewählt. Für diesen Port wird eine allgemeine, unidentifizierte MCU erstellt, inklusive Terminator (Zeilenumbruch), Verbindungsgeschwindigkeit und Verarbeitungsfunktion.

  app.ports = serialportlist("available"); 
     % serialportlist() erstellt eine Liste von Ports
     % "available": definiert, dass nur die belegten Ports in die Liste aufgenommen werden sollen                
  app.port_nummer = 1;                                      
  app.mcu = serialport(app.ports(app.port_nummer), 115200); 
     % serialport(port, baudrate): Verbindung zum Port
     % port: Definition, welcher Port angesprochen werden soll (app.ports(app.port_nummer): "port_nummer"stes Element der Liste ports)
     % baudrate: Baudrate der Verbindung (Geschwindigkeit) 115200 Bd
  configureTerminator(app.mcu, "CR/LF");
     % configureTerminator(device, terminator): Definition eines Terminators für eine Property ("Ende der Antwort")
     % device: Property für die Terminator gelten soll (allgemeine MCU app.mcu)  
     % terminator: Definition des Terminators ("CR/LF": Zeilenumbruch)                  
  configureCallback(app.mcu, "terminator", @app.init_mcus);
     % configureCallback(device, "terminator", callbackFcn): Definiert die aufzurufende Funktion bei erkanntem Terminator
     % callbackFcn: aufzurufende Funktion

Um die am ersten Port angelegte MCU zu identifizieren wird mittels eines writeline Befehls die ident()-Funktion importiert und ausgeführt.

  writeline(app.mcu, "import ident");
     % writeline(device, command): schreibt den Command (Text) zum definierten Device (mit anschließendem Terminator)
     % command: "import ident" Import des Python-Programms ident                  
  writeline(app.mcu, "ident.ident()");
     % ident.ident(): Aufruf der Funktion ident() aus dem Programm ident

Die zugehörige Python-Funktion sendet das Identifikationskürzel der Platine als Antwort an MATLAB® zurück:

ANPASSEN: PYTHON_CODE

Der Zeilenumbruch am Ende der Antwort triggert als Terminator den Aufruf der init_mcus()-Funktion.

init_mcus()
In dieser wird zunächst die gesendete Antwort eingelesen und abgespeichert.

  zeile = readline(app.mcu); 
     % readline(device): liest die gesendete Antwort bis zum ersten Terminator         
  app.zeile_char = char(zeile);

Mittels einer if-Abfrage wird überprüft, welches der zuvor definierten Kürzel (ssr, bas, mwp) die Antwort enthält und ob diese Platine nicht bereits initialisiert wurde. Die eigentliche Initialisierung wird gestartet, welches dem Benutzer an einer Meldung in der Statusleiste signalisiert wird. (Hier wird dies am Beispiel der SSR dokumentiert. BAS- und MWP-Platine sind analog angelegt). Die zuvor angelegte allgemeine MCU wird als ssr_platine abgespeichert und ihr eine neue, SSR-spezifische Verarbeitungsfunktion zugewiesen.

  if contains(app.zeile_char,'ssr') && app.ssr_init == 0 
     % contains(string, pattern): sucht im String string nach dem Ausdruck pattern
     % 'ssr': Identifikationskürzel der SSR-Platine nach dem gesucht werden soll
     app.SSR_Status.Text = ('SSR-Platine wird konfiguriert');
     app.ssr_platine = app.mcu;
     configureCallback(app.ssr_platine, "terminator", @app.ssr_verarbeitung)

Anschließend wird die, der Übersichtlichkeit halber, separate Initialiserungsfunktion aufgerufen, in der sämtliche Python-Programme importiert und die Schrittmotoren sowie Magnetventile mechanisch initialisiert werden. Nach Abschluss der Initialisierung der Platine kann der Initialisierungsstatus auf den Wert 1 gesetzt werde, sodass eine erneute Initialisierung verhindert wird und dem Benutzer eine entsprechende Nachricht angezeigt werden.

  app.ssr_initalisieren();                                              
  app.ssr_init = 1;                                                      
  app.SSR_Status.Text = ('SSR-Platine Initalisierung abgeschlossen'); 

Abschließend wird die Variable hochzaehlen_erlauben auf den Wert 1 gesetzt, um gegebenenfalls die Bearbeitung des nächsten Ports zuzulassen. Diese Variable stellt sicher, dass der aktuelle Port erst nach abgeschlossener Bearbeitung gewechselt werden kann. Dazu wird am Ende der init_mcus()-Funktion die hochzaehlen()-Funktion aufgerufen.
In dieser wird nun überprüft, ob es noch weitere zu bearbeitende Ports gibt, indem die aktuelle Portnummer mit der Länge der Liste mit allen verfügbaren Ports verglichen wird. Sind noch weitere Ports zu bearbeiten und besitzt die Variable zur Erlaubnis zum Hochzählen den Wert 1, kann der nächste Port angewählt werden.

  if app.port_nummer < length(app.ports) && app.next == 1 


hochzaehlen()
In der hochzaehlen()-Funktion wird nun der nächste Port ausgewählt und erneut eine allgemeine MCU inklusive Terminator (Zeilenumbruch), Verarbeitungsgeschwindigkeit und -funktion erstellt. Durch Aufruf der ident()-Funktion wird erneut die init_mcus()-Funktion aufgerufen.

  app.port_nummer = app.port_nummer +1;                 
  app.mcu = serialport(app.ports(app.port_nummer), 115200);
  configureTerminator(app.mcu, "CR/LF");                   
  configureCallback(app.mcu, "terminator", @app.init_mcus);
  writeline(app.mcu, "import ident");                       
  writeline(app.mcu, "ident.ident()"); 

Sind alle belegten Ports abgearbeitet, also der Initialisierungsstatus aller Platinen auf dem Wert 1, wird dies dem Benutzer durch eine Information in der Statuszeile signalisiert.

  if (app.ssr_init == 1 && app.bas_init == 1 && app.mwp_init == 1) 
       app.Allgemeiner_Status.Text = char('Alle MCUs initalisert');

Dieses Verfahren hat den Vorteil, dass die Platinen in einer unbekannten Reihenfolge durch einmaligen Knopfdruck auf den Verbinden-Button verbunden und initialisiert werden können.

Melina Scherf, 28.04.2023

Während der Neuprogrammierung der MATLAB® GUI ist so vorzugehen, dass jeder hinzugefügte Codeabschnitt zunächst mit Versuchs-Microcontrollern getestet wird. Dazu wurden Melina Scherf 4 Versuchs-Microcontroller übergeben.
Es wird bis zur Fertigstellung der neuen GUI durch Madita vom Stein mit einer Testumgebung gearbeitet, in der nach und nach alle für die Programmierung notwendigen Oberflächenelemente angelegt werden. Nach der Fertigstellung werden diese dann in die neue Oberfläche übertragen.
Um die Kommunikation zwischen MATLAB® und MicroPython zu ermöglichen, müssen die MCUs initialisiert werden. Dazu wurde mit Armin Rohnen eine Workshop durchgeführt, in welchem das Vorgehen zur Initialisierung vorgestellt wurde. Anschließend wurde von Melina Scherf diese in den neuen Code eingepflegt. Die MCUs heißen im Code nun ssr_platine, bas_platine, mwp_platine, rpi_platine nach zuvor festgelegten Definition der Benennungen. So wird die Ansprache der alten STM32 zukünftig auf unterschiedliche MCUs aufgeteilt werden.

Melina Scherf, 16.04.2023

Als Vereinfachungspotential wurde zum einen die generelle Übersichtlichkeit des Codes festgestellt. Um dies zu verbessern, wird mit größeren Einzügen gearbeitet, um die übergeordneten Funktionen auf einen Blick zu erkennen. Die Reihenfolge der functions wird zudem thematisch sinnvoll gegliedert, vorläufig in:

  • Programme
  • Regler
  • Kommunikation mit MCU
  • Aktoren

Dazu wurde am Anfang der methods im Code ein Inhaltsverzeichnis und über den jeweiligen Abschnitten Überschriften eingefügt.
Das Programm des Mischwasserbezugs besteht im vorhanden Code aus 8 Funktionen, diese sollen zusammengekürzt und (sofern möglich) in einer Funktion zusammengefasst werden.

Melina Scherf, 01.04.2023

Die Neuprogrammierung der MATLAB® GUI wird von Madita vom Stein und Melina Scherf durchgeführt.
Dabei wird Madita vom Stein die graphische Oberfläche der App erstellen. Es soll versucht werden eine übersichtlichere und ansprechendere Ansicht insbesondere des manuellen Modus' zu erarbeiten.
Melina Scherf wird sich in den bereits bestehenden Code einarbeiten und Vereinfachungs- und Verbesserungspotentiale identifizieren. Diese sollen in die neue MATLAB® GUI einfließen.

Melina Scherf, 31.03.2023

Um den aktuellen Projektteilnehmern einen besseren Überblick zu gewähren, wurde beschlossen die MATLAB® GUI neu zu programmieren. Dies soll dazu beitragen die Lesbarkeit des Codes zu erhöhen.

Melina Scherf, 24.03.2023

Ein Einführungsworkshop wurde durch Armin Rohnen durchgeführt. Dieser diente dazu, den Projektteilnehmern einen ersten Einblick in die Programmierung von MATLAB® in Kombination mit MicroPython zu geben.
Es wurde die generelle Funktion des MATLAB® App Designers vorgestellt und dafür eine erste Testoberfläche erstellt, welche einen Raspberry Pi initialisiert und eine Lampe per Schalter an- und ausschaltet.
Der dabei entstandene Code wurde von Melina Scherf kommentiert, um den anderen Projektteilnehmern, die zuvor noch nicht mit (Micro)Python gearbeitet hatten, eine eigenständige Nacharbeitung zu ermöglichen

Armin Rohnen, 16.02.2023

Für die Inbetriebnahme der Multi-MCU-Elektronik muss der Programmcode der MATLAB®-GUI entsprechend angepasst werden. Die bisherige Datenverarbeitung der STM32-MCU wird dabei auf drei Datenverarbeitungen aufgeteilt. Die angeschlossenen MCUs müssen dabei eindeutig identifiziert werden.