首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用TComPort读取ASTM 1391数据时的TComDataPacket数据丢失

使用TComPort读取ASTM 1391数据时的TComDataPacket数据丢失
EN

Stack Overflow用户
提问于 2013-05-31 06:46:35
回答 3查看 2.3K关注 0票数 0

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

我尝试使用相同的方法添加对ASTM 1391协议的支持。ASTM 1391分组由ENQ、一个或多个以STX开始并以CR LF结束的分组和一个EOT组成,以标记数据传输的结束。而作为对ENQCR LF的回应,ACK应该被送回去。一个非常简单的和仪器和计算机之间对话的原理图是这样的:

  • INST:ENQ
  • 主机:ACK
  • INST:STX..............CR LF
  • 主机:ACK
  • INST:STX...................CR LF
  • 主机:ACK
  • INST:STX....................................CR LF
  • 主机:ACK
  • INST:STX..............CR LF
  • 主机:ACK
  • INST:EOT

下面是我的OnCustomStartOnCustomEndOnPacket事件中的代码:

代码语言:javascript
复制
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() (在OnCustomStart中值大于500 ),那么在OnPacket中,Str只被设置为STX。由于我在几台不同的计算机和不同的仪器上,甚至在我的测试机器上都有过这个问题,即使我的测试机器上有一个循环回退的虚拟串口,我的猜测是,这与TComPortTComDataPacket的内部结构有关。有人能把我引向正确的方向吗?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-06-03 13:55:12

你的密码里有台风。

代码语言:javascript
复制
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的竞争中。

一些代码优化

代码语言:javascript
复制
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;
票数 2
EN

Stack Overflow用户

发布于 2013-05-31 12:18:33

这里有一些问题。

首先,您的自定义数据包处理程序是可重入的。这很糟糕。在数据包处理程序中调用Application.ProcessMessages将从处理程序中脱离出来,如果同时接收到新的数据包,则将从一开始就开始运行该函数,但在完全处理完后续包之后,只会继续继续运行(除非在此期间出现了另一个数据包,在这种情况下,它将再次重新启动)。这不是您想要的行为,因为它将导致无序的数据包处理,并且可能与您对sleep的需求有关。

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

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

您需要一些类变量、记录、对象等来跟踪数据包的进度。就像这样,举个例子

代码语言:javascript
复制
TASTMPkt = record
  Started : boolean;
  Complete : boolean;
  Data : TStringList;
end;

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

代码语言:javascript
复制
if FASTMPkt.Data = nil then FASTMPkt.Data := TStringList.Create();
FASTMData.Clear();
FASTMPkt.Started := true;
FASTMPkt.Complete := false;
cpCom.WriteStr(cACK);

STX处理程序中:

代码语言:javascript
复制
if (not FASTMPkt.Started) or (FASTMPkt.Complete) then begin 
  // Raise exception, etc
end else begin
  FASTMPkt.Data.Add(Str);
  cpCom.WriteStr(cACK);
end;

EOT处理程序中:

代码语言:javascript
复制
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/

票数 2
EN

Stack Overflow用户

发布于 2013-06-02 11:19:52

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

为了解决这个问题,我自己添加了TComPort,如下所示。这可能不是最优的,但它可以与许多不同协议的串行设备一起工作。

首先,按照以下方式配置TComPort -关键位是FStopEvent.

代码语言:javascript
复制
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,因此不存在轮转问题,并且允许超时。

代码语言:javascript
复制
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代码中实现这一点,我将不胜感激。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16851294

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档