![]() |
Jobliste Kommunikation mit externem Gerät
Hallo,
ich bin zurzeit dabei eine Software für ein externen Gerät zu entwickeln. Dieses Gerät kann sowohl seriell über RS232 als auch USB angesteuert werden angesteuert. Als USB-Controller ist ein FTDI FT232BM eingebaut. Nun meine Frage, ich kommuniziere mit dem Gerät folgendermaßen: Ich habe ein Objekt vom Typ (TGeraet) Geraet.GetStatus(Status:boolean); Geraet.GetMesswert(Status:boolean); Jede Funktion durchläuft bei mir ein Kommunikationsmodul, dass zB.: -->den Buffer mit dem Befehl füllt. -->den Befehl abschicken -->auf eine Antwort warten (pollen bis 7 Bytes am Port sind) -->falls 7 Bytes da sind, diese vom Port lesen und bewerten -->wenn nach einer bestimmten Zeit keine Antwort dann, rausspringen mit Fehlermeldung Diese Kommunikation läuft bei mir nun im Hauptthread und wenn ich mehrer Befehle (GetXYT, SetSeriennummer....) ausführe, dann hängt meine Anwendung zeitweise. Ich habe versucht diese in einen extra Thread zu packen, denn ich von aussen anstosse, aber irgendwie klappt das nicht wie gewollt. Habe auch schon Luckies Tutorial zum Thema Thread gelesen. Ich würde gerne eine Art Jobliste machen, die in einem Thread abläuft und immerwieder die Jobs (GetXYZ...) abarbeitet und ich sie von aussen fülle. Beispiel: Joblist.Add(Get_YXZ) Joblist.Add(SetSeriennummer('SN9090'); so dass ich Design von Logik trennen kann und es in einem extra Joblist Thread abläuft. Hoffentlich könnt ihr mir dabei helfen... Danke schonmal im Vorraus!!! Gruß DelphiManiac |
Re: Jobliste Kommunikation mit externem Gerät
Zitat:
was du als erstes brauchst, ist eine Statusmaschine für jeden Job, etwa in der Art: Idle - SendHeader - Senddata - SendCRC - WaitforAnswer - RecvHeader - RecvData - RecvCRC die Events verzweigen dann je nach Status und UnterStatus, z.B. RecvCRC -> CRCByte1, CRCByte2, CRCByte3, CRCByte4, hier werden die CRCs geprüft und wenn Ok -> Idle, usw. usw. Die Jobliste kann dann den nächsten Job anleiern, wenn der Zustand Idle ist, indem sie das erste Byte in das Transmit Register schreibt und den Zustand auf SendHeader setzt, oder indem sie einen String übergibt (mit Header,Daten,CRC) und einen Writebefehl ans Com-Port schickt und den Status auf Sending setzt - ganz wie das System es erfordert. Die Statusmaschine(n) sorgen dann selbst dafür, dass ein Job komplett abgewickelt wird, solange kein Fehler auftritt. Zweckmässig ist es, das ganze in eine Matrix aufzunehmen bzw. in eine Sprungtabelle, dann kann man auch keine Kombination vergessen:
Delphi-Quellcode:
unvollständig, ich hoffe man erkennt das Prinzip. Zweckmässig ist eine Tabelle mit einer Routine für jede Kombination aus Status und Event. Die ist zugleich eine gute Dokumentation, auch genormte Protokolle werden vorzugsweise mit Statusmaschinen beschrieben.
(zur besseren Formatierung)
Event-> Char Sent Char Recvd V24Error Timeout Status: Idle ??? ??? ??? ??? {should not happen} SendHeader next Char rep Error rep Error rep Error Header done? -> Idle -> Idle -> Idle y->SendData SendData next Char rep Error rep Error rep Error Data done? -> Idle -> Idle -> Idle y->SendCRC ..... Gruss Reinhard |
Re: Jobliste Kommunikation mit externem Gerät
Zitat:
klingt ein wenig so, als ob etwas dabei nicht ganz geklappt hat (schließe ich aus klappt nicht wie gewollt), aber du sagst nicht was genau anders läuft. Wäre schön wenn du also noch sagen würdest, was du probiert hast und was dabei passiert ist. An sich denke ich kannst du hier einfach einen Thread nehmen, der eine Methode besitzt, mit der ihm von außen Jobs hinzugefügt werden und ggf. natürlich einer weiteren, die Ergebnisse nach außen reicht. Dazu wäre der imho sauberste Weg, dass du eine abstrakte Klasse Job schaffst, die einfach eine Methode zum abarbeiten des Jobs besitzt. Ist diese Methode abstrakt, kann jeder Nachfahre sie beliebig implementieren. Der Vorteil ist damit natürlich, dass diese Liste für jede Art von Job geeignet ist. Die Jobs kannst du dann im Thread in einer TObjectList speichern. Im einfachsten Fall besteht dann die Execute-Methode des Threads einfach nur in einer Endlosschleife, die schaut ob die Job-Liste leer ist und sonst den ersten Job (oder dringensten oder oder) Job aus der Liste nimmt und ausführt. Ok, das Pollen ist an der Stelle nicht so schön, da könntest du sicher auch mit Signalen arbeiten, aber für einen ersten Test dürfte es reichen. Später dann einfach den Thread schlafen legen, so dass der auf ein Signal reagiert, dass du immer dann setzt, wenn du etwas in die Liste einfügst. Beim einfügen in die Liste solltest du dann unbedingt synchronisieren. Also hier meine ich die Verwendung eines Sperrobjekts, da du insbesondere beim pollen sonst das Problem bekommen kannst, wann ein Objekt eingefügt wurde und wann die Abfrage nach dem Füllstand der Liste erfolgt. Hoffe ist grob klar, wie ich das meine. Letztlich wäre es aber wichtig zu wissen, was du bisher versucht hast und woran du eigentlich wirklich scheiterst um dir zu helfen. Deshalb erstmal nicht mehr dazu, frag einfach noch mal genauer nach :wink: Gruß Der Unwissende |
Re: Jobliste Kommunikation mit externem Gerät
Hallo erstmal super vielen Dank @Reinhard Kern und @Der_Unwissende !!!
@Reinhard Kern: Das mit der Statusmaschine ist mir leider nicht so ganz klar geworden wie ich soetwas umsetzte @Der_Unwissende Danke für deine ausführliche(n) Antwort(en)!! Nunja ich weiss ehrlich gesagt nicht wie ich so eine Jobliste erstellen soll. TObjectlist ist ja eine Klasse, die Zeiger speichert, sehe ich doch richtig, oder? Wenn ich nun meiner Objektliste vom Typ TObjeclist eine Funktion übergeben will, wie mache ich dass denn?
Delphi-Quellcode:
:?
Objekliste.Add(Set_Serialnumber('SeriennummerXYZ'));
Ich habe es zurzeit recht statisch gelöst, aber bin nicht sehr glücklich drüber. Mein Thread arbeitet statische Lese Methoden ab, je nach Parameter, den ich von aussen vorgebe. Wenn ich dann eine Schreiboperation abgebe, dann stoppe ich den Thread vorher und lasse ihn dann weiterlaufen. (Dass finde ich sehr unschön) folgenden Thread-Code habe ich bisher:
Delphi-Quellcode:
Abhängig von FCommand führe ich meine Lesebefehle aus.
procedure TWorkThread.Execute;
(* ----------------------------------------- *) var com: Integer; Identifier: Integer; I: Integer; begin { Thread-Code hier einfügen } // FSTKObj.OnCommunicate:=UpdateCaption; isStopped:=false; FSTKObj.Get_Device_Identify (Identifier); { TODO : LÖSCHEN } Identifier:=1000; while NOT(Terminated) do begin fCS.Enter; LeseZyklischeDaten; fCS.Leave; case FCommand of STOPP_THREAD: begin isStopped:=true; Self.Suspend; // isStopped:=false; end; READ_HERSTELLERDATEN : begin Synchronize(ZeigeSandUhr); LeseStammdaten; Synchronize(UpdateStammdaten); if ((FSTKObj.IsMulti) or (FSTKObj.IsMaster)) then begin fCS.Enter; LeseStammdatenMaster; Synchronize(UpdateStammdatenMaster); fCS.Leave; end; Synchronize(VersteckeSandUhr); end; READ_ANALOGOUT : begin fCS.Enter; FSTKObj.Set_CalConfig(false); try fCS.Enter; FSTKObj.Set_CalibDaten(frmMain.editKalibrierwert.Value); fCS.Leave; except fCS.Leave; FSTKObj.Set_CalibDaten(0); end; .... Aber ich will ja so eine schöne Jobliste, der ich auch Schreiboperationen mit Parameter übergabe aus dem Hauptthread übergeben kann. Vielleicht kannst du mir ja mal ein Code Schnippsel schreiben, wäre super, stehe nämlich ganz schön aufm Schlauch. DANKE!!! |
Re: Jobliste Kommunikation mit externem Gerät
Zitat:
An sich ist hier dann die Idee, dass du dir einfach ein Abstraktes BasisObjekt schaffst.
Delphi-Quellcode:
Dieses Objekt hat eine Methode doJob, die hier aber abstrakt (nicht implementiert) ist. Du hast somit nur festgelegt, dass jeder Nachfahre eine solche doJob Methode besitzen muss, die irgendwie implemntiert werden kann.
type
TBaseJob = class(TObject) public procedure doJob; virtual; abstract; end; Damit hast du auch schon die Basis für alle Jobs:
Delphi-Quellcode:
Ok, soweit hast du erstmal nur eine Menge von Klassen und vielleicht auch eine Menge an Overhead. Wo liegt jetzt also der Sinn? Ganz einfach, du kannst nun eine Liste von TBaseJobs verwalten. Vererbung kann in der Programmierung auch als Erweiterung oder Spezialisierung beschrieben werden. TStopJob und TReadHerstellerDatenJob sind Erben von TBaseJob. Das heißt, dass sie mindestens dass können, was auch TBaseJob kann. Du kannst sie also auch als TBaseJob behandeln. So kann dein TStopJob noch 10 weitere Funktionen haben, behandelst du ihn als TBaseJob, kannst du nur auf die Funktion doJob zugreifen (weil das alles ist, was ein TBaseJob hat). Diese Funktion kann aber natürlich auf alle anderen Funktionen von TStopJob zugreifen und alle Variablen lesen (usw).
type
TStopJob = class(TBaseJob) public procedure doJob; override; end; TReadHerstellerDatenJob = class(TBaseJob) public procedure doJob; override; end; ... procedure TStopJob.doJob; begin // konkrete Implementierung end; ... So, nun erzeugst du zu einfach die speziellen Jobs, die du benötigst und tust die in eine TObjectList. Die TObjectList speichert hier alles was du reintust als TObject ab. Wenn du also einen Job rausnimmst, musst du ihn casten. Da für alle Jobs eine Basis existiert, castest du in ein TBaseJob (muss für alle Jobs klappen) und rufst von diesem TBaseJob die Methode doJob auf. Damit ist es egal was für einen Job du gerade aus der Liste genommen hast, du musst keine Details über diesen Job kennen, du weißt dass er eine Methode doJob hat und die verwendest du:
Delphi-Quellcode:
// hinzufügen von neuen Jobs:
self.objectList.Add(TStopJob.Create(...)); self.objectList.Add(TReaderHerstellerAngabenJob.Create(...)); self.objectList.Add(TStopJob.Create(...)); ...
Delphi-Quellcode:
Ja, ist jetzt hier etwas aus eine bestimmten Prozedur rausgenommen, aber ich hoffe die Idee ist so klar.
var buffer : TBaseJob;
// Abarbeitung der Jobs while (self.objectList.Count > 0) do begin buffer := TBaseJob(self.objectList[0]); buffer.doJob; self.ojectList.remove(0); // hier musst du schauen ob die Funktion so heißt, kann auch delete sein end; Die eigentliche Implementierung, wie man einen Job stoppt oder eben hinzufügt steckt nun in den Hüllklassen (Wrapper), hier wären dass TStopJob usw. Die beerben alle eine Basisklasse, die einfach die Signatur einer Methode festlegt, die alle Nachfahren besitzen müssen. Die JobListe macht wiederum nichts anderes als den ersten enthaltenen Job zu entnehmen und abzuarbeiten. Was für ein Job das genau ist, ist der Liste egal. Die Logik steckt in der Instanz, die diese Methode überschrieben hat. Diese Vorgehensweise entspricht ein wenig dem Kommando-Pattern. Gruß Der Unwissende |
Re: Jobliste Kommunikation mit externem Gerät
@Der_WISSENDE :-)
Hallo, cool, was für eine schnell (und noch dazu ausführliche Antwort)!! Habe den Ansatz mit den verschiedenen JobKlassen verstanden, also es gibt eine BasisJobKlasse, die eine nicht implementierte doJob Methode besitzt. Diese wird in den abgeleiteten Klassen implementiert. Meine Frage ist jetzt folgende: ich habe ja eine Klasse die ungefähr 50-60 Kommunikationsmethoden implemetiert: Beispiel:
Delphi-Quellcode:
Wie soll ich nun meinem Job sagen, dass er die Funktion Set_Temperatur ausführen soll?
TGeraet.Get_Temperatur(var Temp:integer);
TGeraet.Set_Temperatur(Temp:integer); TGerat.Get_SerialNumber(var SN:String); TGeraet.Set_SerialNumber(SN:String); Sollte ich eine Parameterübergabe ( ich benötige ja den Parameter Temp, da ich ihn ja an die Funktion übergeben muss) per Konstruktor machen, oder wüsstest du eine bessere Methode? ich habe einige Jobs, die immer (zyklisch) ausgeführt werden müssen, wie z.B.: dass Lesen der Messwerte, hast du eine Ahnung, wie ich das am besten bewerkstellige, sollte ich einen Timer laufen lassen, der die Jobliste dann wieder und wieder mit dem gleichen Job füllt? Vielen Dank schonmal für deine Mühe!! Gruß DelphiManiac |
Re: Jobliste Kommunikation mit externem Gerät
Zitat:
Zitat:
|
Re: Jobliste Kommunikation mit externem Gerät
@Der_Unwissende:
Ist die TObjectlist denn Threadsafe, habe nämlich bei der Ausführung ein paar Probleme. Danke. |
Re: Jobliste Kommunikation mit externem Gerät
Zitat:
|
Re: Jobliste Kommunikation mit externem Gerät
Hi,
Ja das mit dem Sperrobjekt habe ich mir gedacht. Reicht es wenn ich ein Sperrobjekt lokal erzeuge (wahrscheinlich nicht, oder)? oder muss es über jede Unit eingebunden sein? Wo genau im Code deklariert man denn am besten so ein Sperrobjekt (bzw die CriticalSection). Danke schonmal :-) |
Re: Jobliste Kommunikation mit externem Gerät
Das Speerobjekt brauchst du eigentlich nur in einer Unit, da wo die Liste verwaltet wird. Letztlich muss die Liste ja auch allen die darauf zugreifen bekannt sein. Du könntest dies verhalten z.B. in einer eigenen Unit kapseln (und die Liste umhüllen).
Dann hast einen Wrapper, der dir einfach sagt ob die Liste leer ist, der ein Objekt hinzufügen kann und wenn sie nicht leer ist dir das nächste Objekt zurück gibt. Beim erzeugen dieses Wrappers legst du eine Instanz Variable vom Typ TCriticalSection an, die dann immer gesperrt wird, bevor du ein Element in die Liste tust bzw. aus ihr entfernst:
Delphi-Quellcode:
type
TJobList = class(TObject) private FSyncObj : TCriticalSection; FList : TObjectList; public constructor create; destructor destrory; override; procedure addJob(const Job : TBaseJob); procedure getNextJob(out Job : TBaseJob); function isEmpty : Boolean; end; // Konstruktor und Destruktor sind klar, erzeugen bzw. freigeben der Objekte procedure TJobList.addJob(const Job : TBaseJob); begin self.FSyncObj.Acquire; self.FList.Add(Job); self.FSyncObj.Release; end; procedure TJobList.getNextJob(out Job : TBaseJob); begin self.FSyncObj.Acquire; Job := TBaseJob(self.FList.Extract(self.FList[0])); self.FSyncObj.Release; end; function TJobList.isEmpty : Boolean; begin self.FSyncObj.Acquire; result := self.FList.Count > 0; self.FSyncObj.Release; end; |
Re: Jobliste Kommunikation mit externem Gerät
Super klappt alles wie gewollt.
Danke für deine Hilfe!! :angel: |
Re: Jobliste Kommunikation mit externem Gerät
@Der_Unwissende
Hallo, eine Frage hätte ich noch: beim jeglichen Zugriff auf die VCL muss man ja synchronisieren, dass ist mir ja klar, da sie nicht Threadsafe ist. Nun habe ich ja jetzt meine verschiedenen Jobs wie zb. ReadMesswerte In meinem Thread arbeite ich nun die Liste ab:
Delphi-Quellcode:
Meine Frage nun, ich habe in meiner spezifischen Implementation von
procedure TJobListThread.Execute;
var einJob: TBaseJob; begin { Thread-Code hier einfügen } while NOT (Terminated) do begin If NOT (JobListe.isempty) then begin JobListe.getNextJob(einJob); einJob.doJob; Jobliste.deleteJob(einJob); // Synchronize(einJob.doJob); Sleep(100); end; end; end;
Delphi-Quellcode:
eine Funktion, die die Einzelnen Messwerte holt und die VCL aktualisiert (Labels...usw) eine Art Observer Pattern.
doJob
Wie kann ich denn diesen Zugriff auf die VCL synchronisieren, ich will ja nicht den kompletten Job Synchronisieren,... dann bräuchte ich ja den Thread nicht :-(. Vielen Dank |
Re: Jobliste Kommunikation mit externem Gerät
Zitat:
Also einfach den Job ausführen, dir merken welche Werte dabei verändert wurden und dann in einer Methode xyz (die dann auch in der abstrakten Klasse verfügbar ist) die VCL Komponenten aktualisieren.
Delphi-Quellcode:
Ok, am Namen xyz kann man noch arbeiten :wink:, aber die Idee ist denke ich klar. Du könntest hier z.B. auch einen Datensatz zurückgeben, der alle Messwerte enthält und die GUI kümmert sich selbst um die aktualisierung.
procedure TJobListThread.Execute;
var einJob: TBaseJob; begin { Thread-Code hier einfügen } while NOT (Terminated) do begin If NOT (JobListe.isempty) then begin JobListe.getNextJob(einJob); einJob.doJob; Jobliste.deleteJob(einJob); synchronize(einJob.xyz); // Synchronize(einJob.doJob); Sleep(100); end; end; end; Was deine Messungen angeht, so hätte ich hier mal noch eine Frage, ob du hier wirklich immer alle 100 ms etwas machst? An sich denke ich wäre es schöner, wenn die Jobliste alle Jobs (wenn mal welche in der Liste sind) so schnell wie möglich abarbeitet. Sollte dann eine Pause zwischen zwei Jobs gemacht werden, wäre es irgendwie schöner, wenn man dies hier in den Jobs festlegen kann. Andererseits gibt es natürlich auch Zeitpunkte an denen die Jobliste keine Jobs beinhaltet, dann sollte natürlich auch der Thread nicht wirklich viel tun, deshalb würde ich dir empfehlen, dass du den Thread immer schlafen legst (self.suspend) und den von außen halt einfach aufweckst, wenn der schläft bevor und du einen Job einfügst. |
Re: Jobliste Kommunikation mit externem Gerät
@Der_Unwissende
Hi danke, ja die Idee ist klar. Eines der Gründe warum ich mich für Delphi als Programmiersprache entschieden habe ist, diese Forum gewesen... Habe also keinen Fehler gemacht, was das angeht :-D Meine Jobliste, soll an sich eigentlich immer was zu tun bekommen :-) Es soll z.B.: immer der Status des Geräts gelesen werden und bestimmte Parameter (Druck/Temp usw) dann kommen je nach angezeigter Maske noch andere Werte hinzu, die ständig, bzw einmalig (Beispiel anzeigen einer Kalbrierungsmaske (einlesen der Kalibrierungswerte)) gelesen werden müssen. Bin immer noch nach etwa 1 1/2 Jahren dabei ein Programmdesign (wobei ich den Code + Gui und deren maximale Trennung voneinander) Suche Beispielprojekte, die etwas konkreter sind. Ich habe im Forum gesehen, dass du dich mit Entwurfsmustern sehr gut auskennst. Bin auch gerade dabei ein paar Bücher zu wälzen zu einigen Mustern. Aber wie gesagt, mir fehlt da oft die praktische Anwendung. Wollte dich mal fragen, ob du mir mal eines deiner Projekte schicken könntest? Ich weiss ist wahrscheinlich zuviel verlangt, aber irgendwie muss man ja dazulernen. Um was es in dem Projekt geht ist mir eigentlich egal, es geht mir hauptsächlicht um die Kommunikation von Fachkonzept und GUI. Vielen Dank ... Gruß DelphiManiac |
Re: Jobliste Kommunikation mit externem Gerät
Zitat:
Was ein Projekt angeht, so muss ich dich leider enttäuschen, die wo du ein sauberes Design und eine Halbwegsentkopplung vorfindest sind alles kommerzielle Produkte, an denen hält mein Arbeitgeber bzw. verschiedene Kunden die Copyrights, die darf ich also nicht ohne weitere herausgeben. Ich denke dass verstehst du und wirst das nicht persönlich nehmen. Da ich privat dann immer lieber das leben geniesse als zu programmieren, kann ich dir leider auch hier kein Programm zu schicken. Ich kann dir nur anbieten dir weiterhin (sogut es geht) bei Entscheidungen eine weitere Meinung zu liefern, wobei ich sagen muss, dass ich genau wie Du und alle anderen auch nur Ideen für's Design habe, es können sich alle gleich irren. Ich denke das wichtigste für ein gutes Desing ist und bleibt die Einfachheit. Kleine Units, die genau ein Objekt (soweit du OO verwendest) beinhalten (bzw. analog eine bestimmte Funktionalität). Auch dieses Objekt / die Funktionen / Methoden sollten möglichst einfach gehalten sein. Das führt zu wenig Fehlern und verständlichen Code. Möchte man dies konsequent erreichen, führt dass quasi automatisch zur Entkoppelung, da die Darstellung eine andere Funktionalität als die Verwaltung der Daten oder die Logik ist. Design-Pattern helfen hier einfach nur bei dem Verständnis für bestimmte Dinge. Design Entscheidungen sollten immer gut dokumentiert werden, da es sie das Programm ausmachen. Später kann man anhand dieser Entscheidungen leicht nachvollziehen warum das Programm wie aufgebaut ist, was die Wartbarkeit erhöht. Bei den Design-Pattern hat man damit zwei Vorteile: 1. Jmd. hat hier schon ein bestimmtes Problem gelöst 2. Die Lösung ist eindeutig definiert. Verweist man auf ein Pattern ist klar was gemeint ist Der Rest ist dann noch ein wenig Abstraktion. An sich sollte es bei komplexeren Vorgängen, an denen Verschiedene Objekte beteiligt sind immer einen Controller geben, der zwischen den einzelnen Objekten vermitteln kann. Nur der muss dann alle Objekte kennen, diese sich jedoch nicht. Der Controller kann wiederum mit Abstrakten Klassen oder Interfaces arbeiten, so dass hier die Logik des Controllers unabhängig von der Implementierung der von ihm verwalteten Klassen erhalten bleiben kann. Ja, es ist klar, dass ein Kontroller dann wiederum zwischen Teilsystemen vermitteln kann, wobei der Controller hier nur zwischen anderen Controllern (denen des Teilsystems) vermittelt. Versucht man diese Prinzipien in eigenen Programmen umzusetzen, so hat man (meiner Erfahrung nach) immer eine recht orgentliche Basis. Wie gesagt, die höchste Priorität hat imho die Einfachheit. Gruß Der Unwissende |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:23 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz