Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
acdc21e
SurfaceOperationalStatusV2Msg: V2 compatible implementation for `hasP…
mrksngl Apr 1, 2026
39a570a
SurfaceOperationalStatusV2Msg: improved methods for getting GPS anten…
mrksngl Apr 2, 2026
99a3899
TargetStateAndStatusMsg: split into V1/V2 with common interface, adap…
mrksngl Apr 6, 2026
50401c2
EmergencyOrPriorityStatusV2Msg: new class, moved mode a code there
mrksngl Apr 6, 2026
5fae89f
StatefulModeSDecoder: do not throw on undefined Operational Status V0…
mrksngl Apr 6, 2026
0a3074d
ModeACodeV1Msg: new message to handle V1 Mode A Code. Extended decode…
mrksngl Apr 6, 2026
bafe6b0
AirspeedHeadingMsg: fixed notes on heading status bit
mrksngl Apr 6, 2026
2ef2e30
AirspeedHeadingMsg: return raw NACv, as in VelocityOverGroundMsg. Ref…
mrksngl Apr 6, 2026
3c525ec
AirborneVelocityMessage: common interface for AirspeedHeadingMsg and …
mrksngl Apr 6, 2026
fe12343
AirborneOperationalStatusV1Msg and SurfaceOperationalStatusV1Msg: che…
mrksngl Apr 1, 2026
29a6552
OperationalStatusV0Msg: check enroute capability class in constructor
mrksngl Apr 1, 2026
6d15246
AirborneOperationalStatusV1Msg and SurfaceOperationalStatusV1Msg: add…
mrksngl Apr 1, 2026
b9014cd
AirborneOperationalStatusV1Msg: added method to get Target Change Rep…
mrksngl Apr 1, 2026
49cb925
TargetStateAndStatusMsg: fixed typos and comments
mrksngl Apr 6, 2026
947f4b3
PositionMsg: moved `hasTimeFlag()` from all implementing classes to i…
mrksngl Apr 1, 2026
877af55
SurfaceOperationalStatusV1Msg: renamed internal field
mrksngl Apr 1, 2026
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
98 changes: 62 additions & 36 deletions src/main/java/de/serosystems/example/ExampleDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
* comma-separated timestamp, receiver latitude, receiver longitude and the
* raw Mode S/ADS-B message. Receiver coordinates can be omitted. In that
* case, surface position messages cannot be decoded properly and plausibility
* checks for positions are limited.
*
* checks for positions are limited.
*
* Example input:
*
*
* 1,8d4b19f39911088090641010b9b0
* 2,8d4ca513587153a8184a2fb5adeb
* 3,8d3413c399014e23c80f947ce87c
Expand All @@ -46,7 +46,7 @@
* 7,5d506c28000000
* 8,a8000102fe81c1000000004401e3
* 9,a0001839000000000000004401e3
*
*
* @author Matthias Schäfer (schaefer@sero-systems.de)
* @author Markus Fuchs (fuchs@opensky-network.org)
*/
Expand Down Expand Up @@ -176,9 +176,16 @@ public void decodeMsg(long timestamp, String raw, Position receiver) {

break;
case ADSB_EMERGENCY:
case ADSB_EMERGENCY_V2:
EmergencyOrPriorityStatusMsg status = (EmergencyOrPriorityStatusMsg) msg;
System.out.println("["+icao24+"]: "+status.getEmergencyStateText());
System.out.println(" Mode A code is "+status.getIdentity());
if (status instanceof EmergencyOrPriorityStatusV2Msg) {
System.out.println(" Mode A code is "+((EmergencyOrPriorityStatusV2Msg) status).getIdentity());
}
break;
case ADSB_MODE_A_CODE_V1:
ModeACodeV1Msg modeACode = (ModeACodeV1Msg) msg;
System.out.println("["+icao24+"]: Mode A code is "+modeACode.getIdentity());
break;
case ADSB_AIRSPEED:
AirspeedHeadingMsg airspeed = (AirspeedHeadingMsg) msg;
Expand Down Expand Up @@ -291,40 +298,59 @@ public void decodeMsg(long timestamp, String raw, Position receiver) {

break;
case ADSB_TARGET_STATE_AND_STATUS:
TargetStateAndStatusMsg tStatus = (TargetStateAndStatusMsg) msg;
System.out.println("["+icao24+"]: Target State and Status reported");
System.out.println(" Navigation Accuracy Category for position (NACp): " + tStatus.getNACp());
System.out.println(" Has operational TCAS: " + tStatus.hasOperationalTCAS());
System.out.println(" Surveillance/Source Integrity Level (SIL): " + tStatus.getSIL());
System.out.println(" Has SIL supplement: " + tStatus.hasSILSupplement());
System.out.println(" Barometric altitude cross-checked: " + tStatus.getBarometricAltitudeIntegrityCode());

System.out.printf(" Selected altitude is derived from %s\n", tStatus.isFMSSelectedAltitude() ? "FMS" : "MCP/FCU");
if (tStatus.hasSelectedAltitudeInfo()) {
System.out.println(" Selected altitude: " + tStatus.getSelectedAltitude() + " ft");
} else {
System.out.println(" No selected altitude info");
}

if (tStatus.hasBarometricPressureSettingInfo()) {
System.out.println(" Barometric pressure setting (minus 800 mbar): " + tStatus.getBarometricPressureSetting() + " mbar");
if (msg instanceof TargetStateAndStatusMsgV1) {
TargetStateAndStatusMsgV1 tStatus = (TargetStateAndStatusMsgV1) msg;
System.out.println(" Navigation Accuracy Category for position (NACp): " + tStatus.getNACp());
System.out.println(" Has operational TCAS: " + tStatus.hasOperationalTCAS());
System.out.println(" Surveillance/Source Integrity Level (SIL): " + tStatus.getSIL());
System.out.println(" Barometric altitude cross-checked: " + tStatus.getBarometricAltitudeIntegrityCode());
if (tStatus.hasSelectedAltitudeInfo()) {
System.out.println(" Selected altitude: " + tStatus.getSelectedAltitude() + " ft");
} else {
System.out.println(" No selected altitude info");
}
if (tStatus.hasSelectedHeadingInfo()) {
System.out.println(" Selected heading: " + tStatus.getSelectedHeading() + "°");
} else {
System.out.println(" No selected heading info");
}
} else {
System.out.println(" No barometric pressure setting info");
}

if (tStatus.hasSelectedHeadingInfo()) {
System.out.println(" Selected heading: " + tStatus.getSelectedHeading() + "°");
} else {
System.out.println(" No selected heading info");
}
if (tStatus.hasModeInfo()) {
System.out.printf(" Autopilot is%s enganged\n", tStatus.hasAutopilotEngaged() ? "" : " not");
System.out.printf(" VNAV mode is%s enganged\n", tStatus.hasVNAVModeEngaged() ? "" : " not");
System.out.printf(" Altitude hold mode is%s enganged\n", tStatus.hasActiveAltitudeHoldMode() ? "" : " not");
System.out.printf(" Approach mode is%s enganged\n", tStatus.hasActiveApproachMode() ? "" : " not");
System.out.printf(" LNAV mode is%s enganged\n", tStatus.hasLNAVModeEngaged() ? "" : " not");
} else {
System.out.println(" No MCP/FCU mode info");
TargetStateAndStatusMsgV2 tStatus = (TargetStateAndStatusMsgV2) msg;
System.out.println(" Navigation Accuracy Category for position (NACp): " + tStatus.getNACp());
System.out.println(" Has operational TCAS: " + tStatus.hasOperationalTCAS());
System.out.println(" Surveillance/Source Integrity Level (SIL): " + tStatus.getSIL());
System.out.println(" Has SIL supplement: " + tStatus.hasSILSupplement());
System.out.println(" Barometric altitude cross-checked: " + tStatus.getBarometricAltitudeIntegrityCode());

System.out.printf(" Selected altitude is derived from %s\n", tStatus.isFMSSelectedAltitude() ? "FMS" : "MCP/FCU");
if (tStatus.hasSelectedAltitudeInfo()) {
System.out.println(" Selected altitude: " + tStatus.getSelectedAltitude() + " ft");
} else {
System.out.println(" No selected altitude info");
}

if (tStatus.hasBarometricPressureSettingInfo()) {
System.out.println(" Barometric pressure setting (minus 800 mbar): " + tStatus.getBarometricPressureSetting() + " mbar");
} else {
System.out.println(" No barometric pressure setting info");
}

if (tStatus.hasSelectedHeadingInfo()) {
System.out.println(" Selected heading: " + tStatus.getSelectedHeading() + "°");
} else {
System.out.println(" No selected heading info");
}
if (tStatus.hasModeInfo()) {
System.out.printf(" Autopilot is%s enganged\n", tStatus.hasAutopilotEngaged() ? "" : " not");
System.out.printf(" VNAV mode is%s enganged\n", tStatus.hasVNAVModeEngaged() ? "" : " not");
System.out.printf(" Altitude hold mode is%s enganged\n", tStatus.hasActiveAltitudeHoldMode() ? "" : " not");
System.out.printf(" Approach mode is%s enganged\n", tStatus.hasActiveApproachMode() ? "" : " not");
System.out.printf(" LNAV mode is%s enganged\n", tStatus.hasLNAVModeEngaged() ? "" : " not");
} else {
System.out.println(" No MCP/FCU mode info");
}
}

break;
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/de/serosystems/lib1090/StatefulModeSDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ public ModeSDownlinkMsg decode(ModeSDownlinkMsg modes, long timestamp) throws Ba
}
}

if (ftc == 23) { // Test Message, check subtype
int subtype = es1090.getMessage()[0] & 0x7;
if (subtype == 7 && dd.adsbVersion == 1) // Mode A code
return new ModeACodeV1Msg(es1090);
}

if (ftc == 24) {
int subtype = es1090.getMessage()[0] & 0x7;
if (subtype == 1)
Expand All @@ -181,11 +187,10 @@ public ModeSDownlinkMsg decode(ModeSDownlinkMsg modes, long timestamp) throws Ba

if (ftc == 29) {
int subtype = (es1090.getMessage()[0]>>>1) & 0x3;
// DO-260B 2.2.3.2.7.1: ignore for ADS-B v0 transponders if ME bit 11 != 0
boolean hasMe11Bit = (es1090.getMessage()[1]&0x20) != 0;

if (subtype == 1 && (dd.adsbVersion > 0 || !hasMe11Bit)) {
return new TargetStateAndStatusMsg(es1090);
if (subtype == 0 && dd.adsbVersion == 1) {
return new TargetStateAndStatusMsgV1(es1090);
} else if (subtype == 1 && dd.adsbVersion == 2) {
return new TargetStateAndStatusMsgV2(es1090);
}
}

Expand All @@ -212,8 +217,8 @@ public ModeSDownlinkMsg decode(ModeSDownlinkMsg modes, long timestamp) throws Ba
} else if (subtype == 1) {
// surface
switch (dd.adsbVersion) {
case 0:
return new OperationalStatusV0Msg(es1090);
case 0: // undefined subtype for v0, handle like any other undefined subtype
break;
case 1:
SurfaceOperationalStatusV1Msg s1 = new SurfaceOperationalStatusV1Msg(es1090);
dd.nicSupplA = s1.hasNICSupplementA();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
/**
* @author Markus Fuchs (fuchs@sero-systems.de)
*/
public final class VelocityOverGround {
public final class AirborneVelocity {

private VelocityOverGround() {}
private AirborneVelocity() {}

/**
* The 95% accuracy for horizontal velocity. We interpret the coding according to
* DO-260B Table 2-22 for all ADS-B versions.
* DO-260B Table 2-22 for all ADS-B family airborne velocity messages.
* @return Navigation Accuracy Category for velocity according to RTCA DO-260B 2.2.3.2.6.1.5 in m/s, -1 means
* "unknown" or >10m
*/
public static float decodeAccuracyBound(byte navigationAccuracyCategory) {
switch(navigationAccuracyCategory) {
case 1: return 10;
case 2: return 3;
case 3: return 1;
switch (navigationAccuracyCategory) {
case 1: return 10.f;
case 2: return 3.f;
case 3: return 1.f;
case 4: return 0.3f;
default: return -1;
default: return -1.f;
}
}
}
25 changes: 0 additions & 25 deletions src/main/java/de/serosystems/lib1090/decoding/Airspeed.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ public enum subtype {
ADSB_SURFACE_POSITION_V1,
ADSB_SURFACE_POSITION_V2,
ADSB_AIRSPEED,
ADSB_MODE_A_CODE_V1,
ADSB_EMERGENCY,
ADSB_EMERGENCY_V2,
ADSB_TCAS,
ADSB_VELOCITY,
ADSB_IDENTIFICATION,
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/de/serosystems/lib1090/msgs/PositionMsgWithTime.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package de.serosystems.lib1090.msgs;

public interface PositionMsgWithTime extends PositionMsg {

/**
* @return flag which will indicate whether the Time of Applicability of the message
* is synchronized with UTC time. False will denote that the time is not synchronized
* to UTC. True will denote that Time of Applicability is synchronized to UTC time.
*/
boolean hasTimeFlag();

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public AirborneOperationalStatusV1Msg(byte[] raw_message) throws BadFormatExcept
/**
* @param squitter extended squitter which contains this message
* @throws BadFormatException if message has the wrong typecode or ADS-B version or is not an airborne
* operational status message or the capability code is invalid.
* operational status message or the capability class code or operational mode
* code is invalid.
* @throws UnspecifiedFormatError if message has the wrong subtype
*/
public AirborneOperationalStatusV1Msg(ExtendedSquitter squitter) throws BadFormatException, UnspecifiedFormatError {
Expand Down Expand Up @@ -101,6 +102,8 @@ public AirborneOperationalStatusV1Msg(ExtendedSquitter squitter) throws BadForma

if ((capability_class_code & 0xC000) != 0)
throw new BadFormatException("Unknown capability class code!");
if ((operational_mode_code & 0xC000) != 0)
throw new BadFormatException("Unknown operational mode code!");

nic_suppl = b.readByte(44, 44) == 1;
nac_pos = b.readByte(45, 48);
Expand Down Expand Up @@ -143,12 +146,27 @@ public boolean hasTargetStateReport() {

/**
* @return whether target change reports are supported
* @see #getTargetChangeReportCapability() returns true if this getter returns 1 or 2
*/
public boolean supportsTargetChangeReport() {
byte target_change_report_capability = (byte) ((capability_class_code & 0xC0) >>> 6);
byte target_change_report_capability = getTargetChangeReportCapability();
return target_change_report_capability == 1 || target_change_report_capability == 2;
}

/**
* Get target change report capability.
* <ul>
* <li>0: Not supported</li>
* <li>1: Supports TC+0 only</li>
* <li>2: Supports multiple TCs</li>
* <li>3: Reserved</li>
* </ul>
* @return target change report capability
*/
public byte getTargetChangeReportCapability() {
return (byte) ((capability_class_code & 0xC0) >>> 6);
}

/**
* @return whether TCAS Resolution Advisory (RA) is active
*/
Expand All @@ -163,6 +181,13 @@ public boolean hasActiveIDENTSwitch() {
return (operational_mode_code & 0x1000) != 0;
}

/**
* @return whether ADS-B Transmitting Subsystem is receiving ATC services.
*/
public boolean hasReceivingATCServices() {
return (operational_mode_code & 0x800) != 0;
}

/**
* @return the version number of the formats and protocols in use on the aircraft installation.<br>
* 0: Conformant to DO-260/ED-102 and DO-242<br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import de.serosystems.lib1090.decoding.Altitude;
import de.serosystems.lib1090.exceptions.BadFormatException;
import de.serosystems.lib1090.exceptions.UnspecifiedFormatError;
import de.serosystems.lib1090.msgs.PositionMsg;
import de.serosystems.lib1090.msgs.PositionMsgWithTime;
import de.serosystems.lib1090.msgs.modes.ExtendedSquitter;

import java.io.Serializable;
Expand All @@ -33,7 +33,7 @@
* @author Matthias Schäfer (schaefer@sero-systems.de)
*/
@SuppressWarnings("unused")
public class AirbornePositionV0Msg extends ExtendedSquitter implements Serializable, PositionMsg {
public class AirbornePositionV0Msg extends ExtendedSquitter implements Serializable, PositionMsgWithTime {

private static final long serialVersionUID = 5661463389938495220L;

Expand Down Expand Up @@ -215,11 +215,7 @@ public boolean hasSingleAntenna() {
return antenna_or_nic_suppl_b;
}

/**
* @return flag which will indicate whether or not the Time of Applicability of the message
* is synchronized with UTC time. False will denote that the time is not synchronized
* to UTC. True will denote that Time of Applicability is synchronized to UTC time.
*/
@Override
public boolean hasTimeFlag() {
return time_flag;
}
Expand Down
Loading
Loading