I have downloaded and installed the comport library. I have a simulator sending data through serial RS232 to a Selphi program. This is the following code snippet.
procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
begin
ComPort.ReadStr(CPort.Str, Count);
Memo.Text := Memo.Text + CPort.Str;
end;
As for the CPort library portion, I added:
var
Str: String;
here is the problem.
The data example that is coming through is roughly like
$HEHDT,288.45,T*1D
$HEHDT,288.46,T*18
$HEHDT,288.47,T*1A
and so on. Each line is sent per second. So with the code above, the memo will display all these data as it.
However, if I change the code to:
procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
begin
ComPort.ReadStr(CPort.Str, Count);
Memo.Text := Memo.Text + CPort.Str + 'haha';
end;
This is what appears on the memo:
$HEHDT,2haha88.45,T*haha1Dhaha
$HEHDT,2haha88.46,T*haha18haha
$HEHDT,2haha88.47,T*haha1Ahaha
The "haha" appears after 8 ASCII characters. so does that mean in the CPort.pas library, under the asynchronous/synchronous portion, only 8ASCII characters max is assigned to the variable Str?
How do I go about changing the codes such that the whole data string, regardless of its size, will be allocated to the variable Str instead of only 8 bytes.
UPDATE**I realised that theres this part of the CPort library that contains the following code. can anyone enlighten me on how to edit the code? or whether it is the correct block that i have sourced out. thanks!
// split buffer in packets
procedure TComDataPacket.HandleBuffer;
procedure DiscardPacketToPos(Pos: Integer);
var
Str: string;
begin
FInPacket := True;
if Pos > 1 then
begin
Str := Copy(Buffer, 1, Pos - 1); // some discarded data
Buffer := Copy(Buffer, Pos, Length(Buffer) - Pos + 1);
DoDiscard(Str);
end;
end;
procedure FormPacket(CutSize: Integer);
var
Str: string;
begin
Str := Copy(Buffer, 1, CutSize);
Buffer := Copy(Buffer, CutSize + 1, Length(Buffer) - CutSize);
CheckIncludeStrings(Str);
DoPacket(Str);
end;
procedure StartPacket;
var
Found: Integer;
begin
// check for custom start condition
Found := -1;
DoCustomStart(Buffer, Found);
if Found > 0 then
DiscardPacketToPos(Found);
if Found = -1 then
begin
if Length(FStartString) > 0 then // start string valid
begin
Found := Pos(Upper(FStartString), Upper(Buffer));
if Found > 0 then
DiscardPacketToPos(Found);
end
else
FInPacket := True;
end;
end;
procedure EndPacket;
var
Found, CutSize, Len: Integer;
begin
// check for custom stop condition
Found := -1;
DoCustomStop(Buffer, Found);
if Found > 0 then
begin
// custom stop condition detected
CutSize := Found;
FInPacket := False;
end
else
if Found = -1 then
begin
Len := Length(Buffer);
if (FSize > 0) and (Len >= FSize) then
begin
// size stop condition detected
FInPacket := False;
CutSize := FSize;
end
else
begin
Len := Length(FStartString);
Found := Pos(Upper(FStopString),
Upper(Copy(Buffer, Len + 1, Length(Buffer) - Len)));
if Found > 0 then
begin
// stop string stop condition detected
CutSize := Found + Length(FStopString) + Len - 1;
FInPacket := False;
end;
end;
end;
if not FInPacket then
FormPacket(CutSize); // create packet
end;
A minimalistic solution with full error checking would be:
Update :
(Given that the received string ends with a CRLF combination and the packet length is not constant. This is a NMEA 0183 packet)
var
finalBuf: AnsiString;
// Example packet: $HEHDT,10.17,T*28 + CRLF
// This is a NMEA 0183 protocol (Marine and GPS standard)
// Subset for reading the heading.
// HDT Heading – True
// 1 2 3
// | | |
//$--HDT,x.x,T*hh
//1) Heading Degrees, true
//2) T = True
//3) Checksum
// HE stands for: Heading – North Seeking Gyro
{- Checking packet and checksum, calculating heading }
Function ParseAndCheckNMEA_HDT(const parseS: AnsiString;
var heading: Double): Integer;
// Example packet: $HEHDT,10.17,T*28 + CRLF
var
i, p, err: Integer;
xorSum: Byte;
xorStr: AnsiString;
headingStr: AnsiString;
begin
Result := 0; // Assume ok
if (Pos('$HEHDT', parseS) = 1) then // Start header ok ?
begin
p := Pos('*', parseS);
if (p <> 0) and (Length(parseS) >= p + 2) then
// Assumes a checksum in packet
begin
xorSum := Ord(parseS[2]);
for i := 3 to p - 1 do // Calculate checksum
xorSum := xorSum xor Ord(parseS[i]);
xorStr := IntToHex(xorSum, 2);
if (UpperCase(xorStr) = Copy(parseS, p + 1, 2)) then // Checksum ok ?
begin
// Validate heading
headingStr := Copy(parseS, 8, p - 10);
Val(headingStr, heading, err);
if (err <> 0) then
Result := 4; // Not a valid float
end
else
Result := 3; // Wrong checksum
end
else
Result := 2; // No checksum
end
else
Result := 1; // Wrong header
end;
procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
var
i,err: Integer;
strBuf: AnsiString;
heading: Double;
begin
ComPort.ReadStr(CPort.Str, Count);
strBuf := CPort.str;
for i := 1 to Length(strBuf) do
case strBuf[i] of
'$' :
finalBuf := '$'; // Start of package
#10 :
begin
if (finalBuf <> '') and (finalBuf[1] = '$') then // Simple validate check
begin
SetLength( finalBuf, Length(finalBuf) - 1); // Strips CR
err := ParseAndCheckNMEA_HDT(finalBuf, heading);
if (err = 0) then
Memo.Lines.Add(finalBuf); // Add validated string
//else
// Memo.Lines.Add('Error:' + IntToStr(err));
end;
finalBuf := '';
end;
else
finalBuf := finalBuf + strBuf[i];
end;
end;
Knowing the start character and packet ending, this should be fairly safe to use. The #13 and #10 (CR LF) characters that marks the end of package, are stripped and the package is checked for a valid checksum and the resulting heading value is calculated. The validated string is then added to the memo.
Update 2
To answer the direct question, why your receiving method can add your 'haha' string in the middle of the data string:
The comport routine delivers data, one or more characters, at its own pace. You cannot control when you are getting the data or how many characters there will be. Using the scheme in my answer, data is buffered until a complete package is delivered. With the packet support of TComPort it is possible to do the same.
From your comments it seems as there are several NMEA 0183 sensor types connected to the serial port. (Giving other lengths of the packages but all starting with $ character).
Replace the ParseAndCheckNMEA_HDT with the following function to validate the strings in that case :
Function ParseAndCheckNMEA(const parseS: AnsiString): Integer;
// Example packet: $HEHDT,10.17,T*28 + CRLF
var
i, p: Integer;
xorSum: Byte;
xorStr: AnsiString;
begin
Result := 0; // Assume ok
if (Pos('$', parseS) = 1) then // Start header ok ?
begin
p := Pos('*', parseS);
if (p <> 0) and (Length(parseS) >= p + 2) then
// Assumes a checksum in packet
begin
xorSum := Ord(parseS[2]);
for i := 3 to p - 1 do // Calculate checksum
xorSum := xorSum xor Ord(parseS[i]);
xorStr := IntToHex(xorSum, 2);
if (UpperCase(xorStr) <> Copy(parseS, p + 1, 2)) then // Checksum ok ?
Result := 3; // Wrong checksum
end
else
Result := 2; // No checksum
end
else
Result := 1; // Wrong header
end;
any
NMEA 0183 packet, provided they have a checksum (which is not mandatory) - LU RD 2012-04-13 12:24
You should do your own buffering. That gives you more flexibility as to what delimits a message. For example, if you delimit messages with a carriage-return (#13), you can do:
var Buf: string;
procedure DoSomething(const Str: String);
begin
Memo.Text := Memo.Text + Str;
end;
procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
var I: Integer;
begin
ComPort.ReadStr(CPort.Str, Count);
Buf := Buf + CPort.Str;
for i := 1 to Length(Buf) do
if Buf[i] = #13 then
begin
DoSomething(Copy(Buf, 1, i));
Delete(Buf, 1, i+1);
Break;
end;
end;
If you are using the ComPort library from Dejan Crnila, you should have a look at the TComDataPacket
component, especially its OnPacket
and OnCustomStop
events.
Update: Here is a code example. Assuming the start string is always the same and the end string begins with ',T*' followed by two characters (perhaps a CRC code). If that assumption is wrong, the stop logic has to be adjusted.
procedure TForm136.ComDataPacketCustomStop(Sender: TObject; const Str: string;
var Pos: Integer);
begin
Pos := System.Pos(',T*', Str);
if (Pos > 0) then begin
Inc(Pos, 4);
if Pos <= Length(Str) then Exit;
end;
Pos := -1;
end;
procedure TForm136.FormCreate(Sender: TObject);
begin
ComDataPacket.ComPort := ComPort;
ComDataPacket.StartString := '$HEHDT,';
ComDataPacket.OnCustomStop := ComDataPacketCustomStop;
end;