📡 CygnusRFI: An open-source RFI analysis tool for Ground Stations & Radio Telescopes

Hi all,

I just published a new project on git:

:satellite: CygnusRFI: An open-source RFI analysis tool for Ground Stations & Radio Telescopes

CygnusRFI is an easy-to-use open-source Radio Frequency Interference (RFI) analysis tool, based on Python and GNU Radio Companion (GRC) that is conveniently applicable to any ground station/radio telescope working with a GRC-supported software-defined radio (SDR).

In addition to data acquisition, CygnusRFI also carries out automated analysis of the recorded data, producing a series of averaged spectra covering a wide range of frequencies of interest.

CygnusRFI is built for ground station operators, radio astronomers, amateur radio operators and anyone who wishes to get an idea of how “radio-quiet” their environment is, using inexpensive equipment like SDRs.

You can check out CygnusRFI here: :point_down:

Feedback appreciated! :slight_smile:


This is a very cool piece of work, obviously quite a bit of time and effort went into it… and quite timely, for my own personal interests. I need to get a less subjective handle on my own local RF environment.

For changes to the source block used in pfb.grc are there any issues with using a gr-soapy source ?
Like are there any subtleties in how the processing downstream might depend on the way gr-osmosdr exposes the source hardware, vs soapy ?

I mean most of my gear is covered by Osmo, but the Lime is feeling a little left out… and I’m starting to shift everything over to soapy anyways, but if those sources are cool, then that’s awesome!

1 Like

Thanks for the feedback! Sure, feel free to add a LimeSDR Source block, or any other block. gr-soapy source blocks should output raw I/Q samples, which the CygnusRFI flowgraph processes and analyzes automatically, so it should work. :slight_smile:


I would strongly suggest adding functionality to ‘crop’ off the filter skirts which are present at the edges of your measurements.
rtl_power does this with the -c argument. On something like a RTLSDR, realistically only the centre 70% or so of your received spectrum is nominally flat (though it will still have some ripple). Anything outside this range should be discarded, and the step size adjusted accordingly.

Without this, the resultant plots for measurements where the receiver has been stepped are very misleading, with big ‘dips’ repeating at the step frequency.


As discussed in the #satnogs channel, that is something I’m looking to do soon. The currently intended solution to not lose the information between the individual passbands is to take another measurement, starting from f_min+bandwidth/2. This will shift the center frequency of every spectrum to the the edges of the previous measurement, and give you all the data.

I don’t think the plotted data is misleading (perhaps because I’m familiar with the passband shape?), but I’m looking to improve this as you can see in the to-do section, with passband calibration. These two features will not only give me the missing information between the bands, but will also calibrate the passbands (which are not perfectly flat, they have ripples even in the 70% of the “sub-band”), and ultimately flatten the entire spectrum out. :slight_smile:


I ran it with your example command and got this:

python CygnusRFI.py -b 2400000 -c 2048 -t 0.5 -d 5 -f 400000000 -F 430000000

1 Like

Cool! Looks like there are some harmonics at around 399 MHz and some faint transmissions here and there, but nothing super strong to stop you from doing satellite receptions at that band. Are you using an RTL-SDR by itself or with filters/amplifiers?


That is just a workstation in the kitchen with a small antenna as a test. No filters or amps there. I haven’t connected it to my SatNOGS antennas/raspberrypi yet. I have a Nooelec LaNA LNA on one of those.



OK, I ran it on two raspberry PIs that are used for SatNOGS. The second one has LNA. See below:



Edit: add ground stations…

I’m getting this error, but it doesn’t appear to break anything (?):

[ERROR] SoapySSDPEndpoint::sendTo(udp://[ff02::c]:1900) = -1
  sendto(udp://[ff02::c]:1900) [99: Cannot assign requested address]

More pics:

python CygnusRFI.py -b 2400000 -c 2048 -t 0.5 -d 5 -f 135000000 -F 148000000

python CygnusRFI.py -b 2400000 -c 2048 -t 0.5 -d 5 -f 399000000 -F 470000000


Cool, looks like your LNA helps raise the S/N of various signals in that band. :slightly_smiling_face:

Regarding the SoapySSDPEndpoint error, are you seeing this for the first time? Have you put any device arguments, or does your SDR Source block differ in any way from the SatNOGS data acquisition GRC flowgraph (which I assume doesn’t pop that error)? In any case, it doesn’t really seem to affect you. Looks more like a warning that you can ignore rather than an error.


I think it is just non-fatal error. I hadn’t noticed that error before, but I haven’t searched either. I didn’t put in device arguments, I ran it like displayed above. Thanks!

So, I’m having a “crazy thought” here… and partly I blame my real-world job of being a professional software/tech “dot-connector” between different “interesting” applications of technology, but in this particular case, I really wonder how beneficial the following “mashup” idea could be.

@0xCoto you described the “intended use” of CygnusRFI to be {satellite} ground-stations and radio-telescope operators, but in reality it’s quite well suited to anyone setting up an SDR receiver, on any band, right ?

Especially anyone who’s looking to make an SDR, and its output accessible to remote users/viewers…

I’m a big fan of the OpenWebRX project that recently shifted hands in terms of primary code contributors. In the past, I’ve gotten plenty of folks interested in RF and setting up their own SDR receiver stations simply by pointing them at one publicly hosted OpenWebRX server or another. Some were even properly configured (antenna & band) to receive audio from the ISS as it passed overhead that ground location, which got those folks even more interested in the “satellite” side of RF things.

I personally find value in using the network of these publicly accessible servers to “sample” raw IQ streams from different stations around the world. Not just for VHF/UHF (and higher), but also down in the HF bands as well. Where the receive conditions might be poor, or undesirable, for my own gear, there could be other stations with better environmental conditions that I can tap into. It’s obviously much less “organized” than the SATNOGS network, but equally valuable in it’s own way, and, most importantly, equally concerned with RFI.

For those not familiar with this system, it’s more than just a “web portal” to an SDR receiver though. It’s a proper framework where 3rd party extensions (all web accessible) exist to do all manner of signal processing and exploitation of whats being “heard”.

Many of these are demodulators for digital modes, while others are even more niche. There’s even a semi-commercial product (KiwiSDR) that uses an earlier fork of OpenWebRX, along with a very specialized use of an on-board software-defined GPS to do HF TDOA work.

So now that OpenWebRX has a new lease on life, and has been upgraded in terms of Python, server-side security, as well as a whole slew of signal decoding modes… @0xCoto I wonder if there’s a match with having your project, and the value it brings, added as an extension to the OpenWebRX framework ?

Just a suggestion, mind you. I have a feeling that anyone trying to set up an OpenWebRX server would very much benefit from being able to sample, and visually view the RFI conditions… and since the entire premise of the OpenWebRX server is to interact via a web-portal, even for administrative stuff, it kind of fits to have the output of your utility show up as a “dashboard” on the admin pages for this server.

Not asking anyone to do any kind of code hacking here… hell, I’ll probably be spending a weekend trying it anyways :nerd_face:, just curious if folks think it’s a good idea ?
there’s a bunch of stuff that needs to be checked of course…

  • are the open-source licenses for each project compatible ?
  • are the software dependencies compatible (python versions, GnuRadio)?
  • etc…

Jakob Ketterl, (DD5JFK), the new “maintainer” of the OpenWebRX code is probably not a member of SATNOGS, so he’s also probably un-aware of the CygnusRFI utility (unless it was promoted elsewhere), but he might be curious about it, maybe even excited to add it, should things prove to be compatible…

So before I go off, firing emails left and right to bring attention to the potential mashup/collab here, what think you all ? (especally @0xCoto since this is your baby :wink:

1 Like

Indeed, CygnusRFI is for every RF/SDR user who wishes to get an idea of their environment in terms of radio frequency interference.

I’m not sure I understand, but that doesn’t seem to match the purpose of CygnusRFI. This tool is meant to be used by the SDR user once in a while to monitor their local environment for RFI.

I personally find no value in acquiring and sharing raw streams of I/Q data with other users online (unless pre-FFT analysis is necessary). Post-FFT waterfall data are significantly lower in data size and generally offer the information you’re after.

CygnusRFI is fully open source, so if any developer (including OpenWebRX maintainers) wishes to implement the code into their framework, they are more than welcome to. Luigi F. Cruz, the developer & maintainer of PiSDR has already integrated VIRGO and CygnusRFI into his project, so I’m sure the developer(s)/maintainer(s) of OpenwebRX can do the same if they’re interested.

I’m not super familiar with the OpenWebRX community, so I can’t have a meaningful opinion. :slight_smile:

CygnusRFI is licensed under the MIT License*, so no issues on my end.

Integration is possible if the developers/maintainers of the project wish to work on such feature update.

* If someone believes this license imposes limitations for the growth of software, technology or science, I am open to discussion.

1 Like

Upper limitation of the CygnusRFI, at least on my system.
*I started to monitor the frequency bands around me, decided to divide the range, used by the rtlsdr, to 40-60 MHz slices. *
But the range between 60 and 100 MHz, I made a mistake, I inserted in the second frequency patrameter additional zeroes. The upper frequency become 1 GHz instead of the 100 MHz.
The program sarted, and after more than half hour processing, it aborted, because the generated picture file size become more than the possible max size on my system. It saved 391 files full with data.
Here is the message of the program, after the abort:

  • [] Currently monitoring f_center = 998.4 +/- 1.2 MHz (iteration: 391)…

gr-osmosdr 0.1.4 (0.1.4) gnuradio
*built-in source types: file osmosdr fcd rtl rtl_tcp uhd miri hackrf bladerf rfspace airspy airspyhf soapy redpitaya freesrp *

RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (hw:0,2), Device or resource busy.

Using device #0 Realtek RTL2838UHIDIR SN: 00000001
Found Rafael Micro R820T tuner
[R82XX] PLL not locked!
[R82XX] PLL not locked!

| Measurement finished! |

Image size of 196000x2025 pixels is too large. It must be less than 2^16 in each direction.


That’s an issue related to matplotlib, but I would expect a user to have a hard time trying to load extremely high-resolution images on their machine anyway. As you said, you accidentally added an extra 0 to the upper frequency limit, so you would have a hard time viewing the data later on anyway (due to the range being extremely wide).

Best to keep the ranges shorter, because even if matplotlib didn’t crash (and this is likely to protect your computer’s memory from clogging up), it would be difficult for you to view the image.

EDIT: By the way, if you happen to accidentally enter incorrect values, can use Ctrl + c to interrupt CygnusRFI from taking further spectrum measurements.

1 Like

From there, the interactive software should ask you for the parameters of your RFI measurement, which you can simply enter in and let CygnusRFI do its magic! Once the observation is finished, your data will be processed, analyzed and saved as rfi_plot.png (in the same directory as observe.py ).

I was not asked for any parameters of my RFI measurement after entering the sample command line, only that the observation would take about 60s and if I wanted to proceed. At the end, it said

| Measurement finished! |

Unknown property pad
Your data has been saved as rfi_plot.png.

However rfi_plot.png is nowhere to be found and I see no “observe.py”, either.

What am I doing wrong?


I recently added argument support so you don’t have to enter the observation parameters every single time, so “interactive” might be a bit misleading after that improvement. I just edited the README to better fit the usage of the software, but you should have seen the parameters before the proceed-to-measurement confirmation.

I am very curious about the Unknown property pad issue. It seems to be related to matplotlib, but Google has nothing on that issue… I think I’ve seen it once before on a Windows machine, but trying to update the library using pip install --upgrade matplotlib completely destroyed the environment variables (related to the presence of GNU Radio on the same computer running its own Python versions), so I didn’t have the chance debug further…

Could you please try commenting out lines 78-80 on rfi_plotter.py and retry? I am particularly suspicious of line 80, since it’s the only line with property pad=25, which seems to be associated with the Unknown property pad error you got.



Commented out lines 78-80 and the error is gone, and I got this plot!


Aha! I was just going to comment that I just checked, and line 80 seems to be the problem indeed. Feel free to uncomment the lines so you get the axis labels back. All you need to do is remove the pad property, so replace this line:

ax1.set_title("\nAveraged RFI Spectrum", pad=25)

with this:

ax1.set_title("\nAveraged RFI Spectrum")

That way you get all the title and axis labels back. :slight_smile:

Thanks for reporting this bug! I’ve now made this code snippet fail-safe, so in case pad is an unrecognized property, it ignores it:

    ax1.set_title("\nAveraged RFI Spectrum", pad=25)
    ax1.set_title("\nAveraged RFI Spectrum")

Excellent! Axis labels are back and everything works great. Thank you!