0

我在我的一个应用程序中使用各种医疗仪器TComPortTComDataPacket与之通信。我有几行代码,TComDataPacket.OnCustomStart根据TComDataPacket.OnCustomEnd仪器类型标记数据包的开始和结束。对于具有固定开始和结束字符对(例如STX/ ETX)的简单数据包,一切正常。

我尝试使用相同的方法添加对 ASTM 1391 协议的支持。ASTM 1391 数据包由一个ENQ、一个或多个以 开头STX和结尾的数据包CR LF和一个EOT标记数据传输结束的数据包组成。并且响应ENQand CR LF, andACK应该被发回。仪器和计算机之间的对话非常简单的示意图如下所示:

  • 安装:ENQ
  • 主持人:ACK
  • 安装:STX........CR LF
  • 主持人:ACK
  • 安装:STX...................CR LF
  • 主持人:ACK
  • 安装:STX..................................................CR LF
  • 主持人:ACK
  • 安装:STX........CR LF
  • 主持人:ACK
  • 安装:EOT

这是 my OnCustomStartOnCustomEndOnPacketevents 中的代码:

procedure TdmInstrument.cdpPacketCustomStart(Sender: TObject; const Str: string;
  var Pos: Integer);
begin
  if not FInitInfo.IsASTM then // simple packet structure
    Pos := System.Pos(FInitInfo.StartChar, Str)
  else
  begin
    Sleep(500); // no idea why this is required
    Application.ProcessMessages;
    Pos := System.Pos(cENQ, Str);
    if Pos = 0 then
    begin
      Pos := System.Pos(cSTX, Str);
      if Pos = 0 then
        Pos := System.Pos(cEOT, Str);
    end
    else
      ASTMStr := '';
  end;
end;

procedure TdmInstrument.cdpPacketCustomStop(Sender: TObject; const Str: string;
  var Pos: Integer);
begin
  if not FInitInfo.IsASTM then
    Pos := System.Pos(FInitInfo.EndChar, Str)
  else
  begin
    Pos := System.Pos(cENQ, Str);
    if Pos = 0 then
    begin
      Pos := System.Pos(cCR + cLF, Str) + 1;
      if Pos = 0 then
        Pos := System.Pos(cEOT, Str);
    end;
  end;
end;

procedure TdmInstrument.cdpPacketPacket(Sender: TObject; const Str: string);
var
  i: Integer;
begin
  if not FInitInfo.IsASTM then
  begin
    RawRecord := '';
    for i := 1 to Length(Str) do
      if Str[i] <> #0 then
        RawRecord := RawRecord + Str[i]
      else
        RawRecord := RawRecord + ' ';
  end else begin
    ASTMStr := ASTMStr + Str;
    if Str <> cEOT then
      cpCom.WriteStr(cACK);
    if Pos(cENQ, ASTMStr) * Pos(cEOT, ASTMStr) = 0 then // ASTM packet is not yet complete - exit
      Exit;
    RawRecord := ASTMStr;
  end;

  // we have a packet, so parse it
  ParsePacket;
end;

我的问题是,如果我不Sleep()使用大于 500 in 的值进行调用,则OnCustomStartin设置为only。由于我在很多不同的计算机和不同的仪器上都遇到过这个问题,甚至在我的带有环回虚拟串行端口的测试机器上,我的猜测是这与or的内部结构有关。谁能指出我正确的方向?OnPacketStrSTXTComPortTComDataPacket

4

3 回答 3

2

您的代码中有错字。

procedure TdmInstrument.cdpPacketCustomStop ...

begin
  ....

      Pos := System.Pos(cCR + cLF, Str) + 1;
      if Pos = 0 then
        Pos := System.Pos(cEOT, Str);
  ....
end;

if Pos = 0 then. Pos 永远不可能0

你不应该使用Pos你的变量。并用它来竞争System.Pos

一些代码优化

procedure TdmInstrument.cdpPacketPacket(Sender: TObject; const Str: string);

begin
  if not FInitInfo.IsASTM then
  begin
    RawRecord := '';
    if Pos(#0, Str) > 0 then Str:=Stringreplace(Str,#0,' ',[]);
    RawRecord := RawRecord + Str;
  end else begin
    ASTMStr := ASTMStr + Str;
    if (Pos(cENQ, ASTMStr) + Pos(cEOT, ASTMStr) + Pos(cCR + cLF,Str) = 0)  then 
      Exit; // ASTM packet is not yet complete - exit
            // Do Not exit if there is a `cCR + cLF`
    if Pos(cEOT, Str) = 0 then cpCom.WriteStr(cACK);
            // write only when one of  `cENQ , cCR + cLF` is present
    RawRecord := ASTMStr;
  end;

 // we have a packet, so parse it
  ParsePacket;
  end;
于 2013-06-03T13:55:12.670 回答
2

这里有几个问题。

首先,您的自定义数据包处理程序是可重入的。这是不好的。如果同时接收到一个新的数据包,则在数据包处理程序内部调用Application.ProcessMessages将跳出处理程序并从头开始运行函数,仅在后续数据包已完全处理后从中断处继续(除非在此期间另一个数据包进来,在这种情况下它将再次重新启动)。这不是您想要的行为,因为它会导致数据包处理乱序,并且可能与您对sleep.

我可能建议为从仪器发送的每个命令配置单独的数据包 - 即:

  • 数据包 1:开始字符串ENQ,结束字符串#13#10{CRLF}
  • 数据包 2:开始字符串STX,结束字符串#13#10{CRLF}
  • 数据包 3:开始字符串EOT,结束字符串#13#10{CRLF}

您将需要一些类变量、记录、对象等来跟踪数据包的进度。像这样的东西,例如:

TASTMPkt = record
  Started : boolean;
  Complete : boolean;
  Data : TStringList;
end;

ENQ数据包处理程序中,您将执行以下操作:

if FASTMPkt.Data = nil then FASTMPkt.Data := TStringList.Create();
FASTMData.Clear();
FASTMPkt.Started := true;
FASTMPkt.Complete := false;
cpCom.WriteStr(cACK);

STX处理程序中:

if (not FASTMPkt.Started) or (FASTMPkt.Complete) then begin 
  // Raise exception, etc
end else begin
  FASTMPkt.Data.Add(Str);
  cpCom.WriteStr(cACK);
end;

EOT处理程序中:

if (not FASTMPkt.Started) or (FASTMPkt.Complete) then begin 
  // Raise exception, etc
end else begin
  cpCom.WriteStr(cACK);
  FASTMPacket.Complete := true;
  ProcessPacket;
end;

然后可以在哪里ProcessPacket处理字符串列表数据并做任何事情。这可以避免阻塞等待可能传入的数据包的 UI 线程,允许您使用计时器来FASTMPkt及时检查是否完成(您可以在ENQ数据包处理程序中启动超时计时器,在处理程序中重置它,然后在处理STX程序中停止它EOT, 例如)。它还避免了睡眠和 ProcessMessages,并且通常为您提供处理错误和确保正确流程流的方法。

顺便说一句,我从未使用过 TComPort 或 TDataPacket,但我强烈推荐 AsyncPro(TApdComPort、TApdDataPacket 等)——这些都是很棒的组件,可以配置为可视或非可视组件,我发现它们非常称职并且可靠。我在这里的回答假设这两个组件通常以相同的方式工作(我认为它们确实如此)。

http://sourceforge.net/projects/tpapro/

于 2013-05-31T12:18:33.143 回答
1

在我看来,使用 TComPort(或任何串行库)创建成功的数据交换是最难的事情。TComPort 为您提供了很好的例程,但没有简单的“发送并等待回复”的好例子,而且我用 Delphi 完成的几乎所有串行通信都需要某种“等待终止条件”。我曾经使用 AsyncPro,尽管它仍然可用,但它也没有明确的示例来说明如何设置带有发送和回复的双向串行。因此,您很想创建一些使用 Application.ProcessMessages 来“获取”响应字符的东西,正如 J..所指出的那样,这会带来其他问题。

为了解决这个问题,我对 TComPort 进行了如下添加。这可能不是最佳的,但它适用于许多具有许多不同协议的串行仪器。

首先,如下配置 TComPort - 关键位是 FStopEvent...

constructor TArtTComPort.Create( const APort : string);
begin
  inherited Create;

  FTimeoutMS := 3000;

  FComPort := TComPort.Create( nil );

  FComPort.Events := [];  // do not create monitoring thread

  FComPort.Port := APort;

  FComPortParametersStr := sDefaultSerialPortParameters;

  FFlowControl := sfcNone;

  // Prepare a stop event for killing a waiting communication wait.
  FStopEvent := TEvent.Create(
    nil, //ManualReset
    false, //InitialState
    false,
    'StopEvent' );

end;

要发送字符,您只需调用 FComPort.WriteStr。

当您等待一些回复时,我使用以下内容。这允许我指定终止字符是什么,并在之后忽略(或处理)尾随字符。成功时,它只是返回响应。它不调用 Application.ProcessMessages,因此不存在租用问题并且允许超时。

function TArtTComPort.SerialPort_AwaitChars(AMinLength: integer;
  ATerminator: char; AQtyAfterTerm: integer; ARaise: boolean): string;
var
  fDueBy : TDateTime;

  function IsEndOfReplyOrTimeout( var AStr : string ) : boolean;
  var
   I : integer;
  begin
    Result := False;
    If ATerminator <> #0 then
      begin
      I := Length( AStr ) - AQtyAfterTerm;
      If I > 0 then
        Result := AStr[I] = ATerminator;
      end;
    If not Result then
      Result := Length(AStr) >= AMinLength;


    // Un-comment this next line to disable the timeout.
    //Exit;

    If not Result then
      begin
      Result := Now > fDueBy;
      If Result then
        If ARaise then
          raise EArtTComPort.Create( 'Serial port reply timeout' )
        else
          AStr := '';
      end;
  end;

var
  Events : TComEvents;
  iCount : integer;
  S : string;
begin
  Assert( AMinLength > 0, 'Invalid minimum length' );

  If not FComPort.Connected then
    begin
    Result := '';
    Exit;
    end;

  fDueBy := Now + (FTimeoutMS * TDMSec );

  Result := '';

  Repeat

    // Setup events to wait for:
    Events := [evRxChar, evTxEmpty, evRxFlag, evRing, evBreak,
             evCTS, evDSR, evError, evRLSD, evRx80Full];

    // Wait until at least one event happens.
    FComPort.WaitForEvent(
      Events,
      FStopEvent.Handle,
      FTimeOutMS);

    If Events = [] then // timeout
      begin
      If ARaise then
        raise EArtTComPort.Create( 'Serial port reply timeout' )
      end
     else
      begin
      If evRxChar in Events then
        begin
        iCount := FComport.InputCount;
        FComPort.ReadStr( S, iCount );
        Result := Result + S;
        end;
      end;

  until IsEndOfReplyOrTimeout( Result );


end;

请注意,此代码还有一些未显示的其他小依赖项,但它应该给您一个良好的开端。如果有人能告诉我如何在 TComPort 代码中实现这一点,我将不胜感激。

于 2013-06-02T11:19:52.527 回答