Refactored extras field parsing
This commit is contained in:
257
src/frame.ts
257
src/frame.ts
@@ -764,32 +764,60 @@ export class Frame implements IFrame {
|
||||
|
||||
const extras: Partial<Extras> = {};
|
||||
const fields: Field[] = [];
|
||||
const beforeFields: Field[] = [];
|
||||
let altitudeOffset: number | undefined = undefined;
|
||||
let altitudeFields: Field[] = [];
|
||||
let commentOffset: number = 0;
|
||||
let commentBefore: string | undefined = undefined;
|
||||
|
||||
// Process successive 7-byte data extensions at the start of the comment.
|
||||
let ext = comment.trimStart();
|
||||
while (ext.length >= 7) {
|
||||
// We first process the altitude marker, because it may appear anywhere
|
||||
// in the comment and we want to extract it and its value before
|
||||
// processing other tokens that may be present.
|
||||
//
|
||||
// /A=NNNNNN -> altitude in feet (6 digits)
|
||||
// /A=-NNNNN -> altitude in feet with leading minus for negative altitudes (5 digits)
|
||||
const altMatch = ext.match(/\/A=(-\d{5}|\d{6})/);
|
||||
if (altMatch) {
|
||||
if (altitudeOffset === undefined && altMatch) {
|
||||
const altitude = feetToMeters(parseInt(altMatch[1], 10)); // feet to meters
|
||||
if (isNaN(altitude)) {
|
||||
break; // Invalid altitude format, stop parsing extras
|
||||
}
|
||||
extras.altitude = altitude;
|
||||
|
||||
// Keep track of where the altitude token appears in the comment for structure purposes.
|
||||
altitudeOffset = comment.indexOf(altMatch[0]);
|
||||
|
||||
if (withStructure) {
|
||||
fields.push({ type: FieldType.STRING, name: "altitude marker", length: 2 });
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "altitude value",
|
||||
length: altMatch[1].length,
|
||||
value: altitude.toFixed(3) + "m"
|
||||
});
|
||||
altitudeFields = [
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "altitude marker",
|
||||
data: new TextEncoder().encode("/A=").buffer,
|
||||
length: 3
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "altitude",
|
||||
data: new TextEncoder().encode(altMatch[1]).buffer,
|
||||
length: altMatch[1].length,
|
||||
value: altitude.toFixed(3) + "m"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// remove altitude token from comment and advance ext for further parsing
|
||||
comment = comment.replace(altMatch[0], "").trimStart();
|
||||
if (altitudeOffset > 0) {
|
||||
// Splice the comment into "before" and "after" around the altitude token.
|
||||
commentBefore = comment.substring(0, altitudeOffset).trimEnd();
|
||||
comment = comment.substring(altitudeOffset + altMatch[0].length).trimStart();
|
||||
ext = commentBefore + comment; // Update ext to reflect the new comment with altitude token removed
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove altitude token from ext and advance ext for further parsing
|
||||
commentOffset += 7;
|
||||
ext = ext.replace(altMatch[0], "").trimStart();
|
||||
|
||||
continue;
|
||||
@@ -801,17 +829,24 @@ export class Frame implements IFrame {
|
||||
if (/^\d{4}$/.test(r)) {
|
||||
extras.range = milesToMeters(parseInt(r, 10)) / 1000.0; // Convert to kilometers
|
||||
if (withStructure) {
|
||||
fields.push({ type: FieldType.STRING, name: "RNG marker", length: 3, value: "RNG" });
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "range (rrrr)",
|
||||
length: 4,
|
||||
value: extras.range.toString() + "km"
|
||||
});
|
||||
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "range marker",
|
||||
value: "RNG",
|
||||
length: 3
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "range (rrrr)",
|
||||
length: 4,
|
||||
value: extras.range.toString() + "km"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// remove range token from comment and advance ext for further parsing
|
||||
comment = comment.substring(7).trimStart();
|
||||
// remove range token from ext and advance ext for further parsing
|
||||
commentOffset += 7;
|
||||
ext = ext.substring(7).trimStart();
|
||||
|
||||
continue;
|
||||
@@ -852,40 +887,42 @@ export class Frame implements IFrame {
|
||||
};
|
||||
|
||||
if (withStructure) {
|
||||
fields.push({ type: FieldType.STRING, name: "PHG marker", length: 3, value: "PHG" });
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "power (p)",
|
||||
length: 1,
|
||||
value: powerWatts !== undefined ? powerWatts.toString() + "W" : undefined
|
||||
});
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "height (h)",
|
||||
length: 1,
|
||||
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
||||
});
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "gain (g)",
|
||||
length: 1,
|
||||
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
||||
});
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "directivity (d)",
|
||||
length: 1,
|
||||
value:
|
||||
directivity !== undefined
|
||||
? typeof directivity === "number"
|
||||
? directivity.toString() + "°"
|
||||
: directivity
|
||||
: undefined
|
||||
});
|
||||
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||
{ type: FieldType.STRING, name: "PHG marker", length: 3, value: "PHG" },
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "power (p)",
|
||||
length: 1,
|
||||
value: powerWatts !== undefined ? powerWatts.toString() + "W" : undefined
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "height (h)",
|
||||
length: 1,
|
||||
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "gain (g)",
|
||||
length: 1,
|
||||
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "directivity (d)",
|
||||
length: 1,
|
||||
value:
|
||||
directivity !== undefined
|
||||
? typeof directivity === "number"
|
||||
? directivity.toString() + "°"
|
||||
: directivity
|
||||
: undefined
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// remove PHG token from comment and advance ext for further parsing
|
||||
comment = comment.substring(7).trimStart();
|
||||
// remove PHG token from ext and advance ext for further parsing
|
||||
commentOffset += 7;
|
||||
ext = ext.substring(7).trimStart();
|
||||
|
||||
continue;
|
||||
@@ -934,40 +971,42 @@ export class Frame implements IFrame {
|
||||
};
|
||||
|
||||
if (withStructure) {
|
||||
fields.push({ type: FieldType.STRING, name: "DFS marker", length: 3, value: "DFS" });
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "strength (s)",
|
||||
length: 1,
|
||||
value: strength !== undefined ? strength.toString() : undefined
|
||||
});
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "height (h)",
|
||||
length: 1,
|
||||
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
||||
});
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "gain (g)",
|
||||
length: 1,
|
||||
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
||||
});
|
||||
fields.push({
|
||||
type: FieldType.STRING,
|
||||
name: "directivity (d)",
|
||||
length: 1,
|
||||
value:
|
||||
directivity !== undefined
|
||||
? typeof directivity === "number"
|
||||
? directivity.toString() + "°"
|
||||
: directivity
|
||||
: undefined
|
||||
});
|
||||
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||
{ type: FieldType.STRING, name: "DFS marker", length: 3, value: "DFS" },
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "strength (s)",
|
||||
length: 1,
|
||||
value: strength !== undefined ? strength.toString() : undefined
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "height (h)",
|
||||
length: 1,
|
||||
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "gain (g)",
|
||||
length: 1,
|
||||
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
||||
},
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "directivity (d)",
|
||||
length: 1,
|
||||
value:
|
||||
directivity !== undefined
|
||||
? typeof directivity === "number"
|
||||
? directivity.toString() + "°"
|
||||
: directivity
|
||||
: undefined
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// remove DFS token from comment and advance ext for further parsing
|
||||
comment = comment.substring(7).trimStart();
|
||||
// remove DFS token from ext and advance ext for further parsing
|
||||
commentOffset += 7;
|
||||
ext = ext.substring(7).trimStart();
|
||||
|
||||
continue;
|
||||
@@ -981,9 +1020,11 @@ export class Frame implements IFrame {
|
||||
extras.spd = knotsToKmh(parseInt(speedStr, 10));
|
||||
|
||||
if (withStructure) {
|
||||
fields.push({ type: FieldType.STRING, name: "course", length: 3, value: extras.cse.toString() + "°" });
|
||||
fields.push({ type: FieldType.CHAR, name: "marker", length: 1, value: "/" });
|
||||
fields.push({ type: FieldType.STRING, name: "speed", length: 3, value: extras.spd.toString() + " km/h" });
|
||||
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||
{ type: FieldType.STRING, name: "course", length: 3, value: extras.cse.toString() + "°" },
|
||||
{ type: FieldType.CHAR, name: "marker", length: 1, value: "/" },
|
||||
{ type: FieldType.STRING, name: "speed", length: 3, value: extras.spd.toString() + " km/h" }
|
||||
);
|
||||
}
|
||||
|
||||
// remove course/speed token from comment and advance ext for further parsing
|
||||
@@ -1004,14 +1045,16 @@ export class Frame implements IFrame {
|
||||
extras.dfs.strength = dfStrength;
|
||||
|
||||
if (withStructure) {
|
||||
fields.push({ type: FieldType.STRING, name: "DF marker", length: 1, value: "/" });
|
||||
fields.push({ type: FieldType.STRING, name: "bearing", length: 3, value: dfBearing.toString() + "°" });
|
||||
fields.push({ type: FieldType.CHAR, name: "separator", length: 1, value: "/" });
|
||||
fields.push({ type: FieldType.STRING, name: "strength", length: 3, value: dfStrength.toString() });
|
||||
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||
{ type: FieldType.STRING, name: "DF marker", length: 1, value: "/" },
|
||||
{ type: FieldType.STRING, name: "bearing", length: 3, value: dfBearing.toString() + "°" },
|
||||
{ type: FieldType.CHAR, name: "separator", length: 1, value: "/" },
|
||||
{ type: FieldType.STRING, name: "strength", length: 3, value: dfStrength.toString() }
|
||||
);
|
||||
}
|
||||
|
||||
// remove DF token from comment and advance ext for further parsing
|
||||
comment = comment.substring(8).trimStart();
|
||||
// remove DF token from ext and advance ext for further parsing
|
||||
commentOffset += 8;
|
||||
ext = ext.substring(8).trimStart();
|
||||
|
||||
continue;
|
||||
@@ -1024,8 +1067,36 @@ export class Frame implements IFrame {
|
||||
break;
|
||||
}
|
||||
|
||||
// Export comment with extras fields removed, if any were parsed.
|
||||
comment = comment.substring(commentOffset).trimStart();
|
||||
extras.comment = comment;
|
||||
extras.fields = fields.length > 0 ? fields : undefined;
|
||||
|
||||
if (withStructure) {
|
||||
const commentBeforeFields: Field[] = commentBefore
|
||||
? [
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "comment",
|
||||
value: commentBefore,
|
||||
length: commentBefore.length
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
const commentFields: Field[] = comment
|
||||
? [
|
||||
{
|
||||
type: FieldType.STRING,
|
||||
name: "comment",
|
||||
value: comment,
|
||||
length: comment.length
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
// Insert the altitude fields at the correct position in the comment section based on where the altitude token was located in the original comment. If there was no altitude token, put all fields at the start of the comment section.
|
||||
extras.fields = [...commentBeforeFields, ...beforeFields, ...altitudeFields, ...fields, ...commentFields];
|
||||
}
|
||||
|
||||
return extras as Extras;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ describe("APRS extras test vectors", () => {
|
||||
const commentSeg = structure.find((s) => /comment/i.test(String(s.name))) as Segment | undefined;
|
||||
expect(commentSeg).toBeDefined();
|
||||
const fieldsRNG = (commentSeg!.fields ?? []) as Field[];
|
||||
const hasRNG = fieldsRNG.some((f) => f.name === "RNG marker");
|
||||
const hasRNG = fieldsRNG.some((f) => f.name === "range marker");
|
||||
expect(hasRNG).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -950,7 +950,7 @@ describe("Packet dissection with sections", () => {
|
||||
const commentSection = result.structure?.find((s) => s.name === "comment");
|
||||
expect(commentSection).toBeDefined();
|
||||
expect(commentSection?.data?.byteLength).toBe("Test message".length);
|
||||
expect(commentSection?.fields?.[0]?.name).toBe("text");
|
||||
expect(commentSection?.fields?.[0]?.name).toBe("comment");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user