@fourscore/readsb
v0.1.0
Published
TypeScript decoder for readsb / Mode-S / ADS-B: BEAST, AVR, SBS BaseStation, aircraft.json, and the underlying Mode-S frames.
Downloads
142
Maintainers
Readme
readsb
TypeScript decoder for readsb and the underlying Mode-S / ADS-B protocols. Includes:
- Mode-S frame decoder — DF 0/4/5/11/16/17/18/20/21, with CRC and address recovery (AP-XOR).
- ADS-B ME decoder — identification, surface position, airborne position (baro & GNSS), airborne velocity (subtypes 1–4), aircraft status, target state & status, operation status.
- CPR position decoder — global and local, airborne and surface, with the canonical ICAO Annex 10 NL boundary table.
- Comm-B / BDS — BDS 1,7 (capability), 2,0 (callsign), 4,0 (selected vertical intent), 5,0 (track and turn), 6,0 (heading and speed).
- Wire formats — streaming parsers for BEAST binary, AVR raw hex
(
*…;and@<MLAT>…;), and SBS BaseStation CSV. - readsb outputs — typed loader for
aircraft.jsonand a binary decoder foraircraft.binCraft/globe_*.binCraft(binCraft format version20250403). - Aircraft tracker — merges decoded messages into per-aircraft state with global+local CPR pairing.
ESM only. Tested on Bun and Node 18+.
Install
bun add readsb # or: npm i readsbQuick examples
Decode a single Mode-S frame
import { decodeModeS, decodeAdsb, fromHex } from "readsb";
const frame = fromHex("8D4840D6202CC371C32CE0576098");
const m = decodeModeS(frame);
// { df: 17, icao: "4840D6", crcOk: true, ca: 5 }
const adsb = decodeAdsb(frame);
// { kind: "identification", callsign: "KLM1023", category: 0, categorySet: "A", tc: 4 }Parse a BEAST stream
import { BeastParser, decodeModeS } from "readsb";
const parser = new BeastParser();
for await (const chunk of socket) {
for (const frame of parser.parse(chunk)) {
if (frame.type !== "mode-s-long" && frame.type !== "mode-s-short") continue;
const decoded = decodeModeS(frame.bytes);
console.log(frame.timestamp, decoded);
}
}Decode a CPR pair
import { decodeCprGlobalAirborne } from "readsb";
const pos = decodeCprGlobalAirborne(
{ lat: 93000, lon: 51372, format: "even" },
{ lat: 74158, lon: 50194, format: "odd" },
);
// { lat: 52.25720, lon: 3.91937 }Track aircraft over time
import { Tracker, parseBeast } from "readsb";
const tracker = new Tracker({ receiverLocation: { lat: 52.0, lon: 4.0 } });
for (const f of parseBeast(buf)) {
tracker.ingest(f);
}
console.log(tracker.aircraft());Decode a aircraft.binCraft.zst snapshot
import { decodeBinCraft } from "readsb";
import { readFileSync } from "node:fs";
import { decompress } from "fzstd"; // or run `zstd -d` externally
const compressed = readFileSync("./aircraft.binCraft.zst");
const bytes = decompress(compressed);
const snap = decodeBinCraft(bytes);
console.log(`${snap.aircraft.length} aircraft, snapshot ${new Date(snap.header.nowMs)}`);
for (const a of snap.aircraft) {
console.log(a.icao, a.callsign, a.lat, a.lon, a.altBaroFt);
}Public API
| Module | Exports |
|---|---|
| Mode-S | decodeModeS, expectedLength, crc24, parityField, decodeAC12, decodeAC13, decodeID13 |
| ADS-B | decodeAdsb, typeCode, meField |
| CPR | decodeCprGlobalAirborne, decodeCprGlobalSurface, decodeCprLocalAirborne, decodeCprLocalSurface, nl |
| Comm-B | decodeCommB, guessBds |
| Wire | BeastParser/parseBeast, AvrParser/parseAvr, SbsParser/parseSbs/parseSbsLine |
| readsb | parseAircraftJson, decodeBinCraft |
| State | Tracker |
All types are exported from the package root.
Limitations
- Mode-A/C-only frames (type
0x31in BEAST) are surfaced asBeastFramebut no further decoding is performed. - BDS register inference for Comm-B replies is best-effort. Pass an explicit register to
decodeCommBwhen known; otherwise callguessBdsand treat the result as a hint. - Military DF19 and Comm-D ELM (DF24) are passed through but not interpreted.
Development
bun install
bun test
bun run typecheck
bun run buildDecode the bundled example.readsb (a base64-encoded zstd-compressed binCraft snapshot):
bun run scripts/decode-example.tsLicense
MIT
