Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Zugriff auf RS232 über TApdComPort und Thread (https://www.delphipraxis.net/212420-zugriff-auf-rs232-ueber-tapdcomport-und-thread.html)

norwegen60 7. Feb 2023 15:46

Zugriff auf RS232 über TApdComPort und Thread
 
Hallo zusammen,

ich handle die komplette RS232-Kommunikation in einem Thread.
Ziel ist:
  • Der Thread puffert alle eingehenden Befehle und sendet den nächsten erst, wenn der vorhergehende geantwortet hat oder keine Antwort erwartet wird
  • Der Thread wartet z.B, auf eine bestimmte Antwort. Das kann auch bedeutet, dass er laufend einen Status abfragt, bis der gewünschte Status erreicht ist.
  • Der Thread schickt den Befehl noch mal, wenn keine Antwort innerhalb eines definierten Timeouts kommt

Momentan habe ich aber das Problem, dass hin und wieder Antworten verloren gehen. Deshalb die Frage ob an meinem Konstrukt prinzipiell was falsch ist. Dieses sieht stark vereinfacht so aus:
Delphi-Quellcode:
unit Unit1;

interface

uses
  Classes,
  AdPort;

type
  TThreadString = procedure(Value: String) of object;

  TMyComThread = class(TThread)
  private
    FCommand: String;
    FCommandList: TStringList;
    FComPort: TApdComPort;
    FBuf: String;
    FBusy: Boolean;
    FResponse: String;
    FSyncResponse: TThreadString;

    procedure Execute;

    procedure SyncResponseEvent;
    procedure SetResponse(const Value: String);
  protected
    procedure ComPortTriggerAvail(CP: TObject; Count: Word);
  public
    property Response: String write SetResponse;
    property SyncResponse: TThreadString read FSyncResponse write FSyncResponse;

    constructor Create(sEinst: String);
    destructor Destroy; override;

    procedure SendCommand(sValue: String);
  end;

implementation

procedure TMyComThread.ComPortTriggerAvail(CP: TObject; Count: Word);
var
  i: Integer;
  cBuf: Char;

begin
  for i := 1 to Count do
  begin
    cBuf := Char(FComPort.GetChar);

    if cBuf = #13 then
    begin
      Response := FBuf;
      FBuf := '';

      FBusy := False;
    end
    else
      FBuf := FBuf + cBuf;
  end;
end;

constructor TMyComThread.Create(sEinst: String);
begin
  FComPort := TApdComPort.Create(nil);
  FComPort.OnTriggerAvail := ComPortTriggerAvail;
end;

destructor TMyComThread.Destroy;
begin
  FComPort.Open := False;
  FComPort.Free;

  FCommandList.Free;

  inherited;
end;

procedure TMyComThread.Execute;
begin
  FComPort.Open := True;
  FBusy := False;

  while not Terminated do
  begin
    if not FBusy and (FCommandList.Count > 0) then
    begin
      FCommand := FCommandList[0];
      FCommandList.Delete(0);
      FComPort.PutString(AnsiString(FCommand + #13 + #10));
      FBusy := True;
    end;

    TThread.Sleep(50);
  end;
end;

procedure TMyComThread.SendCommand(sValue: String);
begin
  FCommandList.Add(sValue);
end;

procedure TMyComThread.SetResponse(const Value: String);
begin
  FResponse := Value;
  Synchronize(SyncResponseEvent);
end;

procedure TMyComThread.SyncResponseEvent;
begin
  if Assigned(FSyncResponse) then
    FSyncResponse(FResponse);
end;

end.
und im Main Thread

Delphi-Quellcode:
  Com := TMyComThread.Create (sEinst);
  Com.SyncResponse:= SyncResponse;

procedure TForm1.SyncResponse(sMsg: String);
begin
  Edit1.Text:= sMsg;
end;

procedure TForm1.Button1ClickSender: TObject);
begin
  Com.SendCommand(Edit2.Text);
end;
Ich habe aus dem Code alles wie Timeout-Überwachung, unterbrochene Verbindung, wiederholtes senden, falsches Kommando, Kommando ohne Rückgabewert, korrektes beenden, .... raus gelassen

Es geht mir hauptsächlich darum
  1. Kann bei der Datenübergabe von
    Delphi-Quellcode:
    Com.SendCommand(Edit2.Text);
    was schief gehen
  2. Ist sichergestellt dass
    Delphi-Quellcode:
    ComPortTriggerAvail
    innerhalb des Threads läuft oder ist der von Haus aus an den Main-Thread gebunden? Wie könnte ich die Daten Threadsave aus TApdComPort empfangen
  3. Ist die Rückgabe der Antwort über
    Delphi-Quellcode:
    Synchronize(SyncResponseEvent);
    eine gute Wahl
  4. Oder macht der Einsatz von
    Delphi-Quellcode:
    TCriticalSection
    statt
    Delphi-Quellcode:
    Synchronize
    Sinn

Vor allem bei 2. bin ich mir unsicher.

Um Verzögerungen aus dem Main-Thread zu verhindern, schreibe ich die Responses in TForm ebenfalls in eine Liste und arbeite die dann ab.


Grüße
Gerd

Rollo62 7. Feb 2023 17:09

AW: Zugriff auf RS232 über TApdComPort und Thread
 
Vielleicht hilft Dir dies weiter ?
https://en.delphipraxis.net/topic/36...#comment-30310
https://www.delphipraxis.net/205297-...fentlicht.html
https://blog.grijjy.com/2017/01/12/e...c-ring-buffer/
https://github.com/grijjy/GrijjyFoundation
https://en.delphipraxis.net/topic/37...#comment-32259
https://www.delphipraxis.net/126525-...am-memory.html
http://delphicodemonkey.blogspot.com/2015/


Ich nutze für Ähnliches einen auf einem TMemoryStream basierenden, Ringbuffer, könnte aber auch TBytes sein, der von RS232 beschrieben wird.
Dieser läuft in einem Thread und behandelt alle Write/Read Prozesse.

himitsu 7. Feb 2023 17:13

AW: Zugriff auf RS232 über TApdComPort und Thread
 
Das ist nicht im Thread.
Delphi-Quellcode:
constructor TMyComThread.Create(sEinst: String);
begin
  FComPort := TApdComPort.Create(nil);
  FComPort.OnTriggerAvail := ComPortTriggerAvail;
end;

destructor TMyComThread.Destroy;
begin
  FComPort.Open := False;
  FComPort.Free;

  FCommandList.Free;

  inherited;
end;
Aber
Delphi-Quellcode:
FComPort.Open := False;
und .PutString nicht.
-> NICHT thread-save

Der Zugriff auf FCommandList ist auch NICHT thread-save.

Und wo ist im Contructor das Inherited, bzw.
Delphi-Quellcode:
inherited Create(False); // oder True
?

Vielen solcher Komponenten arbeien selbst mit einem Thread und synchronisieren dann die Events meistens in den Hauptthread. (aber nicht immer)
Prüfen wo ComPortTriggerAvail ausgeführt wird.

Rollo62 7. Feb 2023 18:45

AW: Zugriff auf RS232 über TApdComPort und Thread
 
Ich habe da eine eigene Lösung, basierend auf einem aufgebohrtem TMemoryStream, den Orginalink dazu finde ich gerade nicht mehr,
war was älteres Asiatisches.
Der war auch nicht Thread-Save, musste was drumrum gebaut werden.
Bei den ganzen Links könnte aber was Passendes für de Fragesteller dabei sein, wenn er sich auf RingBuffer einlassen möchte.


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:58 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz