From 6fdcdcbf5d864adfc07d410246a5609231d675b0 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello <66272872+aromanielloNTIA@users.noreply.github.com> Date: Mon, 23 Oct 2023 08:06:45 -0600 Subject: [PATCH] Use thread lock for entire IQ capture method (#48) * Apply threading lock to entire IQ capture method * Update pre-commit hooks * Update package version * name lock object * Fix creation of sigan_lock * Update pre-commit hooks * Add plugin version as instance variable in SiganInterface * Add plugin_version property to sigan interface * Require scos-actions 6.4.0 * Switch to testing scos-actions branch * tagged scos-actions 6.4.0 * Switch scos actions version for testing * Update to tagged scos-actions 6.4.1 --- .pre-commit-config.yaml | 8 +- pyproject.toml | 2 +- src/scos_tekrsa/__init__.py | 2 +- src/scos_tekrsa/hardware/tekrsa_sigan.py | 206 ++++++++++++----------- 4 files changed, 114 insertions(+), 104 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d9d6576..6d0c9f3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3.8 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-ast types: [file, python] @@ -18,7 +18,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.15.0 hooks: - id: pyupgrade args: ["--py38-plus"] @@ -30,12 +30,12 @@ repos: types: [file, python] args: ["--profile", "black", "--filter-files", "--gitignore"] - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.10.0 hooks: - id: black types: [file, python] - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.35.0 + rev: v0.37.0 hooks: - id: markdownlint types: [file, markdown] diff --git a/pyproject.toml b/pyproject.toml index 393b551..7ee9969 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ classifiers = [ dependencies = [ "environs>=9.5.0", "tekrsa-api-wrap>=1.3.2", - "scos_actions @ git+https://github.com/NTIA/scos-actions@6.3.3", + "scos_actions @ git+https://github.com/NTIA/scos-actions@6.4.1", ] [project.optional-dependencies] diff --git a/src/scos_tekrsa/__init__.py b/src/scos_tekrsa/__init__.py index f749372..1fe90f6 100644 --- a/src/scos_tekrsa/__init__.py +++ b/src/scos_tekrsa/__init__.py @@ -1 +1 @@ -__version__ = "3.1.3" +__version__ = "3.1.4" diff --git a/src/scos_tekrsa/hardware/tekrsa_sigan.py b/src/scos_tekrsa/hardware/tekrsa_sigan.py index 3225d39..ad8b4ed 100644 --- a/src/scos_tekrsa/hardware/tekrsa_sigan.py +++ b/src/scos_tekrsa/hardware/tekrsa_sigan.py @@ -8,6 +8,7 @@ ) import scos_tekrsa.hardware.tekrsa_constants as rsa_constants +from scos_tekrsa import __version__ as SCOS_TEKRSA_VERSION from scos_tekrsa import settings from scos_tekrsa.hardware.mocks.rsa_block import MockRSA @@ -21,6 +22,7 @@ def __init__(self): try: super().__init__() logger.info("Initializing Tektronix RSA Signal Analyzer") + self._plugin_version = SCOS_TEKRSA_VERSION self.rsa = None self._is_available = False # should not be set outside of connect method @@ -106,6 +108,11 @@ def is_available(self): """Returns True if initialized and ready for measurements""" return self._is_available + @property + def plugin_version(self): + """Returns the current version of scos-tekrsa.""" + return self._plugin_version + @property def sample_rate(self): self._iq_bandwidth, self._sample_rate = self.rsa.IQSTREAM_GetAcqParameters() @@ -255,109 +262,112 @@ def acquire_time_domain_samples( cal_adjust: bool = True, ): """Acquire specific number of time-domain IQ samples.""" - self._capture_time = None - if isinstance(num_samples, int) or ( - isinstance(num_samples, float) and num_samples.is_integer() - ): - nsamps_req = int(num_samples) # Requested number of samples - else: - raise ValueError("Requested number of samples must be an integer.") - nskip = int(num_samples_skip) # Requested number of samples to skip - nsamps = nsamps_req + nskip # Total number of samples to collect - - if cal_adjust: - # Get calibration data for acquisition - if not (settings.RUNNING_TESTS or settings.MOCK_SIGAN): - cal_params = sensor_calibration.calibration_parameters + with sigan_lock: + self._capture_time = None + if isinstance(num_samples, int) or ( + isinstance(num_samples, float) and num_samples.is_integer() + ): + nsamps_req = int(num_samples) # Requested number of samples else: - # Make it work for mock sigan/testing. Just match frequency. - cal_params = [vars(self)["_frequency"]] - try: - cal_args = [vars(self)[f"_{p}"] for p in cal_params] - except KeyError: - raise Exception( - "One or more required cal parameters is not a valid sigan setting." - ) - logger.debug(f"Matched calibration params: {cal_args}") - self.recompute_sensor_calibration_data(cal_args) - # Compute the linear gain - db_gain = self.sensor_calibration_data["gain"] - linear_gain = 10.0 ** (db_gain / 20.0) - else: - linear_gain = 1 + raise ValueError("Requested number of samples must be an integer.") + nskip = int(num_samples_skip) # Requested number of samples to skip + nsamps = nsamps_req + nskip # Total number of samples to collect + + if cal_adjust: + # Get calibration data for acquisition + if not (settings.RUNNING_TESTS or settings.MOCK_SIGAN): + cal_params = sensor_calibration.calibration_parameters + else: + # Make it work for mock sigan/testing. Just match frequency. + cal_params = [vars(self)["_frequency"]] + try: + cal_args = [vars(self)[f"_{p}"] for p in cal_params] + except KeyError: + raise Exception( + "One or more required cal parameters is not a valid sigan setting." + ) + logger.debug(f"Matched calibration params: {cal_args}") + self.recompute_sensor_calibration_data(cal_args) + # Compute the linear gain + db_gain = self.sensor_calibration_data["gain"] + linear_gain = 10.0 ** (db_gain / 20.0) + else: + linear_gain = 1 - # Determine correct time length (round up, integer ms) - durationMsec = int(1000 * (nsamps / self.sample_rate)) + ( - 1000 * nsamps % self.sample_rate > 0 - ) + # Determine correct time length (round up, integer ms) + durationMsec = int(1000 * (nsamps / self.sample_rate)) + ( + 1000 * nsamps % self.sample_rate > 0 + ) - if durationMsec == 0: - # Num. samples requested is less than minimum duration for IQ stream. - # Handle this by skipping more samples than requested - durationMsec = 1 # Minimum allowed IQ stream duration - nskip = int((self.sample_rate / 1000) - nsamps_req) - nsamps = nskip + nsamps_req + if durationMsec == 0: + # Num. samples requested is less than minimum duration for IQ stream. + # Handle this by skipping more samples than requested + durationMsec = 1 # Minimum allowed IQ stream duration + nskip = int((self.sample_rate / 1000) - nsamps_req) + nsamps = nskip + nsamps_req - logger.debug(f"acquire_time_domain_samples starting, num_samples = {nsamps}") - logger.debug(f"Number of retries = {retries}") + logger.debug( + f"acquire_time_domain_samples starting, num_samples = {nsamps}" + ) + logger.debug(f"Number of retries = {retries}") - max_retries = retries + max_retries = retries - while True: - self._capture_time = utils.get_datetime_str_now() - with sigan_lock: + while True: + self._capture_time = utils.get_datetime_str_now() data, status = self.rsa.IQSTREAM_Tempfile_NoConfig(durationMsec, True) - - data = data[nskip : nskip + nsamps_req] # Remove extra samples, if any - data_len = len(data) - - logger.debug(f"IQ Stream status: {status}") - - # Check status string for overload / data loss - self.overload = False - if "Input overrange" in status: - self.overload = True - logger.warning("IQ stream: ADC overrange event occurred.") - - if "data loss" in status or "discontinuity" in status: # Invalid data - if retries > 0: - logger.warning( - f"Data loss occurred during IQ streaming. Retrying {retries} more times." - ) - retries -= 1 - continue - else: - err = "Data loss occurred with no retries remaining." - err += f" (tried {max_retries} times.)" - raise RuntimeError(err) - elif ( - not data_len == nsamps_req - ): # Invalid data: incorrect number of samples - if retries > 0: - msg = f"RSA error: requested {nsamps_req + nskip} samples, but got {data_len}." - logger.warning(msg) - logger.warning(f"Retrying {retries} more times.") - retries -= 1 - continue + data = data[nskip : nskip + nsamps_req] # Remove extra samples, if any + data_len = len(data) + + logger.debug(f"IQ Stream status: {status}") + + # Check status string for overload / data loss + self.overload = False + if "Input overrange" in status: + self.overload = True + logger.warning("IQ stream: ADC overrange event occurred.") + + if "data loss" in status or "discontinuity" in status: # Invalid data + if retries > 0: + logger.warning( + f"Data loss occurred during IQ streaming. Retrying {retries} more times." + ) + retries -= 1 + continue + else: + err = "Data loss occurred with no retries remaining." + err += f" (tried {max_retries} times.)" + raise RuntimeError(err) + elif ( + not data_len == nsamps_req + ): # Invalid data: incorrect number of samples + if retries > 0: + msg = f"RSA error: requested {nsamps_req + nskip} samples, but got {data_len}." + logger.warning(msg) + logger.warning(f"Retrying {retries} more times.") + retries -= 1 + continue + else: + err = "Failed to acquire correct number of samples " + err += f"{max_retries} times in a row." + raise RuntimeError(err) else: - err = "Failed to acquire correct number of samples " - err += f"{max_retries} times in a row." - raise RuntimeError(err) - else: - logger.debug(f"IQ stream: successfully acquired {data_len} samples.") - # Scale data to RF power and return - logger.debug(f"Applying gain of {linear_gain}") - data /= linear_gain - - measurement_result = { - "data": data, - "overload": self.overload, - "frequency": self.frequency, - "reference_level": self.reference_level, - "sample_rate": self.rsa.IQSTREAM_GetAcqParameters()[1], - "capture_time": self._capture_time, - } - if self.device_name not in ["RSA306B", "RSA306"]: - measurement_result["attenuation"] = self.attenuation - measurement_result["preamp_enable"] = self.preamp_enable - return measurement_result + logger.debug( + f"IQ stream: successfully acquired {data_len} samples." + ) + # Scale data to RF power and return + logger.debug(f"Applying gain of {linear_gain}") + data /= linear_gain + + measurement_result = { + "data": data, + "overload": self.overload, + "frequency": self.frequency, + "reference_level": self.reference_level, + "sample_rate": self.rsa.IQSTREAM_GetAcqParameters()[1], + "capture_time": self._capture_time, + } + if self.device_name not in ["RSA306B", "RSA306"]: + measurement_result["attenuation"] = self.attenuation + measurement_result["preamp_enable"] = self.preamp_enable + return measurement_result