Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use thread lock for entire IQ capture method #48

Merged
merged 13 commits into from
Oct 23, 2023
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.14.0
hooks:
- id: pyupgrade
args: ["--py38-plus"]
Expand All @@ -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.9.1
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]
Expand Down
2 changes: 1 addition & 1 deletion src/scos_tekrsa/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.1.3"
__version__ = "3.1.4"
202 changes: 102 additions & 100 deletions src/scos_tekrsa/hardware/tekrsa_sigan.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

logger = logging.getLogger(__name__)

sigan_lock = threading.Lock()
aromanielloNTIA marked this conversation as resolved.
Show resolved Hide resolved


class TekRSASigan(SignalAnalyzerInterface):
def __init__(self):
Expand Down Expand Up @@ -255,109 +253,113 @@ 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
iq_capture_lock = threading.Lock()
aromanielloNTIA marked this conversation as resolved.
Show resolved Hide resolved
with iq_capture_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