Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions src/main/java/de/serosystems/example/ExampleDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ public void decodeMsg(long timestamp, String raw, Position receiver) {
AirborneOperationalStatusV1Msg opstatA1 = (AirborneOperationalStatusV1Msg) msg;
System.out.println("["+icao24+"]: Using ADS-B version "+opstatA1.getVersion());
System.out.println(" Barometric altitude cross-checked: "+opstatA1.getBarometricAltitudeIntegrityCode());
System.out.println(" Gemoetric vertical accuracy: "+opstatA1.getGeometricVerticalAccuracy()+"m");

if (opstatA1.getHorizontalReferenceDirection())
System.out.println(" Horizontal reference: true north");
Expand All @@ -225,18 +224,18 @@ public void decodeMsg(long timestamp, String raw, Position receiver) {
System.out.println(" Position Uncertainty (based on NACp): " + opstatA1.getPositionUncertainty());
System.out.println(" Has NIC supplement A: " + opstatA1.hasNICSupplementA());
System.out.println(" Surveillance/Source Integrity Level (SIL): " + opstatA1.getSIL());
System.out.println(" System design assurance: " + opstatA1.getSystemDesignAssurance());
System.out.println(" Has 1090ES in: " + opstatA1.has1090ESIn());
System.out.println(" Has 1090 ES In: " + opstatA1.has1090ESIn());
System.out.println(" IDENT switch active: " + opstatA1.hasActiveIDENTSwitch());
System.out.println(" Has operational TCAS: " + opstatA1.hasOperationalTCAS());
System.out.println(" Has TCAS resolution advisory: " + opstatA1.hasTCASResolutionAdvisory());
System.out.println(" Has UAT in: " + opstatA1.hasUATIn());
System.out.println(" Uses single antenna: " + opstatA1.hasSingleAntenna());
System.out.println(" Supports air-referenced velocity reports: " + opstatA1.hasAirReferencedVelocity());

// SIL supplement in version 2
if (msg instanceof AirborneOperationalStatusV2Msg) {
System.out.println(" Gemoetric vertical accuracy: "+((AirborneOperationalStatusV2Msg) msg).getGeometricVerticalAccuracy()+"m");
System.out.println(" System design assurance: " + ((AirborneOperationalStatusV2Msg) msg).getSystemDesignAssurance());
System.out.println(" Has UAT in: " + ((AirborneOperationalStatusV2Msg) msg).hasUATIn());
System.out.println(" Has SIL supplement: " + ((AirborneOperationalStatusV2Msg) msg).hasSILSupplement());
System.out.println(" Uses single antenna: " + ((AirborneOperationalStatusV2Msg) msg).hasSingleAntenna());
}

break;
Expand All @@ -245,7 +244,6 @@ public void decodeMsg(long timestamp, String raw, Position receiver) {
SurfaceOperationalStatusV1Msg opstatS1 = (SurfaceOperationalStatusV1Msg) msg;

System.out.println("["+icao24+"]: Using ADS-B version "+opstatS1.getVersion());
System.out.println(" Gemoetric vertical accuracy: "+opstatS1.getGeometricVerticalAccuracy()+"m");

if (opstatS1.getHorizontalReferenceDirection())
System.out.println(" Horizontal reference: true north");
Expand All @@ -254,24 +252,23 @@ public void decodeMsg(long timestamp, String raw, Position receiver) {
System.out.println(" Navigation Accuracy Category for position (NACp): " + opstatS1.getNACp());
System.out.println(" Position Uncertainty (based on NACp): " + opstatS1.getPositionUncertainty());
System.out.println(" Has NIC supplement A: " + opstatS1.hasNICSupplementA());
System.out.println(" Has NIC supplement C: " + opstatS1.getNICSupplementC());
System.out.println(" Surveillance/Source Integrity Level (SIL): " + opstatS1.getSIL());
System.out.println(" System design assurance: " + opstatS1.getSystemDesignAssurance());
System.out.println(" Has 1090ES in: " + opstatS1.has1090ESIn());
System.out.println(" Has 1090 ES In: " + opstatS1.has1090ESIn());
System.out.println(" IDENT switch active: " + opstatS1.hasActiveIDENTSwitch());
System.out.println(" Has TCAS resolution advisory: " + opstatS1.hasTCASResolutionAdvisory());
System.out.println(" Has UAT in: " + opstatS1.hasUATIn());
System.out.println(" Uses single antenna: " + opstatS1.hasSingleAntenna());
System.out.println(" Airplane length: " + opstatS1.getAirplaneLength() + "m");
System.out.println(" Airplane width: " + opstatS1.getAirplaneWidth() + "m");
System.out.println(" Navigation Accuracy Category for velocity (NACv): " + opstatS1.getNACv());
System.out.println(" Low (<70W) TX power: " + opstatS1.hasLowTxPower());
System.out.println(" Encoded GPS antenna offset: " + opstatS1.getGPSAntennaOffset());
System.out.println(" Has track heading info: " + opstatS1.hasTrackHeadingInfo());

// SIL supplement in version 2
if (msg instanceof SurfaceOperationalStatusV2Msg) {
System.out.println(" Has NIC supplement C: " + ((SurfaceOperationalStatusV2Msg) msg).getNICSupplementC());
System.out.println(" System design assurance: " + ((SurfaceOperationalStatusV2Msg) msg).getSystemDesignAssurance());
System.out.println(" Navigation Accuracy Category for velocity (NACv): " + ((SurfaceOperationalStatusV2Msg) msg).getNACv());
System.out.println(" Has SIL supplement: " + ((SurfaceOperationalStatusV2Msg) msg).hasSILSupplement());
System.out.println(" Has UAT in: " + ((SurfaceOperationalStatusV2Msg) msg).hasUATIn());
System.out.println(" Encoded GPS antenna offset: " + ((SurfaceOperationalStatusV2Msg) msg).getGPSAntennaOffset());
System.out.println(" Uses single antenna: " + ((SurfaceOperationalStatusV2Msg) msg).hasSingleAntenna());
}

break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ public ModeSDownlinkMsg decode(ModeSDownlinkMsg modes, long timestamp) throws Ba
case 1:
SurfaceOperationalStatusV1Msg s1 = new SurfaceOperationalStatusV1Msg(es1090);
dd.nicSupplA = s1.hasNICSupplementA();
dd.nicSupplC = s1.getNICSupplementC();
return s1;
case 2:
SurfaceOperationalStatusV2Msg s2 = new SurfaceOperationalStatusV2Msg(es1090);
Expand Down
124 changes: 124 additions & 0 deletions src/main/java/de/serosystems/lib1090/decoding/BitReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package de.serosystems.lib1090.decoding;

import java.util.Objects;

/**
* A stateless utility for extracting bits from byte arrays using absolute bit ranges.
* <p>
* <b>Indexing Convention:</b> This class uses <b>1-based indexing</b> to match
* technical specifications like ICAO Annex 10.
* <ul>
* <li>Bit 1: The Most Significant Bit (MSB) of the first byte.</li>
* <li>Bit 8: The Least Significant Bit (LSB) of the first byte.</li>
* <li>Bit 9: The MSB of the second byte.</li>
* </ul>
* <p>
* Example: {@code readInt(1, 5)} extracts the first 5 bits of the message.
*/
public class BitReader {
private final byte[] data;
private final boolean bigEndian;

private BitReader(byte[] data, boolean bigEndian) {
this.data = Objects.requireNonNull(data);
this.bigEndian = bigEndian;
}

/**
* Factory for Big-Endian bit ordering (Network Byte Order).
* Bit 1 is the MSB of data[0].
*/
public static BitReader forBigEndian(byte[] data) {
return new BitReader(data, true);
}

/**
* Factory for Little-Endian bit ordering.
* Bit 1 is the LSB of data[0].
*/
public static BitReader forLittleEndian(byte[] data) {
return new BitReader(data, false);
}

public byte readByte(int from, int to) {
return (byte) readRange(from, to, 8);
}

public short readShort(int from, int to) {
return (short) readRange(from, to, 16);
}

public int readInt(int from, int to) {
return (int) readRange(from, to, 32);
}

public long readLong(int from, int to) {
return readRange(from, to, 64);
}

/**
* Internal extraction logic.
* * @param from The starting bit position (inclusive, starts at 1).
*
* @param to The ending bit position (inclusive, starts at 1).
* @param maxBits Maximum allowed bit span for the return type.
* @return The extracted value as a long.
*/
private long readRange(int from, int to, int maxBits) {
if (from < 1) {
throw new IllegalArgumentException("Start bit must be >= 1.");
}

if (to < from) {
throw new IllegalArgumentException("End bit < start bit.");
}

int numBits = (to - from) + 1;
if (numBits > maxBits) {
throw new IllegalArgumentException("Range exceeds type capacity.");
}
if (to > data.length * 8) {
throw new IndexOutOfBoundsException("End of buffer.");
}

if (bigEndian) {
return readBigEndian(from, to, numBits);
} else {
return readLittleEndian(from, to);
}
}

/**
* Optimized Byte-Block extraction for Big-Endian.
*/
private long readBigEndian(int from, int to, int numBits) {
int startBit0 = from - 1;
int endBit0 = to - 1;
int startByte = startBit0 / 8;
int endByte = endBit0 / 8;

long value = 0;
for (int i = startByte; i <= endByte; i++) {
value = (value << 8) | (data[i] & 0xFFL);
}

int bitsInLastByte = 7 - (endBit0 % 8);
value >>>= bitsInLastByte;
long mask = (numBits == 64) ? -1L : (1L << numBits) - 1;
return value & mask;
}

/**
* Bit-by-bit extraction for Little-Endian to ensure LSB-first accuracy.
*/
private long readLittleEndian(int from, int to) {
long value = 0;
for (int i = to - 1; i >= from - 1; i--) {
int byteIdx = i / 8;
int bitIdxInByte = i % 8; // LSB is bit 0
long bit = (data[byteIdx] >> bitIdxInByte) & 1;
value = (value << 1) | bit;
}
return value;
}
}
Loading