198 lines
5.8 KiB
TypeScript
198 lines
5.8 KiB
TypeScript
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;
|