Atozed Forums

Full Version: Using IdTcpClient for logging data (Lazarus/Fpc)
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I need to write a small utility for receiving logging data from an embedded WiFi device.
The device (based on ESP8266) is programmed to work as a TCP<=>Serial bridge for channeling data between a data collection unit and a network connected master (can be a PC program or an Android App using TCP socket communications).
We have problems in certain operating modes with the device and so I need to check the data handled by the device. Dropped bytes is a killer here...

So I added two TCP servers to the device each listening on different ports and dedicated to log data sent to and from the serial port of the device. Every time there is any data in either of these sources it will be sent to the logging client in addition to the relaying destination.

Now I need to add two instances of a "logging client" to my configuration program (written with Lazarus/FPC and using the indylaz package components) and the plan is to do this as follows:

- Write a TCP logging class I can instantiate twice for the two logging directions
- Each class instance shall connect to the server on one of the two ports dedicated for logging
- While connected it shall wait for incoming data and dump all received bytes to a log file.
- The running count of the received bytes shall be reported to the main application for display (event function)

In order to do the above I probably need to use a threaded approach since I must keep the application live and responsive.

But I have not succeeded to find any good example to start from where the TCP client is threaded and does not send anything at all by itself, it shall just receive and write to disk whatever comes along. And generate an event with the current byte count.

Any suggestions on how this can be accomplished?

This is as far as I have come as of now:


Code:
type

  TRxEvent = procedure (const Buffer: TIdBytes) of object;

  { TWiFiCommLogger }

  TWiFiCommLogger = class(TThread)
  private
    FComm: TIdTcpClient;
    FServer: string;
    FPort: TIdPort;
    FTcpConnected: boolean;
    FOnRxData: TRxEvent;
    FBuffer: TIdBytes;
   FActive: boolean;
    procedure OnConnected(Sender: TObject);
    procedure OnDisConnected(Sender: TObject);
  protected
    procedure Execute; override;
  public
    constructor Create;
   destructor Destroy; override;
    procedure Connect(Server: string; Port: word);
   procedure Disconnect;
   property Active: boolean read FActive write FActive;
    property Connected: boolean read FTcpConnected;
    property OnRxData: TRxEvent read FOnRxData write FOnRxData;
  end;

implementation

{ TWiFiCommLogger }

procedure TWiFiCommLogger.OnConnected(Sender: TObject);
begin
  FTcpConnected := true;
end;

procedure TWiFiCommLogger.OnDisConnected(Sender: TObject);
begin
  FTcpConnected := false;
end;

procedure TWiFiCommLogger.Execute;
begin
  while not Terminated do
 begin
   if FActive and FTcpConnected then
    begin
      //What do I put here?
      FComm.IOHandler.ReadBytes(FBuffer, -1, true); //Append indata to buffer
      if Length(FBuffer) > 0 then
        if Assigned(FOnRxData) then
        begin
          FOnRxData(FBuffer); //Data are supposed to be saved in the main thread
          SetLength(FBuffer, 0); //So after executing the evnt procedure, erase existing data
        end;
    end;  
 end; //while
end;

constructor TWiFiCommLogger.Create;
begin
 FActive := false;
  FComm := TIdTCPClient.Create(NIL);
  FComm.IPVersion := Id_IPv4;
  FComm.ReadTimeout := 100;
  FComm.ConnectTimeout := 5000;
  FTcpConnected := false;
  FComm.OnConnected := OnConnected;
  FComm.OnDisconnected := OnDisconnected;
end;

destructor TWiFiCommLogger.Destroy;
begin
  if Connected then
    Disconnect;
  FComm.Free;
  inherited Destroy;
end;

procedure TWiFiCommLogger.Connect(Server: string; Port: word);
begin
  FServer := Server;
  FPort := Port;
  FComm.Connect(FServer, FPort);
end;

procedure TWiFiCommLogger.Disconnect;
begin
  FComm.Disconnect;
end;

Usage in main form:

Code:
procedure TfrmCommTest.FormCreate(Sender: TObject);
begin
  ...
  FLogSS := TWiFiCommLogger.Create;
  FLogSS.OnRxData := OnRxSS;
  ...
end;

procedure TfrmCommTest.btnStartLogClick(Sender: TObject);
begin
  if (FLogSS.Connected and FLogSSM.Connected) then
  begin
    FLogSS.Active := false;
    FLogSS.Disconnect;
    ledLog.Brush.Color := clRed;
    btnStartLog.Caption := 'Start Log';
  end
  else
  begin
    FLogSS.Connect(edAddress.Text, speTcpPort.Value + 10);
    if FLogSS.Connected then
    begin
      ledLog.Brush.Color := clLime;
      btnStartLog.Caption := 'Stop Log';
      FLogSS.Active := true;
    end;
  end;
end;

procedure TfrmCommTest.OnRxSS(const Buf: TBytes);
var
  len, lbuf: integer;
begin
  len := Length(Buf);
  lbuf := Length(FLogBufSS);
  SetLength(FLogBufSS, lbuf + len);
  Move(Buf[0], FLogBufSS[lbuf], len); //Append data to log buffer
 SaveLogFile(Buf); //Append data to file
 SetLength(Buf, 0);  //Clear data after save
  FSSDataRx := true;
end;

The data received (into a TIdBytes container) is sent to the main thread in the event function if defined to be stored into a file (file save not shown above).

Do I need some exception handling inside the Execute method?