@aeroswarm/codec 0.0.0-stage → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +182 -3
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1249 -0
  5. package/dist/surveillance/adsb/index.d.ts +16 -0
  6. package/dist/surveillance/adsb/index.d.ts.map +1 -0
  7. package/dist/surveillance/aftn/index.d.ts +3 -0
  8. package/dist/surveillance/aftn/index.d.ts.map +1 -0
  9. package/dist/surveillance/aftn/parser.d.ts +16 -0
  10. package/dist/surveillance/aftn/parser.d.ts.map +1 -0
  11. package/dist/surveillance/aftn/parser.test.d.ts +2 -0
  12. package/dist/surveillance/aftn/parser.test.d.ts.map +1 -0
  13. package/dist/surveillance/aftn/types.d.ts +75 -0
  14. package/dist/surveillance/aftn/types.d.ts.map +1 -0
  15. package/dist/surveillance/ais/index.d.ts +17 -0
  16. package/dist/surveillance/ais/index.d.ts.map +1 -0
  17. package/dist/surveillance/asmgcs/index.d.ts +14 -0
  18. package/dist/surveillance/asmgcs/index.d.ts.map +1 -0
  19. package/dist/surveillance/asterix/cat10/index.d.ts +3 -0
  20. package/dist/surveillance/asterix/cat10/index.d.ts.map +1 -0
  21. package/dist/surveillance/asterix/cat21/index.d.ts +5 -0
  22. package/dist/surveillance/asterix/cat21/index.d.ts.map +1 -0
  23. package/dist/surveillance/asterix/cat48/index.d.ts +5 -0
  24. package/dist/surveillance/asterix/cat48/index.d.ts.map +1 -0
  25. package/dist/surveillance/asterix/cat62/index.d.ts +3 -0
  26. package/dist/surveillance/asterix/cat62/index.d.ts.map +1 -0
  27. package/dist/surveillance/asterix/decoder.d.ts +14 -0
  28. package/dist/surveillance/asterix/decoder.d.ts.map +1 -0
  29. package/dist/surveillance/asterix/fields.d.ts +4 -0
  30. package/dist/surveillance/asterix/fields.d.ts.map +1 -0
  31. package/dist/surveillance/asterix/index.d.ts +5 -0
  32. package/dist/surveillance/asterix/index.d.ts.map +1 -0
  33. package/dist/surveillance/asterix/parser.d.ts +16 -0
  34. package/dist/surveillance/asterix/parser.d.ts.map +1 -0
  35. package/dist/surveillance/asterix/parser.test.d.ts +2 -0
  36. package/dist/surveillance/asterix/parser.test.d.ts.map +1 -0
  37. package/dist/surveillance/asterix/resolvers.d.ts +17 -0
  38. package/dist/surveillance/asterix/resolvers.d.ts.map +1 -0
  39. package/dist/surveillance/asterix/types.d.ts +56 -0
  40. package/dist/surveillance/asterix/types.d.ts.map +1 -0
  41. package/dist/surveillance/asterix/values.d.ts +10 -0
  42. package/dist/surveillance/asterix/values.d.ts.map +1 -0
  43. package/dist/surveillance/asterix/xml-loader.d.ts +46 -0
  44. package/dist/surveillance/asterix/xml-loader.d.ts.map +1 -0
  45. package/dist/surveillance/index.d.ts +7 -0
  46. package/dist/surveillance/index.d.ts.map +1 -0
  47. package/dist/surveillance/mlat/index.d.ts +16 -0
  48. package/dist/surveillance/mlat/index.d.ts.map +1 -0
  49. package/package.json +40 -6
  50. package/src/config/asterix.dtd +78 -0
  51. package/src/config/asterix.ini +17 -0
  52. package/src/config/asterix_bds.xml +462 -0
  53. package/src/config/asterix_cat001_1_1.xml +872 -0
  54. package/src/config/asterix_cat001_1_2.xml +836 -0
  55. package/src/config/asterix_cat002_1_0.xml +258 -0
  56. package/src/config/asterix_cat004_1_12.xml +1713 -0
  57. package/src/config/asterix_cat008_1_0.xml +353 -0
  58. package/src/config/asterix_cat010_1_1.xml +791 -0
  59. package/src/config/asterix_cat011_1_2.xml +1691 -0
  60. package/src/config/asterix_cat019_1_2.xml +293 -0
  61. package/src/config/asterix_cat020_1_7.xml +1256 -0
  62. package/src/config/asterix_cat021_0_26.xml +902 -0
  63. package/src/config/asterix_cat021_1_4.xml +1594 -0
  64. package/src/config/asterix_cat021_1_8.xml +1594 -0
  65. package/src/config/asterix_cat021_2_4.xml +2191 -0
  66. package/src/config/asterix_cat023_1_2.xml +357 -0
  67. package/src/config/asterix_cat025_1_2.xml +306 -0
  68. package/src/config/asterix_cat030_6_2.xml +1387 -0
  69. package/src/config/asterix_cat031_6_2.xml +214 -0
  70. package/src/config/asterix_cat032_6_2.xml +465 -0
  71. package/src/config/asterix_cat032_7_0.xml +702 -0
  72. package/src/config/asterix_cat034_1_26.xml +586 -0
  73. package/src/config/asterix_cat034_1_27.xml +683 -0
  74. package/src/config/asterix_cat048_1_14.xml +1142 -0
  75. package/src/config/asterix_cat048_1_21.xml +1241 -0
  76. package/src/config/asterix_cat062_0_17.xml +1442 -0
  77. package/src/config/asterix_cat062_1_16.xml +3031 -0
  78. package/src/config/asterix_cat062_1_18.xml +3303 -0
  79. package/src/config/asterix_cat062_1_7.xml +2817 -0
  80. package/src/config/asterix_cat062_1_9.xml +737 -0
  81. package/src/config/asterix_cat063_1_3.xml +298 -0
  82. package/src/config/asterix_cat065_1_2.xml +168 -0
  83. package/src/config/asterix_cat065_1_3.xml +183 -0
  84. package/src/config/asterix_cat094_0_1.xml +1103 -0
  85. package/src/config/asterix_cat205_1_0.xml +385 -0
  86. package/src/config/asterix_cat240_1_3.xml +289 -0
  87. package/src/config/asterix_cat242_1_0.xml +644 -0
  88. package/src/config/asterix_cat247_1_2.xml +92 -0
  89. package/src/config/asterix_cat252_6_1.xml +1826 -0
  90. package/src/config/asterix_cat252_6_2.xml +1831 -0
  91. package/src/config/asterix_cat252_7_0.xml +1867 -0
  92. package/src/config/eurocontroll_version/asterix-spec.xsd +343 -0
  93. package/src/config/eurocontroll_version/asterix_cat094.xml +457 -0
package/README.md CHANGED
@@ -1,3 +1,182 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @aeroswarm/codec
2
+
3
+ Decoding library for aviation surveillance protocols. Converts raw wire-format data (ASTERIX UDP datagrams) into structured records that the rest of the AeroSwarm pipeline normalizes into track events.
4
+
5
+ **Node-only.** The decoder reads its XML format definitions from disk at runtime — never import this package from browser code. Frontends consume the already-normalized events via `@aeroswarm/client`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @aeroswarm/codec
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import { parseAsterix, resolveAsterixPosition, resolveAsterixAltitudeFt } from "@aeroswarm/codec";
17
+
18
+ socket.on("message", (buf: Buffer) => {
19
+ // A single UDP datagram may contain multiple concatenated ASTERIX blocks —
20
+ // parseAsterix handles that and returns one record per block.
21
+ const records = parseAsterix(buf);
22
+
23
+ for (const rec of records) {
24
+ // rec.category → 21, 23, 48, …
25
+ // rec.edition → which edition actually decoded it, e.g. "0.26"
26
+ // rec.fields → decoded values (icao24, lat/lon, callsign, …)
27
+ const pos = resolveAsterixPosition(rec.fields, radarSite);
28
+ const altFt = resolveAsterixAltitudeFt(rec.fields);
29
+ }
30
+ });
31
+ ```
32
+
33
+ ### Forcing a specific edition
34
+
35
+ Auto-detection tries every known edition per category and keeps the **richest plausible decode** (a wrong-edition UAP can still "successfully" decode with fields landing in the wrong slots, so first-match is not safe). When you know your sensor's edition, pin it — deterministic and one decode per packet instead of several:
36
+
37
+ ```ts
38
+ // e.g. the kantor radar transmits CAT21 edition 0.26
39
+ const records = parseAsterix(buf, { 21: "0.26" });
40
+ ```
41
+
42
+ In the services this is wired to the `ASTERIX_EDITIONS` env var (format `"21:0.26,48:1.21"`).
43
+
44
+ ## Supported categories & editions
45
+
46
+ | Category | Content | Editions (newest first) |
47
+ |----------|---------|--------------------------|
48
+ | CAT010 | A-SMGCS monosensor surface movement (aircraft + vehicles) | 1.1 |
49
+ | CAT011 | A-SMGCS fused surface movement | 1.2 |
50
+ | CAT020 | Multilateration (MLAT) target reports | 1.7 |
51
+ | CAT021 | ADS-B target reports | 2.4, 1.8, 1.4, 0.26 |
52
+ | CAT023 | CNS/ATM ground station & service status | 1.2 |
53
+ | CAT034 | Radar service messages (north marker, sector crossing) — sensor id only | 1.27, 1.26 |
54
+ | CAT048 | Monoradar (PSR/SSR) target reports | 1.21, 1.14 |
55
+ | CAT062 | System/fused tracks from ATC (keyed by system track number; item 380 is compound — only the target address `ADR` is unpacked into `icao24`) | 1.18, 1.16, 1.9, 1.7, 0.17 |
56
+ | CAT063 | Sensor status — sensor id only | 1.3 |
57
+
58
+ CAT021 (edition 1.4+) and CAT048 also expose `trackNumber` (I021/161, I048/161), so reports without a Mode-S address can still be tracked per sensor.
59
+
60
+ Definitions are standard `asterix` XML files in [`src/config/`](src/config/). More category XMLs are already present there (CAT001, 002, 004, …) but not yet registered — see "Adding a category" below.
61
+
62
+ ## AFTN / ATS messages
63
+
64
+ Flight-plan data, not positions: `parseAftn` turns one raw AFTN message (FPL, CHG, DLA, DEP, ARR, CNL) into plain JSON.
65
+
66
+ ```ts
67
+ import { parseAftn, splitAftnStream } from "@aeroswarm/codec";
68
+
69
+ // UDP: one message per datagram
70
+ const msg = parseAftn(datagram); // AftnMessage | null
71
+ // msg.callsign "BTK7630" · msg.adep "WARR" · msg.ades "WIII" · msg.aircraftType "A320"
72
+ // msg.cruiseSpeed "N0450" · msg.cruiseLevel "F350" · msg.route "DCT ABMAL L897 BIKMA DCT"
73
+ // msg.icao24 "8a0123" (item 18 CODE/) · msg.eobtAt / atdAt / ataAt (epoch ms, UTC)
74
+
75
+ // TCP: a message ends at NNNN or ETX
76
+ const { messages, rest } = splitAftnStream(buffered + chunk);
77
+ ```
78
+
79
+ - Tolerates gateway framing: SOH/STX/ETX, a `ZCZC` start line, `NNNN`, unknown header lines. Returns `null` when no `( … )` body is present — never throws.
80
+ - Month and year are resolved from `DOF/` or, failing that, the origin `DDHHMM` group nearest to `options.now`.
81
+ - `icao24` comes from `CODE/` and is the precise key for correlating a plan with a surveillance track; callsign is the fallback.
82
+ - Route stays a string (airways, fixes, `DCT`, coordinates) — turning fixes into coordinates needs an AIRAC database this package does not carry.
83
+
84
+ ## Exposed API
85
+
86
+ ### `parseAsterix(buf, defaultEdition?)`
87
+
88
+ ```ts
89
+ function parseAsterix(
90
+ buf: Buffer,
91
+ defaultEdition?: Record<number, string>, // category → edition, skips auto-detect
92
+ ): AsterixRecord[]
93
+ ```
94
+
95
+ Parses one UDP datagram (possibly multiple concatenated blocks) into records. Never throws on malformed input — bad blocks are skipped and an empty array is a valid result. Records failing the plausibility check (lat/lon out of range) are dropped.
96
+
97
+ ### `resolveAsterixPosition(fields, radarSite?)`
98
+
99
+ Returns `{ lat, lon } | null`. Uses WGS-84 position when present; for radar categories reporting range/azimuth (`measuredPosition`) or cartesian offsets (`positionCartesian`), converts relative to `radarSite`.
100
+
101
+ ### `resolveAsterixAltitudeFt(fields)`
102
+
103
+ Returns the best available altitude in feet (`geometric → flight level` preference) or `undefined`.
104
+
105
+ ### Types
106
+
107
+ ```ts
108
+ interface AsterixRecord { category: number; edition: string; fields: AsterixFields; }
109
+
110
+ interface AsterixFields {
111
+ icao24?: string; // 6-char hex aircraft address
112
+ callsign?: string; // e.g. "MAS179"
113
+ squawk?: string; // 4-digit octal
114
+ lat?: number; lon?: number; // WGS-84 degrees
115
+ altitudeFt?: number;
116
+ timeOfDay?: number; // seconds since UTC midnight
117
+ trackNumber?: number; // per-sensor track number (CAT010/011/020/021/048/062)
118
+ velocity?: { groundSpeedKt?: number; headingDeg?: number };
119
+ positionCartesian?: { xNm?: number; yNm?: number };
120
+ measuredPosition?: { rangeNm?: number; azimuthDeg?: number };
121
+ dataSource?: { sac: number; sic: number };
122
+ // CAT23 ground-station status
123
+ reportType?: number; // 1 = ground station status
124
+ gsStatus?: { nogo: boolean; odp: boolean; oxt: boolean; msc: boolean; tsv: boolean; spo: boolean };
125
+ operationalRangeNm?: number;
126
+ }
127
+
128
+ interface RadarSite { lat: number; lon: number; }
129
+ ```
130
+
131
+ ### Per-category subpath re-exports
132
+
133
+ `cat10/`, `cat21/`, `cat48/`, `cat62/` re-export the same parser API for callers that want category-scoped imports.
134
+
135
+ ### Other protocol entry points (stubs)
136
+
137
+ `decodeAdsbFrame`, `decodeAis`, `decodeMlatFrame`, `decodeAsmgcsFrame` exist as placeholders and currently return `null`. Raw ADS-B/AIS decoding is not implemented — ADS-B arrives via ASTERIX CAT21.
138
+
139
+ ## How decoding works (architecture)
140
+
141
+ ```
142
+ XML definition (src/config/asterix_catXXX_Y_Z.xml)
143
+ ├─ UAP → which FSPEC bit maps to which DataItem
144
+ ├─ formats → fixed/variable/repetitive/compound/explicit item lengths
145
+ └─ <Bits> → named bit-fields: range, signedness, encoding, scale
146
+ ↓
147
+ decoder.ts walks FSPEC + items, slices each present item's bytes
148
+ values.ts extractBitFields() turns bytes into named raw values per the XML
149
+ fields.ts semantic mapper: raw names → canonical AsterixFields
150
+ (XML-first; falls back to manual byte decoding when a
151
+ definition lacks usable <Bits> specs)
152
+ parser.ts edition selection: forced, or richest-plausible-decode wins
153
+ ```
154
+
155
+ Value extraction is **config-driven**: bit positions, two's-complement handling, scale factors (`180/2²⁵` vs `180/2²³` per edition, etc.), 6-bit ICAO callsign chars, octal squawk — all read from the XML. The semantic layer stays as a thin, reviewable mapping because "which item is *the* position" and altitude-preference rules are policy, not format.
156
+
157
+ ### Adding a category or edition
158
+
159
+ 1. Drop the standard `asterix` XML into `src/config/` (many are already there).
160
+ 2. Register it in `FILE_MAP` (xml-loader.ts) and `EDITIONS` (parser.ts).
161
+ 3. If the category introduces new semantic fields, extend `AsterixFields` (types.ts) and add a small mapping in `fields.ts`.
162
+ 4. Add a regression test with a real captured packet in `parser.test.ts`.
163
+
164
+ ## Testing
165
+
166
+ ```bash
167
+ pnpm test
168
+ ```
169
+
170
+ Tests use **real packets captured from the live radar feed** as fixtures (a CAT21/0.26 MAS179 report and a CAT23 ground-station status), plus multi-block and malformed-input cases. When touching the decoder, always keep these green — they pin exact decoded values.
171
+
172
+ ## Low-level API (advanced)
173
+
174
+ Exposed for tooling/tests; not needed for normal decoding:
175
+
176
+ | Export | Purpose |
177
+ |--------|---------|
178
+ | `loadDef(cat, edition)` | Load + cache one XML definition (`AsterixDef`: UAP + item formats + bit-fields) |
179
+ | `decodeBlock(block, def, onField)` | Walk one ASTERIX block, invoking `onField(id, bytes)` per present item |
180
+ | `parseFspec(buf, start?)` | Parse an FX-extended FSPEC into a bit array |
181
+ | `extractBitFields(data, defs)` | Apply `BitFieldDef[]` to raw item bytes → named values |
182
+ | `BitFieldDef`, `ItemDef`, `AsterixDef`, `UAPEntry`, `RawValues` | Supporting types |
@@ -0,0 +1,2 @@
1
+ export * from "./surveillance/index.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}