Cleaned up the frame.ts by splitting payload parsing to subpackages
This commit is contained in:
129
src/payload.weather.ts
Normal file
129
src/payload.weather.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { FieldType, type Segment } from "@hamradio/packet";
|
||||
|
||||
import { DataType, type IPosition, type Payload, type WeatherPayload } from "./frame.types";
|
||||
import { isCompressedPosition, parseCompressedPosition, parseUncompressedPosition } from "./payload.position";
|
||||
import Timestamp from "./timestamp";
|
||||
|
||||
export const decodeWeatherPayload = (
|
||||
raw: string,
|
||||
withStructure: boolean = false
|
||||
): {
|
||||
payload: Payload | null;
|
||||
segment?: Segment[];
|
||||
} => {
|
||||
try {
|
||||
if (raw.length < 2) return { payload: null };
|
||||
|
||||
let offset = 1; // skip '_' data type
|
||||
const segments: Segment[] = withStructure ? [] : [];
|
||||
|
||||
// Try optional timestamp (7 chars)
|
||||
let timestamp;
|
||||
if (raw.length >= offset + 7) {
|
||||
const timeStr = raw.substring(offset, offset + 7);
|
||||
const parsed = Timestamp.fromString(timeStr, withStructure);
|
||||
timestamp = parsed.timestamp;
|
||||
if (parsed.segment) {
|
||||
segments.push(parsed.segment);
|
||||
}
|
||||
if (timestamp) offset += 7;
|
||||
}
|
||||
|
||||
// Try optional position following timestamp
|
||||
let position: IPosition | undefined;
|
||||
let consumed = 0;
|
||||
const tail = raw.substring(offset);
|
||||
if (tail.length > 0) {
|
||||
// If the tail starts with a wind token like DDD/SSS, treat it as weather data
|
||||
// and do not attempt to parse it as a position (avoids mis-detecting wind
|
||||
// values as compressed position fields).
|
||||
if (/^\s*\d{3}\/\d{1,3}/.test(tail)) {
|
||||
// no position present; leave consumed = 0
|
||||
} else if (isCompressedPosition(tail)) {
|
||||
const parsed = parseCompressedPosition(tail, withStructure);
|
||||
if (parsed.position) {
|
||||
position = {
|
||||
latitude: parsed.position.latitude,
|
||||
longitude: parsed.position.longitude,
|
||||
symbol: parsed.position.symbol,
|
||||
altitude: parsed.position.altitude
|
||||
};
|
||||
if (parsed.segment) segments.push(parsed.segment);
|
||||
consumed = 13;
|
||||
}
|
||||
} else {
|
||||
const parsed = parseUncompressedPosition(tail, withStructure);
|
||||
if (parsed.position) {
|
||||
position = {
|
||||
latitude: parsed.position.latitude,
|
||||
longitude: parsed.position.longitude,
|
||||
symbol: parsed.position.symbol,
|
||||
ambiguity: parsed.position.ambiguity
|
||||
};
|
||||
if (parsed.segment) segments.push(parsed.segment);
|
||||
consumed = 19;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset += consumed;
|
||||
|
||||
const rest = raw.substring(offset).trim();
|
||||
|
||||
const payload: WeatherPayload = {
|
||||
type: DataType.WeatherReportNoPosition
|
||||
};
|
||||
if (timestamp) payload.timestamp = timestamp;
|
||||
if (position) payload.position = position;
|
||||
|
||||
if (rest && rest.length > 0) {
|
||||
// Parse common tokens
|
||||
// Wind: DDD/SSS [gGGG]
|
||||
const windMatch = rest.match(/(\d{3})\/(\d{1,3})(?:g(\d{1,3}))?/);
|
||||
if (windMatch) {
|
||||
payload.windDirection = parseInt(windMatch[1], 10);
|
||||
payload.windSpeed = parseInt(windMatch[2], 10);
|
||||
if (windMatch[3]) payload.windGust = parseInt(windMatch[3], 10);
|
||||
}
|
||||
|
||||
// Temperature: tNNN (F)
|
||||
const tempMatch = rest.match(/t(-?\d{1,3})/i);
|
||||
if (tempMatch) payload.temperature = parseInt(tempMatch[1], 10);
|
||||
|
||||
// Rain: rNNN (last hour), pNNN (24h), PNNN (since midnight) - values are hundredths of inch
|
||||
const rMatch = rest.match(/r(\d{3})/);
|
||||
if (rMatch) payload.rainLastHour = parseInt(rMatch[1], 10);
|
||||
const pMatch = rest.match(/p(\d{3})/);
|
||||
if (pMatch) payload.rainLast24Hours = parseInt(pMatch[1], 10);
|
||||
const PMatch = rest.match(/P(\d{3})/);
|
||||
if (PMatch) payload.rainSinceMidnight = parseInt(PMatch[1], 10);
|
||||
|
||||
// Humidity: hNN
|
||||
const hMatch = rest.match(/h(\d{1,3})/);
|
||||
if (hMatch) payload.humidity = parseInt(hMatch[1], 10);
|
||||
|
||||
// Pressure: bXXXX or bXXXXX (tenths of millibar)
|
||||
const bMatch = rest.match(/b(\d{4,5})/);
|
||||
if (bMatch) payload.pressure = parseInt(bMatch[1], 10);
|
||||
|
||||
// Add raw comment
|
||||
payload.comment = rest;
|
||||
|
||||
if (withStructure) {
|
||||
segments.push({
|
||||
name: "weather",
|
||||
data: new TextEncoder().encode(rest).buffer,
|
||||
isString: true,
|
||||
fields: [{ type: FieldType.STRING, name: "text", length: rest.length }]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (withStructure) return { payload, segment: segments };
|
||||
return { payload };
|
||||
} catch {
|
||||
return { payload: null };
|
||||
}
|
||||
};
|
||||
|
||||
export default decodeWeatherPayload;
|
||||
Reference in New Issue
Block a user