beast-decoder
Version:
Parses messages coming from a BEAST TCP port. For example out.adsb.lol:1337 [adsb.lol](https://www.adsb.lol/docs/overview/introduction/)
1 lines • 24.3 kB
Source Map (JSON)
{"version":3,"sources":["../src/util/event_manager.ts","../src/parse.ts","../src/util/env.ts","../src/util/log.ts","../src/beast_handler.ts"],"sourcesContent":["\r\nexport default class EventManager<M extends {[key: string] : any}> {\r\n private listeners = new Map<String, Function[]>()\r\n\r\n addListener<E extends keyof M>(eventName: E, callback: (data: M[E])=> void){\r\n if(!this.listeners.has(eventName as string)){\r\n this.listeners.set(eventName as string, [])\r\n }\r\n\r\n this.listeners.get(eventName as string)!.push(callback)\r\n }\r\n\r\n removeListener<E extends keyof M>(eventName: E, callback: (data: M[E])=> void){\r\n if(this.listeners.has(eventName as string)){\r\n const list = this.listeners.get(eventName as string)\r\n\r\n const index = list!.findIndex(c => c === callback)\r\n\r\n if(index !== -1){\r\n list!.splice(index, 1)\r\n }\r\n\r\n }\r\n }\r\n\r\n protected dispatch<E extends keyof M>(eventName: E, data: M[E]){\r\n if(this.listeners.has(eventName as string)){\r\n const list = this.listeners.get(eventName as string)\r\n\r\n for(const l of list!){\r\n l(data)\r\n }\r\n\r\n }\r\n }\r\n}\r\n","\r\n// docs see https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats\r\n\r\nimport * as modeSAdsbParser from 'mode-s-adsb-parser'\r\nimport {logError, logWarning} from './util/log'\r\nimport {Byte} from './util/typescript'\r\n\r\ntype BeastBinaryFrame<T extends BeastBinaryFrameFormat> = {\r\n format: T,\r\n bytes: Byte[]\r\n}\r\n\r\nexport type BeastParsedFrameType = BeastParsedFrameType_Mode_AC | BeastParsedFrameType_Mode_S | BeastParsedFrameType_ADSB | BeastParsedFrameType_Debug\r\ntype BeastParsedFrameType_Mode_AC = 'MODE_AC'\r\ntype BeastParsedFrameType_Mode_S = 'MODE_S'\r\ntype BeastParsedFrameType_ADSB = 'ADSB'\r\ntype BeastParsedFrameType_Debug = 'DEBUG'\r\n\r\nexport type BeastParsedFrame<T extends BeastParsedFrameType> = {\r\n type: T,\r\n mlatTimestamp: number\r\n signalLevel: Byte\r\n dataBytes: Byte[]\r\n}\r\n\r\nexport function parseBeastSocketData(data: Buffer<ArrayBufferLike>, ignoreInvalid?: boolean){\r\n\r\n const frames = parseBeastLine(data, ignoreInvalid)\r\n\r\n const parsedFrames: BeastParsedFrame<BeastParsedFrameType>[]= []\r\n for(const frame of frames){\r\n try {\r\n parsedFrames.push(parseBeastFrame(frame))\r\n } catch (err){\r\n if(ignoreInvalid){\r\n //ignore\r\n logWarning('ignoring invalid frame', frame.format, frame.bytes)\r\n } else {\r\n throw err\r\n }\r\n }\r\n }\r\n return parsedFrames\r\n}\r\n\r\n\r\nconst ESCAPE_BYTE = 0x1a\r\n\r\nconst BEAST_BINARY_FRAME_FORMAT_MODE_AC: BeastBinaryFrameFormat_Mode_AC = 0x31\r\nconst BEAST_BINARY_FRAME_FORMAT_MODE_S_SHORT: BeastBinaryFrameFormat_Mode_S = 0x32\r\nconst BEAST_BINARY_FRAME_FORMAT_MODE_S_LONG: BeastBinaryFrameFormat_ADSB = 0x33\r\nconst BEAST_BINARY_FRAME_FORMAT_DEBUG: BeastBinaryFrameFormat_Debug = 0x34\r\n\r\n// just the content, no escape bytes, no frame format bytes\r\nconst BEAST_BINARY_FRAME_CONTENT_LENGTHS: Record<BeastBinaryFrameFormat, number>= {\r\n 0x31: 6 + 1 + 2,\r\n 0x32: 6 + 1 + 7,\r\n 0x33: 6 + 1 + 14,\r\n 0x34: 6 + 1 + 1 //TODO is this correct? documentation is unclear https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats\r\n}\r\n\r\ntype BeastBinaryFrameFormat = BeastBinaryFrameFormat_Mode_AC | BeastBinaryFrameFormat_Mode_S | BeastBinaryFrameFormat_ADSB | BeastBinaryFrameFormat_Debug\r\ntype BeastBinaryFrameFormat_Mode_AC = 0x31\r\ntype BeastBinaryFrameFormat_Mode_S = 0x32\r\ntype BeastBinaryFrameFormat_ADSB = 0x33\r\ntype BeastBinaryFrameFormat_Debug = 0x34\r\n\r\nconst BEAST_BINARY_FRAME_TYPES: Record<BeastBinaryFrameFormat, BeastParsedFrameType> = {\r\n '49': 'MODE_AC',\r\n '50': 'MODE_S',\r\n '51': 'ADSB',\r\n '52': 'DEBUG'\r\n}\r\n\r\n\r\nclass FormatError extends Error {\r\n constructor(message: string){\r\n super(message)\r\n }\r\n}\r\n\r\nclass FormatErrorWithByteIndex extends Error {\r\n constructor(message: string, byteIndex: number){\r\n super(message + ' at byte ' + byteIndex)\r\n }\r\n}\r\n\r\n\r\n/**\r\n *\r\n * @returns messages contained in that line (already unescaped)\r\n */\r\nexport function parseBeastLine(bytes: Buffer<ArrayBufferLike>, ignoreInvalid?: boolean){\r\n\r\n /* for documentation see\r\n\r\n https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats\r\n https://github.com/denzen84/dump1090/blob/master/sdr_beast.c @ static int copyBinMessageSafe()\r\n */\r\n\r\n\r\n const frames: BeastBinaryFrame<BeastBinaryFrameFormat>[] = [] // TODO is this efficient? shouldnt we use some kind of Buffer?\r\n\r\n if(bytes[0] !== ESCAPE_BYTE){\r\n if(ignoreInvalid){\r\n return []\r\n } else {\r\n throw new FormatErrorWithByteIndex('first byte is not an escape byte', 0)\r\n }\r\n }\r\n\r\n let currentFrameBytes: Byte[] = []\r\n let currentFrameFormat: BeastBinaryFrameFormat\r\n\r\n for(let byteIndex = 0; byteIndex < bytes.length; byteIndex++){\r\n const byte = bytes[byteIndex] as Byte\r\n\r\n if(currentFrameBytes.length === 0 && typeof currentFrameFormat !== 'number' && byte !== ESCAPE_BYTE){\r\n if(ignoreInvalid){\r\n break\r\n } else {\r\n throw new FormatErrorWithByteIndex('first byte of frame is not an escape byte', byteIndex)\r\n }\r\n }\r\n\r\n if(byte === ESCAPE_BYTE){\r\n const nextByteIndex = byteIndex+1\r\n if(bytes.length <= nextByteIndex){\r\n if(ignoreInvalid){\r\n break\r\n } else {\r\n throw new FormatErrorWithByteIndex('unexpected escape byte at the very end', byteIndex)\r\n }\r\n }\r\n\r\n const nextByte = bytes[nextByteIndex] as Byte\r\n\r\n if(nextByte === ESCAPE_BYTE){\r\n currentFrameBytes.push(nextByte)\r\n } else if(nextByte === BEAST_BINARY_FRAME_FORMAT_MODE_AC || nextByte === BEAST_BINARY_FRAME_FORMAT_MODE_S_SHORT || nextByte === BEAST_BINARY_FRAME_FORMAT_MODE_S_LONG || nextByte === BEAST_BINARY_FRAME_FORMAT_DEBUG){\r\n // new message starts\r\n\r\n if(currentFrameBytes.length > 0){\r\n makeFrameAndCleanup()\r\n }\r\n\r\n currentFrameFormat = nextByte\r\n } else {\r\n if(ignoreInvalid){\r\n currentFrameBytes.push(nextByte)\r\n } else {\r\n throw new FormatErrorWithByteIndex('unexpected escape byte (the following byte ' + (byteIndex+1) + ' does not need escaping)', byteIndex)\r\n }\r\n }\r\n\r\n byteIndex += 1 //skip processing of next byte\r\n } else {\r\n currentFrameBytes.push(byte)\r\n }\r\n }\r\n\r\n if(currentFrameBytes.length > 0){\r\n makeFrameAndCleanup()\r\n }\r\n\r\n function makeFrameAndCleanup(){\r\n if(typeof currentFrameFormat !== 'number'){\r\n if(ignoreInvalid){\r\n cleanup()\r\n return\r\n } else {\r\n throw new FormatError('can not make message, currentFrameFormat is undefined')\r\n }\r\n }\r\n\r\n const expectedFrameContentLength = BEAST_BINARY_FRAME_CONTENT_LENGTHS[currentFrameFormat]\r\n if(currentFrameBytes.length !== expectedFrameContentLength){\r\n if(ignoreInvalid){\r\n cleanup()\r\n return\r\n } else {\r\n logError(currentFrameBytes, Buffer.from(currentFrameBytes).toString('ascii'))\r\n throw new FormatError('invalid content byte length for frame type \"' + String.fromCharCode(currentFrameFormat) + '\": ' + currentFrameBytes.length + ' (expected ' + expectedFrameContentLength + ')')\r\n }\r\n }\r\n\r\n let allBytesAreNull = true\r\n for(const byte of currentFrameBytes){\r\n if(byte !== 0){\r\n allBytesAreNull = false\r\n break\r\n }\r\n }\r\n\r\n //skip empty frames\r\n if(!allBytesAreNull){\r\n frames.push({\r\n format: currentFrameFormat,\r\n bytes: currentFrameBytes\r\n })\r\n }\r\n\r\n cleanup()\r\n\r\n function cleanup(){\r\n\r\n currentFrameBytes = []\r\n currentFrameFormat = undefined\r\n }\r\n }\r\n\r\n return frames\r\n}\r\n\r\n\r\nexport function parseBeastFrame(frame: BeastBinaryFrame<BeastBinaryFrameFormat>) : BeastParsedFrame<BeastParsedFrameType> {\r\n\r\n const dataBytes = frame.bytes.slice(7)\r\n const mlatTimestamp = modeSAdsbParser.util.bits_and_bytes.bitStringToUnsignedIntegerSafeRangeFromBits(modeSAdsbParser.util.bits_and_bytes.bytesToFullBitString(frame.bytes.slice(0, 6)), 6*8, 'mlatTimestamp')\r\n const signalLevel = frame.bytes[6]\r\n\r\n if(frame.format < 49 || frame.format > 52){\r\n throw new Error('unexpected frame.format: ' + frame.format)\r\n }\r\n\r\n return {\r\n type: BEAST_BINARY_FRAME_TYPES[frame.format],\r\n mlatTimestamp,\r\n signalLevel,\r\n dataBytes\r\n }\r\n}\r\n\r\n","export function isDevEnvironment(){\r\n return process.env.NODE_ENV !== 'production'\r\n}\r\n","import { isDevEnvironment } from \"./env\"\r\n\r\nexport function logsAreEnabled(){\r\n return isDevEnvironment()\r\n}\r\n\r\nexport function logError(...args: any){\r\n if(logsAreEnabled()){\r\n console.error.apply(null, args)\r\n }\r\n}\r\n\r\nexport function logWarning(...args: any){\r\n if(logsAreEnabled()){\r\n console.warn.apply(null, args)\r\n }\r\n}\r\n","import EventManager from \"./util/event_manager\";\r\nimport {BeastParsedFrame, BeastParsedFrameType, parseBeastSocketData} from './parse'\r\nimport { decodeBasics, parseADSB, parseModeS, calculateUnambigiousLocation, CprData } from \"mode-s-adsb-parser\";\r\n\r\nexport type AircraftData = {\r\n icaoAddress: AircraftIcaoAddress\r\n}\r\n\r\nexport interface AircraftDataWithLocation extends AircraftData {\r\n location: {\r\n latitude: number\r\n longitude: number\r\n }\r\n}\r\n\r\nexport type BeastHandlerEvents = {\r\n 'aircraft_new': AircraftData // first time we see a message from this aircraft (aircraft not in the cache)\r\n 'aircraft_update': AircraftData // received message from aircraft\r\n 'aircraft_update_with_location': AircraftDataWithLocation // received message from aircraft and we can locate it\r\n 'warning': {message: string, context: any}\r\n 'removed_from_cache': AircraftIcaoAddress // no message received from this aircraft for some time\r\n}\r\n\r\ntype AircraftIcaoAddress = string\r\n\r\ntype EncodedCprData = {\r\n receivedTime: number\r\n cpr: CprData\r\n}\r\n\r\ntype AircraftCacheData = {\r\n lastDataReceivedTime: number\r\n lastUnambigiousLocation?: {\r\n latitude: number\r\n longitude: number\r\n }\r\n lastEncodedLocationOdd?: EncodedCprData\r\n lastEncodedLocationEven?: EncodedCprData\r\n}\r\n\r\nconst MAX_AIRCRAFT_CACHE_TIME = 1000 * 60 * 30 // 30 minutes\r\n\r\nexport class BeastHandler extends EventManager<BeastHandlerEvents> {\r\n\r\n private readonly aircraftCache = new Map<AircraftIcaoAddress, AircraftCacheData>()\r\n private cleanUpCacheInterval: NodeJS.Timeout\r\n\r\n /**\r\n * don't forget to call stop() once you are done\r\n */\r\n constructor(){\r\n super()\r\n\r\n this.cleanUpCacheInterval = setInterval(()=>{\r\n this.cleanUpCache()\r\n }, 1000 * 30)\r\n }\r\n\r\n stop(){\r\n clearInterval(this.cleanUpCacheInterval)\r\n this.aircraftCache.clear()\r\n }\r\n\r\n //TODO rewrite to be non-blocking\r\n private cleanUpCache(){\r\n\r\n const now = Date.now()\r\n\r\n for(const aircraftEntry of this.aircraftCache){\r\n const aircraft = aircraftEntry[1]\r\n\r\n if(now - aircraft.lastDataReceivedTime > MAX_AIRCRAFT_CACHE_TIME){\r\n const icaoAddress = aircraftEntry[0]\r\n this.aircraftCache.delete(icaoAddress)\r\n\r\n this.dispatch('removed_from_cache', icaoAddress)\r\n }\r\n }\r\n }\r\n\r\n\r\n handleBeastData(data: Buffer<ArrayBufferLike>){\r\n const frames = parseBeastSocketData(data, true)\r\n\r\n for(const frame of frames){\r\n this.handleBeastFrame(frame)\r\n }\r\n }\r\n\r\n private handleBeastFrame(frame: BeastParsedFrame<BeastParsedFrameType>){\r\n if(frame.type === 'ADSB'){\r\n try {\r\n const parsedModeS = parseModeS(frame.dataBytes)\r\n const parsedADSB = parseADSB(parsedModeS)\r\n\r\n const myIcaoAddressHex = icaoNumberToHex(parsedModeS.icaoAddress)\r\n\r\n const existingAircraft = this.aircraftCache.get(myIcaoAddressHex)\r\n const aircraft: AircraftCacheData = existingAircraft || {\r\n lastDataReceivedTime: Date.now()\r\n }\r\n\r\n this.aircraftCache.set(myIcaoAddressHex, aircraft)\r\n\r\n const decodedBasics = decodeBasics(parsedADSB)\r\n\r\n if(decodedBasics.locationInformation.locationAvailable){\r\n if(decodedBasics.locationInformation.location.format === 'even'){\r\n aircraft.lastEncodedLocationEven = {\r\n receivedTime: Date.now(),//TODO shouldnt we use mlatTimestamp for this?\r\n cpr: decodedBasics.locationInformation.location\r\n }\r\n } else {\r\n aircraft.lastEncodedLocationOdd = {\r\n receivedTime: Date.now(),//TODO shouldnt we use mlatTimestamp for this?\r\n cpr: decodedBasics.locationInformation.location\r\n }\r\n }\r\n }\r\n\r\n if(aircraft.lastEncodedLocationEven && aircraft.lastEncodedLocationOdd){\r\n if(Math.abs(aircraft.lastEncodedLocationEven.receivedTime - aircraft.lastEncodedLocationOdd.receivedTime) < 1000 * 60 * 2){// 2 minutes\r\n\r\n const olderMessage = aircraft.lastEncodedLocationEven.receivedTime < aircraft.lastEncodedLocationOdd.receivedTime ? aircraft.lastEncodedLocationEven.cpr : aircraft.lastEncodedLocationOdd.cpr\r\n\r\n const moreRecentMessage = aircraft.lastEncodedLocationEven.receivedTime < aircraft.lastEncodedLocationOdd.receivedTime ? aircraft.lastEncodedLocationOdd.cpr : aircraft.lastEncodedLocationEven.cpr\r\n\r\n const unambigiousLocation = calculateUnambigiousLocation([olderMessage, moreRecentMessage])\r\n\r\n aircraft.lastUnambigiousLocation = unambigiousLocation\r\n\r\n this.dispatch('aircraft_update_with_location', {\r\n icaoAddress: myIcaoAddressHex,\r\n location: unambigiousLocation\r\n })\r\n }\r\n }\r\n\r\n\r\n\r\n if(!existingAircraft){\r\n this.dispatch('aircraft_new', {\r\n icaoAddress: myIcaoAddressHex\r\n })\r\n } else {\r\n if(!aircraft.lastUnambigiousLocation){\r\n this.dispatch('aircraft_update', {\r\n icaoAddress: myIcaoAddressHex\r\n })\r\n }\r\n }\r\n\r\n } catch (err){\r\n this.dispatch('warning', {\r\n message: 'Failed to decode adsb frame',\r\n context: err\r\n })\r\n }\r\n } else if(frame.type === 'MODE_S') {\r\n try {\r\n const parsedModeS = parseModeS(frame.dataBytes)\r\n\r\n const myIcaoAddressHex = icaoNumberToHex(parsedModeS.icaoAddress)\r\n\r\n const existingAircraft = this.aircraftCache.get(myIcaoAddressHex)\r\n const aircraft: AircraftCacheData = existingAircraft || {\r\n lastDataReceivedTime: Date.now()\r\n }\r\n\r\n this.aircraftCache.set(myIcaoAddressHex, aircraft)\r\n\r\n if(existingAircraft){\r\n this.dispatch('aircraft_update', {\r\n icaoAddress: myIcaoAddressHex\r\n })\r\n } else {\r\n this.dispatch('aircraft_new', {\r\n icaoAddress: myIcaoAddressHex\r\n })\r\n }\r\n\r\n } catch (err){\r\n this.dispatch('warning', {\r\n message: 'Failed to decode mode s frame',\r\n context: err\r\n })\r\n }\r\n } else {\r\n this.dispatch('warning', {\r\n message: 'Unsupported frame type',\r\n context: frame.type\r\n })\r\n }\r\n }\r\n\r\n}\r\n\r\nfunction icaoNumberToHex(n: number){\r\n return n.toString(16)\r\n}\r\n"],"mappings":"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","names":[]}