|
|
Zeile 1: |
Zeile 1: |
| = 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
| |
|
| |
|
| <br> Meine Aufgaben
| |
| * Datensicherung: erste Sicherung automatische_datensicherung auf 1
| |
| * Verarbeitung MWP/BAS anpassen, warum _neu
| |
|
| |
| <br> Meine Kommentierung
| |
| * neue Aktorenstellung
| |
| * Vorschlag init_mcus()
| |
| * nur von mir bearbeitete Funktionen von mir kommentiert
| |
|
| |
| <br> 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. <br>
| |
| 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] <br>
| |
| 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 = | | = Madita vom Stein, 06.06.2023 = |
Zeile 132: |
Zeile 17: |
|
| |
|
|
| |
|
| = 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. <br>
| |
| 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. <br>
| |
| 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. <br>
| |
| 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. <br>
| |
| 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. <br>
| |
| * 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=
| |
| Die automatische (nach vorgegebener Zeit in der Oberfläche) und die manuelle Datensicherung (per Knopfdruck) wurden programmiert. Die entstandenen 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.
| |
|
| |
| Um den Benutzer zu signalisieren, dass gerade eine Datensicherung durchgeführt wird, wird dies in der allgemeinen Statusanzeige angezeigt.
| |
|
| |
| app.Allgemeiner_Status.Text = char('Datensicherung gestartet'); % Anzeige im allgemeinen Statusfeld
| |
|
| |
| Zunächst werden alle editierbaren Eingaben des Start-Reiters abgespeichert.
| |
|
| |
| 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;
| |
|
| |
| Um die Messwerte zuordenbar abzuspeichern werden Überschriften erstellt.
| |
|
| |
| 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';
| |
|
| |
| Anschließend werden die Messwerte und Zeitpunkte jeweils als Data- und Zeitobjekt gespeichert
| |
| messdaten.data = app.messwert_puffer;
| |
| messdaten.time = app.zeitstempel_messung;
| |
|
| |
| Es soll bei der Speicherung unterschieden werden, ob es sich um eine automatische oder manuelle Datensicherung handelt und dies auch im Dateinamen kenntlich gemacht werden. Um keine zwei separaten Funktionen programmieren zu müssen, wurden die Variablen automatische_datensicherung und manuelle_datensicherung erstellt. Im weiteren Funktionsverlauf wird abgefragt, ob die Variable zur automatischen Datensicherung aktuell den Wert 1 besitzt. Sollte dies der Fall sein wird eine automatische Datensicherung gestartet und die Variable wieder auf 0 gesetzt. In der Variable t wird der das aktuelle Datum und Uhrzeit als DD.MM.YYYY HHMMSS Format gespeichert. Dies wird in einen Dateinamen nach dem Motto "YYYYMMDD_Timehh.mm.ss_automatisch" umgewandelt. Zusätzlich wird der Pfad zum Ordner, in dem die Sicherungsdatei abgelegt werden soll, abgespeichert. In der Variable save_data wird nun der vollständige Dateipfad (Ordnerpfad + Dateiname) gesichert und die Datei mit den zuvor eingelesenen editierbaren Eingaben und Messpunkten erstellt.
| |
|
| |
| 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. <br>
| |
| 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:
| |
| # Boilertemperatur -> MWP
| |
| # Frischwassertemperatur -> MWP
| |
| # Mischwassertemp -> MWP
| |
| # Boilerdruck -> MWP
| |
| # Wasserleitfähigkeit -> MWP
| |
| # Füllstand -> BAS
| |
| # Durchfluss -> BAS
| |
| # Pulse Flowmeter -> BAS
| |
| # Temperatur hinter der Wasserwendel/ vor der Brühgruppe -> MWP
| |
| # Druck in der Brühgruppe -> MWP
| |
| # Pumpensteuerspannung
| |
| # Bypass-Stellung
| |
| # Dosierventilstellung
| |
| # Brühgruppendrossel
| |
| # 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.
| |
| <br> 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. <br>
| |
| 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 [[Messwerte erfassen Multi-MCU|Programm zur simulierten Messdatenerfassung]] angepasst, sodass die passende Anzahl der Messwerte wie im Real-Betrieb gesendet werden. <br>
| |
| MWP: <br>
| |
| print('M!',T_Boiler, T_Eingang, T_Mischer, P_Boiler, Leitwert, T_Bruehgruppe, P_Bruehgruppe)
| |
|
| |
| BAS: <br>
| |
| 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. <br>
| |
|
| |
| 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 [[Messwerte erfassen Multi-MCU|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. <br>
| |
| <br>
| |
| '''Callback Verbinden Button pushed''' <br>
| |
| 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. <br>
| |
| <br>
| |
| '''init_mcus()''' <br>
| |
| 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. <br>
| |
|
| |
| app.ssr_initalisieren();
| |
| app.ssr_init = 1;
| |
| app.SSR_Status.Text = ('SSR-Platine Initalisierung abgeschlossen');
| |
| app.hochzaehlen_erlauben = 1;
| |
|
| |
|
| |
| <br>
| |
| '''hochzaehlen()''' <br>
| |
| 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 = | | = Melina Scherf, 28.04.2023 = |