1

I'm trying to reading a simple string from host on Serial line RS232. The parameters of receive - transmission are the same for both:

Baudrate: 9600;
Data: 8bit;
Parity: None;
Stop: 1bit;
Flow Control: None;

I've tried to send this string:

!A243B324C213D300#

I use '!' and '#' as header and finish to be sure the string will be received at all. The problem is that I can receive the string until the 15th character (3).

I know this because if I use this code:

procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer);
begin
 ComPort1.ReadStr(Str, Count);
 memo1.Lines.Add(str);
end;

I will see


!A243B324C213D3

00#


I've tried to extend the buffer of the comport, without results.

So why I can't receive the complete string in one shot?

I found this library here and it works fine until now:

http://sourceforge.net/projects/comport/

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
Drift89
  • 69
  • 1
  • 1
  • 6
  • 1
    You have to store the received data into a "global" buffer and only submit it, if you receive the whole part -> checking start and finish header – Sir Rufo Mar 16 '14 at 11:40
  • That's not an header problem. If I send !AxxxBxxxCxxx# for example I can receive it at all without problems. Anyway, how can I put everything inside a "global" buffer? You mean an array? Now I found that if I increase the baud-rate from 9600 to 115200 I can receive the complete string !AxxxBxxxCxxxDxxx#... But I don't know why! – Drift89 Mar 16 '14 at 12:49
  • Declare a field in the form class as the "global" buffer and append all received data. Once you have a complete message inside that buffer call an event with that message and remove that message from the buffer. – Sir Rufo Mar 16 '14 at 13:13
  • Simply the device have not finished sending the string at the time you're reading it. Build your string in OnReceiveChar or give an other round of reading if the message is incomplete, until it's complete, or wait more before attempting to read, or whatever you find more appropriate. – Sertac Akyuz Mar 16 '14 at 14:15

2 Answers2

4

With TComPort component comes another one: TComDataPacket. Simply connect this to TComPort, setup StartString and StopString properties and use event onPacket to get complete data.

example:

...
    ComDataPacket1.Comport := Comport1;
    ComDataPacket1.StartString := '!';
    ComDataPacket1.StopString := '#';
    ComDataPacket1.onPacket := ComDataPacket1Packet;
...

//this is real code from one of my applications where I use it for barcode reader service
procedure TDM.ComDataPacket1Packet(Sender: TObject; const Str: string);
begin
     BarCodeReader.Barcode := Str;  
end;
  • Thanks for your reply. I've notice that component, but I don't know how to read its buffer, because I'm a n00b. if I use ComDataPacket.AddData(str) how can I read the 'str' from buffer? – Drift89 Mar 17 '14 at 17:22
  • I added example code. I hope this is what you needed. – user3421422 Mar 17 '14 at 21:02
  • Now is all clear and it works perfectly also with other packets (like %#, $# ect. that I manage inside the same program). So the famous "buffer" can be read by reading Str constant inside OnPacket event? That's what I've understood from this example. – Drift89 Mar 18 '14 at 18:48
3

You cannot rely on receiving complete messages in any communications at that low level of communication. Also you cannot rely on receiving only one message at a time.

You must implement something, that will guarantee to notify only on complete messages.

To do so you have to store the incoming data until you receive the complete message (header and finish flags).

Here is a small console app with a TMessageBuffer class, that handles the incoming data and complete messages

program so_22436319;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  SysUtils;

type
  TMessageBuffer = class
  private
    FBuffer : string;
    FMsgPart : Boolean;
    procedure DoHandleMessage( const CompleteMessage : string );
  public
    procedure AddData( const Data : string );
  end;

procedure Test;
var
  LMsgBuffer : TMessageBuffer;
begin
  LMsgBuffer := TMessageBuffer.Create;
  try
    // receive complete message
    LMsgBuffer.AddData( '!A243B324C213D300#' );
    // receive 2 complete message in one go
    LMsgBuffer.AddData( '!A243B324C213D300#!A243B324C213D300#' );
    // receive parts of the message
    LMsgBuffer.AddData( '!A243B324' );
    LMsgBuffer.AddData( 'C213D300#!A243' );
    LMsgBuffer.AddData( 'B324C213D300#!A' );
    LMsgBuffer.AddData( '243B324C2' );
    LMsgBuffer.AddData( '13D300#' );

  finally
    LMsgBuffer.Free;
  end;
end;

  { TMessageBuffer }

procedure TMessageBuffer.AddData( const Data : string );
var
  LIdx : Integer;
  LChar : Char;
begin
  for LIdx := 1 to Length( Data ) do
  begin
    LChar := Data[LIdx];
    if FMsgPart then
      if LChar = '#' then
      begin
        DoHandleMessage( FBuffer );
        FMsgPart := False;
        FBuffer := '';
      end
      else
      begin
        FBuffer := FBuffer + LChar
      end
    else if LChar = '!' then
    begin
      FMsgPart := True;
    end;
  end;
end;

procedure TMessageBuffer.DoHandleMessage( const CompleteMessage : string );
begin
  Writeln( 'MSG: ', CompleteMessage );
end;

begin
  try
    Test;
  except
    on E : Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  ReadLn;
end.

The generated output is

MSG: A243B324C213D300
MSG: A243B324C213D300
MSG: A243B324C213D300
MSG: A243B324C213D300
MSG: A243B324C213D300
MSG: A243B324C213D300

The class removes the header and finish char, because this is part of the transportation protocol and therefore not needed any more. But you can still add it if you like.

Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
  • Very good answer, thanks! No problem if I'll "lose" ! and #, cause are there just to be sure to receive the string at all. – Drift89 Mar 17 '14 at 17:27