def process(samples: SampleStream, sdr: RtlSdr) -> None: sample_rate_fm = 240000 # sample_rate_fm = 180000 iq_comercial = signal.decimate(samples, int(sdr.get_sample_rate()) // sample_rate_fm) angle_comercial = np.unwrap(np.angle(iq_comercial)) demodulated_comercial = np.diff(angle_comercial) audio_signal = signal.decimate(demodulated_comercial, sample_rate_fm // audio_rate, zero_phase=True) # audio_signal = np.int16(14000 * audio_signal) audio_signal = np.int16(24000 * audio_signal) stream_audio(audio_signal.astype("int16").tobytes())
class RtlReceiver(Process): """Set up and receive data from RTLSDR. """ def __init__(self, sample_rate, center_freq, gain, ppm, sdr_out_A, sdr_out_B, decimate, satellite=False): super(RtlReceiver, self).__init__() self.sample_rate = sample_rate self.center_freq = center_freq self.gain = gain self.freq_correction = ppm self.async_sample_size = 1024 * 24 #default 1024 self.sdr_out_A = sdr_out_A self.sdr_out_B = sdr_out_B self.decimate = decimate if satellite: # Channel 75 self.freq_shift_A = self.center_freq - 156.775 * 1e6 # Channel 76 self.freq_shift_B = self.center_freq - 156.825 * 1e6 else: # Channel 88B self.freq_shift_A = self.center_freq - 162.025 * 1e6 # Channel 87B self.freq_shift_B = self.center_freq - 161.975 * 1e6 print("Frequency shift A: {}MHz".format(self.freq_shift_A / 1e6)) print("Frequency shift B: {}MHz".format(self.freq_shift_B / 1e6)) self.fc1_A = np.exp(1.0j * 2.0 * np.pi * self.freq_shift_A / self.sample_rate * np.arange(self.async_sample_size)) self.fc1_B = np.exp(1.0j * 2.0 * np.pi * self.freq_shift_B / self.sample_rate * np.arange(self.async_sample_size)) def send_samples(self, samples, rtl): #shift frequency, filter, detect phase, and send if len(samples) == self.async_sample_size: pass else: pad = np.zeros(self.async_sample_size - len(samples)) samples = np.append(samples, pad) print("Samples padded: {}".format(len(pad))) #Frequency shift shifted_A = samples * self.fc1_A shifted_B = samples * self.fc1_B self.sdr_out_A.send(shifted_A) self.sdr_out_B.send(shifted_B) def run(self): #Import here so that a working RtlSdr isn't required to work with recordings from rtlsdr import RtlSdr, RtlSdrTcpClient self.sdr = RtlSdr() #self.sdr = RtlSdrTcpClient(hostname='192.168.0.6', port=1234) # configure device self.sdr.center_freq = self.center_freq self.sdr.sample_rate = self.sample_rate self.sdr.gain = self.gain if self.freq_correction != 0: self.sdr.freq_correction = self.freq_correction #try for some extra filtering try: #self.sdr.bandwidth = (.350 * 1e6) pass except IOError: print("No bandwidth adjustment availible.") print("SDR Frequency: {}MHz".format(self.sdr.get_center_freq() / 1e6)) print("SDR Sample Rate: {}MS/s".format(self.sdr.get_sample_rate() / 1e6)) self.sdr.read_samples_async(self.send_samples, self.async_sample_size) print("Radio closing...")
process(samples, rtl_sdr_obj) parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--ppm', type=int, default=0, help='ppm error correction') parser.add_argument('--gain', type=int, default=20, help='RF gain level') parser.add_argument('--freq', type=int, default=92900000, help='frequency to listen to, in Hertz') parser.add_argument('--verbose', action='store_true', help='mute audio output') args = parser.parse_args() sdr = RtlSdr() sdr.rs = 1024000 sdr.fc = args.freq sdr.gain = args.gain sdr.err_ppm = args.ppm sdr.read_samples_async(read_callback, int(sdr.get_sample_rate()) // 16)
class Input(DataSource.DataSource): def __init__(self, source: str, data_type: str, sample_rate: float, centre_frequency: float, input_bw: float): """ The rtlsdr input source :param source: The device number, normally zero :param data_type: The data type the rtlsdr is providing, we will convert this :param sample_rate: The sample rate we will set the source to, note true sps is set from the device :param centre_frequency: The centre frequency the source will be set to :param input_bw: The filtering of the input, may not be configurable """ # Driver converts to floating point for us, underlying is 8o? self._constant_data_type = "16tle" super().__init__(source, self._constant_data_type, sample_rate, centre_frequency, input_bw) self._connected = False self._sdr = None self._tuner_type = 0 self._device_index = 0 self._gain_modes = ["auto", "manual"] # would ask, but can't super().set_gain_mode(self._gain_modes[0]) super().set_help(help_string) super().set_web_help(web_help_string) def open(self) -> bool: global import_error_msg if import_error_msg != "": msgs = f"No {module_type} support available, ", import_error_msg self._error = msgs logger.error(msgs) raise ValueError(msgs) if self._source == "?": self._error = self.find_devices() return False try: self._device_index = int(self._source) except ValueError as err: msgs = f"port number from {self._source}, {err}" self._error = str(err) logger.error(msgs) raise ValueError(err) try: self._sdr = RtlSdr(device_index=self._device_index) except Exception as err: self._error = f"Failed to connect {str(err)}" logger.error(self._error) raise ValueError(self._error) self._tuner_type = self._sdr.get_tuner_type() logger.debug(f"Connected to {module_type}") try: self.set_sample_rate_sps(self._sample_rate_sps) self.set_centre_frequency_hz(self._centre_frequency_hz) # self._sdr.freq_correction = 0 # ppm self.set_gain_mode('auto') self.set_gain(0) except ValueError: pass except Exception as err: self._error = str(err) raise ValueError(err) # recover the true values from the device self._sample_rate_sps = float(self._sdr.get_sample_rate()) self._centre_frequency_hz = float(self._sdr.get_center_freq()) logger.debug( f"{allowed_tuner_types[self._tuner_type]} {self._centre_frequency_hz / 1e6:.6}MHz @ " f"{self._sample_rate_sps:.3f}sps") self._connected = True return self._connected def close(self) -> None: if self._sdr: self._sdr.close() self._sdr = None self._connected = False @staticmethod def find_devices() -> str: devices = "" # could do with a call that returns the valid device_index's max_device = 10 for device in range(max_device): try: sdr = RtlSdr(device_index=device) type_of_tuner = sdr.get_tuner_type() # index = sdr.get_device_index_by_serial('0000001') # permissions required # addresses = sdr.get_device_serial_addresses() # permissions required sdr.close() devices += f"device {device}, type {type_of_tuner} {allowed_tuner_types[type_of_tuner]}\n" except Exception: pass if devices == "": devices = f"No rtlsdr devices found, scanned 0 to {max_device-1}" print(devices) return devices def get_sample_rate_sps(self) -> float: if self._sdr: self._sample_rate_sps = float(self._sdr.get_sample_rate()) return self._sample_rate_sps def get_centre_frequency_hz(self) -> float: if self._sdr: if self._hw_ppm_compensation: self._centre_frequency_hz = float(self._sdr.get_center_freq()) return self._centre_frequency_hz def set_sample_type(self, data_type: str) -> None: # we can't set a different sample type on this source super().set_sample_type(self._constant_data_type) def set_sample_rate_sps(self, sample_rate: float) -> None: # rtlsdr has limits on allowed sample rates # from librtlsdr.c data_source.get_bytes_per_sample() # /* check if the rate is supported by the resampler */ # if ((samp_rate <= 225000) || (samp_rate > 3200000) || # ((samp_rate > 300000) && (samp_rate <= 900000))) { # fprintf(stderr, "Invalid sample rate: %u Hz\n", samp_rate); # return -EINVAL; # } # logger.info(f"set sr rtlsdr tuner type {self._tuner_type}, {allowed_tuner_types[self._tuner_type]}") if (sample_rate <= 225000) or (sample_rate > 3200000) or ( (sample_rate > 300000) and (sample_rate <= 900000)): err = f"{module_type} invalid sample rate, {sample_rate}sps, 225000-3000000 and not 300000-900000" self._error = err logger.error(err) sample_rate = 1e6 # something safe self._sample_rate_sps = sample_rate if self._sdr: try: self._sdr.sample_rate = sample_rate self._sample_rate_sps = float(self._sdr.get_sample_rate()) except Exception as err: self._error = str(err) logger.debug( f"bad sr {sample_rate} now {self._sample_rate_sps}") logger.info(f"Set sample rate {sample_rate}sps") def set_centre_frequency_hz(self, frequency: float) -> None: # limits depend on tuner type: from https://wiki.radioreference.com/index.php/RTL-SDR # Tuner Frequency Range # ======================================= # Elonics E4000 52 – 1100 MHz / 1250 - 2200 MHz # Rafael Micro R820T(2) 24 – 1766 MHz # Fitipower FC0013 22 – 1100 MHz # Fitipower FC0012 22 - 948.6 MHz # FCI FC2580 146 – 308 MHz / 438 – 924 MHz freq_ok = True ok = True # logger.info(f"set cf rtlsdr tuner type {self._tuner_type}, {allowed_tuner_types[self._tuner_type]}") # what type of tuner do we have ? freq_range = "" if self._tuner_type == 1: # E4000 if (frequency < 52e6) or (frequency > 2200e6): freq_ok = False freq_range = "52 – 1100 MHz and 1250 - 2200 MHz" elif (frequency > 1100e6) and (frequency < 1250e6): freq_ok = False freq_range = "52 – 1100 MHz and 1250 - 2200 MHz" elif self._tuner_type == 2: # FC0012 if (frequency < 22e6) or (frequency > 948.6e6): freq_ok = False freq_range = "22 - 948.6 MHz" elif self._tuner_type == 3: # FC0013 if (frequency < 22e6) or (frequency > 1100e6): freq_ok = False freq_range = "22 – 1100 MHz" elif self._tuner_type == 4: # FC2580 if (frequency < 146e6) or (frequency > 924e6): freq_ok = False freq_range = "146 – 308 MHz and 438 – 924 MHz" elif (frequency > 308e6) and (frequency < 438e6): freq_ok = False freq_range = "146 – 308 MHz and 438 – 924 MHz" elif self._tuner_type == 5 or self._tuner_type == 6: # R820T or R828D if (frequency < 24e6) or (frequency > 1.766e9): freq_ok = False freq_range = "24 – 1766 MHz" else: self._error = f"Unknown tuner type {self._tuner_type}, frequency range checking impossible" logger.error(self._error) ok = False if not freq_ok: self._error = f"{allowed_tuner_types[self._tuner_type]} invalid frequency {frequency}Hz, " \ f"outside range {freq_range}" logger.error(self._error) ok = False if self._sdr and ok: try: if self._hw_ppm_compensation: self._sdr.center_freq = frequency self._centre_frequency_hz = float( self._sdr.get_center_freq()) else: self._centre_frequency_hz = frequency self._sdr.center_freq = self.get_ppm_corrected(frequency) # print(f"freq {frequency} ppm {self._ppm} -> {frequency + (self._ppm * frequency / 1e6)}") logger.info(f"Set frequency {frequency / 1e6:0.6f}MHz") except Exception as err: self._error = str(err) def set_ppm(self, ppm: float) -> None: """ +ve reduces tuned frequency -ve increases the tuned frequency :param ppm: Parts per million error, :return: """ self._ppm = ppm self.set_centre_frequency_hz(self._centre_frequency_hz) def get_gain(self) -> float: if self._sdr: self._gain = self._sdr.get_gain() return self._gain def set_gain(self, gain: float) -> None: self._gain = gain if self._sdr: try: # horrible _sdr.set_gain() - either a number or string if self._gain_mode == 'auto': self._sdr.set_gain('auto') else: self._sdr.set_gain(float(gain)) except Exception as err: self._error = f"failed to set gain of '{gain}', {err}" def set_gain_mode(self, mode: str) -> None: if mode in self._gain_modes: self._gain_mode = mode if self._sdr: # because the 'best' way to set the mode is to set the gain, apparently self.set_gain(self._gain) return def set_bandwidth_hz(self, bw: float) -> None: if self._sdr: try: self._sdr.set_bandwidth(int(bw)) except Exception as err: self._error += str(err) self._bandwidth_hz = self._sdr.get_bandwidth() def get_bandwidth_hz(self) -> float: if self._sdr: self._bandwidth_hz = self._sdr.get_bandwidth() return self._bandwidth_hz def read_cplx_samples(self, number_samples: int) -> Tuple[np.array, float]: """ Get complex float samples from the device Note that we don't use unpack() for this device :return: A tuple of a numpy array of complex samples and time in nsec """ complex_data = None rx_time = 0 if self._sdr and self._connected: try: complex_data = self._sdr.read_samples( number_samples) # will return np.complex128 rx_time = self.get_time_ns() complex_data = np.array( complex_data, dtype=np.complex64 ) # (?) we need all values to be 32bit floats except Exception as err: self._connected = False self._error = str(err) logger.error(self._error) raise ValueError(err) return complex_data, rx_time
class Sdr(): def __init__(self): #Abro el dongle self.dispositivo = RtlSdr() #Lo configuro self.dispositivo.sample_rate = 2.048e6 #Hz self.dispositivo.center_freq = 1e9 #Hz self.dispositivo.freq_correction = 60 #ppm self.dispositivo.gain = 49.6 #dB self.dispositivo.set_agc_mode(False) self.frecuencia_oscilador_local_lnb = 10.5e9 self.limite_inferior_Ku = 11.45e9 self.limite_superior_Ku = 12.25e9 self.nfft = 1024 self.n = 256 self.Vref = 1 #V self.Zo = 2e3 #Ohm self.potencia_calibrada = None def leer_potencia(self): #Leo las muestras iq = self.dispositivo.read_samples(self.n * self.nfft) #Calculo la potencia de la señal potencia = calcular_potencia(iq, self.Vref, self.Zo) - self.dispositivo.gain return potencia def leer_potencia_calibrada(self): if self.esta_calibrado() == True: return self.leer_potencia() - self.potencia_calibrada else: return None def calibrar(self): print('Calibrando...') potencia_aux = 0 for i in range(10): potencia_aux += self.leer_potencia() self.potencia_calibrada = potencia_aux / (i + 1) print('Calibrado a {:2.2f} dBFS'.format(self.potencia_calibrada)) def esta_calibrado(self): return self.potencia_calibrada != None def configurar(self): entrada = 0 while entrada != 6: entrada = interfaz.imprimir_menu_configuracion() if entrada == 1: frecuencia = float( input( 'Ingrese una frecuencia entre {:f} Hz y {:f} (en Hz): ' .format(self.limite_inferior_Ku, self.limite_superior_Ku))) if (frecuencia < self.limite_inferior_Ku) or ( frecuencia > self.limite_superior_Ku): print('Valor fuera de rango') else: self.dispositivo.set_center_freq( frecuencia - self.frecuencia_oscilador_local_lnb) elif entrada == 2: ancho_banda = float( input( 'Ingrese un ancho de banda entre 1 MHz y 3.2 MHz (En Hz): ' )) if (ancho_banda < 1e6) or (ancho_banda > 3.2e6): print('Valor fuera de rango') else: self.dispositivo.set_sample_rate(ancho_banda) elif entrada == 3: lista = np.array(self.dispositivo.valid_gains_db) try: self.dispositivo.set_gain( float( input( 'Ingrese una ganancia entre {:2.1f} dB y {:2.1f} dB: ' .format(np.min(lista), np.max(lista))))) print('Ganancia configurada en {:2.1f} dB'.format( self.dispositivo.get_gain())) except: print('Valor fuera de rango') elif entrada == 4: try: self.n = int( input( 'Ingrese la cantidad de paquetes de 1024 muestras: ' )) except: print('Valor fuera de rango') elif entrada == 5: frecuencia_central = self.get_frecuencia_central() #Hz ancho_banda = self.dispositivo.get_sample_rate() #Hz ganancia = self.dispositivo.get_gain() #dB paquetes = self.n tamanio_paquete = self.nfft print('Configuracion actual: ') print('Frecuencia central: {:f} Hz'.format(frecuencia_central)) print('Ancho de banda: {:f} Hz'.format(ancho_banda)) print('Ganancia: {:2.1f} dB'.format(ganancia)) print('Mediciones compuestas por {} paquetes de {} muestras'. format(paquetes, tamanio_paquete)) print('Tiempo aproximado por medicion: {:f} ms'.format( 1e3 * paquetes * tamanio_paquete / ancho_banda)) elif entrada == 6: print('Configuracion realizada\n') else: interfaz.orden_no_valida() def get_frecuencia_central(self): return self.dispositivo.get_center_freq( ) + self.frecuencia_oscilador_local_lnb def esta_abierto(self): return self.dispositivo.device_opened def cerrar(self): if self.esta_abierto(): self.dispositivo.close()