Cleaned up the frame.ts by splitting payload parsing to subpackages
This commit is contained in:
149
src/payload.item.ts
Normal file
149
src/payload.item.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { FieldType, type Segment } from "@hamradio/packet";
|
||||
|
||||
import { DO_NOT_ARCHIVE_MARKER, DataType, type IPosition, type ItemPayload, type Payload } from "./frame.types";
|
||||
import { attachExtras, decodeCommentExtras } from "./payload.extras";
|
||||
import { isCompressedPosition, parseCompressedPosition, parseUncompressedPosition } from "./payload.position";
|
||||
import Timestamp from "./timestamp";
|
||||
|
||||
export const decodeItemPayload = (
|
||||
raw: string,
|
||||
withStructure: boolean = false
|
||||
): {
|
||||
payload: Payload | null;
|
||||
segment?: Segment[];
|
||||
} => {
|
||||
// Item format is similar to Object but name may be 3-9 chars (stored in a 9-char field)
|
||||
// Example: )NNN... where ) is data type, next 9 chars are name, then state char, then timestamp, then position
|
||||
if (raw.length < 12) return { payload: null }; // minimal: 1 + 3 + 1 + 7
|
||||
|
||||
let offset = 1; // skip data type identifier ')'
|
||||
const segment: Segment[] = withStructure ? [] : [];
|
||||
|
||||
// Read 9-char name field (pad/truncate as present)
|
||||
const rawName = raw.substring(offset, offset + 9);
|
||||
const name = rawName.trimEnd();
|
||||
if (withStructure) {
|
||||
segment.push({
|
||||
name: "item name",
|
||||
data: new TextEncoder().encode(rawName).buffer,
|
||||
isString: true,
|
||||
fields: [{ type: FieldType.STRING, name: "name", length: 9 }]
|
||||
});
|
||||
}
|
||||
offset += 9;
|
||||
|
||||
// State character: '*' = alive, '_' = killed
|
||||
const stateChar = raw.charAt(offset);
|
||||
if (stateChar !== "*" && stateChar !== "_") {
|
||||
return { payload: null };
|
||||
}
|
||||
const alive = stateChar === "*";
|
||||
if (withStructure) {
|
||||
segment.push({
|
||||
name: "item state",
|
||||
data: new TextEncoder().encode(stateChar).buffer,
|
||||
isString: true,
|
||||
fields: [
|
||||
{
|
||||
type: FieldType.CHAR,
|
||||
name: "State (* alive, _ killed)",
|
||||
length: 1
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
offset += 1;
|
||||
|
||||
// Timestamp (7 chars)
|
||||
const timeStr = raw.substring(offset, offset + 7);
|
||||
const { timestamp, segment: timestampSection } = Timestamp.fromString(timeStr.substring(offset), withStructure);
|
||||
if (!timestamp) return { payload: null };
|
||||
if (timestampSection) segment.push(timestampSection);
|
||||
offset += 7;
|
||||
|
||||
const isCompressed = isCompressedPosition(raw.substring(offset));
|
||||
|
||||
// eslint-disable-next-line no-useless-assignment
|
||||
let position: IPosition | null = null;
|
||||
// eslint-disable-next-line no-useless-assignment
|
||||
let consumed = 0;
|
||||
|
||||
if (isCompressed) {
|
||||
const { position: compressed, segment: compressedSection } = parseCompressedPosition(
|
||||
raw.substring(offset),
|
||||
withStructure
|
||||
);
|
||||
if (!compressed) return { payload: null };
|
||||
|
||||
position = {
|
||||
latitude: compressed.latitude,
|
||||
longitude: compressed.longitude,
|
||||
symbol: compressed.symbol,
|
||||
altitude: compressed.altitude
|
||||
};
|
||||
consumed = 13;
|
||||
|
||||
if (compressedSection) segment.push(compressedSection);
|
||||
} else {
|
||||
const { position: uncompressed, segment: uncompressedSection } = parseUncompressedPosition(
|
||||
raw.substring(offset),
|
||||
withStructure
|
||||
);
|
||||
if (!uncompressed) return { payload: null };
|
||||
|
||||
position = {
|
||||
latitude: uncompressed.latitude,
|
||||
longitude: uncompressed.longitude,
|
||||
symbol: uncompressed.symbol,
|
||||
ambiguity: uncompressed.ambiguity
|
||||
};
|
||||
consumed = 19;
|
||||
|
||||
if (uncompressedSection) segment.push(uncompressedSection);
|
||||
}
|
||||
|
||||
offset += consumed;
|
||||
const remainder = raw.substring(offset);
|
||||
const doNotArchive = remainder.includes(DO_NOT_ARCHIVE_MARKER);
|
||||
let comment = remainder;
|
||||
|
||||
const extras = decodeCommentExtras(comment, withStructure);
|
||||
comment = extras.comment;
|
||||
|
||||
if (comment) {
|
||||
position.comment = comment;
|
||||
if (withStructure) {
|
||||
segment.push({
|
||||
name: "comment",
|
||||
data: new TextEncoder().encode(remainder).buffer,
|
||||
isString: true,
|
||||
fields: extras.fields || []
|
||||
});
|
||||
}
|
||||
} else if (withStructure && extras.fields) {
|
||||
// No free-text comment, but extras fields exist: emit comment-only segment
|
||||
segment.push({
|
||||
name: "comment",
|
||||
data: new TextEncoder().encode(remainder).buffer,
|
||||
isString: true,
|
||||
fields: extras.fields || []
|
||||
});
|
||||
}
|
||||
|
||||
const payload: ItemPayload = {
|
||||
type: DataType.Item,
|
||||
doNotArchive,
|
||||
name,
|
||||
alive,
|
||||
position
|
||||
};
|
||||
attachExtras(payload, extras);
|
||||
|
||||
if (withStructure) {
|
||||
return { payload, segment };
|
||||
}
|
||||
|
||||
return { payload };
|
||||
};
|
||||
|
||||
export default decodeItemPayload;
|
||||
Reference in New Issue
Block a user