Cleaned up the frame.ts by splitting payload parsing to subpackages

This commit is contained in:
2026-03-20 10:38:36 +01:00
parent 1aa8eb363f
commit 75e31c2008
26 changed files with 2695 additions and 2429 deletions

197
src/payload.telemetry.ts Normal file
View File

@@ -0,0 +1,197 @@
import { FieldType, type Segment } from "@hamradio/packet";
import {
DataType,
type Payload,
type TelemetryBitSensePayload,
type TelemetryCoefficientsPayload,
type TelemetryDataPayload,
type TelemetryParameterPayload,
type TelemetryUnitPayload
} from "./frame.types";
export const decodeTelemetryPayload = (
raw: string,
withStructure: boolean = false
): {
payload: Payload | null;
segment?: Segment[];
} => {
try {
if (raw.length < 2) return { payload: null };
const rest = raw.substring(1).trim();
if (!rest) return { payload: null };
const segments: Segment[] = withStructure ? [] : [];
// Telemetry data: convention used here: starts with '#' then sequence then analogs and digital
if (rest.startsWith("#")) {
const parts = rest.substring(1).trim().split(/\s+/);
const seq = parseInt(parts[0], 10);
let analog: number[] = [];
let digital = 0;
if (parts.length >= 2) {
// analogs as comma separated
analog = parts[1].split(",").map((v) => parseFloat(v));
}
if (parts.length >= 3) {
digital = parseInt(parts[2], 10);
}
if (withStructure) {
segments.push({
name: "telemetry sequence",
data: new TextEncoder().encode(String(seq)).buffer,
isString: true,
fields: [
{
type: FieldType.STRING,
name: "sequence",
length: String(seq).length
}
]
});
segments.push({
name: "telemetry analog",
data: new TextEncoder().encode(parts[1] || "").buffer,
isString: true,
fields: [
{
type: FieldType.STRING,
name: "analogs",
length: (parts[1] || "").length
}
]
});
segments.push({
name: "telemetry digital",
data: new TextEncoder().encode(String(digital)).buffer,
isString: true,
fields: [
{
type: FieldType.STRING,
name: "digital",
length: String(digital).length
}
]
});
}
const payload: TelemetryDataPayload = {
type: DataType.TelemetryData,
variant: "data",
sequence: isNaN(seq) ? 0 : seq,
analog,
digital: isNaN(digital) ? 0 : digital
};
if (withStructure) return { payload, segment: segments };
return { payload };
}
// Telemetry parameters: 'PARAM' keyword
if (/^PARAM/i.test(rest)) {
const after = rest.replace(/^PARAM\s*/i, "");
const names = after.split(/[,\s]+/).filter(Boolean);
if (withStructure) {
segments.push({
name: "telemetry parameters",
data: new TextEncoder().encode(after).buffer,
isString: true,
fields: [{ type: FieldType.STRING, name: "names", length: after.length }]
});
}
const payload: TelemetryParameterPayload = {
type: DataType.TelemetryData,
variant: "parameters",
names
};
if (withStructure) return { payload, segment: segments };
return { payload };
}
// Telemetry units: 'UNIT'
if (/^UNIT/i.test(rest)) {
const after = rest.replace(/^UNIT\s*/i, "");
const units = after.split(/[,\s]+/).filter(Boolean);
if (withStructure) {
segments.push({
name: "telemetry units",
data: new TextEncoder().encode(after).buffer,
isString: true,
fields: [{ type: FieldType.STRING, name: "units", length: after.length }]
});
}
const payload: TelemetryUnitPayload = {
type: DataType.TelemetryData,
variant: "unit",
units
};
if (withStructure) return { payload, segment: segments };
return { payload };
}
// Telemetry coefficients: 'COEFF' a:,b:,c:
if (/^COEFF/i.test(rest)) {
const after = rest.replace(/^COEFF\s*/i, "");
const aMatch = after.match(/A:([^\s;]+)/i);
const bMatch = after.match(/B:([^\s;]+)/i);
const cMatch = after.match(/C:([^\s;]+)/i);
const parseList = (s?: string) => (s ? s.split(",").map((v) => parseFloat(v)) : []);
const coefficients = {
a: parseList(aMatch?.[1]),
b: parseList(bMatch?.[1]),
c: parseList(cMatch?.[1])
};
if (withStructure) {
segments.push({
name: "telemetry coefficients",
data: new TextEncoder().encode(after).buffer,
isString: true,
fields: [{ type: FieldType.STRING, name: "coeffs", length: after.length }]
});
}
const payload: TelemetryCoefficientsPayload = {
type: DataType.TelemetryData,
variant: "coefficients",
coefficients
};
if (withStructure) return { payload, segment: segments };
return { payload };
}
// Telemetry bitsense/project: 'BITS' <number> [project]
if (/^BITS?/i.test(rest)) {
const parts = rest.split(/\s+/).slice(1);
const sense = parts.length > 0 ? parseInt(parts[0], 10) : 0;
const projectName = parts.length > 1 ? parts.slice(1).join(" ") : undefined;
if (withStructure) {
segments.push({
name: "telemetry bitsense",
data: new TextEncoder().encode(rest).buffer,
isString: true,
fields: [{ type: FieldType.STRING, name: "bitsense", length: rest.length }]
});
}
const payload: TelemetryBitSensePayload = {
type: DataType.TelemetryData,
variant: "bitsense",
sense: isNaN(sense) ? 0 : sense,
...(projectName ? { projectName } : {})
};
if (withStructure) return { payload, segment: segments };
return { payload };
}
return { payload: null };
} catch {
return { payload: null };
}
};
export default decodeTelemetryPayload;