diff --git a/src/main/java/de/serosystems/example/ExampleDecoder.java b/src/main/java/de/serosystems/example/ExampleDecoder.java index 8022654..3be9fe4 100644 --- a/src/main/java/de/serosystems/example/ExampleDecoder.java +++ b/src/main/java/de/serosystems/example/ExampleDecoder.java @@ -298,7 +298,8 @@ public void decodeMsg(long timestamp, String raw, Position receiver) { System.out.println(" Has IFR capability: " + veloc.hasIFRCapability()); break; - case ADSB_TARGET_STATE_AND_STATUS: + case ADSB_TARGET_STATE_AND_STATUS_V1: + case ADSB_TARGET_STATE_AND_STATUS_V2: System.out.println("["+icao24+"]: Target State and Status reported"); if (msg instanceof TargetStateAndStatusV1Msg) { TargetStateAndStatusV1Msg tStatus = (TargetStateAndStatusV1Msg) msg; diff --git a/src/main/java/de/serosystems/lib1090/msgs/ModeSDownlinkMsg.java b/src/main/java/de/serosystems/lib1090/msgs/ModeSDownlinkMsg.java index 24b27f6..f222b77 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/ModeSDownlinkMsg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/ModeSDownlinkMsg.java @@ -79,7 +79,8 @@ public enum subtype { ADSB_SURFACE_STATUS_V1, ADSB_AIRBORN_STATUS_V2, ADSB_SURFACE_STATUS_V2, - ADSB_TARGET_STATE_AND_STATUS, + ADSB_TARGET_STATE_AND_STATUS_V1, + ADSB_TARGET_STATE_AND_STATUS_V2, SURFACE_SYSTEM_STATUS, // TIS-B subtypes diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1Msg.java index c762c48..e85c82a 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1Msg.java @@ -32,11 +32,11 @@ * * @author Matthias Schäfer (schaefer@sero-systems.de) */ -public class AirborneOperationalStatusV1Msg extends ExtendedSquitter implements Serializable { +public class AirborneOperationalStatusV1Msg extends ExtendedSquitter implements Serializable, OperationalStatusV1Msg { private static final long serialVersionUID = -4371842571556132611L; + private static final byte SUBTYPE_CODE = 0; - private byte subtype_code; protected int capability_class_code; // actually 16 bit unsigned protected int operational_mode_code; // actually 16 bit unsigned private byte version; @@ -90,10 +90,10 @@ public AirborneOperationalStatusV1Msg(ExtendedSquitter squitter) throws BadForma BitReader b = BitReader.forBigEndian(msg); - subtype_code = b.readByte(6, 8); - if (subtype_code > 1) { // currently only 0 and 1 specified, 2-7 are reserved - throw new UnspecifiedFormatError("Operational status message subtype " + subtype_code + " reserved."); - } else if (subtype_code != 0) { + byte subtypeCode = b.readByte(6, 8); + if (subtypeCode > 1) { // currently only 0 and 1 specified, 2-7 are reserved + throw new UnspecifiedFormatError("Operational status message subtype " + subtypeCode + " reserved."); + } else if (subtypeCode != SUBTYPE_CODE) { throw new BadFormatException("Not an airborne operational status message"); } @@ -115,6 +115,14 @@ public AirborneOperationalStatusV1Msg(ExtendedSquitter squitter) throws BadForma hrd = b.readByte(54, 54) == 1; } + /** + * @return the subtype code, 0 for airborne operational status messages + */ + @Override + public byte getSubtypeCode() { + return SUBTYPE_CODE; + } + /** * @return true if TCAS is operational or unknown, false if TCAS is not operational. */ @@ -258,7 +266,6 @@ public boolean getHorizontalReferenceDirection() { @Override public String toString() { return "AirborneOperationalStatusV1Msg{" + - "subtype_code=" + subtype_code + ", capability_class_code=" + capability_class_code + ", operational_mode_code=" + operational_mode_code + ", version=" + version + diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV2Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV2Msg.java index c772141..e2499b0 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV2Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV2Msg.java @@ -27,7 +27,7 @@ /** * @author Markus Fuchs (fuchs@opensky-network.org) */ -public class AirborneOperationalStatusV2Msg extends AirborneOperationalStatusV1Msg implements Serializable { +public class AirborneOperationalStatusV2Msg extends AirborneOperationalStatusV1Msg implements Serializable, OperationalStatusV2Msg { private static final long serialVersionUID = -2032348919695227545L; @@ -87,6 +87,7 @@ public boolean hasOperationalTCAS() { /** * @return whether aircraft has an UAT receiver */ + @Override public boolean hasUATIn() { return (capability_class_code & 0x20) != 0; } @@ -94,6 +95,7 @@ public boolean hasUATIn() { /** * @return whether aircraft uses a single antenna or two */ + @Override public boolean hasSingleAntenna() { return (operational_mode_code & 0x400) != 0; } @@ -121,6 +123,7 @@ else if (geometric_vertical_accuracy == 2) * * @return system design assurance (see A.1.4.10.14 in RTCA DO-260B) */ + @Override public byte getSystemDesignAssurance() { return (byte) ((operational_mode_code & 0x300) >>> 8); } @@ -131,6 +134,7 @@ public byte getSystemDesignAssurance() { * @return true if SIL (Source Integrity Level) is based on "per sample" probability, otherwise * it's based on "per hour". */ + @Override public boolean hasSILSupplement() { return sil_supplement; } diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/AirspeedHeadingMsg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/AirspeedHeadingMsg.java index a611b7c..4253981 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/AirspeedHeadingMsg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/AirspeedHeadingMsg.java @@ -37,7 +37,7 @@ public class AirspeedHeadingMsg extends ExtendedSquitter implements Serializable private boolean ifr_capability; private byte navigation_accuracy_category; private boolean heading_status_bit; - private double heading; // in degrees + private short heading; // raw value private boolean true_airspeed; // 0 = indicated AS, 1 = true AS private short airspeed; // in knots private boolean airspeed_available; @@ -98,7 +98,7 @@ public AirspeedHeadingMsg(ExtendedSquitter squitter) throws BadFormatException { // heading available heading_status_bit = (msg[1]&0x4)>0; - heading = ((msg[1]&0x3)<<8 | msg[2]&0xFF) * 360./1024.; + heading = (short) (((msg[1]&0x3)<<8) | (msg[2]&0xFF)); true_airspeed = (msg[3]&0x80)>0; airspeed = (short) (((msg[3]&0x7F)<<3 | msg[4]>>>5&0x07)-1); @@ -205,13 +205,21 @@ public Integer getGeoMinusBaro() { return geo_minus_baro; } + /** + * @return raw heading field value (10 bit). Check {@link #hasHeadingStatusFlag()} to determine whether this value + * is valid. + */ + public int getHeadingRaw() { + return heading; + } + /** * @return heading in decimal degrees ([0, 360]). 0° = geographic north or null if no information is available. * The latter can also be checked using {@link #hasHeadingStatusFlag()}. */ public Double getHeading() { if (!heading_status_bit) return null; - return heading; + return heading * 360. / 1024.; } /** diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusMsg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusMsg.java new file mode 100644 index 0000000..d32d616 --- /dev/null +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusMsg.java @@ -0,0 +1,35 @@ +/* + * This file is part of lib1090. + * Copyright (C) 2026 SeRo Systems GmbH + * + * lib1090 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lib1090 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with de.serosystems.lib1090. If not, see . + */ + +package de.serosystems.lib1090.msgs.adsb; + +/** + * Common API for ADS-B operational status messages. + */ +public interface OperationalStatusMsg { + + /** + * @return whether 1090ES IN is available + */ + boolean has1090ESIn(); + + /** + * @return the version number of the formats and protocols in use on the aircraft installation + */ + byte getVersion(); +} diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0Msg.java index 2e15801..a56a868 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0Msg.java @@ -29,7 +29,7 @@ * * @author Markus Fuchs (fuchs@opensky-network.org) */ -public class OperationalStatusV0Msg extends ExtendedSquitter implements Serializable { +public class OperationalStatusV0Msg extends ExtendedSquitter implements Serializable, OperationalStatusMsg { private static final long serialVersionUID = -8925123066831152922L; @@ -106,6 +106,15 @@ public boolean hasOperationalCDTI() { return (enroute_capabilities & 0x10) != 0; } + /** + * @return whether 1090ES IN is available + * @see #hasOperationalCDTI() alias: the field has been renamed in V1 + */ + @Override + public boolean has1090ESIn() { + return hasOperationalCDTI(); + } + /** * the version number of the formats and protocols in use on the aircraft installation.
* 0: Conformant to DO-260/ED-102 and DO-242
diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV1Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV1Msg.java new file mode 100644 index 0000000..c8ba72d --- /dev/null +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV1Msg.java @@ -0,0 +1,76 @@ +/* + * This file is part of lib1090. + * Copyright (C) 2026 SeRo Systems GmbH + * + * lib1090 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lib1090 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with de.serosystems.lib1090. If not, see . + */ + +package de.serosystems.lib1090.msgs.adsb; + +import de.serosystems.lib1090.decoding.OperationalStatus; + +/** + * Common API for ADS-B operational status version 1 messages. + */ +public interface OperationalStatusV1Msg extends OperationalStatusMsg { + + /** + * @return the subtype code, 0 for airborne operational status messages and 1 for surface operational status messages + */ + byte getSubtypeCode(); + + /** + * @return the NIC supplement A to the format type code of position messages + */ + boolean hasNICSupplementA(); + + /** + * @return the navigation accuracy for position messages; rather use getPositionUncertainty + */ + byte getNACp(); + + /** + * Get the 95% horizontal accuracy bounds (EPU) derived from NACp value. + * + * @return the estimated position uncertainty according to the position NAC in meters (-1 for unknown) + */ + default double getPositionUncertainty() { + return OperationalStatus.nacPtoEPU(getNACp()); + } + + /** + * @return the source integrity level (SIL) + */ + byte getSIL(); + + /** + * @return whether TCAS Resolution Advisory (RA) is active + */ + boolean hasTCASResolutionAdvisory(); + + /** + * @return whether the IDENT switch is active + */ + boolean hasActiveIDENTSwitch(); + + /** + * @return whether ADS-B Transmitting Subsystem is receiving ATC services. + */ + boolean hasReceivingATCServices(); + + /** + * @return 0 if horizontal reference direction is the true north, 1 if magnetic north + */ + boolean getHorizontalReferenceDirection(); +} diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV2Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV2Msg.java new file mode 100644 index 0000000..4620063 --- /dev/null +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV2Msg.java @@ -0,0 +1,50 @@ +/* + * This file is part of lib1090. + * Copyright (C) 2026 SeRo Systems GmbH + * + * lib1090 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lib1090 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with de.serosystems.lib1090. If not, see . + */ + +package de.serosystems.lib1090.msgs.adsb; + +/** + * Common API for ADS-B operational status version 2 messages. + */ +public interface OperationalStatusV2Msg extends OperationalStatusV1Msg { + + /** + * @return whether aircraft has an UAT receiver + */ + boolean hasUATIn(); + + /** + * @return whether aircraft uses a single antenna or two + */ + boolean hasSingleAntenna(); + + /** + * For interpretation see Table 2-65 in DO-260B + * + * @return system design assurance (see A.1.4.10.14 in RTCA DO-260B) + */ + byte getSystemDesignAssurance(); + + /** + * DO-260B 2.2.3.2.7.2.14 + * + * @return true if SIL (Source Integrity Level) is based on "per sample" probability, otherwise + * it's based on "per hour". + */ + boolean hasSILSupplement(); +} diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1Msg.java index 0fa0dde..c261373 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1Msg.java @@ -32,14 +32,14 @@ * @author Matthias Schaefer (schaefer@sero-systems.de) * @author Markus Fuchs (fuchs@opensky-network.org) */ -public class SurfaceOperationalStatusV1Msg extends ExtendedSquitter implements Serializable { +public class SurfaceOperationalStatusV1Msg extends ExtendedSquitter implements Serializable, OperationalStatusV1Msg { private static final long serialVersionUID = -3513411664476607448L; + private static final byte SUBTYPE_CODE = 1; - private byte subtype_code; protected int capability_class_code; // actually 16 bit unsigned protected int operational_mode_code; // actually 16 bit unsigned - private byte airplane_len_width; // only in subtype_code == 1 surface msgs + private byte airplane_len_width; // only for surface messages private byte version; private boolean nic_suppl; // may be passed to position messages private byte nac_pos; // navigational accuracy category - position @@ -89,10 +89,10 @@ public SurfaceOperationalStatusV1Msg(ExtendedSquitter squitter) throws BadFormat byte[] msg = this.getMessage(); BitReader b = BitReader.forBigEndian(msg); - subtype_code = b.readByte(6, 8); - if (subtype_code > 1) { // currently only 0 and 1 specified, 2-7 are reserved - throw new UnspecifiedFormatError("Operational status message subtype " + subtype_code + " reserved."); - } else if (subtype_code != 1) { + byte subtypeCode = b.readByte(6, 8); + if (subtypeCode > 1) { // currently only 0 and 1 specified, 2-7 are reserved + throw new UnspecifiedFormatError("Operational status message subtype " + subtypeCode + " reserved."); + } else if (subtypeCode != SUBTYPE_CODE) { throw new BadFormatException("Not surface operational status message"); } @@ -119,8 +119,9 @@ public SurfaceOperationalStatusV1Msg(ExtendedSquitter squitter) throws BadFormat * and 1 for surface operational status msgs; all other codes * are "reserved" */ + @Override public byte getSubtypeCode() { - return subtype_code; + return SUBTYPE_CODE; } /** @@ -166,6 +167,13 @@ public boolean hasReceivingATCServices() { } + /** + * @return raw aircraft vehicle length and width code (4 bit) + */ + public byte getAircraftVehicleLengthAndWidthCode() { + return airplane_len_width; + } + /** * According to DO-260B Table 2-74. Compatible with ADS-B version 1 and 2 * @@ -247,7 +255,6 @@ public boolean getHorizontalReferenceDirection() { @Override public String toString() { return "SurfaceOperationalStatusV1Msg{" + - "subtype_code=" + subtype_code + ", capability_class_code=" + capability_class_code + ", operational_mode_code=" + operational_mode_code + ", airplane_len_width=" + airplane_len_width + diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV2Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV2Msg.java index 5510eb1..aef2ef2 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV2Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV2Msg.java @@ -27,7 +27,7 @@ /** * @author Markus Fuchs (fuchs@opensky-network.org) */ -public class SurfaceOperationalStatusV2Msg extends SurfaceOperationalStatusV1Msg implements Serializable { +public class SurfaceOperationalStatusV2Msg extends SurfaceOperationalStatusV1Msg implements Serializable, OperationalStatusV2Msg { private static final long serialVersionUID = 5774750859726557576L; @@ -77,6 +77,7 @@ public SurfaceOperationalStatusV2Msg(ExtendedSquitter squitter) throws BadFormat /** * @return whether aircraft has an UAT receiver */ + @Override public boolean hasUATIn() { return (capability_class_code & 0x100) != 0; } @@ -98,6 +99,7 @@ public boolean getNICSupplementC() { /** * @return whether aircraft uses a single antenna or two */ + @Override public boolean hasSingleAntenna() { return (operational_mode_code & 0x400) != 0; } @@ -107,6 +109,7 @@ public boolean hasSingleAntenna() { * * @return system design assurance (see A.1.4.10.14 in RTCA DO-260B) */ + @Override public byte getSystemDesignAssurance() { return (byte) ((operational_mode_code & 0x300) >>> 8); } @@ -131,7 +134,7 @@ public boolean hasPositionOffsetApplied() { *
  • values are measured from the longitudinal center line (=roll axis) of the aircraft
  • *
  • values are given in meters
  • *
  • positive values mean "toward left wing tip"
  • - *
  • negative values mean "toward right wind tip
  • + *
  • negative values mean "toward right wind tip"
  • *
  • values have a resolution of 2m
  • *
  • values are capped at 6m
  • *
  • {@code null} means "no data"
  • @@ -171,6 +174,7 @@ public Integer getLongitudinalAxisGPSAntennaOffset() { * @return true if SIL (Source Integrity Level) is based on "per sample" probability, otherwise * it's based on "per hour". */ + @Override public boolean hasSILSupplement() { return sil_supplement; } diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfacePositionV0Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfacePositionV0Msg.java index c94f0fe..8507e1f 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfacePositionV0Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/SurfacePositionV0Msg.java @@ -159,6 +159,13 @@ public byte getSIL() { return (byte) (getFormatTypeCode() == 0 ? 0 : 2); } + /** + * @return the raw movement field value (7 bit) + */ + public byte getMovementRaw() { + return movement; + } + /** * @return whether ground speed information is available */ @@ -182,6 +189,13 @@ public Double getGroundSpeedResolution() { return SurfacePosition.groundSpeedResolution(movement); } + /** + * @return the raw heading field value (7 bit) + */ + public byte getHeadingRaw() { + return ground_track; + } + /** * @return whether valid heading information is available */ diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusMsg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusMsg.java index 8d792e3..a2e62b9 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusMsg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusMsg.java @@ -59,6 +59,12 @@ public interface TargetStateAndStatusMsg { */ Float getSelectedHeading(); + /** + * Get raw selected heading including sign bit. + * @return the raw selected heading (named. target heading track in V1) field value including sign bit + */ + int getSelectedHeadingRaw(); + /** * @return the navigation accuracy category for position */ diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV1Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV1Msg.java index 67f0e90..45b0b30 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV1Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV1Msg.java @@ -76,7 +76,7 @@ public TargetStateAndStatusV1Msg(byte[] raw_message) throws BadFormatException, */ public TargetStateAndStatusV1Msg(ExtendedSquitter squitter) throws BadFormatException, UnspecifiedFormatError { super(squitter); - setType(subtype.ADSB_TARGET_STATE_AND_STATUS); + setType(subtype.ADSB_TARGET_STATE_AND_STATUS_V1); if (getFormatTypeCode() != 29) { throw new BadFormatException("Target state and status messages must have typecode 29."); @@ -143,6 +143,11 @@ public Float getSelectedHeading() { return target_heading_track_angle * (360.f / 512); } + @Override + public int getSelectedHeadingRaw() { + return target_heading_track_angle; + } + @Override public byte getNACp() { return nac_p; diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV2Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV2Msg.java index 07f53dd..7d6f6d1 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV2Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsb/TargetStateAndStatusV2Msg.java @@ -79,7 +79,7 @@ public TargetStateAndStatusV2Msg(byte[] raw_message) throws BadFormatException, */ public TargetStateAndStatusV2Msg(ExtendedSquitter squitter) throws BadFormatException, UnspecifiedFormatError { super(squitter); - setType(subtype.ADSB_TARGET_STATE_AND_STATUS); + setType(subtype.ADSB_TARGET_STATE_AND_STATUS_V2); if (getFormatTypeCode() != 29) { throw new BadFormatException("Target state and status messages must have typecode 29."); @@ -198,6 +198,11 @@ public Float getSelectedHeading() { return selected_heading * (180.f / 256) + (selected_heading_sign ? 180F : 0F); } + @Override + public int getSelectedHeadingRaw() { + return ((selected_heading_sign ? 1 : 0) << 8) | selected_heading; + } + @Override public byte getNACp() { return nac_p; diff --git a/src/main/java/de/serosystems/lib1090/msgs/adsr/SurfaceOperationalStatusV1Msg.java b/src/main/java/de/serosystems/lib1090/msgs/adsr/SurfaceOperationalStatusV1Msg.java index 963c49d..56e48f6 100644 --- a/src/main/java/de/serosystems/lib1090/msgs/adsr/SurfaceOperationalStatusV1Msg.java +++ b/src/main/java/de/serosystems/lib1090/msgs/adsr/SurfaceOperationalStatusV1Msg.java @@ -198,6 +198,13 @@ public byte getGPSAntennaOffset() { return (byte) (operational_mode_code&0xFF); } + /** + * @return raw aircraft vehicle length and width code (4 bit) + */ + public byte getAircraftVehicleLengthAndWidthCode() { + return airplane_len_width; + } + /** * According to DO-260B Table 2-74. Compatible with ADS-R version 1 and 2 * @return the airplane's length in meters; -1 for unknown diff --git a/src/test/java/de/serosystems/lib1090/StatefulModeSDecoderTest.java b/src/test/java/de/serosystems/lib1090/StatefulModeSDecoderTest.java index 7a99ba4..be95513 100644 --- a/src/test/java/de/serosystems/lib1090/StatefulModeSDecoderTest.java +++ b/src/test/java/de/serosystems/lib1090/StatefulModeSDecoderTest.java @@ -52,7 +52,8 @@ public void tssV0Me11Set_shouldNotDecode() throws UnspecifiedFormatError, BadFor final ModeSDownlinkMsg reply = decoder.decode(TargetStateAndStatusV2MsgTest.TSS_WITH_ME11_BIT_SET, 0L); assertEquals(ModeSDownlinkMsg.subtype.EXTENDED_SQUITTER, reply.getType()); - assertNotEquals(ModeSDownlinkMsg.subtype.ADSB_TARGET_STATE_AND_STATUS, reply.getType()); + assertNotEquals(ModeSDownlinkMsg.subtype.ADSB_TARGET_STATE_AND_STATUS_V1, reply.getType()); + assertNotEquals(ModeSDownlinkMsg.subtype.ADSB_TARGET_STATE_AND_STATUS_V2, reply.getType()); } @Test @@ -63,7 +64,7 @@ public void tssV2ME11Set_shouldDecode() throws UnspecifiedFormatError, BadFormat // decode message with ME bit 11 set final ModeSDownlinkMsg reply = decoder.decode(TargetStateAndStatusV2MsgTest.TSS_WITH_ME11_BIT_SET, 0L); - assertEquals(ModeSDownlinkMsg.subtype.ADSB_TARGET_STATE_AND_STATUS, reply.getType()); + assertEquals(ModeSDownlinkMsg.subtype.ADSB_TARGET_STATE_AND_STATUS_V2, reply.getType()); TargetStateAndStatusV2Msg tss = (TargetStateAndStatusV2Msg) reply; @@ -78,7 +79,7 @@ public void tssV1_shouldDecode() throws UnspecifiedFormatError, BadFormatExcepti final ModeSDownlinkMsg reply = decoder.decode(TargetStateAndStatusV1MsgTest.TSS_V1, 0L); - assertEquals(ModeSDownlinkMsg.subtype.ADSB_TARGET_STATE_AND_STATUS, reply.getType()); + assertEquals(ModeSDownlinkMsg.subtype.ADSB_TARGET_STATE_AND_STATUS_V1, reply.getType()); assertTrue(reply instanceof TargetStateAndStatusV1Msg); diff --git a/src/test/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1MsgTest.java b/src/test/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1MsgTest.java index 5188d42..beabd7e 100644 --- a/src/test/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1MsgTest.java +++ b/src/test/java/de/serosystems/lib1090/msgs/adsb/AirborneOperationalStatusV1MsgTest.java @@ -46,6 +46,8 @@ public void testValidVersion1Message() throws Exception { // ME = F8 00 02 00 49 29 00 byte[] msg = Tools.hexStringToByteArray("8D000000F8000200492900000000"); AirborneOperationalStatusV1Msg status = new AirborneOperationalStatusV1Msg(msg); + assertTrue(status instanceof OperationalStatusV1Msg); + assertEquals(0, status.getSubtypeCode()); assertEquals(1, status.getVersion()); assertEquals(9, status.getNACp()); } diff --git a/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusMsgTest.java b/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusMsgTest.java index ab69c5f..a844da5 100644 --- a/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusMsgTest.java +++ b/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusMsgTest.java @@ -55,14 +55,19 @@ public class OperationalStatusMsgTest { // parity (correct, not test here) "209514"; + // Surface operational status message with ADS-B version 2 + public static final String S_OPSTAT_V2 = "8D000000F9000000004000000000"; + @Test public void testDecodeAirborneOpstat() throws UnspecifiedFormatError, BadFormatException { final AirborneOperationalStatusV2Msg opstat = new AirborneOperationalStatusV2Msg(A_OPSTAT_V2); + assertTrue(opstat instanceof OperationalStatusV2Msg); assertEquals("4d0131", opstat.getAddress().getHexAddress()); assertEquals(31, opstat.getFormatTypeCode()); assertEquals(2, opstat.getVersion()); + assertFalse(opstat.has1090ESIn()); assertFalse(opstat.hasNICSupplementA()); assertEquals(9, opstat.getNACp()); @@ -71,4 +76,17 @@ public void testDecodeAirborneOpstat() throws UnspecifiedFormatError, BadFormatE assertTrue(opstat.getBarometricAltitudeIntegrityCode()); assertFalse(opstat.getHorizontalReferenceDirection()); } + + @Test + public void testDecodeSurfaceOpstat() throws UnspecifiedFormatError, BadFormatException { + final SurfaceOperationalStatusV2Msg opstat = new SurfaceOperationalStatusV2Msg(S_OPSTAT_V2); + assertTrue(opstat instanceof OperationalStatusV2Msg); + + assertEquals(2, opstat.getVersion()); + assertFalse(opstat.has1090ESIn()); + assertFalse(opstat.hasUATIn()); + assertFalse(opstat.hasSingleAntenna()); + assertEquals(0, opstat.getSystemDesignAssurance()); + assertFalse(opstat.hasSILSupplement()); + } } diff --git a/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0MsgTest.java b/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0MsgTest.java index 06e3475..d230980 100644 --- a/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0MsgTest.java +++ b/src/test/java/de/serosystems/lib1090/msgs/adsb/OperationalStatusV0MsgTest.java @@ -32,8 +32,10 @@ class OperationalStatusV0MsgTest { public void testValidEnrouteCapabilities() throws Exception { byte[] msg = Tools.hexStringToByteArray("8D000000F8300000000000000000"); OperationalStatusV0Msg status = new OperationalStatusV0Msg(msg); + assertTrue(status instanceof OperationalStatusMsg); assertFalse(status.hasOperationalTCAS()); assertTrue(status.hasOperationalCDTI()); + assertTrue(status.has1090ESIn()); } @Test diff --git a/src/test/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1MsgTest.java b/src/test/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1MsgTest.java index d48432a..2d6f5ac 100644 --- a/src/test/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1MsgTest.java +++ b/src/test/java/de/serosystems/lib1090/msgs/adsb/SurfaceOperationalStatusV1MsgTest.java @@ -31,6 +31,7 @@ class SurfaceOperationalStatusV1MsgTest { public void testValidVersion1Message() throws Exception { byte[] msg = Tools.hexStringToByteArray("8D000000F9000000002000000000"); SurfaceOperationalStatusV1Msg status = new SurfaceOperationalStatusV1Msg(msg); + assertEquals(1, status.getSubtypeCode()); assertEquals(1, status.getVersion()); }