Hallo,
ich hatte letzthin das Vergnügen, dass ich für einen Kollegen eine Applikation zur Steuerung einer Microcontrollerschaltung schreiben durfte. Die tut auch soweit und keine Fehler bisher. Entstanden ist die Applikation allerdings kurzfristig auf Zuruf und "überraschend", weil nicht eingeplant, aber natürlich dringend
Meine C#/.NET-Kenntnisse sind relativ begrenzt, ich verwende es hauptsächlich für eigene kleine Testprogramme. Das Programm für den Kollegen war komplexer als das was ich bisher geschrieben habe, dementsprechend war da überhaupt keine gescheite Trennung zwischen Zugriff auf die Hardwareschnittstelle, Programmlogik und GUI drin. Nun steht die nächste Schaltung an, und diesmal konnte ich wenigstens vorher abklären, dass auch hierfür wieder eine Applikation her muss. Nur würd ich's diesmal gern "richtiger" machen und nicht mit der heißen Nadel stricken - außerdem kann ich dabei gleich was lernen.
Den Artikel zum
Drei-Schichten-Modell hab ich durchgelesen, der Ansatz scheint genau das passende zu sein, wenn ich die Datenzugriffsschicht durch eine Kommunikationsschicht ersetze. Der Artikel beschreibt m.E. was zu tun ist, ich möchte nun das wie herausfinden.
Die Applikation soll hauptsächlich Daten von Sensoren visualisieren und die Möglichkeit bieten, die Einstellungen der Sensoren zu modifizieren. Für die Visualisierung selbst werde ich vermutlich OxyPlot verwenden. Genügend Beispiele sind soweit ich sehen kann vorhanden, das sollte also kein Problem sein.
Als Schnittstelle zur Hardware kommen aktuell entweder "echte" COM-Ports oder USB-UART-Wandler zum Einsatz.
Die Logikschicht dazwischen wird vermutlich außer dem Parsen der als Strings ankommenden Daten und dem Konfigurieren der Sensoren nicht viel zu tun haben.
Die Firmware für die Sensorhardware wird ebenfalls selbst geschrieben, d.h. Protokoll ist ziemlich frei. An der Ecke hab ich wesentlich mehr Erfahrung
Okay, nun zu meiner geplanten Umsetzung des 3-Schichten-Modells, ich fange bei der untersten, der Kommunikationsschicht an:
Für die Kommunikation zur Logikschicht würde ich ein Interface mit den Funktionen Read() und Write() sowie einem Event für angekommene Daten definieren. Für die Kommunikation selbst würde ich zwei unabhängige Klassen für die "echten" COM-Ports und die USB-UART-Wandler implementieren. Hintergrund dafür ist, dass ich bei den USB-UART-Wandlern über die Vendor- und Product-ID die Sensorhardware eindeutig erkennen kann. Bei den echten COM-Ports muss ich jeden Port durchgehen und per Kommando/Antwort prüfen, ob die Sensorhardware dran hängt. In beiden Fällen handelt es sich um eine automatische Erkennung. Falls bei weiteren Projekten ganz auf COM-Ports bzw. USB-UART-Wandler verzichtet und stattdessen bspw. direkt über USB-Endpunkte kommuniziert wird, will ich dafür gerüstet sein
Daher das magere Interface.
Hier stellen sich zwei Fragen:
- ist diese Form der Erkennung in der untersten Schicht korrekt oder gehört sowas schon zur Logik- oder einer weiteren Zwischenschicht?
- da es sich in den beiden genannten Fällen aus Sicht des Betriebssystems um serielle Ports handelt, könnte ich auch nur eine Klasse definieren, in welcher geprüft wird, ob eine Hardware mit passender VID/PID angeschlossen ist und zu dieser den entsprechenden Port ermitteln. Ist dies nicht der Fall, werden die COM-Ports manuell geprüft. Hätte zumindest den Vorteil, dass ich (theoretisch) nur einmal die Handhabung des seriellen Ports implementieren muss. Gibt's da sowas wie den "richtigen" Ansatz oder ist das eher Geschmackssache?
Die Logikschicht implementiert die Kommandos zur Hardware und übergibt sie nach "unten" über das genannte Interface mittels Write(). Antwortet die Hardware, wird das der Logikschicht mittels einem Event mitgeteilt und die Antwort per Read() ausgelesen und aufgearbeitet. Die Logikschicht hält die Sensordaten vor und wenn diese aktualisiert wurden, wird ein Event ausgelöst, über welches die GUI die Daten zur Anzeige entgegennehmen kann.
Von der GUI zur Logikschicht, also bspw. für die Einstellungen der Sensoren, hier bin ich unschlüssig, wie das aussehen soll. Zum einen habe ich mindestens zwei Typen von Sensoren und diese ggf. in unterschiedlicher Anzahl sowie dementsprechend unterschiedlichen Einstellungen, und zum anderen weiß ich nicht, wie man das nach "oben" bekannt machen kann. Ist ein bisschen schwierig zu beschreiben, wo es da klemmt.
Ich frag mal so: Davon ausgehend, dass die Programmlogik von der GUI getrennt ist, wie teilt beispielsweise die Programmlogik einem GUI-Listenfeld für Einstellungen mit, welche Einstellungen vorhanden/erlaubt sind? Ich nehm mal das Beispiel der SerialPort-Klasse, dort gibt es eine Methode "GetPortNames()", welche eine Liste mit den vorhandenen COM-Ports zurückliefert. Das Listenfeld wird mit der Liste gefüllt, und das gewählte Listenelement umgekehrt als zu öffnender Port an die Logikschicht übergeben. Im Prinzip müsste ich ja dann genau sowas für alle Einstellungen in der Logikschicht implementieren, oder? Im Fall von numerischen Werten prüft die Logikschicht, ob der Wert innerhalb der erlaubten Grenzen ist, wenn nicht wird eine Ausnahme abgefeuert, welche von der GUI abgefangen und bspw. in eine Infobox ausgegeben wird.
Bzgl. der verschiedenen Sensortypen, kann man die in einer einzelnen Logikschicht implementieren oder nimmt man zwei parallele Schichten? Letzteres macht m.E. keinen Sinn, weil beide dann auf eine Hardware-Schnittstelle zugreifen. Wenn die Sensoren aber in einer Schicht implementiert werden, wie sieht es dann bspw. mit Erweiterungen um andere Sensortypen aus? Ein gemeinsames Interface kann es ja dann nicht geben, weil die Sensoren ja unterschiedliche Eigenschaften/Funktionen haben. Wie wird sowas gelöst?
Ist das o.g. der richtige Ansatz oder zumindest die richtige Richtung, um das 3-Schichtenmodell korrekt zu implementieren? Oder noch zu unsauber bzw. vielleicht sogar das falsche Implementierungsmodell?
Grüße