@squawk/mode-s 0.1.0
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.
- package/README.md +243 -0
- package/dist/acas.d.ts +14 -0
- package/dist/acas.d.ts.map +1 -0
- package/dist/acas.js +90 -0
- package/dist/altitude.d.ts +34 -0
- package/dist/altitude.d.ts.map +1 -0
- package/dist/altitude.js +99 -0
- package/dist/bits.d.ts +41 -0
- package/dist/bits.d.ts.map +1 -0
- package/dist/bits.js +55 -0
- package/dist/comm-b.d.ts +37 -0
- package/dist/comm-b.d.ts.map +1 -0
- package/dist/comm-b.js +245 -0
- package/dist/cpr.d.ts +71 -0
- package/dist/cpr.d.ts.map +1 -0
- package/dist/cpr.js +220 -0
- package/dist/emergency-status.d.ts +10 -0
- package/dist/emergency-status.d.ts.map +1 -0
- package/dist/emergency-status.js +21 -0
- package/dist/frame.d.ts +42 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +68 -0
- package/dist/identification.d.ts +10 -0
- package/dist/identification.d.ts.map +1 -0
- package/dist/identification.js +64 -0
- package/dist/identity.d.ts +11 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +20 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/message.d.ts +40 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +293 -0
- package/dist/mode-ac.d.ts +17 -0
- package/dist/mode-ac.d.ts.map +1 -0
- package/dist/mode-ac.js +86 -0
- package/dist/operational-status.d.ts +9 -0
- package/dist/operational-status.d.ts.map +1 -0
- package/dist/operational-status.js +23 -0
- package/dist/surface-movement.d.ts +9 -0
- package/dist/surface-movement.d.ts.map +1 -0
- package/dist/surface-movement.js +37 -0
- package/dist/target-state-status.d.ts +9 -0
- package/dist/target-state-status.d.ts.map +1 -0
- package/dist/target-state-status.js +34 -0
- package/dist/test-utils.d.ts +7 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +17 -0
- package/dist/types/acas.d.ts +82 -0
- package/dist/types/acas.d.ts.map +1 -0
- package/dist/types/acas.js +1 -0
- package/dist/types/comm-b.d.ts +69 -0
- package/dist/types/comm-b.d.ts.map +1 -0
- package/dist/types/comm-b.js +1 -0
- package/dist/types/cpr.d.ts +25 -0
- package/dist/types/cpr.d.ts.map +1 -0
- package/dist/types/cpr.js +1 -0
- package/dist/types/emergency-status.d.ts +6 -0
- package/dist/types/emergency-status.d.ts.map +1 -0
- package/dist/types/emergency-status.js +1 -0
- package/dist/types/frame.d.ts +32 -0
- package/dist/types/frame.d.ts.map +1 -0
- package/dist/types/frame.js +1 -0
- package/dist/types/identification.d.ts +9 -0
- package/dist/types/identification.d.ts.map +1 -0
- package/dist/types/identification.js +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/message.d.ts +235 -0
- package/dist/types/message.d.ts.map +1 -0
- package/dist/types/message.js +1 -0
- package/dist/types/mode-ac.d.ts +16 -0
- package/dist/types/mode-ac.d.ts.map +1 -0
- package/dist/types/mode-ac.js +1 -0
- package/dist/types/operational-status.d.ts +28 -0
- package/dist/types/operational-status.d.ts.map +1 -0
- package/dist/types/operational-status.js +1 -0
- package/dist/types/target-state-status.d.ts +34 -0
- package/dist/types/target-state-status.d.ts.map +1 -0
- package/dist/types/target-state-status.js +1 -0
- package/dist/types/velocity.d.ts +35 -0
- package/dist/types/velocity.d.ts.map +1 -0
- package/dist/types/velocity.js +1 -0
- package/dist/velocity.d.ts +9 -0
- package/dist/velocity.d.ts.map +1 -0
- package/dist/velocity.js +73 -0
- package/package.json +61 -0
package/dist/comm-b.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { extractBits } from './bits.js';
|
|
2
|
+
/**
|
|
3
|
+
* Combines an unsigned magnitude and a separate sign bit into a signed
|
|
4
|
+
* value. Mode-S encodes sign and magnitude as separate bit fields (not
|
|
5
|
+
* two's complement) - sign=1, magnitude=0 represents `-2**width`, not `-0`.
|
|
6
|
+
*/
|
|
7
|
+
function signed(value, width, sign) {
|
|
8
|
+
return sign === 1 ? value - (1 << width) : value;
|
|
9
|
+
}
|
|
10
|
+
/** Wraps an angle into the half-open interval [0, 360) - JavaScript's `%` follows the dividend's sign for negative input, unlike the floor-mod this needs. */
|
|
11
|
+
function normalizeAngleDeg(deg) {
|
|
12
|
+
return ((deg % 360) + 360) % 360;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns true if a status-gated value field is inconsistent: BDS registers
|
|
16
|
+
* with status-bit gates encode each field as a status bit followed by a
|
|
17
|
+
* fixed-width value, and when the status is 0 the entire value field
|
|
18
|
+
* (including any sign bit) must also be 0. A nonzero value with status=0
|
|
19
|
+
* indicates either a corrupt message or a different BDS register's payload
|
|
20
|
+
* accidentally passing this one's checks.
|
|
21
|
+
*/
|
|
22
|
+
function wrongStatus(mb, statusBit, valueStart, valueWidth) {
|
|
23
|
+
if (extractBits(mb, statusBit, 1) !== 0) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return extractBits(mb, valueStart, valueWidth) !== 0;
|
|
27
|
+
}
|
|
28
|
+
function decodeTargetAltitudeSource(raw) {
|
|
29
|
+
switch (raw) {
|
|
30
|
+
case 1:
|
|
31
|
+
return 'aircraftAltitude';
|
|
32
|
+
case 2:
|
|
33
|
+
return 'mcpFcu';
|
|
34
|
+
case 3:
|
|
35
|
+
return 'fms';
|
|
36
|
+
default:
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Whether `mb` is a structurally plausible BDS 4,0 (Selected Vertical Intention) register - status-bit/value consistency and reserved-bits-must-be-zero checks. Has no format-identifier byte, so this is a heuristic, not a certainty. */
|
|
41
|
+
function isSelectedVerticalIntention(mb) {
|
|
42
|
+
if (mb.every((byte) => byte === 0)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (wrongStatus(mb, 0, 1, 12)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (wrongStatus(mb, 13, 14, 12)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (wrongStatus(mb, 26, 27, 12)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (wrongStatus(mb, 47, 48, 3)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (wrongStatus(mb, 53, 54, 2)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (extractBits(mb, 39, 8) !== 0) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return extractBits(mb, 51, 2) === 0;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Decodes a BDS 4,0 (Selected Vertical Intention) register.
|
|
67
|
+
*
|
|
68
|
+
* @param mb - The 7-byte MB field of a DF20/21 Comm-B reply, already known (or inferred via {@link inferCommBRegisters}) to be BDS 4,0.
|
|
69
|
+
* @returns The decoded selected vertical intention.
|
|
70
|
+
*/
|
|
71
|
+
export function decodeSelectedVerticalIntention(mb) {
|
|
72
|
+
const mcpStatus = extractBits(mb, 0, 1);
|
|
73
|
+
const fmsStatus = extractBits(mb, 13, 1);
|
|
74
|
+
const baroStatus = extractBits(mb, 26, 1);
|
|
75
|
+
const modeStatus = extractBits(mb, 47, 1);
|
|
76
|
+
const sourceStatus = extractBits(mb, 53, 1);
|
|
77
|
+
return {
|
|
78
|
+
bdsCode: '4,0',
|
|
79
|
+
mcpFcuSelectedAltitudeFt: mcpStatus === 1 ? extractBits(mb, 1, 12) * 16 : undefined,
|
|
80
|
+
fmsSelectedAltitudeFt: fmsStatus === 1 ? extractBits(mb, 14, 12) * 16 : undefined,
|
|
81
|
+
baroPressureSettingMb: baroStatus === 1 ? extractBits(mb, 27, 12) * 0.1 + 800 : undefined,
|
|
82
|
+
vnavModeActive: modeStatus === 1 ? extractBits(mb, 48, 1) === 1 : undefined,
|
|
83
|
+
altitudeHoldModeActive: modeStatus === 1 ? extractBits(mb, 49, 1) === 1 : undefined,
|
|
84
|
+
approachModeActive: modeStatus === 1 ? extractBits(mb, 50, 1) === 1 : undefined,
|
|
85
|
+
targetAltitudeSource: sourceStatus === 1 ? decodeTargetAltitudeSource(extractBits(mb, 54, 2)) : undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Whether `mb` is a structurally plausible BDS 5,0 (Track and Turn Report) register - status-bit consistency plus physically-plausible range checks (e.g. |roll| <= 35deg). Has no format-identifier byte, so this is a heuristic, not a certainty - a payload can pass both this and {@link isHeadingAndSpeedReport}. */
|
|
89
|
+
function isTrackAndTurnReport(mb) {
|
|
90
|
+
if (mb.every((byte) => byte === 0)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (wrongStatus(mb, 0, 1, 10)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (wrongStatus(mb, 11, 12, 11)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (wrongStatus(mb, 23, 24, 10)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (wrongStatus(mb, 34, 35, 10)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (wrongStatus(mb, 45, 46, 10)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (extractBits(mb, 0, 1) === 1) {
|
|
109
|
+
const rollDeg = (signed(extractBits(mb, 2, 9), 9, extractBits(mb, 1, 1)) * 45) / 256;
|
|
110
|
+
if (Math.abs(rollDeg) > 35) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const gsStatus = extractBits(mb, 23, 1);
|
|
115
|
+
const gsRaw = extractBits(mb, 24, 10);
|
|
116
|
+
if (gsStatus === 1 && gsRaw * 2 > 600) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const tasStatus = extractBits(mb, 45, 1);
|
|
120
|
+
const tasRaw = extractBits(mb, 46, 10);
|
|
121
|
+
if (tasStatus === 1 && tasRaw * 2 > 600) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return !(gsStatus === 1 && tasStatus === 1 && Math.abs(tasRaw * 2 - gsRaw * 2) > 200);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Decodes a BDS 5,0 (Track and Turn Report) register.
|
|
128
|
+
*
|
|
129
|
+
* @param mb - The 7-byte MB field of a DF20/21 Comm-B reply, already known (or inferred via {@link inferCommBRegisters}) to be BDS 5,0.
|
|
130
|
+
* @returns The decoded track and turn report.
|
|
131
|
+
*/
|
|
132
|
+
export function decodeTrackAndTurnReport(mb) {
|
|
133
|
+
const rollStatus = extractBits(mb, 0, 1);
|
|
134
|
+
const trackStatus = extractBits(mb, 11, 1);
|
|
135
|
+
const gsStatus = extractBits(mb, 23, 1);
|
|
136
|
+
const trackRateStatus = extractBits(mb, 34, 1);
|
|
137
|
+
const tasStatus = extractBits(mb, 45, 1);
|
|
138
|
+
return {
|
|
139
|
+
bdsCode: '5,0',
|
|
140
|
+
rollAngleDeg: rollStatus === 1
|
|
141
|
+
? (signed(extractBits(mb, 2, 9), 9, extractBits(mb, 1, 1)) * 45) / 256
|
|
142
|
+
: undefined,
|
|
143
|
+
trueTrackDeg: trackStatus === 1
|
|
144
|
+
? normalizeAngleDeg((signed(extractBits(mb, 13, 10), 10, extractBits(mb, 12, 1)) * 90) / 512)
|
|
145
|
+
: undefined,
|
|
146
|
+
groundSpeedKt: gsStatus === 1 ? extractBits(mb, 24, 10) * 2 : undefined,
|
|
147
|
+
trackAngleRateDegPerSec: trackRateStatus === 1
|
|
148
|
+
? (signed(extractBits(mb, 36, 9), 9, extractBits(mb, 35, 1)) * 8) / 256
|
|
149
|
+
: undefined,
|
|
150
|
+
trueAirspeedKt: tasStatus === 1 ? extractBits(mb, 46, 10) * 2 : undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Whether `mb` is a structurally plausible BDS 6,0 (Heading and Speed Report) register - status-bit consistency plus physically-plausible range checks (e.g. Mach <= 1). Has no format-identifier byte, so this is a heuristic, not a certainty - a payload can pass both this and {@link isTrackAndTurnReport}. */
|
|
154
|
+
function isHeadingAndSpeedReport(mb) {
|
|
155
|
+
if (mb.every((byte) => byte === 0)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
if (wrongStatus(mb, 0, 1, 11)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (wrongStatus(mb, 12, 13, 10)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (wrongStatus(mb, 23, 24, 10)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (wrongStatus(mb, 34, 35, 10)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
if (wrongStatus(mb, 45, 46, 10)) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const iasStatus = extractBits(mb, 12, 1);
|
|
174
|
+
if (iasStatus === 1 && extractBits(mb, 13, 10) > 500) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const machStatus = extractBits(mb, 23, 1);
|
|
178
|
+
if (machStatus === 1 && (extractBits(mb, 24, 10) * 2.048) / 512 > 1) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const vrbStatus = extractBits(mb, 34, 1);
|
|
182
|
+
if (vrbStatus === 1) {
|
|
183
|
+
const vrb = signed(extractBits(mb, 36, 9), 9, extractBits(mb, 35, 1)) * 32;
|
|
184
|
+
if (Math.abs(vrb) > 6000) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const vriStatus = extractBits(mb, 45, 1);
|
|
189
|
+
if (vriStatus === 1) {
|
|
190
|
+
const vri = signed(extractBits(mb, 47, 9), 9, extractBits(mb, 46, 1)) * 32;
|
|
191
|
+
if (Math.abs(vri) > 6000) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Decodes a BDS 6,0 (Heading and Speed Report) register.
|
|
199
|
+
*
|
|
200
|
+
* @param mb - The 7-byte MB field of a DF20/21 Comm-B reply, already known (or inferred via {@link inferCommBRegisters}) to be BDS 6,0.
|
|
201
|
+
* @returns The decoded heading and speed report.
|
|
202
|
+
*/
|
|
203
|
+
export function decodeHeadingAndSpeedReport(mb) {
|
|
204
|
+
const headingStatus = extractBits(mb, 0, 1);
|
|
205
|
+
const iasStatus = extractBits(mb, 12, 1);
|
|
206
|
+
const machStatus = extractBits(mb, 23, 1);
|
|
207
|
+
const vrbStatus = extractBits(mb, 34, 1);
|
|
208
|
+
const vriStatus = extractBits(mb, 45, 1);
|
|
209
|
+
return {
|
|
210
|
+
bdsCode: '6,0',
|
|
211
|
+
magneticHeadingDeg: headingStatus === 1
|
|
212
|
+
? normalizeAngleDeg((signed(extractBits(mb, 2, 10), 10, extractBits(mb, 1, 1)) * 90) / 512)
|
|
213
|
+
: undefined,
|
|
214
|
+
indicatedAirspeedKt: iasStatus === 1 ? extractBits(mb, 13, 10) : undefined,
|
|
215
|
+
mach: machStatus === 1 ? (extractBits(mb, 24, 10) * 2.048) / 512 : undefined,
|
|
216
|
+
baroVerticalRateFtPerMin: vrbStatus === 1 ? signed(extractBits(mb, 36, 9), 9, extractBits(mb, 35, 1)) * 32 : undefined,
|
|
217
|
+
inertialVerticalRateFtPerMin: vriStatus === 1 ? signed(extractBits(mb, 47, 9), 9, extractBits(mb, 46, 1)) * 32 : undefined,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Infers which "Enhanced Surveillance" Comm-B register(s) a DF20/21 MB
|
|
222
|
+
* field plausibly holds, and decodes each. Unlike DF17/18's type-code-coded
|
|
223
|
+
* ME field, a Comm-B MB field carries no self-declared register identifier
|
|
224
|
+
* for BDS 4,0/5,0/6,0 - this package would rather report every plausible
|
|
225
|
+
* candidate (via structural and range validation, matching pyModeS's own
|
|
226
|
+
* approach to this exact ambiguity) than silently guess a single wrong
|
|
227
|
+
* answer. In practice this is usually unambiguous; BDS 5,0 and 6,0 are the
|
|
228
|
+
* pair most likely to both pass validation for the same bytes.
|
|
229
|
+
*
|
|
230
|
+
* @param mb - The 7-byte MB field of a DF20/21 Comm-B reply.
|
|
231
|
+
* @returns Every register this MB field plausibly holds - empty if none, one if unambiguous, more than one if genuinely ambiguous.
|
|
232
|
+
*/
|
|
233
|
+
export function inferCommBRegisters(mb) {
|
|
234
|
+
const candidates = [];
|
|
235
|
+
if (isSelectedVerticalIntention(mb)) {
|
|
236
|
+
candidates.push(decodeSelectedVerticalIntention(mb));
|
|
237
|
+
}
|
|
238
|
+
if (isTrackAndTurnReport(mb)) {
|
|
239
|
+
candidates.push(decodeTrackAndTurnReport(mb));
|
|
240
|
+
}
|
|
241
|
+
if (isHeadingAndSpeedReport(mb)) {
|
|
242
|
+
candidates.push(decodeHeadingAndSpeedReport(mb));
|
|
243
|
+
}
|
|
244
|
+
return candidates;
|
|
245
|
+
}
|
package/dist/cpr.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Position } from '@squawk/types';
|
|
2
|
+
import type { CprPosition, CprReference } from './types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the number of CPR longitude zones (NL) at a given latitude, per
|
|
5
|
+
* ICAO Annex 10 Vol. IV / RTCA DO-260B SS A.1.7.2. NL is 1-59, monotone
|
|
6
|
+
* non-increasing in absolute latitude: 59 at the equator, 2 at +-87 deg, 1
|
|
7
|
+
* beyond +-87 deg.
|
|
8
|
+
*
|
|
9
|
+
* @param latDeg - Latitude in decimal degrees.
|
|
10
|
+
* @returns The number of longitude zones, 1-59.
|
|
11
|
+
*/
|
|
12
|
+
export declare function cprNumLongitudeZones(latDeg: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Resolves an absolute position from a paired even and odd airborne CPR
|
|
15
|
+
* frame (ADS-B BDS 0,5 / 2,0 - type codes 9-18 and 20-22), per DO-260B SS
|
|
16
|
+
* A.1.7.3. The two frames must be close enough in time that the aircraft
|
|
17
|
+
* could not have crossed a latitude zone boundary - in practice, no more
|
|
18
|
+
* than about 10 seconds apart, since airborne position is broadcast at
|
|
19
|
+
* roughly 2 Hz. Enforcing that window is the caller's responsibility; this
|
|
20
|
+
* function only performs a zone-consistency check as a lightweight guard,
|
|
21
|
+
* which cannot by itself detect a same-zone false positive from a pair
|
|
22
|
+
* spanning too wide a gap.
|
|
23
|
+
*
|
|
24
|
+
* @param even - Raw CPR fields from the even-format (F=0) frame.
|
|
25
|
+
* @param odd - Raw CPR fields from the odd-format (F=1) frame.
|
|
26
|
+
* @param newerFormat - Which frame is more recent - determines which frame's latitude zone the result is reported in.
|
|
27
|
+
* @returns The resolved position, or undefined if the two frames fall in different latitude zones (the pair cannot be combined) or the result is physically impossible.
|
|
28
|
+
*/
|
|
29
|
+
export declare function decodeAirborneCprPair(even: CprPosition, odd: CprPosition, newerFormat: 'even' | 'odd'): Position | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Resolves an absolute position from a single airborne CPR frame plus a
|
|
32
|
+
* known nearby reference position, per DO-260B SS A.1.7.5. The reference
|
|
33
|
+
* must be within roughly 180 NM of the true position - typically the
|
|
34
|
+
* aircraft's own last known position, or the receiver's location as a
|
|
35
|
+
* fallback for the first message from a new aircraft.
|
|
36
|
+
*
|
|
37
|
+
* @param format - Whether `frame` is an even (F=0) or odd (F=1) format frame.
|
|
38
|
+
* @param frame - Raw CPR fields from the frame.
|
|
39
|
+
* @param reference - A position known to be near the frame's true position.
|
|
40
|
+
* @returns The resolved position.
|
|
41
|
+
*/
|
|
42
|
+
export declare function decodeAirborneCprWithReference(format: 'even' | 'odd', frame: CprPosition, reference: CprReference): Position;
|
|
43
|
+
/**
|
|
44
|
+
* Resolves an absolute position from a paired even and odd surface CPR
|
|
45
|
+
* frame (ADS-B BDS 0,6 - type codes 5-8), per DO-260B SS A.1.7.4. Surface
|
|
46
|
+
* CPR spans a 90 degree latitude zone (versus 360 degree for airborne) and
|
|
47
|
+
* is ambiguous across four longitude quadrants, so unlike the airborne
|
|
48
|
+
* pair decode, a reference position is required to resolve the correct
|
|
49
|
+
* hemisphere and quadrant - the receiver's own location is normally used.
|
|
50
|
+
*
|
|
51
|
+
* @param even - Raw CPR fields from the even-format frame.
|
|
52
|
+
* @param odd - Raw CPR fields from the odd-format frame.
|
|
53
|
+
* @param newerFormat - Which frame is more recent.
|
|
54
|
+
* @param reference - A position known to be within about 45 NM of the true position (typically the receiver's location).
|
|
55
|
+
* @returns The resolved position, or undefined if the two frames fall in different latitude zones.
|
|
56
|
+
*/
|
|
57
|
+
export declare function decodeSurfaceCprPair(even: CprPosition, odd: CprPosition, newerFormat: 'even' | 'odd', reference: CprReference): Position | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Resolves an absolute position from a single surface CPR frame plus a
|
|
60
|
+
* known nearby reference position, per DO-260B SS A.1.7.6. The reference
|
|
61
|
+
* must be within roughly 45 NM of the true position - typically the
|
|
62
|
+
* receiver's own location, since surface traffic is by definition close to
|
|
63
|
+
* an airport the receiver can see.
|
|
64
|
+
*
|
|
65
|
+
* @param format - Whether `frame` is an even (F=0) or odd (F=1) format frame.
|
|
66
|
+
* @param frame - Raw CPR fields from the frame.
|
|
67
|
+
* @param reference - A position known to be near the frame's true position.
|
|
68
|
+
* @returns The resolved position.
|
|
69
|
+
*/
|
|
70
|
+
export declare function decodeSurfaceCprWithReference(format: 'even' | 'odd', frame: CprPosition, reference: CprReference): Position;
|
|
71
|
+
//# sourceMappingURL=cpr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cpr.d.ts","sourceRoot":"","sources":["../src/cpr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAuClE;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAU3D;AA6DD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,MAAM,GAAG,KAAK,GAC1B,QAAQ,GAAG,SAAS,CAuCtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,MAAM,GAAG,KAAK,EACtB,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,YAAY,GACtB,QAAQ,CAEV;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,MAAM,GAAG,KAAK,EAC3B,SAAS,EAAE,YAAY,GACtB,QAAQ,GAAG,SAAS,CA4CtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,GAAG,KAAK,EACtB,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,YAAY,GACtB,QAAQ,CAEV"}
|
package/dist/cpr.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/** Raw CPR fields are 17-bit, normalized to [0, 1) by dividing by 2^17. */
|
|
2
|
+
const CPR_DENOMINATOR = 131072;
|
|
3
|
+
/**
|
|
4
|
+
* Latitude boundaries (absolute degrees, ascending) where the number of CPR
|
|
5
|
+
* longitude zones (NL) steps down by one. Entry `i` is the latitude at
|
|
6
|
+
* which NL transitions from `59 - i` to `59 - i - 1`. Derived once from the
|
|
7
|
+
* closed-form NL(lat) trigonometric expression in ICAO Annex 10 Vol. IV /
|
|
8
|
+
* RTCA DO-260B SS A.1.7.2 with the fixed `nz = 15` zone count - stable
|
|
9
|
+
* across releases since the formula has no other free parameters. A static
|
|
10
|
+
* table avoids repeated trig evaluation on every position decode.
|
|
11
|
+
*/
|
|
12
|
+
const NL_BOUNDARIES = [
|
|
13
|
+
10.47047129996848, 14.828174368686794, 18.186263570713354, 21.029394926028463, 23.545044865570706,
|
|
14
|
+
25.829247070587755, 27.938987101219045, 29.911356857318083, 31.77209707681077, 33.53993436298484,
|
|
15
|
+
35.22899597796385, 36.85025107593526, 38.41241892412256, 39.922566843338615, 41.38651832260239,
|
|
16
|
+
42.80914012243555, 44.194549514192744, 45.546267226602346, 46.867332524987454, 48.160391280966216,
|
|
17
|
+
49.42776439255687, 50.67150165553835, 51.893424691687684, 53.09516152796003, 54.278174722729,
|
|
18
|
+
55.44378444495043, 56.59318756205918, 57.72747353866114, 58.84763776148457, 59.954592766940294,
|
|
19
|
+
61.04917774246351, 62.13216659210329, 63.20427479381928, 64.2661652256744, 65.31845309682089,
|
|
20
|
+
66.36171008382617, 67.39646774084667, 68.4232202208333, 69.44242631144024, 70.454510749876,
|
|
21
|
+
71.45986473028982, 72.45884544728945, 73.45177441667865, 74.43893415725137, 75.42056256653356,
|
|
22
|
+
76.39684390794469, 77.36789461328188, 78.33374082922747, 79.29428225456925, 80.24923213280512,
|
|
23
|
+
81.19801349271948, 82.13956980510606, 83.07199444719814, 83.99173562980565, 84.89166190702085,
|
|
24
|
+
85.75541620944418, 86.535369975121, 87.0,
|
|
25
|
+
];
|
|
26
|
+
/** True modulo (JS `%` can return a negative result; CPR math requires the mathematical modulo). */
|
|
27
|
+
function mod(a, b) {
|
|
28
|
+
return ((a % b) + b) % b;
|
|
29
|
+
}
|
|
30
|
+
/** Wraps a longitude in degrees into the canonical [-180, 180) range. Longitude is circular, so a raw interpolated value outside that range (e.g. 180.5) represents a valid position (-179.5), not an error. */
|
|
31
|
+
function normalizeLongitudeDeg(lonDeg) {
|
|
32
|
+
return mod(lonDeg + 180, 360) - 180;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns the number of CPR longitude zones (NL) at a given latitude, per
|
|
36
|
+
* ICAO Annex 10 Vol. IV / RTCA DO-260B SS A.1.7.2. NL is 1-59, monotone
|
|
37
|
+
* non-increasing in absolute latitude: 59 at the equator, 2 at +-87 deg, 1
|
|
38
|
+
* beyond +-87 deg.
|
|
39
|
+
*
|
|
40
|
+
* @param latDeg - Latitude in decimal degrees.
|
|
41
|
+
* @returns The number of longitude zones, 1-59.
|
|
42
|
+
*/
|
|
43
|
+
export function cprNumLongitudeZones(latDeg) {
|
|
44
|
+
const absLat = Math.abs(latDeg);
|
|
45
|
+
if (absLat > 87) {
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
if (absLat === 87) {
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
const idx = NL_BOUNDARIES.findIndex((boundary) => boundary > absLat);
|
|
52
|
+
return 59 - idx;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Shared core of the even/odd pair decoders: checks the two frames land in
|
|
56
|
+
* consistent NL zones, then interpolates the longitude fraction within
|
|
57
|
+
* `latSpanDeg` (360 for airborne, 90 for surface). Both pair decoders
|
|
58
|
+
* resolve latitude differently before calling this (surface needs an extra
|
|
59
|
+
* hemisphere disambiguation step airborne doesn't), but the zone-check and
|
|
60
|
+
* longitude interpolation that follows is identical modulo the span.
|
|
61
|
+
*/
|
|
62
|
+
function resolveCprPairLongitude(latSpanDeg, latEven, latOdd, cprLonEven, cprLonOdd, newerFormat) {
|
|
63
|
+
const nlEven = cprNumLongitudeZones(latEven);
|
|
64
|
+
const nlOdd = cprNumLongitudeZones(latOdd);
|
|
65
|
+
if (nlEven !== nlOdd) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const lat = newerFormat === 'even' ? latEven : latOdd;
|
|
69
|
+
const nl = newerFormat === 'even' ? nlEven : nlOdd;
|
|
70
|
+
const ni = newerFormat === 'even' ? Math.max(nl, 1) : Math.max(nl - 1, 1);
|
|
71
|
+
const m = Math.floor(cprLonEven * (nl - 1) - cprLonOdd * nl + 0.5);
|
|
72
|
+
const cprLonForFormat = newerFormat === 'even' ? cprLonEven : cprLonOdd;
|
|
73
|
+
const lonFraction = (latSpanDeg / ni) * (mod(m, ni) + cprLonForFormat);
|
|
74
|
+
return { lat, lonFraction };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Shared core of the single-frame-plus-reference decoders: resolves
|
|
78
|
+
* latitude and longitude within `latSpanDeg` (360 for airborne, 90 for
|
|
79
|
+
* surface) against a known nearby reference position.
|
|
80
|
+
*/
|
|
81
|
+
function decodeCprWithReference(latSpanDeg, format, frame, reference) {
|
|
82
|
+
const cprLat = frame.latCpr / CPR_DENOMINATOR;
|
|
83
|
+
const cprLon = frame.lonCpr / CPR_DENOMINATOR;
|
|
84
|
+
const dLat = format === 'odd' ? latSpanDeg / 59 : latSpanDeg / 60;
|
|
85
|
+
const j = Math.floor(0.5 + reference.lat / dLat - cprLat);
|
|
86
|
+
const lat = dLat * (j + cprLat);
|
|
87
|
+
const ni = cprNumLongitudeZones(lat) - (format === 'odd' ? 1 : 0);
|
|
88
|
+
const dLon = ni > 0 ? latSpanDeg / ni : latSpanDeg;
|
|
89
|
+
const m = Math.floor(0.5 + reference.lon / dLon - cprLon);
|
|
90
|
+
const lon = normalizeLongitudeDeg(dLon * (m + cprLon));
|
|
91
|
+
return { lat, lon };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolves an absolute position from a paired even and odd airborne CPR
|
|
95
|
+
* frame (ADS-B BDS 0,5 / 2,0 - type codes 9-18 and 20-22), per DO-260B SS
|
|
96
|
+
* A.1.7.3. The two frames must be close enough in time that the aircraft
|
|
97
|
+
* could not have crossed a latitude zone boundary - in practice, no more
|
|
98
|
+
* than about 10 seconds apart, since airborne position is broadcast at
|
|
99
|
+
* roughly 2 Hz. Enforcing that window is the caller's responsibility; this
|
|
100
|
+
* function only performs a zone-consistency check as a lightweight guard,
|
|
101
|
+
* which cannot by itself detect a same-zone false positive from a pair
|
|
102
|
+
* spanning too wide a gap.
|
|
103
|
+
*
|
|
104
|
+
* @param even - Raw CPR fields from the even-format (F=0) frame.
|
|
105
|
+
* @param odd - Raw CPR fields from the odd-format (F=1) frame.
|
|
106
|
+
* @param newerFormat - Which frame is more recent - determines which frame's latitude zone the result is reported in.
|
|
107
|
+
* @returns The resolved position, or undefined if the two frames fall in different latitude zones (the pair cannot be combined) or the result is physically impossible.
|
|
108
|
+
*/
|
|
109
|
+
export function decodeAirborneCprPair(even, odd, newerFormat) {
|
|
110
|
+
const cprLatEven = even.latCpr / CPR_DENOMINATOR;
|
|
111
|
+
const cprLonEven = even.lonCpr / CPR_DENOMINATOR;
|
|
112
|
+
const cprLatOdd = odd.latCpr / CPR_DENOMINATOR;
|
|
113
|
+
const cprLonOdd = odd.lonCpr / CPR_DENOMINATOR;
|
|
114
|
+
const j = Math.floor(59 * cprLatEven - 60 * cprLatOdd + 0.5);
|
|
115
|
+
let latEven = (360 / 60) * (mod(j, 60) + cprLatEven);
|
|
116
|
+
let latOdd = (360 / 59) * (mod(j, 59) + cprLatOdd);
|
|
117
|
+
if (latEven >= 270) {
|
|
118
|
+
latEven -= 360;
|
|
119
|
+
}
|
|
120
|
+
if (latOdd >= 270) {
|
|
121
|
+
latOdd -= 360;
|
|
122
|
+
}
|
|
123
|
+
const resolved = resolveCprPairLongitude(360, latEven, latOdd, cprLonEven, cprLonOdd, newerFormat);
|
|
124
|
+
if (resolved === undefined) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
// A frame pair that passes the zone-consistency check above but still
|
|
128
|
+
// produces an out-of-range latitude is a straddling-boundary false
|
|
129
|
+
// positive - reject rather than report a physically impossible
|
|
130
|
+
// position. Longitude has no equivalent failure mode: the interpolation
|
|
131
|
+
// formula is bounded to one span by construction, and
|
|
132
|
+
// normalizeLongitudeDeg always yields a valid [-180, 180) result.
|
|
133
|
+
if (Math.abs(resolved.lat) > 90) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return { lat: resolved.lat, lon: normalizeLongitudeDeg(resolved.lonFraction) };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolves an absolute position from a single airborne CPR frame plus a
|
|
140
|
+
* known nearby reference position, per DO-260B SS A.1.7.5. The reference
|
|
141
|
+
* must be within roughly 180 NM of the true position - typically the
|
|
142
|
+
* aircraft's own last known position, or the receiver's location as a
|
|
143
|
+
* fallback for the first message from a new aircraft.
|
|
144
|
+
*
|
|
145
|
+
* @param format - Whether `frame` is an even (F=0) or odd (F=1) format frame.
|
|
146
|
+
* @param frame - Raw CPR fields from the frame.
|
|
147
|
+
* @param reference - A position known to be near the frame's true position.
|
|
148
|
+
* @returns The resolved position.
|
|
149
|
+
*/
|
|
150
|
+
export function decodeAirborneCprWithReference(format, frame, reference) {
|
|
151
|
+
return decodeCprWithReference(360, format, frame, reference);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolves an absolute position from a paired even and odd surface CPR
|
|
155
|
+
* frame (ADS-B BDS 0,6 - type codes 5-8), per DO-260B SS A.1.7.4. Surface
|
|
156
|
+
* CPR spans a 90 degree latitude zone (versus 360 degree for airborne) and
|
|
157
|
+
* is ambiguous across four longitude quadrants, so unlike the airborne
|
|
158
|
+
* pair decode, a reference position is required to resolve the correct
|
|
159
|
+
* hemisphere and quadrant - the receiver's own location is normally used.
|
|
160
|
+
*
|
|
161
|
+
* @param even - Raw CPR fields from the even-format frame.
|
|
162
|
+
* @param odd - Raw CPR fields from the odd-format frame.
|
|
163
|
+
* @param newerFormat - Which frame is more recent.
|
|
164
|
+
* @param reference - A position known to be within about 45 NM of the true position (typically the receiver's location).
|
|
165
|
+
* @returns The resolved position, or undefined if the two frames fall in different latitude zones.
|
|
166
|
+
*/
|
|
167
|
+
export function decodeSurfaceCprPair(even, odd, newerFormat, reference) {
|
|
168
|
+
const cprLatEven = even.latCpr / CPR_DENOMINATOR;
|
|
169
|
+
const cprLonEven = even.lonCpr / CPR_DENOMINATOR;
|
|
170
|
+
const cprLatOdd = odd.latCpr / CPR_DENOMINATOR;
|
|
171
|
+
const cprLonOdd = odd.lonCpr / CPR_DENOMINATOR;
|
|
172
|
+
const j = Math.floor(59 * cprLatEven - 60 * cprLatOdd + 0.5);
|
|
173
|
+
const latEvenNorth = (90 / 60) * (mod(j, 60) + cprLatEven);
|
|
174
|
+
const latOddNorth = (90 / 59) * (mod(j, 59) + cprLatOdd);
|
|
175
|
+
const latEvenSouth = latEvenNorth - 90;
|
|
176
|
+
const latOddSouth = latOddNorth - 90;
|
|
177
|
+
// Surface CPR is ambiguous over two 90 degree latitude zones (north and
|
|
178
|
+
// south of the equator) - resolve using whichever zone's newer-frame
|
|
179
|
+
// latitude lands closer to the reference, rather than the reference's own
|
|
180
|
+
// hemisphere (a receiver and aircraft can be on opposite sides of the
|
|
181
|
+
// equator while still satisfying the 45 NM limit).
|
|
182
|
+
const newerLatNorth = newerFormat === 'even' ? latEvenNorth : latOddNorth;
|
|
183
|
+
const newerLatSouth = newerFormat === 'even' ? latEvenSouth : latOddSouth;
|
|
184
|
+
const useNorth = Math.abs(reference.lat - newerLatNorth) <= Math.abs(reference.lat - newerLatSouth);
|
|
185
|
+
const latEven = useNorth ? latEvenNorth : latEvenSouth;
|
|
186
|
+
const latOdd = useNorth ? latOddNorth : latOddSouth;
|
|
187
|
+
const resolved = resolveCprPairLongitude(90, latEven, latOdd, cprLonEven, cprLonOdd, newerFormat);
|
|
188
|
+
if (resolved === undefined) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
// Surface longitude only narrows to one of four 90 degree quadrants -
|
|
192
|
+
// pick whichever wraps closest to the reference, treating longitude as
|
|
193
|
+
// circular (near the date line, -180 is one degree from +179, not 359).
|
|
194
|
+
let bestLon = resolved.lonFraction;
|
|
195
|
+
let bestLonDiff = Number.POSITIVE_INFINITY;
|
|
196
|
+
for (const quadrantOffset of [0, 90, 180, 270]) {
|
|
197
|
+
const candidateLon = normalizeLongitudeDeg(resolved.lonFraction + quadrantOffset);
|
|
198
|
+
const diff = Math.abs(normalizeLongitudeDeg(candidateLon - reference.lon));
|
|
199
|
+
if (diff < bestLonDiff) {
|
|
200
|
+
bestLonDiff = diff;
|
|
201
|
+
bestLon = candidateLon;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { lat: resolved.lat, lon: bestLon };
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Resolves an absolute position from a single surface CPR frame plus a
|
|
208
|
+
* known nearby reference position, per DO-260B SS A.1.7.6. The reference
|
|
209
|
+
* must be within roughly 45 NM of the true position - typically the
|
|
210
|
+
* receiver's own location, since surface traffic is by definition close to
|
|
211
|
+
* an airport the receiver can see.
|
|
212
|
+
*
|
|
213
|
+
* @param format - Whether `frame` is an even (F=0) or odd (F=1) format frame.
|
|
214
|
+
* @param frame - Raw CPR fields from the frame.
|
|
215
|
+
* @param reference - A position known to be near the frame's true position.
|
|
216
|
+
* @returns The resolved position.
|
|
217
|
+
*/
|
|
218
|
+
export function decodeSurfaceCprWithReference(format, frame, reference) {
|
|
219
|
+
return decodeCprWithReference(90, format, frame, reference);
|
|
220
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { EmergencyState } from './types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Decodes the 3-bit emergency state field of an ADS-B aircraft status
|
|
4
|
+
* message (BDS 6,1 subtype 1, type code 28).
|
|
5
|
+
*
|
|
6
|
+
* @param rawState - The raw 3-bit field, 0-7.
|
|
7
|
+
* @returns The decoded emergency state.
|
|
8
|
+
*/
|
|
9
|
+
export declare function decodeEmergencyState(rawState: number): EmergencyState;
|
|
10
|
+
//# sourceMappingURL=emergency-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emergency-status.d.ts","sourceRoot":"","sources":["../src/emergency-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcvD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAErE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** 3-bit emergency state field values, per ICAO Annex 10 Vol. IV / RTCA DO-260. */
|
|
2
|
+
const EMERGENCY_STATES = [
|
|
3
|
+
'none',
|
|
4
|
+
'general',
|
|
5
|
+
'lifeguardMedical',
|
|
6
|
+
'minimumFuel',
|
|
7
|
+
'noCommunications',
|
|
8
|
+
'unlawfulInterference',
|
|
9
|
+
'downed',
|
|
10
|
+
'reserved',
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Decodes the 3-bit emergency state field of an ADS-B aircraft status
|
|
14
|
+
* message (BDS 6,1 subtype 1, type code 28).
|
|
15
|
+
*
|
|
16
|
+
* @param rawState - The raw 3-bit field, 0-7.
|
|
17
|
+
* @returns The decoded emergency state.
|
|
18
|
+
*/
|
|
19
|
+
export function decodeEmergencyState(rawState) {
|
|
20
|
+
return EMERGENCY_STATES[rawState] ?? 'reserved';
|
|
21
|
+
}
|
package/dist/frame.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ModeSMessageEnvelope } from './types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the downlink format from a Mode-S message's first 5 bits.
|
|
4
|
+
*
|
|
5
|
+
* @param bytes - Raw message bytes (7 or 14 bytes).
|
|
6
|
+
* @returns The raw downlink format value, 0-31.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractDownlinkFormat(bytes: Uint8Array): number;
|
|
9
|
+
/**
|
|
10
|
+
* Computes the 24-bit Mode-S CRC remainder over an entire message (all
|
|
11
|
+
* bytes, including the trailing 24-bit parity field), via the standard
|
|
12
|
+
* bit-serial polynomial division used to check or recover Mode-S CRCs.
|
|
13
|
+
*
|
|
14
|
+
* See {@link ModeSMessageEnvelope.crcRemainder} for how to interpret the
|
|
15
|
+
* result - it is a pass/fail check for squitter formats (DF11/17/18) but a
|
|
16
|
+
* recovered ICAO address for surveillance-reply formats.
|
|
17
|
+
*
|
|
18
|
+
* @param bytes - Raw message bytes (7 or 14 bytes), parity field included.
|
|
19
|
+
* @returns The 24-bit CRC remainder.
|
|
20
|
+
*/
|
|
21
|
+
export declare function computeCrc24(bytes: Uint8Array): number;
|
|
22
|
+
/**
|
|
23
|
+
* Parses the shared envelope (downlink format, CRC remainder) out of a raw
|
|
24
|
+
* Mode-S message. {@link decodeModeSMessage} uses this to decide how to
|
|
25
|
+
* route a message before calling the per-type decoders - those decoders
|
|
26
|
+
* take a raw ME field or an already-extracted field value, not the
|
|
27
|
+
* envelope itself.
|
|
28
|
+
*
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { parseModeSFrame } from '@squawk/mode-s';
|
|
31
|
+
*
|
|
32
|
+
* const envelope = parseModeSFrame(rawMessageBytes);
|
|
33
|
+
* if (envelope.downlinkFormat === 17 && envelope.crcRemainder === 0) {
|
|
34
|
+
* // unmodified DF17 extended squitter, safe to decode further
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @param bytes - Raw message bytes (7 or 14 bytes).
|
|
39
|
+
* @returns The parsed envelope.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseModeSFrame(bytes: Uint8Array): ModeSMessageEnvelope;
|
|
42
|
+
//# sourceMappingURL=frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../src/frame.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAS7D;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAG/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAatD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,oBAAoB,CAMvE"}
|