Update:
I have improved my packet dissector so that it can handle nybble offsets into packets. The old one could not handle some fields that did not start on byte boundaries. This hid some of the more obscure data fields. While none of the new fields are needed by WU, they may be useful if you are saving data to your own MySQL database for later analysis.
The new SDP packet dissector:
UNIT sssSDP;
INTERFACE
TYPE
TDTBytes =
(
DTYr,
DTMo,
DTD,
DTH,
DTM
);
TBCDDateTime = ARRAY[TDTBytes] OF Byte;
TRainArray = ARRAY[0..11] OF Byte;
TWindHist = ARRAY[0..3] OF Byte;
TKWord = ARRAY[0..1] OF Byte;
TRainElement = ARRAY[0..3] OF Byte;
TBP = ARRAY[0..1] OF Byte;
// self-reading record - conversion done elsewhere
TSDPRecA = PACKED RECORD
PRIVATE
Flags: ARRAY[0..196] OF Byte; // payload goes here
FUNCTION GetBCDDateTime(CONST Index : Integer) : TBCDDateTime;
FUNCTION GetBP(CONST Index : Integer) : TBP;
FUNCTION GetByte(CONST Index : Integer) : Byte;
FUNCTION GetRainArray(CONST Index : Integer) : TRainArray;
FUNCTION GetRainElement(CONST Index : Integer) : TRainElement;
FUNCTION GetWindHist(CONST Index : Integer) : TWindHist;
FUNCTION GetWord(CONST Index : Integer) : TKWord;
PROCEDURE FillResult(VAR R; CONST BuffSize, aIndex : Integer);
PUBLIC
PROCEDURE Dump; // debug only
PROCEDURE WriteProp(VAR F : TextFile; CONST PropName : STRING; VAR Prop; PropSize : Byte); // debug only
// index is $OOOOOOLL where O=field offset in nybbles into payload and LL= length in nybbles of the field
// Property getters shift all nybbles in a field to the rightmost position (on odd length fields)
PROPERTY PacketID: Byte index $0002 Read GetByte;
PROPERTY Status: TKWord index $0603 Read GetWord;
PROPERTY DTMaxIT: TBCDDateTime index $090a Read GetBCDDateTime;
PROPERTY DtMinIT: TBCDDateTime index $130a Read GetBCDDateTime;
PROPERTY MaxIT: TKWord index $1d03 Read GetWord;
PROPERTY MinIT: TKWord index $2203 Read GetWord;
PROPERTY CurrIT: TKWord index $2703 Read GetWord;
PROPERTY DtMaxOT: TBCDDateTime index $2d0a Read GetBCDDateTime;
PROPERTY DtMinOT: TBCDDateTime index $370a Read GetBCDDateTime;
PROPERTY MaxOT: TKWord index $4103 Read GetWord;
PROPERTY MinOT: TKWord index $4603 Read GetWord;
PROPERTY CurrOT: TKWord index $4b03 Read GetWord;
PROPERTY DtUnknown1: TBCDDateTime index $510a Read GetBCDDateTime;
PROPERTY DtUnknown2: TBCDDateTime index $5b0a Read GetBCDDateTime;
PROPERTY DtUnknown3: TBCDDateTime index $650a Read GetBCDDateTime; // invalid date in test case
PROPERTY CurrOT2: TKWord index $6403 Read GetWord;
PROPERTY Stat2: Byte index $7202 Read GetByte;
PROPERTY DtMaxIH: TBCDDateTime index $740a Read GetBCDDateTime;
PROPERTY DtMinIH: TBCDDateTime index $7e0a Read GetBCDDateTime;
PROPERTY MaxIH: Byte index $8802 Read GetByte;
PROPERTY MinIH: Byte index $8a02 Read GetByte;
PROPERTY CurIH: Byte index $8c02 Read GetByte;
PROPERTY DtMaxOH: TBCDDateTime index $8e0a Read GetBCDDateTime;
PROPERTY DtMinOH: TBCDDateTime index $980a Read GetBCDDateTime; // this may have wrong offset - invalid date in test case
PROPERTY MaxOH: Byte index $a202 Read GetByte;
PROPERTY MinOH: Byte index $a402 Read GetByte;
PROPERTY CurrOH: Byte index $a602 Read GetByte;
PROPERTY DtUnknown4: TBCDDateTime index $d40a Read GetBCDDateTime;
PROPERTY DtLast1HrRain: TBCDDateTime index $ea0a Read GetBCDDateTime;
PROPERTY DtLastRainReset: TBCDDateTime index $1010a Read GetBCDDateTime;
PROPERTY RainHistory: TRainArray index $10b17 Read GetRainArray; // break elements into separate properties?
PROPERTY RFTotal: TRainElement index $11208 Read GetRainElement;
PROPERTY AvgWind: TKWord index $12204 Read GetWord;
PROPERTY WindDirHist: TWindHist index $12a06 Read GetWindHist;
PROPERTY DtMaxGust: TBCDDateTime index $1300a Read GetBCDDateTime;
PROPERTY MaxGust: TKWord index $13a04 Read GetWord;
PROPERTY CurrGust: TKWord index $14004 Read GetWord;
PROPERTY WindStat: TKWord index $14404 Read GetWord;
PROPERTY WindDirHist2: TWindHist index $14806 Read GetWindHist;
PROPERTY CurrBP: TBP index $14f04 Read GetBP;
PROPERTY BPDelta: TWindHist index $15306 Read GetWindHist; // this is a guess
PROPERTY MinBp: TBP index $15904 Read GetBP;
PROPERTY MaxBp: TBP index $16304 Read GetBP;
PROPERTY DtUnknown5: TBCDDateTime index $16c0a Read GetBCDDateTime; // bp related?
PROPERTY DtUnknown6: TBCDDateTime index $1760a Read GetBCDDateTime; // bp related?
PROPERTY CheckSum: TKword index $18604 Read GetWord;
END;
IMPLEMENTATION
USES
dgLib,
SysUtils;
{ TSDPRecA }
// debug only - not typically used
PROCEDURE TSDPRecA.Dump;
VAR
Dt : TBCDDateTime;
R : TRainArray;
Wh : TWindHist;
W : TKWord;
B : Byte;
F : TextFile;
BP : TBP;
BEGIN
AssignFile(F, 'junk.txt');
Rewrite(F);
B := PacketID;
WriteProp(F, 'PacketID', B, SizeOf(Byte)); // Byte
W := Status;
WriteProp(F, 'Status', W, SizeOf(TKWord)); // TTemp
Dt := DTMaxIT;
WriteProp(F, 'DTMaxIT', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtMinIT;
WriteProp(F, 'DtMinIT', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
W := MaxIT;
WriteProp(F, 'MaxIT', W, SizeOf(TKWord)); // TKWord
W := MinIT;
WriteProp(F, 'MinIT', W, SizeOf(TKWord)); // TKWord
W := CurrIT;
WriteProp(F, 'CurrIT', W, SizeOf(TKWord)); // TKWord
Dt := DtMinOT;
WriteProp(F, 'DtMinOT', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
W := MaxOT;
WriteProp(F, 'MaxOT', W, SizeOf(TKWord)); // TKWord
W := MinOT;
WriteProp(F, 'MinOT', W, SizeOf(TKWord)); // TKWord
W := CurrOT;
WriteProp(F, 'CurrOT', W, SizeOf(TKWord)); // TKWord
Dt := DtUnknown1;
WriteProp(F, 'DtUnknown1', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtUnknown2;
WriteProp(F, 'DtUnknown2', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtUnknown3;
WriteProp(F, 'DtUnknown3', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
W := CurrOT2;
WriteProp(F, 'CurrOT2', W, SizeOf(TKWord)); // TKWord
B := Stat2;
WriteProp(F, 'Stat2', B, SizeOf(Byte)); // Byte
Dt := DtMaxIH;
WriteProp(F, 'DtMaxIH', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtMinIH;
WriteProp(F, 'DtMinIH', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
B := MaxIH;
WriteProp(F, 'MaxIH', B, SizeOf(Byte)); // Byte
B := MinIH;
WriteProp(F, 'MinIH', B, SizeOf(Byte)); // Byte
Dt := DtMaxOH;
WriteProp(F, 'DtMaxOH', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtMinOH;
WriteProp(F, 'DtMinOH', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
B := MaxOH;
WriteProp(F, 'MaxOH', B, SizeOf(Byte)); // Byte
B := MinOH;
WriteProp(F, 'MinOH', B, SizeOf(Byte)); // Byte
B := CurrOH;
WriteProp(F, 'CurrOH', B, SizeOf(Byte)); // Byte
Dt := DtUnknown4;
WriteProp(F, 'DtUnknown4', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtLast1HrRain;
WriteProp(F, 'DtLast1HrRain', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtLastRainReset;
WriteProp(F, 'DtLastRainReset', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
R := RainHistory;
WriteProp(F, 'RainHistory', R, SizeOf(TRainArray)); // TRainArray
W := AvgWind;
WriteProp(F, 'AvgWind', W, SizeOf(TKWord)); // TKWord
Wh := WindDirHist;
WriteProp(F, 'WindDirHist', Wh, SizeOf(TWindHist)); // TWindHist
Dt := DtMaxGust;
WriteProp(F, 'DtMaxGust', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
W := MaxGust;
WriteProp(F, 'MaxGust', W, SizeOf(TKWord)); // TKWord
W := CurrGust;
WriteProp(F, 'CurrGust', W, SizeOf(TKWord)); // TKWord
W := WindStat;
WriteProp(F, 'WindStat', W, SizeOf(TKWord)); // TKWord
Wh := WindDirHist2;
WriteProp(F, 'WindDirHist2', Wh, SizeOf(TWindHist)); // TWindHist
BP := CurrBP;
WriteProp(F, 'CurrBP', BP, SizeOf(TBP)); // TKWord
Wh := BPDelta;
WriteProp(F, 'BPDelta', Wh, SizeOf(TWindHist)); // TWindHist
BP := MinBp;
WriteProp(F, 'MinBp', BP, SizeOf(TBP)); // TKWord
BP := MaxBp;
WriteProp(F, 'MaxBp', BP, SizeOf(TBP)); // TBP
Dt := DtUnknown5;
WriteProp(F, 'DtUnknown5', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
Dt := DtUnknown6;
WriteProp(F, 'DtUnknown6', Dt, SizeOf(TBCDDateTime)); // TBCDDateTime
W := CheckSum;
WriteProp(F, 'CheckSum', W, SizeOf(TKword)); // TKword
CloseFile(F);
END;
// fill output buffer R with the contents of the payload, at the given offset
// and expected field length
PROCEDURE TSDPRecA.FillResult(VAR R; CONST BuffSize, aIndex : Integer);
VAR
I,
NybOffset : Integer;
NrNybs : Integer;
Mask : Integer;
INyb : Integer;
Nt,
B : Byte;
Buff : TByteArray Absolute R;
IBuff : Integer;
BEGIN
NrNybs := aIndex AND $FF;
NybOffset := aIndex SHR 8;
IF (NrNybs SHR 1) > Buffsize THEN
RAISE Exception.Create('Buffer Overrun Detected');
FillChar(Buff, BuffSize, 0); // clear the buffer - set it to all zeros
// field is located completely on natural byte boundary, so just copy bytes
IF (NOT Odd(NybOffset)) AND (NOT Odd(NrNybs)) THEN BEGIN
FOR I := 0 TO (NrNybs DIV 2) - 1 DO
Buff[I] := Flags[(NybOffset DIV 2) + I];
EXIT;
END;
// field was not on byte boundary, so do nybble copy
FOR INyb := NybOffset TO NybOffset + NrNybs - 1 DO BEGIN
// get the nybble we want from the flag array and put it into Nt
B := Flags[INyb DIV 2];
// calculate where we want to put the nybbles in the output buffer
IBuff := (INyb - NybOffset) DIV 2;
IF Odd(INyb) THEN BEGIN
Nt := (B AND $F); // get the right nybble
Buff[IBuff] := (Buff[IBuff] AND $F) OR (Nt SHL 4); // or it into the proper place in the output buffer
END
ELSE BEGIN
Nt := (B AND $F0) SHR 4; // get the left nybble
Buff[IBuff] := (Buff[IBuff] AND $F0) OR Nt;
END;
END;
// shift all nybbles 4 bits to right, if there is dead space on the far right of the output buffer
IF Odd(NrNybs) THEN BEGIN
// go backwards through the output buffer
FOR INyb := NrNybs DIV 2 DOWNTO 1 DO BEGIN
Buff[INyb] := Buff[INyb] SHR 4; // shift it right one nybble
B := (Buff[INyb - 1] AND $0F) SHL 4; // get neighbor low nybble on left and shift it to high nybble
Buff[INyb] := Buff[INyb] OR B; // move it to current byte
Buff[INyb - 1] := Buff[INyb - 1] SHR 4; // shift neighbors upper nybble into lower nybble
END; // shampoo, rinse and repeat
END;
END;
FUNCTION TSDPRecA.GetBCDDateTime(CONST Index : Integer) : TBCDDateTime;
BEGIN
FillResult(Result, SizeOf(Result), Index);
END;
FUNCTION TSDPRecA.GetBP(CONST Index : Integer) : TBP;
BEGIN
FillResult(Result, SizeOf(Result), Index);
END;
FUNCTION TSDPRecA.GetRainArray(CONST Index : Integer) : TRainArray;
BEGIN
FillResult(Result, SizeOf(Result), Index);
END;
FUNCTION TSDPRecA.GetRainElement(CONST Index : Integer) : TRainElement;
BEGIN
FillResult(Result, SizeOf(Result), Index);
END;
FUNCTION TSDPRecA.GetByte(CONST Index : Integer) : Byte;
BEGIN
FillResult(Result, SizeOf(Result), Index);
END;
FUNCTION TSDPRecA.GetWindHist(CONST Index : Integer) : TWindHist;
BEGIN
FillResult(Result, SizeOf(Result), Index);
END;
FUNCTION TSDPRecA.GetWord(CONST Index : Integer) : TKword;
BEGIN
FillResult(Result, SizeOf(Result), Index);
END;
PROCEDURE TSDPRecA.WriteProp(VAR F : TextFile; CONST PropName : STRING; VAR Prop; PropSize : Byte);
VAR
B : TByteArray Absolute Prop;
I : Integer;
BEGIN
Write(F, PadTrim(PropName, 20));
FOR I := 0 TO PropSize - 1 DO
Write(F, IntToHex(B[I], 2) + ' ');
WRITELN(F);
END;
END.