GRX1090 Java API
The Java API provides a thin abstraction layer for the gRPC interface. You need at least Java 17.
Installation
Download the latest API jar
and add it to your
project’s classpath. Note that if you are using Eclipse, make sure you add the
library to your Modulepath rather than the Classpath.
Javadoc
The javadoc for the Java bindings can be found here.
Examples
Receiver API
Getting Mode S status information:
package de.serosystems.grx.example;
import de.serosystems.grx.GRX1090Client;
import de.serosystems.proto.v3.grx.receiverd.ReceiverDProto;
import java.util.concurrent.ExecutionException;
/**
* Example showing how to access internal state of the Mode S receiver.
*/
public class ModeSStatus {
public static void main(String[] args) {
// we expect the first CLI argument to be the IP/hostname of the GRX
if (args.length < 1) {
System.err.println("Missing argument: receiver hostname!");
System.exit(1);
}
// create a client which connects to the given hostname
GRX1090Client grx1090 = new GRX1090Client(args[0]);
// request the hardware's status
var statusReq = grx1090.getRadioStatus();
try {
// retrieve the status (blocks until request finishes)
ReceiverDProto.GetRadioFrontEndStatusReply status = statusReq.get();
System.out.printf("The receiver has %d radio front-end(s).\n", status.getRadioFrontEndStatusCount());
status.getRadioFrontEndStatusList().forEach(rf -> {
System.out.printf("\tRadio front-end %s-%d:\n", rf.getBand().name(), rf.getPerBandIndex());
System.out.printf("\t\tNoise Level: %.3f\n", rf.getNoiseLevel());
System.out.printf("\t\tExternal Fixed Gain: %.3f\n", rf.getExternalFixedGain());
System.out.printf("\t\tInternal Fixed Gain: %.3f\n", rf.getRxChainFixedGain());
System.out.printf("\t\tVariable Gain: %.3f\n", rf.getRxChainVariableGain());
System.out.printf("\t\tCalibration Done: %b\n", rf.getDcOffsetCalibrationDone());
System.out.printf("\t\tAntenna Port: %s\n", rf.getAntennaId());
});
System.out.printf("The receiver has %d antenna ports:\n", status.getAntennaStatusCount());
status.getAntennaStatusMap().forEach((key, value) -> {
System.out.printf("\tAntenna port %s:\n", key);
System.out.printf("\t\tLabel: %s\n", value.getLabel());
System.out.printf("\t\tPower Supply: %s\n",
value.getHasSupplyEnable() ? value.getSupplyEnable() : "N/A");
System.out.printf("\t\tPower Supply Fault: %s\n", value.getSupplyFault().name());
});
System.out.printf("The receiver also has %d antenna switches:\n", status.getAntennaSwitchStatusCount());
status.getAntennaSwitchStatusMap().forEach((key, value) -> {
System.out.printf("\tAntenna switch %s (%s):\n", value.getId(), value.getLabel());
System.out.printf("\t\tState: '%s' (%s)\n", value.getStateId(),
value.getStateIdToLabelMap().get(value.getStateId()));
});
} catch (InterruptedException | ExecutionException e) {
// something unexpected happened
e.printStackTrace();
}
}
}
Retrieving messages as a synchronous stream:
package de.serosystems.grx.example;
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import de.serosystems.grx.GRX1090Client;
import de.serosystems.proto.v3.grx.receiverd.ReceiverDProto;
/**
* Example showing how to subscribe to GRX1090 Mode S message stream in a blocking fashion.
* <br />
* This example subscribes to DF 20 and 21 only. The IP of the device must be passed via the
* first commandline argument.
*/
public class BlockingModeSDownlink {
public static void main(String[] args) {
// we expect the first CLI argument to be the IP/hostname of the GRX
if (args.length < 1) {
System.err.println("Missing argument: receiver hostname!");
System.exit(1);
}
// create a client which connects to the given hostname
GRX1090Client grx1090 = new GRX1090Client(args[0]);
// subscribe to Mode S Downlink formats 20 and 21
GRX1090Client.ChannelIterator<ReceiverDProto.ModeSDownlinkFrameWithStreamInfo> chnIter =
grx1090.subscribeModeSDownlink(Sets.newHashSet(20, 21), false);
// retrieve 10 frames, print them, then quit
int i = 0;
while (chnIter.hasNext() && i++ < 10) {
ReceiverDProto.ModeSDownlinkFrameWithStreamInfo msg = chnIter.next();
System.out.printf("Message %d: t = %d, m = %s\n",
i, msg.getFrame().getTimestamp(),
BaseEncoding.base16().lowerCase().encode(msg.getFrame().getPayload().toByteArray()));
}
// close everything
chnIter.close();
grx1090.close();
}
}
Retrieving messages synchronously with IQ data:
package de.serosystems.grx.example;
import com.google.common.io.BaseEncoding;
import de.serosystems.grx.GRX1090Client;
import de.serosystems.proto.v3.grx.receiverd.ReceiverDProto;
import java.nio.ShortBuffer;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
/**
* Example showing how to subscribe to GRX1090 Mode S message stream with IQ data.
* <br />
* This example subscribes to DF17 (ADS-B) data with IQ samples. The IP of the device must be passed via the
* first commandline argument.
*/
public class IQSamples {
// GPS leap seconds as of 2017-01-01
public static final long CURRENT_GPS_LEAP_NANOSECONDS = 18_000_000_000L;
/**
* Converts a GNSS timestamp to a Unix timestamp based on a given reference time which must be in the
* same week. If you are doing live processing, use System.currentTimeMillis(). Function employs
* a grace period of 1 minute for the timestamp week rollover. So reference timestamp should never be
* more than 1 minute away from the GNSS timestamp.
* @param gnssTimestamp GNSS timestamp as provided by the receiver in nanoseconds
* @param referenceTime reference time (Unix timestamp) in milliseconds (tolerance 1 minute)
* @return unix timestamp (in nanoseconds) for the GNSS timestamp
*/
public static long gnssTimestampToUnixtime (long gnssTimestamp, long referenceTime) {
// round to start of week (compensate that Unix timestamp 0 was a Thursday)
long startOfWeekMs = Math.floorDiv(referenceTime + 4L*86_400_000L, 7L*86_400_000L) * 7L*86_400_000L;
// convert back to Unix timestamp
startOfWeekMs = startOfWeekMs - 4L*86_400_000L;
// check if timestamps refer to different weeks (happens close to week rollover)
// they are assumed to refer to different weeks if their difference is about 1 week (1 minute threshold)
long diff = (referenceTime - startOfWeekMs)*1_000_000L - gnssTimestamp;
if (diff < -604740_000_000_000L)
// GNSS timestamp refers to week before
startOfWeekMs = startOfWeekMs - (7L*86_400_000L);
else if (diff > 604740_000_000_000L)
// GNSS timestamp refers to next week
startOfWeekMs = startOfWeekMs + (7L*86_400_000L);
return startOfWeekMs*1_000_000 + gnssTimestamp - CURRENT_GPS_LEAP_NANOSECONDS;
}
public static void main(String[] args) {
// we expect the first CLI argument to be the IP/hostname of the GRX
if (args.length < 1) {
System.err.println("Missing argument: receiver hostname!");
System.exit(1);
}
// create a client which connects to the given hostname
GRX1090Client grx1090 = new GRX1090Client(args[0]);
// subscribe to Mode S Downlink format 17 with IQ data
GRX1090Client.ChannelIterator<ReceiverDProto.ModeSDownlinkFrameWithStreamInfo> chnIter =
grx1090.subscribeModeSDownlink(Collections.singleton(17), true);
// retrieve 10 frames, print them, then quit
int i = 0;
short[] si = null, sq = null;
while (chnIter.hasNext() && i++ < 10) {
ReceiverDProto.ModeSDownlinkFrameWithStreamInfo msg = chnIter.next();
// extract some fields for convenience
ReceiverDProto.ModeSDownlinkFrame frm = msg.getFrame();
long ts = gnssTimestampToUnixtime(frm.getTimestamp(), System.currentTimeMillis());
System.out.printf("Received message number %d:\n", i);
System.out.printf("\tDropped frames: %d\n", msg.getFramesDropped());
System.out.printf("\tPayload: %s\n",
BaseEncoding.base16().lowerCase().encode(frm.getPayload().toByteArray()));
System.out.printf("\tTimestamp: %d ns (%s)\n", frm.getTimestamp(), Instant.ofEpochMilli(ts/1_000_000L));
if (frm.getCarrierFrequencyOffsetComputed())
System.out.printf("\tCarrier frequency offset: %.2f Hz (error: %.4f)\n",
frm.getCarrierFrequencyOffset(), frm.getCarrierFrequencyEstimationError());
System.out.printf("\tSignal/noise level: %.2f dBm/%.2f dBm\n", frm.getLevelSignal(), frm.getLevelNoise());
System.out.printf("\tSamples: %b\n", frm.hasSamples());
System.out.println();
// now print some information about the samples
if (frm.hasSamples()) {
ReceiverDProto.SignalSamples s = frm.getSamples();
System.out.printf("\tSample rate: %f MHz\n", s.getSampleRate()/1e6);
System.out.printf("\tStart of data: %d\n", s.getStartOfData());
System.out.printf("\tSample format: %s\n", s.getSampleFormat());
System.out.printf("\tData volume: %d bytes\n", s.getSamples().size());
// parse samples
if (s.getSampleFormat() == ReceiverDProto.SignalSamples.SampleFormat.S16I_S16Q) {
ShortBuffer iq = s.getSamples().asReadOnlyByteBuffer().asShortBuffer();
int n = iq.capacity()/2;
System.out.printf("\tNumber of samples: %s\n", n);
System.out.printf("\tDuration: %.2f µs\n", 1e6 * n / s.getSampleRate());
// separate I and Q
si = new short[n];
sq = new short[n];
for (int j = 0; j < n; j++) {
si[j] = iq.get(j*2);
sq[j] = iq.get(j*2+1);
}
}
}
System.out.println("\n");
}
System.out.println("The I/Q samples of the last frame were:");
System.out.printf("I = %s\n", Arrays.toString(si));
System.out.printf("Q = %s\n", Arrays.toString(sq));
// close everything
chnIter.close();
grx1090.close();
}
}
Retrieving messages in an asynchronous fashion:
package de.serosystems.grx.example;
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import de.serosystems.grx.GRX1090Client;
import de.serosystems.proto.v3.grx.receiverd.ReceiverDProto;
import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Example showing how to asynchronously subscribe to GRX1090 Mode S message stream.
* The application subscribes to Mode S DF 17, 0, 5, and 11 and then waits for 5 seconds
* before exiting. During these 5 seconds, the application's handler for incoming
* Mode S downlink frames receives and prints the subscribed data.
*/
public class AsyncModeSDownlink {
// a CountDownLatch is used to block the main thread, while streaming
private static final CountDownLatch finishLatch = new CountDownLatch(1);
/**
* This class implements the handler for the received Mode S downlink frames.
*/
private static class ModeSDownlinkFrameHandler
implements StreamObserver<ReceiverDProto.ModeSDownlinkFrameWithStreamInfo> {
@Override
public void onNext(ReceiverDProto.ModeSDownlinkFrameWithStreamInfo msg) {
// Do something with the messages, e.g. just print it
System.out.printf("Message: t = %d, m = %s\n",
msg.getFrame().getTimestamp(),
BaseEncoding.base16().lowerCase().encode(msg.getFrame().getPayload().toByteArray()));
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
System.err.printf("ERROR: %s\n", throwable.getMessage());
// on error, we finish
finishLatch.countDown();
}
@Override
public void onCompleted() {
System.err.println("COMPLETED");
// on stream completion we finish as well
finishLatch.countDown();
}
}
public static void main(String[] args) {
// we expect the first CLI argument to be the IP/hostname of the GRX
if (args.length < 1) {
System.err.println("Missing argument: receiver hostname!");
System.exit(1);
}
// create a client which connects to the given hostname
GRX1090Client grx1090 = new GRX1090Client(args[0]);
// we need to subscribe for particular downlink formats
ManagedChannel channel = grx1090.subscribeModeSDownlink(
Sets.newHashSet(17, 0, 5, 11),false,
new ModeSDownlinkFrameHandler());
try {
// wait for 5 seconds, then quit gracefully
finishLatch.await(5, TimeUnit.SECONDS);
channel.awaitTermination(100, TimeUnit.MILLISECONDS);
grx1090.close();
} catch (InterruptedException e) {
// something unexpected happened
e.printStackTrace();
}
}
}
Retrieving the list of tracked aircraft:
package de.serosystems.grx.example;
import de.serosystems.grx.GRX1090Client;
import de.serosystems.proto.v3.grx.receiverd.ReceiverDProto;
import java.util.concurrent.ExecutionException;
/**
* Example showing how to retrieve the list of currently tracked aircraft
* from the receiver.
*/
public class TrackedAircraft {
public static void main(String[] args) {
// we expect the first CLI argument to be the IP/hostname of the GRX
if (args.length < 1) {
System.err.println("Missing argument: receiver hostname!");
System.exit(1);
}
// create a client which connects to the given hostname
GRX1090Client grx1090 = new GRX1090Client(args[0]);
// request the list of tracked aircraft
var allTargets = grx1090.getAllTargets();
try {
// retrieve the list (blocks until request is finished)
ReceiverDProto.StateVectorList stateVectors = allTargets.get();
// print some information
System.out.printf("Got %d tracked aircraft\n", stateVectors.getStateVectorsCount());
System.out.printf("%d tracked aircraft have an ADS-B position\n",
stateVectors.getStateVectorsList().stream()
.filter(ReceiverDProto.StateVectorList.StateVector::hasPosition)
.count());
// print one example
if (stateVectors.getStateVectorsCount() > 0)
System.out.printf("First one:\n==========\n%s\n", stateVectors.getStateVectors(0));
} catch (InterruptedException | ExecutionException e) {
// something unexpected happened
e.printStackTrace();
}
}
}
Monitoring API
Getting GNSS status information:
package de.serosystems.grx.example;
import de.serosystems.grx.GRX1090Client;
import de.serosystems.proto.v3.grx.monitord.MonitorDProto;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
/**
* Example showing how to retrieve GPS status information from the receiver.
*/
public class GNSSStatus {
public static void main(String[] args) {
// we expect the first CLI argument to be the IP/hostname of the GRX
if (args.length < 1) {
System.err.println("Missing argument: receiver hostname!");
System.exit(1);
}
// create a client which connects to the given hostname
GRX1090Client grx1090 = new GRX1090Client(args[0]);
// request the GNSS information from the device
var gnssFuture = grx1090.getGNSSInfo();
try {
// retrieve the GNSS info (blocks until request is finished)
MonitorDProto.GNSSInformation info = gnssFuture.get();
System.out.printf(Locale.US, "%-20s: %.5f, %.5f\n",
"Position (lat, lon)",
info.getPosition().getLatitude(),
info.getPosition().getLongitude());
System.out.printf("%-20s: %.0fm\n", "Height", info.getPosition().getHeight());
System.out.printf("%-20s: %s\n", "Fix Type", info.getPosition().getFixType());
System.out.printf("%-20s: %s\n", "UTC Time", info.getTiming().getUtc());
System.out.printf("%-20s: %d\n", "Satellites", info.getPosition().getSatsUsed());
System.out.printf("%-20s: %dns\n", "Time Accuracy",
info.getTiming().getGnssTimeAccuracy());
System.out.printf(Locale.US, "%-20s: %.2fm\n", "Horizontal Accuracy",
info.getPosition().getHorizontalAccuracy());
System.out.printf(Locale.US, "%-20s: %.2f\n", "Vertical Accuracy",
info.getPosition().getVerticalAccuracy());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
Spectrum API
Retrieving and showing a waterfall chart:
package de.serosystems.grx.example;
import com.google.protobuf.Empty;
import de.serosystems.grx.GRX1090Client;
import de.serosystems.proto.v3.grx.spectrumd.SpectrumDProto;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
/**
* Example showing how to retrieve a waterfall chart from the spectrumd API.
* See the respective .proto files to learn about all functions provided by
* the API.
*/
public class SpecShot {
public static void main(String[] args) {
// we expect the first CLI argument to be the IP/hostname of the GRX
if (args.length < 1) {
System.err.println("Missing argument: receiver hostname!");
System.exit(1);
}
// create a client which connects to the given hostname
GRX1090Client grx1090 = new GRX1090Client(args[0]);
// retrieve the current specs of aggregated FFTs
SpectrumDProto.AggregatedFFTProperties props = null;
try {
props = grx1090.getSpectrumdAPI().getAggregatedFFTProperties(Empty.newBuilder().build()).get();
} catch (InterruptedException | ExecutionException e) {
// something unexpected happened
e.printStackTrace();
System.exit(1);
}
// the specshot should cover 10 seconds of data
float duration = 10; // seconds
float dline = props.getAggregationFactor() * props.getFftSize() / (float) props.getSampleRate();
int nlines = (int) Math.ceil(duration / dline);
System.out.printf("Will collect data for %.2f seconds.\n", nlines * dline);
SpectrumDProto.GetWaterfallJPEGRequest req = SpectrumDProto.GetWaterfallJPEGRequest.newBuilder()
.setNumLines(nlines)
.setMinLevel(-180) // dBm
.setMaxLevel(-90) // dBm
.setJpegQuality(90) // %
.build();
SpectrumDProto.WaterfallJPEGImage img = null;
try {
img = grx1090.getSpectrumdAPI().getWaterfallJPEG(req).get();
} catch (InterruptedException | ExecutionException e) {
// something unexpected happened
e.printStackTrace();
System.exit(1);
}
System.out.printf("The waterfall chart JPEG image has a size of %d bytes.", img.getImage().size());
// finally show image
BufferedImage buffered = null;
try {
buffered = ImageIO.read(new ByteArrayInputStream(img.getImage().toByteArray()));
} catch (IOException e) {
// could not read image
e.printStackTrace();
System.exit(1);
}
ImageIcon icon = new ImageIcon(buffered);
JFrame frame = new JFrame();
frame.setLayout(new FlowLayout());
frame.setSize(icon.getIconWidth(), icon.getIconHeight());
JLabel lbl = new JLabel();
lbl.setIcon(icon);
frame.add(lbl);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}