Added support for !DAO! marker parsing

This commit is contained in:
2026-03-20 13:53:36 +01:00
parent c7c54984ba
commit 4502f9902b
3 changed files with 410 additions and 181 deletions

View File

@@ -3,8 +3,8 @@ import { describe, expect, it } from "vitest";
import { Frame } from "../src/frame";
import { DataType, type ObjectPayload, type PositionPayload } from "../src/frame.types";
import { feetToMeters, milesToMeters } from "../src/parser";
import { decodeTelemetry } from "../src/payload.extras";
import { base91ToNumber, feetToMeters, knotsToKmh, milesToMeters } from "../src/parser";
import { decodeDAO, decodeTelemetry } from "../src/payload.extras";
describe("APRS extras test vectors", () => {
it("parses altitude token in the beginning of a comment and emits structure", () => {
@@ -174,6 +174,50 @@ describe("APRS extras test vectors", () => {
const commentIndex = (commentSeg!.fields ?? []).findIndex((f) => f.name === "comment");
expect(commentIndex).toBeGreaterThan(altitudeIndex); // Comment comes after altitude
});
it("parses DAO token and emits structure", () => {
const raw = "N0CALL-7>APLT00,WIDE1-1,QB1N4,qAO,N0CALL-10:!5140.06N/00615.91E[360/028/A=000085 !wrt!";
const frame = Frame.fromString(raw);
const res = frame.decode(true) as { payload: PositionPayload | null; structure: Dissected };
const { payload } = res;
expect(payload).not.toBeNull();
expect(payload!.type).toBe(DataType.PositionNoTimestampNoMessaging);
expect(payload!.position.dao!).toBeDefined();
expect(payload!.position.dao!.datum_id).toBe("W");
});
});
describe("decodeDAO", () => {
it("decodes valid DAO token with WGS84 datum", () => {
const dao = decodeDAO("W84");
expect(dao).not.toBeNull();
expect(dao!.datum_id).toBe("W");
expect(dao!.resolution).toBe(knotsToKmh(1));
expect(dao!.latitude).toBeCloseTo((8 * 0.01) / 60, 6);
expect(dao!.longitude).toBeCloseTo((4 * 0.01) / 60, 6);
});
it("decodes valid DAO base91 token", () => {
const dao = decodeDAO("wrt");
expect(dao).not.toBeNull();
expect(dao!.datum_id).toBe("W");
expect(dao!.resolution).toBe(knotsToKmh(0.1));
expect(dao!.latitude).toBeCloseTo((base91ToNumber("r") * 0.01) / 60, 6);
expect(dao!.longitude).toBeCloseTo((base91ToNumber("t") * 0.01) / 60, 6);
});
it("decodes valid DAO only token", () => {
const dao = decodeDAO("! ");
expect(dao).not.toBeNull();
expect(dao!.datum_id).toBe("!");
});
it("returns undefined for invalid DAO token", () => {
expect(decodeDAO("invalid")).toBeUndefined();
expect(decodeDAO("")).toBeUndefined();
expect(decodeDAO("ab")).toBeUndefined();
});
});
describe("decodeTelemetry", () => {
@@ -238,4 +282,18 @@ describe("decodeTelemetry", () => {
expect(payload!.position).toBeDefined();
expect(payload!.position.comment).toBe("LoRa APRS Tracker");
});
it("decodes composite test vector with altitude and telemetry", () => {
const raw = "N0CALL-11>APLRFT,qAR,N0CALL-10:!\\45;<P(6y>HIGLoRa APRS Tracker|$T%R#`| on air/A=000012!";
const frame = Frame.fromString(raw);
const { payload, structure } = frame.decode(true) as { payload: ObjectPayload | null; structure: Dissected };
// console.log(structure[structure.length - 1]); // Log the last segment for debugging
expect(payload).not.toBeNull();
expect(payload!.position).toBeDefined();
expect(payload!.position.altitude).toBeCloseTo(feetToMeters(12), 3);
//expect(payload!.position.comment).toBe("LoRa APRS Tracker on air");
expect(structure[structure.length - 1].fields.filter((s) => s.name === "comment").length).toBe(3);
});
});