Spectrum API
Note
The Spectrum API is provided on TCP port 5306.
The Spectrum API provides frequency spectrum monitoring capabilities. The device continuously computes FFT (Fast Fourier Transform) on the raw I/Q samples from each radio front-end and provides aggregated results as streams or rendered images.
Service Methods
// Get static FFT parameters (center frequency, sample rate, FFT size, etc.)
rpc GetAggregatedFFTProperties (AggregatedFFTRequest) returns (AggregatedFFTProperties);
// Stream aggregated (average + peak) FFT blocks
rpc GetAggregatedFFTBlockStream (AggregatedFFTRequest) returns (stream AggregatedFFTBlock);
// Get a single waterfall chart as JPEG image
rpc GetWaterfallJPEG (GetWaterfallJPEGRequest) returns (WaterfallJPEGImage);
// Stream waterfall charts as JPEG images
rpc GetWaterfallJPEGStream (GetWaterfallJPEGRequest) returns (stream WaterfallJPEGImage);
// Stream channel power measurements over a frequency sub-band
rpc GetChannelPowerStream (ChannelPowerRequest) returns (stream ChannelPower);
How It Works
The spectrum processing pipeline works as follows:
FFT Block:
fft_sizeI/Q samples are transformed into the frequency domain, producingfft_sizefrequency bins.Aggregation:
aggregation_factorconsecutive FFT blocks are combined by averaging and taking the peak (maximum). This produces one aggregated FFT block.Output: The aggregated blocks are delivered via
GetAggregatedFFTBlockStreamor rendered into waterfall images.
Key parameters (returned by GetAggregatedFFTProperties):
Center frequency (Hz): The center of the observed band.
Sample rate (Hz): Total bandwidth is
sample_rateHz, centered at the center frequency.FFT size: Number of frequency bins. Each bin has bandwidth
sample_rate / fft_size.Aggregation factor: Number of FFT blocks averaged into one output. The time duration of each aggregated block is
aggregation_factor * fft_size / sample_rateseconds.
Waterfall Charts
GetWaterfallJPEG collects num_lines aggregated FFT blocks and renders
them as a JPEG image (one line per block, from top to bottom over time). The
request parameters are:
num_lines: Height of the image in pixels (and number of time steps).min_level/max_level: Color scale range in dBm.jpeg_quality: JPEG compression quality (0–100).aggregation_type: UseAVERAGEorPEAKdata.
The total recording duration is:
num_lines * aggregation_factor * fft_size / sample_rate seconds.
Example: 10-second waterfall chart at 1090 MHz showing ADS-B/Mode S activity around the center frequency and DME activity near 1085 MHz.
Channel Power
GetChannelPowerStream provides continuous power measurements for a specific
frequency sub-band (defined by lower_bin and upper_bin). This is useful
for monitoring channel occupancy or interference levels over time.
Multiple Radio Front-Ends
On multi-band receivers, each method requires identifying the radio channel.
Use the radio_identification field (preferred) or the deprecated
rx_channel_index. Call GetAggregatedFFTProperties to discover the
center frequency and sample rate for each available channel.
Protocol Buffer Definition
syntax = "proto3";
import "google/protobuf/empty.proto";
package serosystems.proto.v3.grx.spectrumd;
option java_package = "de.serosystems.proto.v3.grx.spectrumd";
option java_outer_classname = "SpectrumDProto";
import "Common.proto";
/* Request for GetAggregatedFFT* calls */
message AggregatedFFTRequest {
/*
* Index of the chosen RX channel.
* Useful if the device has more than a single RX channel.
* If that index is not available, the call will fail.
*
* Only used, if radio_identification is not present.
* Deprecated, as radio_identifacation should be used instead.
*/
uint32 rx_channel_index = 1 [ deprecated = true ];
/* If present, the specified RadioIdentification is used to identify the channel.
* In this case, the value of rx_channel_index will be ignored.
*/
RadioIdentification radio_identification = 2;
}
/*
* This is a service for measuring spectrum occupancy.
* For that, (fft_size) samples are taken and transformed into frequency domain.
* We call this an FFT block. Then (aggregation_factor) of those blocks are
* aggregated (avg and peak=max). We call this an aggregated FFT block.
* Regarding those aggregated FFT blocks, different services are derived:
* - GetAggregatedFFTProperties() returns the fixed parameters used for
* transformation and aggregation,
* - GetAggregatedFFTBlockStream() returns a stream of aggregated FFT blocks,
* - GetWaterfallJPEG() gathers multiple FFT blocks into an waterfall plot,
* returning the plot rendered in a JPEG,
* - GetWaterfallJPEGStream() returns a stream of such rendered plots,
* - GetChannelPowerStream() re-aggregates aggregated FFT blocks over a longer
* period and for a frequency range (we call that a channel).
*/
/*
* Aggregated FFT properties.
* Response of GetAggregatedFFTProperties() call.
*/
message AggregatedFFTProperties {
/* Center Frequency [Hz]. */
uint32 center_frequency = 1;
/*
* Sample rate [Hz].
* Observed band is [-sample_rate/2,+sample_rate/2),
* centered at (center_frequency).
*/
uint32 sample_rate = 2;
/*
* FFT size, i.e. number of samples used in a single FFT block.
* This translates directly into the number of frequency bins of the FFT
* result. Each bin has bandwidth (sample_rate / fft_size).
* Thinking of a waterfall plot, this is the number of steps of the x axis
* (frequency).
*/
uint32 fft_size = 3;
/*
* Number of FFT blocks aggregated into an aggregated FFT block.
* The duration observed for a single FFT block is
* (fft_size / sample_rate). Accordingly, the duration observed for an
* aggregated FFT block is (aggregation_factor * fft_size / sample_rate).
*/
uint32 aggregation_factor = 4;
}
/*
* Aggregated (avg/peak) FFT block, basically one line of a waterfall plot.
* Has observation duration (aggregation_factor * fft_size / sample_rate).
* Response of GetAggregatedFFTBlock() call.
*/
message AggregatedFFTBlock {
/*
* Averaged FFT bins [dBm].
* Each bin has bandwidth (sample_rate / fft_size) and is averaged over
* a duration of (aggregation_factor * fft_size / sample_rate).
* The FFT bins are centered around center_frequency, i.e.
* first element corresponds to (center_frequency - 0.5 * sample_rate),
* last element corresponds to
* (center_frequency + (0.5 - 1/fft_size) * sample_rate),
* and the center frequency is found at index (fft_size/2).
* The number of FFT bins is fixed to (fft_size).
*/
repeated float bins_avg = 1;
/*
* Aggregated (peak=max) FFT bins [dBm].
* Each bin has bandwidth (sample_rate / fft_size) and is the maximum
* observed over a duration of
* (aggregation_factor * fft_size / sample_rate).
* The FFT bins are centered around center_frequency, i.e.
* first element corresponds to center_frequency - 0.5 * sample_rate,
* last element corresponds to
* (center_frequency + (0.5 - 1/fft_size) * sample_rate),
* and the center frequency is found at index (fft_size/2).
* The number of FFT bins is fixed to fft_size.
*
* Important note: intermediate peak results are highly compressed (using a
* logarithmic representation), thus you will almost certainly note
* "steps" in the data. Small values might be smaller than the average
* values or they might even be -inf, due to a low resolution for low
* values.
* In such cases, the average value represents a "better" peak.
* This should not be an issue for strong signals, though.
*/
repeated float bins_peak = 2;
}
/*
* Request of GetWaterfallJPEG() and GetWaterfallJPEGStream() calls.
*/
message GetWaterfallJPEGRequest {
/*
* Number of lines of the rendered image, defining the JPEG height [px].
* The service records num_lines aggregated FFT block messages and renders
* them as JPEG. Each line corresponds to one aggregated FFT block.
* The duration shown in the waterfall plot corresponds to
* (lines * aggregation_factor * fft_size / sample_rate)
* which is also the duration needed for the call to return.
*/
uint32 num_lines = 1;
/* Lower limit of the color scale [dBm]. */
float min_level = 2;
/* Upper limit of the color scale [dBm]. */
float max_level = 3;
/* JPEG quality [%]. */
uint32 jpeg_quality = 4;
/* Aggregation type for aggregated FFT blocks. */
enum AggregationType {
/* Aggregate blocks by average. */
AVERAGE = 0;
/* Aggregate blocks by peak. */
PEAK = 1;
}
/* Aggregation type for aggregated FFT blocks. */
AggregationType aggregation_type = 5;
/*
* Index of the chosen RX channel.
* Useful if the device has more than a single RX channel.
* If that index is not available, the call will fail.
*
* Only used, if radio_identification is not present.
* Deprecated, as radio_identifacation should be used instead.
*/
uint32 rx_channel_index = 6 [ deprecated = true ];
/* If present, the specified RadioIdentification is used to identify the channel.
* In this case, the value of rx_channel_index will be ignored.
*/
RadioIdentification radio_identification = 7;
}
/*
* Waterfall plot rendered as a JPEG image.
* Response of GetWaterfallJPEG() and GetWaterfallJPEGStream() calls.
*/
message WaterfallJPEGImage {
/*
* Timestamp [s since epoch UTC] for the bottom line in the JPEG.
*/
uint64 timestamp = 1;
/*
* JPEG image for waterfall plot, rendered from top (first block) to bottom
* (last block).
*/
bytes image = 2;
}
/* Request of GetChannelPowerStream() call. */
message ChannelPowerRequest {
/*
* Additional aggregation factor for channel power measurements.
* The total aggregation factor will be this factor multiplied by
* (aggregation_factor).
* The response is measured over a period of
* (channel_aggregation_factor * aggregation_factor * fft_size
* / sample_rate). This is also the time needed for the call to return.
*/
uint32 channel_aggregation_factor = 1;
/*
* Lower bin index of the FFT to be included in the result.
* See also documentation of AggregatedFFTBlock.bins_avg.
*/
uint32 lower_bin = 2;
/*
* Upper bin index of the FFT to be included in the result.
* See also documentation of AggregatedFFTBlock.bins_avg.
*/
uint32 upper_bin = 3;
/*
* Index of the chosen RX channel.
* Useful if the device has more than a single RX channel.
* If that index is not available, the call will fail.
*
* Only used, if radio_identification is not present.
* Deprecated, as radio_identifacation should be used instead.
*/
uint32 rx_channel_index = 4 [ deprecated = true ];
/* If present, the specified RadioIdentification is used to identify the channel.
* In this case, the value of rx_channel_index will be ignored.
*/
RadioIdentification radio_identification = 5;
}
/* Response of GetChannelPowerStream() call. */
message ChannelPower {
/*
* Timestamp [s since epoch UTC] when the measurement was finished.
*/
uint64 timestamp = 1;
/*
* Average channel power [dBm].
* Average over all
* (channel_aggregation_factor * aggregation_factor) FFT blocks,
* averaged over the channel.
*/
float average_channel_power = 2;
/*
* Peak average channel power [dBm], peak over averages:
* Peak over AggregatedFFTBlock.bins_avg averaged over the channel.
*/
float peak_average_channel_power = 3;
/*
* Peak power [dBm].
* Peak over all
* (channel_aggregation_factor * aggregation_factor) FFTs,
* averaged over the channel.
* Please observe the notes on AggregatedFFTBlock.bins_peak when
* interpreting the data.
*/
float peak_channel_power = 4;
}
/* Spectrum daemon service definition. Port 5306. */
service Spectrumd {
/* Request the aggregated FFT properties, they do not change over time.
* Returns ABORTED if desired RX channel is not available
*/
rpc GetAggregatedFFTProperties (AggregatedFFTRequest) returns (AggregatedFFTProperties);
/* Request a stream of aggregated (avg/peak) FFT blocks.
* Returns ABORTED if desired RX channel is not available
*/
rpc GetAggregatedFFTBlockStream (AggregatedFFTRequest) returns (stream AggregatedFFTBlock);
/* Request a single waterfall plot as JPEG image.
* Returns ABORTED if desired RX channel is not available
*/
rpc GetWaterfallJPEG (GetWaterfallJPEGRequest) returns (WaterfallJPEGImage);
/* Request a stream of waterfall plots as JPEG images.
* Returns ABORTED if desired RX channel is not available
*/
rpc GetWaterfallJPEGStream (GetWaterfallJPEGRequest) returns (stream WaterfallJPEGImage);
/* Request a stream of channel occupancy measurements.
* Returns ABORTED if desired RX channel is not available
*/
rpc GetChannelPowerStream (ChannelPowerRequest) returns (stream ChannelPower);
}