I am still very new to python and the world of rtlsdr but have a project I'm working on with a Raspberry Pi to essentially trip warning lights when a radio signal is heard.
At a high level, my project is using an external antenna for the 2 meter radio band connected to an RTL-SDR dongle plugged into the Raspberry Pi. I have a standard relay connected to the GPIO pins that switches the lights on when it "hears" the signal.
Really I don't care about 99% of the incoming signal. I just want to know when there's a carrier at 147.3MHz and let the GPIO turn the lights on. That's it. Nothing more and nothing less. Surely this could be simply done? I've read up on the scipy.signal.butter bandpass filter but I can't get it to work at this frequency.
My code is below and I welcome any suggestions for improvement but ideally I'm looking for a way for the program to recognize peaks at the center frequency of 147.3MHz and then call my 'warning_lights.py' script.
import RPi.GPIO as GPIO
from rtlsdr import *
from scipy import signal
import peakdetect
import datetime
import sys
import subprocess
def restart():
import subprocess
import time
time.sleep(120)
command = "/usr/bin/sudo /sbin/shutdown -r now"
process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
output = process.communicate()[0]
print (output)
# configure SDR device and settings
sdr = RtlSdr()
sdr.sample_rate = 2.4e6 # Hz
sdr.center_freq = 147.3e6 # Hz
sdr.gain = 'auto' # Possible values are 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6
num_samples = 1024*1024
procs = []
while True:
try:
samples = sdr.read_samples(num_samples)
power, psd_freq = psd(samples, NFFT=1024, Fs=sdr.sample_rate/1e6,
Fc=sdr.center_freq/1e6)
power_db = 10*np.log10(power)
maxima, minima = peakdetect.peakdetect(power_db, psd_freq, delta=1)
for mx in maxima:
if mx[0] == 147.3: #checking that peak was on 147.3 MHz
if mx[1] > (-15): #checking dBm of signal
try:
while proc.poll() is None:
proc.terminate()
except:
print("Warning lights not on. Turning them on.")
finally:
print(mx[1])
proc = subprocess.Popen([sys.executable, '/home/pi/scripts/warning_lights.py'])
procs.append(proc)
except:
restart()
When you tune to 147.3MHz, what you get out of the SDR is not the actual radio signal as it appears at the input. The SDR shifts the RF signal down in frequency to a lower frequency that is easier to process and digitise. As a result, the part of the original signal that is centred around 147.3MHz is shifted down to 0Hz, into what's known as "baseband". Everything outside of a certain bandwidth (2.4MHz in your case) is filtered away. So, if there was a pure sinusoid signal at 148.3MHz for example, this would appear as a 1MHz sinusoid in the output of your SDR. It's very helpful to think of a spectrum plot to visualise this: Wikipedia article on baseband.
So, if there's a radio signal centred at 147.3MHz, you won't see that at 147.3MHz in the output of the SDR, but instead at 0Hz (with a slight offset since the frequency of the SDR circuitry isn't perfectly accurate). This should be easy to filter using scipy as you mentioned.
There is one other gotcha which may be an issue. Most SDRs produce a small peak in frequency at the frequency they are tuned to (known as the "DC Spike"). In my RTL-SDR, I have found that this is the case when the gain is low. The way to get around this is to tune to a frequency that is outside the bandwidth of the signal you're interested in (something like 147.5MHz would work) and then compensate for this shift in your processing.
As a result, what should work is:
This isn't illustrated in the image, but you also can get negative frequencies in your signal from any frequencies below the carrier.