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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions sensors/aviation/sensorhub-driver-airnav-adsb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# AirNav ADS-B FlightStick
The AirNav ADS-B driver connects to a dump1090 SBS-1/BaseStation TCP stream and publishes aircraft identification, position, altitude, and velocity observations.

This driver has been tested with
- `dump1090-fa`
- `dump1090-mutability`


### Hardware
| Component | Source |
|---|---|
| AirNav ADS-B FlightStick (RTL-SDR dongle) | [airnavradar.com/store](https://www.airnavradar.com/store) |
| ADS-B 1090 MHz Antenna | [airnavradar.com/store](https://www.airnavradar.com/store) |
---


### Prereqs
**macOS**
Install RTL-SDR utilities and `dump1090`:
```brew
brew update
brew install librtlsdr
brew install dump1090-fa
```
Verify the installed
```brew
dump1090-fa --help
```

**Linux**
Install RTL-SDR utilities and `dump1090`:
```bash
sudo apt update
sudo apt install rtl-sdr dump1090-mutability
```
Add your user to the plugdev group:
```bash
sudo usermod -aG plugdev $USER
```

### Hardware Setup
1. Connect the antenna to the FlightStick SMA port
2. Plug the FlightStick into a USB port
3. Verify the device is detected
```bash
rtl_test -t
```

[//]: # (````### Start dump1090)

[//]: # (Start `dump1090` with the SBS-1 TCP output enabled on port `30003`.)

[//]: # ()
[//]: # (FlightAware dump109-fa)

[//]: # (```bash)

[//]: # (dump1090-fa --net-sbs-port 30003)

[//]: # (```)

[//]: # (Generic dump109)

[//]: # (```bash)

[//]: # (dump1090 --net-sbs-port 30003)

[//]: # (```)

[//]: # (The driver consumes the SBS-1 TCP stream format produced by `dump1090`.)

[//]: # ()
[//]: # (### Verify the SBS port is open:)

[//]: # (```bash)

[//]: # (nc localhost 30003)

[//]: # (```)

[//]: # (You should see comma-delimited SBS-1 Messages)

[//]: # (```)

[//]: # (MSG,3,1,1,A12345,1,2024/01/01,12:00:00.000,2024/01/01,12:00:00.000,,35000,,,33.1234,-97.5678,,,0,0,0,0)

[//]: # (```)

[//]: # (Aircraft messages are only outputted when aircraft are in reception range. Make sure you place the antenna outside, )

[//]: # (and have a good line of sight.````)

### OSH Driver Config
1. Add a TCP Comm Module
2. Update the following fields:
- host: `localhost`
- port: `30003`
3. Click `Apply Changes` to initialize the driver
4. Right-click the driver in the `Sensors` tab and click `Start`
5. Verify data is coming in

### Troubleshooting
**No devices found**
- Verify the FlightStick is plugged in:
- Try a different USB port
- Try a different USB cable
- Confirm `rtl_test -t` detects the device
- On linux,
- Verify the user is in the `plugdev`
- replug the device in after updating permission

**Driver connects but no data**
- Confirm `dump1090` is running with `--net-sbs-port 30003`
- Check `nc localhost 30003` produces output
- Move antenna outdoors or near a window for better reception
- Verify no firewall is blocking TCP port `30003`
- If running `dump1090` remotely, confirm the configured IP is reachable


### References
- [AirNav](https://www.airnavradar.com/)
- [dump1090](https://github.com/flightaware/dump1090)

29 changes: 29 additions & 0 deletions sensors/aviation/sensorhub-driver-airnav-adsb/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
description = 'AirNav Radar FlightStick'
ext.details = 'ADS-B receiver USB dongle designed for flight tracking'
version = '1.0.0'

dependencies {
implementation 'org.sensorhub:sensorhub-core:' + oshCoreVersion
implementation project(':sensorhub-utils-aero')

}

// add info to OSGi manifest
osgi {
manifest {
attributes('Bundle-Vendor': 'GeoRobotix Innovative Research')
attributes('Bundle-Activator': 'org.sensorhub.impl.sensor.adsb.Activator')
}
}

// add info to maven pom
ext.pom >>= {
developers {
developer {
id 'kalynstricklin'
name 'Kalyn Stricklin'
organization 'GeoRobotix Innovative Research'
organizationUrl 'https://georobotix.us'
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/***************************** BEGIN LICENSE BLOCK ***************************

The contents of this file are subject to the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.

Copyright (C) 2026 GeoRobotix Innovative Research. All Rights Reserved.

******************************* END LICENSE BLOCK ***************************/

package org.sensorhub.impl.sensor.adsb;

import org.osgi.framework.BundleActivator;
import org.sensorhub.utils.OshBundleActivator;


/*
* Needed to expose java services as OSGi services
*/
public class Activator extends OshBundleActivator implements BundleActivator
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/***************************** BEGIN LICENSE BLOCK ***************************

The contents of this file are subject to the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.

Copyright (C) 2026 GeoRobotix Innovative Research. All Rights Reserved.

******************************* END LICENSE BLOCK ***************************/

package org.sensorhub.impl.sensor.adsb;

import org.sensorhub.api.config.DisplayInfo;
import org.sensorhub.api.sensor.PositionConfig;
import org.sensorhub.api.sensor.SensorConfig;
import org.sensorhub.impl.comm.TCPCommProviderConfig;


public class AdsbConfig extends SensorConfig {
@DisplayInfo.Required
@DisplayInfo(desc = "Serial number or unique identifier for this ADS-B receiver")
public String serialNumber = "adsb001";

@DisplayInfo(desc = "Communication settings to connect to the dump1090 SBS output (default: localhost:30003)")
public TCPCommProviderConfig commSettings;

@DisplayInfo(desc = "ADS-B Receiver Location")
public PositionConfig positionConfig = new PositionConfig();

@Override
public PositionConfig.LLALocation getLocation(){return positionConfig.location;}
@Override
public PositionConfig.EulerOrientation getOrientation(){ return positionConfig.orientation;}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/***************************** BEGIN LICENSE BLOCK ***************************

The contents of this file are subject to the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.

Copyright (C) 2026 GeoRobotix Innovative Research. All Rights Reserved.

******************************* END LICENSE BLOCK ***************************/

package org.sensorhub.impl.sensor.adsb;

import net.opengis.gml.v32.Point;
import net.opengis.swe.v20.DataBlock;
import net.opengis.swe.v20.DataComponent;
import net.opengis.swe.v20.DataEncoding;
import net.opengis.swe.v20.DataRecord;
import org.sensorhub.api.data.DataEvent;
import org.sensorhub.impl.sensor.AbstractSensorOutput;
import org.sensorhub.utils.aero.AeroHelper;
import org.vast.swe.SWEHelper;

public class AirNavADSBOutput extends AbstractSensorOutput<AirNavADSBSensor> {
private static final String SENSOR_OUTPUT_NAME = "adsbOutput";
private static final String SENSOR_OUTPUT_LABEL = "ADS-B Aircraft";
private static final String SENSOR_OUTPUT_DESCRIPTION = "Aircraft position and identification data decoded from ADS-B transmissions";

private static final int AVERAGE_SAMPLING_PERIOD = 1;

DataRecord dataStruct;
DataEncoding dataEncoding;

public AirNavADSBOutput(AirNavADSBSensor parentSensor) {
super(SENSOR_OUTPUT_NAME, parentSensor);
}

protected void init() {
AeroHelper aeroHelper = new AeroHelper();

dataStruct = aeroHelper.createRecord()
.name(SENSOR_OUTPUT_NAME)
.label(SENSOR_OUTPUT_LABEL)
.description(SENSOR_OUTPUT_DESCRIPTION)
.addField("sampleTime", aeroHelper.createTime()
.asSamplingTimeIsoUTC()
.label("Sample Time")
.description("Time of data collection"))
.addField("aircraftType", aeroHelper.createAircraftType())
.addField("callsign", aeroHelper.createCallSign())
.addField("pos", aeroHelper.createAircraftLocation())
.addField("alt_baro", aeroHelper.createBaroAlt())
.addField("gs", aeroHelper.createGroundSpeed())
.addField("heading", aeroHelper.createTrueHeading())
.addField("alt_rate", aeroHelper.createVerticalRate())
.addField("squawk", aeroHelper.createText()
.label("Squawk Code")
.definition(SWEHelper.getPropertyUri( "SquawkCode"))
.description("4-digit octal ATC transponder code"))
.addField("alert", aeroHelper.createBoolean()
.label("Alert Flag")
.definition(SWEHelper.getPropertyUri("AlertFlag"))
.description("Indicates squawk code has changed"))
.addField("emergency", aeroHelper.createBoolean()
.label("Emergency Flag")
.definition(SWEHelper.getPropertyUri("EmergencyFlag"))
.description("Indicates aircraft is in emergency status"))
.addField("isOnGround", aeroHelper.createBoolean()
.label("Is On Ground")
.definition(SWEHelper.getPropertyUri("IsOnGround"))
.description("Indicates aircraft is on the ground"))
.build();

dataEncoding = aeroHelper.newTextEncoding(",", "\n");
}

protected synchronized void publishAircraftState(AircraftState state) {
Point geometry = parentSensor.getGeometry(state.lat, state.lon);
var foiUID = parentSensor.addFoi(state.icao, state.callsign, geometry);

DataBlock dataBlock;
if (latestRecord == null)
dataBlock = dataStruct.createDataBlock();
else
dataBlock = latestRecord.renew();

int idx = 0;
dataBlock.setDoubleValue(idx++, System.currentTimeMillis() / 1000.0);
dataBlock.setStringValue(idx++, state.icao);
dataBlock.setStringValue(idx++, state.callsign != null ? state.callsign : "");
dataBlock.setDoubleValue(idx++, state.lat);
dataBlock.setDoubleValue(idx++, state.lon);
dataBlock.setDoubleValue(idx++, Double.isNaN(state.altFt) ? 0.0 : state.altFt * 0.3048); // ft to meters
dataBlock.setDoubleValue(idx++, Double.isNaN(state.groundSpeed) ? 0.0 : state.groundSpeed);
dataBlock.setDoubleValue(idx++, Double.isNaN(state.heading) ? 0.0 : state.heading);
dataBlock.setDoubleValue(idx++, Double.isNaN(state.verticalRate) ? 0.0 : state.verticalRate);
dataBlock.setStringValue(idx++, state.squawk != null ? state.squawk : "");
dataBlock.setBooleanValue(idx++, state.alert);
dataBlock.setBooleanValue(idx++, state.emergency);
dataBlock.setBooleanValue(idx++, state.isOnGround);

latestRecord = dataBlock;
latestRecordTime = System.currentTimeMillis();
eventHandler.publish(new DataEvent(latestRecordTime, this, foiUID, dataBlock));
}

public double getAverageSamplingPeriod() {
return AVERAGE_SAMPLING_PERIOD;
}

@Override
public DataComponent getRecordDescription() {
return dataStruct;
}

@Override
public DataEncoding getRecommendedEncoding() {
return dataEncoding;
}
}
Loading
Loading