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 extras: Partial<Extras> = {};
|
||||||
const fields: Field[] = [];
|
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.
|
// Process successive 7-byte data extensions at the start of the comment.
|
||||||
let ext = comment.trimStart();
|
let ext = comment.trimStart();
|
||||||
while (ext.length >= 7) {
|
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=NNNNNN -> altitude in feet (6 digits)
|
||||||
// /A=-NNNNN -> altitude in feet with leading minus for negative altitudes (5 digits)
|
// /A=-NNNNN -> altitude in feet with leading minus for negative altitudes (5 digits)
|
||||||
const altMatch = ext.match(/\/A=(-\d{5}|\d{6})/);
|
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
|
const altitude = feetToMeters(parseInt(altMatch[1], 10)); // feet to meters
|
||||||
if (isNaN(altitude)) {
|
if (isNaN(altitude)) {
|
||||||
break; // Invalid altitude format, stop parsing extras
|
break; // Invalid altitude format, stop parsing extras
|
||||||
}
|
}
|
||||||
extras.altitude = altitude;
|
extras.altitude = altitude;
|
||||||
|
|
||||||
|
// Keep track of where the altitude token appears in the comment for structure purposes.
|
||||||
|
altitudeOffset = comment.indexOf(altMatch[0]);
|
||||||
|
|
||||||
if (withStructure) {
|
if (withStructure) {
|
||||||
fields.push({ type: FieldType.STRING, name: "altitude marker", length: 2 });
|
altitudeFields = [
|
||||||
fields.push({
|
{
|
||||||
type: FieldType.STRING,
|
type: FieldType.STRING,
|
||||||
name: "altitude value",
|
name: "altitude marker",
|
||||||
length: altMatch[1].length,
|
data: new TextEncoder().encode("/A=").buffer,
|
||||||
value: altitude.toFixed(3) + "m"
|
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
|
if (altitudeOffset > 0) {
|
||||||
comment = comment.replace(altMatch[0], "").trimStart();
|
// 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();
|
ext = ext.replace(altMatch[0], "").trimStart();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -801,17 +829,24 @@ export class Frame implements IFrame {
|
|||||||
if (/^\d{4}$/.test(r)) {
|
if (/^\d{4}$/.test(r)) {
|
||||||
extras.range = milesToMeters(parseInt(r, 10)) / 1000.0; // Convert to kilometers
|
extras.range = milesToMeters(parseInt(r, 10)) / 1000.0; // Convert to kilometers
|
||||||
if (withStructure) {
|
if (withStructure) {
|
||||||
fields.push({ type: FieldType.STRING, name: "RNG marker", length: 3, value: "RNG" });
|
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||||
fields.push({
|
{
|
||||||
type: FieldType.STRING,
|
type: FieldType.STRING,
|
||||||
name: "range (rrrr)",
|
name: "range marker",
|
||||||
length: 4,
|
value: "RNG",
|
||||||
value: extras.range.toString() + "km"
|
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
|
// remove range token from ext and advance ext for further parsing
|
||||||
comment = comment.substring(7).trimStart();
|
commentOffset += 7;
|
||||||
ext = ext.substring(7).trimStart();
|
ext = ext.substring(7).trimStart();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -852,40 +887,42 @@ export class Frame implements IFrame {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (withStructure) {
|
if (withStructure) {
|
||||||
fields.push({ type: FieldType.STRING, name: "PHG marker", length: 3, value: "PHG" });
|
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||||
fields.push({
|
{ type: FieldType.STRING, name: "PHG marker", length: 3, value: "PHG" },
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "power (p)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "power (p)",
|
||||||
value: powerWatts !== undefined ? powerWatts.toString() + "W" : undefined
|
length: 1,
|
||||||
});
|
value: powerWatts !== undefined ? powerWatts.toString() + "W" : undefined
|
||||||
fields.push({
|
},
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "height (h)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "height (h)",
|
||||||
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
length: 1,
|
||||||
});
|
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
||||||
fields.push({
|
},
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "gain (g)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "gain (g)",
|
||||||
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
length: 1,
|
||||||
});
|
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
||||||
fields.push({
|
},
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "directivity (d)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "directivity (d)",
|
||||||
value:
|
length: 1,
|
||||||
directivity !== undefined
|
value:
|
||||||
? typeof directivity === "number"
|
directivity !== undefined
|
||||||
? directivity.toString() + "°"
|
? typeof directivity === "number"
|
||||||
: directivity
|
? directivity.toString() + "°"
|
||||||
: undefined
|
: directivity
|
||||||
});
|
: undefined
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove PHG token from comment and advance ext for further parsing
|
// remove PHG token from ext and advance ext for further parsing
|
||||||
comment = comment.substring(7).trimStart();
|
commentOffset += 7;
|
||||||
ext = ext.substring(7).trimStart();
|
ext = ext.substring(7).trimStart();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -934,40 +971,42 @@ export class Frame implements IFrame {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (withStructure) {
|
if (withStructure) {
|
||||||
fields.push({ type: FieldType.STRING, name: "DFS marker", length: 3, value: "DFS" });
|
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||||
fields.push({
|
{ type: FieldType.STRING, name: "DFS marker", length: 3, value: "DFS" },
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "strength (s)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "strength (s)",
|
||||||
value: strength !== undefined ? strength.toString() : undefined
|
length: 1,
|
||||||
});
|
value: strength !== undefined ? strength.toString() : undefined
|
||||||
fields.push({
|
},
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "height (h)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "height (h)",
|
||||||
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
length: 1,
|
||||||
});
|
value: heightMeters !== undefined ? heightMeters.toString() + "m" : undefined
|
||||||
fields.push({
|
},
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "gain (g)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "gain (g)",
|
||||||
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
length: 1,
|
||||||
});
|
value: gainDbi !== undefined ? gainDbi.toString() + "dBi" : undefined
|
||||||
fields.push({
|
},
|
||||||
type: FieldType.STRING,
|
{
|
||||||
name: "directivity (d)",
|
type: FieldType.STRING,
|
||||||
length: 1,
|
name: "directivity (d)",
|
||||||
value:
|
length: 1,
|
||||||
directivity !== undefined
|
value:
|
||||||
? typeof directivity === "number"
|
directivity !== undefined
|
||||||
? directivity.toString() + "°"
|
? typeof directivity === "number"
|
||||||
: directivity
|
? directivity.toString() + "°"
|
||||||
: undefined
|
: directivity
|
||||||
});
|
: undefined
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove DFS token from comment and advance ext for further parsing
|
// remove DFS token from ext and advance ext for further parsing
|
||||||
comment = comment.substring(7).trimStart();
|
commentOffset += 7;
|
||||||
ext = ext.substring(7).trimStart();
|
ext = ext.substring(7).trimStart();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -981,9 +1020,11 @@ export class Frame implements IFrame {
|
|||||||
extras.spd = knotsToKmh(parseInt(speedStr, 10));
|
extras.spd = knotsToKmh(parseInt(speedStr, 10));
|
||||||
|
|
||||||
if (withStructure) {
|
if (withStructure) {
|
||||||
fields.push({ type: FieldType.STRING, name: "course", length: 3, value: extras.cse.toString() + "°" });
|
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||||
fields.push({ type: FieldType.CHAR, name: "marker", length: 1, value: "/" });
|
{ type: FieldType.STRING, name: "course", length: 3, value: extras.cse.toString() + "°" },
|
||||||
fields.push({ type: FieldType.STRING, name: "speed", length: 3, value: extras.spd.toString() + " km/h" });
|
{ 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
|
// 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;
|
extras.dfs.strength = dfStrength;
|
||||||
|
|
||||||
if (withStructure) {
|
if (withStructure) {
|
||||||
fields.push({ type: FieldType.STRING, name: "DF marker", length: 1, value: "/" });
|
(altitudeOffset !== undefined && commentOffset < altitudeOffset ? beforeFields : fields).push(
|
||||||
fields.push({ type: FieldType.STRING, name: "bearing", length: 3, value: dfBearing.toString() + "°" });
|
{ type: FieldType.STRING, name: "DF marker", length: 1, value: "/" },
|
||||||
fields.push({ type: FieldType.CHAR, name: "separator", length: 1, value: "/" });
|
{ type: FieldType.STRING, name: "bearing", length: 3, value: dfBearing.toString() + "°" },
|
||||||
fields.push({ type: FieldType.STRING, name: "strength", length: 3, value: dfStrength.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
|
// remove DF token from ext and advance ext for further parsing
|
||||||
comment = comment.substring(8).trimStart();
|
commentOffset += 8;
|
||||||
ext = ext.substring(8).trimStart();
|
ext = ext.substring(8).trimStart();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -1024,8 +1067,36 @@ export class Frame implements IFrame {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export comment with extras fields removed, if any were parsed.
|
||||||
|
comment = comment.substring(commentOffset).trimStart();
|
||||||
extras.comment = comment;
|
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;
|
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;
|
const commentSeg = structure.find((s) => /comment/i.test(String(s.name))) as Segment | undefined;
|
||||||
expect(commentSeg).toBeDefined();
|
expect(commentSeg).toBeDefined();
|
||||||
const fieldsRNG = (commentSeg!.fields ?? []) as Field[];
|
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);
|
expect(hasRNG).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -950,7 +950,7 @@ describe("Packet dissection with sections", () => {
|
|||||||
const commentSection = result.structure?.find((s) => s.name === "comment");
|
const commentSection = result.structure?.find((s) => s.name === "comment");
|
||||||
expect(commentSection).toBeDefined();
|
expect(commentSection).toBeDefined();
|
||||||
expect(commentSection?.data?.byteLength).toBe("Test message".length);
|
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