diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
index 1923f8e..f9ad960 100644
--- a/.github/workflows/publish-release.yml
+++ b/.github/workflows/publish-release.yml
@@ -24,15 +24,11 @@ jobs:
with:
java-version: 11
- - name: Upload release
- run: ./gradlew uploadArchives --no-daemon --no-parallel
- env:
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
- ORG_GRADLE_PROJECT_SIGNING_PRIVATE_KEY: ${{ secrets.GPG_SECRET_KEYS }}
+ - uses: gradle/gradle-build-action@v2
- name: Publish release
- run: ./gradlew closeAndReleaseRepository --no-daemon --no-parallel
+ run: ./gradlew publishAllPublicationsToMavenCentralRepository
env:
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
+ ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
+ ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
+ ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SECRET_KEYS }}
diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml
index 2941df7..787802d 100644
--- a/.github/workflows/publish-snapshot.yml
+++ b/.github/workflows/publish-snapshot.yml
@@ -19,12 +19,15 @@ jobs:
with:
java-version: 11
+ - uses: gradle/gradle-build-action@v2
+
- name: Retrieve version
run: |
- echo "VERSION_NAME=$(cat gradle.properties | grep -w 'VERSION_NAME' | cut -d'=' -f2)" >> $GITHUB_ENV
+ echo "VERSION_NAME=$(cat gradle.properties | grep -w "VERSION_NAME" | cut -d'=' -f2)" >> $GITHUB_ENV
+
- name: Publish snapshot
- run: ./gradlew uploadArchives --no-daemon --no-parallel
+ run: ./gradlew publishAllPublicationsToMavenCentralRepository
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
- ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
+ ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
+ ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
diff --git a/README.md b/README.md
index 257d828..adc20a8 100644
--- a/README.md
+++ b/README.md
@@ -5,19 +5,19 @@ This library decodes ADS-B Messages and creates an easy to work with track objec
Message support status
-| Downlink Format | Human Readable | Supported | Note |
-|-----------------|------------------------------------------|-----------|------|
-| DF0 | Short Air-Air Surveillance | ✅ | |
-| DF4 | Surveillance, Altitude request | ✅ | Updates Altitude information + SPI (Ident) |
-| DF5 | Surveillance, Identity request | ✅ | Updates Callsign + SPI (Ident) |
-| DF11 | MODE S only all-call | ✅ | Broadcasts ICAO Mode-S address |
-| DF16 | Long Air-Air Surveillance | ✅ | Logs warning when ACAS RA is active |
-| DF17 | Extended Squitter (Most ADS-B Data) | ⚠️ | Partial supported see DF17/18 status below |
-| DF18 | Extended Squitter/Supplementary (Ground) | ⚠️ | Partial supported see DF17/18 status below |
-| DF19 | Extended Squitter Military | ❌ | Not supported at this stage |
-| DF20 | Comm-B Altitude | ⚠️ | Partial supported see DF20/21 status below |
-| DF21 | Comm-B Identity | ⚠️ | Partial supported see DF20/21 status below |
-| DF24 | Comm-B ELM | ❌ | Not supported at this stage |
+| Downlink Format | Human Readable | Supported | Note |
+|-----------------|------------------------------------------|-----------|-----------------------------------------------|
+| DF0 | Short Air-Air Surveillance | ✅ | |
+| DF4 | Surveillance, Altitude request | ✅ | Updates Altitude information + SPI (Ident) |
+| DF5 | Surveillance, Identity request | ✅ | Updates Callsign + SPI (Ident) |
+| DF11 | MODE S only all-call | ✅ | Broadcasts ICAO Mode-S address |
+| DF16 | Long Air-Air Surveillance | ✅ | Logs warning when ACAS RA is active |
+| DF17 | Extended Squitter (Most ADS-B Data) | ⚠️ | Partially supported, see DF17/18 status below |
+| DF18 | Extended Squitter/Supplementary (Ground) | ⚠️ | Partially supported, see DF17/18 status below |
+| DF19 | Extended Squitter Military | ❌ | Not supported at this stage |
+| DF20 | Comm-B Altitude | ⚠️ | Partially supported, see DF20/21 status below |
+| DF21 | Comm-B Identity | ⚠️ | Partially supported, see DF20/21 status below |
+| DF24 | Comm-B ELM | ⚠️ | Implemented basic sequence no decoding. |
## DF17/DF18 - Extended Squitter
@@ -27,48 +27,47 @@ DF17 is used for aircraft, while DF18 is used for other vessels (ground vehicles
Most features of the DF17/18 protocol have been implemented, some message lack support for specific fields.
-| Type Code | Human Readable | Supported | Note |
-|-----------|------------------------------|-----------|------|
-| 0 | Airborne/Surface No altitude | ✅ |
-| 1 | Aircraft Identification | ✅ |
-| 2 | Aircraft Identification | ✅ |
-| 3 | Aircraft Identification | ✅ |
-| 4 | Aircraft Identification | ✅ |
-| 5 | Surface Position | ❌ | Not implemented yet
-| 6 | Surface Position | ❌ | Not implemented yet
-| 7 | Surface Position | ❌ | Not implemented yet
-| 8 | Surface Position | ❌ | Not implemented yet
-| 9 | Airborne Position | ✅ |
-| 10 | Airborne Position | ✅ |
-| 11 | Airborne Position | ✅ |
-| 12 | Airborne Position | ✅ |
-| 13 | Airborne Position | ✅ |
-| 14 | Airborne Position | ✅ |
-| 15 | Airborne Position | ✅ |
-| 16 | Airborne Position | ✅ |
-| 17 | Airborne Position | ✅ |
-| 18 | Airborne Position | ✅ |
-| 19 | Airborne Velocity | ✅ |
-| 20 | Airborne Position | ✅ |
-| 21 | Airborne Position | ✅ |
-| 22 | Airborne Position | ✅ |
-| 23 | Test Message | ✅ |
-| 24 | Surface System Status | ❌ | Not implemented yet
-| 25 | Reserved Message | ✅ |
-| 26 | Reserved Message | ✅ |
-| 27 | Reserved (Trajectory Change) | ✅ |
-| 28 | Aircraft Status Message | ✅ | Priority mode A code (emergency) + TCAS/ACAS RA Broadcast
-| 29 | Target Status Message | ✅ | Partial support
-| 30 | Reserved Message | ✅ |
-| 31 | Aircraft Operational Status | ✅ | Partial support
-
+| Type Code | Human Readable | Supported | Note |
+|-----------|------------------------------|-----------|-----------------------------------------------------------|
+| 0 | Airborne/Surface No altitude | ✅ | |
+| 1 | Aircraft Identification | ✅ | |
+| 2 | Aircraft Identification | ✅ | |
+| 3 | Aircraft Identification | ✅ | |
+| 4 | Aircraft Identification | ✅ | |
+| 5 | Surface Position | ✅ | |
+| 6 | Surface Position | ✅ | |
+| 7 | Surface Position | ✅ | |
+| 8 | Surface Position | ✅ | |
+| 9 | Airborne Position | ✅ | |
+| 10 | Airborne Position | ✅ | |
+| 11 | Airborne Position | ✅ | |
+| 12 | Airborne Position | ✅ | |
+| 13 | Airborne Position | ✅ | |
+| 14 | Airborne Position | ✅ | |
+| 15 | Airborne Position | ✅ | |
+| 16 | Airborne Position | ✅ | |
+| 17 | Airborne Position | ✅ | |
+| 18 | Airborne Position | ✅ | |
+| 19 | Airborne Velocity | ✅ | |
+| 20 | Airborne Position | ✅ | |
+| 21 | Airborne Position | ✅ | |
+| 22 | Airborne Position | ✅ | |
+| 23 | Test Message | ✅ | |
+| 24 | Surface System Status | ❌ | Not implemented yet |
+| 25 | Reserved Message | ✅ | |
+| 26 | Reserved Message | ✅ | |
+| 27 | Reserved (Trajectory Change) | ✅ | |
+| 28 | Aircraft Status Message | ✅ | Priority mode A code (emergency) + TCAS/ACAS RA Broadcast |
+| 29 | Target Status Message | ✅ | Partial support |
+| 30 | Reserved Message | ✅ | |
+| 31 | Aircraft Operational Status | ✅ | Partial support |
## DF20/21 Comm-B
DF20/21 messages are replies to data requests from a radar station, you'll only receive these messages if Mode-S radar
is actively requesting this information. You will only receive messages requested by the radar.
-Message is structure as follows
+Message is structured as follows:
```
LSB |1----|6--|9----|14----|20-----------|33------------------------------------------------------|89-----------------------|
@@ -96,57 +95,56 @@ You run through each BDS and pass the message to the decoder, if the message doe
Repeat this process until you have a match.
At this moment the coded logic has too many incorrect matches and thus decided to disable BDS guessing.
-We are actively looking for a fix, or at least ability to enable this with a experimental flag.
+We are actively looking for a fix, or at least the ability to enable this with an experimental flag.
We hope with more BDS implemented the guessing accuracy will improve.
-| BDS | Human Readable | Supported | Note |
-|-----|--------------------------------------------|-----------|------|
-| 1,0 | Data link capability report | ❌ | Detection implemented, decoding missing
-| 1,7 | Common usage GICB capability report | ✅ |
-| 1,8 | Mode S services GICB capability report | ❌ |
-| 1,9 | Mode S services GICB capability report | ❌ |
-| 1,A | Mode S services GICB capability report | ❌ |
-| 1,B | Mode S services GICB capability report | ❌ |
-| 1,C | Mode S services GICB capability report | ❌ |
-| 1,D | Mode S services GICB capability report | ❌ |
-| 1,E | Mode S services GICB capability report | ❌ |
-| 1,F | Mode S services GICB capability report | ❌ |
-| 2,0 | Aircraft Identification | ✅ |
-| 2,1 | Aircraft and Airline registration marking | ✅️ | Experimental
-| 2,2 | Antenna positions | ❌ |
-| 2,5 | Antenna type | ❌ |
-| 3,0 | ACAS Active resolution advisory | ❌ | Detection implemented, decoding missing
-| 4,0 | Selected vertical intention | ✅️ |
-| 4,1 | Next waypoint details | ❌ | 9 Characters
-| 4,2 | Next waypoint details | ❌ | Waypoint lat/lon + crossing altitude
-| 4,3 | Next waypoint details | ❌ | Bearing, time and distance to waypoint
-| 4,4 | Meteorological routine air report | ✅ |
-| 4,5 | Meteorological hazard report | ✅ |
-| 4,8 | VHF Channel report | ❌ | Info on VHF 1/2/3 (frequency + status) & Guard status
-| 5,0 | Track and turn report | ✅ |
-| 5,1 | Position report coarse | ❌ |
-| 5,2 | Position report fine | ❌ |
-| 5,3 | Air-reference state vector | ✅ |
-| 5,4 | Waypoint 1 | ❌ | 5 Chars, ETA, Estimated level, time to go
-| 5,5 | Waypoint 2 | ❌ | 5 Chars, ETA, Estimated level, time to go
-| 5,5 | Waypoint 3 | ❌ | 5 Chars, ETA, Estimated level, time to go
-| 5,F | Quasi-static parameter monitoring | ❌ |
-| 6,0 | Heading and speed report | ✅ |
-| 6,1 | Priority/emergency status | ❌ |
-| 6,5 | Aircraft operational status | ❌ |
-| E,3 | Transponder type/part number | ❌ |
-| E,4 | Transponder software revision number | ❌ |
-| E,5 | ACAS type/part number | ❌ |
-| E,6 | ACAS software revision number | ❌ |
-| E,7 | Transponder status and diagnostics | ❌ |
-| E,A | Vendor specific status and diagnostics | ❌ |
-| F,1 | Military application | ❌ |
-| F,2 | Military application | ❌ |
-
+| BDS | Human Readable | Supported | Note |
+|-----|-------------------------------------------|-----------|-------------------------------------------------------|
+| 1,0 | Data link capability report | ❌ | Detection implemented, decoding missing |
+| 1,7 | Common usage GICB capability report | ✅ | |
+| 1,8 | Mode S services GICB capability report | ❌ | |
+| 1,9 | Mode S services GICB capability report | ❌ | |
+| 1,A | Mode S services GICB capability report | ❌ | |
+| 1,B | Mode S services GICB capability report | ❌ | |
+| 1,C | Mode S services GICB capability report | ❌ | |
+| 1,D | Mode S services GICB capability report | ❌ | |
+| 1,E | Mode S services GICB capability report | ❌ | |
+| 1,F | Mode S services GICB capability report | ❌ | |
+| 2,0 | Aircraft Identification | ✅ | |
+| 2,1 | Aircraft and Airline registration marking | ✅️ | Experimental |
+| 2,2 | Antenna positions | ❌ | |
+| 2,5 | Antenna type | ❌ | |
+| 3,0 | ACAS Active resolution advisory | ❌ | Detection implemented, decoding missing |
+| 4,0 | Selected vertical intention | ✅️ | |
+| 4,1 | Next waypoint details | ❌ | 9 Characters |
+| 4,2 | Next waypoint details | ❌ | Waypoint lat/lon + crossing altitude |
+| 4,3 | Next waypoint details | ❌ | Bearing, time and distance to waypoint |
+| 4,4 | Meteorological routine air report | ✅ | |
+| 4,5 | Meteorological hazard report | ✅ | |
+| 4,8 | VHF Channel report | ❌ | Info on VHF 1/2/3 (frequency + status) & Guard status |
+| 5,0 | Track and turn report | ✅ | |
+| 5,1 | Position report coarse | ❌ | |
+| 5,2 | Position report fine | ❌ | |
+| 5,3 | Air-reference state vector | ✅ | |
+| 5,4 | Waypoint 1 | ❌ | 5 Chars, ETA, Estimated level, time to go |
+| 5,5 | Waypoint 2 | ❌ | 5 Chars, ETA, Estimated level, time to go |
+| 5,5 | Waypoint 3 | ❌ | 5 Chars, ETA, Estimated level, time to go |
+| 5,F | Quasi-static parameter monitoring | ❌ | |
+| 6,0 | Heading and speed report | ✅ | |
+| 6,1 | Priority/emergency status | ❌ | |
+| 6,5 | Aircraft operational status | ❌ | |
+| E,3 | Transponder type/part number | ❌ | |
+| E,4 | Transponder software revision number | ❌ | |
+| E,5 | ACAS type/part number | ❌ | |
+| E,6 | ACAS software revision number | ❌ | |
+| E,7 | Transponder status and diagnostics | ❌ | |
+| E,A | Vendor specific status and diagnostics | ❌ | |
+| F,1 | Military application | ❌ | |
+| F,2 | Military application | ❌ | |
# Installation
-This package is available through maven central
+This package is available through Maven Central
Pom
```xml
@@ -159,7 +157,7 @@ Pom
Gradle
```
- compile('aero.t2s:mode-s:0.2.0-SNAPSHOT')
+ compile('aero.t2s:mode-s:0.2.0-SNAPSHOT')
```
# Usage
@@ -191,9 +189,9 @@ class Main
## Using Aircraft Database
-This library is compatible with opensky dataset (https://opensky-network.org/datasets/metadata/).
+This library is compatible with [OpenSky dataset](https://opensky-network.org/datasets/metadata/).
-In order to use the database version you can start ModeS plugin as follows
+In order to use the database version you can start ModeS plugin as follows:
```java
class Main
@@ -222,9 +220,9 @@ class Main
# Contributing
-You can contribute to this project by reporting/fixing bugs or implemented a new packet.
+You can contribute to this project by reporting/fixing bugs or implement a new packet.
We are always looking for help on this project.
# License
-This library is Apache 2.0 licensed read the full license [here](LICENSE).
+This library is Apache 2.0 licensed. You can read the full license [here](LICENSE).
diff --git a/build.gradle b/build.gradle
index 366f138..b6a5e66 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.vanniktech:gradle-maven-publish-plugin:0.13.0'
+ classpath 'com.vanniktech:gradle-maven-publish-plugin:0.22.0'
}
}
@@ -11,8 +11,18 @@ apply plugin: 'idea'
apply plugin: 'java-library'
apply plugin: "com.vanniktech.maven.publish"
+def javaTargetVersion = 11
+
+sourceCompatibility = javaTargetVersion
+
+subprojects {
+ tasks.withType(JavaCompile.class) {
+ options.release = javaTargetVersion
+ }
+}
+
dependencies {
- compile 'org.slf4j:slf4j-api:1.7.32'
+ api 'org.slf4j:slf4j-api:1.7.32'
// Use JUnit test framework
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
@@ -24,15 +34,20 @@ repositories {
mavenLocal()
}
-compileJava {
- sourceCompatibility = 11
- targetCompatibility = 11
+tasks.withType(JavaCompile.class) {
+ options.release = javaTargetVersion
}
test {
useJUnitPlatform()
}
+
+mavenPublishing {
+ publishToMavenCentral()
+ signAllPublications()
+}
+
signing {
if (hasProperty('SIGNING_PRIVATE_KEY')) {
useInMemoryPgpKeys(SIGNING_PRIVATE_KEY, "")
diff --git a/example/.gitignore b/example/.gitignore
deleted file mode 100644
index c379362..0000000
--- a/example/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-build/
-.gradle/
-
diff --git a/example/build/classes/java/main/example/Demo$1.class b/example/build/classes/java/main/example/Demo$1.class
deleted file mode 100644
index c222b03..0000000
Binary files a/example/build/classes/java/main/example/Demo$1.class and /dev/null differ
diff --git a/example/build/classes/java/main/example/Demo$2.class b/example/build/classes/java/main/example/Demo$2.class
deleted file mode 100644
index c1d57f1..0000000
Binary files a/example/build/classes/java/main/example/Demo$2.class and /dev/null differ
diff --git a/example/build/classes/java/main/example/Demo.class b/example/build/classes/java/main/example/Demo.class
deleted file mode 100644
index b902e3b..0000000
Binary files a/example/build/classes/java/main/example/Demo.class and /dev/null differ
diff --git a/example/build/classes/java/main/example/FlightsTable.class b/example/build/classes/java/main/example/FlightsTable.class
deleted file mode 100644
index 0c4acb6..0000000
Binary files a/example/build/classes/java/main/example/FlightsTable.class and /dev/null differ
diff --git a/example/build/classes/java/main/example/flight/FlightFrame$1.class b/example/build/classes/java/main/example/flight/FlightFrame$1.class
deleted file mode 100644
index d899d5b..0000000
Binary files a/example/build/classes/java/main/example/flight/FlightFrame$1.class and /dev/null differ
diff --git a/example/build/classes/java/main/example/flight/FlightFrame$GcibTable.class b/example/build/classes/java/main/example/flight/FlightFrame$GcibTable.class
deleted file mode 100644
index 76f0777..0000000
Binary files a/example/build/classes/java/main/example/flight/FlightFrame$GcibTable.class and /dev/null differ
diff --git a/example/build/classes/java/main/example/flight/FlightFrame.class b/example/build/classes/java/main/example/flight/FlightFrame.class
deleted file mode 100644
index 81f11cf..0000000
Binary files a/example/build/classes/java/main/example/flight/FlightFrame.class and /dev/null differ
diff --git a/example/build/tmp/compileJava/source-classes-mapping.txt b/example/build/tmp/compileJava/source-classes-mapping.txt
deleted file mode 100644
index a1ed673..0000000
--- a/example/build/tmp/compileJava/source-classes-mapping.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-example/FlightsTable.java
- example.FlightsTable
-example/flight/FlightFrame.java
- example.flight.FlightFrame
- example.flight.FlightFrame$1
- example.flight.FlightFrame$GcibTable
-example/Demo.java
- example.Demo
- example.Demo$1
- example.Demo$2
diff --git a/example/gradle/wrapper/gradle-wrapper.jar b/example/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 62d4c05..0000000
Binary files a/example/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/example/gradle/wrapper/gradle-wrapper.properties b/example/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 622ab64..0000000
--- a/example/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/example/gradlew b/example/gradlew
deleted file mode 100755
index fbd7c51..0000000
--- a/example/gradlew
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/env sh
-
-#
-# Copyright 2015 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
- echo "$*"
-}
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-exec "$JAVACMD" "$@"
diff --git a/example/gradlew.bat b/example/gradlew.bat
deleted file mode 100644
index a9f778a..0000000
--- a/example/gradlew.bat
+++ /dev/null
@@ -1,104 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/example/build.gradle b/examples/gui/build.gradle
similarity index 75%
rename from example/build.gradle
rename to examples/gui/build.gradle
index 78563b7..89bb44a 100644
--- a/example/build.gradle
+++ b/examples/gui/build.gradle
@@ -8,5 +8,5 @@ repositories {
}
dependencies {
- compile ('aero.t2s:mode-s:0.2.0-SNAPSHOT')
+ implementation project(':')
}
diff --git a/example/src/main/java/example/Demo.java b/examples/gui/src/main/java/example/Demo.java
similarity index 98%
rename from example/src/main/java/example/Demo.java
rename to examples/gui/src/main/java/example/Demo.java
index 71f82c1..57b1932 100644
--- a/example/src/main/java/example/Demo.java
+++ b/examples/gui/src/main/java/example/Demo.java
@@ -1,18 +1,18 @@
package example;
-import aero.t2s.modes.ModeS;
-import aero.t2s.modes.Track;
-import example.flight.FlightFrame;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.swing.*;
-
import java.awt.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
+import javax.swing.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import aero.t2s.modes.ModeS;
+import aero.t2s.modes.Track;
+import example.flight.FlightFrame;
/*
* ButtonDemo.java requires the following files:
@@ -23,7 +23,7 @@
public class Demo extends JPanel {
private static final Logger logger = LoggerFactory.getLogger(Demo.class);
- private static final String IP = "192.168.178.190";
+ private static final String IP = "127.0.0.1";
private static final int PORT = 30002;
private static final double LAT = 51;
private static final double LON = 2;
diff --git a/example/src/main/java/example/FlightsTable.java b/examples/gui/src/main/java/example/FlightsTable.java
similarity index 55%
rename from example/src/main/java/example/FlightsTable.java
rename to examples/gui/src/main/java/example/FlightsTable.java
index d93da7c..7c3cabc 100644
--- a/example/src/main/java/example/FlightsTable.java
+++ b/examples/gui/src/main/java/example/FlightsTable.java
@@ -1,9 +1,9 @@
package example;
-import aero.t2s.modes.Track;
-
-import javax.swing.table.AbstractTableModel;
import java.util.List;
+import javax.swing.table.AbstractTableModel;
+
+import aero.t2s.modes.Track;
class FlightsTable extends AbstractTableModel {
private static String[] columns = {
@@ -43,15 +43,19 @@ public String getColumnName(int column) {
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
- switch (columnIndex) {
- case 0: return tracks.get(rowIndex).getIcao();
- case 1: return tracks.get(rowIndex).getCallsign();
- case 2: return String.format("%2.4f", tracks.get(rowIndex).getLat());
- case 3: return String.format("%03.4f", tracks.get(rowIndex).getLon());
- case 4: return tracks.get(rowIndex).getRocd();
- case 5: return tracks.get(rowIndex).getAltitude().getAltitude();
- case 6: return Math.round(tracks.get(rowIndex).getMagneticHeading());
- case 7: return (int)tracks.get(rowIndex).getGs();
+ try {
+ switch (columnIndex) {
+ case 0: return tracks.get(rowIndex).getIcao();
+ case 1: return tracks.get(rowIndex).getCallsign();
+ case 2: return String.format("%2.4f", tracks.get(rowIndex).getLat());
+ case 3: return String.format("%03.4f", tracks.get(rowIndex).getLon());
+ case 4: return tracks.get(rowIndex).getRocd();
+ case 5: return tracks.get(rowIndex).getAltitude().getAltitude();
+ case 6: return Math.round(tracks.get(rowIndex).getMagneticHeading());
+ case 7: return (int)tracks.get(rowIndex).getGs();
+ }
+ } catch (IndexOutOfBoundsException ex) {
+ return null;
}
return null;
diff --git a/example/src/main/java/example/flight/FlightFrame.form b/examples/gui/src/main/java/example/flight/FlightFrame.form
similarity index 63%
rename from example/src/main/java/example/flight/FlightFrame.form
rename to examples/gui/src/main/java/example/flight/FlightFrame.form
index faaf4f2..fd6ed8c 100644
--- a/example/src/main/java/example/flight/FlightFrame.form
+++ b/examples/gui/src/main/java/example/flight/FlightFrame.form
@@ -19,7 +19,7 @@
-
+
@@ -35,14 +35,6 @@
-
-
-
-
-
-
-
-
@@ -169,7 +161,7 @@
-
+
@@ -177,31 +169,47 @@
-
+
-
+
-
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -209,7 +217,7 @@
-
+
@@ -223,7 +231,7 @@
-
+
@@ -231,7 +239,7 @@
-
+
@@ -244,31 +252,143 @@
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/src/main/java/example/flight/FlightFrame.java b/examples/gui/src/main/java/example/flight/FlightFrame.java
similarity index 61%
rename from example/src/main/java/example/flight/FlightFrame.java
rename to examples/gui/src/main/java/example/flight/FlightFrame.java
index 8f4ff43..5723f2d 100644
--- a/example/src/main/java/example/flight/FlightFrame.java
+++ b/examples/gui/src/main/java/example/flight/FlightFrame.java
@@ -1,20 +1,17 @@
package example.flight;
-import aero.t2s.modes.Track;
-import aero.t2s.modes.constants.Hazard;
-
-import javax.swing.*;
-import javax.swing.table.AbstractTableModel;
import java.util.Timer;
import java.util.TimerTask;
+import javax.swing.*;
+
+import aero.t2s.modes.Track;
+import aero.t2s.modes.constants.Hazard;
public class FlightFrame extends JFrame {
java.util.Timer timer = new Timer();
private Track track;
-
private JTabbedPane tabbedPane1;
- private JTable gcibReportTable;
private JList meteoHazards;
private JLabel meteoHumidity;
private JLabel meteoSat;
@@ -23,11 +20,16 @@ public class FlightFrame extends JFrame {
private JLabel meteoRadioHeight;
private JPanel mainPanel;
private JTextArea flightModelLeft;
- private JTextArea flightInfoRight;
- private JTextArea infoAccuracy;
private JTextArea infoAbds;
private JTextArea acas;
+ private JTextArea register05;
+ private JTextArea register06;
+ private JTextArea register07;
+ private JTextArea register08;
+ private JTextArea register09;
+ private JTextArea register17;
+
public FlightFrame(Track track) {
this.track = track;
@@ -47,8 +49,6 @@ public void run() {
private void updateContent() {
setTitle(track.getCallsign() + " - " + track.getIcao());
- ((GcibTable)gcibReportTable.getModel()).fireTableDataChanged();
-
// Meteo Hazard
((DefaultListModel)meteoHazards.getModel()).clear();
if (track.getMeteo().getTurbulence() != Hazard.NIL) {
@@ -83,7 +83,6 @@ private void updateContent() {
"CALLSIGN: %s\n" +
"WTC: %s\n" +
"ATYP: %s\n (REG: %s)\n" +
- "Width & Length Code: %s\n" +
"OPERATOR: %s\n" +
"MODE A: %04d\n" +
"State: %s\n" +
@@ -95,9 +94,7 @@ private void updateContent() {
"Flight Status SPI: %s\n" +
"-------------------------------\n" +
"Altitude: %d%s (step size: %d)\n" +
- "Baro Altitude: %d\n" +
"Geometric offset: %d\n" +
- "GNSS Altitude: %d\n" +
"Selected Altitude Source: %s\n" +
"Selected Altitude: %d\n" +
"Selected Altitude FMS: %s\n" +
@@ -111,7 +108,6 @@ private void updateContent() {
"ROCD Baro Available: %s\n" +
"Baro ROCD: %dft/min\n" +
"------------------------------\n" +
- "Heading Source: %s\n" +
"Magnetic Heading: %d\n" +
"True Heading: %d\n" +
"Selected Heading: %d\n" +
@@ -136,7 +132,6 @@ private void updateContent() {
track.getCallsign(),
track.getWtc(),
track.getAtype(), track.getRegistration(),
- track.getLengthWidthCode().name(),
track.getOperator(),
track.getModeA(),
track.isGroundBit() ? "GROUND" : "AIRBORNE",
@@ -147,9 +142,7 @@ private void updateContent() {
track.getFlightStatus().isAlert() ? "YES" : "NO",
track.getFlightStatus().isSpi() ? "YES" : "NO",
(int)track.getAltitude().getAltitude(), track.getAltitude().isMetric() ? "M" : "FT", track.getAltitude().getStep(),
- track.getBaroAltitude(),
track.getGeometricHeightOffset(),
- track.getGnssHeight(),
track.getSelectedAltitudeSource().toString(),
track.getSelectedAltitude(),
track.getSelectedAltitudeManagedFms() ? "YES" : "NO",
@@ -160,7 +153,6 @@ private void updateContent() {
track.getRocd(),
track.getRocdSourceBaro() ? "YES" : "NO",
(int)track.getBaroRocd(),
- track.isMagneticHeading() ? "MAGNETIC" : "TRUE",
(int)track.getMagneticHeading(),
(int)track.getTrueHeading(),
(int)track.getSelectedHeading(),
@@ -216,30 +208,17 @@ private void updateContent() {
track.getAcas().getTargetRange()
));
- infoAccuracy.setText(String.format(
- "NIC: %d\n" +
- "NICa: %d\n" +
- "NICb: %d\n" +
- "NICc: %d\n" +
- "NACv: %d\n" +
- "NACp: %s\n" +
- "SIL: %d\n" +
- "----------------------------\n",
- track.getNIC(),
- track.getNICa(),
- track.getNICb(),
- track.getNICc(),
- track.getNACv(),
- track.getNACp(),
- track.getSil()
- ));
-
- infoAbds.setText(String.format(
+ infoAbds.setText(
"Version: %s\n" +
- "Single Antenna: %s\n",
- track.getVersion().name(),
- track.getSingleAntenna() ? "YES" : "NO"
- ));
+ track.getVersion().name()
+ );
+
+ register05.setText(track.register05().toString());
+ register06.setText(track.register06().toString());
+ register07.setText(track.register07().toString());
+ register08.setText(track.register08().toString());
+ register09.setText(track.register09().toString());
+ register17.setText(track.register17().toString());
}
private String formatAcasResolution() {
@@ -249,108 +228,4 @@ private String formatAcasResolution() {
return track.getAcas().getResolutionAdvisory().toString();
}
-
- private void createUIComponents() {
- gcibReportTable = new JTable(new GcibTable(track));
- gcibReportTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
- }
-
- private class GcibTable extends AbstractTableModel {
- private final String[] GCIB = {
- "0,5 Extended squitter airborne position",
- "0,6 Extended squitter surface position",
- "0,7 Extended squitter identification and category",
- "0,7 Extended squitter status",
- "0,9 Extended squitter airborne velocity information",
- "0,A Extended squitter event-driven information",
- "2,0 Aircraft identification",
- "2,1 Aircraft registration number",
- "4,0 Selected vertical intention",
- "4,1 Next waypoint identifier",
- "4,2 Next waypoint position",
- "4,3 Next waypoint information",
- "4,4 Meteorological routine report",
- "4,5 Meteorological hazard report",
- "4,8 VHF channel report",
- "5,0 Track and turn report",
- "5,1 Position coarse",
- "5,2 Position fine",
- "5,3 Air-referenced state vector",
- "5,4 Waypoint 1",
- "5,5 Waypoint 2",
- "5,6 Waypoint 3",
- "5,F Quasi-static parameter monitoring",
- "6,0 Heading and speed report",
- };
-
- public GcibTable(Track track) {
- }
-
- @Override
- public int getRowCount() {
- if (track.getCapabilityReport().isAvailable()) {
- return 24;
- }
-
- return 1;
- }
-
- @Override
- public int getColumnCount() {
- return 2;
- }
-
- @Override
- public String getColumnName(int column) {
- switch (column) {
- case 0: return "GCIB";
- case 1: return "Available";
- }
-
- return null;
- }
-
- @Override
- public Object getValueAt(int rowIndex, int columnIndex) {
- if (!track.getCapabilityReport().isAvailable()) {
- if (columnIndex == 0)
- return "Not Available";
- else
- return "";
- }
-
- if (columnIndex == 0) {
- return GCIB[rowIndex];
- }
-
- switch (rowIndex) {
- case 0: return track.getCapabilityReport().isBds05() ? "YES" : "NO";
- case 1: return track.getCapabilityReport().isBds06() ? "YES" : "NO";
- case 2: return track.getCapabilityReport().isBds07() ? "YES" : "NO";
- case 3: return track.getCapabilityReport().isBds08() ? "YES" : "NO";
- case 4: return track.getCapabilityReport().isBds09() ? "YES" : "NO";
- case 5: return track.getCapabilityReport().isBds0A() ? "YES" : "NO";
- case 6: return track.getCapabilityReport().isBds20() ? "YES" : "NO";
- case 7: return track.getCapabilityReport().isBds21() ? "YES" : "NO";
- case 8: return track.getCapabilityReport().isBds40() ? "YES" : "NO";
- case 9: return track.getCapabilityReport().isBds41() ? "YES" : "NO";
- case 10: return track.getCapabilityReport().isBds42() ? "YES" : "NO";
- case 11: return track.getCapabilityReport().isBds43() ? "YES" : "NO";
- case 12: return track.getCapabilityReport().isBds44() ? "YES" : "NO";
- case 13: return track.getCapabilityReport().isBds45() ? "YES" : "NO";
- case 14: return track.getCapabilityReport().isBds48() ? "YES" : "NO";
- case 15: return track.getCapabilityReport().isBds50() ? "YES" : "NO";
- case 16: return track.getCapabilityReport().isBds51() ? "YES" : "NO";
- case 17: return track.getCapabilityReport().isBds52() ? "YES" : "NO";
- case 18: return track.getCapabilityReport().isBds53() ? "YES" : "NO";
- case 19: return track.getCapabilityReport().isBds54() ? "YES" : "NO";
- case 20: return track.getCapabilityReport().isBds55() ? "YES" : "NO";
- case 21: return track.getCapabilityReport().isBds56() ? "YES" : "NO";
- case 22: return track.getCapabilityReport().isBds5F() ? "YES" : "NO";
- case 23: return track.getCapabilityReport().isBds60() ? "YES" : "NO";
- }
-
- return null;
- }
- }
}
diff --git a/examples/stdout/build.gradle b/examples/stdout/build.gradle
new file mode 100644
index 0000000..89bb44a
--- /dev/null
+++ b/examples/stdout/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'idea'
+apply plugin: 'java'
+apply plugin: 'application'
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+}
+
+dependencies {
+ implementation project(':')
+}
diff --git a/examples/stdout/src/main/java/aero/t2s/modes/examples/StdOutExample.java b/examples/stdout/src/main/java/aero/t2s/modes/examples/StdOutExample.java
new file mode 100644
index 0000000..336e666
--- /dev/null
+++ b/examples/stdout/src/main/java/aero/t2s/modes/examples/StdOutExample.java
@@ -0,0 +1,67 @@
+package aero.t2s.modes.examples;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import aero.t2s.modes.ModeS;
+import aero.t2s.modes.decoder.df.DF20;
+import aero.t2s.modes.decoder.df.DF21;
+
+public class StdOutExample {
+ public static void main(String[] args) {
+ ModeS modes = new ModeS(
+ "192.168.178.190", // Host IP where the Dump1090 server is running
+ 30002, // The port with raw output (default 30002)
+ 51, // Decimal latitude
+ 4 // Decimal longitude
+ );
+// modes.onTrackCreated(track -> System.out.println("CREATED " + track.toString()));
+// modes.onTrackUpdated(track -> System.out.println("UPDATED " + track.toString()));
+// modes.onTrackDeleted(track -> System.out.println("DELETED " + track.toString()));
+
+ MessageCount count = new MessageCount();
+
+ Timer timer = new Timer();
+ timer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ if (count.total > 0) {
+ System.out.print("Multi BDS messages last 60 seconds: " + count.multi + " ( " + Math.round((float)count.multi / count.total) + "%)\n");
+ count.reset();
+ }
+ }
+ }, 0, 60000);
+
+ modes.onMessage(df -> {
+ count.increment();;
+ if (df instanceof DF20) {
+ if (((DF20) df).isMultipleMatches())
+ count.incrementMulti();
+ }
+ if (df instanceof DF21) {
+ if (((DF21) df).isMultipleMatches())
+ count.incrementMulti();
+ }
+ });
+
+ modes.start();
+ }
+
+ static class MessageCount {
+ long total = 0;
+ long multi = 0;
+
+ void reset() {
+ total = 0;
+ multi = 0;
+ }
+
+ void increment() {
+ total++;
+ }
+
+ void incrementMulti() {
+ multi++;
+ }
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index 25dcfce..5b03a12 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
GROUP=aero.t2s
-VERSION_NAME=0.2.2-SNAPSHOT
+VERSION_NAME=0.2.8-SNAPSHOT
POM_ARTIFACT_ID=mode-s
POM_NAME=Mode-S/ADS-B (1090Mhz)
@@ -8,10 +8,10 @@ POM_PACKAGING=jar
POM_DESCRIPTION=Mode-S/ADS-B (1090Mhz) allows for both message based decoding and tracking aircraft.
POM_INCEPTION_YEAR=2020
-POM_URL=https://github.com/terminal2/java-modes-library/
-POM_SCM_URL=https://github.com/terminal2/java-modes-library/
-POM_SCM_CONNECTION=scm:git:git://github.com/terminal2/java-modes-library.git
-POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/terminal2/java-modes-library.git
+POM_URL=https://github.com/terminal2/java-mode-s/
+POM_SCM_URL=https://github.com/terminal2/java-mode-s/
+POM_SCM_CONNECTION=scm:git:git://github.com/terminal2/java-mode-s.git
+POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/terminal2/java-mode-s.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 62d4c05..249e583 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 622ab64..ae04661 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index fbd7c51..a69d9cb 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,101 @@
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
+APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
+ JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +140,101 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=`expr $i + 1`
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index a9f778a..53a6b23 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -64,21 +64,6 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
@@ -86,17 +71,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..f3f0a36
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include('examples:gui', 'examples:stdout')
diff --git a/src/main/java/aero/t2s/modes/CapabilityReport.java b/src/main/java/aero/t2s/modes/CapabilityReport.java
index df9a433..67bbf8c 100644
--- a/src/main/java/aero/t2s/modes/CapabilityReport.java
+++ b/src/main/java/aero/t2s/modes/CapabilityReport.java
@@ -214,4 +214,35 @@ public void all() {
bds5F = true;
bds60 = true;
}
+
+
+ @Override
+ public String toString() {
+ return "Bds17{" +
+ "\nbds05=" + bds05 +
+ ",\n bds06=" + bds06 +
+ ",\n bds07=" + bds07 +
+ ",\n bds08=" + bds08 +
+ ",\n bds09=" + bds09 +
+ ",\n bds0A=" + bds0A +
+ ",\n bds20=" + bds20 +
+ ",\n bds21=" + bds21 +
+ ",\n bds40=" + bds40 +
+ ",\n bds41=" + bds41 +
+ ",\n bds42=" + bds42 +
+ ",\n bds43=" + bds43 +
+ ",\n bds44=" + bds44 +
+ ",\n bds45=" + bds45 +
+ ",\n bds48=" + bds48 +
+ ",\n bds50=" + bds50 +
+ ",\n bds51=" + bds51 +
+ ",\n bds52=" + bds52 +
+ ",\n bds53=" + bds53 +
+ ",\n bds54=" + bds54 +
+ ",\n bds55=" + bds55 +
+ ",\n bds56=" + bds56 +
+ ",\n bds5F=" + bds5F +
+ ",\n bds60=" + bds60 +
+ "\n}";
+ }
}
diff --git a/src/main/java/aero/t2s/modes/CprPosition.java b/src/main/java/aero/t2s/modes/CprPosition.java
index 5326368..2192335 100644
--- a/src/main/java/aero/t2s/modes/CprPosition.java
+++ b/src/main/java/aero/t2s/modes/CprPosition.java
@@ -1,9 +1,43 @@
package aero.t2s.modes;
+import java.time.Instant;
+
public class CprPosition {
private double lat;
private double lon;
- private int time;
+ private boolean surface = false;
+ private boolean valid = false;
+ private double latZone;
+ private double lonZone;
+ private long time;
+
+ public CprPosition() {
+ this.lat = 0.0;
+ this.lon = 0.0;
+ this.surface = false;
+ this.valid = false;
+ }
+
+ public CprPosition(double lat, double lon, boolean surface) {
+ setLatLon(lat ,lon);
+ this.surface = surface;
+ }
+
+ public CprPosition(double lat, double lon) {
+ setLatLon(lat ,lon);
+ }
+
+ public CprPosition(int cprLat, int cprLon, boolean surface) {
+ setLatLon(cprLat / (double) (1 << 17), cprLon / (double) (1 << 17));
+ this.surface = surface;
+ }
+
+ public void setLatLon(double lat, double lon) {
+ this.lat = lat;
+ this.lon = lon;
+ this.time = Instant.now().toEpochMilli();
+ this.valid = true;
+ }
public void setLat(double lat) {
this.lat = lat;
@@ -21,15 +55,51 @@ public double getLon() {
return lon;
}
- public void setTime(int time) {
+ public void setSurface(boolean surface) {
+ this.surface = surface;
+ }
+
+ public boolean getSurface() {
+ return this.surface;
+ }
+
+ public void setZones(double latZone, double lonZone) {
+ this.latZone = latZone;
+ this.lonZone = lonZone;
+ }
+ public void setLatZone(double zone) {
+ this.latZone = zone;
+ }
+
+ public double getLatZone() {
+ return this.latZone;
+ }
+
+ public void setLonZone(double zone) {
+ this.lonZone = zone;
+ }
+
+ public double getLonZone() {
+ return this.lonZone;
+ }
+
+ public void setTime(long time) {
this.time = time;
}
- public int getTime() {
+ public long getTime() {
return time;
}
public boolean isValid() {
- return lat != 0d && lon != 0;
+ return valid;
+ }
+
+ public void setValid(boolean valid) {
+ this.valid = valid;
+ }
+
+ public boolean isExpired() {
+ return time < Instant.now().minusSeconds(10).toEpochMilli();
}
}
diff --git a/src/main/java/aero/t2s/modes/ModeAcMessageException.java b/src/main/java/aero/t2s/modes/ModeAcMessageException.java
new file mode 100644
index 0000000..5219775
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/ModeAcMessageException.java
@@ -0,0 +1,5 @@
+package aero.t2s.modes;
+
+public class ModeAcMessageException extends Exception implements DownlinkException {
+
+}
diff --git a/src/main/java/aero/t2s/modes/ModeS.java b/src/main/java/aero/t2s/modes/ModeS.java
index ad9b448..d4cdffd 100644
--- a/src/main/java/aero/t2s/modes/ModeS.java
+++ b/src/main/java/aero/t2s/modes/ModeS.java
@@ -26,8 +26,8 @@ public ModeS(String host, int port, double originLat, double originLon, ModeSDat
handler = new ModeSTrackHandler(tracks, originLat, originLon, database);
handler.onTrackCreated(onTrackCreated);
- handler.onTrackCreated(onTrackUpdated);
- handler.onTrackCreated(onTrackDeleted);
+ handler.onTrackUpdated(onTrackUpdated);
+ handler.onTrackDeleted(onTrackDeleted);
handler.onMessage(onMessage);
listener = new ModeSListener(new InetSocketAddress(host, port), handler);
diff --git a/src/main/java/aero/t2s/modes/ModeSHandler.java b/src/main/java/aero/t2s/modes/ModeSHandler.java
index a4ea818..c752f03 100644
--- a/src/main/java/aero/t2s/modes/ModeSHandler.java
+++ b/src/main/java/aero/t2s/modes/ModeSHandler.java
@@ -1,10 +1,13 @@
package aero.t2s.modes;
import aero.t2s.modes.decoder.df.DownlinkFormat;
+import aero.t2s.modes.decoder.df.df17.PositionUpdate;
import java.util.function.Consumer;
abstract public class ModeSHandler {
+ protected double originLat;
+ protected double originLon;
protected Consumer
*
- * Note: LSB (1 bit) = 16feet with a range 0 - 65520 feet, this class considers altitudes above 50000ft as invalid / error.
+ * Note: LSB (1 bit) = 16feet with a range 0 - 65520 feet, this class considers altitudes above 52000ft as invalid / error.
*
* FMS Selected altitude
*
@@ -54,13 +54,13 @@
* The FMS selected altitude field is transmitting 32000ft, the Target Altitude Source flag is set to MCP.
*
*
- * Note: LSB (1 bit) = 16feet with a range 0 - 65520 feet, this class considers altitudes above 50000ft as invalid / error.
+ * Note: LSB (1 bit) = 16feet with a range 0 - 65520 feet, this class considers altitudes above 52000ft as invalid / error.
*
* Barometric Pressure Setting
*
- * When status flag (bit 27) is set to false indicates the information is valid and van be used
+ * When status flag (bit 27) is set to 1 indicates the information is valid and van be used
*
- * Note: LSB (1 bit) = 0.1mb with a range 0 - 410mb. You need to add 800mb to receive the real baro steting
+ * Note: LSB (1 bit) = 0.1mb with a range 0 - 410mb. You need to add 800mb to receive the real baro setting
*
* MCP/FCU Mode bits
*
@@ -113,7 +113,7 @@ public Bds40(short[] data) {
selectedAltitude = (((data[4] & 0b01111111) << 5) | (data[5] & 0b11111000) >>> 3) * 16;
if (statusMcp) {
- if (selectedAltitude > 50000) {
+ if (selectedAltitude > 52000) {
invalidate();
return;
}
@@ -126,7 +126,7 @@ public Bds40(short[] data) {
fmsAltitude = (((data[5] & 0x3) << 10) | (data[6] << 2) | ((data[7] >>> 6) & 0x3)) * 16;
if (statusFms) {
- if (fmsAltitude <= 0 || fmsAltitude > 50000) {
+ if (fmsAltitude < 0 || fmsAltitude > 52000) {
invalidate();
return;
}
diff --git a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds44.java b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds44.java
index 26c6680..f202da7 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds44.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds44.java
@@ -33,6 +33,14 @@ public Bds44(short[] data) {
}
statusWindSpeed = (data[4] & 0b00001000) != 0;
+ statusAverageStaticPressure = (data[8] & 0b00100000) != 0;
+ statusTurbulence = (data[9] & 0b00000010) != 0;
+
+ if (!statusWindSpeed) {
+ invalidate();
+ return;
+ }
+
windSpeed = (data[4] & 0b00000111) << 6 | data[5] >> 2;
windDirection = ((data[5] & 0b00000011) << 7 | data[6] >> 1) * WIND_DIRECTION_ACCURACY;
if (!statusWindSpeed && windSpeed != 0) {
@@ -52,14 +60,12 @@ public Bds44(short[] data) {
return;
}
- statusAverageStaticPressure = (data[8] & 0b00100000) != 0;
averageStaticPressure = ((data[8] & 0b00011111) << 6) | data[9] >> 2;
if (!statusAverageStaticPressure && averageStaticPressure != 0) {
invalidate();
return;
}
- statusTurbulence = (data[9] & 0b00000010) != 0;
turbulence = Hazard.find(((data[9] & 0b00000001) << 1) | data[10] >>> 7);
if (!statusTurbulence && turbulence != Hazard.NIL) {
invalidate();
diff --git a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds45.java b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds45.java
index 60d956d..7b6f8b4 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds45.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds45.java
@@ -1,8 +1,10 @@
package aero.t2s.modes.decoder.df.bds;
+import aero.t2s.modes.Altitude;
import aero.t2s.modes.Meteo;
import aero.t2s.modes.Track;
import aero.t2s.modes.constants.Hazard;
+import aero.t2s.modes.decoder.AltitudeEncoding;
public class Bds45 extends Bds {
private static final double SAT_ACCURACY = 0.25d;
@@ -105,6 +107,53 @@ public Bds45(short[] data) {
invalidate();
return;
}
+
+ // Windshear + turbulence + microburst => Very unlikely to be a BDS 45 valid message let's flag it as invalid
+ if (statusTurbulence && statusWindShear && statusMicroBurst) {
+ invalidate();
+ return;
+ }
+
+ if (statusMicroBurst) {
+ // If message is DF20 (altitude encoding) and altitude is above 10000ft microburst is unlikely flag as invalid
+ if (data[0] >>> 3 == 20) {
+ Altitude altitude = AltitudeEncoding.decode((data[2] & 0x1F) << 8 | data[3]);
+ if (altitude.getAltitude() > 10_000) {
+ invalidate();
+ return;
+ }
+ }
+ // DF 21 message since we do not have altitude info and message is likely to be BDS17 flag BDS45 on DF21 with microburst as invalid
+ else {
+ invalidate();
+ return;
+ }
+ }
+
+ if (statusTurbulence || statusWindShear || statusMicroBurst || statusIcing || statusWake) {
+ boolean nothing = true;
+
+ if (statusTurbulence && turbulence != Hazard.NIL) {
+ nothing = false;
+ }
+ if (statusWindShear && windShear != Hazard.NIL) {
+ nothing = false;
+ }
+ if (statusMicroBurst && microBurst != Hazard.NIL) {
+ nothing = false;
+ }
+ if (statusIcing && icing != Hazard.NIL) {
+ nothing = false;
+ }
+ if (statusWake && wake != Hazard.NIL) {
+ nothing = false;
+ }
+
+ if (nothing) {
+ invalidate();
+ return;
+ }
+ }
}
diff --git a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds50.java b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds50.java
index cf23e53..3098ae8 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds50.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds50.java
@@ -1,6 +1,8 @@
package aero.t2s.modes.decoder.df.bds;
+import aero.t2s.modes.Altitude;
import aero.t2s.modes.Track;
+import aero.t2s.modes.decoder.AltitudeEncoding;
/**
* 56-bit MB Field is structured in the following format
@@ -201,6 +203,48 @@ public Bds50(short[] data) {
invalidate();
return;
}
+
+ // Check if values are way off th scale.
+ // We can only check large values
+ if ((statusTas || statusGs) && statusTrackAngle && statusRollAngle && Math.abs(trackAngleRate) > 0.25 && Math.abs(rollAngle) > 5) {
+ // We cannot have a rate one turn at 180 knots or greater at less than 30 degrees of bank
+ if ((gs > 180 || tas > 180) && rollAngle < 30 && trackAngleRate > 3) {
+ invalidate();
+ return;
+ }
+ }
+ }
+
+ public Bds compareWithBds60(Bds60 bds60) {
+ // average speed of sound between 0 - 45000ft
+ double speedOfSound = 617d;
+
+ short[] data = getData();
+ if (data[0] >>> 3 == 20) {
+ Altitude altitude = AltitudeEncoding.decode((data[2] & 0x1F) << 8 | data[3]);
+ // Estimated TAS
+ double bds60Tas = bds60.getIas() * (1 + (altitude.getAltitude() / 1000) * 0.02);
+ // Estimated Mach
+ double bds60Mach = bds60Tas / speedOfSound;
+
+ if (Math.abs(bds60Mach - bds60.getMach()) < 0.1) {
+ return bds60;
+ }
+
+ return this;
+ } else {
+ // No altitude information assume check in 2000ft increments
+ for (int altitude = 0; altitude < 45; altitude += 2) {
+ double bds60Tas = bds60.getIas() * (1 + altitude * 0.02);
+ double bds60Mach = bds60Tas / speedOfSound;
+
+ if (Math.abs(bds60Mach - bds60.getMach()) < 0.01) {
+ return bds60;
+ }
+ }
+
+ return this;
+ }
}
@Override
diff --git a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds60.java b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds60.java
index 55bf427..1d1f697 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/bds/Bds60.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/bds/Bds60.java
@@ -79,7 +79,7 @@ public Bds60(short[] data) {
}
double baroSign = ((data[8] >>> 4) & 0x1) == 1 ? -512.0 : 0.0;
- baroRocd = ((((data[8] & 0b00001111) << 5) | (data[9] >>> 3)) + baroSign) * ROCD_ACCURCY;
+ baroRocd = (((((data[8] & 0b00001111) << 5) | (data[9] >>> 3)) + baroSign) * ROCD_ACCURCY) % 16384;
if (statusBaroRocd) {
if (baroRocd < -8000 || baroRocd > 8000) {
invalidate();
@@ -93,7 +93,7 @@ public Bds60(short[] data) {
}
double irsSign = ((data[9] >> 1) & 0x1) == 1 ? -512.0 : 0.0;
- irsRocd = (((data[9] & 0x1) << 8 | data[10]) + irsSign) * ROCD_ACCURCY;
+ irsRocd = ((((data[9] & 0x1) << 8 | data[10]) + irsSign) * ROCD_ACCURCY) % 16384;
if (statusIrsRocd) {
if (irsRocd < -8000 || irsRocd > 6000) {
invalidate();
@@ -106,6 +106,14 @@ public Bds60(short[] data) {
}
}
+ if ((irsRocd > 0 && baroRocd < 0) || (irsRocd < 0 && baroRocd > 0)) {
+ // Aircraft cannot be climbing and descending at the same time
+ if (Math.abs(irsRocd) > 1000 && Math.abs(baroRocd) > 1000) {
+ invalidate();
+ return;
+ }
+ }
+
if (statusMach && statusIas && data[0] >>> 3 == 20) {
double altitude = AltitudeEncoding.decode((data[2] & 0x1F) << 8 | data[3]).getAltitude();
if (altitude > 0) {
diff --git a/src/main/java/aero/t2s/modes/decoder/df/bds/BdsDecoder.java b/src/main/java/aero/t2s/modes/decoder/df/bds/BdsDecoder.java
index bb7209d..0c57ca5 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/bds/BdsDecoder.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/bds/BdsDecoder.java
@@ -44,6 +44,18 @@ public Bds decode() throws MultipleBdsMatchesFoundException, EmptyMessageExcepti
}
if (valid.size() > 1) {
+ // Is BDS 50 / 60 pair
+ if (valid.size() == 2 && valid.stream().anyMatch(bds -> bds.getClass().equals(Bds50.class)) && valid.stream().anyMatch(bds -> bds.getClass().equals(Bds60.class))) {
+ Bds bds50 = valid.stream().filter(bds -> bds.getClass().equals(Bds50.class)).findFirst().get();
+ Bds bds60 = valid.stream().filter(bds -> bds.getClass().equals(Bds60.class)).findFirst().get();
+
+ Bds likelyBds = ((Bds50)bds50).compareWithBds60((Bds60) bds60);
+
+ if (likelyBds != null) {
+ return likelyBds;
+ }
+ }
+
throw new MultipleBdsMatchesFoundException(valid);
}
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/AirbornePosition.java b/src/main/java/aero/t2s/modes/decoder/df/df17/AirbornePosition.java
index 67129e2..eb37d3a 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/AirbornePosition.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/AirbornePosition.java
@@ -1,28 +1,29 @@
package aero.t2s.modes.decoder.df.df17;
import aero.t2s.modes.Track;
-import aero.t2s.modes.constants.BarometricAltitudeIntegrityCode;
-import aero.t2s.modes.constants.SurveillanceStatus;
-import aero.t2s.modes.constants.Version;
+import aero.t2s.modes.CprPosition;
+import aero.t2s.modes.constants.*;
+import aero.t2s.modes.registers.Register05;
+import aero.t2s.modes.registers.Register05V0;
+import aero.t2s.modes.registers.Register05V2;
-public class AirbornePosition extends ExtendedSquitter {
- private final double originLat;
- private final double originLon;
+import java.util.*;
+public class AirbornePosition extends ExtendedSquitter {
+ private final String address;
private SurveillanceStatus surveillanceStatus;
private int singleAntennaFlag;
private boolean altitudeSourceBaro;
private int altitude;
- private boolean positionAvailable;
private double lat;
private double lon;
+ private boolean positionAvailable;
- public AirbornePosition(short[] data, final double originLat, final double originLon) {
+ public AirbornePosition(short[] data, String address) {
super(data);
- this.originLat = originLat;
- this.originLon = originLon;
+ this.address = address;
}
@Override
@@ -42,10 +43,8 @@ public AirbornePosition decode() {
return this;
}
- positionAvailable = true;
-
int time = (data[6] >>> 3) & 0x1;
- boolean cprEven = ((data[6] >>> 2) & 0x1) == 0;
+ boolean isCprEven = ((data[6] >>> 2) & 0x1) == 0;
int cprLat = (data[6] & 0x3) << 15;
cprLat = cprLat | (data[7] << 7);
@@ -55,8 +54,14 @@ public AirbornePosition decode() {
cprLon = cprLon | (data[9] << 8);
cprLon = cprLon | data[10];
- calculatePosition(cprEven, ((double)cprLat) / ((double)(1 << 17)), ((double)cprLon) / ((double)(1 << 17)), time);
-
+ CprPosition newPosition = PositionUpdate.calculate(address, isCprEven, new CprPosition(cprLat, cprLon, false));
+ if (newPosition != null) {
+ this.lat = newPosition.getLat();
+ this.lon = newPosition.getLon();
+ this.positionAvailable = true;
+ } else {
+ this.positionAvailable = false;
+ }
return this;
}
@@ -66,23 +71,43 @@ public void apply(Track track) {
track.setSpi(surveillanceStatus == SurveillanceStatus.SPI);
track.setTempAlert(surveillanceStatus == SurveillanceStatus.TEMPORARY_ALERT);
track.setEmergency(surveillanceStatus == SurveillanceStatus.PERMANENT_ALERT);
+ if (positionAvailable) {
+ track.setLatLon(lat, lon);
+ }
- // Determine Antenna or Navigation Integrity Category
- if (track.getVersion().ordinal() < Version.VERSION2.ordinal()) {
- track.setSingleAntenna(singleAntennaFlag == 0);
- } else {
- track.setNICb(singleAntennaFlag);
+ if (versionChanged(track)) {
+ switch (track.getVersion()) {
+ case VERSION0:
+ case VERSION1:
+ track.register05(new Register05V0());
+ break;
+ case VERSION2:
+ track.register05(new Register05V2());
+ break;
+ }
}
+ HorizontalProtectionLimit hpl = determineHorizontalProtection(track);
+ AltitudeSource altitudeSource = determineAltitudeSource();
+
+ Register05 position = track.register05();
- if (altitudeSourceBaro) {
- track.setBaroAltitude(altitude);
+ if (position instanceof Register05V2) {
+ position.update(hpl, altitude, altitudeSource, lat, lon, surveillanceStatus);
} else {
- track.setGnssHeight(altitude);
+ ((Register05V0) position).update(hpl, altitude, altitudeSource, lat, lon, surveillanceStatus, singleAntennaFlag == 1);
+ }
+ }
+
+ private boolean versionChanged(Track track) {
+ if (track.register05() == null) {
+ return true;
+ }
+
+ if (track.getVersion() == Version.VERSION2 && track.register05().getVersion() != Version.VERSION2) {
+ return true;
}
- track.setNIC(determineNIC(track, typeCode));
- track.setLat(lat);
- track.setLon(lon);
+ return false;
}
public int getSingleAntennaFlag() {
@@ -99,14 +124,6 @@ public BarometricAltitudeIntegrityCode getNICbaro() {
}
}
- public double getOriginLat() {
- return originLat;
- }
-
- public double getOriginLon() {
- return originLon;
- }
-
public SurveillanceStatus getSurveillanceStatus() {
return surveillanceStatus;
}
@@ -131,137 +148,57 @@ public double getLon() {
return lon;
}
- private int determineNIC(Track track, int typeCode) {
+ private HorizontalProtectionLimit determineHorizontalProtection(Track track) {
switch (typeCode) {
case 9:
case 20:
- return 11;
+ return HorizontalProtectionLimit.RC_7_5;
case 10:
case 21:
- return 10;
+ return HorizontalProtectionLimit.RC_25;
case 11:
- if (track.getNICa() == 1 && track.getNICb() == 1) {
- return 9;
- } else if (track.getNICa() == 0 && track.getNICb() == 0) {
- return 8;
+ if (singleAntennaFlag == 1 && track.getVersion() == Version.VERSION2) {
+ return HorizontalProtectionLimit.RC_75;
} else {
- return 0;
+ return HorizontalProtectionLimit.RC_185;
}
case 12:
- return 7;
+ return HorizontalProtectionLimit.RC_370;
case 13:
- return 6;
+ if (singleAntennaFlag == 1 && track.getVersion() == Version.VERSION2) {
+ return HorizontalProtectionLimit.RC_555;
+ } else {
+ return HorizontalProtectionLimit.RC_926;
+ }
case 14:
- return 5;
+ return HorizontalProtectionLimit.RC_1852;
case 15:
- return 4;
+ return HorizontalProtectionLimit.RC_3704;
case 16:
- if (track.getNICa() == 0 && track.getNICb() == 0) {
- return 2;
- } else if (track.getNICa() == 1 && track.getNICb() == 1) {
- return 3;
+ if (singleAntennaFlag == 1 && track.getVersion() == Version.VERSION2) {
+ return HorizontalProtectionLimit.RC_7408;
} else {
- return 0;
+ return HorizontalProtectionLimit.RC_14816;
}
case 17:
- return 1;
+ return HorizontalProtectionLimit.RC_37040;
case 18:
case 22:
default:
- return 0;
+ return HorizontalProtectionLimit.RC_UNKNOWN;
}
}
- private void calculatePosition(boolean isEven, double lat, double lon, double time) {
-// CprPosition cprEven = track.getCprPosition(true);
-// CprPosition cprOdd = track.getCprPosition(false);
-
-// if (! (cprEven.isValid() && cprOdd.isValid())) {
- calculateLocal(isEven, lat, lon, time);
-// return;
-// }
-
-// calculateGlobal(track, cprEven, cprOdd);
- }
-
- private void calculateLocal(boolean isEven, double lat, double lon, double time) {
- boolean isOdd = !isEven;
-// CprPosition cpr = track.getCprPosition(isEven);
-
- double dlat = isOdd ? 360.0 / 59.0 : 360.0 / 60.0;
-
- double j = Math.floor(originLat / dlat) + Math.floor((originLat % dlat) / dlat - lat + 0.5);
-
- lat = dlat * (j + lat);
-
- double nl = NL(lat) - (isOdd ? 1.0 : 0.0);
- double dlon = nl > 0 ? 360.0 / nl : 360;
-
- double m = Math.floor(originLon / dlon) + Math.floor((originLon % dlon) / dlon - lon + 0.5);
- lon = dlon * (m + lon);
+ private AltitudeSource determineAltitudeSource() {
+ if (typeCode < 19) {
+ return AltitudeSource.BARO;
+ }
- this.lat = lat;
- this.lon = lon;
- }
+ if (typeCode == 19) {
+ return AltitudeSource.BARO_GNSS_DIFF;
+ }
-// private void calculateGlobal(Track track, CprPosition cprEven, CprPosition cprOdd) {
-// double dLat0 = 360.0 / 60.0;
-// double dLat1 = 360.0 / 59.0;
-//
-// double j = Math.floor(59.0 * cprEven.getLat() - 60.0 * cprOdd.getLat() + 0.5);
-//
-// double latEven = dLat0 * (j % 60.0 + cprEven.getLat());
-// double latOdd = dLat1 * (j % 59.0 + cprOdd.getLat());
-//
-// if (latEven >= 270.0 && latEven <= 360.0) {
-// latEven -= 360.0;
-// }
-//
-// if (latOdd >= 270.0 && latOdd <= 360.0) {
-// latOdd -= 360.0;
-// }
-//
-// if (NL(latEven) != NL(latOdd)) {
-// return;
-// }
-//
-// double lat;
-// double lon;
-// if (cprEven.getTime() > cprOdd.getTime()) {
-// double ni = cprN(latEven, 0);
-// double m = Math.floor(cprEven.getLon() * (NL(latEven) - 1) - cprOdd.getLon() * NL(latEven) + 0.5);
-//
-// lat = latEven;
-// lon = (360d / ni) * (m % ni + cprEven.getLon());
-// } else {
-// double ni = cprN(latOdd, 1);
-// double m = Math.floor(cprEven.getLon() * (NL(latOdd) - 1) - cprOdd.getLon() * NL(latOdd) + 0.5);
-//
-// lat = latOdd;
-// lon = (360d / ni) * (m % ni + cprOdd.getLon());
-// }
-//
-// if (lon > 180d) {
-// lon -= 360d;
-// }
-//
-// track.setLat(lat);
-// track.setLon(lon);
-// }
-
-// private double cprN(double lat, double isOdd) {
-// double nl = NL(lat) - isOdd;
-//
-// return nl > 1 ? nl : 1;
-// }
-
- private double NL(double lat) {
- if (lat == 0) return 59;
- else if (Math.abs(lat) == 87) return 2;
- else if (Math.abs(lat) > 87) return 1;
-
- double tmp = 1 - (1 - Math.cos(Math.PI / (2.0 * 15.0))) / Math.pow(Math.cos(Math.PI / 180.0 * Math.abs(lat)), 2);
- return Math.floor(2 * Math.PI / Math.acos(tmp));
+ return AltitudeSource.GNSS_HAE;
}
private int calculateAltitude(short[] data, int typeCode) {
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityAirspeedHeading.java b/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityAirspeedHeading.java
index 4d819cf..7f67d26 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityAirspeedHeading.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityAirspeedHeading.java
@@ -1,8 +1,9 @@
package aero.t2s.modes.decoder.df.df17;
import aero.t2s.modes.Track;
-import aero.t2s.modes.constants.RocdSource;
-import aero.t2s.modes.constants.Speed;
+import aero.t2s.modes.constants.*;
+import aero.t2s.modes.registers.Register09;
+import aero.t2s.modes.registers.Register09V0;
public class AirborneVelocityAirspeedHeading extends AirborneVelocity {
private static final double HEADING_RESOLUTION = 360.0 / 1024.0;
@@ -35,8 +36,6 @@ public AirborneVelocityAirspeedHeading decode() {
@Override
public void apply(Track track) {
- track.setNACv(NACv.ordinal());
-
if (isGnssAltitudeDifferenceFromBaroAvailable()) {
track.setGeometricHeightOffset(getGnssAltitudeDifferenceFromBaro());
}
@@ -50,6 +49,31 @@ public void apply(Track track) {
track.setRocd(getRocd());
}
}
+
+
+ if (track.getVersion().equals(Version.VERSION2) && track.register09() instanceof Register09V0) {
+ track.register09(new Register09V0());
+ }
+ if (!track.getVersion().equals(Version.VERSION2) && !(track.register09() instanceof Register09V0)) {
+ track.register09(new Register09());
+ }
+
+ if (track.register09() instanceof Register09V0) {
+ ((Register09V0) track.register09()).setIfrCapability(isIfrCapability());
+ }
+
+ track.register09()
+ .setHeadingSource(isHeadingAvailable() ? Angle.HEADING : Angle.UNAVAILABLE)
+ .setHeading((int) Math.round(heading))
+ .setAirspeedSource(isAirspeedAvailable() ? airspeedType : Speed.UNKNOWN)
+ .setAirspeed(airspeed)
+ .setVerticalRateSource(isRocdAvailable() ? getRocdSource() : RocdSource.UNKNOWN)
+ .setVerticalRate(isRocdAvailable() ? getRocd() : 0)
+ .setGnssDifferenceFromBaro(isGnssAltitudeDifferenceFromBaroAvailable() ? getGnssAltitudeDifferenceFromBaro() : 0)
+ .setIntentChangeFlag(isIntentChange())
+ .setNACv(NavigationAccuracyCategoryVelocity.values()[NACv.ordinal()])
+ .validate();
+ ;
}
public boolean isHeadingAvailable() {
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityGroundspeed.java b/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityGroundspeed.java
index 8cc08aa..7cdeaac 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityGroundspeed.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/AirborneVelocityGroundspeed.java
@@ -1,7 +1,11 @@
package aero.t2s.modes.decoder.df.df17;
import aero.t2s.modes.Track;
+import aero.t2s.modes.constants.NavigationAccuracyCategoryVelocity;
import aero.t2s.modes.constants.RocdSource;
+import aero.t2s.modes.constants.Version;
+import aero.t2s.modes.registers.Register09;
+import aero.t2s.modes.registers.Register09V0;
public class AirborneVelocityGroundspeed extends AirborneVelocity {
private boolean xVelocityAvailable;
@@ -38,8 +42,6 @@ public AirborneVelocityGroundspeed decode() {
@Override
public void apply(Track track) {
- track.setNACv(NACv.ordinal());
-
if (isGnssAltitudeDifferenceFromBaroAvailable()) {
track.setGeometricHeightOffset(getGnssAltitudeDifferenceFromBaro());
}
@@ -65,5 +67,46 @@ public void apply(Track track) {
if (xVelocityAvailable && yVelocityAvailable) {
track.setGs(Math.sqrt(xVelocity * xVelocity + yVelocity * yVelocity));
}
+
+ if (track.getVersion().equals(Version.VERSION2) && track.register09() instanceof Register09V0) {
+ track.register09(new Register09V0());
+ }
+ if (!track.getVersion().equals(Version.VERSION2) && !(track.register09() instanceof Register09V0)) {
+ track.register09(new Register09());
+ }
+
+ if (track.register09() instanceof Register09V0) {
+ ((Register09V0) track.register09()).setIfrCapability(isIfrCapability());
+ }
+
+ track.register09()
+ .setVerticalRateSource(isRocdAvailable() ? getRocdSource() : RocdSource.UNKNOWN)
+ .setVerticalRate(isRocdAvailable() ? getRocd() : 0)
+ .setGnssDifferenceFromBaro(isGnssAltitudeDifferenceFromBaroAvailable() ? getGnssAltitudeDifferenceFromBaro() : 0)
+ .setIntentChangeFlag(isIntentChange())
+ .setNACv(NavigationAccuracyCategoryVelocity.values()[NACv.ordinal()])
+ .validate();
+
+ if (xVelocityAvailable && yVelocityAvailable) {
+ track.register09()
+ .setVx(xVelocity)
+ .setVy(yVelocity);
+ }
+ }
+
+ public boolean isVxAvailable() {
+ return xVelocityAvailable;
+ }
+
+ public int getVx() {
+ return xVelocity;
+ }
+
+ public boolean isVyAvailable() {
+ return yVelocityAvailable;
+ }
+
+ public int getVy() {
+ return yVelocity;
}
}
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftIdentification.java b/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftIdentification.java
index 6b04cc4..d2fc21f 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftIdentification.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftIdentification.java
@@ -1,10 +1,12 @@
package aero.t2s.modes.decoder.df.df17;
import aero.t2s.modes.Track;
+import aero.t2s.modes.constants.AircraftCategory;
import aero.t2s.modes.decoder.Common;
public class AircraftIdentification extends ExtendedSquitter {
private String acid;
+ private int aircraftEmitterCategory;
public AircraftIdentification(short[] data) {
super(data);
@@ -21,6 +23,7 @@ public AircraftIdentification decode() {
Common.charToString(((data[9] & 0xF) << 2) | (data[10] >>> 6)) +
Common.charToString(data[10] & 0x3F);
acid = acid.replace("_", "");
+ aircraftEmitterCategory = data[4] >> 4;
return this;
}
@@ -28,6 +31,51 @@ public AircraftIdentification decode() {
@Override
public void apply(Track track) {
track.setCallsign(acid);
+
+ track.register08().update(acid, determineAircraftCategory());
+ }
+
+ private AircraftCategory determineAircraftCategory() {
+ switch (typeCode) {
+ case 4:
+ switch (aircraftEmitterCategory) {
+ case 0: return AircraftCategory.NO_ADS_B_EMITTER;
+ case 1: return AircraftCategory.LIGHT;
+ case 2: return AircraftCategory.SMALL;
+ case 3: return AircraftCategory.LARGE;
+ case 4: return AircraftCategory.HIGH_VORTEX_LARGE;
+ case 5: return AircraftCategory.HEAVY;
+ case 6: return AircraftCategory.HIGH_PERFORMANCE;
+ case 7: return AircraftCategory.ROTORCRAFT;
+ default: return AircraftCategory.UNKNOWN;
+ }
+ case 3:
+ switch (aircraftEmitterCategory) {
+ case 0: return AircraftCategory.NO_ADS_B_EMITTER;
+ case 1: return AircraftCategory.GLIDER;
+ case 2: return AircraftCategory.LIGHTER_THAN_AIR;
+ case 3: return AircraftCategory.SKYDIVER;
+ case 4: return AircraftCategory.ULTRALIGHT;
+ case 5: return AircraftCategory.RESERVED;
+ case 6: return AircraftCategory.UNMANNED_AERIAL_VEHICLE;
+ case 7: return AircraftCategory.SPACE;
+ default: return AircraftCategory.UNKNOWN;
+ }
+ case 2:
+ switch (aircraftEmitterCategory) {
+ case 0: return AircraftCategory.NO_ADS_B_EMITTER;
+ case 1: return AircraftCategory.SURFACE_VEHICLE_EMERGENCY;
+ case 2: return AircraftCategory.SURFACE_VEHICLE_SERVICE;
+ case 3: return AircraftCategory.POINT_OBSTACLE;
+ case 4: return AircraftCategory.CLUSTER_OBSTACLE;
+ case 5: return AircraftCategory.LINE_OBSTACLE;
+ case 6: return AircraftCategory.RESERVED;
+ case 7: return AircraftCategory.RESERVED;
+ default: return AircraftCategory.UNKNOWN;
+ }
+ default:
+ return AircraftCategory.UNKNOWN;
+ }
}
public String getAcid() {
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftOperationalStatusVersion2Surface.java b/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftOperationalStatusVersion2Surface.java
index fe2b30a..4b25011 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftOperationalStatusVersion2Surface.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/AircraftOperationalStatusVersion2Surface.java
@@ -13,7 +13,7 @@ public class AircraftOperationalStatusVersion2Surface extends AircraftOperationa
private Angle horizontalSource;
private SourceIntegrityLevelSupplement SILsupp;
private SourceIntegrityLevel SIL;
- private Version verison;
+ private Version version;
public AircraftOperationalStatusVersion2Surface(short[] data) {
super(data);
@@ -21,23 +21,27 @@ public AircraftOperationalStatusVersion2Surface(short[] data) {
@Override
public AircraftOperationalStatusVersion2Surface decode() {
- verison = Version.VERSION2;
- surfaceCapability = new SurfaceCapability((data[5] << 4) | (data[6] & 0b11110000) >>> 4, verison);
+ version = Version.VERSION2;
+ surfaceCapability = new SurfaceCapability((data[5] << 4) | (data[6] & 0b11110000) >>> 4, version);
lengthWidthCode = LengthWidthCode.from(data[6] & 0b00001111);
operationalMode = new SurfaceOperationalMode((data[7] << 8) | data[8]);
- SIL = SourceIntegrityLevel.from((data[10] & 0b000110000) >>> 4);
- SILsupp = SourceIntegrityLevelSupplement.from((data[10] & 0b00000010) >>> 1);
-
int NICsuppA = (data[9] & 0b00010000) >>> 4;
int NACp = (data[9] & 0b00001111);
- NICp = NavigationIntegrityCategory.surface(NACp, NICsuppA);
+ int GVA = (data[10] & 0b11000000) >>> 6;
+ int SIL = (data[10] & 0b00110000) >>> 4;
+
+ int SILsupp = (data[10] & 0b00000010) >>> 1;
+
+ this.SIL = SourceIntegrityLevel.from(SIL);
+ this.SILsupp = SourceIntegrityLevelSupplement.from(SILsupp);
+ this.NICp = NavigationIntegrityCategory.surface(NACp, NICsuppA);
if ((data[10] & 0b00001000) != 0) {
if ((data[10] & 0b00000100) != 0) {
- horizontalSource = Angle.TRUE_TRACK;
- } else {
horizontalSource = Angle.MAGNETIC_TRACK;
+ } else {
+ horizontalSource = Angle.TRUE_TRACK;
}
} else {
if ((data[10] & 0b00000100) != 0) {
@@ -53,6 +57,7 @@ public AircraftOperationalStatusVersion2Surface decode() {
@Override
public void apply(Track track) {
track.setVersion(Version.VERSION2);
+ track.setHorizontalSource(horizontalSource);
}
public SurfaceCapability getSurfaceCapability() {
@@ -83,7 +88,7 @@ public SourceIntegrityLevel getSIL() {
return SIL;
}
- public Version getVerison() {
- return verison;
+ public Version getVersion() {
+ return version;
}
}
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/PositionUpdate.java b/src/main/java/aero/t2s/modes/decoder/df/df17/PositionUpdate.java
new file mode 100644
index 0000000..a744711
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/PositionUpdate.java
@@ -0,0 +1,283 @@
+package aero.t2s.modes.decoder.df.df17;
+
+import aero.t2s.modes.CprPosition;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+public class PositionUpdate {
+ private static double originLat; // Origin is passed-in as a command-line argument as an indication of where the receiver is located
+ private static double originLon;
+
+ private static final double updateThreshold = 0.5d; // Positions which move between updates by more than this (in degrees) should be rejected
+ private static final double receiverRange = 4.0d; // Positions further away from the receiver than this (in degrees) should be rejected
+ private static double receiverLat; // Receiver position is calculated based on data received
+ private static double receiverLon;
+ private static double receiverSumLat; // Running-total of valid positions that can be used to calculate the average receiver position
+ private static double receiverSumLon;
+ private static double receiverSumCount = 0; // number of valid positions received that are used to calculate the average
+
+ private static Map cache = new HashMap<>();
+ private static Timer cacheCleanup;
+
+ private static final double dLatEven = 4.0 * 15.0;
+ private static final double dLatOdd = 4.0 * 15.0 - 1.0;
+ private static final double nlNumerator = 1 - Math.cos(Math.PI / (2.0 * 15.0));
+ private static final double nlPi180 = Math.PI / 180.0;
+
+ private CprPosition even;
+ private CprPosition odd;
+ private CprPosition previous;
+ private CprPosition current;
+
+ static public CprPosition calculate(String address, boolean isCprEven, CprPosition newCpr) {
+ if (!cache.containsKey(address)) {
+ synchronized (cache) {
+ cache.putIfAbsent(address, new PositionUpdate(isCprEven, newCpr));
+ }
+ return null;
+ }
+
+ PositionUpdate positionUpdate;
+ synchronized (cache) {
+ positionUpdate = cache.get(address);
+ }
+ return positionUpdate.calculate(isCprEven, newCpr);
+ }
+
+ private CprPosition calculate(boolean isCprEven, CprPosition newCpr) {
+ if (isCprEven) {
+ even = newCpr;
+ // If the even/odd frames were too many seconds apart, of if their surface bits do not match, discard the previously received frame
+ if ((odd != null) && (odd.isExpired() || (odd.getSurface() != even.getSurface()))) {
+ odd = null;
+ previous = null;
+ }
+ } else {
+ odd = newCpr;
+ if ((even != null) && (even.isExpired() || (even.getSurface() != odd.getSurface()))) {
+ even = null;
+ previous = null;
+ }
+ }
+
+ if (isComplete() && previous == null) {
+ // We've now got a pair of odd/even frames but there was no previous position... so we must use Global calculation to get accurate position
+ calculateGlobal();
+ // Update the receiver position only from Global updates (likely to be the first time updating from this aircraft)
+ PositionUpdate.receiverUpdate(current.getLat(), current.getLon());
+ } else if (previous != null) {
+ // We had a previous accurate position, so we can use the most recent frame to do Local calculation
+ calculateLocal(!isCprEven);
+ } else {
+ current = null;
+ }
+
+ if (current != null && current.isValid()) {
+ // Sanity Check: Is the position just received too far away from the receiver position?
+ if ((Math.abs(current.getLat() - receiverLat) > receiverRange) || (Math.abs(current.getLon() - receiverLon) > receiverRange)) {
+ LoggerFactory.getLogger(getClass()).info("Position Update discarded due to outside receiver range.");
+ current = null;
+ }
+ else {
+ previous = current;
+ }
+ }
+
+ return current;
+ }
+
+ private void calculateLocal(boolean isOdd) {
+ CprPosition cpr = isOdd ? odd : even;
+ CprPosition otherCpr = isOdd ? even : odd;
+ double degrees = cpr.getSurface() ? 90.0 : 360.0;
+ double dLat = degrees / (isOdd ? dLatOdd : dLatEven);
+ double j = Math.floor(previous.getLat() / dLat) + Math.floor(cprMod(previous.getLat(), dLat) / dLat - cpr.getLat() + 0.5);
+ double newLat = dLat * (j + cpr.getLat());
+ if (newLat < 0) {
+ // Something screwed-up
+ current = null;
+ return;
+ }
+
+ double nl = NL(newLat) - (isOdd ? 1.0 : 0.0);
+ double dLon = (nl > 0) ? degrees / nl : degrees;
+ double m = Math.floor(previous.getLon() / dLon) + Math.floor(cprMod(previous.getLon(), dLon) / dLon - cpr.getLon() + 0.5);
+ double newLon = dLon * (m + cpr.getLon());
+
+ cpr.setZones(j, m);
+
+ current = new CprPosition(newLat, newLon, cpr.getSurface());
+ if (current.getSurface()) {
+ validateSurface(current);
+ }
+
+ if ((Math.abs(current.getLat() - previous.getLat()) > updateThreshold) || (Math.abs(current.getLon() - previous.getLon()) > updateThreshold)) {
+ LoggerFactory.getLogger(getClass()).info("Position Update discarded due to unreasonable distance between updates.");
+ current = null;
+ }
+ }
+
+ private void calculateGlobal() {
+ double j = Math.floor(dLatOdd * even.getLat() - dLatEven * odd.getLat() + 0.5);
+ double degrees = even.getSurface() ? 90.0 : 360.0; // Doesn't matter whether we check odd or even as they must both match by now
+
+ double latEven = (degrees / dLatEven) * (cprMod(j, dLatEven) + even.getLat());
+ double latOdd = (degrees / dLatOdd) * (cprMod(j, dLatOdd) + odd.getLat());
+
+ latEven = normaliseLat(latEven);
+ latOdd = normaliseLat(latOdd);
+
+ double nlLatEven = NL(latEven);
+ double nlLatOdd = NL(latOdd);
+
+ if (nlLatEven != nlLatOdd) {
+ // The even and odd frames are in different Longitude zones, so we need to discard BOTH
+ odd = null;
+ even = null;
+ previous = null;
+ current = null;
+ return;
+ }
+
+ double newLat, newLon, ni, m;
+ if (even.getTime() > odd.getTime()) {
+ ni = cprN(nlLatEven, 0);
+ m = Math.floor(even.getLon() * (nlLatEven - 1) - odd.getLon() * nlLatEven + 0.5);
+
+ newLat = latEven;
+ newLon = (degrees / ni) * (cprMod(m, ni) + even.getLon());
+ } else {
+ ni = cprN(nlLatOdd, 1);
+ m = Math.floor(even.getLon() * (nlLatOdd - 1) - odd.getLon() * nlLatOdd + 0.5);
+
+ newLat = latOdd;
+ newLon = (degrees / ni) * (cprMod(m, ni) + odd.getLon());
+ }
+ newLon = normaliseLon(newLon);
+
+ even.setZones(nlLatEven, m);
+ odd.setZones(nlLatOdd, m);
+
+ current = new CprPosition(newLat, newLon, even.getSurface());
+ if (current.getSurface()) {
+ validateSurface(current);
+ }
+ }
+
+ static private double cprMod(double a, double b) {
+ return a - b * Math.floor(a/b);
+ }
+
+ static private double cprN(double nlLat, double isOdd) {
+ return Math.max(1, nlLat - isOdd);
+ }
+
+ static private double NL(double lat) {
+ if (lat == 0) return 59;
+ else if (Math.abs(lat) == 87) return 2;
+ else if (Math.abs(lat) > 87) return 1;
+
+ double tmpCos = Math.cos(nlPi180 * Math.abs(lat));
+ double tmpArc = 1 - nlNumerator / (tmpCos * tmpCos);
+ return Math.floor(2 * Math.PI / Math.acos(tmpArc));
+ }
+
+ public PositionUpdate(boolean isEven, CprPosition cpr) {
+ if (isEven) {
+ this.even = cpr;
+ } else {
+ this.odd = cpr;
+ }
+ }
+
+ public boolean isComplete() {
+ return even != null && odd != null;
+ }
+
+ public boolean isExpired() {
+ return even.isExpired() || odd.isExpired();
+ }
+
+ static public double normaliseLat(double lat) {
+ if (lat >= 270.0 && lat <= 360.0) {
+ lat -= 360.0;
+ }
+ return lat;
+ }
+
+ static public double normaliseLon(double lon) {
+ if (lon > 180.0) {
+ lon -= 360.0;
+ }
+ return lon;
+ }
+
+ private void validateSurface(CprPosition pos) {
+ // For SurfacePositions we get 8 possible solutions: 2x latitude, 4x longitude zones at 90 degree increments
+ double diff;
+ double test;
+ double testDiff;
+
+ // Pick the lat which is closest to the receiver
+ test = pos.getLat();
+ diff = Math.abs(normaliseLat(test) - receiverLat);
+ for (int i = 0 ; i < 1 ; i++){
+ test = normaliseLat(test + 90.0);
+ testDiff = Math.abs(test - receiverLat);
+ if (testDiff < diff) {
+ diff = testDiff;
+ pos.setLat(test);
+ }
+ }
+
+ // Pick the lon which is closest to the receiver
+ test = pos.getLon();
+ diff = Math.abs(normaliseLon(test) - receiverLon);
+ for (int i = 0 ; i < 3 ; i++){
+ test = normaliseLon(test + 90.0);
+ testDiff = Math.abs(test - receiverLon);
+ if (testDiff < diff) {
+ diff = testDiff;
+ pos.setLon(test);
+ }
+ }
+ }
+
+ static private void receiverUpdate(double lat, double lon) {
+ if (!((lat == 0) && (lon == 0))) {
+ receiverSumCount ++;
+ receiverSumLat += lat;
+ receiverSumLon += lon;
+ receiverLat = receiverSumLat / receiverSumCount;
+ receiverLon = receiverSumLon / receiverSumCount;
+ }
+ }
+
+ static public void start(double originLat, double originLon) {
+ PositionUpdate.originLat = originLat;
+ PositionUpdate.originLon = originLon;
+
+ receiverUpdate(originLat, originLon);
+
+ cache.clear();
+ cacheCleanup.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ List expired = new LinkedList<>();
+
+ synchronized (cache) {
+ cache.entrySet().stream().filter(entry -> entry.getValue().isExpired()).forEach(entry -> expired.add(entry.getKey()));
+ expired.forEach(cache::remove);
+ }
+ }
+ }, 0, 10_000);
+ }
+
+ static public void stop() {
+ cacheCleanup.cancel();
+ cacheCleanup = null;
+
+ cache.clear();
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/SurfacePosition.java b/src/main/java/aero/t2s/modes/decoder/df/df17/SurfacePosition.java
index df4097b..04e5d93 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/SurfacePosition.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/SurfacePosition.java
@@ -1,20 +1,155 @@
package aero.t2s.modes.decoder.df.df17;
-import aero.t2s.modes.NotImplementedException;
+import aero.t2s.modes.CprPosition;
import aero.t2s.modes.Track;
+import aero.t2s.modes.constants.Angle;
public class SurfacePosition extends ExtendedSquitter {
- public SurfacePosition(short[] data) {
+ private final String address;
+ private int velocityEncoded = 0;
+ private double velocity = 0;
+
+ private boolean trackAvailable = false;
+ private int trackEncoded = 0;
+ private double track = 0;
+
+ private double lat;
+ private double lon;
+ private boolean positionAvailable = false;
+ private boolean velocityAvailable = false;
+
+ public SurfacePosition(short[] data, String address) {
super(data);
+ this.address = address;
}
@Override
public SurfacePosition decode() {
- throw new NotImplementedException(getClass().getSimpleName() + " Not implemented");
+ velocityEncoded = ((data[4] & 0x7) << 4) | (data[5] >>> 4);
+ velocityAvailable = velocityEncoded != 0;
+ velocity = decodeEncodedVelocity();
+
+ trackAvailable = ((data[5] >>> 3) & 0x1) == 0x1;
+ trackEncoded = ((data[5] & 0x7) << 4) | (data[6] >>> 4);
+ track = trackEncoded * 360.0 / 128.0;
+
+ // Decode position
+ int time = (data[6] >>> 3) & 0x1;
+ boolean isCprEven = ((data[6] >>> 2) & 0x1) == 0;
+
+ int cprLat = (data[6] & 0x3) << 15;
+ cprLat = cprLat | (data[7] << 7);
+ cprLat = cprLat | (data[8] >>> 1);
+
+ int cprLon = ((data[8] & 0x1) << 16);
+ cprLon = cprLon | (data[9] << 8);
+ cprLon = cprLon | data[10];
+
+ CprPosition newPosition = PositionUpdate.calculate(address, isCprEven, new CprPosition(cprLat, cprLon, true));
+ if (newPosition != null) {
+ this.lat = newPosition.getLat();
+ this.lon = newPosition.getLon();
+ this.positionAvailable = true;
+ } else {
+ this.positionAvailable = false;
+ }
+
+ return this;
}
@Override
public void apply(Track track) {
- throw new NotImplementedException(getClass().getSimpleName() + " Not implemented");
+ track.setGroundBit(true);
+
+ if (positionAvailable) {
+ track.setLatLon(lat, lon);
+ }
+
+ if (velocityAvailable) {
+ track.setGs(velocity);
+ }
+
+ if (trackAvailable) {
+ if (track.getHorizontalSource() == Angle.TRUE_TRACK) {
+ track.setTrueHeading(this.track);
+ } else if (track.getHorizontalSource() == Angle.MAGNETIC_HEADING) {
+ track.setMagneticHeading(this.track);
+ } else {
+ track.setTrueHeading(this.track);
+ track.setMagneticHeading(this.track);
+ }
+ }
+ }
+
+ public boolean isPositionAvailable() {
+ return positionAvailable;
+ }
+
+ public boolean isVelocityAvailable() {
+ return this.velocityAvailable;
+ }
+
+ public boolean isTrackAvailable() {
+ return trackAvailable;
+ }
+
+ public double getLat() {
+ return lat;
+ }
+
+ public double getLon() {
+ return lon;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public int getVelocityEncoded() {
+ return velocityEncoded;
+ }
+
+ public double getVelocity() {
+ return this.velocity;
+ }
+
+ public int getTrackEncoded() {
+ return trackEncoded;
+ }
+
+ public double getTrack() {
+ return track;
+ }
+
+ private double decodeEncodedVelocity() {
+ if (this.velocityEncoded == 1) {
+ return 0;
+ }
+ if (this.velocityEncoded <= 8) {
+ return (this.velocityEncoded - 1) * 0.125 + 0;
+ }
+ if (this.velocityEncoded <= 12) {
+ return (this.velocityEncoded - 9) * 0.25 + 1.0;
+ }
+ if (this.velocityEncoded <= 38) {
+ return (this.velocityEncoded - 13) * 0.5 + 2.0;
+ }
+ if (this.velocityEncoded <= 93) {
+ return (this.velocityEncoded - 39) * 1.0 + 15.0;
+ }
+ if (this.velocityEncoded <= 93) {
+ return (this.velocityEncoded - 39) * 1.0 + 15.0;
+ }
+ if (this.velocityEncoded <= 108) {
+ return (this.velocityEncoded - 94) * 2.0 + 70.0;
+ }
+ if (this.velocityEncoded <= 123) {
+ return (this.velocityEncoded - 109) * 5.0 + 100.0;
+ }
+ if (this.velocityEncoded == 124) {
+ return 175.0;
+ }
+
+ return 0; // Could be either "not available" or "reserved"... just assume zero speed for now
}
}
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType0.java b/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType0.java
index 03f1410..85989a9 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType0.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType0.java
@@ -1,6 +1,5 @@
package aero.t2s.modes.decoder.df.df17;
-import aero.t2s.modes.NotImplementedException;
import aero.t2s.modes.Track;
import aero.t2s.modes.constants.*;
@@ -61,8 +60,6 @@ public TargetStatusMessageType0 decode() {
@Override
public void apply(Track track) {
- track.setNACp(NACp);
-
if (horizontalDataAvailable != HorizontalDataAvailable.NOT_VALID)
track.setSelectedHeading(targetHeadingTrack);
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType1.java b/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType1.java
index 7a3adb5..80c4a5f 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType1.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/TargetStatusMessageType1.java
@@ -81,16 +81,10 @@ public void apply(Track track) {
track.setSelectedAltitudeManagedMcp(selectedAltitudeType == SelectedAltitudeSource.MCP);
track.setSelectedAltitude(selectedAltitudeType != SelectedAltitudeSource.UNKNOWN ? selectedAltitude : 0);
- if (isBaroAvailable()) {
- track.setBaroAltitude((int) Math.round(baroSetting));
- }
-
if (selectedHeadingAvailable) {
track.setSelectedHeading(selectedHeading);
}
- track.setNACp(NACp);
- track.setNICb(NICbaro.ordinal());
track.setSil(SIL.ordinal());
track.setAutopilot(autopilot);
track.setVnav(autopilotVnav);
diff --git a/src/main/java/aero/t2s/modes/decoder/df/df17/data/SurfaceOperationalMode.java b/src/main/java/aero/t2s/modes/decoder/df/df17/data/SurfaceOperationalMode.java
index b775c51..8508fe0 100644
--- a/src/main/java/aero/t2s/modes/decoder/df/df17/data/SurfaceOperationalMode.java
+++ b/src/main/java/aero/t2s/modes/decoder/df/df17/data/SurfaceOperationalMode.java
@@ -27,7 +27,7 @@ public SurfaceOperationalMode(int data) {
acasRA = (data & 0b0010000000000000) != 0 ? AcasState.RA_ACTIVE : AcasState.RA_NOT_ACTIVE;
acasIdent = (data & 0b0001000000000000) != 0;
singleAntennaFlag = (data & 0b0000010000000000) != 0;
- systemDesignAssurance = SourceIntegrityLevel.from((data & 0b0000001100000000) >>> 7);
+ systemDesignAssurance = SourceIntegrityLevel.from((data & 0b0000001100000000) >>> 8);
int gpsAntennaOffset = (data & 0b0000000011111111);
gpsLateralOffset = (gpsAntennaOffset & 0b11100000) >>> 5;
diff --git a/src/main/java/aero/t2s/modes/examples/StdOutExample.java b/src/main/java/aero/t2s/modes/examples/StdOutExample.java
deleted file mode 100644
index 113034b..0000000
--- a/src/main/java/aero/t2s/modes/examples/StdOutExample.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package aero.t2s.modes.examples;
-
-import aero.t2s.modes.ModeS;
-
-public class StdOutExample {
- public static void main(String[] args) {
- ModeS modes = new ModeS(
- "127.0.0.1", // Host IP where the Dump1090 server is running
- 30002, // The port with raw output (default 30002)
- 51, // Decimal latitude
- 0 // Decimal longitude
- );
- modes.onTrackCreated(track -> System.out.println("CREATED " + track.toString()));
- modes.onTrackUpdated(track -> System.out.println("UPDATED " + track.toString()));
- modes.onTrackDeleted(track -> System.out.println("DELETED " + track.toString()));
-
- modes.start();
- }
-}
diff --git a/src/main/java/aero/t2s/modes/registers/Register.java b/src/main/java/aero/t2s/modes/registers/Register.java
new file mode 100644
index 0000000..0007fb5
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register.java
@@ -0,0 +1,15 @@
+package aero.t2s.modes.registers;
+
+abstract public class Register {
+ private boolean valid = false;
+
+ public void validate() {
+ valid = true;
+ }
+
+ public boolean isValid() {
+ return valid;
+ }
+
+ public abstract String toString();
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register05.java b/src/main/java/aero/t2s/modes/registers/Register05.java
new file mode 100644
index 0000000..b9ebaff
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register05.java
@@ -0,0 +1,103 @@
+package aero.t2s.modes.registers;
+
+import aero.t2s.modes.constants.AltitudeSource;
+import aero.t2s.modes.constants.HorizontalProtectionLimit;
+import aero.t2s.modes.constants.SurveillanceStatus;
+import aero.t2s.modes.constants.Version;
+
+import java.time.Instant;
+
+public abstract class Register05 extends Register {
+ private SurveillanceStatus surveillanceStatus = SurveillanceStatus.NO_CONDITION;
+ private int altitude = 0;
+ private AltitudeSource altitudeSource = AltitudeSource.BARO;
+ private double lat = 0;
+ private double lon = 0;
+ private HorizontalProtectionLimit hpl = HorizontalProtectionLimit.RC_UNKNOWN;
+ private Instant updated = Instant.MIN;
+
+ public SurveillanceStatus getSurveillanceStatus() {
+ return surveillanceStatus;
+ }
+
+ public Register05 setSurveillanceStatus(SurveillanceStatus surveillanceStatus) {
+ this.surveillanceStatus = surveillanceStatus;
+ return this;
+ }
+
+ public int getAltitude() {
+ return altitude;
+ }
+
+ public Register05 setAltitude(int altitude) {
+ this.altitude = altitude;
+ return this;
+ }
+
+ public AltitudeSource getAltitudeSource() {
+ return altitudeSource;
+ }
+
+ public Register05 setAltitudeSource(AltitudeSource altitudeSource) {
+ this.altitudeSource = altitudeSource;
+ return this;
+ }
+
+ public double getLat() {
+ return lat;
+ }
+
+ public Register05 setLat(double lat) {
+ this.lat = lat;
+ return this;
+ }
+
+ public double getLon() {
+ return lon;
+ }
+
+ public Register05 setLon(double lon) {
+ this.lon = lon;
+ return this;
+ }
+
+ public HorizontalProtectionLimit getHpl() {
+ return hpl;
+ }
+
+ public Register05 setHpl(HorizontalProtectionLimit hpl) {
+ this.hpl = hpl;
+ return this;
+ }
+
+ public Instant getUpdated() {
+ return updated;
+ }
+
+ public abstract Version getVersion();
+
+
+ public void update(HorizontalProtectionLimit hpl, int altitude, AltitudeSource source, double lat, double lon, SurveillanceStatus surveillanceStatus) {
+ this.hpl = hpl;
+ this.altitude = altitude;
+ this.lat = lat;
+ this.lon = lon;
+ this.surveillanceStatus = surveillanceStatus;
+ this.updated = Instant.now();
+ this.validate();
+ }
+
+ @Override
+ public String toString() {
+ return "Register05{\n" +
+ "valid=" + isValid() +
+ ",\n surveillanceStatus=" + surveillanceStatus +
+ ",\n altitude=" + altitude +
+ ",\n altitudeSource=" + altitudeSource.name() +
+ ",\n lat=" + lat +
+ ",\n lon=" + lon +
+ ",\n hpl=" + hpl +
+ ",\n updated=" + updated +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register05V0.java b/src/main/java/aero/t2s/modes/registers/Register05V0.java
new file mode 100644
index 0000000..0f63bd2
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register05V0.java
@@ -0,0 +1,36 @@
+package aero.t2s.modes.registers;
+
+import aero.t2s.modes.constants.AltitudeSource;
+import aero.t2s.modes.constants.HorizontalProtectionLimit;
+import aero.t2s.modes.constants.SurveillanceStatus;
+import aero.t2s.modes.constants.Version;
+
+public class Register05V0 extends Register05 {
+ private boolean singleAntennaFlag = false;
+
+ public boolean isSingleAntennaFlag() {
+ return singleAntennaFlag;
+ }
+
+ public Register05V0 setSingleAntennaFlag(boolean singleAntennaFlag) {
+ this.singleAntennaFlag = singleAntennaFlag;
+ return this;
+ }
+
+ @Override
+ public Version getVersion() {
+ return Version.VERSION0;
+ }
+
+ public void update(HorizontalProtectionLimit hpl, int altitude, AltitudeSource altitudeSource, double lat, double lon, SurveillanceStatus surveillanceStatus, boolean singleAntennaFlag) {
+ super.update(hpl, altitude, altitudeSource, lat, lon, surveillanceStatus);
+ this.singleAntennaFlag = singleAntennaFlag;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "\n" + "Register05V0{" +
+ "\nsingleAntennaFlag=" + singleAntennaFlag +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register05V2.java b/src/main/java/aero/t2s/modes/registers/Register05V2.java
new file mode 100644
index 0000000..60bcfde
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register05V2.java
@@ -0,0 +1,23 @@
+package aero.t2s.modes.registers;
+
+import aero.t2s.modes.constants.AltitudeSource;
+import aero.t2s.modes.constants.HorizontalProtectionLimit;
+import aero.t2s.modes.constants.SurveillanceStatus;
+import aero.t2s.modes.constants.Version;
+
+public class Register05V2 extends Register05 {
+ @Override
+ public Version getVersion() {
+ return Version.VERSION2;
+ }
+
+ @Override
+ public void update(HorizontalProtectionLimit hpl, int altitude, AltitudeSource altitudeSource, double lat, double lon, SurveillanceStatus surveillanceStatus) {
+ super.update(hpl, altitude, altitudeSource, lat, lon, surveillanceStatus);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "\n" + "Register05V2 {}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register06.java b/src/main/java/aero/t2s/modes/registers/Register06.java
new file mode 100644
index 0000000..2823235
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register06.java
@@ -0,0 +1,19 @@
+package aero.t2s.modes.registers;
+
+public class Register06 extends Register {
+ private double groundSpeed;
+ private double groundTrack;
+ private double lat;
+ private double lon;
+
+ @Override
+ public String toString() {
+ return "Register06{\n" +
+ "valid=" + isValid() +
+ ",\n groundSpeed=" + groundSpeed +
+ ",\n groundTrack=" + groundTrack +
+ ",\n lat=" + lat +
+ ",\n lon=" + lon +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register07.java b/src/main/java/aero/t2s/modes/registers/Register07.java
new file mode 100644
index 0000000..e378d7a
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register07.java
@@ -0,0 +1,42 @@
+package aero.t2s.modes.registers;
+
+import aero.t2s.modes.constants.AltitudeSource;
+import aero.t2s.modes.constants.TransmissionRate;
+
+public class Register07 extends Register {
+ private TransmissionRate transmissionRate = TransmissionRate.UNKNOWN;
+ private AltitudeSource altitudeType = AltitudeSource.BARO;
+
+ public TransmissionRate getTransmissionRate() {
+ return transmissionRate;
+ }
+
+ public Register07 setTransmissionRate(TransmissionRate transmissionRate) {
+ this.transmissionRate = transmissionRate;
+ return this;
+ }
+
+ public AltitudeSource getAltitudeType() {
+ return altitudeType;
+ }
+
+ public Register07 setAltitudeType(AltitudeSource altitudeType) {
+ this.altitudeType = altitudeType;
+ return this;
+ }
+
+ public void update(TransmissionRate trs, AltitudeSource altitudeType) {
+ setTransmissionRate(trs);
+ setAltitudeType(altitudeType);
+ validate();
+ }
+
+ @Override
+ public String toString() {
+ return "Register07{\n" +
+ "valid=" + isValid() +
+ ",\ntransmissionRate=" + transmissionRate +
+ ",\n altitudeType=" + altitudeType +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register08.java b/src/main/java/aero/t2s/modes/registers/Register08.java
new file mode 100644
index 0000000..8549868
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register08.java
@@ -0,0 +1,41 @@
+package aero.t2s.modes.registers;
+
+import aero.t2s.modes.constants.AircraftCategory;
+
+public class Register08 extends Register {
+ private String acid;
+ private AircraftCategory aircraftCategory = AircraftCategory.UNKNOWN;
+
+ public String getAcid() {
+ return acid;
+ }
+
+ public Register08 setAcid(String acid) {
+ this.acid = acid;
+ return this;
+ }
+
+ public AircraftCategory getAircraftCategory() {
+ return aircraftCategory;
+ }
+
+ public Register08 setAircraftCategory(AircraftCategory aircraftCategory) {
+ this.aircraftCategory = aircraftCategory;
+ return this;
+ }
+
+ public void update(String acid, AircraftCategory aircraftCategory) {
+ setAcid(acid);
+ setAircraftCategory(aircraftCategory);
+ validate();
+ }
+
+ @Override
+ public String toString() {
+ return "Register08{\n" +
+ "valid=" + isValid() +
+ "\nacid='" + acid + '\'' +
+ ",\n aircraftCategory=" + aircraftCategory +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register09.java b/src/main/java/aero/t2s/modes/registers/Register09.java
new file mode 100644
index 0000000..6bf83d6
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register09.java
@@ -0,0 +1,142 @@
+package aero.t2s.modes.registers;
+
+import aero.t2s.modes.constants.Angle;
+import aero.t2s.modes.constants.NavigationAccuracyCategoryVelocity;
+import aero.t2s.modes.constants.RocdSource;
+import aero.t2s.modes.constants.Speed;
+
+public class Register09 extends Register {
+ private NavigationAccuracyCategoryVelocity NACv;
+
+ /**
+ * An intent change event shall be triggered 4 seconds after the detection of new information being inserted in registers 4016 to 4216.
+ * The code shall remain set for 18 +/-1 second following an intent change.
+ */
+ private boolean intentChangeFlag;
+ private int heading = 0;
+ private Angle headingSource = Angle.UNAVAILABLE;
+ private int airspeed = 0;
+ private Speed airspeedSource = Speed.IAS;
+ private int vx = 0;
+ private int vy = 0;
+ private int gnssDifferenceFromBaro = 0;
+ private int verticalRate;
+ private RocdSource verticalRateSource = RocdSource.GNSS;
+
+ public NavigationAccuracyCategoryVelocity getNACv() {
+ return NACv;
+ }
+
+ public Register09 setNACv(NavigationAccuracyCategoryVelocity NACv) {
+ this.NACv = NACv;
+ return this;
+ }
+
+ public boolean isIntentChangeFlag() {
+ return intentChangeFlag;
+ }
+
+ public Register09 setIntentChangeFlag(boolean intentChangeFlag) {
+ this.intentChangeFlag = intentChangeFlag;
+ return this;
+ }
+
+ public int getHeading() {
+ return heading;
+ }
+
+ public Register09 setHeading(int heading) {
+ this.heading = heading;
+ return this;
+ }
+
+ public Angle getHeadingSource() {
+ return headingSource;
+ }
+
+ public Register09 setHeadingSource(Angle headingSource) {
+ this.headingSource = headingSource;
+ return this;
+ }
+
+ public int getAirspeed() {
+ return airspeed;
+ }
+
+ public Register09 setAirspeed(int airspeed) {
+ this.airspeed = airspeed;
+ return this;
+ }
+
+ public Speed getAirspeedSource() {
+ return airspeedSource;
+ }
+
+ public Register09 setAirspeedSource(Speed airspeedSource) {
+ this.airspeedSource = airspeedSource;
+ return this;
+ }
+
+ public int getVx() {
+ return vx;
+ }
+
+ public Register09 setVx(int vx) {
+ this.vx = vx;
+ return this;
+ }
+
+ public int getVy() {
+ return vy;
+ }
+
+ public Register09 setVy(int vy) {
+ this.vy = vy;
+ return this;
+ }
+
+ public int getGnssDifferenceFromBaro() {
+ return gnssDifferenceFromBaro;
+ }
+
+ public Register09 setGnssDifferenceFromBaro(int gnssDifferenceFromBaro) {
+ this.gnssDifferenceFromBaro = gnssDifferenceFromBaro;
+ return this;
+ }
+
+ public int getVerticalRate() {
+ return verticalRate;
+ }
+
+ public Register09 setVerticalRate(int verticalRate) {
+ this.verticalRate = verticalRate;
+ return this;
+ }
+
+ public RocdSource getVerticalRateSource() {
+ return verticalRateSource;
+ }
+
+ public Register09 setVerticalRateSource(RocdSource verticalRateSource) {
+ this.verticalRateSource = verticalRateSource;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Register09{\n" +
+ "valid=" + isValid() +
+ ",\n NACv=" + NACv +
+ ",\n intentChangeFlag=" + intentChangeFlag +
+ ",\n heading=" + heading +
+ ",\n headingSource=" + headingSource +
+ ",\n airspeed=" + airspeed +
+ ",\n airspeedSource=" + airspeedSource +
+ ",\n vx=" + vx +
+ ",\n vy=" + vy +
+ ",\n gnssDifferenceFromBaro=" + gnssDifferenceFromBaro +
+ ",\n verticalRate=" + verticalRate +
+ ",\n verticalRateSource=" + verticalRateSource +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register09V0.java b/src/main/java/aero/t2s/modes/registers/Register09V0.java
new file mode 100644
index 0000000..d11552f
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register09V0.java
@@ -0,0 +1,26 @@
+package aero.t2s.modes.registers;
+
+public class Register09V0 extends Register09 {
+ /**
+ * The IFR capability flag shall be a 1-bit (bit 10) subfield in the subtypes 1, 2, 3 and 4 airborne velocity messages.
+ * IFR=1 shall signify that the transmitting aircraft has a capability for applications requiring ADS-B equipage class A1 or above.
+ * Otherwise, IFR shall be set to 0.
+ */
+ private boolean ifrCapability;
+
+ public boolean isIfrCapability() {
+ return ifrCapability;
+ }
+
+ public Register09V0 setIfrCapability(boolean ifrCapability) {
+ this.ifrCapability = ifrCapability;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "\nRegister09V0{\n" +
+ "ifrCapability=" + ifrCapability +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register17.java b/src/main/java/aero/t2s/modes/registers/Register17.java
new file mode 100644
index 0000000..fc74e4e
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register17.java
@@ -0,0 +1,26 @@
+package aero.t2s.modes.registers;
+
+import aero.t2s.modes.CapabilityReport;
+import aero.t2s.modes.decoder.df.bds.Bds17;
+
+public class Register17 extends Register {
+ private CapabilityReport capabilityReport = new CapabilityReport();
+
+ public CapabilityReport getCapabilityReport() {
+ return capabilityReport;
+ }
+
+ public Register17 update(Bds17 bds) {
+ capabilityReport.update(bds);
+ this.validate();
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Register17{\n" +
+ "valid=" + isValid() +
+ "\n" + capabilityReport.toString() +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register20.java b/src/main/java/aero/t2s/modes/registers/Register20.java
new file mode 100644
index 0000000..93b46bb
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register20.java
@@ -0,0 +1,23 @@
+package aero.t2s.modes.registers;
+
+public class Register20 extends Register {
+ private String acid;
+
+ public String getAcid() {
+ return acid;
+ }
+
+ public Register20 setAcid(String acid) {
+ this.acid = acid;
+ validate();
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Register20{\n" +
+ "valid=" + isValid() +
+ "\nacid='" + acid + '\'' +
+ "\n}";
+ }
+}
diff --git a/src/main/java/aero/t2s/modes/registers/Register21.java b/src/main/java/aero/t2s/modes/registers/Register21.java
new file mode 100644
index 0000000..00f1322
--- /dev/null
+++ b/src/main/java/aero/t2s/modes/registers/Register21.java
@@ -0,0 +1,29 @@
+package aero.t2s.modes.registers;
+
+public class Register21 extends Register {
+ private String aircraft;
+ private String airline;
+
+ public String getAircraft() {
+ return aircraft;
+ }
+
+ public String getAirline() {
+ return airline;
+ }
+
+ public void update(String aircraft, String airline) {
+ this.aircraft = airline;
+ this.airline = airline;
+ validate();
+ }
+
+ @Override
+ public String toString() {
+ return "Register21{\n" +
+ "valid=" + isValid() +
+ ",\n aircraft='" + aircraft + '\'' +
+ ",\n airline='" + airline + '\'' +
+ "\n}";
+ }
+}
diff --git a/src/test/java/aero/t2s/modes/decoder/df/DF0Test.java b/src/test/java/aero/t2s/modes/decoder/df/DF0Test.java
index 3bfbba2..c27b083 100644
--- a/src/test/java/aero/t2s/modes/decoder/df/DF0Test.java
+++ b/src/test/java/aero/t2s/modes/decoder/df/DF0Test.java
@@ -43,7 +43,7 @@ void it_decodes_sensitivity_level()
void it_decodes_reply_information()
{
df0 = new DF0(BinaryHelper.stringToByteArray("02E194979F2C4B")).decode();
- assertEquals(AcasReplyInformation.RESERVED3, df0.getReplyInformation());
+ assertEquals(AcasReplyInformation.ACAS_RA_VERTICAL_ONLY, df0.getReplyInformation());
}
@Test
@@ -51,7 +51,7 @@ void it_decodes_altitude()
{
df0 = new DF0(BinaryHelper.stringToByteArray("02E1951DE7596A")).decode();
- assertEquals(33325, df0.getAltitude().getAltitude(), 0.1);
+ assertEquals(32925, df0.getAltitude().getAltitude(), 0.1);
assertEquals(25, df0.getAltitude().getStep());
assertFalse(df0.getAltitude().isMetric());
}
diff --git a/src/test/java/aero/t2s/modes/decoder/df/DF24Test.java b/src/test/java/aero/t2s/modes/decoder/df/DF24Test.java
new file mode 100644
index 0000000..897a474
--- /dev/null
+++ b/src/test/java/aero/t2s/modes/decoder/df/DF24Test.java
@@ -0,0 +1,18 @@
+package aero.t2s.modes.decoder.df;
+
+import aero.t2s.modes.BinaryHelper;
+import aero.t2s.modes.decoder.UnknownDownlinkFormatException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class DF24Test {
+ @Test
+ public void test_df24_elm() throws UnknownDownlinkFormatException {
+ DF24 df = new DF24(BinaryHelper.stringToByteArray("C33D2901090141AE21C600180121"));
+ df.decode();
+
+ assertEquals("76CEFA", df.getIcao());
+ assertEquals(3, df.getSequenceNo());
+ }
+}
diff --git a/src/test/java/aero/t2s/modes/decoder/df/DfRealMessageTest.java b/src/test/java/aero/t2s/modes/decoder/df/DfRealMessageTest.java
new file mode 100644
index 0000000..039f009
--- /dev/null
+++ b/src/test/java/aero/t2s/modes/decoder/df/DfRealMessageTest.java
@@ -0,0 +1,438 @@
+package aero.t2s.modes.decoder.df;
+
+import aero.t2s.modes.BinaryHelper;
+import aero.t2s.modes.constants.*;
+import aero.t2s.modes.database.ModeSDatabase;
+import aero.t2s.modes.decoder.Decoder;
+import aero.t2s.modes.decoder.UnknownDownlinkFormatException;
+import aero.t2s.modes.decoder.df.bds.*;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class DfRealMessageTest {
+ @Test
+ public void test_bds60() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A0001838E71A21357F640110A153");
+
+ assertInstanceOf(DF20.class, df);
+
+ assertTrue(((DF20)df).isValid());
+ assertFalse(((DF20)df).isMultipleMatches());
+ assertInstanceOf(Bds60.class, ((DF20)df).getBds());
+ assertEquals(38000, ((DF20) df).getAltitude().getAltitude());
+
+ Bds60 bds = (Bds60) ((DF20)df).getBds();
+ assertTrue(bds.isStatusMagneticHeading());
+ assertEquals(289.863, bds.getMagneticHeading(), 0.001);
+ assertTrue(bds.isStatusIas());
+ assertEquals(272, bds.getIas());
+ assertTrue(bds.isStatusMach());
+ assertEquals(0.852, bds.getMach());
+ assertTrue(bds.isStatusBaroRocd());
+ assertEquals(-640, bds.getBaroRocd());
+ assertTrue(bds.isStatusIrsRocd());
+ assertEquals(32, bds.getIrsRocd());
+ }
+
+ @Test
+ public void test_df_20_bds_40_a48e35() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A00006A1E3A71D30AA014672C8DF");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("A48E35", df.getIcao());
+ assertEquals(51000, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds40.class, df20.getBds());
+
+ Bds40 bds = (Bds40) df20.getBds();
+ assertTrue(bds.isStatusTargetSource());
+ assertEquals(SelectedAltitudeSource.MCP, bds.getSelectedAltitudeSource());
+ assertTrue(bds.isStatusMcp());
+ assertEquals(51008, bds.getSelectedAltitude());
+ assertTrue(bds.isStatusFms());
+ assertEquals(51008, bds.getFmsAltitude());
+ assertTrue(bds.isStatusBaro());
+ assertEquals(1013.3, bds.getBaro(), 0.1);
+ assertTrue(bds.isStatusMcpMode());
+ assertFalse(bds.isAutopilotApproach());
+ assertFalse(bds.isAutopilotVnav());
+ assertTrue(bds.isAutopilotAltitudeHold());
+ }
+
+
+ @Test
+ public void test_df20_bds60_800736() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A0001A1FA439F534BF07FFDE1ECC");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("800736", df.getIcao());
+ assertEquals(40975, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds60.class, df20.getBds());
+
+ Bds60 bds = (Bds60) df20.getBds();
+ assertTrue(bds.isStatusIrsRocd());
+ assertEquals(-32, bds.getIrsRocd());
+ assertTrue(bds.isStatusBaroRocd());
+ assertEquals(-1024.0, bds.getBaroRocd());
+ assertTrue(bds.isStatusIas());
+ assertEquals(250, bds.getIas());
+ assertTrue(bds.isStatusMach());
+ assertEquals(0.84, bds.getMach(), 0.1);
+ assertTrue(bds.isStatusMagneticHeading());
+ assertEquals(101.7, bds.getMagneticHeading(), 0.1);
+ }
+
+ @Test
+ public void test_df21_bds60_4CA708() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A8800337D439E730BFE600C28696");
+
+ assertInstanceOf(DF21.class, df);
+ DF21 df21 = (DF21) df;
+ assertEquals("4CA708", df.getIcao());
+ assertEquals(2547, df21.getModeA());
+
+ assertTrue(df21.isValid());
+ assertInstanceOf(Bds60.class, df21.getBds());
+
+ Bds60 bds = (Bds60) df21.getBds();
+ assertTrue(bds.isStatusIrsRocd());
+ assertEquals(0, bds.getIrsRocd(), 0.1);
+ assertTrue(bds.isStatusBaroRocd());
+ assertEquals(-128, bds.getBaroRocd(), 0.1);
+ assertTrue(bds.isStatusIas());
+ assertEquals(243, bds.getIas());
+ assertTrue(bds.isStatusMach());
+ assertEquals(0.77, bds.getMach(), 0.1);
+ assertTrue(bds.isStatusMagneticHeading());
+ assertEquals(236.7, bds.getMagneticHeading(), 0.1);
+ }
+
+ @Test
+ public void test_df21_bds60_00000() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A800198EEABA2B30F0041257522A");
+
+ assertInstanceOf(DF21.class, df);
+ DF21 df21 = (DF21) df;
+ assertEquals("000000", df.getIcao()); // Military / corrupt transponder
+ assertEquals(5652, df21.getModeA());
+
+ assertTrue(df21.isValid());
+ assertInstanceOf(Bds60.class, df21.getBds());
+
+ Bds60 bds = (Bds60) df21.getBds();
+ assertTrue(bds.isStatusIrsRocd());
+ assertEquals(576, bds.getIrsRocd(), 0.1);
+ assertTrue(bds.isStatusBaroRocd());
+ assertEquals(0, bds.getBaroRocd(), 0.1);
+ assertTrue(bds.isStatusIas());
+ assertEquals(277, bds.getIas());
+ assertTrue(bds.isStatusMach());
+ assertEquals(0.77, bds.getMach(), 0.1);
+ assertTrue(bds.isStatusMagneticHeading());
+ assertEquals(300.0, bds.getMagneticHeading(), 0.1);
+ }
+
+ @Test
+ public void test_df21_bds60_406ECD() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A8000C98A549F33461E40552947D");
+
+ assertInstanceOf(DF21.class, df);
+ DF21 df21 = (DF21) df;
+ assertEquals("406ECD", df.getIcao()); // Military / corrupt transponder
+ assertEquals(5221, df21.getModeA());
+
+ assertTrue(df21.isValid());
+ assertInstanceOf(Bds60.class, df21.getBds());
+
+ Bds60 bds = (Bds60) df21.getBds();
+ assertTrue(bds.isStatusIrsRocd());
+ assertEquals(160, bds.getIrsRocd(), 0.1);
+ assertTrue(bds.isStatusBaroRocd());
+ assertEquals(1920, bds.getBaroRocd(), 0.1);
+ assertTrue(bds.isStatusIas());
+ assertEquals(249, bds.getIas());
+ assertTrue(bds.isStatusMach());
+ assertEquals(0.77, bds.getMach(), 0.1);
+ assertTrue(bds.isStatusMagneticHeading());
+ assertEquals(104.7, bds.getMagneticHeading(), 0.1);
+ }
+
+
+ @Test
+ public void test_df21_bds50_44CD73() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A0000333E17987192250004423EC");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("44CD73", df.getIcao()); // Military / corrupt transponder
+ assertEquals(4275, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds50.class, df20.getBds());
+
+ Bds50 bds = (Bds50) df20.getBds();
+ assertTrue(bds.isStatusGs());
+ assertEquals(200, bds.getGs(), 0.1);
+ assertFalse(bds.isStatusTas());
+ assertEquals(0, bds.getTas(), 0.1);
+ assertTrue(bds.isStatusRollAngle());
+ assertEquals(-43, bds.getRollAngle(), 0.1);
+ assertTrue(bds.isStatusTrueAngleRate());
+ assertEquals(2.3, bds.getTrackAngleRate(), 0.1);
+ assertTrue(bds.isStatusTrackAngle());
+ assertEquals(214.2, bds.getTrueTrack(), 0.1);
+ }
+
+ @Test
+ public void test_df21_bds40_407776() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A000169EB2CC0030A80106C25083");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("407776", df.getIcao()); // Military / corrupt transponder
+ assertEquals(35350, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds40.class, df20.getBds());
+
+ Bds40 bds = (Bds40) df20.getBds();
+ assertTrue(bds.isStatusMcp());
+ assertEquals(26000, bds.getSelectedAltitude(), 0.1);
+ assertTrue(bds.isStatusFms());
+ assertEquals(0, bds.getFmsAltitude(), 0.1);
+ assertTrue(bds.isStatusBaro());
+ assertEquals(1013.2, bds.getBaro(), 0.1);
+ assertTrue(bds.isStatusTargetSource());
+ assertEquals(SelectedAltitudeSource.MCP, bds.getSelectedAltitudeSource());
+ assertTrue(bds.isStatusMcpMode());
+ assertFalse(bds.isAutopilotVnav());
+ assertFalse(bds.isAutopilotAltitudeHold());
+ assertFalse(bds.isAutopilotApproach());
+ }
+
+
+ @Test
+ public void test_df21_bds10_407776() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A000042210000600B0000089B69E");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("44CC63", df.getIcao()); // Military / corrupt transponder
+ assertEquals(2000, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds10.class, df20.getBds());
+
+ Bds10 bds = (Bds10) df20.getBds();
+ }
+
+
+ @Test
+ public void test_df21_bds40_4CA6F8() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A000069E8BBC2F30A40000528AB8");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("4CA6F8", df.getIcao()); // Military / corrupt transponder
+ assertEquals(9750, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds40.class, df20.getBds());
+
+ Bds40 bds = (Bds40) df20.getBds();
+ assertFalse(bds.isStatusTargetSource());
+ assertNull(bds.getSelectedAltitudeSource());
+ assertTrue(bds.isStatusMcp());
+ assertEquals(6000, bds.getSelectedAltitude());
+ assertTrue(bds.isStatusFms());
+ assertEquals(3008, bds.getFmsAltitude());
+ assertTrue(bds.isStatusBaro());
+ assertEquals(1013.0, bds.getBaro(), 0.1);
+ assertFalse(bds.isStatusMcpMode());
+ assertFalse(bds.isAutopilotApproach());
+ assertFalse(bds.isAutopilotVnav());
+ assertFalse(bds.isAutopilotAltitudeHold());
+ }
+
+ @Test
+ public void test_df21_bds50_485209() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A000093BFFF16B276004997B748F");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("485209", df.getIcao()); // Military / corrupt transponder
+ assertEquals(14075, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds50.class, df20.getBds());
+
+ Bds50 bds = (Bds50) df20.getBds();
+ assertTrue(bds.isStatusGs());
+ assertEquals(314, bds.getGs(), 0.1);
+ assertTrue(bds.isStatusTas());
+ assertEquals(306, bds.getTas(), 0.1);
+ assertTrue(bds.isStatusRollAngle());
+ assertEquals(-0.1, bds.getRollAngle(), 0.1);
+ assertTrue(bds.isStatusTrueAngleRate());
+ assertEquals(0, bds.getTrackAngleRate(), 0.1);
+ assertTrue(bds.isStatusTrackAngle());
+ assertEquals(31.8, bds.getTrueTrack(), 0.1);
+ }
+
+ @Test
+ public void test_df21_bds50_484FDF() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A800080080502D2A600CA9DF5877");
+
+ assertInstanceOf(DF21.class, df);
+ DF21 df21 = (DF21) df;
+ assertEquals("484FDF", df.getIcao()); // Military / corrupt transponder
+ assertEquals(1000, df21.getModeA());
+
+ assertTrue(df21.isValid());
+ assertInstanceOf(Bds50.class, df21.getBds());
+
+ Bds50 bds = (Bds50) df21.getBds();
+ assertTrue(bds.isStatusGs());
+ assertEquals(338, bds.getGs(), 0.1);
+ assertTrue(bds.isStatusTas());
+ assertEquals(338, bds.getTas(), 0.1);
+ assertTrue(bds.isStatusRollAngle());
+ assertEquals(0.35, bds.getRollAngle(), 0.1);
+ assertTrue(bds.isStatusTrueAngleRate());
+ assertEquals(0, bds.getTrackAngleRate(), 0.1);
+ assertTrue(bds.isStatusTrackAngle());
+ assertEquals(3.8, bds.getTrueTrack(), 0.1);
+ }
+
+ @Test
+ public void test_df20_bds50_48418A() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A0000E978F1FBD316114BDA0FFBF");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("48418A", df.getIcao()); // Military / corrupt transponder
+ assertEquals(22375, df20.getAltitude().getAltitude());
+
+ assertTrue(df20.isValid());
+ assertInstanceOf(Bds50.class, df20.getBds());
+
+ Bds50 bds = (Bds50) df20.getBds();
+ assertTrue(bds.isStatusGs());
+ assertEquals(394, bds.getGs(), 0.1);
+ assertTrue(bds.isStatusTas());
+ assertEquals(378, bds.getTas(), 0.1);
+ assertTrue(bds.isStatusRollAngle());
+ assertEquals(21, bds.getRollAngle(), 0.1);
+ assertTrue(bds.isStatusTrueAngleRate());
+ assertEquals(1, bds.getTrackAngleRate(), 0.1);
+ assertTrue(bds.isStatusTrackAngle());
+ assertEquals(354, bds.getTrueTrack(), 0.1);
+ }
+
+ @Test
+ public void test_df16_02A198() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("80C18819584195384EF8505941FD");
+
+ assertInstanceOf(DF16.class, df);
+ DF16 df16 = (DF16) df;
+ assertEquals("02A198", df.getIcao()); // Military / corrupt transponder
+ assertEquals(12025, df16.getAltitude().getAltitude());
+
+ assertEquals(VerticalStatus.AIRBORNE, df16.getVerticalStatus());
+ assertEquals(AcasSensitivity.LEVEL6, df16.getSensitivity());
+ assertEquals(AcasReplyInformation.ACAS_RA_VERTICAL_ONLY, df16.getReplyInformation());
+ assertFalse(df16.getResolutionAdvisory().isActive());
+ assertFalse(df16.getResolutionAdvisory().isRequiresCorrectionUpwards());
+ assertFalse(df16.getResolutionAdvisory().isRequiresCorrectionDownwards());
+ assertFalse(df16.getResolutionAdvisory().isRequiresPositiveClimb());
+ assertFalse(df16.getResolutionAdvisory().isRequiresPositiveDescend());
+ assertFalse(df16.getResolutionAdvisory().isRequiresCrossing());
+ assertFalse(df16.getResolutionAdvisory().isSenseReversal());
+
+ assertFalse(df16.isMultipleThreats());
+ assertFalse(df16.isRANotPassAbove());
+ assertFalse(df16.isRANotPassBelow());
+ assertFalse(df16.isRANotTurnLeft());
+ assertFalse(df16.isRANotTurnRight());
+ }
+
+ @Test
+ public void test_df21_bds50_02A185() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A8001AA0F4596112BDFC569A3AEC");
+
+ assertInstanceOf(DF21.class, df);
+ DF21 df21 = (DF21) df;
+ assertEquals("02A185", df.getIcao()); // Military / corrupt transponder
+ assertEquals(7110, df21.getModeA());
+
+ assertFalse(df21.isMultipleMatches());
+ assertTrue(df21.isValid());
+ assertEquals(Bds50.class, df21.getBds().getClass());
+
+ Bds50 bds = (Bds50) df21.getBds();
+ assertTrue(bds.isStatusGs());
+ assertEquals(148, bds.getGs(), 0.1);
+ assertTrue(bds.isStatusTas());
+ assertEquals(172, bds.getTas(), 0.1);
+ assertTrue(bds.isStatusRollAngle());
+ assertEquals(-16.5, bds.getRollAngle(), 0.1);
+ assertTrue(bds.isStatusTrueAngleRate());
+ assertEquals(-2.0, bds.getTrackAngleRate(), 0.1);
+ assertTrue(bds.isStatusTrackAngle());
+ assertEquals(210.9, bds.getTrueTrack(), 0.1);
+ }
+
+ @Test
+ public void test_df20_bds17_3D2C7C() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("A0280314020100000000004E25E8");
+
+ assertInstanceOf(DF20.class, df);
+ DF20 df20 = (DF20) df;
+ assertEquals("3D2C7C", df.getIcao()); // Military / corrupt transponder
+ assertEquals(3900, df20.getAltitude().getAltitude());
+
+ assertFalse(df20.isMultipleMatches());
+ assertTrue(df20.isValid());
+ assertEquals(Bds17.class, df20.getBds().getClass());
+
+ Bds17 bds = (Bds17) df20.getBds();
+ assertFalse(bds.isBds0A());
+ assertFalse(bds.isBds05());
+ assertFalse(bds.isBds06());
+ assertFalse(bds.isBds07());
+ assertFalse(bds.isBds08());
+ assertFalse(bds.isBds09());
+ assertFalse(bds.isBds0A());
+ assertTrue(bds.isBds20());
+ assertFalse(bds.isBds21());
+ assertFalse(bds.isBds40());
+ assertFalse(bds.isBds41());
+ assertFalse(bds.isBds42());
+ assertFalse(bds.isBds43());
+ assertFalse(bds.isBds44());
+ assertFalse(bds.isBds45());
+ assertFalse(bds.isBds48());
+ assertTrue(bds.isBds50());
+ assertFalse(bds.isBds51());
+ assertFalse(bds.isBds52());
+ assertFalse(bds.isBds53());
+ assertFalse(bds.isBds54());
+ assertFalse(bds.isBds55());
+ assertFalse(bds.isBds56());
+ assertFalse(bds.isBds5F());
+ assertFalse(bds.isBds60());
+ }
+
+ private DownlinkFormat testMessage(String message) throws UnknownDownlinkFormatException {
+ Decoder decoder = new Decoder(new HashMap<>(), 50, 2, ModeSDatabase.createDatabase());
+
+ return decoder.decode(BinaryHelper.stringToByteArray(message));
+ }
+}
diff --git a/src/test/java/aero/t2s/modes/decoder/df/DfTEst.java b/src/test/java/aero/t2s/modes/decoder/df/DfTEst.java
deleted file mode 100644
index bc4f862..0000000
--- a/src/test/java/aero/t2s/modes/decoder/df/DfTEst.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package aero.t2s.modes.decoder.df;
-
-import aero.t2s.modes.BinaryHelper;
-import aero.t2s.modes.database.ModeSDatabase;
-import aero.t2s.modes.decoder.Decoder;
-import aero.t2s.modes.decoder.UnknownDownlinkFormatException;
-import org.junit.jupiter.api.Test;
-
-import java.util.HashMap;
-
-public class DfTEst {
- @Test
- public void test() throws UnknownDownlinkFormatException {
- String message = "A0001338000557F0A8000098FCDB";
-
- Decoder decoder = new Decoder(new HashMap<>(), 50, 2, ModeSDatabase.createDatabase());
-
- DownlinkFormat df = decoder.decode(BinaryHelper.stringToByteArray(message));
-
- System.out.println(df.toString());
- }
-}
diff --git a/src/test/java/aero/t2s/modes/decoder/df/bds/Bds50Test.java b/src/test/java/aero/t2s/modes/decoder/df/bds/Bds50Test.java
index 5948577..a7d10fd 100644
--- a/src/test/java/aero/t2s/modes/decoder/df/bds/Bds50Test.java
+++ b/src/test/java/aero/t2s/modes/decoder/df/bds/Bds50Test.java
@@ -29,46 +29,6 @@ public void it_does_nothing_with_all_zeros()
assertEquals(0, bds.getTas());
}
- @Test
- public void it_decodes_bds50_roll_angle()
- {
- bds = new Bds50(new short[] {
- 0x0, 0x0, 0x0, 0x0,
- 0b10100000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- });
-
- assertTrue(bds.isValid());
- assertEquals(45, bds.getRollAngle());
- assertEquals(0, bds.getTrueTrack());
- assertEquals(0, bds.getGs());
- assertEquals(0, bds.getTrackAngleRate());
- assertEquals(0, bds.getTas());
-
- bds = new Bds50(new short[] {
- 0x0, 0x0, 0x0, 0x0,
- 0b11100000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- 0b00000000,
- });
-
- assertTrue(bds.isValid());
- assertEquals(-45, bds.getRollAngle());
- assertEquals(0, bds.getTrueTrack());
- assertEquals(0, bds.getGs());
- assertEquals(0, bds.getTrackAngleRate());
- assertEquals(0, bds.getTas());
- }
-
@Test
public void it_is_not_bds50_when_roll_angle_is_not_available_and_bits_are_set()
{
diff --git a/src/test/java/aero/t2s/modes/decoder/df/df17/AirbornePositionTest.java b/src/test/java/aero/t2s/modes/decoder/df/df17/AirbornePositionTest.java
new file mode 100644
index 0000000..64fe9ec
--- /dev/null
+++ b/src/test/java/aero/t2s/modes/decoder/df/df17/AirbornePositionTest.java
@@ -0,0 +1,66 @@
+package aero.t2s.modes.decoder.df.df17;
+
+import aero.t2s.modes.BinaryHelper;
+import aero.t2s.modes.database.ModeSDatabase;
+import aero.t2s.modes.decoder.Decoder;
+import aero.t2s.modes.decoder.UnknownDownlinkFormatException;
+import aero.t2s.modes.decoder.df.DF17;
+import aero.t2s.modes.decoder.df.DF18;
+import aero.t2s.modes.decoder.df.DownlinkFormat;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class AirbornePositionTest {
+ @Test
+ public void test_airborne_position_aircraft() throws UnknownDownlinkFormatException {
+ // This is an ODD frame with Airborne Position
+ DownlinkFormat dfA = testMessage("8d4076635883069b318845770698");
+
+ assertInstanceOf(DF17.class, dfA);
+ DF17 df17A = (DF17) dfA;
+ assertEquals("407663", df17A.getIcao());
+ ExtendedSquitter exSqA = df17A.getExtendedSquitter();
+ assertInstanceOf(AirbornePosition.class, exSqA);
+ AirbornePosition positionA = (AirbornePosition) exSqA;
+ // We should NOT have a valid position after only one frame
+ assertFalse(positionA.isPositionAvailable());
+
+ // This is an EVEN frame with Airborne Position... so we should be able to do a global calculation of position
+ DownlinkFormat dfB = testMessage("8d407663588303313d84f719b2de");
+
+ assertInstanceOf(DF17.class, dfB);
+ DF17 df17B = (DF17) dfB;
+ assertEquals("407663", df17B.getIcao());
+ ExtendedSquitter exSqB = df17B.getExtendedSquitter();
+ assertInstanceOf(AirbornePosition.class, exSqB);
+ AirbornePosition positionB = (AirbornePosition) exSqB;
+ // We should now have a valid position after receiving both even and odd frames
+ assertTrue(positionB.isPositionAvailable());
+ assertEquals(52.789, positionB.getLat(), 0.01);
+ assertEquals(-2.405, positionB.getLon(), 0.01);
+
+ // This is another EVEN frame with Airborne Position... so we should be able to do a local calculation of position
+ DownlinkFormat dfC = testMessage("8d407663588303312385138ddce6");
+
+ assertInstanceOf(DF17.class, dfC);
+ DF17 df17C = (DF17) dfC;
+ assertEquals("407663", df17C.getIcao());
+ ExtendedSquitter exSqC = df17C.getExtendedSquitter();
+ assertInstanceOf(AirbornePosition.class, exSqC);
+ AirbornePosition positionC = (AirbornePosition) exSqC;
+ // We should still have a valid position after receiving another even frame
+ assertTrue(positionC.isPositionAvailable());
+ assertEquals(52.788, positionC.getLat(), 0.01);
+ assertEquals(-2.401, positionC.getLon(), 0.01);
+ }
+
+ private DownlinkFormat testMessage(String message) throws UnknownDownlinkFormatException {
+ Decoder decoder = new Decoder(new HashMap<>(), 53, -2, ModeSDatabase.createDatabase());
+
+ return decoder.decode(BinaryHelper.stringToByteArray(message));
+ }
+
+}
diff --git a/src/test/java/aero/t2s/modes/decoder/df/df17/AircraftOperationalStatusMessageTest.java b/src/test/java/aero/t2s/modes/decoder/df/df17/AircraftOperationalStatusMessageTest.java
new file mode 100644
index 0000000..b4fa927
--- /dev/null
+++ b/src/test/java/aero/t2s/modes/decoder/df/df17/AircraftOperationalStatusMessageTest.java
@@ -0,0 +1,43 @@
+package aero.t2s.modes.decoder.df.df17;
+
+import aero.t2s.modes.BinaryHelper;
+import aero.t2s.modes.constants.*;
+import aero.t2s.modes.decoder.df.df17.data.SurfaceOperationalMode;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class AircraftOperationalStatusMessageTest {
+ @Test
+ public void it_decodes_surface_version1_message() {
+ AircraftOperationalStatusMessage message = new AircraftOperationalStatusMessage(BinaryHelper.stringToByteArray("8F40622DF900260283493839FD1F")).decode();
+
+ Assertions.assertInstanceOf(AircraftOperationalStatusVersion2.class, message);
+ Assertions.assertInstanceOf(AircraftOperationalStatusVersion2Surface.class, message);
+
+ AircraftOperationalStatusVersion2Surface surface = (AircraftOperationalStatusVersion2Surface) message;
+ Assertions.assertEquals(Version.VERSION2, surface.getVersion());
+ Assertions.assertEquals(LengthWidthCode.CAT6, surface.getLengthWidthCode());
+
+ Assertions.assertFalse(surface.getSurfaceCapability().isCockpitDisplayOfTraffic());
+ Assertions.assertFalse(surface.getSurfaceCapability().isUatReceive());
+ Assertions.assertFalse(surface.getSurfaceCapability().isLowB2Power());
+ Assertions.assertFalse(surface.getSurfaceCapability().isPositionOffsetApplied());
+ Assertions.assertFalse(surface.getSurfaceCapability().isReceive1090ES());
+ Assertions.assertEquals(NavigationUncertaintyCategory.NUC1, surface.getSurfaceCapability().getNACv());
+ Assertions.assertEquals(0, surface.getSurfaceCapability().getNICsuppC());
+
+ Assertions.assertFalse(surface.getOperationalMode().isAcasIdent());
+ Assertions.assertTrue(surface.getOperationalMode().isGpsLateralOffsetAvailable());
+ Assertions.assertFalse(surface.getOperationalMode().isSingleAntennaFlag());
+ Assertions.assertTrue(surface.getOperationalMode().isGpsLongitudinalOffsetAvailable());
+ Assertions.assertFalse(surface.getOperationalMode().isGpsLongitudinalOffsetAppliedBySensor());
+ Assertions.assertEquals(AcasState.RA_NOT_ACTIVE, surface.getOperationalMode().getAcasRA());
+ Assertions.assertEquals(0, surface.getOperationalMode().getGpsLateralOffset());
+ Assertions.assertEquals(0, surface.getOperationalMode().getGpsLateralOffset());
+ Assertions.assertEquals(4, surface.getOperationalMode().getGpsLongitudinalOffset());
+ Assertions.assertEquals(SourceIntegrityLevel.LESS_THEN_ONE_PER_HUNDRED_THOUSAND, surface.getOperationalMode().getSystemDesignAssurance());
+
+ Assertions.assertEquals(NavigationIntegrityCategory.RC_75_M, surface.getNICp());
+ Assertions.assertEquals(Angle.TRUE_TRACK, surface.getHorizontalSource());
+ }
+}
diff --git a/src/test/java/aero/t2s/modes/decoder/df/df17/SurfacePositionTest.java b/src/test/java/aero/t2s/modes/decoder/df/df17/SurfacePositionTest.java
new file mode 100644
index 0000000..e7cbe8c
--- /dev/null
+++ b/src/test/java/aero/t2s/modes/decoder/df/df17/SurfacePositionTest.java
@@ -0,0 +1,90 @@
+package aero.t2s.modes.decoder.df.df17;
+
+import aero.t2s.modes.BinaryHelper;
+import aero.t2s.modes.database.ModeSDatabase;
+import aero.t2s.modes.decoder.Decoder;
+import aero.t2s.modes.decoder.UnknownDownlinkFormatException;
+import aero.t2s.modes.decoder.df.DF17;
+import aero.t2s.modes.decoder.df.DF18;
+import aero.t2s.modes.decoder.df.DownlinkFormat;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SurfacePositionTest {
+ @Test
+ public void test_surface_position_aircraft() throws UnknownDownlinkFormatException {
+ // This is an EVEN frame with Surface Position
+ DownlinkFormat dfA = testMessage("8c4ca6a938dd224b663c964f058e");
+
+ assertInstanceOf(DF17.class, dfA);
+ DF17 df17A = (DF17) dfA;
+ assertEquals("4CA6A9", df17A.getIcao());
+ ExtendedSquitter exSqA = df17A.getExtendedSquitter();
+ assertInstanceOf(SurfacePosition.class, exSqA);
+ SurfacePosition positionA = (SurfacePosition) exSqA;
+ assertEquals(13, positionA.getVelocityEncoded());
+ assertEquals(2.0, positionA.getVelocity());
+ assertTrue(positionA.isTrackAvailable());
+ assertEquals(82, positionA.getTrackEncoded());
+ assertEquals(230.625, positionA.getTrack(), 0.001);
+ // We should NOT have a valid position after only one frame
+ assertFalse(positionA.isPositionAvailable());
+
+ // This is an ODD frame with Surface Position
+ DownlinkFormat dfB = testMessage("8c4ca6a938cd27ec46497d947b4d");
+
+ assertInstanceOf(DF17.class, dfB);
+ DF17 df17B = (DF17) dfB;
+ assertEquals("4CA6A9", df17B.getIcao());
+ ExtendedSquitter exSqB = df17B.getExtendedSquitter();
+ assertInstanceOf(SurfacePosition.class, exSqB);
+ SurfacePosition positionB = (SurfacePosition) exSqB;
+ // We should now have a valid position after receiving both even and odd frames
+ assertTrue(positionB.isPositionAvailable());
+ assertEquals(53.3604, positionB.getLat(), 0.001);
+ assertEquals(-2.2671, positionB.getLon(), 0.001);
+ assertTrue(positionB.isVelocityAvailable());
+ assertEquals(1.75, positionB.getVelocity(), 0.1);
+ assertTrue(positionB.isTrackAvailable());
+ assertEquals(230.625, positionB.getTrack(), 0.1);
+
+ // This is another ODD frame with Surface Position... so we should be able to do a local calculation of position
+ DownlinkFormat dfC = testMessage("8c4ca6a9389d27ec46497d8ea39e");
+
+ assertInstanceOf(DF17.class, dfC);
+ DF17 df17C = (DF17) dfC;
+ assertEquals("4CA6A9", df17C.getIcao());
+ ExtendedSquitter exSqC = df17C.getExtendedSquitter();
+ assertInstanceOf(SurfacePosition.class, exSqC);
+ SurfacePosition positionC = (SurfacePosition) exSqC;
+ // We should still have a valid position after receiving another odd frame
+
+ assertTrue(positionC.isPositionAvailable());
+ assertEquals(53.360, positionC.getLat(), 0.001);
+ assertEquals(-2.267, positionC.getLon(), 0.001);
+ }
+
+ @Test
+ public void test_surface_position_vehicle() throws UnknownDownlinkFormatException {
+ DownlinkFormat df = testMessage("90425854281007f03e44e4ba62ea");
+
+ assertInstanceOf(DF18.class, df);
+ DF18 df18 = (DF18) df;
+ assertEquals("425854", df.getIcao());
+ ExtendedSquitter exSq = df18.getExtendedSquitter();
+ assertInstanceOf(SurfacePosition.class, exSq);
+ SurfacePosition position = (SurfacePosition) exSq;
+ assertEquals(1, position.getVelocityEncoded());
+ assertEquals(0.0, position.getVelocity());
+ assertFalse(position.isTrackAvailable());
+ }
+
+ private DownlinkFormat testMessage(String message) throws UnknownDownlinkFormatException {
+ Decoder decoder = new Decoder(new HashMap<>(), 53, -2, ModeSDatabase.createDatabase());
+
+ return decoder.decode(BinaryHelper.stringToByteArray(message));
+ }
+}