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);
	}

}