Ejemplo n.º 1
0
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