Repository: NTIA/tekrsa-api-wrap (language: python)

Issue:
Simplify package structure and update unit test
- Refactors package structure to remove unnecessary nested directories
- Updates unit test for changes in #4 
- Changes package name to `tekrsa_api_wrap` from `tekrsa-api-wrap` to prepare for eventual PyPi publication
- Updates documentation where necessary to account for these changes

This update is a step towards PyPi publication of this package.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index dbeb955..ae23778 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,9 @@
 # NTIA/ITS Python Wrapper for Tektronix® RSA API for Linux
 
+![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/NTIA/tekrsa-api-wrap?display_name=tag&sort=semver)
+![GitHub all releases](https://img.shields.io/github/downloads/NTIA/tekrsa-api-wrap/total)
+![GitHub issues](https://img.shields.io/github/issues/NTIA/tekrsa-api-wrap)
+
 This Python package provides a module which wraps the [Tektronix Python/Ctypes RSA API](https://github.com/tektronix/RSA_API/tree/master/Python), with the goal of masking the Ctypes dependency and streamlining use of the API in a Python development environment. It implements most of the available RSA API functionality (see below for more information). Basic documentation is included in docstrings for quick reference during development, but this is not meant as a substitute for the comprehensive [RSA API Programming Reference manual](https://www.tek.com/spectrum-analyzer/rsa306-manual/rsa306-rsa306b-and-rsa500a-600a-0) offered by Tektronix. The manual details many peculiarities in API or device behavior which are not immediately obvious, and yet are important for developing software to control an RSA device.
 
 This wrapper was developed for applications involving programmatic control of Tektronix RSA devices from Linux. Depending on your use case, and especially if you plan to run your program from Microsoft Windows®, it may be worth looking into the [Tektronix Python/Cython RSA API](https://github.com/tektronix/RSA_API/tree/master/Python/Cython%20Version) instead of using this wrapper.
@@ -15,16 +19,16 @@ This wrapper was developed for applications involving programmatic control of Te
 
 ## Installation
 
-Requires python>=3.7, numpy>=1.21, and the Tektronix RSA API for Linux.
+Requires `python>=3.7`, `numpy>=1.21`, and the Tektronix RSA API for Linux.
 
 First, download and install the [RSA API for Linux](https://www.tek.com/spectrum-analyzer/rsa306-software/rsa-application-programming-interface--api-for-64bit-linux--v100014) from Tektronix. Follow the included installation instructions, then copy the `libRSA_API.so` and `libcyusb_shared.so` files into your project.
 
-These shared object files are required, and this API wrapper by default expects to find them in the [`scos-sensor`](https://github.com/NTIA/scos-sensor/) drivers directory (`/drivers/`). If you are running without `scos-sensor`, you will need to specify your drivers directory when instantiating the API wrapper. See the [Usage section](#usage) below for an example of how to do this.
+These shared object files are required, and this API wrapper by default expects to find them in the [SCOS Sensor](https://github.com/NTIA/scos-sensor/) drivers directory (`/drivers/`). If you are running without SCOS Sensor, you will need to specify your drivers directory when instantiating the API wrapper. See the [Usage section](#usage) below for an example of how to do this.
 
 Next, download the most recent [release](https://github.com/NTIA/tekrsa-api-ntia/releases) of this package, and install it using `pip`:
 
 ```bash
-pip install tekrsa-api-wrap-1.1.0.tar.gz
+pip install tekrsa_api_wrap-1.2.0.tar.gz
 ```
 
 ## Usage
@@ -74,6 +78,7 @@ A handful of useful functions are included in this wrapper which streamline some
 - `IQSTREAM_Tempfile()`
 - `IQSTREAM_Tempfile_NoConfig()`
 - `DEVICE_SearchAndConnect()`
+- `DEVICE_GetTemperature()`
 
 To read more about these functions, check their docstrings with `help()`.
 
@@ -85,9 +90,12 @@ Known issues exist in the underlying Tektronix RSA API for Linux, and therefore
 
 A testing file is included in the `tests` directory of this repository. The test uses `unittest` to test supported API functions. Running a test requires an RSA device to be connected. The same test is used for any supported RSA device, with some tests being enabled, disabled, or modified as needed depending on the device's specific supported API functions. For example, tests of the preamp configuration are not run when testing with an RSA which does not have a preamp.
 
-From the top-level directory of this repository, run the test, with segmentation fault handling, by running:
+From the top-level directory of this repository, run the test by running:
 
-`python3 -q -X faulthandler tests/rsa_api_test.py <path-to-shared-objects>`
+  ```bash
+  export SO_DIR=/path/to/drivers
+  python -X faulthandler -m unittest
+  ```
 
 Replacing `<path-to-shared-objects>` with the path to a directory containing both `libRSA_API.so` and `libcyusb_shared.so`.
 
diff --git a/rsa_api/__init__.py b/rsa_api/__init__.py
new file mode 100644
index 0000000..1b65072
--- /dev/null
+++ b/rsa_api/__init__.py
@@ -0,0 +1,1 @@
+from .rsa_api import *
\ No newline at end of file
diff --git a/src/rsa_api/rsa_api_wrap.py b/rsa_api/rsa_api.py
similarity index 92%
rename from src/rsa_api/rsa_api_wrap.py
rename to rsa_api/rsa_api.py
index 0e0f307..4c48213 100644
--- a/src/rsa_api/rsa_api_wrap.py
+++ b/rsa_api/rsa_api.py
@@ -20,6 +20,8 @@
 _HW_VERSION_STRLEN = 4  # Bytes allocated for HW version number string
 _NOMENCLATURE_STRLEN = 8  # Bytes allocated for device nomenclature string
 _API_VERSION_STRLEN = 8  # Bytes allocated for API version number string
+_FREQ_REF_USER_SETTING_STRLEN = 54  # Characters in frequency reference user setting string
+_DEVINFO_MAX_STRLEN = 19 # Datetime substring length in user setting string
 
 """ ENUMERATION TUPLES """
 
@@ -31,12 +33,18 @@
 _SPECTRUM_TRACES = ('Trace1', 'Trace2', 'Trace3')
 _SPECTRUM_VERTICAL_UNITS = ('dBm', 'Watt', 'Volt', 'Amp', 'dBmV')
 _SPECTRUM_WINDOWS = ('Kaiser', 'Mil6dB', 'BlackmanHarris', 'Rectangular', 'FlatTop', 'Hann')
-_TRIGGER_MODE = ('freeRun', 'triggered')
+_TRIGGER_MODE = ('freerun', 'triggered')
 _TRIGGER_SOURCE = ('External', 'IFPowerLevel')
 _TRIGGER_TRANSITION = ('LH', 'HL', 'Either')
 
 """ CUSTOM DATA STRUCTURES """
 
+class _FreqRefUserInfo(Structure):
+    _fields_ = [('isvalid', c_bool),
+                ('dacValue', c_uint32),
+                ('datetime', c_char * _DEVINFO_MAX_STRLEN),
+                ('temperature', c_double)]
+
 
 class _SpectrumLimits(Structure):
     _fields_ = [('maxSpan', c_double),
@@ -458,6 +466,35 @@ def CONFIG_SetCenterFreq(self, cf: Union[float, int]) -> None:
         cf = RSA.check_range(cf, self.CONFIG_GetMinCenterFreq(), self.CONFIG_GetMaxCenterFreq())
         self.err_check(self.rsa.CONFIG_SetCenterFreq(c_double(cf)))
 
+    def CONFIG_DecodeFreqRefUserSettingString(self, i_usstr: str) -> dict:
+        """
+        Decode a formatted User setting string into component elements.
+
+        Parameters
+        ----------
+        i_usstr: A formatted User setting string.
+
+        Returns
+        -------
+        A dict with keys:
+            'isvalid' (bool) : True if the dict contains valid data.
+            'dacValue' (int) : Control DAC value
+            'datetime' (str) : Datetime string, formatted "YYYY-MM-DDThh:mm:ss"
+            'temperature' (float) : Device temperature when user setting data
+                was created.
+        """
+        i_usstr = c_char_p(i_usstr.encode('utf-8'))
+        o_fui = _FreqRefUserInfo()
+        self.err_check(self.rsa.CONFIG_DecodeFreqRefUserSettingString(i_usstr, byref(o_fui)))
+        # Temperature result is always 0, so manually parse the input string
+        temperature = float(i_usstr.value.decode('utf-8')[-8:-3])
+        fui = {'isvalid': o_fui.isvalid,
+               'dacValue': o_fui.dacValue,
+               'datetime': o_fui.datetime.decode('utf-8'),
+               'temperature': temperature
+        }
+        return fui
+
     def CONFIG_SetExternalRefEnable(self, ext_ref_en: bool) -> None:
         """
         Enable or disable the external reference.
@@ -499,6 +536,50 @@ def CONFIG_SetFrequencyReferenceSource(self, src: str) -> None:
         else:
             raise RSAError("Input does not match a valid setting.")
 
+    def CONFIG_GetFreqRefUserSetting(self) -> str:
+        """
+        Get the Frequency Reference User-source setting value.
+
+        Returns
+        --------
+        A formatted user setting string containing:
+        "$FRU,<devType>,<devSN>,<dacVal>,<dateTime>,<devTemp>*<CS>"
+        Where:
+            <devType> : device type
+            <devSN> : device serial number
+            <dacVal> : integer DAC value
+            <dateTime> : date and time of creation, format:
+                "YYY-MM-DDThh:mm:ss"
+            <devTemp> : device temperature (degC) at creation
+            <CS> : integer checksum of characters before '*'
+        
+        If the User setting is not valid, then the user string result
+        returns the string "Invalid User Setting"
+        """
+        o_usstr = (c_char * _FREQ_REF_USER_SETTING_STRLEN)()
+        self.err_check(self.rsa.CONFIG_GetFreqRefUserSetting(byref(o_usstr)))
+        return o_usstr.value.decode('utf-8')
+
+    def CONFIG_SetFreqRefUserSetting(self, i_usstr: Union[str, None] = None) -> None:
+        """
+        Set the Frequency Reference User-source setting value.
+
+        Parameters
+        ----------
+        i_usstr: The user setting string, which must be formatted as
+            by CONFIG_GetFreqRefUserSetting(). If this parameter is 
+            None (the default behavior), the current frequency reference
+            setting is copied to the User setting memory.
+        """
+        if i_usstr is None:
+            self.err_check(self.rsa.CONFIG_SetFreqRefUserSetting(None))
+        else:
+            RSA.check_string(i_usstr)
+            if i_usstr == 'Invalid User Setting' or len(i_usstr) != _FREQ_REF_USER_SETTING_STRLEN:
+                raise RSAError("User setting is invalid.")
+            i_usstr = c_char_p(i_usstr.encode('utf-8'))
+            self.err_check(self.rsa.CONFIG_SetFreqRefUserSetting(i_usstr))
+
     def CONFIG_SetReferenceLevel(self, ref_level: Union[float, int]) -> None:
         """
         Set the reference level
@@ -733,7 +814,6 @@ def DEVICE_GetInfo(self) -> dict:
         Returns
         -------
         dict
-            All of the above listed information as strings.
             Keys: nomenclature, serialNum, fwVersion, fpgaVersion,
                   hwVersion, apiVersion
         """
@@ -1737,7 +1817,7 @@ def TRIG_GetTriggerMode(self) -> str:
         Returns
         -------
         string
-            Either "freeRun" or "Triggered".
+            Either "freeRun" or "triggered".
         """
         global _TRIGGER_MODE
         mode = c_int()
@@ -1779,11 +1859,10 @@ def TRIG_GetTriggerTransition(self) -> str:
 
         Returns
         -------
-        string
-            Name of the trigger transition mode. Valid results:
-                LH : Trigger on low-to-high input level change.
-                HL : Trigger on high-to-low input level change.
-                Either : Trigger on either LH or HL transitions.
+        Name of the trigger transition mode. Valid results:
+            LH : Trigger on low-to-high input level change.
+            HL : Trigger on high-to-low input level change.
+            Either : Trigger on either LH or HL transitions.
         """
         global _TRIGGER_TRANSITION
         transition = c_int()
@@ -1795,7 +1874,7 @@ def TRIG_SetIFPowerTriggerLevel(self, level: Union[float, int]) -> None:
         Set the IF power detection level.
 
         Parameters
-         ----------
+        ----------
         level : float or int
             The detection power level setting for the IF power trigger
             source.
@@ -1810,10 +1889,9 @@ def TRIG_SetTriggerMode(self, mode: str) -> None:
 
         Parameters
         ----------
-        mode : string
-            The trigger mode. Valid settings:
-                freeRun : to continually gather data
-                Triggered : do not acquire new data unless triggered
+        mode : The trigger mode, case insensitive. Valid settings:
+            freeRun : to continually gather data
+            Triggered : do not acquire new data unless triggered
 
         Raises
         ------
@@ -1822,8 +1900,8 @@ def TRIG_SetTriggerMode(self, mode: str) -> None:
         """
         global _TRIGGER_MODE
         mode = RSA.check_string(mode)
-        if mode in _TRIGGER_MODE:
-            mode_value = _TRIGGER_MODE.index(mode)
+        if mode.lower() in _TRIGGER_MODE:
+            mode_value = _TRIGGER_MODE.index(mode.lower())
             self.err_check(self.rsa.TRIG_SetTriggerMode(c_int(mode_value)))
         else:
             raise RSAError("Invalid trigger mode input string.")
@@ -2183,7 +2261,7 @@ def IQSTREAM_StatusParser(iq_stream_info: _IQStreamFileInfo, exit: bool=True):
                 return status_str
 
     def SPECTRUM_Acquire(self, trace: str = 'Trace1', trace_points: int = 801,
-                         timeout_msec: int = 10) -> Tuple[np.ndarray, int]:
+                         timeout_msec: int = 50) -> Tuple[np.ndarray, int]:
         """
         Acquire spectrum trace.
 
@@ -2207,8 +2285,10 @@ def SPECTRUM_Acquire(self, trace: str = 'Trace1', trace_points: int = 801,
         """
         self.DEVICE_Run()
         self.SPECTRUM_AcquireTrace()
-        while not self.SPECTRUM_WaitForTraceReady(timeout_msec):
-            pass
+        ready = False
+        while not ready:
+            ready = self.SPECTRUM_WaitForTraceReady(timeout_msec)
+            sleep(int(timeout_msec * 1e-3))
         return self.SPECTRUM_GetTrace(trace, trace_points)
 
     def IQBLK_Configure(self, cf: Union[float, int] = 1e9, ref_level: Union[float, int] = 0,
@@ -2232,7 +2312,7 @@ def IQBLK_Configure(self, cf: Union[float, int] = 1e9, ref_level: Union[float, i
         self.IQBLK_SetIQBandwidth(iq_bw)
         self.IQBLK_SetIQRecordLength(record_length)
 
-    def IQBLK_Acquire(self, rec_len: int = 1024, timeout_ms: int = 10) -> Tuple[np.ndarray, np.ndarray]:
+    def IQBLK_Acquire(self, rec_len: int = 1024, timeout_ms: int = 50) -> Tuple[np.ndarray, np.ndarray]:
         """
         Acquire IQBLK data using IQBLK_GetIQDataDeinterleaved.
 
@@ -2252,6 +2332,53 @@ def IQBLK_Acquire(self, rec_len: int = 1024, timeout_ms: int = 10) -> Tuple[np.n
         """
         self.DEVICE_Run()
         self.IQBLK_AcquireIQData()
-        while not self.IQBLK_WaitForIQDataReady(timeout_ms):
-            pass
+        ready = False
+        while not ready:
+            ready = self.IQBLK_WaitForIQDataReady(timeout_ms)
+            sleep(int(timeout_ms * 1e-3))
         return self.IQBLK_GetIQDataDeinterleaved(req_length=rec_len)
+
+
+    def DEVICE_GetTemperature(self, unit: str = 'celsius') -> float:
+        """
+        Get the device temperature.
+
+        Parameters
+        ----------
+        unit: The unit for the returned temperature value. May
+            be any of 'celsius', 'fahrehnheit', 'kelvin', 'kelvins',
+            'c', 'f', or 'k' (case-insensitive). Defaults to 'celsius'.
+
+        Returns
+        -------
+        The device temperature in the specified units.
+        """
+        # Store previous frequency reference setting
+        old_fru = (c_char * _FREQ_REF_USER_SETTING_STRLEN)()
+        self.err_check(self.rsa.CONFIG_GetFreqRefUserSetting(byref(old_fru)))
+        old_fru = old_fru.value.decode('utf-8')
+
+        # Update frequency reference setting to update temperature value
+        self.CONFIG_SetFreqRefUserSetting(None)
+
+        # Retrieve new frequency reference setting
+        fru = self.CONFIG_GetFreqRefUserSetting()
+
+        # Restore previous frequency reference setting
+        if old_fru != 'Invalid User Setting':
+            self.CONFIG_SetFreqRefUserSetting(old_fru)
+
+        # Read back in value
+        temp_c = self.CONFIG_DecodeFreqRefUserSettingString(fru)['temperature']
+
+        # Handle unit conversion if needed
+        if unit.lower() in ['c', 'celsius']:
+            temp = temp_c
+        elif unit.lower() in ['f', 'fahrenheit']:
+            temp = (temp_c * 9. / 5.) + 32
+        elif unit.lower() in ['k', 'kelvin', 'kelvins']:
+            temp = temp_c + 273.15
+        else:
+            raise RSAError("Invalid temperature unit selection.")
+
+        return temp
diff --git a/setup.cfg b/setup.cfg
index e2e32fb..e93d531 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,11 +1,11 @@
 [metadata]
-name = tekrsa-api-wrap
-version = 1.1.0
+name = tekrsa_api_wrap
+version = 1.2.0
 author = The Institute for Telecommunication Sciences
 description = NTIA Python wrapper for Tektronix RSA API for Linux
 long_description = file: README.md
 long_description_content_type = text/markdown
-url = https://github.com/ntia/tekrsa-api-wrap
+url = https://github.com/NTIA/tekrsa-api-wrap
 project_urls =
 	Bug Tracker = https://github.com/ntia/tekrsa-api-wrap/issues
 classifiers =
@@ -13,16 +13,16 @@ classifiers =
     Intended Audience :: Science/Research
     Intended Audience :: Telecommunications Industry
     Natural Language :: English
-    Operating System :: Unix
+    Operating System :: POSIX :: Linux
     Programming Language :: Python :: 3
 
 [options]
 package_dir =
-    = src
+    = rsa_api
 packages = find:
 python_requires = >=3.7
 install_requires =
     numpy>=1.21
 
 [options.packages.find]
-where = src
+where = rsa_api
diff --git a/src/rsa_api/__init__.py b/src/rsa_api/__init__.py
deleted file mode 100644
index 406612f..0000000
--- a/src/rsa_api/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-from .rsa_api_wrap import *
\ No newline at end of file


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/rsa_api_test.py b/tests/rsa_api_test.py
deleted file mode 100644
index 25d2be2..0000000
--- a/tests/rsa_api_test.py
+++ /dev/null
@@ -1,483 +0,0 @@
-import unittest
-from os.path import isdir
-from os import mkdir
-import rsa_api
-from time import sleep
-import numpy as np
-import sys
-
-"""
-This is a test for the entire API Wrapper.
-It requires a compatible RSA device to be connected.
-"""
-
-class rsa_api_test(unittest.TestCase):
-    """Test for rsa_api_wrap.py"""
-
-    """ALIGN Command Testing"""
-
-    def test_ALIGN_GetAlignmentNeeded(self):
-        self.assertIsInstance(rsa.ALIGN_GetAlignmentNeeded(), bool)
-
-    def test_ALIGN_GetWarmupStatus(self):
-        self.assertIsInstance(rsa.ALIGN_GetWarmupStatus(), bool)
-
-    def test_ALIGN_RunAlignment(self):
-        self.assertIsNone(rsa.ALIGN_RunAlignment())
-
-    """CONFIG Command Testing"""
-
-    def test_CONFIG_CenterFreq(self):
-        cf = 2.4453e9
-        self.assertIsNone(rsa.CONFIG_SetCenterFreq(cf))
-        self.assertEqual(rsa.CONFIG_GetCenterFreq(), cf)
-
-        self.assertRaises(TypeError, rsa.CONFIG_SetCenterFreq, 'abc')
-        self.assertRaises(TypeError, rsa.CONFIG_SetCenterFreq, False)
-        self.assertRaises(ValueError, rsa.CONFIG_SetCenterFreq, 400e9)
-        self.assertRaises(ValueError, rsa.CONFIG_SetCenterFreq, -40e6)
-
-    """
-    # The following test requires an external reference to be in use.
-    def test_CONFIG_ExternalRef(self):
-        self.assertIsNone(CONFIG_SetExternalRefEnable(exRefEn=True))
-        self.assertTrue(CONFIG_GetExternalRefEnable())
-        extRefFreq = 10e6
-        self.assertEqual(CONFIG_GetExternalRefFrequency(), extRefFreq)
-        CONFIG_SetExternalRefEnable(exRefEn=False)
-        self.assertRaises(rsa_api.RSAError, CONFIG_GetExternalRefFrequency)
-    """
-
-    def test_CONFIG_FrequencyReferenceSource(self):
-        self.assertRaises(rsa_api.RSAError, rsa.CONFIG_SetFrequencyReferenceSource, 'GNSS')
-        self.assertRaises(rsa_api.RSAError, rsa.CONFIG_SetFrequencyReferenceSource, 'abc')
-        self.assertRaises(TypeError, rsa.CONFIG_SetFrequencyReferenceSource, 0)
-        self.assertIsNone(rsa.CONFIG_SetFrequencyReferenceSource('INTERNAL'))
-        self.assertIsInstance(rsa.CONFIG_GetFrequencyReferenceSource(), str)
-
-    def test_CONFIG_Preset(self):
-        self.assertIsNone(rsa.CONFIG_Preset())
-        self.assertEqual(rsa.CONFIG_GetCenterFreq(), 1.5e9)
-        self.assertEqual(rsa.CONFIG_GetReferenceLevel(), 0)
-        self.assertEqual(rsa.IQBLK_GetIQBandwidth(), 40e6)
-        self.assertEqual(rsa.IQBLK_GetIQRecordLength(), 1024)
-
-    def test_CONFIG_ReferenceLevel(self):
-        refLevel = 17
-        self.assertIsNone(rsa.CONFIG_SetReferenceLevel(refLevel))
-        self.assertEqual(rsa.CONFIG_GetReferenceLevel(), refLevel)
-        self.assertRaises(TypeError, rsa.CONFIG_SetReferenceLevel, 'abc')
-        self.assertRaises(ValueError, rsa.CONFIG_SetReferenceLevel, 31)
-        self.assertRaises(ValueError, rsa.CONFIG_SetReferenceLevel, -131)
-
-    def test_CONFIG_GetMaxCenterFreq(self):
-        global device
-        maxCf = 0
-        if device in ['RSA306B', 'RSA306']:
-            # Unsure if RSA306 (non-B variant) has same max freq.
-            maxCf = 6.2e9
-        elif device in ['RSA503A', 'RSA603A']:
-            maxCf = 3.0e9
-        elif device in ['RSA507A', 'RSA607A']:
-            maxCf = 7.5e9
-        elif device == 'RSA513A':
-            maxCf = 13.6e9
-        elif device == 'RSA518A':
-            maxCf = 18.0e9
-        self.assertEqual(rsa.CONFIG_GetMaxCenterFreq(), maxCf)
-
-    def test_CONFIG_GetMinCenterFreq(self):
-        minCf = 9e3
-        self.assertEqual(rsa.CONFIG_GetMinCenterFreq(), minCf)
-
-    def test_CONFIG_Attenuation(self):
-        # Only run test for RSA 500A/600A devices
-        global device
-        if device not in ['RSA306B', 'RSA306']:
-            self.assertIsNone(rsa.CONFIG_SetAutoAttenuationEnable(True))
-            self.assertTrue(rsa.CONFIG_GetAutoAttenuationEnable())
-            self.assertIsNone(rsa.CONFIG_SetAutoAttenuationEnable(False))
-            self.assertFalse(rsa.CONFIG_GetAutoAttenuationEnable())
-            atten_setting = -25
-            self.assertIsNone(rsa.CONFIG_SetRFAttenuator(atten_setting))
-            self.assertEqual(rsa.CONFIG_GetRFAttenuator(), atten_setting)
-
-    def test_CONFIG_Preamp(self):
-        # Only run test for RSA 500A/600A devices.
-        global device
-        if device not in ['RSA306B', 'RSA306']:
-            self.assertIsNone(rsa.CONFIG_SetRFPreampEnable(False))
-            rsa.DEVICE_Stop()
-            rsa.CONFIG_SetAutoAttenuationEnable(True)
-            rsa.DEVICE_Run()
-            self.assertFalse(rsa.CONFIG_GetRFPreampEnable())
-            self.assertIsNone(rsa.CONFIG_SetRFPreampEnable(True))
-            rsa.DEVICE_Stop()
-            rsa.DEVICE_Run()
-            self.assertTrue(rsa.CONFIG_GetRFPreampEnable())
-
-    """DEVICE Command Testing"""
-
-    def test_DEVICE_Connect(self):
-        # Stop and disconnect device first
-        rsa.DEVICE_Stop()
-        self.assertIsNone(rsa.DEVICE_Disconnect())
-        self.assertRaises(TypeError, rsa.DEVICE_Connect, 'abc')
-        self.assertRaises(ValueError, rsa.DEVICE_Connect, neg)
-        self.assertIsNone(rsa.DEVICE_Connect(0))
-
-    def test_DEVICE_PrepareForRun(self):
-        self.assertIsNone(rsa.DEVICE_PrepareForRun())
-
-    def test_DEVICE_Run(self):
-        self.assertIsNone(rsa.DEVICE_Run())
-        self.assertTrue(rsa.DEVICE_GetEnable())
-
-    def test_DEVICE_Stop(self):
-        self.assertIsNone(rsa.DEVICE_Stop())
-        self.assertFalse(rsa.DEVICE_GetEnable())
-
-    def test_DEVICE_GetEventStatus_no_signal(self):
-        eventType = ['OVERRANGE', 'TRIGGER', '1PPS']
-        for e in eventType:
-            event, timestamp = rsa.DEVICE_GetEventStatus(e)
-            self.assertFalse(event)
-            self.assertEqual(timestamp, 0)
-        self.assertRaises(TypeError, rsa.DEVICE_GetEventStatus, 0)
-        self.assertRaises(rsa_api.RSAError, rsa.DEVICE_GetEventStatus, 'abc')
-
-    def test_DEVICE_GetEventStatus_trig_event(self):
-        rsa.DEVICE_Run()
-        rsa.TRIG_ForceTrigger()
-        sleep(0.05)
-        trig, trigTs = rsa.DEVICE_GetEventStatus('TRIGGER')
-        self.assertTrue(trig)
-        self.assertGreater(trigTs, 0)
-
-    """
-    # The following test would require an actual overrange event.
-    def test_DEVICE_GetEventStatus_overrange(self):
-        pass
-    """
-
-    def test_DEVICE_GetOverTemperatureStatus(self):
-        self.assertIsInstance(rsa.DEVICE_GetOverTemperatureStatus(), bool)
-        self.assertEqual(rsa.DEVICE_GetOverTemperatureStatus(), False)
-
-    def test_DEVICE_GetNomenclature(self):
-        self.assertIsInstance(rsa.DEVICE_GetNomenclature(), str)
-
-    def test_DEVICE_GetSerialNumber(self):
-        sn = rsa.DEVICE_GetSerialNumber()
-        self.assertIsInstance(sn, str)
-        self.assertEqual(len(sn), 7)
-
-    def test_DEVICE_GetAPIVersion(self):
-        # This uses the Linux API version number
-        self.assertEqual(rsa.DEVICE_GetAPIVersion(), '1.0.0014')
-
-    def test_DEVICE_GetFWVersion(self):
-        self.assertIsInstance(rsa.DEVICE_GetFWVersion(), str)
-
-    def test_DEVICE_GetFPGAVersion(self):
-        self.assertIsInstance(rsa.DEVICE_GetFPGAVersion(), str)
-
-    def test_DEVICE_GetHWVersion(self):
-        self.assertIsInstance(rsa.DEVICE_GetHWVersion(), str)
-
-    def test_DEVICE_GetInfo(self):
-        info = rsa.DEVICE_GetInfo()
-        self.assertIsInstance(info, dict)
-        self.assertEqual(len(info), 6)
-        self.assertEqual(len(info['serialNum']), 7)
-        self.assertEqual(info['apiVersion'], '1.0.0014')
-        self.assertIsInstance(info['fwVersion'], str)
-        self.assertIsInstance(info['fpgaVersion'], str)
-        self.assertIsInstance(info['hwVersion'], str)
-
-    """IQBLK Command Testing"""
-
-    def test_IQBLK_MinMaxIQBandwidth(self):
-        maxBw = rsa.IQBLK_GetMaxIQBandwidth()
-        minBw = rsa.IQBLK_GetMinIQBandwidth()
-        rsa.IQBLK_SetIQBandwidth(maxBw)  # To get maxRl properly
-        maxRl = rsa.IQBLK_GetMaxIQRecordLength()
-        self.assertEqual(maxBw, 40e6)
-        self.assertEqual(minBw, 100)
-        self.assertEqual(maxRl, 126000000)
-
-    def test_IQBLK_IQBandwidth(self):
-        iqBw = 20e6
-        self.assertIsNone(rsa.IQBLK_SetIQBandwidth(iqBw))
-        self.assertEqual(iqBw, rsa.IQBLK_GetIQBandwidth())
-        self.assertRaises(ValueError, rsa.IQBLK_SetIQBandwidth, neg)
-        self.assertRaises(ValueError, rsa.IQBLK_SetIQBandwidth, 100e6)
-        self.assertRaises(TypeError, rsa.IQBLK_SetIQBandwidth, 'abc')
-
-    def test_IQBLK_IQRecordLength(self):
-        iqRl = 8192
-        self.assertIsNone(rsa.IQBLK_SetIQRecordLength(iqRl))
-        self.assertEqual(iqRl, rsa.IQBLK_GetIQRecordLength())
-        self.assertRaises(ValueError, rsa.IQBLK_SetIQRecordLength, neg)
-        self.assertRaises(ValueError, rsa.IQBLK_SetIQRecordLength, 200e6)
-        self.assertRaises(TypeError, rsa.IQBLK_SetIQRecordLength, 'abc')
-
-    def test_IQBLK_GetSampleRate(self):
-        self.assertIsInstance(rsa.IQBLK_GetIQSampleRate(), float)
-
-    def test_IQBLK_GetIQData(self):
-        rl = 1000
-        rsa.IQBLK_Configure()  # Configure to defaults
-        i, q = rsa.IQBLK_Acquire(rl, 10)
-        self.assertEqual(len(i), rl)
-        self.assertEqual(len(q), rl)
-
-        self.assertRaises(ValueError, rsa.IQBLK_Acquire, rec_len=neg)
-        self.assertRaises(ValueError, rsa.IQBLK_Acquire, rec_len=200000000)
-        self.assertRaises(TypeError, rsa.IQBLK_Acquire, rec_len='abc')
-
-    """IQSTREAM Command Testing"""
-
-    def test_IQSTREAM_MinMaxIQBandwidth(self):
-        minBandwidthHz = rsa.IQSTREAM_GetMinAcqBandwidth()
-        maxBandwidthHz = rsa.IQSTREAM_GetMaxAcqBandwidth()
-        self.assertEqual(minBandwidthHz, 9765.625)
-        self.assertEqual(maxBandwidthHz, 40e6)
-
-    def test_IQSTREAM_AcqBandwidth(self):
-        bwHz_req = [40e6, 20e6, 10e6, 5e6, 2.5e6, 1.25e6, 625e3, 312.5e3,
-                    156.25e3, 78125, 39062.5, 19531.25, 9765.625]
-        srSps_req = [56e6, 28e6, 14e6, 7e6, 3.5e6, 1.75e6, 875e3,
-                     437.5e3, 218.75e3, 109.375e3, 54687.5, 27343.75,
-                     13671.875]
-        baseSize = [65536, 65536, 65536, 65536, 65536, 32768, 16384, 8192,
-                    4096, 2048, 1024, 512, 256, 128]
-        for b, s, r in zip(bwHz_req, srSps_req, baseSize):
-            self.assertIsNone(rsa.IQSTREAM_SetAcqBandwidth(b))
-            bwHz_act, srSps = rsa.IQSTREAM_GetAcqParameters()
-            self.assertEqual(bwHz_act, b)
-            self.assertEqual(srSps, s)
-            self.assertIsNone(rsa.IQSTREAM_SetIQDataBufferSize(r))
-            self.assertEqual(rsa.IQSTREAM_GetIQDataBufferSize(), r)
-
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetAcqBandwidth, 'abc')
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetAcqBandwidth, [num])
-        self.assertRaises(ValueError, rsa.IQSTREAM_SetAcqBandwidth, 41e6)
-
-    def test_IQSTREAM_SetOutputConfiguration(self):
-        dest = ['CLIENT', 'FILE_TIQ', 'FILE_SIQ', 'FILE_SIQ_SPLIT']
-        dtype = ['SINGLE', 'INT32', 'INT16', 'SINGLE_SCALE_INT32']
-
-        for d in dest:
-            for t in dtype:
-                if d == 'FILE_TIQ' and 'SINGLE' in t:
-                    self.assertRaises(rsa_api.RSAError,
-                                      rsa.IQSTREAM_SetOutputConfiguration, d, t)
-                else:
-                    self.assertIsNone(rsa.IQSTREAM_SetOutputConfiguration(d, t))
-
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetOutputConfiguration,
-                          num, dtype[0])
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetOutputConfiguration,
-                          dest[0], num)
-        self.assertRaises(rsa_api.RSAError, rsa.IQSTREAM_SetOutputConfiguration, 'a', 'SINGLE')
-        self.assertRaises(rsa_api.RSAError, rsa.IQSTREAM_SetOutputConfiguration, 'CLIENT', 'a')
-
-    def test_IQSTREAM_SetDiskFilenameBase(self):
-        path = '/tmp/rsa_api_unittest'
-        if not isdir(path):
-            mkdir(path)
-        filename = 'iqstream_test'
-        filenameBase = path + filename
-        self.assertIsNone(rsa.IQSTREAM_SetDiskFilenameBase(filenameBase))
-
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetDiskFilenameBase, num)
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetDiskFilenameBase, b'abc')
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetDiskFilenameBase, [num])
-
-    def test_IQSTREAM_SetDiskFilenameSuffix(self):
-        suffixCtl = [0, -1, -2]
-        for s in suffixCtl:
-            self.assertIsNone(rsa.IQSTREAM_SetDiskFilenameSuffix(s))
-
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetDiskFilenameSuffix, 'abc')
-        self.assertRaises(ValueError, rsa.IQSTREAM_SetDiskFilenameSuffix, neg)
-
-    def test_IQSTREAM_SetDiskFileLength(self):
-        length = 100
-        self.assertIsNone(rsa.IQSTREAM_SetDiskFileLength(length))
-        self.assertRaises(TypeError, rsa.IQSTREAM_SetDiskFileLength, 'abc')
-        self.assertRaises(ValueError, rsa.IQSTREAM_SetDiskFileLength, neg)
-
-    def test_IQSTREAM_Operation(self):
-        rsa.IQSTREAM_SetAcqBandwidth(5e6)
-        rsa.IQSTREAM_SetOutputConfiguration('CLIENT', 'INT16')
-        rsa.IQSTREAM_GetAcqParameters()
-        rsa.DEVICE_Run()
-
-        self.assertIsNone(rsa.IQSTREAM_Start())
-        self.assertTrue(rsa.IQSTREAM_GetEnable())
-
-        self.assertIsNone(rsa.IQSTREAM_Stop())
-        self.assertFalse(rsa.IQSTREAM_GetEnable())
-
-        rsa.DEVICE_Stop()
-
-    def test_IQSTREAM_ClearAcqStatus(self):
-        self.assertIsNone(rsa.IQSTREAM_ClearAcqStatus())
-
-    """TRIG Command Testing"""
-
-    def test_TRIG_TriggerMode(self):
-        mode = ["freeRun", "triggered"]
-        for m in mode:
-            self.assertIsNone(rsa.TRIG_SetTriggerMode(m))
-            self.assertEqual(rsa.TRIG_GetTriggerMode(), m)
-
-    def test_TRIG_TriggerSource(self):
-        source = ["External", "IFPowerLevel"]
-        for s in source:
-            self.assertIsNone(rsa.TRIG_SetTriggerSource(s))
-            self.assertEqual(rsa.TRIG_GetTriggerSource(), s)
-
-    def test_TRIG_TriggerTransition(self):
-        trans = ["LH", "HL", "Either"]
-        for t in trans:
-            self.assertIsNone(rsa.TRIG_SetTriggerTransition(t))
-            self.assertEqual(rsa.TRIG_GetTriggerTransition(), t)
-        self.assertRaises(TypeError, rsa.TRIG_SetTriggerTransition, 0)
-
-    def test_TRIG_IFPowerTriggerLevel(self):
-        trigLevel = -10
-        self.assertIsNone(rsa.TRIG_SetIFPowerTriggerLevel(trigLevel))
-        self.assertEqual(rsa.TRIG_GetIFPowerTriggerLevel(), trigLevel)
-        self.assertRaises(TypeError, rsa.TRIG_SetIFPowerTriggerLevel, 'trigger')
-        self.assertRaises(ValueError, rsa.TRIG_SetIFPowerTriggerLevel, 31)
-        self.assertRaises(ValueError, rsa.TRIG_SetIFPowerTriggerLevel, -131)
-
-    def test_TRIG_TriggerPositionPercent(self):
-        self.assertRaises(ValueError, rsa.TRIG_SetTriggerPositionPercent, 0.5)
-        self.assertRaises(ValueError, rsa.TRIG_SetTriggerPositionPercent, 100)
-        self.assertRaises(TypeError, rsa.TRIG_SetTriggerPositionPercent, 'abc')
-
-        pos = 20
-        self.assertIsNone(rsa.TRIG_SetTriggerPositionPercent(pos))
-        self.assertEqual(rsa.TRIG_GetTriggerPositionPercent(), pos)
-
-    def test_TRIG_ForceTrigger(self):
-        self.assertIsNone(rsa.TRIG_ForceTrigger())
-
-    """SPECTRUM Command Testing"""
-
-    def test_SPECTRUM_Enable(self):
-        enable = [False, True]
-        for e in enable:
-            self.assertIsNone(rsa.SPECTRUM_SetEnable(e))
-            self.assertEqual(rsa.SPECTRUM_GetEnable(), e)
-
-    def test_SPECTRUM_Settings(self):
-        self.assertIsNone(rsa.SPECTRUM_SetDefault())
-
-        span = 20e6
-        rbw = 100e3
-        enableVBW = True
-        vbw = 50e3
-        traceLength = 1601
-        window = 'Hann'
-        verticalUnit = 'dBm'
-        self.assertIsNone(rsa.SPECTRUM_SetSettings(span, rbw, enableVBW, vbw,
-                                                   traceLength, window,
-                                                   verticalUnit))
-        settings = rsa.SPECTRUM_GetSettings()
-        self.assertIsInstance(settings, dict)
-        self.assertEqual(len(settings), 13)
-        self.assertEqual(settings['span'], span)
-        self.assertEqual(settings['rbw'], rbw)
-        self.assertEqual(settings['enableVBW'], enableVBW)
-        self.assertEqual(settings['vbw'], vbw)
-        self.assertEqual(settings['window'], window)
-        self.assertEqual(settings['traceLength'], traceLength)
-        self.assertEqual(settings['verticalUnit'], verticalUnit)
-
-        self.assertRaises(TypeError, rsa.SPECTRUM_SetSettings, 'span', 'rbw',
-                          'enableVBW', 'vbw', 'traceLength',
-                          1, 0)
-
-    def test_SPECTRUM_TraceType(self):
-        trace = 'Trace2'
-        enable = True
-        detector = 'AverageVRMS'
-        self.assertIsNone(rsa.SPECTRUM_SetTraceType(trace, enable, detector))
-        o_enable, o_detector = rsa.SPECTRUM_GetTraceType(trace)
-        self.assertEqual(enable, o_enable)
-        self.assertEqual(detector, o_detector)
-
-        self.assertRaises(rsa_api.RSAError, rsa.SPECTRUM_SetTraceType, trace='abc')
-        self.assertRaises(TypeError, rsa.SPECTRUM_SetTraceType, trace=40e5)
-        self.assertRaises(rsa_api.RSAError, rsa.SPECTRUM_SetTraceType,
-                          detector='abc')
-        self.assertRaises(TypeError, rsa.SPECTRUM_SetTraceType, detector=40e5)
-
-    def test_SPECTRUM_GetLimits(self):
-        global device
-        if device in ['RSA306B', 'RSA306']:
-            maxSpan = 6.2e9
-        elif device in ['RSA503A', 'RSA603A']:
-            maxSpan = 3.0e9
-        elif device in ['RSA507A', 'RSA607A']:
-            maxSpan = 7.5e9
-        elif device == 'RSA513A':
-            maxSpan = 13.6e9
-        elif device == 'RSA518A':
-            maxSpan = 18.0e9
-
-        limits = rsa.SPECTRUM_GetLimits()
-        self.assertIsInstance(limits, dict)
-        self.assertEqual(len(limits), 8)
-        self.assertEqual(limits['maxSpan'], maxSpan)
-        self.assertEqual(limits['minSpan'], 1e3)
-        self.assertEqual(limits['maxRBW'], 10e6)
-        self.assertEqual(limits['minRBW'], 10)
-        self.assertEqual(limits['maxVBW'], 10e6)
-        self.assertEqual(limits['minVBW'], 1)
-        self.assertEqual(limits['maxTraceLength'], 64001)
-        self.assertEqual(limits['minTraceLength'], 801)
-
-    def test_SPECTRUM_Acquire(self):
-        rsa.SPECTRUM_SetEnable(True)
-        span = 20e6
-        rbw = 100e3
-        enableVBW = True
-        vbw = 50e3
-        traceLength = 1601
-        window = 'Hann'
-        verticalUnit = 'dBm'
-        rsa.SPECTRUM_SetSettings(span, rbw, enableVBW, vbw, traceLength, window,
-                                 verticalUnit)
-        spectrum, outTracePoints = rsa.SPECTRUM_Acquire(trace='Trace1',
-                                                        trace_points=traceLength)
-        self.assertEqual(len(spectrum), traceLength)
-        self.assertIsInstance(spectrum, np.ndarray)
-        self.assertRaises(TypeError, rsa.SPECTRUM_Acquire, trace=1)
-
-        traceInfo = rsa.SPECTRUM_GetTraceInfo()
-        self.assertIsInstance(traceInfo, dict)
-        self.assertEqual(len(traceInfo), 2)
-
-
-if __name__ == '__main__':
-    """There must be a connected RSA device in order to test."""
-    TEST_SO_DIR = sys.argv[1]
-    del sys.argv[1:]
-    rsa = rsa_api.RSA(so_dir=TEST_SO_DIR)
-    rsa.DEVICE_Connect(0)
-    device = rsa.DEVICE_GetNomenclature()
-
-    # Some values used in testing
-    num = 400
-    neg = -400
-    unittest.main()
-
-    # Test cleanup
-    rsa.DEVICE_Stop()
-    rsa.DEVICE_Disconnect()
diff --git a/tests/test_rsa_api.py b/tests/test_rsa_api.py
new file mode 100644
index 0000000..0e3b9aa
--- /dev/null
+++ b/tests/test_rsa_api.py
@@ -0,0 +1,487 @@
+import unittest
+import numpy as np
+from os.path import isdir
+from os import mkdir, environ
+from time import sleep
+
+import rsa_api
+
+"""
+This is a test for the entire API Wrapper.
+It requires a compatible RSA device to be connected.
+"""
+
+class rsa_api_test(unittest.TestCase):
+    """Test for rsa_api_wrap.py"""
+
+    @classmethod
+    def setUpClass(cls):
+        cls.rsa = rsa_api.RSA(so_dir=environ["SO_DIR"])
+        cls.rsa.DEVICE_Connect(0)
+        cls.device = cls.rsa.DEVICE_GetNomenclature()
+        cls.num = 400
+        cls.neg = -400
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.rsa.DEVICE_Disconnect()
+        
+    """ALIGN Command Testing"""
+
+    def test_ALIGN_GetAlignmentNeeded(self):
+        self.assertIsInstance(self.rsa.ALIGN_GetAlignmentNeeded(), bool)
+
+    def test_ALIGN_GetWarmupStatus(self):
+        self.assertIsInstance(self.rsa.ALIGN_GetWarmupStatus(), bool)
+
+    def test_ALIGN_RunAlignment(self):
+        self.assertIsNone(self.rsa.ALIGN_RunAlignment())
+
+    """CONFIG Command Testing"""
+
+    def test_CONFIG_CenterFreq(self):
+        cf = 2.4453e9
+        self.assertIsNone(self.rsa.CONFIG_SetCenterFreq(cf))
+        self.assertEqual(self.rsa.CONFIG_GetCenterFreq(), cf)
+
+        self.assertRaises(TypeError, self.rsa.CONFIG_SetCenterFreq, 'abc')
+        self.assertRaises(TypeError, self.rsa.CONFIG_SetCenterFreq, False)
+        self.assertRaises(ValueError, self.rsa.CONFIG_SetCenterFreq, 400e9)
+        self.assertRaises(ValueError, self.rsa.CONFIG_SetCenterFreq, -40e6)
+
+    """
+    # The following test requires an external reference to be in use.
+    def test_CONFIG_ExternalRef(self):
+        self.assertIsNone(CONFIG_SetExternalRefEnable(exRefEn=True))
+        self.assertTrue(CONFIG_GetExternalRefEnable())
+        extRefFreq = 10e6
+        self.assertEqual(CONFIG_GetExternalRefFrequency(), extRefFreq)
+        CONFIG_SetExternalRefEnable(exRefEn=False)
+        self.assertRaises(rsa_api.RSAError, CONFIG_GetExternalRefFrequency)
+    """
+
+    def test_CONFIG_FrequencyReferenceSource(self):
+        if self.device in ['RSA306B', 'RSA306']:
+            self.assertRaises(rsa_api.RSAError, self.rsa.CONFIG_SetFrequencyReferenceSource, 'GNSS')
+        self.assertRaises(rsa_api.RSAError, self.rsa.CONFIG_SetFrequencyReferenceSource, 'abc')
+        self.assertRaises(TypeError, self.rsa.CONFIG_SetFrequencyReferenceSource, 0)
+        self.assertIsNone(self.rsa.CONFIG_SetFrequencyReferenceSource('INTERNAL'))
+        self.assertIsInstance(self.rsa.CONFIG_GetFrequencyReferenceSource(), str)
+
+    def test_CONFIG_FreqRefUserSetting(self):
+        self.assertIsInstance(self.rsa.CONFIG_GetFreqRefUserSetting(), str)
+        self.assertRaises(TypeError, self.rsa.CONFIG_SetFreqRefUserSetting, 0)
+        self.assertRaises(rsa_api.RSAError, self.rsa.CONFIG_SetFreqRefUserSetting, 'Invalid User Setting')
+        self.assertRaises(rsa_api.RSAError, self.rsa.CONFIG_SetFreqRefUserSetting, 'incorrect length input')
+        self.assertIsNone(self.rsa.CONFIG_SetFreqRefUserSetting(None))
+        self.assertIsInstance(self.rsa.CONFIG_DecodeFreqRefUserSettingString(self.rsa.CONFIG_GetFreqRefUserSetting()), dict)
+
+    def test_CONFIG_Preset(self):
+        self.assertIsNone(self.rsa.CONFIG_Preset())
+        self.assertEqual(self.rsa.CONFIG_GetCenterFreq(), 1.5e9)
+        self.assertEqual(self.rsa.CONFIG_GetReferenceLevel(), 0)
+        self.assertEqual(self.rsa.IQBLK_GetIQBandwidth(), 40e6)
+        self.assertEqual(self.rsa.IQBLK_GetIQRecordLength(), 1024)
+
+    def test_CONFIG_ReferenceLevel(self):
+        refLevel = 17
+        self.assertIsNone(self.rsa.CONFIG_SetReferenceLevel(refLevel))
+        self.assertEqual(self.rsa.CONFIG_GetReferenceLevel(), refLevel)
+        self.assertRaises(TypeError, self.rsa.CONFIG_SetReferenceLevel, 'abc')
+        self.assertRaises(ValueError, self.rsa.CONFIG_SetReferenceLevel, 31)
+        self.assertRaises(ValueError, self.rsa.CONFIG_SetReferenceLevel, -131)
+
+    def test_CONFIG_GetMaxCenterFreq(self):
+        maxCf = 0
+        if self.device in ['RSA306B', 'RSA306']:
+            # Unsure if RSA306 (non-B variant) has same max freq.
+            maxCf = 6.2e9
+        elif self.device in ['RSA503A', 'RSA603A']:
+            maxCf = 3.0e9
+        elif self.device in ['RSA507A', 'RSA607A']:
+            maxCf = 7.5e9
+        elif self.device == 'RSA513A':
+            maxCf = 13.6e9
+        elif self.device == 'RSA518A':
+            maxCf = 18.0e9
+        self.assertEqual(self.rsa.CONFIG_GetMaxCenterFreq(), maxCf)
+
+    def test_CONFIG_GetMinCenterFreq(self):
+        minCf = 9e3
+        self.assertEqual(self.rsa.CONFIG_GetMinCenterFreq(), minCf)
+
+    def test_CONFIG_Attenuation(self):
+        # Only run test for RSA 500A/600A devices
+        if self.device not in ['RSA306B', 'RSA306']:
+            self.assertIsNone(self.rsa.CONFIG_SetAutoAttenuationEnable(True))
+            self.assertTrue(self.rsa.CONFIG_GetAutoAttenuationEnable())
+            self.assertIsNone(self.rsa.CONFIG_SetAutoAttenuationEnable(False))
+            self.assertFalse(self.rsa.CONFIG_GetAutoAttenuationEnable())
+            atten_setting = -25
+            self.assertIsNone(self.rsa.CONFIG_SetRFAttenuator(atten_setting))
+            self.assertEqual(self.rsa.CONFIG_GetRFAttenuator(), atten_setting)
+
+    def test_CONFIG_Preamp(self):
+        # Only run test for RSA 500A/600A devices.
+        if self.device not in ['RSA306B', 'RSA306']:
+            self.assertIsNone(self.rsa.CONFIG_SetRFPreampEnable(False))
+            self.rsa.DEVICE_Stop()
+            self.rsa.CONFIG_SetAutoAttenuationEnable(True)
+            self.rsa.DEVICE_Run()
+            self.assertFalse(self.rsa.CONFIG_GetRFPreampEnable())
+            self.assertIsNone(self.rsa.CONFIG_SetRFPreampEnable(True))
+            self.rsa.DEVICE_Stop()
+            self.rsa.DEVICE_Run()
+            self.assertTrue(self.rsa.CONFIG_GetRFPreampEnable())
+
+    """DEVICE Command Testing"""
+
+    def test_DEVICE_Connect(self):
+        # Stop and disconnect device first
+        self.rsa.DEVICE_Stop()
+        self.assertIsNone(self.rsa.DEVICE_Disconnect())
+        self.assertRaises(TypeError, self.rsa.DEVICE_Connect, 'abc')
+        self.assertRaises(ValueError, self.rsa.DEVICE_Connect, self.neg)
+        self.assertIsNone(self.rsa.DEVICE_Connect(0))
+
+    def test_DEVICE_PrepareForRun(self):
+        self.assertIsNone(self.rsa.DEVICE_PrepareForRun())
+
+    def test_DEVICE_Run(self):
+        self.assertIsNone(self.rsa.DEVICE_Run())
+        self.assertTrue(self.rsa.DEVICE_GetEnable())
+
+    def test_DEVICE_Stop(self):
+        self.assertIsNone(self.rsa.DEVICE_Stop())
+        self.assertFalse(self.rsa.DEVICE_GetEnable())
+
+    def test_DEVICE_GetEventStatus_no_signal(self):
+        eventType = ['OVERRANGE', 'TRIGGER', '1PPS']
+        for e in eventType:
+            event, timestamp = self.rsa.DEVICE_GetEventStatus(e)
+            self.assertFalse(event)
+            self.assertEqual(timestamp, 0)
+        self.assertRaises(TypeError, self.rsa.DEVICE_GetEventStatus, 0)
+        self.assertRaises(rsa_api.RSAError, self.rsa.DEVICE_GetEventStatus, 'abc')
+
+    def test_DEVICE_GetEventStatus_trig_event(self):
+        self.rsa.DEVICE_Stop()
+        # Setup trigger mode
+        self.rsa.TRIG_SetTriggerMode('triggered')
+        self.rsa.TRIG_SetTriggerSource('IFPowerLevel')
+        self.rsa.TRIG_SetTriggerTransition('Either')
+        self.rsa.TRIG_SetIFPowerTriggerLevel(0)
+        self.rsa.DEVICE_Run()
+        self.rsa.TRIG_ForceTrigger()
+        sleep(0.5)
+        trig, trigTs = self.rsa.DEVICE_GetEventStatus('TRIGGER')
+        self.assertTrue(trig)
+        self.assertGreater(trigTs, 0)
+
+    """
+    # The following test would require an actual overrange event.
+    def test_DEVICE_GetEventStatus_overrange(self):
+        pass
+    """
+
+    def test_DEVICE_GetOverTemperatureStatus(self):
+        self.assertIsInstance(self.rsa.DEVICE_GetOverTemperatureStatus(), bool)
+        self.assertEqual(self.rsa.DEVICE_GetOverTemperatureStatus(), False)
+
+    def test_DEVICE_GetNomenclature(self):
+        self.assertIsInstance(self.rsa.DEVICE_GetNomenclature(), str)
+
+    def test_DEVICE_GetSerialNumber(self):
+        sn = self.rsa.DEVICE_GetSerialNumber()
+        self.assertIsInstance(sn, str)
+        self.assertEqual(len(sn), 7)
+
+    def test_DEVICE_GetAPIVersion(self):
+        # This uses the Linux API version number
+        self.assertEqual(self.rsa.DEVICE_GetAPIVersion(), '1.0.0014')
+
+    def test_DEVICE_GetFWVersion(self):
+        self.assertIsInstance(self.rsa.DEVICE_GetFWVersion(), str)
+
+    def test_DEVICE_GetFPGAVersion(self):
+        self.assertIsInstance(self.rsa.DEVICE_GetFPGAVersion(), str)
+
+    def test_DEVICE_GetHWVersion(self):
+        self.assertIsInstance(self.rsa.DEVICE_GetHWVersion(), str)
+
+    def test_DEVICE_GetInfo(self):
+        info = self.rsa.DEVICE_GetInfo()
+        self.assertIsInstance(info, dict)
+        self.assertEqual(len(info), 6)
+        self.assertEqual(len(info['serialNum']), 7)
+        self.assertEqual(info['apiVersion'], '1.0.0014')
+        self.assertIsInstance(info['fwVersion'], str)
+        self.assertIsInstance(info['fpgaVersion'], str)
+        self.assertIsInstance(info['hwVersion'], str)
+
+    """IQBLK Command Testing"""
+
+    def test_IQBLK_MinMaxIQBandwidth(self):
+        maxBw = self.rsa.IQBLK_GetMaxIQBandwidth()
+        minBw = self.rsa.IQBLK_GetMinIQBandwidth()
+        self.rsa.IQBLK_SetIQBandwidth(maxBw)  # To get maxRl properly
+        maxRl = self.rsa.IQBLK_GetMaxIQRecordLength()
+        self.assertEqual(maxBw, 40e6)
+        self.assertEqual(minBw, 100)
+        self.assertEqual(maxRl, 126000000)
+
+    def test_IQBLK_IQBandwidth(self):
+        iqBw = 20e6
+        self.assertIsNone(self.rsa.IQBLK_SetIQBandwidth(iqBw))
+        self.assertEqual(iqBw, self.rsa.IQBLK_GetIQBandwidth())
+        self.assertRaises(ValueError, self.rsa.IQBLK_SetIQBandwidth, self.neg)
+        self.assertRaises(ValueError, self.rsa.IQBLK_SetIQBandwidth, 100e6)
+        self.assertRaises(TypeError, self.rsa.IQBLK_SetIQBandwidth, 'abc')
+
+    def test_IQBLK_IQRecordLength(self):
+        iqRl = 8192
+        self.assertIsNone(self.rsa.IQBLK_SetIQRecordLength(iqRl))
+        self.assertEqual(iqRl, self.rsa.IQBLK_GetIQRecordLength())
+        self.assertRaises(ValueError, self.rsa.IQBLK_SetIQRecordLength, self.neg)
+        self.assertRaises(ValueError, self.rsa.IQBLK_SetIQRecordLength, 200e6)
+        self.assertRaises(TypeError, self.rsa.IQBLK_SetIQRecordLength, 'abc')
+
+    def test_IQBLK_GetSampleRate(self):
+        self.assertIsInstance(self.rsa.IQBLK_GetIQSampleRate(), float)
+
+    def test_IQBLK_GetIQData(self):
+        self.rsa.IQBLK_Configure()  # Configure to defaults
+        i, q = self.rsa.IQBLK_Acquire()
+        self.assertEqual(len(i), 1024)
+        self.assertEqual(len(q), 1024)
+
+        self.assertRaises(ValueError, self.rsa.IQBLK_Acquire, rec_len=self.neg)
+        self.assertRaises(ValueError, self.rsa.IQBLK_Acquire, rec_len=200000000)
+        self.assertRaises(TypeError, self.rsa.IQBLK_Acquire, rec_len='abc')
+
+    """IQSTREAM Command Testing"""
+
+    def test_IQSTREAM_MinMaxIQBandwidth(self):
+        minBandwidthHz = self.rsa.IQSTREAM_GetMinAcqBandwidth()
+        maxBandwidthHz = self.rsa.IQSTREAM_GetMaxAcqBandwidth()
+        self.assertEqual(minBandwidthHz, 9765.625)
+        self.assertEqual(maxBandwidthHz, 40e6)
+
+    def test_IQSTREAM_AcqBandwidth(self):
+        bwHz_req = [40e6, 20e6, 10e6, 5e6, 2.5e6, 1.25e6, 625e3, 312.5e3,
+                    156.25e3, 78125, 39062.5, 19531.25, 9765.625]
+        srSps_req = [56e6, 28e6, 14e6, 7e6, 3.5e6, 1.75e6, 875e3,
+                     437.5e3, 218.75e3, 109.375e3, 54687.5, 27343.75,
+                     13671.875]
+        baseSize = [65536, 65536, 65536, 65536, 65536, 32768, 16384, 8192,
+                    4096, 2048, 1024, 512, 256, 128]
+        for b, s, r in zip(bwHz_req, srSps_req, baseSize):
+            self.assertIsNone(self.rsa.IQSTREAM_SetAcqBandwidth(b))
+            bwHz_act, srSps = self.rsa.IQSTREAM_GetAcqParameters()
+            self.assertEqual(bwHz_act, b)
+            self.assertEqual(srSps, s)
+            self.assertIsNone(self.rsa.IQSTREAM_SetIQDataBufferSize(r))
+            self.assertEqual(self.rsa.IQSTREAM_GetIQDataBufferSize(), r)
+
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetAcqBandwidth, 'abc')
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetAcqBandwidth, [self.num])
+        self.assertRaises(ValueError, self.rsa.IQSTREAM_SetAcqBandwidth, 41e6)
+
+    def test_IQSTREAM_SetOutputConfiguration(self):
+        dest = ['CLIENT', 'FILE_TIQ', 'FILE_SIQ', 'FILE_SIQ_SPLIT']
+        dtype = ['SINGLE', 'INT32', 'INT16', 'SINGLE_SCALE_INT32']
+
+        for d in dest:
+            for t in dtype:
+                if d == 'FILE_TIQ' and 'SINGLE' in t:
+                    self.assertRaises(rsa_api.RSAError,
+                                      self.rsa.IQSTREAM_SetOutputConfiguration, d, t)
+                else:
+                    self.assertIsNone(self.rsa.IQSTREAM_SetOutputConfiguration(d, t))
+
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetOutputConfiguration,
+                          self.num, dtype[0])
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetOutputConfiguration,
+                          dest[0], self.num)
+        self.assertRaises(rsa_api.RSAError, self.rsa.IQSTREAM_SetOutputConfiguration, 'a', 'SINGLE')
+        self.assertRaises(rsa_api.RSAError, self.rsa.IQSTREAM_SetOutputConfiguration, 'CLIENT', 'a')
+
+    def test_IQSTREAM_SetDiskFilenameBase(self):
+        path = '/tmp/rsa_api_unittest'
+        if not isdir(path):
+            mkdir(path)
+        filename = 'iqstream_test'
+        filenameBase = path + filename
+        self.assertIsNone(self.rsa.IQSTREAM_SetDiskFilenameBase(filenameBase))
+
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetDiskFilenameBase, self.num)
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetDiskFilenameBase, b'abc')
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetDiskFilenameBase, [self.num])
+
+    def test_IQSTREAM_SetDiskFilenameSuffix(self):
+        suffixCtl = [0, -1, -2]
+        for s in suffixCtl:
+            self.assertIsNone(self.rsa.IQSTREAM_SetDiskFilenameSuffix(s))
+
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetDiskFilenameSuffix, 'abc')
+        self.assertRaises(ValueError, self.rsa.IQSTREAM_SetDiskFilenameSuffix, self.neg)
+
+    def test_IQSTREAM_SetDiskFileLength(self):
+        length = 100
+        self.assertIsNone(self.rsa.IQSTREAM_SetDiskFileLength(length))
+        self.assertRaises(TypeError, self.rsa.IQSTREAM_SetDiskFileLength, 'abc')
+        self.assertRaises(ValueError, self.rsa.IQSTREAM_SetDiskFileLength, self.neg)
+
+    def test_IQSTREAM_Operation(self):
+        self.rsa.IQSTREAM_SetAcqBandwidth(5e6)
+        self.rsa.IQSTREAM_SetOutputConfiguration('CLIENT', 'INT16')
+        self.rsa.IQSTREAM_GetAcqParameters()
+        self.rsa.DEVICE_Run()
+
+        self.assertIsNone(self.rsa.IQSTREAM_Start())
+        self.assertTrue(self.rsa.IQSTREAM_GetEnable())
+
+        self.assertIsNone(self.rsa.IQSTREAM_Stop())
+        self.assertFalse(self.rsa.IQSTREAM_GetEnable())
+
+        self.rsa.DEVICE_Stop()
+
+    def test_IQSTREAM_ClearAcqStatus(self):
+        self.assertIsNone(self.rsa.IQSTREAM_ClearAcqStatus())
+
+    """TRIG Command Testing"""
+
+    def test_TRIG_TriggerMode(self):
+        mode = ["freerun", "triggered"]
+        for m in mode:
+            self.assertIsNone(self.rsa.TRIG_SetTriggerMode(m))
+            self.assertEqual(self.rsa.TRIG_GetTriggerMode(), m)
+
+    def test_TRIG_TriggerSource(self):
+        source = ["External", "IFPowerLevel"]
+        for s in source:
+            self.assertIsNone(self.rsa.TRIG_SetTriggerSource(s))
+            self.assertEqual(self.rsa.TRIG_GetTriggerSource(), s)
+
+    def test_TRIG_TriggerTransition(self):
+        trans = ["LH", "HL", "Either"]
+        for t in trans:
+            self.assertIsNone(self.rsa.TRIG_SetTriggerTransition(t))
+            self.assertEqual(self.rsa.TRIG_GetTriggerTransition(), t)
+        self.assertRaises(TypeError, self.rsa.TRIG_SetTriggerTransition, 0)
+
+    def test_TRIG_IFPowerTriggerLevel(self):
+        trigLevel = -10
+        self.assertIsNone(self.rsa.TRIG_SetIFPowerTriggerLevel(trigLevel))
+        self.assertEqual(self.rsa.TRIG_GetIFPowerTriggerLevel(), trigLevel)
+        self.assertRaises(TypeError, self.rsa.TRIG_SetIFPowerTriggerLevel, 'trigger')
+        self.assertRaises(ValueError, self.rsa.TRIG_SetIFPowerTriggerLevel, 31)
+        self.assertRaises(ValueError, self.rsa.TRIG_SetIFPowerTriggerLevel, -131)
+
+    def test_TRIG_TriggerPositionPercent(self):
+        self.assertRaises(ValueError, self.rsa.TRIG_SetTriggerPositionPercent, 0.5)
+        self.assertRaises(ValueError, self.rsa.TRIG_SetTriggerPositionPercent, 100)
+        self.assertRaises(TypeError, self.rsa.TRIG_SetTriggerPositionPercent, 'abc')
+
+        pos = 20
+        self.assertIsNone(self.rsa.TRIG_SetTriggerPositionPercent(pos))
+        self.assertEqual(self.rsa.TRIG_GetTriggerPositionPercent(), pos)
+
+    def test_TRIG_ForceTrigger(self):
+        self.assertIsNone(self.rsa.TRIG_ForceTrigger())
+
+    """SPECTRUM Command Testing"""
+
+    def test_SPECTRUM_Enable(self):
+        enable = [False, True]
+        for e in enable:
+            self.assertIsNone(self.rsa.SPECTRUM_SetEnable(e))
+            self.assertEqual(self.rsa.SPECTRUM_GetEnable(), e)
+
+    def test_SPECTRUM_Settings(self):
+        self.assertIsNone(self.rsa.SPECTRUM_SetDefault())
+
+        span = 20e6
+        rbw = 100e3
+        enableVBW = True
+        vbw = 50e3
+        traceLength = 1601
+        window = 'Hann'
+        verticalUnit = 'dBm'
+        self.assertIsNone(self.rsa.SPECTRUM_SetSettings(span, rbw, enableVBW, vbw,
+                                                   traceLength, window,
+                                                   verticalUnit))
+        settings = self.rsa.SPECTRUM_GetSettings()
+        self.assertIsInstance(settings, dict)
+        self.assertEqual(len(settings), 13)
+        self.assertEqual(settings['span'], span)
+        self.assertEqual(settings['rbw'], rbw)
+        self.assertEqual(settings['enableVBW'], enableVBW)
+        self.assertEqual(settings['vbw'], vbw)
+        self.assertEqual(settings['window'], window)
+        self.assertEqual(settings['traceLength'], traceLength)
+        self.assertEqual(settings['verticalUnit'], verticalUnit)
+
+        self.assertRaises(TypeError, self.rsa.SPECTRUM_SetSettings, 'span', 'rbw',
+                          'enableVBW', 'vbw', 'traceLength',
+                          1, 0)
+
+    def test_SPECTRUM_TraceType(self):
+        trace = 'Trace2'
+        enable = True
+        detector = 'AverageVRMS'
+        self.assertIsNone(self.rsa.SPECTRUM_SetTraceType(trace, enable, detector))
+        o_enable, o_detector = self.rsa.SPECTRUM_GetTraceType(trace)
+        self.assertEqual(enable, o_enable)
+        self.assertEqual(detector, o_detector)
+
+        self.assertRaises(rsa_api.RSAError, self.rsa.SPECTRUM_SetTraceType, trace='abc')
+        self.assertRaises(TypeError, self.rsa.SPECTRUM_SetTraceType, trace=40e5)
+        self.assertRaises(rsa_api.RSAError, self.rsa.SPECTRUM_SetTraceType,
+                          detector='abc')
+        self.assertRaises(TypeError, self.rsa.SPECTRUM_SetTraceType, detector=40e5)
+
+    def test_SPECTRUM_GetLimits(self):
+        if self.device in ['RSA306B', 'RSA306']:
+            maxSpan = 6.2e9
+        elif self.device in ['RSA503A', 'RSA603A']:
+            maxSpan = 3.0e9
+        elif self.device in ['RSA507A', 'RSA607A']:
+            maxSpan = 7.5e9
+        elif self.device == 'RSA513A':
+            maxSpan = 13.6e9
+        elif self.device == 'RSA518A':
+            maxSpan = 18.0e9
+
+        limits = self.rsa.SPECTRUM_GetLimits()
+        self.assertIsInstance(limits, dict)
+        self.assertEqual(len(limits), 8)
+        self.assertEqual(limits['maxSpan'], maxSpan)
+        self.assertEqual(limits['minSpan'], 1e3)
+        self.assertEqual(limits['maxRBW'], 10e6)
+        self.assertEqual(limits['minRBW'], 10)
+        self.assertEqual(limits['maxVBW'], 10e6)
+        self.assertEqual(limits['minVBW'], 1)
+        self.assertEqual(limits['maxTraceLength'], 64001)
+        self.assertEqual(limits['minTraceLength'], 801)
+
+    def test_SPECTRUM_Acquire(self):
+        self.rsa.SPECTRUM_SetEnable(True)
+        span = 20e6
+        rbw = 100e3
+        enableVBW = True
+        vbw = 50e3
+        traceLength = 1601
+        window = 'Hann'
+        verticalUnit = 'dBm'
+        self.rsa.SPECTRUM_SetSettings(span, rbw, enableVBW, vbw, traceLength, window,
+                                 verticalUnit)
+        spectrum, outTracePoints = self.rsa.SPECTRUM_Acquire(trace='Trace1',
+                                                        trace_points=traceLength)
+        self.assertEqual(len(spectrum), traceLength)
+        self.assertIsInstance(spectrum, np.ndarray)
+        self.assertRaises(TypeError, self.rsa.SPECTRUM_Acquire, trace=1)
+
+        traceInfo = self.rsa.SPECTRUM_GetTraceInfo()
+        self.assertIsInstance(traceInfo, dict)
+        self.assertEqual(len(traceInfo), 2)