Donald Adams
Donald Adams

Reputation: 147

Delphi Indy IdTCPServer and IdTCPClient. read and write control characters and text

Delphi XE3, Indy 10.5.9.0

I am creating an interface between a computer and an instrument. The instrument uses ASTM protocol.

I have successfully sent text based messages back and forth between the server and client. I have been able to send control characters to the server and read those. What I have not figured out after 3 days of searching is how to write and read messages that have a mixture of control characters and text.

I am sending ASTM protocol messages which require control characters and text like the following line. Everything in angle brackets are control characters. Writing the message is not where I run into problems. It is when reading it since I will receive both text and control characters. My code below is how I read the control characters. How can I tell when I get the character whether it is a control character and when it is text in the same string of control and text characters? Thanks to Remy Lebeau and his posts on this site to get me where I am. He talked about how to use buffers but I couldn't tell how to read a buffer that contained control characters and text characters.

<STX>3O|1|G-13-00017||^^^HPV|R||||||N||||||||||||||O<CR><ETX>D3<CR><LF>

I have added the following code to my server components OnConnect event which is supposed to allows me to send control characters...

...
AContext.Connection.IOHandler.DefStringEncoding := TIdTextEncoding.UTF8;
...

My server OnExecute event...

procedure TTasksForm.IdTCPServer1Execute(AContext: TIdContext);
var
  lastline : WideString;
  lastcmd :  WideString ;
  lastbyte : Byte ;
begin 

  ServerTrafficMemo.Lines.Add('OnExecute') ;

  lastline := '' ;
  lastcmd := '' ;

  lastbyte := (AContext.Connection.IOHandler.ReadByte) ;

  if lastbyte = Byte(5) then
  begin

    lastcmd := '<ENQ>' ;

    ServerTrafficMemo.Lines.Add(lastcmd) ;

    AContext.Connection.IOHandler.WriteLn(lastcmd + ' received') ;

  end;

end;

Upvotes: 1

Views: 6197

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595305

The only control characters present are STX and ETX, and they are both < 32, so ASCII and UTF-8 will both handle them just fine. Or, you can use Indy's own built-in 8bit encoding instead.

For this type of data, there are several different ways to read it with Indy. Since the bulk of the data is textual, and the control characters are just used as frame delimiters, the easiest way would be to use IOHandler.ReadLn() or IOHandler.WaitFor() with explicit terminators.

Of course, there are other options as well, such as reading bytes from the IOHandler.InputBuffer directly (which I think is overkill in this situation), using the InputBuffer.IndexOf() method to know how many bytes to read.

Also, TIdTCPServer is a multithreaded component, where its events are fired in worker threads, but your code is directly accessing the UI, which is not thread-safe. You MUST synchronize with the UI thread.

And you shouldn't be WideString, either. Use (Unicode)String instead.

Try something like this:

procedure TTasksForm.IdTCPServer1Connect (AContext: TIdContext);
begin AContext.Connection.IOHandler.DefStringEncoding := Indy8BitEncoding;
end;

procedure TTasksForm.IdTCPServer1Execute(AContext: TIdContext);
var
  lastline : string;
  lastcmd :  string ;
  lastbyte : Byte ;
begin
  TThread.Synchronize(nil,
    procedure
    begin
      ServerTrafficMemo.Lines.Add('OnExecute') ;
    end
  );

  lastbyte := (AContext.Connection.IOHandler.ReadByte);

  if lastbyte = $5 then
  begin
    lastcmd := '<ENQ>' ;
    TThread.Synchronize(nil,
      procedure
      begin
        ServerTrafficMemo.Lines.Add(lastcmd) ;
      end
    );
  end
  else if lastbyte = $2 then
  begin
    lastline := #2 + AContext.Connection.IOHandler.ReadLn(#3) + #3;
    lastline := lastline + AContext.Connection.IOHandler.ReadLn(#13#10) + #13#10;
    { or:
    lastline := #2 + AContext.Connection.IOHandler.WaitFor(#3, true, true);
    lastline := lastline + AContext.Connection.IOHandler.WaitFor(#13#10, true, true);
    }
    lastcmd := '<STX>' ;
    TThread.Synchronize(nil,
      procedure
      begin
        ServerTrafficMemo.Lines.Add(lastcmd) ;
      end
    );
  end;
  AContext.Connection.IOHandler.WriteLn(lastcmd + ' received') ;
end;

Upvotes: 1

Dan Barclay
Dan Barclay

Reputation: 39

I couldn't tell how to read a buffer that contained control characters and text characters

This protocol is no doubt using ASCII strings. Any characters below decimal 32 will be control characters. Those 32 and above will be data characters. See http://ascii-table.com/ascii.php

Dealing with that as bytes works fine. You can also use ansistring, which is ASCII plus the top 127 characters. In this situation I would avoid UTF(any) and stick with either byte or ansistring. You need to control the message at the character level, and these characters are 8 bits per character with no escapes.

Alsosee the first example, in the first answer here:

Upvotes: 0

Related Questions