在我的一个应用程序中,我使用TComPort和TComDataPacket与各种医疗器械进行通信。根据仪器类型,我在TComDataPacket.OnCustomStart和TComDataPacket.OnCustomEnd中有几行代码来标记数据包的开始和结束。对于具有固定开始和结束字符对(例如STX/ETX)的简单数据包,一切都正常。
我尝试使用相同的方法添加对ASTM 1391协议的支持。ASTM 1391分组由ENQ、一个或多个以STX开始并以CR LF结束的分组和一个EOT组成,以标记数据传输的结束。而作为对ENQ和CR LF的回应,ACK应该被送回去。一个非常简单的和仪器和计算机之间对话的原理图是这样的:
ENQACKSTX..............CR LFACKSTX...................CR LFACKSTX....................................CR LFACKSTX..............CR LFACKEOT下面是我的OnCustomStart、OnCustomEnd和OnPacket事件中的代码:
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。由于我在几台不同的计算机和不同的仪器上,甚至在我的测试机器上都有过这个问题,即使我的测试机器上有一个循环回退的虚拟串口,我的猜测是,这与TComPort或TComDataPacket的内部结构有关。有人能把我引向正确的方向吗?
发布于 2013-06-03 13:55:12
你的密码里有台风。
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-05-31 12:18:33
这里有一些问题。
首先,您的自定义数据包处理程序是可重入的。这很糟糕。在数据包处理程序中调用Application.ProcessMessages将从处理程序中脱离出来,如果同时接收到新的数据包,则将从一开始就开始运行该函数,但在完全处理完后续包之后,只会继续继续运行(除非在此期间出现了另一个数据包,在这种情况下,它将再次重新启动)。这不是您想要的行为,因为它将导致无序的数据包处理,并且可能与您对sleep的需求有关。
我可能建议的是为从该仪器发送的每个命令配置单独的数据包--即:
ENQ,结束字符串#13#10 {CRLF}STX,结束字符串#13#10 {CRLF}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-06-02 11:19:52
在我看来,使用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代码中实现这一点,我将不胜感激。
https://stackoverflow.com/questions/16851294
复制相似问题