Vererbung und die Kunst des Schachtelns?

the_muck

Level-2
Beiträge
468
Reaktionspunkte
111
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo,
ich sitze gerade an meinem ersten kleinen Codesys Projekt. Das ist an sich nichts wildes und eine sehr Simple Anlage...

Ich muss gestehen das ich nach den ersten Umsetzungen einige aha Erlebnisse hatte, aber auch einige frustrierende Momente :D. Mit KI / YT versuche ich mich gerade etwas einzuarbeiten. Dann kann man aus einem einfachen Temp > 30°C -> Motor ein, eine Verschachtelte Struktur aufbauen mit einer DriveBase_Unit, Contactor_Unit die dann mit Schrittketten schonmal schauen ob NotAus, MSS, und ob Antrieb Hochgelaufen ist :D.

Mein (Siemens) Kollege meinte dann, das kann ja keiner mehr lesen / warten. Und da muss ich ihm etwas recht geben. Aber ist es nicht so das wenn die Bausteine getestet sind, die Anwendung eigentlich einfacher zu warten ist? Man gibt IMHO feinere Zustände nach außen... Bei einer Siemens Anlage von Dritten wurde gerade eine Ventilabfrage nach Programmiert. Da wurde nur mit einem Globalen Ablaufalarm gearbeitet. Anstatt das wir nach 1s den Fehler haben mussten wir 10min warten bis die Anlage in Zeitablauf Störung gegangen ist. Diese Änderung hat der Kollege jetzt in die Schrittketten, wo die Ventile gebraucht werden, eingebaut weil die Struktur es wohl nicht anders hergegeben hat mit vertretbarem Aufwand.

Mein "Problem" ist gerade, einen Weg für mich zu finden sowas gleich richtig zu machen! Ich fand es in Videos von "TC Academy" sehr schön wie der Kollege beschreibt was er in der Vergangenheit anders gemacht hat. Und stehts versucht bei neuen Projekten es eleganter zu lösen, dabei aber nicht die Übersichtlichkeit verlieren will.

Bei den Ventilen also gleich einen Baustein der schaut ob Ventil angesteuert und ob es in Position ist, oder nicht angesteuert und in Position. Fehler Reset und solche dinge. Wie ist das aber mit dem Speicher und der Anzahl von Timern, das verleitet ja sehr zum aufblähen von Programmen :D. Und dann muss ich eventuell doch soviel Sonderteile implementieren das meine Vererbung nicht mehr so sinvoll ist.

Na ja ich suche gerade Literatur zum Thema, einfache Beispiele oder Struktur Ideen um ins Thema der Objektorientierten Programmierung zu kommen.

Grüße Malte
 
Hallo Malte,
vielleicht mal grundsätzlich :
Wenn deine erstellte Struktur so komplex geworden ist, dass das keiner mehr lesen oder warten kann dann bist du, auch wenn es genauso funktioniert wie du es haben willst, komplett auf dem falschen Weg.
OOP ist eine tolle Sache wenn man es richtig macht. Richtig heißt aus meiner Sicht aber nicht "mache was geht" sondern "mache was sinnvoll und übersichtlich ist". In dem Sinne habe ich Bausteine unter dem Aspekt der Wiederverwendbarkeit erstellt. Das konnte dabei genauso ein FU-Baustein sein wie auch einer, der eine Kurvenaufzeichnung mit anschließender Auswertung macht.
Ventile beispielsweise und deren Auswertung hatte ich nie als Baustein umgesetzt - wahrscheinlich weil ich immer zuuuu viele davon hatte. Hier hatte ich immer Aggregate-bezogene Schrittketten bei denen auf Timeout geprüft worden ist. Das hat immer gereicht denn in der Realität ist es egal ob eine Fehlermeldung fast sofort oder erst nach 10 Sekunden kommt - Hauptsache sie kommt und ist idealerweise auch qualifiziert.

Beim Vererben selbst ist es auch immer wichtig, dass die Basisklasse noch einen sinnvollen Bezug zur neuen Klasse hat - also nicht "Stein" als Basis nehmen wenn das Endergebnis "Schrank" (oder "Brett") sein soll ...

Wenn du die Standard-Regeln einhätst, wie DRY (Don't Repeat Yourself) und Übersichtlichkeit und Nachvollziehbarkeit dann ist schon viel gewonnen ...

Dieses Thema kann man jetzt natürlich unendlich aufblähen, ähnliche Diskussionen gar es auch schon mehrfach und auch hier gilt : "frage 3 Programmierer und du erhältst 4 unterschiedliche Meinungen" ...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Beim Thema OOP sollte man die Aspekte Wartbarkeit und Lesbarkeit in den Vordergrund stellen.
Eine einfache Überlegung zur Vererbung ist: "Was spare ich bzw. was gewinne ich?"
Ein früherer Kollege hat mal versucht komplett OOP umzusetzen.
Es gab Bewegung -> Bewegung mit 2 Sensoren und 2 Aktoren (also z.B. Ventil) -> Bewegung mit 2 S und 2 A und Laufzeit -> Bewegung mit FU -> Bewegung mit FU und 2 Geschwindigkeiten, usw. Bei den Betriebsarten war es ähnlich komplex.
Vollkommen logischer Aufbau laut Klassendiagramm. Er hatte dann nen Unfall und ich musste sein Projekt während der Inbetriebnahme übernehmen. Ich bin beinahe verrückt geworden. Aufgrund der hohen Verschachtelung durch die Vererbung war es für mich kaum nachvollziehbar.
Ich hab dann etwas seine Bewegungsklasse zusammengestrichen, hatte etwas weniger DRY und hab damit aber deutlich Übersichtlichkeit gewonnen.
 
Vielleicht noch einmal dazu weil Dieter das jetzt auch noch einmal schön ausgeführt hat :
In der .Net-Welt gibt es bei der Vererbung ja z.B. die Kette :
Object - Control - Label
Hier ist es so, dass jedes in der Hierarchie folgende Element nicht Funktionen der Basisklasse überschreibt sondern nur welche dazu macht. Es hat allerdings auch hier zur Folge, dass dabei Properties und Events mitgereicht weerden, die die Endklasse vielleicht gar nicht mehr benötigt. Es wird aber nie eine Funktion der Basisklasse verändert.
Macht man es bei der SPS vergleichbar dann, so würde ich sagen, bleibt das Endergebnis verständlich und handelbar ...
 
Wir programmieren mittlerweile sehr viel in TwinCAT objektorientiert. Damit das funktioniert, muss man aber ein bisschen berücksichtigen und, zumindest wir, auch einiges (neu) lernen. Wir bauen Sondermaschinen für unseren Eigenbedarf und haben dadurch den gesamten Lebenszyklus einer Maschine, einschließlich Betrieb, Wartung usw., unter Kontrolle. Wenn man Maschinen verkaufen möchte, sind die Hürden sicherlich deutlich größer, da man betreiberseitig weniger/keine Kontrolle hat.

Ein wichtiger Punkt ist die Schulung aller beteiligten Mitarbeiter. Das betrifft nicht nur die Programmierer, die initial die Maschine programmieren und in Betrieb nehmen, sondern auch die Instandhalter. Unsere E-Werkstatt ist aufgeteilt in Hardware- und Softwarespezialisten in allen Schichten. Die Softwarespezialisten sind daher auch geschult auf diese Art und Weise der Programmierung. Das klappt ganz gut

Extrem wichtig ist auch die Vorplanung. Wenn man "normal" prozedural programmiert, kann man sich auch ohne Vorplanung irgendwie durchwurschteln. Wird dann zwar nicht gut/schön und ist in der Regel auch nicht gut wartbar, aber irgendwie funktioniert es. OOP-Programmierung ohne Vorplanung führt unweigerlich ins Chaos. Bei der Vorplanung darf man nicht nur den einzelnen FB im Fokus haben, sondern muss die Gesamtheit berücksichtigen. Nur dann kann man Schnittstellen, Vererbungshierarchien planen und den Funktionsumfang für Methodenimplementierungen passend schneiden.
Aufgrund unzureichender Planung eben dieser Punkte hat unser internes Framework nach zwei Maschinen nochmal ein komplettes Rewrite bekommen. Nun ist es aber gut.
Bei der Vorplanung ist dann auch Dokumentation sehr wichtig. Wir nutzen dazu Sphinx, damit kann man einiges automatisieren (Querverweise für Deklarationen usw). Allgemein gibt es natürlich auch Klassendiagramme. Die machen wir aber nicht streng nach UML sondern eher Freestyle, UML halte ich für unnötig komplex. Dafür gibts unterschiedliche Tools. Wir nutzen v. a. Visio und Mermaid in Sphinx.

Ein riesiger Vorteil für uns ist mittlerweile die Fehlersuche. Durch die kleinen funktionalen Einheiten in den Methoden bist du beim Debuggen viel isolierter und fokussierter unterwegs. Wir bereiten aktuell automatisierte Unit Tests für die Basis-Bibliotheken vor. Das wird aber wohl noch etwas dauern...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Na ja ich suche gerade Literatur zum Thema, einfache Beispiele oder Struktur Ideen um ins Thema der Objektorientierten Programmierung zu kommen.
Eigentliches Thema vergessen :D

Ich finde Bücher gut, die unabhängig von irgendwelchen Sprachen sind. "Clean Code" von Robert Martin und "Refactoring" von Martin Fowler finde ich gut. Speziell auf das Thema SPS gibt es hier eher wenig.
Gibt auch ein paar gute YouTube Kanäle zum Thema Softwarearchitektur. Sehr vieles aus den klassischen Sprachen lässt sich auch in SPS umsetzen.
 
Ein Kriterium für mich ist, dass Abhängigkeiten / Verriegelungen über Querverweise gefunden werden können und daß sie Status angezeigt werden können. Wenn jemand anders als der ursprüngliche Programmierer an ne Anlage geht, wie ist dann die Vorgehensweise?
Du schaust an der Anlage und suchst den betroffenen Anlagenteil und da dann meist einen Motor, Ventil oder nen Sensor. Dann wirft man nen Blick ins SPS-Programm. Anhand der Hardware-Informationen sollte man schon nah ans Ziel kommen.
Mit jeder Abstraktions- und Vererbungsschicht kann es komplexer werden.
Nehmen wir an ein Förderband läuft nicht, weil eine Stau-Lichtschranke verstellt ist.
Klassisch programmiert ist vielleicht einfach E45.3 direkt mit dem Ausgang A19.6 verknüft.
Mit OOP kann es so sein das Linie45->Segment35->Puffer4->Rollenbahn3->Staumeldung die Ursache ist.
 
Mit jeder Abstraktions- und Vererbungsschicht kann es komplexer werden.
Stimmt. Deshalb ist eine gleichbleibende Struktur projektübergreifend neben der Dokumentation sehr wichtig.

Klassisch programmiert ist vielleicht einfach E45.3 direkt mit dem Ausgang A19.6 verknüft.
Mit OOP kann es so sein das Linie45->Segment35->Puffer4->Rollenbahn3->Staumeldung die Ursache ist.
Das hat in TwinCAT nicht unbedingt etwas mit OOP zu tun. Mit der AT%I* bzw. AT%Q* Notation in einem FB_RollerConveyor in einer FB-Instanzkette, welche sich an der Maschinenstruktur orientiert, geht das auch vollständig ohne die Mittel, die man als "OOP" bezeichnet (Methoden, Vererbung, Schnittstellen, Eigenschaften usw.). Eine solche Struktur halte ich in TwinCAT sowie für Best Practice, auch wenn man OOP nicht nutzen möchte.

Anhand der Hardware-Informationen sollte man schon nah ans Ziel kommen.
Das geht ja recht fix über den IO-Baum. Entsprechendes Signal auf der IO-Hardware suchen und dann kann man zur Instanz springen, welche des Signal mappt.

Wenn jemand anders als der ursprüngliche Programmierer an ne Anlage geht, wie ist dann die Vorgehensweise?
Der erste Schritt ist nicht der Blick ins Programm, wobei du das sicherlich auch nicht meintest. Für unsere Instandhaltung wäre eine übliche Vorgehensweise am Beispiel deines Rollenförderers:

1) Blick ins Meldesystem, ob Meldungen zum Rollenfördersystem, dem betroffenen Segment oder benachbarten Segmenten anstehen. Bei einer Staumeldung würde hier sicher nichts zu finden sein. Man könnte zwar das Loglevel ändern, das bringt aber nachträglich nichts und führt natürlich zu seeeehr vielen Meldungen. Prinzipiell gibt es auch ein davon unabhängiges Textlog, das nutzt die Instandhaltung aber eher selten - sagen wir lieber nie :D Ist aber für schwierige Probleme, welche noch nicht nachstellbar sind, sehr hilfreich im Engineering.
2) Blick in die Detailansicht des Maschinensegments. Bei einem Rollenförderer würde man hier u. a. alle relevanten IOs sowei eine schematische Darstellung des Segments sehen. Eventuell würde hier bereits auffallen, dass das Segment als belegt dargestellt wird, tatsächlich aber leer ist.
3) Blick in die Software. Da arbeitet jeder sicher etwas anders. Man könnte zu dem Ausgang für das Schütz über den IO-Baum gehen oder man ruft den FB für das Rollenförderersegment auf. Jeder FB hat eine Methode Cyclic(). Hier würde das Lesen und Schreiben der IOs passieren. Die Startanforderung StartConveyor() des Rollenförderers kann tatsächlich prinzipiell im Projekt an vielen Stellen verteilt sein (Rollenförderersegmente davor/danach, HvO-Steuerung, zentrales HMI, ...). Diese Startanforderungen senden ihre Anforderung zusammen mit ein paar Metadaten aber einfach nur an Cyclic(), wo es verarbeitet wird.
Spätestens in Cyclic() würde man also sehen, dass es eine Startanforderung vom vorhergehenden Segment gibt, diese aber nicht ausgeführt wird, weil das eigene Segment noch als belegt markiert wird.

In meinen Augen relativ simpel zu finden, wenn man in die Struktur unterwiesen wurde und das ein paar Mal geübt habt.

Hat nun aber auch noch nicht so viel mit OOP zu tun. Ich denke ohnehin, dass sich in der eigentlichen Maschinenapplikation der Hardcore-OOP Anteil in Grenzen hält. Wir nutzen da eher die Strukturen und Schnittstellen aus unseren Bibliotheken. Natürlich solche Methodenaufrufe usw. auch OOP.

Komplexer wird es dann aber erst in den eigentlichen Bibliotheken. Das oben angesprochene Textlog nutzt bspw. Singleton und Publisher/Subscriber-Muster. Das muss schon gut abgetestet werden. Schwierige Probleme findet man da nicht, wenn man mit dem PG direkt neben der Maschine in der Fertigung sitzt. Dafür ist es dort zu unruhig...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
@roboticBeet :
Das hast du sehr schön beschrieben was ihr da so macht. Das soll jetzt bitte auch von meiner Seite nicht als zu despektierlich rüberkommen - ich freue mich aber, dass ihr das so nur für euch macht. Als euer Kunde und mit der Instandhaltung betraut möchte ich mit derartigen Konstrukten lieber nichts zu tun haben.
Aus meiner Sicht : es gibt auch in der OOP immer noch einen prozeduralen Teil und an irgendeiner Stelle sollte man "die Kirche im Dorf lassen".

Aber wie schon geschrieben : das ist meine Ansicht (und so habe ich im Umfeld der .Net-Programmierung auch immer gearbeitet)
 
Das hast du sehr schön beschrieben was ihr da so macht. Das soll jetzt bitte auch von meiner Seite nicht als zu despektierlich rüberkommen - ich freue mich aber, dass ihr das so nur für euch macht.
Alles in Ordnung, ich habe auch keine missionarischen Absichten, sondern wollte das lediglich als Erfahrungsbericht teilen.

Natürlich hängt die Umsetzbarkeit stark von der Domäne ab. In klassischen TIA-Umgebungen sind einem die Hände hinsichtlich OOP gebunden. Mit AX tun sich da aber eventuell gerade neue Möglichkeiten auf. Habe ich aber noch nicht getestet. In anderen Unternehmen sind wiederum Frameworks wie Nexeed gesetzt. Das Prinzip bleibt aber ähnlich: Auch bei Nexeed steht und fällt der Erfolg damit, wie nah die Maschinenstruktur an den strukturellen Vorgaben der Software liegt und wie gut das gesamte Team einschließlich Instandhaltung geschult ist.

es gibt auch in der OOP immer noch einen prozeduralen Teil
Nichts anderes habe ich oben geschrieben.
 
Neexed ist ein schönes Beispiel.
Wenn es Werksstandard ist und alle Anlagen damit erstellt wurden, dann kennen sich Bediener und vor allem auch Instandhaltung damit aus.
Hast du nur ne einzelne Anlage und musst dann als Instandhalter ran, wird die Begeisterung sehr überschaubar sein.

OOP bietet dem Programmierer viele Möglichkeiten Arbeitszeit und Programmieraufwand zu sparen. Dabei muss man aber auch an die denken, die danach mal an die Anlage müssen. Egal ob nun Instandhalter oder Kollegen / externe Firmen die nen Umbau machen müssen.
Und hier ist nun ein Unterschied, ob das KnowHow intern, wie bei @roboticBeet vorhanden ist oder eben nicht.
Ich denke die Kunst bei OOP ist hier ein gesundes Mas zu finden. Gerade bei Veerbung ist das Risiko groß über das Ziel hinauszuschiessen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
OOP bietet dem Programmierer viele Möglichkeiten Arbeitszeit und Programmieraufwand zu sparen.

Das sollte meines Erachtens nach ein guter Nebeneffekt sein. Durch die Standardisierung kann man sich Testumgebungen bauen die bei einem Update gewährleisten, dass die vorherigen funktionieren nicht beeinträchtigt werden.

Ich erstelle z.B. eine abstrakte Basisklasse für Behälter, damit weis ich was sich im Behälter für ein Medien befinden und welche Batch ID diese haben. Wie der Name des Behälters ist, wer diesen gerade verwendet, wer diesen abonniert um Events zu erhalten, die Eigenschaften wie maximale Füllmenge… habe ich jetzt ein Behälter mit x Sensoren, dann erbe ich die Basisklasse und verschalte die Sensoren/Aktoren. Da Speicher und Performance in Fülle da sind, kann man auch alles in Logdateien speichern, was das debuggen erheblich erleichtert.

Mit der neuen Variante von Codesys wird es noch einmal einfacher da Überladungen möglich sind und so wie es bei Siemens AX aussieht, wird dies Standard. Eine Gefahr muss man dennoch berücksichtigen, das nachladen von Programmen, hier kann es gerne zu Problemen kommen wenn man nicht die richtige Wahl trifft.
 
Kannst du bitte mal ein Beispiel für eine einfache Basisklasse und eine Überladung zeigen?
Zum Beispiel wenn wir den Behälter nehmen.
Beh900.Entleeren() dann gibt dieser über sein Ventil etwas ab. Beh900.Entleeren(Menge:=500) dann dosiert der Behälter mit seiner Waage/IDM….
Oder Beh900.Entleeren(Medium:=eMedium.Wasser),
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Die Startanforderung StartConveyor() des Rollenförderers kann tatsächlich prinzipiell im Projekt an vielen Stellen verteilt sein (Rollenförderersegmente davor/danach, HvO-Steuerung, zentrales HMI, ...). Diese Startanforderungen senden ihre Anforderung zusammen mit ein paar Metadaten aber einfach nur an Cyclic(), wo es verarbeitet wird.
Kann dann anhand der Metadaten festgestellt werden, an welchem Aufruf die Startanforderung erfolgte?
Event vorbei, wer war’s denn nun?
Sind die Metadaten dann auch im Programm verwertbar?
 
Kann dann anhand der Metadaten festgestellt werden, an welchem Aufruf die Startanforderung erfolgte?
Event vorbei, wer war’s denn nun?
Sind die Metadaten dann auch im Programm verwertbar?
Wenn ich das richtig verstanden habe, willst du wissen wer auf den FB zugreift, das kannst du erledigen indem du noch eine variable in der Methode hinzufügst und diese mit This^ übergibst. und mit dem queryinterface kannst du dein Aufrufer wieder informieren
 
Diese Metadaten sind eine Datenstruktur mit unterschiedlichen zusätzlichen Informationen, u. a. auch Instanzpfad, Klartextname sowie Identifikationsname (BMK oder aus Maschinenstruktur heraus), beinhaltet. Dies wird standardmäßig den Methoden mit übergeben, welche von anderen Stellen im Programm aufgerufen werden. Andere Aufrufstellen können, wie schon genannt, die benachbarten Rollenförderersegmente sein oder die Ablaufsteuerung von einem Roboter oder oder oder.

Es ist dann am Programmierer, in der Methode der Startanforderung dies auszuwerten. In der Regel sieht eine Auswertung derart aus, dass zunächst geprüft wird, ob ein Log-Eintrag erfolgen soll (Logging aktiv und globaler bzw. lokaler Log-Level passen) und falls ja, wird ein Logeintrag in einer Textdatei erzeugt.
Pseudocodemäßig sieht das dann so aus:

Code:
IF _shouldLog(DEBUG)
THEN
  Log.Debug(_stationData.Name, "Conveyor start request by <rMetaData.Name>");
END_IF

Der Übersicht halber habe ich das Concat bzw. den String-Builder hier mal durch die spitzen Klammern versucht darzustellen. Der Log-String wird halt eigentlich zusammengesetzt.

Das Ergebnis wäre dann zum Beispiel: 2026-01-25 18:34:47.123 [DEBUG] Machine.Conveyor.Section32 - Conveyor start request by Machine.Conveyor.Section31

Die Metadaten sind eine ganz normal übergebene Variablenstruktur - aus Performancegründen als Referenz. Damit kannst du als Programmierer dann alles machen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Das Ergebnis wäre dann zum Beispiel: 2026-01-25 18:34:47.123 [DEBUG] Machine.Conveyor.Section32 - Conveyor start request by Machine.Conveyor.Section31
Das landet dann halt in einer Textdatei. Man kann an den Logger aber auch andere Datensenken anschließen, z. B. das Alarmsystem. Denkbar wären auch andere Datensenken bspw. eine SQL-Datenbank, TcAnalytics oder OPC UA.

Der Logger ist halt so aufgebaut, dass du drei Schichten hast.

1. In jeder Bausteininstanz einen _fbLog als Client, der dann z. B. die Methode Debug() mitbringt. Die Methode nimmt einfach die Meldung, packt einen Zeitstempel dazu und sendet es an die Zentralinstanz (Nr. 2)
2. Die Zentralinstanz ist nur einmalig im gesamten Projekt vorhanden. Diese Instanz wird über eine statische Variable während der Initialisierung an alle Clients (siehe 1) gemeldet. Diese Zentralinstanz nimmt die Meldungen der Clients entgegen und packt sie in erster Linie nur in einen Ringspeicher.
3. Eine oder mehrere Datensenken (FileWriter, ...) werden ebenfalls an die Zentralinstanz angekoppelt und fragen dort den Ringspeicher ab, ob neue Einträge vorhanden sind und falls ja, werden die Einträge übernommen. Die Datensenken verarbeiten die Eintrage des Ringspeichers dann auf ihre jeweils eigene Art und Weise (Schreiben in txt-Datei oder bspw. schreiben in eine SQL-Datenbank).
 
Zurück
Oben