Adaption an Multi-MCU - Neuprogrammierung MATLAB® GUI

Aus Technische Beeinflussbarkeit der Geschmacksache Kaffee
Zur Navigation springen Zur Suche springen

Melina Scherf, 25.07.2023, offene Themen

Aufgaben für zukünftige Bearbeitende

  • Messwertpuffr -> Vernünftige Reihenfolge
  • Initalisierung bageschlossen zum Schluss erscheinen
  • Durchrollieren
  • Messwerte erst bei Knopfdruck starten
  • Fehlerfall >>> Überschreibung?
  • Signalwort 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
  • kann es zur Überschreibung der Messwerte während einer Verarbeitungsroutine kommen? Neu einkommende Antowrt überschreibt alte Messwerte: Idee verarbeitung_aktiv bereits in Verarbeitungsfunktion, aber könnte zu Problem führen, wenn beide Platinen auf gleiche Messwertverarbeitungsfunktion zugreifen


Meine Aufgaben

  • Datensicherung: erste Sicherung automatische_datensicherung auf 1
  • Verarbeitung MWP/BAS anpassen, warum _neu


Meine Kommentierung

  • neue Aktorenstellung
  • Vorschlag init_mcus()
  • nur von mir bearbeitete Funktionen von mir kommentiert


Programmcode schon vorhanden

  • 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, 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, 16.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, 15.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

In der Funktion wird abgefragt, ob zum aktuellen Zeitpunkt keine Verarbeitung aktiv ist, somit kann eine komplette Bearbeitung ohne Unterbrechung garantiert werden. Sollte dies der Fall sein, wird die zugehörige Variable auf 1 gesetzt und die Funktion kann weiter ablaufen.

  if app.verarbeitung_aktiv == 0
     app.verarbeitung_aktiv = 1; 

Die Funktion wird sowohl von der MWP als auch von der BAS aufgerufen, somit müssen keine zwei separaten, relativ analoge Funktionen verwendet werden. Dies führt jedoch zu einem Fehler, wenn die eingelesenen Antworten einer der beiden Platinen zum Zeitpunkt der Verarbeitung keine Messwerte enthält (z.B. Stellbefehl für Schrittmotoren BAS), denn diese Antwortzeilen könnten nicht verarbeitet werden. Darum wird je für die MWP und BAS ein Vektor der Länge der übertragenen Messwerte mit 0 gefüllt angelegt. Anschließend wird überprüft, ob es sich bei der aktuell eingelesenen Zeile um Messwerte handelt, indem nach dem Signalzeichen M/B am Zeilenbeginn (app.zeile_char_mwp) gefragt wird. Sollte dies der Fall sein, wird der Nullvektor mit den Messwerten aus der Antwortzeile ohne Signalkennzeichen (app.zeile_char_mwp_neu) überschrieben. Hier kommt es zum Tragen, dass während der Verarbeitungsfunktion die Antwort einmal mit und ohne das Kennzeichen gespeichert wurde. Sollte es sich bei der aktuellen eingelesenen Antwort nicht um Messwerte handeln, wird in der Oberfläche eine 0 aus dem Nullvektor an den jeweiligen Stellen angezeigt.

  messwerte_mwp = [0,0,0,0,0,0,0,0];
     % []: legt einen Vektor an
  messwerte_bas = [0,0,0,0,0,0];
  if strcmp(app.zeile_char_mwp(1:1),'M') 
     messwerte_mwp =str2num(app.zeile_char_mwp_neu);
        % str2num(text): wandelt text von string/char in ein numeric array um, die Messwerte werden von den Platinen als string/char eingelesen, die weitere Verrechnung erfordert jedoch numerische Werte
  end   
  if strcmp(app.zeile_char_bas(1:1),'B')
     messwerte_bas =str2num(app.zeile_char_bas_neu); 
  end

Um die Zeitstempel zu erstellen muss zunächst die aktuelle Uhrzeit ermittelt und anschließend in Sekunden umgerechnet werde, um damit rechnen zu können.

  aktuelle_uhrzeit = datestr(now,'HH:MM:SS.FFF');   
      % datestr(): gibt Uhrzeit als string zurück
      % now: Explizitheit der Uhrzeit (jetzt)
      % 'HH:MM:SS.FFF': Definition des Zeitformates
  uhrzeit_sekunden = str2num(aktuelle_uhrzeit(1:2))*3600 + str2num(aktuelle_uhrzeit(4:5))*60 + str2num(aktuelle_uhrzeit(7:end));
                                                          

Anschließend wird abgefragt, ob es sich bei der aktuellen Messung um die erste Messung handelt. Dazu wurde eine Variable uhrzeit_erste_messung als 0 definiert. Wird das erste Mal in die if-Abfrage gesprungen wird deren Wert auf die aktuelle Uhrzeit, den Zeitpunkt der ersten Messung, gesetzt. Diese Uhrzeit wird nun in der ersten Zeile der ersten Spalte der Matrix zeitstempel_messung gespeichert. In ihr werden sämtliche Zeitstempel aller Messungen gespeichert.
Sollte es sich nicht um die erste Messung handeln wird an die Matrix eine weitere Zeile angehängt und die Differenz zwischen der aktuellen Uhrzeit und der ersten Messung in die erste Spalte gespeichert werden.

  if app.uhrzeit_erste_messung == 0           
     app.uhrzeit_erste_messung = uhrzeit_sekunden; 
     app.zeitstempel_messung(1,1) = 0;             
  else
     app.zeitstempel_messung(end+1,1) = uhrzeit_sekunden - app.uhrzeit_erste_messung;            
  end

Um zu ermitteln um die wievielte Messung es sich handelt wird die Länge der Zeitstempel-Matrix (Zeilenanzahl) abgefragt. In der ermittelten Zeile werden nun die Messwerte in der Variable messwert_puffer in unterschiedlichen Spalten gespeichert. Die Messwerte werden dafür entweder aus der MWP- oder BAS-Antwortzeile eingelesen und umgerechnet. Diese Zuordnung folgt noch einer veralteten Logik und ist daher recht kompliziert. Eine Änderung würde jedoch auch Änderungen vor allem der Regler nach sich ziehen, weshalb hier darauf verzichtet wurde und als offene Aufgabe übernommen wurde. Zusätzlich aufgenommen wurde, dass es bei 9. Temperatur hinter der Wasserwendel einen Fehler gibt sollte eine 0 als Messwert übergeben werden, hier muss sich eine Lösung überlegt werden. Auch gibt es noch Unstimmigkeiten in den Messwerten 11- 15, diese sind entweder doppelt belegt oder nicht vorhanden.

  pos_zeitstempel = length(app.zeitstempel_messung);
  %1. Boilertemperatur
     app.messwert_puffer(pos_zeitstempel,1) = app.boilerkalibrierkurve(round(messwerte_mwp(1)*3300/4096));      
  % 2. Frischwassertemperatur
     app.messwert_puffer(pos_zeitstempel,2)=(messwerte_mwp(2)*1.00416-1.68)*140/4096; 
  % 3. Mischwassertemp
     app.messwert_puffer(pos_zeitstempel,3)=app.mischerkalibrierkurve(round(messwerte_mwp(3)*3300/4096));  
  % 4. Boilerdruck
     app.messwert_puffer(pos_zeitstempel,4)=4500*((messwerte_mwp(4)*1.00605-4.72)/4096)-500; 
  % 5. Wasserleitfähigkeit
     app.messwert_puffer(pos_zeitstempel,5)=((messwerte_mwp(5)*1.00382-1.78)*19.8/4096)+0.2;  
  % 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);
  % 9. Temperatur hinter der Wasserwendel/ vor der Brühgruppen
     app.messwert_puffer(pos_zeitstempel,9)=app.mischerkalibrierkurve(round(messwerte_mwp(6)*3300/4096));       
     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; 
  % 11. Brühgruppentemperatur (neu??)
     app.messwert_puffer(pos_zeitstempel,11)= messwerte_mwp(7); 
  % 11. Pumpensteuerspannung -> BAS
     app.messwert_puffer(pos_zeitstempel,11) = app.Pumpenleistung.Value;
  % 12. Bypass-Stellung 
     app.messwert_puffer(pos_zeitstempel,12) = app.Pumpenbypass.Value; 
  % 13. Dosierventilstellung 
     app.messwert_puffer(pos_zeitstempel,13) = app.Dosierventil.Value;
  % 14. Brühgruppendrossel
     app.messwert_puffer(pos_zeitstempel,14) = app.Bruehgruppendrossel.Value;
  % 15. Heizleistung PWM
     app.messwert_puffer(pos_zeitstempel,15) = 0; 
  

Um eine gewisse Pufferlänge einzuhalten wurde die Variable puffer_laenge eingeführt, welche definiert, wie viele Messungen gleichzeitig gespeichert werden sollen. Wenn die Zeitstempel-Matrix mehr Zeilen enthalten sollte als die Pufferlänge hergibt, wird sowohl die Zeitstempel-Matrix als auch der Messpuffer auf die Pufferlänge gekürzt.

  if length(app.zeitstempel_messung)>app.puffer_laenge 
     app.messwert_puffer = app.messwert_puffer(end - app.puffer_laenge:end,:);         
         % app.messwert_puffer(end - app.puffer_laenge:end,:): Löschen aller Zeilen von messwert_puffer bis auf die letzten "puffer_laenge"-Zeilen
         % app.messwert_puffer(end,:): letzte Zeile mit allen Spalten der Variable messwert_puffer
         % end - app.puffer_laenge: Index des letzten Wertes minus puffer_laenge (Index, ab dem Zeilen erhalten bleiben) 
         % app.messwert_puffer(end - app.puffer_laenge:end,:): Startindex bis letzte Zeile alle Spalten 
     app.zeitstempel_messung = app.zeitstempel_messung(end - app.puffer_laenge:end,1); 
         % app.zeitstempel_messung(end,1): erste Spalte der letzten Zeile von zeitstempel_messung
  end

Anschließend werden die Funktion zum anzeigen der Messwerte auf der Oberfläche sowie der Regler- und Funktionsaufruf aufgerufen.

  app.messwerte_anzeigen();         
  app.reglerundfunktionsaufruf(); 
   

Letztendlich soll noch die automatische Datensicherung erstellt werden. Dazu wird anhand der Variable zeitpunkt_letzte_sicherung abgefragt, ob bereits eine erste Sicherung erfolgte (s. uhrzeit_erste_messung). Sollte dies nicht der Falls sein wird die Variable, die anzeigt, dass es sich um eine automatische Datensicherung handelt auf 1 gesetzt und die Funktion zur Datenverarbeitung aufgerufen. Der aktuelle Zeitpunkt wird als letzter Zeitpunkt der Sicherung abgespeichert. Sollte bereits die erste Sicherung erfolgt sein, wird überprüft, ob die letzte Sicherung länger her ist als der auf der Oberfläche im Reiter "Start" angegebene Wert. Trifft dies zu wird erneut eine automatische Datensicherung aufgerufen und der Zeitpunkt neu gespeichert.

  if app.zeitpunkt_letzte_sicherung == 0
     app.automatische_datensicherung = 1;
     app.datensicherung(app);                          
     app.zeitpunkt_letzte_sicherung = uhrzeit_sekunden;
  elseif (uhrzeit_sekunden - app.zeitpunkt_letzte_sicherung) > app.Datensicherung.Value
     app.automatische_datensicherung = 1;            
     app.datensicherung(app);                          
     app.zeitpunkt_letzte_sicherung = uhrzeit_sekunden; 
  end

In letzten Schritt wird die Variable verarbeitung_aktiv wieder der Wert 0 zugewiesen, sodass eine erneute Routine begonnen werden kann

  app.verarbeitung_aktiv = 0;

Melina Scherf, 14.05.2023, Verarbeitungsfunktion BAS (Messwerte)

Analog zur MWP wurde ebenso die Verarbeitungsfunktion der BAS erstellt. Einziger Unterschied ist, dass einkommende Messwerte der BAS-Platine an einem "F!" zu erkennen sind.
Die simulierte Messwerterfassung/Initialisierung wurde in gleicher Weise wie für die MWP für die BAS-Platine erstellt. Um die Funktion in der Praxis zu testen, wurde je die letzte Zeile des von Armin Rohnen erstellten Programm zur simulierten Messdatenerfassung angepasst, sodass die passende Anzahl der Messwerte wie im Real-Betrieb gesendet werden.
MWP:

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

BAS:

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

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 die Messwerte mit dem Signalwort und ohne das Signalwort abzuspeichern (wichtig für Messwertverarbeitung).

  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 Initialisierungsfunktion 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. 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.

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



hochzaehlen()
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.hochzaehlen_erlauben == 1 


In der hochzaehlen()-Funktion wird die Variable, die das Hochzählen erlaubt wieder auf 0 gesetzt. Es wird 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.hochzaehlen_erlauben = 0;  
  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.