From 5e28250f4d37446644ffdd48184a93de540364eb Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 8 Jan 2024 13:34:54 -0700 Subject: [PATCH 01/90] sigan monitor --- scos_actions/hardware/__init__.py | 13 ++++++++++++- scos_actions/hardware/signal_analyzer_monitor.py | 13 +++++++++++++ .../signal_analyzer_registration_handler.py | 13 +++++++++++++ scos_actions/signals.py | 3 +++ scos_actions/status/__init__.py | 1 - 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 scos_actions/hardware/signal_analyzer_monitor.py create mode 100644 scos_actions/hardware/signal_analyzer_registration_handler.py diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index 34af394d..fc1ee4eb 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -7,13 +7,21 @@ from scos_actions import utils from scos_actions.capabilities import capabilities +from scos_actions.hardware import sigan_registration_handler +from scos_actions.hardware.signal_analyzer_monitor import SignalAnalyzerMonitor +from scos_actions.hardware.signal_analyzer_registration_handler import ( + signal_analyzer_registration_handler, +) from scos_actions.settings import ( PRESELECTOR_CLASS, PRESELECTOR_CONFIG_FILE, PRESELECTOR_MODULE, SWITCH_CONFIGS_DIR, ) -from scos_actions.signals import register_component_with_status +from scos_actions.signals import ( + register_component_with_status, + register_signal_analyzer, +) from scos_actions.status.status_registration_handler import status_registration_handler logger = logging.getLogger(__name__) @@ -68,8 +76,11 @@ def load_preselector(preselector_config, module, preselector_class_name): return ps +signa_analyzer_monitor = SignalAnalyzerMonitor() +register_signal_analyzer.connect(signal_analyzer_registration_handler) register_component_with_status.connect(status_registration_handler) logger.debug("Connected status registration handler") +register_signal_analyzer.connect(sigan_registration_handler) preselector = load_preselector_from_file(PRESELECTOR_CONFIG_FILE) switches = load_switches(SWITCH_CONFIGS_DIR) logger.debug(f"Loaded {(len(switches))} switches.") diff --git a/scos_actions/hardware/signal_analyzer_monitor.py b/scos_actions/hardware/signal_analyzer_monitor.py new file mode 100644 index 00000000..14c5df1f --- /dev/null +++ b/scos_actions/hardware/signal_analyzer_monitor.py @@ -0,0 +1,13 @@ +import logging + +logger = logging.getLogger(__name__) + + +class SignalAnalyzerMonitor: + def __init__(self): + logger.debug("Initializing Signal Analyzer Monitor") + self.signal_analyzer = None + + def register_signal_analyzer(self, sigan): + logger.debug(f"Setting Signal Analyzer to {sigan}") + self.signal_analyzer = sigan diff --git a/scos_actions/hardware/signal_analyzer_registration_handler.py b/scos_actions/hardware/signal_analyzer_registration_handler.py new file mode 100644 index 00000000..012e49e8 --- /dev/null +++ b/scos_actions/hardware/signal_analyzer_registration_handler.py @@ -0,0 +1,13 @@ +import logging + +from . import signa_analyzer_monitor + +logger = logging.getLogger(__name__) + + +def signal_analyzer_registration_handler(sender, **kwargs): + try: + logger.debug(f"Registering {sender} as status provider") + signa_analyzer_monitor.register_signal_analyzer(kwargs["signal_analyzer"]) + except: + logger.exception("Error registering signal analyzer") diff --git a/scos_actions/signals.py b/scos_actions/signals.py index 36baaf3b..339f1919 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -13,3 +13,6 @@ # Provides argument 'action' register_action = Signal() + +# Provides argument 'sigan' +register_signal_analyzer = Signal() diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py index f21e1efc..bdaa1846 100644 --- a/scos_actions/status/__init__.py +++ b/scos_actions/status/__init__.py @@ -3,5 +3,4 @@ from .status_monitor import StatusMonitor status_registrar = StatusMonitor() - start_time = datetime.datetime.utcnow() From bb4299651773d5235aeac2c031c58fff12363c5e Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 8 Jan 2024 13:52:10 -0700 Subject: [PATCH 02/90] Move sigan monitor to status. --- scos_actions/hardware/__init__.py | 10 +++------- scos_actions/status/__init__.py | 2 ++ .../{hardware => status}/signal_analyzer_monitor.py | 0 .../signal_analyzer_registration_handler.py | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) rename scos_actions/{hardware => status}/signal_analyzer_monitor.py (100%) rename scos_actions/{hardware => status}/signal_analyzer_registration_handler.py (85%) diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index fc1ee4eb..0f569ebc 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -7,11 +7,6 @@ from scos_actions import utils from scos_actions.capabilities import capabilities -from scos_actions.hardware import sigan_registration_handler -from scos_actions.hardware.signal_analyzer_monitor import SignalAnalyzerMonitor -from scos_actions.hardware.signal_analyzer_registration_handler import ( - signal_analyzer_registration_handler, -) from scos_actions.settings import ( PRESELECTOR_CLASS, PRESELECTOR_CONFIG_FILE, @@ -23,6 +18,9 @@ register_signal_analyzer, ) from scos_actions.status.status_registration_handler import status_registration_handler +from scos_actions.status.signal_analyzer_registration_handler import ( + signal_analyzer_registration_handler, +) logger = logging.getLogger(__name__) @@ -76,11 +74,9 @@ def load_preselector(preselector_config, module, preselector_class_name): return ps -signa_analyzer_monitor = SignalAnalyzerMonitor() register_signal_analyzer.connect(signal_analyzer_registration_handler) register_component_with_status.connect(status_registration_handler) logger.debug("Connected status registration handler") -register_signal_analyzer.connect(sigan_registration_handler) preselector = load_preselector_from_file(PRESELECTOR_CONFIG_FILE) switches = load_switches(SWITCH_CONFIGS_DIR) logger.debug(f"Loaded {(len(switches))} switches.") diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py index bdaa1846..2583372b 100644 --- a/scos_actions/status/__init__.py +++ b/scos_actions/status/__init__.py @@ -1,6 +1,8 @@ import datetime +from .signal_analyzer_monitor import SignalAnalyzerMonitor from .status_monitor import StatusMonitor status_registrar = StatusMonitor() +signal_analyzer_monitor = SignalAnalyzerMonitor() start_time = datetime.datetime.utcnow() diff --git a/scos_actions/hardware/signal_analyzer_monitor.py b/scos_actions/status/signal_analyzer_monitor.py similarity index 100% rename from scos_actions/hardware/signal_analyzer_monitor.py rename to scos_actions/status/signal_analyzer_monitor.py diff --git a/scos_actions/hardware/signal_analyzer_registration_handler.py b/scos_actions/status/signal_analyzer_registration_handler.py similarity index 85% rename from scos_actions/hardware/signal_analyzer_registration_handler.py rename to scos_actions/status/signal_analyzer_registration_handler.py index 012e49e8..117759b4 100644 --- a/scos_actions/hardware/signal_analyzer_registration_handler.py +++ b/scos_actions/status/signal_analyzer_registration_handler.py @@ -1,6 +1,6 @@ import logging -from . import signa_analyzer_monitor +from scos_actions.hardware import signa_analyzer_monitor logger = logging.getLogger(__name__) From 588fb6fa5ceb188c65087eac2e1adb86dc49d561 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 8 Jan 2024 14:09:29 -0700 Subject: [PATCH 03/90] fix signal_analyzer_monitor. --- scos_actions/status/signal_analyzer_registration_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scos_actions/status/signal_analyzer_registration_handler.py b/scos_actions/status/signal_analyzer_registration_handler.py index 117759b4..01253a46 100644 --- a/scos_actions/status/signal_analyzer_registration_handler.py +++ b/scos_actions/status/signal_analyzer_registration_handler.py @@ -1,6 +1,6 @@ import logging -from scos_actions.hardware import signa_analyzer_monitor +from . import signal_analyzer_monitor logger = logging.getLogger(__name__) @@ -8,6 +8,6 @@ def signal_analyzer_registration_handler(sender, **kwargs): try: logger.debug(f"Registering {sender} as status provider") - signa_analyzer_monitor.register_signal_analyzer(kwargs["signal_analyzer"]) + signal_analyzer_monitor.register_signal_analyzer(kwargs["signal_analyzer"]) except: logger.exception("Error registering signal analyzer") From eba51a98fd58cc397e46753005a237ac91c226fa Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 8 Jan 2024 21:08:01 -0700 Subject: [PATCH 04/90] Create core module to initialize the StatusMonitor and SiganMonitor --- scos_actions/core/__init__.py | 5 +++++ scos_actions/{status => core}/signal_analyzer_monitor.py | 4 ++++ scos_actions/{status => core}/status_monitor.py | 0 scos_actions/discover/__init__.py | 1 + scos_actions/hardware/__init__.py | 8 +++++--- .../signal_analyzer_registration_handler.py | 2 +- scos_actions/status/__init__.py | 5 +---- scos_actions/status/status_registration_handler.py | 2 +- 8 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 scos_actions/core/__init__.py rename scos_actions/{status => core}/signal_analyzer_monitor.py (81%) rename scos_actions/{status => core}/status_monitor.py (100%) rename scos_actions/{status => hardware}/signal_analyzer_registration_handler.py (86%) diff --git a/scos_actions/core/__init__.py b/scos_actions/core/__init__.py new file mode 100644 index 00000000..f717c05b --- /dev/null +++ b/scos_actions/core/__init__.py @@ -0,0 +1,5 @@ +from .signal_analyzer_monitor import SignalAnalyzerMonitor +from .status_monitor import StatusMonitor + +signal_analyzer_monitor = SignalAnalyzerMonitor() +status_registrar = StatusMonitor() diff --git a/scos_actions/status/signal_analyzer_monitor.py b/scos_actions/core/signal_analyzer_monitor.py similarity index 81% rename from scos_actions/status/signal_analyzer_monitor.py rename to scos_actions/core/signal_analyzer_monitor.py index 14c5df1f..94833b4a 100644 --- a/scos_actions/status/signal_analyzer_monitor.py +++ b/scos_actions/core/signal_analyzer_monitor.py @@ -11,3 +11,7 @@ def __init__(self): def register_signal_analyzer(self, sigan): logger.debug(f"Setting Signal Analyzer to {sigan}") self.signal_analyzer = sigan + + @property + def signal_analyzer(self): + return self.signal_analyzer diff --git a/scos_actions/status/status_monitor.py b/scos_actions/core/status_monitor.py similarity index 100% rename from scos_actions/status/status_monitor.py rename to scos_actions/core/status_monitor.py diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index f77df84d..136e03df 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -1,3 +1,4 @@ +import scos_actions.core from scos_actions.actions import action_classes from scos_actions.actions.logger import Logger from scos_actions.actions.monitor_sigan import MonitorSignalAnalyzer diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index 0f569ebc..5f8e6252 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -7,6 +7,10 @@ from scos_actions import utils from scos_actions.capabilities import capabilities +from scos_actions.hardware.signal_analyzer_registration_handler import ( + signal_analyzer_registration_handler, +) +from scos_actions.initialization import signal_analyzer_monitor from scos_actions.settings import ( PRESELECTOR_CLASS, PRESELECTOR_CONFIG_FILE, @@ -18,9 +22,6 @@ register_signal_analyzer, ) from scos_actions.status.status_registration_handler import status_registration_handler -from scos_actions.status.signal_analyzer_registration_handler import ( - signal_analyzer_registration_handler, -) logger = logging.getLogger(__name__) @@ -74,6 +75,7 @@ def load_preselector(preselector_config, module, preselector_class_name): return ps +signal_analyzer_monitor = signal_analyzer_monitor register_signal_analyzer.connect(signal_analyzer_registration_handler) register_component_with_status.connect(status_registration_handler) logger.debug("Connected status registration handler") diff --git a/scos_actions/status/signal_analyzer_registration_handler.py b/scos_actions/hardware/signal_analyzer_registration_handler.py similarity index 86% rename from scos_actions/status/signal_analyzer_registration_handler.py rename to scos_actions/hardware/signal_analyzer_registration_handler.py index 01253a46..2555d6ba 100644 --- a/scos_actions/status/signal_analyzer_registration_handler.py +++ b/scos_actions/hardware/signal_analyzer_registration_handler.py @@ -1,6 +1,6 @@ import logging -from . import signal_analyzer_monitor +from scos_actions.core import signal_analyzer_monitor logger = logging.getLogger(__name__) diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py index 2583372b..2d749073 100644 --- a/scos_actions/status/__init__.py +++ b/scos_actions/status/__init__.py @@ -1,8 +1,5 @@ import datetime -from .signal_analyzer_monitor import SignalAnalyzerMonitor -from .status_monitor import StatusMonitor +from scos_actions.core.status_monitor import StatusMonitor -status_registrar = StatusMonitor() -signal_analyzer_monitor = SignalAnalyzerMonitor() start_time = datetime.datetime.utcnow() diff --git a/scos_actions/status/status_registration_handler.py b/scos_actions/status/status_registration_handler.py index aed88be3..2cceec5e 100644 --- a/scos_actions/status/status_registration_handler.py +++ b/scos_actions/status/status_registration_handler.py @@ -1,6 +1,6 @@ import logging -from . import status_registrar +from scos_actions.core import status_registrar logger = logging.getLogger(__name__) From 8a3b2c220e31e53b0d38d39ef43fa09a2236c305 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 8 Jan 2024 21:19:49 -0700 Subject: [PATCH 05/90] typo fix. --- scos_actions/core/signal_analyzer_monitor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scos_actions/core/signal_analyzer_monitor.py b/scos_actions/core/signal_analyzer_monitor.py index 94833b4a..eb68de07 100644 --- a/scos_actions/core/signal_analyzer_monitor.py +++ b/scos_actions/core/signal_analyzer_monitor.py @@ -6,12 +6,12 @@ class SignalAnalyzerMonitor: def __init__(self): logger.debug("Initializing Signal Analyzer Monitor") - self.signal_analyzer = None + self._signal_analyzer = None def register_signal_analyzer(self, sigan): logger.debug(f"Setting Signal Analyzer to {sigan}") - self.signal_analyzer = sigan + self._signal_analyzer = sigan @property def signal_analyzer(self): - return self.signal_analyzer + return self._signal_analyzer From 71da125f8f7100d1dc642c35f9de9e0f54ce32d7 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 8 Jan 2024 21:24:21 -0700 Subject: [PATCH 06/90] rename initialization import. --- scos_actions/hardware/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index 5f8e6252..b8eef049 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -7,10 +7,10 @@ from scos_actions import utils from scos_actions.capabilities import capabilities +from scos_actions.core import signal_analyzer_monitor from scos_actions.hardware.signal_analyzer_registration_handler import ( signal_analyzer_registration_handler, ) -from scos_actions.initialization import signal_analyzer_monitor from scos_actions.settings import ( PRESELECTOR_CLASS, PRESELECTOR_CONFIG_FILE, From b2022e497dbf718d145ba37ae546e36fc5e3736d Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Tue, 9 Jan 2024 07:58:42 -0700 Subject: [PATCH 07/90] add test for sigan registration. --- scos_actions/discover/__init__.py | 6 +++++- scos_actions/discover/tests/test_sigan_registration.py | 10 ++++++++++ scos_actions/signals.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 scos_actions/discover/tests/test_sigan_registration.py diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index 136e03df..661da6e2 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -7,12 +7,16 @@ from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.settings import ACTION_DEFINITIONS_DIR, MOCK_SIGAN -from scos_actions.signals import register_component_with_status +from scos_actions.signals import ( + register_component_with_status, + register_signal_analyzer, +) mock_sigan = MockSignalAnalyzer(randomize_values=True) mock_gps = MockGPS() if MOCK_SIGAN: register_component_with_status.send(mock_sigan.__class__, component=mock_sigan) + register_signal_analyzer.send(__name__, signal_analzyer=mock_sigan) actions = {"logger": Logger()} test_actions = { "test_sync_gps": SyncGps( diff --git a/scos_actions/discover/tests/test_sigan_registration.py b/scos_actions/discover/tests/test_sigan_registration.py new file mode 100644 index 00000000..56ffb181 --- /dev/null +++ b/scos_actions/discover/tests/test_sigan_registration.py @@ -0,0 +1,10 @@ +import pytest +from scos_actions.core import signal_analyzer_monitor +from scos_actions.signals import register_signal_analyzer +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer + + +def test_sigan_registration(): + sigan = MockSignalAnalyzer() + register_signal_analyzer.send(__name__, signal_analyzer=sigan) + assert signal_analyzer_monitor.signal_analyzer == sigan diff --git a/scos_actions/signals.py b/scos_actions/signals.py index 339f1919..1818e013 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -14,5 +14,5 @@ # Provides argument 'action' register_action = Signal() -# Provides argument 'sigan' +# Provides argument 'signal_analyzer' register_signal_analyzer = Signal() From 9a7309c226d60d4c961dfbf6b4ca226f06179a46 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Tue, 9 Jan 2024 08:35:11 -0700 Subject: [PATCH 08/90] Increment version. --- scos_actions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/__init__.py b/scos_actions/__init__.py index 54ccb5d2..73d4c8be 100644 --- a/scos_actions/__init__.py +++ b/scos_actions/__init__.py @@ -1 +1 @@ -__version__ = "7.1.0" +__version__ = "8.0.0" From 5909cd6f142a3de54812a3a54e290b2ccbe79534 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Tue, 9 Jan 2024 17:01:18 -0700 Subject: [PATCH 09/90] Restore functionality. Add gps registration and tests. --- .../actions/acquire_sea_data_product.py | 14 +++++---- scos_actions/actions/calibrate_y_factor.py | 7 ++--- scos_actions/core/__init__.py | 6 ++++ scos_actions/core/gps_monitor.py | 30 +++++++++++++++++++ scos_actions/core/signal_analyzer_monitor.py | 13 ++++++++ scos_actions/core/status_monitor.py | 10 ++++++- scos_actions/hardware/__init__.py | 5 ++-- .../hardware/gps_registration_handler.py | 14 +++++++++ scos_actions/hardware/sigan_iface.py | 1 - .../hardware/tests/test_gps_registration.py | 11 +++++++ .../tests/test_sigan_registration.py | 3 +- .../hardware/tests/test_status_handler.py | 9 ++++++ scos_actions/signals.py | 3 ++ 13 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 scos_actions/core/gps_monitor.py create mode 100644 scos_actions/hardware/gps_registration_handler.py create mode 100644 scos_actions/hardware/tests/test_gps_registration.py rename scos_actions/{discover => hardware}/tests/test_sigan_registration.py (96%) create mode 100644 scos_actions/hardware/tests/test_status_handler.py diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 17adb08c..3a763d0f 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -20,7 +20,6 @@ Currently in development. """ -import json import logging import lzma import platform @@ -121,7 +120,9 @@ # Create power detectors TD_DETECTOR = create_statistical_detector("TdMeanMaxDetector", ["max", "mean"]) -FFT_M3_DETECTOR = create_statistical_detector("FftM3Detector", ["max", "mean", "median"]) +FFT_M3_DETECTOR = create_statistical_detector( + "FftM3Detector", ["max", "mean", "median"] +) PFP_M3_DETECTOR = create_statistical_detector("PfpM3Detector", ["min", "max", "mean"]) @@ -158,7 +159,7 @@ def __init__( # Compute the amplitude shift for PSD scaling. The FFT result # is in pseudo-power log units and must be scaled to a PSD. self.fft_scale_factor = ( - - 10.0 * np.log10(impedance_ohms) # Pseudo-power to power + -10.0 * np.log10(impedance_ohms) # Pseudo-power to power + 27.0 # Watts to dBm (+30) and baseband to RF (-3) - 10.0 * np.log10(sample_rate_Hz * fft_size) # PSD scaling + 20.0 * np.log10(window_ecf) # Window energy correction @@ -178,7 +179,9 @@ def run(self, iq: ray.ObjectRef) -> np.ndarray: ) # Power in Watts fft_amplitudes = calculate_pseudo_power(fft_amplitudes) - fft_result = apply_statistical_detector(fft_amplitudes, self.detector) # (max, mean, median) + fft_result = apply_statistical_detector( + fft_amplitudes, self.detector + ) # (max, mean, median) percentile_result = np.percentile(fft_amplitudes, self.percentiles, axis=0) fft_result = np.vstack((fft_result, percentile_result)) fft_result = np.fft.fftshift(fft_result, axes=(1,)) # Shift frequencies @@ -1001,7 +1004,8 @@ def create_global_data_product_metadata(self) -> None: name="Power Spectral Density", series=[d.value for d in FFT_M3_DETECTOR] + [ - f"{int(p)}th_percentile" if p.is_integer() else f"{p}th_percentile" for p in FFT_PERCENTILES + f"{int(p)}th_percentile" if p.is_integer() else f"{p}th_percentile" + for p in FFT_PERCENTILES ], # ["max", "mean", "median", "25th_percentile", "75th_percentile", ... "99.99th_percentile"] length=int(FFT_SIZE * (5 / 7)), x_units="Hz", diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index bc975e65..c6bf6cbd 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -78,7 +78,7 @@ from scos_actions import utils from scos_actions.actions.interfaces.action import Action -from scos_actions.calibration import sensor_calibration, default_sensor_calibration +from scos_actions.calibration import default_sensor_calibration, sensor_calibration from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS from scos_actions.settings import SENSOR_CALIBRATION_FILE @@ -91,10 +91,7 @@ generate_elliptic_iir_low_pass_filter, get_iir_enbw, ) -from scos_actions.signal_processing.power_analysis import ( - calculate_power_watts, - create_statistical_detector, -) +from scos_actions.signal_processing.power_analysis import calculate_power_watts from scos_actions.signal_processing.unit_conversion import convert_watts_to_dBm from scos_actions.signals import trigger_api_restart from scos_actions.utils import ParameterException, get_parameter diff --git a/scos_actions/core/__init__.py b/scos_actions/core/__init__.py index f717c05b..f9631a6f 100644 --- a/scos_actions/core/__init__.py +++ b/scos_actions/core/__init__.py @@ -1,5 +1,11 @@ +import logging + +from .gps_monitor import GpsMonitor from .signal_analyzer_monitor import SignalAnalyzerMonitor from .status_monitor import StatusMonitor +logger = logging.getLogger(__name__) +logger.debug("********** Initializing scos-actions.core **********") signal_analyzer_monitor = SignalAnalyzerMonitor() status_registrar = StatusMonitor() +gps_monitor = GpsMonitor() diff --git a/scos_actions/core/gps_monitor.py b/scos_actions/core/gps_monitor.py new file mode 100644 index 00000000..cdb3f261 --- /dev/null +++ b/scos_actions/core/gps_monitor.py @@ -0,0 +1,30 @@ +import logging + +logger = logging.getLogger(__name__) + + +class GpsMonitor: + def __init__(self): + logger.debug("Initializing GPS Monitor") + self._gps = None + + def register_gps(self, gps): + """ + Registers the GPS so other scos components may access it. The + registered GPS will be accessible by importing + gps_monitor from scos_actions.core and accessing the + gps property. + + :param gps: the instance of a GPSInterface to register. + """ + logger.debug(f"Setting GPS to {gps}") + self._gps = gps + + @property + def gps(self): + """ + Provides access to the registered GPS. + + :return: the registered instance of a GPSInterface. + """ + return self._gps diff --git a/scos_actions/core/signal_analyzer_monitor.py b/scos_actions/core/signal_analyzer_monitor.py index eb68de07..02204a76 100644 --- a/scos_actions/core/signal_analyzer_monitor.py +++ b/scos_actions/core/signal_analyzer_monitor.py @@ -9,9 +9,22 @@ def __init__(self): self._signal_analyzer = None def register_signal_analyzer(self, sigan): + """ + Registers the signal analyzer so other scos components may access it. The + registered signal analyzer will be accessible by importing + signal_analyzer_monitor from scos_actions.core and accessing the + signal_analyzer property. + + :param sigan: the instance of a SignalAnalyzerInterface to register. + """ logger.debug(f"Setting Signal Analyzer to {sigan}") self._signal_analyzer = sigan @property def signal_analyzer(self): + """ + Provides access to the registered signal analyzer. + + :return: the registered instance of a SignalAnalyzerInterface. + """ return self._signal_analyzer diff --git a/scos_actions/core/status_monitor.py b/scos_actions/core/status_monitor.py index dd76d00b..c55bda84 100644 --- a/scos_actions/core/status_monitor.py +++ b/scos_actions/core/status_monitor.py @@ -9,4 +9,12 @@ def __init__(self): self.status_components = [] def add_component(self, component): - self.status_components.append(component) + """ + Allows objects to be registered to provide status. Any object registered will + be included in scos-sensors status endpoint. All objects registered must + implement a get_status() method that returns a dictionary. + + :param component: the object to add to the list of status providing objects. + """ + if hasattr(component, "get_status"): + self.status_components.append(component) diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index b8eef049..8e5fa14d 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -7,7 +7,7 @@ from scos_actions import utils from scos_actions.capabilities import capabilities -from scos_actions.core import signal_analyzer_monitor +from scos_actions.hardware.gps_registration_handler import gps_registration_handler from scos_actions.hardware.signal_analyzer_registration_handler import ( signal_analyzer_registration_handler, ) @@ -19,6 +19,7 @@ ) from scos_actions.signals import ( register_component_with_status, + register_gps, register_signal_analyzer, ) from scos_actions.status.status_registration_handler import status_registration_handler @@ -75,7 +76,7 @@ def load_preselector(preselector_config, module, preselector_class_name): return ps -signal_analyzer_monitor = signal_analyzer_monitor +register_gps.connect(gps_registration_handler) register_signal_analyzer.connect(signal_analyzer_registration_handler) register_component_with_status.connect(status_registration_handler) logger.debug("Connected status registration handler") diff --git a/scos_actions/hardware/gps_registration_handler.py b/scos_actions/hardware/gps_registration_handler.py new file mode 100644 index 00000000..f3cf0023 --- /dev/null +++ b/scos_actions/hardware/gps_registration_handler.py @@ -0,0 +1,14 @@ +import logging + +from scos_actions.core import gps_monitor + +logger = logging.getLogger(__name__) + + +def gps_registration_handler(sender, **kwargs): + try: + gps = kwargs["gps"] + logger.debug(f"Registering GPS: {gps}") + gps_monitor.register_gps(gps) + except: + logger.exception("Error registering gps") diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index 786025d3..7c009662 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -1,4 +1,3 @@ -import copy import logging import time from abc import ABC, abstractmethod diff --git a/scos_actions/hardware/tests/test_gps_registration.py b/scos_actions/hardware/tests/test_gps_registration.py new file mode 100644 index 00000000..dae47c55 --- /dev/null +++ b/scos_actions/hardware/tests/test_gps_registration.py @@ -0,0 +1,11 @@ +from scos_actions.core import gps_monitor +from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.signals import register_gps + + +def test_gps_registration(): + mock_gps = MockGPS() + register_gps.send(mock_gps, gps=mock_gps) + + +# assert gps_monitor.gps == gps diff --git a/scos_actions/discover/tests/test_sigan_registration.py b/scos_actions/hardware/tests/test_sigan_registration.py similarity index 96% rename from scos_actions/discover/tests/test_sigan_registration.py rename to scos_actions/hardware/tests/test_sigan_registration.py index 56ffb181..4a7b699d 100644 --- a/scos_actions/discover/tests/test_sigan_registration.py +++ b/scos_actions/hardware/tests/test_sigan_registration.py @@ -1,7 +1,6 @@ -import pytest from scos_actions.core import signal_analyzer_monitor -from scos_actions.signals import register_signal_analyzer from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.signals import register_signal_analyzer def test_sigan_registration(): diff --git a/scos_actions/hardware/tests/test_status_handler.py b/scos_actions/hardware/tests/test_status_handler.py new file mode 100644 index 00000000..34efd396 --- /dev/null +++ b/scos_actions/hardware/tests/test_status_handler.py @@ -0,0 +1,9 @@ +from scos_actions.core import status_registrar +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.signals import register_component_with_status + + +def test_status_handler(): + mock_sigan = MockSignalAnalyzer() + register_component_with_status.send(__name__, component=mock_sigan) + status_registrar.status_components[0] == mock_sigan diff --git a/scos_actions/signals.py b/scos_actions/signals.py index 1818e013..66d2a64a 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -16,3 +16,6 @@ # Provides argument 'signal_analyzer' register_signal_analyzer = Signal() + +# Provides argument 'gps' +register_gps = Signal() From f2bce4cb00889083c6c4b4a79c9946ef01e42631 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 11 Jan 2024 16:09:56 -0700 Subject: [PATCH 10/90] Remove sigan and gps registration signals and core module. Update actions to have sigan and gps properties. Expose action_types in discover. --- scos_actions/actions/interfaces/action.py | 22 ++++++++++++-- scos_actions/actions/sync_gps.py | 4 +-- scos_actions/core/__init__.py | 11 ------- scos_actions/core/gps_monitor.py | 30 ------------------- scos_actions/core/signal_analyzer_monitor.py | 30 ------------------- scos_actions/discover/__init__.py | 2 +- scos_actions/hardware/__init__.py | 4 --- .../hardware/gps_registration_handler.py | 14 --------- .../signal_analyzer_registration_handler.py | 13 -------- scos_actions/signals.py | 5 ---- scos_actions/status/__init__.py | 4 ++- .../{core => status}/status_monitor.py | 0 12 files changed, 26 insertions(+), 113 deletions(-) delete mode 100644 scos_actions/core/__init__.py delete mode 100644 scos_actions/core/gps_monitor.py delete mode 100644 scos_actions/core/signal_analyzer_monitor.py delete mode 100644 scos_actions/hardware/gps_registration_handler.py delete mode 100644 scos_actions/hardware/signal_analyzer_registration_handler.py rename scos_actions/{core => status}/status_monitor.py (100%) diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index b3172374..55304cbc 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -4,6 +4,7 @@ from scos_actions.capabilities import SENSOR_LOCATION, capabilities from scos_actions.hardware import preselector +from scos_actions.hardware.gps_iface import GPSInterface from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS from scos_actions.metadata.sigmf_builder import SigMFBuilder @@ -34,12 +35,12 @@ class Action(ABC): PRESELECTOR_PATH_KEY = "rf_path" - def __init__(self, parameters, sigan, gps=None): + def __init__(self, parameters, sigan=None, gps=None): if gps is None: gps = MockGPS() self.parameters = deepcopy(parameters) self.sigan = sigan - self.gps = gps + self._gps = gps self.sensor_definition = capabilities["sensor"] self.sigmf_builder = None if ( @@ -54,6 +55,22 @@ def configure(self, params: dict): self.configure_sigan(params) self.configure_preselector(params) + @property + def gps(self): + return self._gps + + @gps.setter + def gps(self, value: GPSInterface): + self._gps = value + + @property + def signal_analyzer(self): + return self.sigan + + @signal_analyzer.setter + def signal_analyzer(self, value): + self.sigan = value + def configure_sigan(self, params: dict): sigan_params = {k: v for k, v in params.items() if k in SIGAN_SETTINGS_KEYS} for key, value in sigan_params.items(): @@ -128,3 +145,4 @@ def name(self): @abstractmethod def __call__(self, schedule_entry, task_id): pass + diff --git a/scos_actions/actions/sync_gps.py b/scos_actions/actions/sync_gps.py index 5444bcb9..c753aeb2 100644 --- a/scos_actions/actions/sync_gps.py +++ b/scos_actions/actions/sync_gps.py @@ -18,12 +18,12 @@ def __init__(self, gps, parameters, sigan): def __call__(self, schedule_entry: dict, task_id: int): logger.debug("Syncing to GPS") - dt = self.gps.get_gps_time() + dt = self._gps.get_gps_time() date_cmd = ["date", "-s", "{:}".format(dt.strftime("%Y/%m/%d %H:%M:%S"))] subprocess.check_output(date_cmd, shell=True) logger.info(f"Set system time to GPS time {dt.ctime()}") - location = self.gps.get_location() + location = self._gps.get_location() if location is None: raise RuntimeError("Unable to synchronize to GPS") diff --git a/scos_actions/core/__init__.py b/scos_actions/core/__init__.py deleted file mode 100644 index f9631a6f..00000000 --- a/scos_actions/core/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -import logging - -from .gps_monitor import GpsMonitor -from .signal_analyzer_monitor import SignalAnalyzerMonitor -from .status_monitor import StatusMonitor - -logger = logging.getLogger(__name__) -logger.debug("********** Initializing scos-actions.core **********") -signal_analyzer_monitor = SignalAnalyzerMonitor() -status_registrar = StatusMonitor() -gps_monitor = GpsMonitor() diff --git a/scos_actions/core/gps_monitor.py b/scos_actions/core/gps_monitor.py deleted file mode 100644 index cdb3f261..00000000 --- a/scos_actions/core/gps_monitor.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -logger = logging.getLogger(__name__) - - -class GpsMonitor: - def __init__(self): - logger.debug("Initializing GPS Monitor") - self._gps = None - - def register_gps(self, gps): - """ - Registers the GPS so other scos components may access it. The - registered GPS will be accessible by importing - gps_monitor from scos_actions.core and accessing the - gps property. - - :param gps: the instance of a GPSInterface to register. - """ - logger.debug(f"Setting GPS to {gps}") - self._gps = gps - - @property - def gps(self): - """ - Provides access to the registered GPS. - - :return: the registered instance of a GPSInterface. - """ - return self._gps diff --git a/scos_actions/core/signal_analyzer_monitor.py b/scos_actions/core/signal_analyzer_monitor.py deleted file mode 100644 index 02204a76..00000000 --- a/scos_actions/core/signal_analyzer_monitor.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -logger = logging.getLogger(__name__) - - -class SignalAnalyzerMonitor: - def __init__(self): - logger.debug("Initializing Signal Analyzer Monitor") - self._signal_analyzer = None - - def register_signal_analyzer(self, sigan): - """ - Registers the signal analyzer so other scos components may access it. The - registered signal analyzer will be accessible by importing - signal_analyzer_monitor from scos_actions.core and accessing the - signal_analyzer property. - - :param sigan: the instance of a SignalAnalyzerInterface to register. - """ - logger.debug(f"Setting Signal Analyzer to {sigan}") - self._signal_analyzer = sigan - - @property - def signal_analyzer(self): - """ - Provides access to the registered signal analyzer. - - :return: the registered instance of a SignalAnalyzerInterface. - """ - return self._signal_analyzer diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index 661da6e2..2b44c04a 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -1,4 +1,3 @@ -import scos_actions.core from scos_actions.actions import action_classes from scos_actions.actions.logger import Logger from scos_actions.actions.monitor_sigan import MonitorSignalAnalyzer @@ -50,3 +49,4 @@ def init( yaml_actions, yaml_test_actions = init() actions.update(yaml_actions) test_actions.update(yaml_test_actions) +action_types = action_classes \ No newline at end of file diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index 8e5fa14d..d4db4e0e 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -7,10 +7,6 @@ from scos_actions import utils from scos_actions.capabilities import capabilities -from scos_actions.hardware.gps_registration_handler import gps_registration_handler -from scos_actions.hardware.signal_analyzer_registration_handler import ( - signal_analyzer_registration_handler, -) from scos_actions.settings import ( PRESELECTOR_CLASS, PRESELECTOR_CONFIG_FILE, diff --git a/scos_actions/hardware/gps_registration_handler.py b/scos_actions/hardware/gps_registration_handler.py deleted file mode 100644 index f3cf0023..00000000 --- a/scos_actions/hardware/gps_registration_handler.py +++ /dev/null @@ -1,14 +0,0 @@ -import logging - -from scos_actions.core import gps_monitor - -logger = logging.getLogger(__name__) - - -def gps_registration_handler(sender, **kwargs): - try: - gps = kwargs["gps"] - logger.debug(f"Registering GPS: {gps}") - gps_monitor.register_gps(gps) - except: - logger.exception("Error registering gps") diff --git a/scos_actions/hardware/signal_analyzer_registration_handler.py b/scos_actions/hardware/signal_analyzer_registration_handler.py deleted file mode 100644 index 2555d6ba..00000000 --- a/scos_actions/hardware/signal_analyzer_registration_handler.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging - -from scos_actions.core import signal_analyzer_monitor - -logger = logging.getLogger(__name__) - - -def signal_analyzer_registration_handler(sender, **kwargs): - try: - logger.debug(f"Registering {sender} as status provider") - signal_analyzer_monitor.register_signal_analyzer(kwargs["signal_analyzer"]) - except: - logger.exception("Error registering signal analyzer") diff --git a/scos_actions/signals.py b/scos_actions/signals.py index 66d2a64a..58801d79 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -14,8 +14,3 @@ # Provides argument 'action' register_action = Signal() -# Provides argument 'signal_analyzer' -register_signal_analyzer = Signal() - -# Provides argument 'gps' -register_gps = Signal() diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py index 2d749073..294a8b85 100644 --- a/scos_actions/status/__init__.py +++ b/scos_actions/status/__init__.py @@ -1,5 +1,7 @@ import datetime -from scos_actions.core.status_monitor import StatusMonitor +from scos_actions.status.status_monitor import StatusMonitor start_time = datetime.datetime.utcnow() + +status_monitor = StatusMonitor() \ No newline at end of file diff --git a/scos_actions/core/status_monitor.py b/scos_actions/status/status_monitor.py similarity index 100% rename from scos_actions/core/status_monitor.py rename to scos_actions/status/status_monitor.py From 5fcb348ff4e87c384fe3c6494c2adc3cfcf4c398 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 11 Jan 2024 16:25:02 -0700 Subject: [PATCH 11/90] remove register sigan and gps signal use. --- scos_actions/hardware/__init__.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index d4db4e0e..54337bed 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -13,11 +13,7 @@ PRESELECTOR_MODULE, SWITCH_CONFIGS_DIR, ) -from scos_actions.signals import ( - register_component_with_status, - register_gps, - register_signal_analyzer, -) +from scos_actions.signals import register_component_with_status from scos_actions.status.status_registration_handler import status_registration_handler logger = logging.getLogger(__name__) @@ -71,9 +67,6 @@ def load_preselector(preselector_config, module, preselector_class_name): ps = None return ps - -register_gps.connect(gps_registration_handler) -register_signal_analyzer.connect(signal_analyzer_registration_handler) register_component_with_status.connect(status_registration_handler) logger.debug("Connected status registration handler") preselector = load_preselector_from_file(PRESELECTOR_CONFIG_FILE) From 2e489c2f831a300749f0cf9118d3b56673d0f193 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 11 Jan 2024 16:30:57 -0700 Subject: [PATCH 12/90] fix import of status_monitor --- scos_actions/status/status_registration_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scos_actions/status/status_registration_handler.py b/scos_actions/status/status_registration_handler.py index 2cceec5e..6c547e6a 100644 --- a/scos_actions/status/status_registration_handler.py +++ b/scos_actions/status/status_registration_handler.py @@ -1,6 +1,6 @@ import logging -from scos_actions.core import status_registrar +from . import status_monitor logger = logging.getLogger(__name__) @@ -8,6 +8,6 @@ def status_registration_handler(sender, **kwargs): try: logger.debug(f"Registering {sender} as status provider") - status_registrar.add_component(kwargs["component"]) + status_monitor.add_component(kwargs["component"]) except: logger.exception("Error registering status component") From 027e23a35b5359163f7e97e4c6aebd88fc6d5113 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 11 Jan 2024 16:43:15 -0700 Subject: [PATCH 13/90] remove old tests and fix test_status_handler. --- scos_actions/hardware/tests/test_gps_registration.py | 11 ----------- .../hardware/tests/test_sigan_registration.py | 9 --------- scos_actions/hardware/tests/test_status_handler.py | 4 ++-- 3 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 scos_actions/hardware/tests/test_gps_registration.py delete mode 100644 scos_actions/hardware/tests/test_sigan_registration.py diff --git a/scos_actions/hardware/tests/test_gps_registration.py b/scos_actions/hardware/tests/test_gps_registration.py deleted file mode 100644 index dae47c55..00000000 --- a/scos_actions/hardware/tests/test_gps_registration.py +++ /dev/null @@ -1,11 +0,0 @@ -from scos_actions.core import gps_monitor -from scos_actions.hardware.mocks.mock_gps import MockGPS -from scos_actions.signals import register_gps - - -def test_gps_registration(): - mock_gps = MockGPS() - register_gps.send(mock_gps, gps=mock_gps) - - -# assert gps_monitor.gps == gps diff --git a/scos_actions/hardware/tests/test_sigan_registration.py b/scos_actions/hardware/tests/test_sigan_registration.py deleted file mode 100644 index 4a7b699d..00000000 --- a/scos_actions/hardware/tests/test_sigan_registration.py +++ /dev/null @@ -1,9 +0,0 @@ -from scos_actions.core import signal_analyzer_monitor -from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer -from scos_actions.signals import register_signal_analyzer - - -def test_sigan_registration(): - sigan = MockSignalAnalyzer() - register_signal_analyzer.send(__name__, signal_analyzer=sigan) - assert signal_analyzer_monitor.signal_analyzer == sigan diff --git a/scos_actions/hardware/tests/test_status_handler.py b/scos_actions/hardware/tests/test_status_handler.py index 34efd396..5a48392f 100644 --- a/scos_actions/hardware/tests/test_status_handler.py +++ b/scos_actions/hardware/tests/test_status_handler.py @@ -1,4 +1,4 @@ -from scos_actions.core import status_registrar +from scos_actions.core import status_monitor from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.signals import register_component_with_status @@ -6,4 +6,4 @@ def test_status_handler(): mock_sigan = MockSignalAnalyzer() register_component_with_status.send(__name__, component=mock_sigan) - status_registrar.status_components[0] == mock_sigan + status_monitor.status_components[0] == mock_sigan From 5327022f1d37ce8a43acdae114b3584ece7ec207 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 11 Jan 2024 16:43:43 -0700 Subject: [PATCH 14/90] fix test_status_handler --- scos_actions/hardware/tests/test_status_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/hardware/tests/test_status_handler.py b/scos_actions/hardware/tests/test_status_handler.py index 5a48392f..027f765e 100644 --- a/scos_actions/hardware/tests/test_status_handler.py +++ b/scos_actions/hardware/tests/test_status_handler.py @@ -1,4 +1,4 @@ -from scos_actions.core import status_monitor +from scos_actions.status import status_monitor from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.signals import register_component_with_status From 42d27675cebc97e2131d0c3d6b8121529ebcd29a Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 11 Jan 2024 16:48:46 -0700 Subject: [PATCH 15/90] remove register sigan signal refs. --- scos_actions/discover/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index 2b44c04a..b268b48b 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -6,16 +6,12 @@ from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.settings import ACTION_DEFINITIONS_DIR, MOCK_SIGAN -from scos_actions.signals import ( - register_component_with_status, - register_signal_analyzer, -) +from scos_actions.signals import register_component_with_status mock_sigan = MockSignalAnalyzer(randomize_values=True) mock_gps = MockGPS() if MOCK_SIGAN: register_component_with_status.send(mock_sigan.__class__, component=mock_sigan) - register_signal_analyzer.send(__name__, signal_analzyer=mock_sigan) actions = {"logger": Logger()} test_actions = { "test_sync_gps": SyncGps( From 023f5541f0101dd05b8b7dcd5fa51bf43113252a Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 06:56:15 -0700 Subject: [PATCH 16/90] pass gps and sigan to actions call method instead of instantiating actions with them. --- scos_actions/actions/acquire_sea_data_product.py | 4 +++- .../actions/acquire_stepped_freq_tdomain_iq.py | 8 ++++---- scos_actions/actions/calibrate_y_factor.py | 4 +++- scos_actions/actions/interfaces/action.py | 10 ++++------ scos_actions/actions/interfaces/measurement_action.py | 11 +++++------ scos_actions/actions/logger.py | 4 +++- scos_actions/actions/monitor_sigan.py | 9 ++++----- scos_actions/actions/sync_gps.py | 6 +++--- scos_actions/signals.py | 3 --- 9 files changed, 29 insertions(+), 30 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 3a763d0f..4b95aa14 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -508,8 +508,10 @@ def __init__(self, parameters, sigan, gps=None): # Get iterable parameter list self.iteration_params = utils.get_iterable_parameters(self.parameters) - def __call__(self, schedule_entry, task_id): + def __call__(self, sigan, gps, schedule_entry, task_id): """This is the entrypoint function called by the scheduler.""" + self.sigan = sigan + self.gps = gps action_start_tic = perf_counter() _ = psutil.cpu_percent(interval=None) # Initialize CPU usage monitor diff --git a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py index 314b245f..4cff2d28 100644 --- a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py @@ -77,9 +77,7 @@ class SteppedFrequencyTimeDomainIqAcquisition(SingleFrequencyTimeDomainIqAcquisi :param sigan: instance of SignalAnalyzerInterface """ - def __init__(self, parameters, sigan, gps=None): - if gps is None: - gps = MockGPS() + def __init__(self, parameters): super().__init__(parameters=parameters, sigan=sigan, gps=gps) num_center_frequencies = len(parameters[FREQUENCY]) @@ -89,8 +87,10 @@ def __init__(self, parameters, sigan, gps=None): self.sigan = sigan # make instance variable to allow mocking self.num_center_frequencies = num_center_frequencies - def __call__(self, schedule_entry: dict, task_id: int): + def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" + self.sigan = sigan + self.gps = gps self.test_required_components() saved_samples = 0 diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index c6bf6cbd..6c11ccfc 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -198,8 +198,10 @@ def __init__(self, parameters, sigan, gps=None): "Only one set of IIR filter parameters may be specified (including sample rate)." ) - def __call__(self, schedule_entry: dict, task_id: int): + def __call__(self, sigan, gps,schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" + self.sigan = sigan + self.gps = gps self.test_required_components() detail = "" diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index 55304cbc..25185315 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -35,12 +35,10 @@ class Action(ABC): PRESELECTOR_PATH_KEY = "rf_path" - def __init__(self, parameters, sigan=None, gps=None): - if gps is None: - gps = MockGPS() + def __init__(self, parameters): + self.gps = None + self.sigan = None self.parameters = deepcopy(parameters) - self.sigan = sigan - self._gps = gps self.sensor_definition = capabilities["sensor"] self.sigmf_builder = None if ( @@ -143,6 +141,6 @@ def name(self): return get_parameter("name", self.parameters) @abstractmethod - def __call__(self, schedule_entry, task_id): + def __call__(self,sigan,gps, schedule_entry, task_id): pass diff --git a/scos_actions/actions/interfaces/measurement_action.py b/scos_actions/actions/interfaces/measurement_action.py index 8acc224f..353f1ef8 100644 --- a/scos_actions/actions/interfaces/measurement_action.py +++ b/scos_actions/actions/interfaces/measurement_action.py @@ -5,7 +5,6 @@ import numpy as np from scos_actions.actions.interfaces.action import Action -from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.metadata.structs import ntia_sensor from scos_actions.metadata.structs.capture import CaptureSegment from scos_actions.signals import measurement_action_completed @@ -21,13 +20,13 @@ class MeasurementAction(Action): """ - def __init__(self, parameters, sigan, gps=None): - if gps is None: - gps = MockGPS() - super().__init__(parameters, sigan, gps) + def __init__(self, parameters): + super().__init__(parameters) self.received_samples = 0 - def __call__(self, schedule_entry: dict, task_id: int): + def __call__(self,sigan, gps, schedule_entry: dict, task_id: int): + self.sigan = sigan + self.gps = gps self.test_required_components() self.configure(self.parameters) measurement_result = self.execute(schedule_entry, task_id) diff --git a/scos_actions/actions/logger.py b/scos_actions/actions/logger.py index 5966235c..4c70ba55 100644 --- a/scos_actions/actions/logger.py +++ b/scos_actions/actions/logger.py @@ -22,9 +22,11 @@ class Logger(Action): def __init__(self, loglvl=LOGLVL_INFO): super().__init__(parameters={"name": "logger"}, sigan=None, gps=None) + self.sigan = sigan + self.gps = gps self.loglvl = loglvl - def __call__(self, schedule_entry, task_id): + def __call__(self,sigan, gps, schedule_entry, task_id): msg = "running test {name}/{tid}" schedule_entry_name = schedule_entry["name"] logger.log( diff --git a/scos_actions/actions/monitor_sigan.py b/scos_actions/actions/monitor_sigan.py index 55427ac3..6644b693 100644 --- a/scos_actions/actions/monitor_sigan.py +++ b/scos_actions/actions/monitor_sigan.py @@ -12,14 +12,13 @@ class MonitorSignalAnalyzer(Action): """Monitor signal analyzer connection and restart container if unreachable.""" - def __init__(self, sigan, parameters={"name": "monitor_sigan"}, gps=None): - if gps is None: - gps = MockGPS() + def __init__(self, parameters={"name": "monitor_sigan"}, gps=None): super().__init__(parameters=parameters, sigan=sigan, gps=gps) - def __call__(self, schedule_entry: dict, task_id: int): + def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): logger.debug("Performing signal analyzer health check") - + self.sigan = sigan + self.gps = gps healthy = self.sigan.healthy() if healthy: diff --git a/scos_actions/actions/sync_gps.py b/scos_actions/actions/sync_gps.py index c753aeb2..9c5a05f4 100644 --- a/scos_actions/actions/sync_gps.py +++ b/scos_actions/actions/sync_gps.py @@ -12,10 +12,10 @@ class SyncGps(Action): """Query the GPS and synchronize time and location.""" - def __init__(self, gps, parameters, sigan): - super().__init__(parameters=parameters, sigan=sigan, gps=gps) + def __init__(self, parameters): + super().__init__(parameters=parameters) - def __call__(self, schedule_entry: dict, task_id: int): + def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): logger.debug("Syncing to GPS") dt = self._gps.get_gps_time() diff --git a/scos_actions/signals.py b/scos_actions/signals.py index 58801d79..d2542030 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -11,6 +11,3 @@ # Provides argument: 'component' register_component_with_status = Signal() -# Provides argument 'action' -register_action = Signal() - From d827a57b036fbd2261878954e89fb321ff18d914 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 07:30:18 -0700 Subject: [PATCH 17/90] Remove sigan from discover --- scos_actions/discover/__init__.py | 7 +++---- scos_actions/discover/yaml.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index b268b48b..f48f7e3a 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -15,10 +15,10 @@ actions = {"logger": Logger()} test_actions = { "test_sync_gps": SyncGps( - parameters={"name": "test_sync_gps"}, sigan=mock_sigan, gps=mock_gps + parameters={"name": "test_sync_gps"} ), "test_monitor_sigan": MonitorSignalAnalyzer( - parameters={"name": "test_monitor_sigan"}, sigan=mock_sigan + parameters={"name": "test_monitor_sigan"} ), "logger": Logger(), } @@ -26,14 +26,13 @@ def init( action_classes=action_classes, - sigan=mock_sigan, gps=mock_gps, yaml_dir=ACTION_DEFINITIONS_DIR, ): yaml_actions = {} yaml_test_actions = {} for key, value in load_from_yaml( - action_classes, sigan=sigan, gps=gps, yaml_dir=yaml_dir + action_classes, yaml_dir=yaml_dir ).items(): if key.startswith("test_"): yaml_test_actions[key] = value diff --git a/scos_actions/discover/yaml.py b/scos_actions/discover/yaml.py index bf76d9bd..364f4635 100644 --- a/scos_actions/discover/yaml.py +++ b/scos_actions/discover/yaml.py @@ -8,7 +8,7 @@ logger = logging.getLogger(__name__) -def load_from_yaml(action_classes, sigan, gps, yaml_dir: Path = ACTION_DEFINITIONS_DIR): +def load_from_yaml(action_classes, yaml_dir: Path = ACTION_DEFINITIONS_DIR): """Load any YAML files in yaml_dir.""" parsed_actions = {} yaml = YAML(typ="safe") @@ -19,7 +19,7 @@ def load_from_yaml(action_classes, sigan, gps, yaml_dir: Path = ACTION_DEFINITIO try: logger.debug(f"Attempting to configure: {class_name}") action = action_classes[class_name]( - parameters=parameters, sigan=sigan, gps=gps + parameters=parameters ) parsed_actions[action.name] = action except KeyError as exc: From a9b0b782ebb78f87f0b5d6a7e0e9c3e8840c3917 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 08:27:52 -0700 Subject: [PATCH 18/90] remove sigan and gps from action constructors. --- README.md | 2 +- scos_actions/actions/acquire_sea_data_product.py | 2 +- scos_actions/actions/acquire_single_freq_fft.py | 4 ++-- scos_actions/actions/acquire_single_freq_tdomain_iq.py | 4 ++-- scos_actions/actions/acquire_stepped_freq_tdomain_iq.py | 4 ++-- scos_actions/actions/calibrate_y_factor.py | 6 +++--- scos_actions/actions/logger.py | 2 +- scos_actions/actions/monitor_sigan.py | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 557e375f..80292eb0 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ actions = { "sync_gps": SyncGps(gps), } -yaml_actions, yaml_test_actions = init(sigan=sigan, yaml_dir=ACTION_DEFINITIONS_DIR) +yaml_actions, yaml_test_actions = init(yaml_dir=ACTION_DEFINITIONS_DIR) actions.update(yaml_actions) ``` diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 4b95aa14..cd3d204b 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -452,7 +452,7 @@ class NasctnSeaDataProduct(Action): :param sigan: Instance of SignalAnalyzerInterface. """ - def __init__(self, parameters, sigan, gps=None): + def __init__(self, parameters): if gps is None: gps = MockGPS() super().__init__(parameters, sigan, gps) diff --git a/scos_actions/actions/acquire_single_freq_fft.py b/scos_actions/actions/acquire_single_freq_fft.py index aaa37913..df15e8a3 100644 --- a/scos_actions/actions/acquire_single_freq_fft.py +++ b/scos_actions/actions/acquire_single_freq_fft.py @@ -143,10 +143,10 @@ class SingleFrequencyFftAcquisition(MeasurementAction): :param sigan: Instance of SignalAnalyzerInterface. """ - def __init__(self, parameters, sigan, gps=None): + def __init__(self, parameters): if gps is None: gps = MockGPS() - super().__init__(parameters, sigan, gps) + super().__init__(parameters) # Pull parameters from action config self.fft_size = get_parameter(FFT_SIZE, self.parameters) self.nffts = get_parameter(NUM_FFTS, self.parameters) diff --git a/scos_actions/actions/acquire_single_freq_tdomain_iq.py b/scos_actions/actions/acquire_single_freq_tdomain_iq.py index 7943dfb7..d198cb3a 100644 --- a/scos_actions/actions/acquire_single_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_single_freq_tdomain_iq.py @@ -71,10 +71,10 @@ class SingleFrequencyTimeDomainIqAcquisition(MeasurementAction): :param sigan: instance of SignalAnalyzerInterface. """ - def __init__(self, parameters, sigan, gps=None): + def __init__(self, parameters): if gps is None: gps = MockGPS() - super().__init__(parameters=parameters, sigan=sigan, gps=gps) + super().__init__(parameters=parameters) # Pull parameters from action config self.nskip = get_parameter(NUM_SKIP, self.parameters) self.duration_ms = get_parameter(DURATION_MS, self.parameters) diff --git a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py index 4cff2d28..3e4c2a95 100644 --- a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py @@ -78,13 +78,13 @@ class SteppedFrequencyTimeDomainIqAcquisition(SingleFrequencyTimeDomainIqAcquisi """ def __init__(self, parameters): - super().__init__(parameters=parameters, sigan=sigan, gps=gps) + super().__init__(parameters=parameters) num_center_frequencies = len(parameters[FREQUENCY]) # Create iterable parameter set self.iterable_params = utils.get_iterable_parameters(parameters) - self.sigan = sigan # make instance variable to allow mocking + self.sigan = None self.num_center_frequencies = num_center_frequencies def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 6c11ccfc..5fe2e996 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -137,12 +137,12 @@ class YFactorCalibration(Action): :param sigan: instance of SignalAnalyzerInterface. """ - def __init__(self, parameters, sigan, gps=None): + def __init__(self, parameters): if gps is None: gps = MockGPS() logger.debug("Initializing calibration action") - super().__init__(parameters, sigan, gps) - self.sigan = sigan + super().__init__(parameters) + self.sigan = None self.iteration_params = utils.get_iterable_parameters(parameters) # IIR Filter Setup diff --git a/scos_actions/actions/logger.py b/scos_actions/actions/logger.py index 4c70ba55..1e339e9e 100644 --- a/scos_actions/actions/logger.py +++ b/scos_actions/actions/logger.py @@ -21,7 +21,7 @@ class Logger(Action): """ def __init__(self, loglvl=LOGLVL_INFO): - super().__init__(parameters={"name": "logger"}, sigan=None, gps=None) + super().__init__(parameters={"name": "logger"}) self.sigan = sigan self.gps = gps self.loglvl = loglvl diff --git a/scos_actions/actions/monitor_sigan.py b/scos_actions/actions/monitor_sigan.py index 6644b693..222d1a34 100644 --- a/scos_actions/actions/monitor_sigan.py +++ b/scos_actions/actions/monitor_sigan.py @@ -13,7 +13,7 @@ class MonitorSignalAnalyzer(Action): """Monitor signal analyzer connection and restart container if unreachable.""" def __init__(self, parameters={"name": "monitor_sigan"}, gps=None): - super().__init__(parameters=parameters, sigan=sigan, gps=gps) + super().__init__(parameters=parameters) def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): logger.debug("Performing signal analyzer health check") From 46ff3112c0377dccebca15ba6bee505a6c25cf04 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 08:33:10 -0700 Subject: [PATCH 19/90] remove erroneous ref to sigan in logger action. --- scos_actions/actions/logger.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scos_actions/actions/logger.py b/scos_actions/actions/logger.py index 1e339e9e..962efaae 100644 --- a/scos_actions/actions/logger.py +++ b/scos_actions/actions/logger.py @@ -22,8 +22,6 @@ class Logger(Action): def __init__(self, loglvl=LOGLVL_INFO): super().__init__(parameters={"name": "logger"}) - self.sigan = sigan - self.gps = gps self.loglvl = loglvl def __call__(self,sigan, gps, schedule_entry, task_id): From ace3525bfb09325712abad1732066ec9343f7a13 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 08:37:41 -0700 Subject: [PATCH 20/90] remove sigan and gps from sea action init. --- scos_actions/actions/acquire_sea_data_product.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index cd3d204b..8c8c58df 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -453,9 +453,7 @@ class NasctnSeaDataProduct(Action): """ def __init__(self, parameters): - if gps is None: - gps = MockGPS() - super().__init__(parameters, sigan, gps) + super().__init__(parameters) # Assume preselector is present rf_path_name = utils.get_parameter(RF_PATH, self.parameters) self.rf_path = {self.PRESELECTOR_PATH_KEY: rf_path_name} From faba66fea6bfcd4b9ad7c1e2d76f96398f151d60 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 08:43:08 -0700 Subject: [PATCH 21/90] remove gps/sigan ref from action inits. --- scos_actions/actions/acquire_single_freq_fft.py | 2 -- scos_actions/actions/acquire_single_freq_tdomain_iq.py | 2 -- scos_actions/actions/calibrate_y_factor.py | 3 --- 3 files changed, 7 deletions(-) diff --git a/scos_actions/actions/acquire_single_freq_fft.py b/scos_actions/actions/acquire_single_freq_fft.py index df15e8a3..3c9893ba 100644 --- a/scos_actions/actions/acquire_single_freq_fft.py +++ b/scos_actions/actions/acquire_single_freq_fft.py @@ -144,8 +144,6 @@ class SingleFrequencyFftAcquisition(MeasurementAction): """ def __init__(self, parameters): - if gps is None: - gps = MockGPS() super().__init__(parameters) # Pull parameters from action config self.fft_size = get_parameter(FFT_SIZE, self.parameters) diff --git a/scos_actions/actions/acquire_single_freq_tdomain_iq.py b/scos_actions/actions/acquire_single_freq_tdomain_iq.py index d198cb3a..a4dafacb 100644 --- a/scos_actions/actions/acquire_single_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_single_freq_tdomain_iq.py @@ -72,8 +72,6 @@ class SingleFrequencyTimeDomainIqAcquisition(MeasurementAction): """ def __init__(self, parameters): - if gps is None: - gps = MockGPS() super().__init__(parameters=parameters) # Pull parameters from action config self.nskip = get_parameter(NUM_SKIP, self.parameters) diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 5fe2e996..3456a18d 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -138,11 +138,8 @@ class YFactorCalibration(Action): """ def __init__(self, parameters): - if gps is None: - gps = MockGPS() logger.debug("Initializing calibration action") super().__init__(parameters) - self.sigan = None self.iteration_params = utils.get_iterable_parameters(parameters) # IIR Filter Setup From af98a153b9bfc078bf4ebc4b69f085f826bda454 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 09:17:20 -0700 Subject: [PATCH 22/90] fix tests. --- scos_actions/actions/interfaces/action.py | 2 +- scos_actions/actions/sync_gps.py | 6 ++--- .../tests/test_acquire_single_freq_fft.py | 5 ++-- .../actions/tests/test_monitor_sigan.py | 23 ++++++++++--------- .../tests/test_single_freq_tdomain_iq.py | 14 ++++++----- .../tests/test_stepped_freq_tdomain_iq.py | 7 ++++-- scos_actions/actions/tests/test_sync_gps.py | 8 ++++--- scos_actions/discover/tests/test_yaml.py | 4 ++-- 8 files changed, 39 insertions(+), 30 deletions(-) diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index 25185315..10d86406 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -141,6 +141,6 @@ def name(self): return get_parameter("name", self.parameters) @abstractmethod - def __call__(self,sigan,gps, schedule_entry, task_id): + def __call__(self,sigan=None,gps=None, schedule_entry=None, task_id=None): pass diff --git a/scos_actions/actions/sync_gps.py b/scos_actions/actions/sync_gps.py index 9c5a05f4..4d83fca8 100644 --- a/scos_actions/actions/sync_gps.py +++ b/scos_actions/actions/sync_gps.py @@ -17,13 +17,13 @@ def __init__(self, parameters): def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): logger.debug("Syncing to GPS") - - dt = self._gps.get_gps_time() + self.gps = gps + dt = gps.get_gps_time() date_cmd = ["date", "-s", "{:}".format(dt.strftime("%Y/%m/%d %H:%M:%S"))] subprocess.check_output(date_cmd, shell=True) logger.info(f"Set system time to GPS time {dt.ctime()}") - location = self._gps.get_location() + location = gps.get_location() if location is None: raise RuntimeError("Unable to synchronize to GPS") diff --git a/scos_actions/actions/tests/test_acquire_single_freq_fft.py b/scos_actions/actions/tests/test_acquire_single_freq_fft.py index e21cb4e4..ceab4a82 100644 --- a/scos_actions/actions/tests/test_acquire_single_freq_fft.py +++ b/scos_actions/actions/tests/test_acquire_single_freq_fft.py @@ -2,6 +2,7 @@ from scos_actions.capabilities import capabilities from scos_actions.discover import test_actions as actions from scos_actions.signals import measurement_action_completed +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer SINGLE_FREQUENCY_FFT_ACQUISITION = { "name": "test_acq", @@ -29,7 +30,7 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_m4s_action"] assert action.description - action(SINGLE_FREQUENCY_FFT_ACQUISITION, 1) + action(sigan = MockSignalAnalyzer(),gps=None, schedule_entry=SINGLE_FREQUENCY_FFT_ACQUISITION, task_id=1) assert _task_id assert _data.any() assert _metadata @@ -84,5 +85,5 @@ def callback(sender, **kwargs): def test_num_samples_skip(): action = actions["test_single_frequency_m4s_action"] assert action.description - action(SINGLE_FREQUENCY_FFT_ACQUISITION, 1) + action(MockSignalAnalyzer(), None, SINGLE_FREQUENCY_FFT_ACQUISITION, 1) assert action.sigan._num_samples_skip == action.parameters["nskip"] diff --git a/scos_actions/actions/tests/test_monitor_sigan.py b/scos_actions/actions/tests/test_monitor_sigan.py index 3cd2e737..dd6a3f19 100644 --- a/scos_actions/actions/tests/test_monitor_sigan.py +++ b/scos_actions/actions/tests/test_monitor_sigan.py @@ -1,5 +1,6 @@ from scos_actions.discover import test_actions as actions from scos_actions.signals import trigger_api_restart +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer MONITOR_SIGAN_SCHEDULE = { "name": "test_monitor", @@ -19,11 +20,11 @@ def callback(sender, **kwargs): trigger_api_restart.connect(callback) action = actions["test_monitor_sigan"] - sigan = action.sigan - sigan._is_available = False - action(MONITOR_SIGAN_SCHEDULE, 1) + mock_sigan = MockSignalAnalyzer() + mock_sigan._is_available = False + action(mock_sigan,None, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == True # signal sent - sigan._is_available = True + mock_sigan._is_available = True def test_monitor_sigan_not_healthy(): @@ -35,9 +36,9 @@ def callback(sender, **kwargs): trigger_api_restart.connect(callback) action = actions["test_monitor_sigan"] - sigan = action.sigan - sigan.times_to_fail_recv = 6 - action(MONITOR_SIGAN_SCHEDULE, 1) + mock_sigan = MockSignalAnalyzer() + mock_sigan.times_to_fail_recv = 6 + action(mock_sigan, None, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == True # signal sent @@ -50,8 +51,8 @@ def callback(sender, **kwargs): trigger_api_restart.connect(callback) action = actions["test_monitor_sigan"] - sigan = action.sigan - sigan._is_available = True - sigan.set_times_to_fail_recv(0) - action(MONITOR_SIGAN_SCHEDULE, 1) + mock_sigan = MockSignalAnalyzer() + mock_sigan._is_available = True + mock_sigan.set_times_to_fail_recv(0) + action(mock_sigan, None, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == False # signal not sent diff --git a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py index fc388fd2..6e8ff19e 100644 --- a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py @@ -3,6 +3,7 @@ from scos_actions.actions.tests.utils import check_metadata_fields from scos_actions.discover import test_actions as actions from scos_actions.signals import measurement_action_completed +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer SINGLE_TIMEDOMAIN_IQ_ACQUISITION = { "name": "test_acq", @@ -29,7 +30,7 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_iq_action"] assert action.description - action(SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + action(MockSignalAnalyzer(), None,SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) assert _data.any() assert _metadata assert _task_id == 1 @@ -57,15 +58,16 @@ def callback(sender, **kwargs): def test_required_components(): action = actions["test_single_frequency_m4s_action"] - sigan = action.sigan - sigan._is_available = False + mock_sigan = MockSignalAnalyzer() + mock_sigan._is_available = False with pytest.raises(RuntimeError): - action(SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) - sigan._is_available = True + action(mock_sigan, None,SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + mock_sigan._is_available = True def test_num_samples_skip(): action = actions["test_single_frequency_iq_action"] assert action.description - action(SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + mock_sigan = MockSignalAnalyzer() + action(mock_sigan, None,SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) assert action.sigan._num_samples_skip == action.parameters["nskip"] diff --git a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py index 6ef6881f..113c510a 100644 --- a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py @@ -1,5 +1,6 @@ from scos_actions.discover import test_actions as actions from scos_actions.signals import measurement_action_completed +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION = { "name": "test_multirec_acq", @@ -32,7 +33,8 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_multi_frequency_iq_action"] assert action.description - action(SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) + mock_sigan = MockSignalAnalyzer() + action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) for i in range(_count): assert _datas[i].any() assert _metadatas[i] @@ -44,7 +46,8 @@ def callback(sender, **kwargs): def test_num_samples_skip(): action = actions["test_multi_frequency_iq_action"] assert action.description - action(SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) + mock_sigan = MockSignalAnalyzer() + action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) if isinstance(action.parameters["nskip"], list): assert action.sigan._num_samples_skip == action.parameters["nskip"][-1] else: diff --git a/scos_actions/actions/tests/test_sync_gps.py b/scos_actions/actions/tests/test_sync_gps.py index f60d6b76..898c44a0 100644 --- a/scos_actions/actions/tests/test_sync_gps.py +++ b/scos_actions/actions/tests/test_sync_gps.py @@ -5,6 +5,7 @@ from scos_actions.discover import test_actions from scos_actions.signals import location_action_completed +from scos_actions.hardware.mocks.mock_gps import MockGPS SYNC_GPS = { "name": "sync_gps", @@ -15,7 +16,7 @@ } -def test_detector(): +def test_location_action_completed(): _latitude = None _longitude = None @@ -28,11 +29,12 @@ def callback(sender, **kwargs): location_action_completed.connect(callback) action = test_actions["test_sync_gps"] if sys.platform == "linux": - action(SYNC_GPS, 1) + action(None, MockGPS(), SYNC_GPS, 1) assert _latitude assert _longitude elif sys.platform == "win32": with pytest.raises(subprocess.CalledProcessError): - action(SYNC_GPS, 1) + action(None, MockGPS(), SYNC_GPS, 1) else: raise NotImplementedError("Test not implemented for current OS.") + diff --git a/scos_actions/discover/tests/test_yaml.py b/scos_actions/discover/tests/test_yaml.py index 479892aa..473b39dd 100644 --- a/scos_actions/discover/tests/test_yaml.py +++ b/scos_actions/discover/tests/test_yaml.py @@ -30,7 +30,7 @@ def test_load_from_yaml_existing(): """Any existing action definitions should be valid yaml.""" - load_from_yaml(actions.action_classes, sigan, gps) + load_from_yaml(actions.action_classes) def _test_load_from_yaml_check_error(yaml_to_write, expected_error): @@ -44,7 +44,7 @@ def _test_load_from_yaml_check_error(yaml_to_write, expected_error): tmpfile.seek(0) # Now try to load the invalid yaml file, expecting an error with pytest.raises(expected_error): - load_from_yaml(actions.action_classes, sigan, gps=gps, yaml_dir=tmpdir) + load_from_yaml(actions.action_classes, yaml_dir=tmpdir) os.unlink(tmpfile.name) From 396034913385cdb0b7f447a9f98ed09e787ae512 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 10:48:03 -0700 Subject: [PATCH 23/90] run pre-commit --- scos_actions/actions/calibrate_y_factor.py | 2 +- scos_actions/actions/interfaces/action.py | 3 +-- scos_actions/actions/interfaces/measurement_action.py | 2 +- scos_actions/actions/logger.py | 2 +- .../actions/tests/test_acquire_single_freq_fft.py | 9 +++++++-- scos_actions/actions/tests/test_monitor_sigan.py | 4 ++-- .../actions/tests/test_single_freq_tdomain_iq.py | 8 ++++---- .../actions/tests/test_stepped_freq_tdomain_iq.py | 2 +- scos_actions/actions/tests/test_sync_gps.py | 3 +-- scos_actions/discover/__init__.py | 10 +++------- scos_actions/discover/yaml.py | 4 +--- scos_actions/hardware/__init__.py | 1 + scos_actions/hardware/tests/test_status_handler.py | 2 +- scos_actions/signals.py | 1 - scos_actions/status/__init__.py | 2 +- 15 files changed, 26 insertions(+), 29 deletions(-) diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 3456a18d..7e261847 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -195,7 +195,7 @@ def __init__(self, parameters): "Only one set of IIR filter parameters may be specified (including sample rate)." ) - def __call__(self, sigan, gps,schedule_entry: dict, task_id: int): + def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" self.sigan = sigan self.gps = gps diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index 10d86406..0bec4c64 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -141,6 +141,5 @@ def name(self): return get_parameter("name", self.parameters) @abstractmethod - def __call__(self,sigan=None,gps=None, schedule_entry=None, task_id=None): + def __call__(self, sigan=None, gps=None, schedule_entry=None, task_id=None): pass - diff --git a/scos_actions/actions/interfaces/measurement_action.py b/scos_actions/actions/interfaces/measurement_action.py index 353f1ef8..df8aa676 100644 --- a/scos_actions/actions/interfaces/measurement_action.py +++ b/scos_actions/actions/interfaces/measurement_action.py @@ -24,7 +24,7 @@ def __init__(self, parameters): super().__init__(parameters) self.received_samples = 0 - def __call__(self,sigan, gps, schedule_entry: dict, task_id: int): + def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): self.sigan = sigan self.gps = gps self.test_required_components() diff --git a/scos_actions/actions/logger.py b/scos_actions/actions/logger.py index 962efaae..5ccf69f5 100644 --- a/scos_actions/actions/logger.py +++ b/scos_actions/actions/logger.py @@ -24,7 +24,7 @@ def __init__(self, loglvl=LOGLVL_INFO): super().__init__(parameters={"name": "logger"}) self.loglvl = loglvl - def __call__(self,sigan, gps, schedule_entry, task_id): + def __call__(self, sigan, gps, schedule_entry, task_id): msg = "running test {name}/{tid}" schedule_entry_name = schedule_entry["name"] logger.log( diff --git a/scos_actions/actions/tests/test_acquire_single_freq_fft.py b/scos_actions/actions/tests/test_acquire_single_freq_fft.py index ceab4a82..6cac1751 100644 --- a/scos_actions/actions/tests/test_acquire_single_freq_fft.py +++ b/scos_actions/actions/tests/test_acquire_single_freq_fft.py @@ -1,8 +1,8 @@ from scos_actions.actions.tests.utils import SENSOR_DEFINITION, check_metadata_fields from scos_actions.capabilities import capabilities from scos_actions.discover import test_actions as actions -from scos_actions.signals import measurement_action_completed from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.signals import measurement_action_completed SINGLE_FREQUENCY_FFT_ACQUISITION = { "name": "test_acq", @@ -30,7 +30,12 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_m4s_action"] assert action.description - action(sigan = MockSignalAnalyzer(),gps=None, schedule_entry=SINGLE_FREQUENCY_FFT_ACQUISITION, task_id=1) + action( + sigan=MockSignalAnalyzer(), + gps=None, + schedule_entry=SINGLE_FREQUENCY_FFT_ACQUISITION, + task_id=1, + ) assert _task_id assert _data.any() assert _metadata diff --git a/scos_actions/actions/tests/test_monitor_sigan.py b/scos_actions/actions/tests/test_monitor_sigan.py index dd6a3f19..39f13d98 100644 --- a/scos_actions/actions/tests/test_monitor_sigan.py +++ b/scos_actions/actions/tests/test_monitor_sigan.py @@ -1,6 +1,6 @@ from scos_actions.discover import test_actions as actions -from scos_actions.signals import trigger_api_restart from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.signals import trigger_api_restart MONITOR_SIGAN_SCHEDULE = { "name": "test_monitor", @@ -22,7 +22,7 @@ def callback(sender, **kwargs): action = actions["test_monitor_sigan"] mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = False - action(mock_sigan,None, MONITOR_SIGAN_SCHEDULE, 1) + action(mock_sigan, None, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == True # signal sent mock_sigan._is_available = True diff --git a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py index 6e8ff19e..286bd546 100644 --- a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py @@ -2,8 +2,8 @@ from scos_actions.actions.tests.utils import check_metadata_fields from scos_actions.discover import test_actions as actions -from scos_actions.signals import measurement_action_completed from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.signals import measurement_action_completed SINGLE_TIMEDOMAIN_IQ_ACQUISITION = { "name": "test_acq", @@ -30,7 +30,7 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_iq_action"] assert action.description - action(MockSignalAnalyzer(), None,SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + action(MockSignalAnalyzer(), None, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) assert _data.any() assert _metadata assert _task_id == 1 @@ -61,7 +61,7 @@ def test_required_components(): mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = False with pytest.raises(RuntimeError): - action(mock_sigan, None,SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) mock_sigan._is_available = True @@ -69,5 +69,5 @@ def test_num_samples_skip(): action = actions["test_single_frequency_iq_action"] assert action.description mock_sigan = MockSignalAnalyzer() - action(mock_sigan, None,SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) assert action.sigan._num_samples_skip == action.parameters["nskip"] diff --git a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py index 113c510a..e7ceace6 100644 --- a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py @@ -1,6 +1,6 @@ from scos_actions.discover import test_actions as actions -from scos_actions.signals import measurement_action_completed from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.signals import measurement_action_completed SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION = { "name": "test_multirec_acq", diff --git a/scos_actions/actions/tests/test_sync_gps.py b/scos_actions/actions/tests/test_sync_gps.py index 898c44a0..3971c52e 100644 --- a/scos_actions/actions/tests/test_sync_gps.py +++ b/scos_actions/actions/tests/test_sync_gps.py @@ -4,8 +4,8 @@ import pytest from scos_actions.discover import test_actions -from scos_actions.signals import location_action_completed from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.signals import location_action_completed SYNC_GPS = { "name": "sync_gps", @@ -37,4 +37,3 @@ def callback(sender, **kwargs): action(None, MockGPS(), SYNC_GPS, 1) else: raise NotImplementedError("Test not implemented for current OS.") - diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index f48f7e3a..0ad100ee 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -14,9 +14,7 @@ register_component_with_status.send(mock_sigan.__class__, component=mock_sigan) actions = {"logger": Logger()} test_actions = { - "test_sync_gps": SyncGps( - parameters={"name": "test_sync_gps"} - ), + "test_sync_gps": SyncGps(parameters={"name": "test_sync_gps"}), "test_monitor_sigan": MonitorSignalAnalyzer( parameters={"name": "test_monitor_sigan"} ), @@ -31,9 +29,7 @@ def init( ): yaml_actions = {} yaml_test_actions = {} - for key, value in load_from_yaml( - action_classes, yaml_dir=yaml_dir - ).items(): + for key, value in load_from_yaml(action_classes, yaml_dir=yaml_dir).items(): if key.startswith("test_"): yaml_test_actions[key] = value else: @@ -44,4 +40,4 @@ def init( yaml_actions, yaml_test_actions = init() actions.update(yaml_actions) test_actions.update(yaml_test_actions) -action_types = action_classes \ No newline at end of file +action_types = action_classes diff --git a/scos_actions/discover/yaml.py b/scos_actions/discover/yaml.py index 364f4635..c0ae8eed 100644 --- a/scos_actions/discover/yaml.py +++ b/scos_actions/discover/yaml.py @@ -18,9 +18,7 @@ def load_from_yaml(action_classes, yaml_dir: Path = ACTION_DEFINITIONS_DIR): for class_name, parameters in definition.items(): try: logger.debug(f"Attempting to configure: {class_name}") - action = action_classes[class_name]( - parameters=parameters - ) + action = action_classes[class_name](parameters=parameters) parsed_actions[action.name] = action except KeyError as exc: err = "Nonexistent action class name {!r} referenced in {!r}" diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index 54337bed..34af394d 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -67,6 +67,7 @@ def load_preselector(preselector_config, module, preselector_class_name): ps = None return ps + register_component_with_status.connect(status_registration_handler) logger.debug("Connected status registration handler") preselector = load_preselector_from_file(PRESELECTOR_CONFIG_FILE) diff --git a/scos_actions/hardware/tests/test_status_handler.py b/scos_actions/hardware/tests/test_status_handler.py index 027f765e..093f4e48 100644 --- a/scos_actions/hardware/tests/test_status_handler.py +++ b/scos_actions/hardware/tests/test_status_handler.py @@ -1,6 +1,6 @@ -from scos_actions.status import status_monitor from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.signals import register_component_with_status +from scos_actions.status import status_monitor def test_status_handler(): diff --git a/scos_actions/signals.py b/scos_actions/signals.py index d2542030..93e9c575 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -10,4 +10,3 @@ # Provides argument: 'component' register_component_with_status = Signal() - diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py index 294a8b85..410faa6d 100644 --- a/scos_actions/status/__init__.py +++ b/scos_actions/status/__init__.py @@ -4,4 +4,4 @@ start_time = datetime.datetime.utcnow() -status_monitor = StatusMonitor() \ No newline at end of file +status_monitor = StatusMonitor() From a72755929ce92150ecdcf96e4af2d77a0fe618f4 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 12 Jan 2024 20:44:03 -0700 Subject: [PATCH 24/90] fixed tests. --- .../actions/acquire_sea_data_product.py | 19 +++-- .../actions/acquire_single_freq_fft.py | 6 +- .../actions/acquire_single_freq_tdomain_iq.py | 8 +- .../acquire_stepped_freq_tdomain_iq.py | 15 ++-- scos_actions/actions/calibrate_y_factor.py | 5 +- scos_actions/actions/interfaces/action.py | 59 ++++++--------- .../actions/interfaces/measurement_action.py | 15 ++-- scos_actions/actions/logger.py | 2 +- scos_actions/actions/monitor_sigan.py | 9 +-- scos_actions/actions/sync_gps.py | 8 +- .../tests/test_acquire_single_freq_fft.py | 12 +-- .../actions/tests/test_monitor_sigan.py | 10 ++- .../tests/test_single_freq_tdomain_iq.py | 12 ++- .../tests/test_stepped_freq_tdomain_iq.py | 17 ++++- scos_actions/actions/tests/test_sync_gps.py | 6 +- scos_actions/capabilities/__init__.py | 46 ------------ scos_actions/hardware/__init__.py | 75 ------------------- scos_actions/hardware/sigan_iface.py | 20 ++--- .../hardware/tests/test_preselector.py | 12 --- .../hardware/tests/test_status_handler.py | 9 --- scos_actions/hardware/utils.py | 3 +- scos_actions/signal_processing/calibration.py | 5 +- 22 files changed, 115 insertions(+), 258 deletions(-) delete mode 100644 scos_actions/capabilities/__init__.py delete mode 100644 scos_actions/hardware/tests/test_preselector.py delete mode 100644 scos_actions/hardware/tests/test_status_handler.py diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 8c8c58df..0a608549 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -38,9 +38,7 @@ from scos_actions import __version__ as SCOS_ACTIONS_VERSION from scos_actions import utils from scos_actions.actions.interfaces.action import Action -from scos_actions.capabilities import SENSOR_DEFINITION_HASH, SENSOR_LOCATION -from scos_actions.hardware import preselector, switches -from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.sensor import Sensor from scos_actions.hardware.utils import ( get_cpu_uptime_seconds, get_current_cpu_clock_speed, @@ -506,10 +504,9 @@ def __init__(self, parameters): # Get iterable parameter list self.iteration_params = utils.get_iterable_parameters(self.parameters) - def __call__(self, sigan, gps, schedule_entry, task_id): + def __call__(self, sensor, schedule_entry, task_id): """This is the entrypoint function called by the scheduler.""" - self.sigan = sigan - self.gps = gps + self._sensor = sensor action_start_tic = perf_counter() _ = psutil.cpu_percent(interval=None) # Initialize CPU usage monitor @@ -640,7 +637,9 @@ def capture_iq(self, params: dict) -> dict: ) return measurement_result - def capture_diagnostics(self, action_start_tic: float, cpu_speeds: list) -> None: + def capture_diagnostics( + self, sensor: Sensor, action_start_tic: float, cpu_speeds: list + ) -> None: """ Capture diagnostic sensor data. @@ -718,7 +717,7 @@ def capture_diagnostics(self, action_start_tic: float, cpu_speeds: list) -> None switch_diag["door_closed"] = not bool(all_switch_status["door_state"]) # Read preselector sensors - ps_diag = preselector.get_status() + ps_diag = sensor.preselector.get_status() del ps_diag["name"] del ps_diag["healthy"] @@ -945,7 +944,7 @@ def add_power_states(self, all_switch_status: dict, switch_diag: dict): else: logger.warning("No preselector_powered found in switch status.") - def create_global_sensor_metadata(self): + def create_global_sensor_metadata(self, sensor: Sensor): # Add (minimal) ntia-sensor metadata to the sigmf_builder: # sensor ID, serial numbers for preselector, sigan, and computer # overall sensor_spec version, e.g. "Prototype Rev. 3" @@ -955,7 +954,7 @@ def create_global_sensor_metadata(self): sensor_spec=ntia_core.HardwareSpec( id=self.sensor_definition["sensor_spec"]["id"], ), - sensor_sha512=SENSOR_DEFINITION_HASH, + sensor_sha512=sensor.capabilities["sensor"]["sensor_sha512"], ) ) diff --git a/scos_actions/actions/acquire_single_freq_fft.py b/scos_actions/actions/acquire_single_freq_fft.py index 3c9893ba..7f4385e0 100644 --- a/scos_actions/actions/acquire_single_freq_fft.py +++ b/scos_actions/actions/acquire_single_freq_fft.py @@ -178,9 +178,9 @@ def execute(self, schedule_entry: dict, task_id: int) -> dict: # Save measurement results measurement_result["data"] = m4s_result measurement_result.update(self.parameters) - measurement_result["calibration_datetime"] = self.sigan.sensor_calibration_data[ - "datetime" - ] + measurement_result[ + "calibration_datetime" + ] = self.sensor.signal_analyzer.sensor_calibration_data["datetime"] measurement_result["task_id"] = task_id measurement_result["classification"] = self.classification diff --git a/scos_actions/actions/acquire_single_freq_tdomain_iq.py b/scos_actions/actions/acquire_single_freq_tdomain_iq.py index a4dafacb..e41700e5 100644 --- a/scos_actions/actions/acquire_single_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_single_freq_tdomain_iq.py @@ -82,16 +82,16 @@ def __init__(self, parameters): def execute(self, schedule_entry: dict, task_id: int) -> dict: # Use the sigan's actual reported instead of requested sample rate - sample_rate = self.sigan.sample_rate + sample_rate = self.sensor.signal_analyzer.sample_rate num_samples = int(sample_rate * self.duration_ms * 1e-3) measurement_result = self.acquire_data(num_samples, self.nskip, self.cal_adjust) end_time = utils.get_datetime_str_now() measurement_result.update(self.parameters) measurement_result["end_time"] = end_time measurement_result["task_id"] = task_id - measurement_result["calibration_datetime"] = self.sigan.sensor_calibration_data[ - "datetime" - ] + measurement_result[ + "calibration_datetime" + ] = self.sensor.signal_analyzer.sensor_calibration_data["datetime"] measurement_result["classification"] = self.classification sigan_settings = self.get_sigan_settings(measurement_result) logger.debug(f"sigan settings:{sigan_settings}") diff --git a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py index 3e4c2a95..721372eb 100644 --- a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py @@ -84,25 +84,24 @@ def __init__(self, parameters): # Create iterable parameter set self.iterable_params = utils.get_iterable_parameters(parameters) - self.sigan = None + self.sensor = None self.num_center_frequencies = num_center_frequencies - def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): + def __call__(self, sensor, schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" - self.sigan = sigan - self.gps = gps + self._sensor = sensor self.test_required_components() saved_samples = 0 for recording_id, measurement_params in enumerate( self.iterable_params, start=1 ): - self.get_sigmf_builder(schedule_entry) + self.get_sigmf_builder(sensor, schedule_entry) self.configure(measurement_params) duration_ms = get_parameter(DURATION_MS, measurement_params) nskip = get_parameter(NUM_SKIP, measurement_params) cal_adjust = get_parameter(CAL_ADJUST, measurement_params) - sample_rate = self.sigan.sample_rate + sample_rate = self.sensor.signal_analyzer.sample_rate num_samples = int(sample_rate * duration_ms * 1e-3) measurement_result = super().acquire_data(num_samples, nskip, cal_adjust) measurement_result.update(measurement_params) @@ -121,8 +120,8 @@ def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): overload=measurement_result["overload"], sigan_settings=sigan_settings, ) - sigan_cal = self.sigan.sigan_calibration_data - sensor_cal = self.sigan.sensor_calibration_data + sigan_cal = self.sensor.signal_analyzer.sigan_calibration_data + sensor_cal = self.sensor.signal_analyzer.sensor_calibration_data if sigan_cal is not None: if "1db_compression_point" in sigan_cal: sigan_cal["compression_point"] = sigan_cal.pop( diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 7e261847..71358f43 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -195,10 +195,9 @@ def __init__(self, parameters): "Only one set of IIR filter parameters may be specified (including sample rate)." ) - def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): + def __call__(self, sensor, schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" - self.sigan = sigan - self.gps = gps + self.sensor = sensor self.test_required_components() detail = "" diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index 0bec4c64..b60cf4ea 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -2,10 +2,8 @@ from abc import ABC, abstractmethod from copy import deepcopy -from scos_actions.capabilities import SENSOR_LOCATION, capabilities -from scos_actions.hardware import preselector from scos_actions.hardware.gps_iface import GPSInterface -from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.sensor import Sensor from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS from scos_actions.metadata.sigmf_builder import SigMFBuilder from scos_actions.metadata.structs import ntia_scos, ntia_sensor @@ -36,54 +34,38 @@ class Action(ABC): PRESELECTOR_PATH_KEY = "rf_path" def __init__(self, parameters): - self.gps = None - self.sigan = None + self._sensor = None self.parameters = deepcopy(parameters) - self.sensor_definition = capabilities["sensor"] self.sigmf_builder = None - if ( - "preselector" in self.sensor_definition - and "rf_paths" in self.sensor_definition["preselector"] - ): - self.has_configurable_preselector = True - else: - self.has_configurable_preselector = False def configure(self, params: dict): self.configure_sigan(params) - self.configure_preselector(params) + self.configure_preselector(self.sensor, params) @property - def gps(self): - return self._gps + def sensor(self): + return self._sensor - @gps.setter - def gps(self, value: GPSInterface): - self._gps = value - - @property - def signal_analyzer(self): - return self.sigan - - @signal_analyzer.setter - def signal_analyzer(self, value): - self.sigan = value + @sensor.setter + def sensor(self, value): + self._sensor = value def configure_sigan(self, params: dict): sigan_params = {k: v for k, v in params.items() if k in SIGAN_SETTINGS_KEYS} for key, value in sigan_params.items(): - if hasattr(self.sigan, key): + if hasattr(self.sensor.signal_analyzer, key): logger.debug(f"Applying setting to sigan: {key}: {value}") - setattr(self.sigan, key, value) + setattr(self.sensor.signal_analyzer, key, value) else: logger.warning(f"Sigan does not have attribute {key}") - def configure_preselector(self, params: dict): + def configure_preselector(self, sensor: Sensor, params: dict): + preselector = sensor.preselector if self.PRESELECTOR_PATH_KEY in params: path = params[self.PRESELECTOR_PATH_KEY] logger.debug(f"Setting preselector RF path: {path}") preselector.set_state(path) - elif self.has_configurable_preselector: + elif sensor.has_configurable_preselector: # Require the RF path to be specified if the sensor has a preselector. raise ParameterException( f"No {self.PRESELECTOR_PATH_KEY} value specified in the YAML config." @@ -92,7 +74,7 @@ def configure_preselector(self, params: dict): # No preselector in use, so do not require an RF path pass - def get_sigmf_builder(self, schedule_entry: dict) -> None: + def get_sigmf_builder(self, sensor: Sensor, schedule_entry: dict) -> None: """ Set the `sigmf_builder` instance variable to an initialized SigMFBuilder. @@ -119,9 +101,14 @@ def get_sigmf_builder(self, schedule_entry: dict) -> None: ) sigmf_builder.set_action(action_obj) - if SENSOR_LOCATION is not None: - sigmf_builder.set_geolocation(SENSOR_LOCATION) - sigmf_builder.set_sensor(ntia_sensor.Sensor(**self.sensor_definition)) + if sensor.location is not None: + sigmf_builder.set_geolocation(sensor.location) + if self.sensor.capabilities is not None and hasattr( + self.sensor.capabilities, "sensor" + ): + sigmf_builder.set_sensor( + ntia_sensor.Sensor(**self.sensor.capabilities["sensor"]) + ) self.sigmf_builder = sigmf_builder @@ -141,5 +128,5 @@ def name(self): return get_parameter("name", self.parameters) @abstractmethod - def __call__(self, sigan=None, gps=None, schedule_entry=None, task_id=None): + def __call__(self, sensor=None, schedule_entry=None, task_id=None): pass diff --git a/scos_actions/actions/interfaces/measurement_action.py b/scos_actions/actions/interfaces/measurement_action.py index df8aa676..d568faab 100644 --- a/scos_actions/actions/interfaces/measurement_action.py +++ b/scos_actions/actions/interfaces/measurement_action.py @@ -24,13 +24,12 @@ def __init__(self, parameters): super().__init__(parameters) self.received_samples = 0 - def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): - self.sigan = sigan - self.gps = gps + def __call__(self, sensor, schedule_entry: dict, task_id: int): + self._sensor = sensor + self.get_sigmf_builder(sensor, schedule_entry) self.test_required_components() self.configure(self.parameters) measurement_result = self.execute(schedule_entry, task_id) - self.get_sigmf_builder(schedule_entry) # Initializes SigMFBuilder self.create_metadata(measurement_result) # Fill metadata data = self.transform_data(measurement_result) self.send_signals(task_id, self.sigmf_builder.metadata, data) @@ -52,8 +51,8 @@ def create_capture_segment( overload=overload, sigan_settings=sigan_settings, ) - sigan_cal = self.sigan.sigan_calibration_data - sensor_cal = self.sigan.sensor_calibration_data + sigan_cal = self.sensor.signal_analyzer.sigan_calibration_data + sensor_cal = self.sensor.signal_analyzer.sensor_calibration_data # Rename compression point keys if they exist # then set calibration metadata if it exists if sensor_cal is not None: @@ -147,7 +146,7 @@ def get_sigan_settings( def test_required_components(self): """Fail acquisition if a required component is not available.""" - if not self.sigan.is_available: + if not self.sensor.signal_analyzer.is_available: msg = "acquisition failed: signal analyzer required but not available" raise RuntimeError(msg) @@ -167,7 +166,7 @@ def acquire_data( + f" and {'' if cal_adjust else 'not '}applying gain adjustment based" + " on calibration data" ) - measurement_result = self.sigan.acquire_time_domain_samples( + measurement_result = self.sensor.signal_analyzer.acquire_time_domain_samples( num_samples, num_samples_skip=nskip, cal_adjust=cal_adjust, diff --git a/scos_actions/actions/logger.py b/scos_actions/actions/logger.py index 5ccf69f5..52d47d91 100644 --- a/scos_actions/actions/logger.py +++ b/scos_actions/actions/logger.py @@ -24,7 +24,7 @@ def __init__(self, loglvl=LOGLVL_INFO): super().__init__(parameters={"name": "logger"}) self.loglvl = loglvl - def __call__(self, sigan, gps, schedule_entry, task_id): + def __call__(self, sensor, schedule_entry, task_id): msg = "running test {name}/{tid}" schedule_entry_name = schedule_entry["name"] logger.log( diff --git a/scos_actions/actions/monitor_sigan.py b/scos_actions/actions/monitor_sigan.py index 222d1a34..f81ed716 100644 --- a/scos_actions/actions/monitor_sigan.py +++ b/scos_actions/actions/monitor_sigan.py @@ -3,7 +3,7 @@ import logging from scos_actions.actions.interfaces.action import Action -from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import trigger_api_restart logger = logging.getLogger(__name__) @@ -15,11 +15,10 @@ class MonitorSignalAnalyzer(Action): def __init__(self, parameters={"name": "monitor_sigan"}, gps=None): super().__init__(parameters=parameters) - def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): + def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): logger.debug("Performing signal analyzer health check") - self.sigan = sigan - self.gps = gps - healthy = self.sigan.healthy() + self._sensor = sensor + healthy = self.sensor.signal_analyzer.healthy() if healthy: logger.info("signal analyzer healthy") diff --git a/scos_actions/actions/sync_gps.py b/scos_actions/actions/sync_gps.py index 4d83fca8..872d0a28 100644 --- a/scos_actions/actions/sync_gps.py +++ b/scos_actions/actions/sync_gps.py @@ -15,15 +15,15 @@ class SyncGps(Action): def __init__(self, parameters): super().__init__(parameters=parameters) - def __call__(self, sigan, gps, schedule_entry: dict, task_id: int): + def __call__(self, sensor, schedule_entry: dict, task_id: int): logger.debug("Syncing to GPS") - self.gps = gps - dt = gps.get_gps_time() + self.sensor = sensor + dt = self.sensor.gps.get_gps_time() date_cmd = ["date", "-s", "{:}".format(dt.strftime("%Y/%m/%d %H:%M:%S"))] subprocess.check_output(date_cmd, shell=True) logger.info(f"Set system time to GPS time {dt.ctime()}") - location = gps.get_location() + location = sensor.gps.get_location() if location is None: raise RuntimeError("Unable to synchronize to GPS") diff --git a/scos_actions/actions/tests/test_acquire_single_freq_fft.py b/scos_actions/actions/tests/test_acquire_single_freq_fft.py index 6cac1751..3c5d6432 100644 --- a/scos_actions/actions/tests/test_acquire_single_freq_fft.py +++ b/scos_actions/actions/tests/test_acquire_single_freq_fft.py @@ -1,7 +1,7 @@ from scos_actions.actions.tests.utils import SENSOR_DEFINITION, check_metadata_fields -from scos_actions.capabilities import capabilities from scos_actions.discover import test_actions as actions from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import measurement_action_completed SINGLE_FREQUENCY_FFT_ACQUISITION = { @@ -11,7 +11,6 @@ "interval": None, "action": "test_single_frequency_m4s_action", } -capabilities["sensor"] = SENSOR_DEFINITION def test_detector(): @@ -30,9 +29,9 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_m4s_action"] assert action.description + sensor = Sensor(signal_analyzer=MockSignalAnalyzer()) action( - sigan=MockSignalAnalyzer(), - gps=None, + sensor=sensor, schedule_entry=SINGLE_FREQUENCY_FFT_ACQUISITION, task_id=1, ) @@ -90,5 +89,6 @@ def callback(sender, **kwargs): def test_num_samples_skip(): action = actions["test_single_frequency_m4s_action"] assert action.description - action(MockSignalAnalyzer(), None, SINGLE_FREQUENCY_FFT_ACQUISITION, 1) - assert action.sigan._num_samples_skip == action.parameters["nskip"] + sensor = Sensor(signal_analyzer=MockSignalAnalyzer()) + action(sensor, SINGLE_FREQUENCY_FFT_ACQUISITION, 1) + assert action.sensor.signal_analyzer._num_samples_skip == action.parameters["nskip"] diff --git a/scos_actions/actions/tests/test_monitor_sigan.py b/scos_actions/actions/tests/test_monitor_sigan.py index 39f13d98..0767ea84 100644 --- a/scos_actions/actions/tests/test_monitor_sigan.py +++ b/scos_actions/actions/tests/test_monitor_sigan.py @@ -1,5 +1,6 @@ from scos_actions.discover import test_actions as actions from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import trigger_api_restart MONITOR_SIGAN_SCHEDULE = { @@ -22,7 +23,8 @@ def callback(sender, **kwargs): action = actions["test_monitor_sigan"] mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = False - action(mock_sigan, None, MONITOR_SIGAN_SCHEDULE, 1) + sensor = Sensor(signal_analyzer=mock_sigan) + action(sensor, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == True # signal sent mock_sigan._is_available = True @@ -38,7 +40,8 @@ def callback(sender, **kwargs): action = actions["test_monitor_sigan"] mock_sigan = MockSignalAnalyzer() mock_sigan.times_to_fail_recv = 6 - action(mock_sigan, None, MONITOR_SIGAN_SCHEDULE, 1) + sensor = Sensor(signal_analyzer=mock_sigan) + action(sensor, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == True # signal sent @@ -54,5 +57,6 @@ def callback(sender, **kwargs): mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = True mock_sigan.set_times_to_fail_recv(0) - action(mock_sigan, None, MONITOR_SIGAN_SCHEDULE, 1) + sensor = Sensor(signal_analyzer=mock_sigan) + action(sensor, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == False # signal not sent diff --git a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py index 286bd546..fa5a1ad4 100644 --- a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py @@ -3,6 +3,7 @@ from scos_actions.actions.tests.utils import check_metadata_fields from scos_actions.discover import test_actions as actions from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import measurement_action_completed SINGLE_TIMEDOMAIN_IQ_ACQUISITION = { @@ -30,7 +31,8 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_iq_action"] assert action.description - action(MockSignalAnalyzer(), None, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + sensor = Sensor(signal_analyzer=MockSignalAnalyzer()) + action(sensor, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) assert _data.any() assert _metadata assert _task_id == 1 @@ -60,8 +62,9 @@ def test_required_components(): action = actions["test_single_frequency_m4s_action"] mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = False + sensor = Sensor(signal_analyzer=mock_sigan) with pytest.raises(RuntimeError): - action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + action(sensor, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) mock_sigan._is_available = True @@ -69,5 +72,6 @@ def test_num_samples_skip(): action = actions["test_single_frequency_iq_action"] assert action.description mock_sigan = MockSignalAnalyzer() - action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) - assert action.sigan._num_samples_skip == action.parameters["nskip"] + sensor = Sensor(signal_analyzer=mock_sigan) + action(sensor, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) + assert action.sensor.signal_analyzer._num_samples_skip == action.parameters["nskip"] diff --git a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py index e7ceace6..40c44141 100644 --- a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py @@ -1,5 +1,6 @@ from scos_actions.discover import test_actions as actions from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import measurement_action_completed SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION = { @@ -34,7 +35,8 @@ def callback(sender, **kwargs): action = actions["test_multi_frequency_iq_action"] assert action.description mock_sigan = MockSignalAnalyzer() - action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) + sensor = Sensor(signal_analyzer=mock_sigan) + action(sensor, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) for i in range(_count): assert _datas[i].any() assert _metadatas[i] @@ -47,8 +49,15 @@ def test_num_samples_skip(): action = actions["test_multi_frequency_iq_action"] assert action.description mock_sigan = MockSignalAnalyzer() - action(mock_sigan, None, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) + sensor = Sensor(signal_analyzer=mock_sigan) + action(sensor, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) if isinstance(action.parameters["nskip"], list): - assert action.sigan._num_samples_skip == action.parameters["nskip"][-1] + assert ( + action.sensor.signal_analyzer._num_samples_skip + == action.parameters["nskip"][-1] + ) else: - assert action.sigan._num_samples_skip == action.parameters["nskip"] + assert ( + action.sensor.signal_analyzer._num_samples_skip + == action.parameters["nskip"] + ) diff --git a/scos_actions/actions/tests/test_sync_gps.py b/scos_actions/actions/tests/test_sync_gps.py index 3971c52e..5b8a9311 100644 --- a/scos_actions/actions/tests/test_sync_gps.py +++ b/scos_actions/actions/tests/test_sync_gps.py @@ -5,6 +5,7 @@ from scos_actions.discover import test_actions from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import location_action_completed SYNC_GPS = { @@ -28,12 +29,13 @@ def callback(sender, **kwargs): location_action_completed.connect(callback) action = test_actions["test_sync_gps"] + sensor = Sensor(gps=MockGPS()) if sys.platform == "linux": - action(None, MockGPS(), SYNC_GPS, 1) + action(sensor, SYNC_GPS, 1) assert _latitude assert _longitude elif sys.platform == "win32": with pytest.raises(subprocess.CalledProcessError): - action(None, MockGPS(), SYNC_GPS, 1) + action(sensor, SYNC_GPS, 1) else: raise NotImplementedError("Test not implemented for current OS.") diff --git a/scos_actions/capabilities/__init__.py b/scos_actions/capabilities/__init__.py deleted file mode 100644 index d17bd457..00000000 --- a/scos_actions/capabilities/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -import hashlib -import json -import logging - -from scos_actions import utils -from scos_actions.metadata.utils import construct_geojson_point -from scos_actions.settings import FQDN, SENSOR_DEFINITION_FILE - -logger = logging.getLogger(__name__) -capabilities = {} -SENSOR_DEFINITION_HASH = None -SENSOR_LOCATION = None - -logger.debug(f"Loading {SENSOR_DEFINITION_FILE}") -try: - capabilities["sensor"] = utils.load_from_json(SENSOR_DEFINITION_FILE) -except Exception as e: - logger.warning( - f"Failed to load sensor definition file: {SENSOR_DEFINITION_FILE}" - + "\nAn empty sensor definition will be used" - ) - capabilities["sensor"] = {"sensor_spec": {"id": "unknown"}} - capabilities["sensor"]["sensor_sha512"] = "UNKNOWN SENSOR DEFINITION" - -# Extract location from sensor definition file, if present -if "location" in capabilities["sensor"]: - try: - sensor_loc = capabilities["sensor"].pop("location") - SENSOR_LOCATION = construct_geojson_point( - sensor_loc["x"], - sensor_loc["y"], - sensor_loc["z"] if "z" in sensor_loc else None, - ) - except: - logger.exception("Failed to get sensor location from sensor definition.") - -# Generate sensor definition file hash (SHA 512) -try: - if "sensor_sha512" not in capabilities["sensor"]: - sensor_def = json.dumps(capabilities["sensor"], sort_keys=True) - SENSOR_DEFINITION_HASH = hashlib.sha512(sensor_def.encode("UTF-8")).hexdigest() - capabilities["sensor"]["sensor_sha512"] = SENSOR_DEFINITION_HASH -except: - capabilities["sensor"]["sensor_sha512"] = "ERROR GENERATING HASH" - # SENSOR_DEFINITION_HASH is None, do not raise Exception - logger.exception(f"Unable to generate sensor definition hash") diff --git a/scos_actions/hardware/__init__.py b/scos_actions/hardware/__init__.py index 34af394d..e69de29b 100644 --- a/scos_actions/hardware/__init__.py +++ b/scos_actions/hardware/__init__.py @@ -1,75 +0,0 @@ -import importlib -import logging -from pathlib import Path - -from its_preselector.configuration_exception import ConfigurationException -from its_preselector.controlbyweb_web_relay import ControlByWebWebRelay - -from scos_actions import utils -from scos_actions.capabilities import capabilities -from scos_actions.settings import ( - PRESELECTOR_CLASS, - PRESELECTOR_CONFIG_FILE, - PRESELECTOR_MODULE, - SWITCH_CONFIGS_DIR, -) -from scos_actions.signals import register_component_with_status -from scos_actions.status.status_registration_handler import status_registration_handler - -logger = logging.getLogger(__name__) - - -def load_switches(switch_dir: Path) -> dict: - switch_dict = {} - if switch_dir is not None and switch_dir.is_dir(): - for f in switch_dir.iterdir(): - file_path = f.resolve() - logger.debug(f"loading switch config {file_path}") - conf = utils.load_from_json(file_path) - try: - switch = ControlByWebWebRelay(conf) - logger.debug(f"Adding {switch.id}") - - switch_dict[switch.id] = switch - logger.debug(f"Registering switch status for {switch.name}") - register_component_with_status.send(__name__, component=switch) - except ConfigurationException: - logger.error(f"Unable to configure switch defined in: {file_path}") - - return switch_dict - - -def load_preselector_from_file(preselector_config_file: Path): - if preselector_config_file is None: - return None - else: - try: - preselector_config = utils.load_from_json(preselector_config_file) - return load_preselector( - preselector_config, PRESELECTOR_MODULE, PRESELECTOR_CLASS - ) - except ConfigurationException: - logger.exception( - f"Unable to create preselector defined in: {preselector_config_file}" - ) - return None - - -def load_preselector(preselector_config, module, preselector_class_name): - if module is not None and preselector_class_name is not None: - preselector_module = importlib.import_module(module) - preselector_constructor = getattr(preselector_module, preselector_class_name) - ps = preselector_constructor(capabilities["sensor"], preselector_config) - if ps and ps.name: - logger.debug(f"Registering {ps.name} as status provider") - register_component_with_status.send(__name__, component=ps) - else: - ps = None - return ps - - -register_component_with_status.connect(status_registration_handler) -logger.debug("Connected status registration handler") -preselector = load_preselector_from_file(PRESELECTOR_CONFIG_FILE) -switches = load_switches(SWITCH_CONFIGS_DIR) -logger.debug(f"Loaded {(len(switches))} switches.") diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index 7c009662..1b2e9286 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -3,7 +3,6 @@ from abc import ABC, abstractmethod from scos_actions.calibration import sensor_calibration, sigan_calibration -from scos_actions.capabilities import capabilities from scos_actions.hardware.utils import power_cycle_sigan from scos_actions.utils import convert_string_to_millisecond_iso_format @@ -27,6 +26,7 @@ def __init__(self): self.sigan_calibration_data = {} self.sensor_calibration = sensor_calibration self.sigan_calibration = sigan_calibration + self._model = "Unknown" @property def last_calibration_time(self) -> str: @@ -147,12 +147,12 @@ def recompute_sigan_calibration_data(self, cal_args: list) -> None: logger.warning("Sigan calibration does not exist.") def get_status(self): - try: - sigan_model = capabilities["sensor"]["signal_analyzer"]["sigan_spec"][ - "model" - ] - if sigan_model.lower() in ["default", ""]: - raise KeyError - except KeyError: - sigan_model = str(self.__class__) - return {"model": sigan_model, "healthy": self.healthy()} + return {"model": self._model, "healthy": self.healthy()} + + @property + def model(self): + return self._model + + @model.setter + def model(self, value): + self._model = value diff --git a/scos_actions/hardware/tests/test_preselector.py b/scos_actions/hardware/tests/test_preselector.py deleted file mode 100644 index 83f02906..00000000 --- a/scos_actions/hardware/tests/test_preselector.py +++ /dev/null @@ -1,12 +0,0 @@ -from environs import Env - -from scos_actions.hardware import load_preselector - - -def test_load_preselector(): - preselector = load_preselector( - {"name": "test", "base_url": "http://127.0.0.1"}, - "its_preselector.web_relay_preselector", - "WebRelayPreselector", - ) - assert preselector is not None diff --git a/scos_actions/hardware/tests/test_status_handler.py b/scos_actions/hardware/tests/test_status_handler.py deleted file mode 100644 index 093f4e48..00000000 --- a/scos_actions/hardware/tests/test_status_handler.py +++ /dev/null @@ -1,9 +0,0 @@ -from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer -from scos_actions.signals import register_component_with_status -from scos_actions.status import status_monitor - - -def test_status_handler(): - mock_sigan = MockSignalAnalyzer() - register_component_with_status.send(__name__, component=mock_sigan) - status_monitor.status_components[0] == mock_sigan diff --git a/scos_actions/hardware/utils.py b/scos_actions/hardware/utils.py index 3f0ef0eb..08f97125 100644 --- a/scos_actions/hardware/utils.py +++ b/scos_actions/hardware/utils.py @@ -3,7 +3,6 @@ import psutil -from scos_actions.hardware import switches from scos_actions.hardware.hardware_configuration_exception import ( HardwareConfigurationException, ) @@ -126,7 +125,7 @@ def get_max_cpu_temperature(fahrenheit: bool = False) -> float: raise e -def power_cycle_sigan(): +def power_cycle_sigan(switches: dict): """ Performs a hard power cycle of the signal analyzer. This method requires power to the signal analyzer is controlled by a Web_Relay (see https://www.github.com/ntia/Preselector) and that the switch id of that diff --git a/scos_actions/signal_processing/calibration.py b/scos_actions/signal_processing/calibration.py index aafa70c5..06b6504d 100644 --- a/scos_actions/signal_processing/calibration.py +++ b/scos_actions/signal_processing/calibration.py @@ -4,7 +4,6 @@ import numpy as np from scipy.constants import Boltzmann -from scos_actions.hardware import preselector from scos_actions.signal_processing.unit_conversion import ( convert_celsius_to_fahrenheit, convert_celsius_to_kelvins, @@ -65,7 +64,7 @@ def y_factor( return noise_figure_dB, gain_dB -def get_linear_enr(cal_source_idx: int = None) -> float: +def get_linear_enr(preselector, cal_source_idx: int = None) -> float: """ Get the excess noise ratio of a calibration source. @@ -106,7 +105,7 @@ def get_linear_enr(cal_source_idx: int = None) -> float: return enr_linear -def get_temperature(sensor_idx: int = None) -> Tuple[float, float, float]: +def get_temperature(preselector, sensor_idx: int = None) -> Tuple[float, float, float]: """ Get the temperature from a preselector sensor. From 6843b97f10cdad6c176e8762f39644f823492565 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 12:27:56 -0700 Subject: [PATCH 25/90] add missing sensor class. --- scos_actions/hardware/sensor.py | 94 +++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 scos_actions/hardware/sensor.py diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py new file mode 100644 index 00000000..739e9b58 --- /dev/null +++ b/scos_actions/hardware/sensor.py @@ -0,0 +1,94 @@ +import hashlib + +from .mocks.mock_gps import MockGPS +from .mocks.mock_sigan import MockSignalAnalyzer +from .sigan_iface import SignalAnalyzerInterface + + +class Sensor: + def __init__( + self, + signal_analyzer=MockSignalAnalyzer, + gps=MockGPS(), + preselector=None, + switches={}, + location=None, + capabilities=None, + ): + self._signal_analyzer = signal_analyzer + self._gps = gps + self._preselector = preselector + self._switches = switches + self._location = location + self.capabilities = capabilities + + @property + def signal_analyzer(self): + return self._signal_analyzer + + @signal_analyzer.setter + def signal_analyzer(self, sigan): + self._signal_analyzer = sigan + + @property + def gps(self): + return self._gps + + @gps.setter + def gps(self, gps): + self._gps = gps + + @property + def preselector(self): + return self._preselector + + @preselector.setter + def preselector(self, preselector): + self._preselector = preselector + + @property + def switches(self): + return self._switches + + @switches.setter + def switches(self, switches): + self._switches = switches + + @property + def location(self): + return self._location + + @location.setter + def location(self, loc): + self._location = loc + + @property + def capabilities(self): + return self._capabilities + + @capabilities.setter + def capabilities(self, capabilities): + if capabilities is not None: + if "sensor_sha512" not in capabilities["sensor"]: + sensor_def = json.dumps(capabilities["sensor"], sort_keys=True) + SENSOR_DEFINITION_HASH = hashlib.sha512( + sensor_def.encode("UTF-8") + ).hexdigest() + capabilities["sensor"]["sensor_sha512"] = SENSOR_DEFINITION_HASH + self._capabilities = capabilities + else: + self._capabilities = None + + @property + def has_configurable_preselector(self): + if self._capabilities is None: + return False + else: + sensor_definition = self._capabilities["sensor"] + if ( + "preselector" in self.sensor_definition + and "rf_paths" in self.sensor_definition["preselector"] + ): + return True + else: + return False From eec0a47b4634e38cd701e14ff2cb6ab7840fe56a Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 13:12:39 -0700 Subject: [PATCH 26/90] add initialization code. --- scos_actions/initialization/__init__.py | 97 +++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 scos_actions/initialization/__init__.py diff --git a/scos_actions/initialization/__init__.py b/scos_actions/initialization/__init__.py new file mode 100644 index 00000000..1bcb6644 --- /dev/null +++ b/scos_actions/initialization/__init__.py @@ -0,0 +1,97 @@ +import hashlib +import importlib +import json +import logging +from pathlib import Path +from its_preselector.configuration_exception import ConfigurationException +from its_preselector.controlbyweb_web_relay import ControlByWebWebRelay + +from scos_actions import utils +from scos_actions.signals import register_component_with_status + +logger = logging.getLogger(__name__) + + +def load_switches(switch_dir: Path) -> dict: + switch_dict = {} + if switch_dir is not None and switch_dir.is_dir(): + for f in switch_dir.iterdir(): + file_path = f.resolve() + logger.debug(f"loading switch config {file_path}") + conf = utils.load_from_json(file_path) + try: + switch = ControlByWebWebRelay(conf) + logger.debug(f"Adding {switch.id}") + + switch_dict[switch.id] = switch + logger.debug(f"Registering switch status for {switch.name}") + register_component_with_status.send(__name__, component=switch) + except ConfigurationException: + logger.error(f"Unable to configure switch defined in: {file_path}") + + return switch_dict + + +def load_preselector_from_file(preselector_config_file: Path): + if preselector_config_file is None: + return None + else: + try: + preselector_config = utils.load_from_json(preselector_config_file) + return load_preselector( + preselector_config, + settings.PRESELECTOR_MODULE, + settings.PRESELECTOR_CLASS, + ) + except ConfigurationException: + logger.exception( + f"Unable to create preselector defined in: {preselector_config_file}" + ) + return None + + +def load_preselector( + preselector_config, module, preselector_class_name, sensor_definition +): + if module is not None and preselector_class_name is not None: + preselector_module = importlib.import_module(module) + preselector_constructor = getattr(preselector_module, preselector_class_name) + ps = preselector_constructor(sensor_definition, preselector_config) + if ps and ps.name: + logger.debug(f"Registering {ps.name} as status provider") + register_component_with_status.send(__name__, component=ps) + else: + ps = None + return ps + + +def load_capabilities(sensor_definition_file): + capabilities = {} + SENSOR_DEFINITION_HASH = None + SENSOR_LOCATION = None + + logger.debug(f"Loading {sensor_definition_file}") + try: + capabilities["sensor"] = utils.load_from_json(sensor_definition_file) + except Exception as e: + logger.warning( + f"Failed to load sensor definition file: {sensor_definition_file}" + + "\nAn empty sensor definition will be used" + ) + capabilities["sensor"] = {"sensor_spec": {"id": "unknown"}} + capabilities["sensor"]["sensor_sha512"] = "UNKNOWN SENSOR DEFINITION" + + # Generate sensor definition file hash (SHA 512) + try: + if "sensor_sha512" not in capabilities["sensor"]: + sensor_def = json.dumps(capabilities["sensor"], sort_keys=True) + SENSOR_DEFINITION_HASH = hashlib.sha512( + sensor_def.encode("UTF-8") + ).hexdigest() + capabilities["sensor"]["sensor_sha512"] = SENSOR_DEFINITION_HASH + except: + capabilities["sensor"]["sensor_sha512"] = "ERROR GENERATING HASH" + # SENSOR_DEFINITION_HASH is None, do not raise Exception + logger.exception(f"Unable to generate sensor definition hash") + + return capabilities From a05408100067250faee2ddb6a1ac5f0e9df85886 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 13:18:44 -0700 Subject: [PATCH 27/90] move initialization utilities back to scos-sensor. --- scos_actions/initialization/__init__.py | 97 ------------------------- 1 file changed, 97 deletions(-) delete mode 100644 scos_actions/initialization/__init__.py diff --git a/scos_actions/initialization/__init__.py b/scos_actions/initialization/__init__.py deleted file mode 100644 index 1bcb6644..00000000 --- a/scos_actions/initialization/__init__.py +++ /dev/null @@ -1,97 +0,0 @@ -import hashlib -import importlib -import json -import logging -from pathlib import Path -from its_preselector.configuration_exception import ConfigurationException -from its_preselector.controlbyweb_web_relay import ControlByWebWebRelay - -from scos_actions import utils -from scos_actions.signals import register_component_with_status - -logger = logging.getLogger(__name__) - - -def load_switches(switch_dir: Path) -> dict: - switch_dict = {} - if switch_dir is not None and switch_dir.is_dir(): - for f in switch_dir.iterdir(): - file_path = f.resolve() - logger.debug(f"loading switch config {file_path}") - conf = utils.load_from_json(file_path) - try: - switch = ControlByWebWebRelay(conf) - logger.debug(f"Adding {switch.id}") - - switch_dict[switch.id] = switch - logger.debug(f"Registering switch status for {switch.name}") - register_component_with_status.send(__name__, component=switch) - except ConfigurationException: - logger.error(f"Unable to configure switch defined in: {file_path}") - - return switch_dict - - -def load_preselector_from_file(preselector_config_file: Path): - if preselector_config_file is None: - return None - else: - try: - preselector_config = utils.load_from_json(preselector_config_file) - return load_preselector( - preselector_config, - settings.PRESELECTOR_MODULE, - settings.PRESELECTOR_CLASS, - ) - except ConfigurationException: - logger.exception( - f"Unable to create preselector defined in: {preselector_config_file}" - ) - return None - - -def load_preselector( - preselector_config, module, preselector_class_name, sensor_definition -): - if module is not None and preselector_class_name is not None: - preselector_module = importlib.import_module(module) - preselector_constructor = getattr(preselector_module, preselector_class_name) - ps = preselector_constructor(sensor_definition, preselector_config) - if ps and ps.name: - logger.debug(f"Registering {ps.name} as status provider") - register_component_with_status.send(__name__, component=ps) - else: - ps = None - return ps - - -def load_capabilities(sensor_definition_file): - capabilities = {} - SENSOR_DEFINITION_HASH = None - SENSOR_LOCATION = None - - logger.debug(f"Loading {sensor_definition_file}") - try: - capabilities["sensor"] = utils.load_from_json(sensor_definition_file) - except Exception as e: - logger.warning( - f"Failed to load sensor definition file: {sensor_definition_file}" - + "\nAn empty sensor definition will be used" - ) - capabilities["sensor"] = {"sensor_spec": {"id": "unknown"}} - capabilities["sensor"]["sensor_sha512"] = "UNKNOWN SENSOR DEFINITION" - - # Generate sensor definition file hash (SHA 512) - try: - if "sensor_sha512" not in capabilities["sensor"]: - sensor_def = json.dumps(capabilities["sensor"], sort_keys=True) - SENSOR_DEFINITION_HASH = hashlib.sha512( - sensor_def.encode("UTF-8") - ).hexdigest() - capabilities["sensor"]["sensor_sha512"] = SENSOR_DEFINITION_HASH - except: - capabilities["sensor"]["sensor_sha512"] = "ERROR GENERATING HASH" - # SENSOR_DEFINITION_HASH is None, do not raise Exception - logger.exception(f"Unable to generate sensor definition hash") - - return capabilities From fbc3400bd3560d327d2b8417e3568f856c62dbcd Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 14:12:04 -0700 Subject: [PATCH 28/90] remove erroneous self.sensor_definition --- scos_actions/actions/acquire_sea_data_product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 0a608549..2fb5daf0 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -952,7 +952,7 @@ def create_global_sensor_metadata(self, sensor: Sensor): self.sigmf_builder.set_sensor( ntia_sensor.Sensor( sensor_spec=ntia_core.HardwareSpec( - id=self.sensor_definition["sensor_spec"]["id"], + id=self.sensor.capabilities["sensor"]["sensor_spec"]["id"], ), sensor_sha512=sensor.capabilities["sensor"]["sensor_sha512"], ) From f2ce96e6151dfb550943664f617be6c6658e375d Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 14:39:33 -0700 Subject: [PATCH 29/90] comment out preselector settings --- scos_actions/settings.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index 00cf702a..0906f525 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -51,19 +51,17 @@ SENSOR_DEFINITION_FILE = Path(settings.SENSOR_DEFINITION_FILE) FQDN = settings.FQDN SCOS_SENSOR_GIT_TAG = settings.SCOS_SENSOR_GIT_TAG - if settings.PRESELECTOR_CONFIG: - PRESELECTOR_CONFIG_FILE = settings.PRESELECTOR_CONFIG - else: - PRESELECTOR_CONFIG_FILE = None - - if settings.PRESELECTOR_MODULE and settings.PRESELECTOR_CLASS: - PRESELECTOR_MODULE = settings.PRESELECTOR_MODULE - PRESELECTOR_CLASS = settings.PRESELECTOR_CLASS - else: - PRESELECTOR_MODULE = "its_preselector.web_relay_preselector" - PRESELECTOR_CLASS = "WebRelayPreselector" - if hasattr(settings, "SWITCH_CONFIGS_DIR"): - SWITCH_CONFIGS_DIR = Path(settings.SWITCH_CONFIGS_DIR) + # if settings.PRESELECTOR_CONFIG: + # PRESELECTOR_CONFIG_FILE = settings.PRESELECTOR_CONFIG + # else: + # PRESELECTOR_CONFIG_FILE = None + # + # if settings.PRESELECTOR_MODULE and settings.PRESELECTOR_CLASS: + # PRESELECTOR_MODULE = settings.PRESELECTOR_MODULE + # PRESELECTOR_CLASS = settings.PRESELECTOR_CLASS + # else: + # PRESELECTOR_MODULE = "its_preselector.web_relay_preselector" + # PRESELECTOR_CLASS = "WebRelayPreselector" SIGAN_POWER_SWITCH = None SIGAN_POWER_CYCLE_STATES = None From 37b67a778203a6e3495d89da352d80c7607812f6 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 15:29:35 -0700 Subject: [PATCH 30/90] Remove calibration initialization. Update sigan constructor to optionally take calibrations and add calibration properties. --- scos_actions/calibration/__init__.py | 90 ----------------------- scos_actions/hardware/mocks/mock_sigan.py | 14 +++- scos_actions/hardware/sigan_iface.py | 22 +++++- 3 files changed, 29 insertions(+), 97 deletions(-) diff --git a/scos_actions/calibration/__init__.py b/scos_actions/calibration/__init__.py index e78f4c7e..e69de29b 100644 --- a/scos_actions/calibration/__init__.py +++ b/scos_actions/calibration/__init__.py @@ -1,90 +0,0 @@ -import logging -from os import path - -from scos_actions.calibration.calibration import Calibration, load_from_json -from scos_actions.settings import ( - DEFAULT_CALIBRATION_FILE, - SENSOR_CALIBRATION_FILE, - SIGAN_CALIBRATION_FILE, -) - -logger = logging.getLogger(__name__) - - -def get_sigan_calibration(sigan_cal_file: str) -> Calibration: - """ - Load signal analyzer calibration data from file. - - :param sigan_cal_file: Path to JSON file containing signal - analyzer calibration data. - :return: The signal analyzer ``Calibration`` object. - """ - try: - sigan_cal = load_from_json(sigan_cal_file) - except Exception: - sigan_cal = None - logger.exception("Unable to load sigan calibration data, reverting to none") - return sigan_cal - - -def get_sensor_calibration(sensor_cal_file: str) -> Calibration: - """ - Load sensor calibration data from file. - - :param sensor_cal_file: Path to JSON file containing sensor - calibration data. - :return: The sensor ``Calibration`` object. - """ - try: - sensor_cal = load_from_json(sensor_cal_file) - except Exception: - sensor_cal = None - logger.exception("Unable to load sensor calibration data, reverting to none") - return sensor_cal - - -def check_for_default_calibration(cal_file_path: str, cal_type: str) -> bool: - default_cal = False - if cal_file_path == DEFAULT_CALIBRATION_FILE: - default_cal = True - logger.warning( - f"***************LOADING DEFAULT {cal_type} CALIBRATION***************" - ) - return default_cal - - -sensor_calibration = None -if SENSOR_CALIBRATION_FILE is None or SENSOR_CALIBRATION_FILE == "": - logger.warning( - "No sensor calibration file specified. Not loading calibration file." - ) -elif not path.exists(SENSOR_CALIBRATION_FILE): - logger.warning( - SENSOR_CALIBRATION_FILE - + " does not exist. Not loading sensor calibration file." - ) -else: - logger.debug(f"Loading sensor cal file: {SENSOR_CALIBRATION_FILE}") - default_sensor_calibration = check_for_default_calibration( - SENSOR_CALIBRATION_FILE, "Sensor" - ) - sensor_calibration = get_sensor_calibration(SENSOR_CALIBRATION_FILE) - -sigan_calibration = None -default_sensor_calibration = False -default_sigan_calibration = False -if SIGAN_CALIBRATION_FILE is None or SIGAN_CALIBRATION_FILE == "": - logger.warning("No sigan calibration file specified. Not loading calibration file.") -elif not path.exists(SIGAN_CALIBRATION_FILE): - logger.warning( - SIGAN_CALIBRATION_FILE + " does not exist. Not loading sigan calibration file." - ) -else: - logger.debug(f"Loading sigan cal file: {SIGAN_CALIBRATION_FILE}") - default_sigan_calibration = check_for_default_calibration( - SIGAN_CALIBRATION_FILE, "Sigan" - ) - sigan_calibration = get_sigan_calibration(SIGAN_CALIBRATION_FILE) - -if sensor_calibration: - logger.debug(f"Last sensor cal: {sensor_calibration.last_calibration_datetime}") diff --git a/scos_actions/hardware/mocks/mock_sigan.py b/scos_actions/hardware/mocks/mock_sigan.py index 28379ab6..791500b0 100644 --- a/scos_actions/hardware/mocks/mock_sigan.py +++ b/scos_actions/hardware/mocks/mock_sigan.py @@ -5,6 +5,7 @@ import numpy as np from scos_actions import __version__ as SCOS_ACTIONS_VERSION +from scos_actions.calibration.calibration import Calibration from scos_actions.hardware.sigan_iface import SignalAnalyzerInterface from scos_actions.utils import get_datetime_str_now @@ -24,8 +25,13 @@ class MockSignalAnalyzer(SignalAnalyzerInterface): gain: requested gain in dB """ - def __init__(self, randomize_values=False): - super().__init__() + def __init__( + self, + sensor_cal: Calibration = None, + sigan_cal: Calibration = None, + randomize_values=False, + ): + super().__init__(sensor_cal, sigan_cal) # Define the default calibration dicts self.DEFAULT_SIGAN_CALIBRATION = { "datetime": get_datetime_str_now(), @@ -202,9 +208,9 @@ def update_calibration(self, params): pass def recompute_sensor_calibration_data(self, cal_args: list) -> None: - if self.sensor_calibration is not None: + if self._sensor_calibration is not None: self.sensor_calibration_data.update( - self.sensor_calibration.get_calibration_dict(cal_args) + self._sensor_calibration.get_calibration_dict(cal_args) ) else: logger.warning("Sensor calibration does not exist.") diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index 1b2e9286..e2815757 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -21,11 +21,11 @@ class SignalAnalyzerInterface(ABC): - def __init__(self): + def __init__(self, sensor_cal=None, sigan_cal=None): self.sensor_calibration_data = {} self.sigan_calibration_data = {} - self.sensor_calibration = sensor_calibration - self.sigan_calibration = sigan_calibration + self._sensor_calibration = sensor_cal + self._sigan_calibration = sigan_cal self._model = "Unknown" @property @@ -156,3 +156,19 @@ def model(self): @model.setter def model(self, value): self._model = value + + @property + def sensor_calibration(self): + return self._sensor_calibration + + @sensor_calibration.setter + def sensor_calibration(self, cal): + self._sensor_calibration = cal + + @property + def sigan_calibration(self): + return self._sigan_calibration + + @sigan_calibration.setter + def sigan_calibration(self, cal): + self._sigan_calibration = cal From bd0689f8c5dd88b463561dac724de70f2d1f095c Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 15:40:57 -0700 Subject: [PATCH 31/90] Don't import cals from actions.calibration --- scos_actions/hardware/sigan_iface.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index e2815757..e8932ff5 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -2,7 +2,7 @@ import time from abc import ABC, abstractmethod -from scos_actions.calibration import sensor_calibration, sigan_calibration +from scos_actions.calibration.calibration import Calibration from scos_actions.hardware.utils import power_cycle_sigan from scos_actions.utils import convert_string_to_millisecond_iso_format @@ -21,7 +21,7 @@ class SignalAnalyzerInterface(ABC): - def __init__(self, sensor_cal=None, sigan_cal=None): + def __init__(self, sensor_cal: Calibration = None, sigan_cal: Calibration = None): self.sensor_calibration_data = {} self.sigan_calibration_data = {} self._sensor_calibration = sensor_cal @@ -32,7 +32,7 @@ def __init__(self, sensor_cal=None, sigan_cal=None): def last_calibration_time(self) -> str: """Returns the last calibration time from calibration data.""" return convert_string_to_millisecond_iso_format( - sensor_calibration.last_calibration_datetime + self.sensor_calibration.last_calibration_datetime ) @property @@ -129,9 +129,9 @@ def power_cycle_and_connect(self, sleep_time: float = 2.0) -> None: def recompute_sensor_calibration_data(self, cal_args: list) -> None: self.sensor_calibration_data = {} - if sensor_calibration is not None: + if self.sensor_calibration is not None: self.sensor_calibration_data.update( - sensor_calibration.get_calibration_dict(cal_args) + self.sensor_calibration.get_calibration_dict(cal_args) ) else: logger.warning("Sensor calibration does not exist.") @@ -139,9 +139,9 @@ def recompute_sensor_calibration_data(self, cal_args: list) -> None: def recompute_sigan_calibration_data(self, cal_args: list) -> None: self.sigan_calibration_data = {} """Set the sigan calibration data based on the current tuning""" - if sigan_calibration is not None: + if self.sigan_calibration is not None: self.sigan_calibration_data.update( - sigan_calibration.get_calibration_dict(cal_args) + self.sigan_calibration.get_calibration_dict(cal_args) ) else: logger.warning("Sigan calibration does not exist.") From f4371ccab46ae4560701a160c0a7f1beb959f54e Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 15:55:25 -0700 Subject: [PATCH 32/90] add is_default property to Calibration. Remove cal imports in y_factor cal. --- scos_actions/actions/calibrate_y_factor.py | 10 ++++++---- scos_actions/calibration/calibration.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 71358f43..e1e989f2 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -78,7 +78,6 @@ from scos_actions import utils from scos_actions.actions.interfaces.action import Action -from scos_actions.calibration import default_sensor_calibration, sensor_calibration from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS from scos_actions.settings import SENSOR_CALIBRATION_FILE @@ -257,18 +256,21 @@ def calibrate(self, params): noise_on_data = sosfilt(self.iir_sos, noise_on_measurement_result["data"]) noise_off_data = sosfilt(self.iir_sos, noise_off_measurement_result["data"]) else: - if default_sensor_calibration: + if self.sensor.signal_analyzer.sensor_calibration.is_default: raise Exception( "Calibrations without IIR filter cannot be performed with default calibration." ) logger.debug("Skipping IIR filtering") # Get ENBW from sensor calibration - assert set(sensor_calibration.calibration_parameters) <= set( + assert set( + self.sensor.signal_analyzer.sensor_calibration.calibration_parameters + ) <= set( sigan_params.keys() ), f"Action parameters do not include all required calibration parameters" cal_args = [ - sigan_params[k] for k in sensor_calibration.calibration_parameters + sigan_params[k] + for k in self.sensor.signal_analyzer.sensor_calibration.calibration_parameters ] self.sigan.recompute_sensor_calibration_data(cal_args) enbw_hz = self.sigan.sensor_calibration_data["enbw"] diff --git a/scos_actions/calibration/calibration.py b/scos_actions/calibration/calibration.py index 9d724af7..3fd47e31 100644 --- a/scos_actions/calibration/calibration.py +++ b/scos_actions/calibration/calibration.py @@ -16,12 +16,23 @@ class Calibration: calibration_data: dict clock_rate_lookup_by_sample_rate: List[Dict[str, float]] + def __init__(self): + self._is_default = False + def __post_init__(self): # Convert key names in calibration_data to strings # This means that formatting will always match between # native types provided in Python and data loaded from JSON self.calibration_data = json.loads(json.dumps(self.calibration_data)) + @property + def is_default(self) -> bool: + return self._is_default + + @is_default.setter + def is_default(self, val: bool): + self._is_default = val + def get_clock_rate(self, sample_rate: Union[float, int]) -> Union[float, int]: """Find the clock rate (Hz) using the given sample_rate (samples per second)""" for mapping in self.clock_rate_lookup_by_sample_rate: From 6f6505e7c1b708d2922332eb5326ac64327a09fc Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 16:25:50 -0700 Subject: [PATCH 33/90] Move status_registration_handler to scos-sensor. Add register_signal_analyzer signal. --- scos_actions/signals.py | 2 ++ scos_actions/status/__init__.py | 2 -- scos_actions/status/status_registration_handler.py | 13 ------------- 3 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 scos_actions/status/status_registration_handler.py diff --git a/scos_actions/signals.py b/scos_actions/signals.py index 93e9c575..30c40845 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -10,3 +10,5 @@ # Provides argument: 'component' register_component_with_status = Signal() + +register_signal_analyzer = Signal() diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py index 410faa6d..c6d73ec9 100644 --- a/scos_actions/status/__init__.py +++ b/scos_actions/status/__init__.py @@ -3,5 +3,3 @@ from scos_actions.status.status_monitor import StatusMonitor start_time = datetime.datetime.utcnow() - -status_monitor = StatusMonitor() diff --git a/scos_actions/status/status_registration_handler.py b/scos_actions/status/status_registration_handler.py deleted file mode 100644 index 6c547e6a..00000000 --- a/scos_actions/status/status_registration_handler.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging - -from . import status_monitor - -logger = logging.getLogger(__name__) - - -def status_registration_handler(sender, **kwargs): - try: - logger.debug(f"Registering {sender} as status provider") - status_monitor.add_component(kwargs["component"]) - except: - logger.exception("Error registering status component") From 63bf60a10d513277266d2275760204399a577d6b Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 16:54:18 -0700 Subject: [PATCH 34/90] remove import --- scos_actions/calibration/tests/test_calibration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scos_actions/calibration/tests/test_calibration.py b/scos_actions/calibration/tests/test_calibration.py index 75fc1eb7..30878d5f 100644 --- a/scos_actions/calibration/tests/test_calibration.py +++ b/scos_actions/calibration/tests/test_calibration.py @@ -9,7 +9,6 @@ import pytest -from scos_actions.calibration import sensor_calibration, sigan_calibration from scos_actions.calibration.calibration import ( Calibration, filter_by_parameter, From e1742538daef1e3115e8887284dc7282938733af Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 19:06:50 -0700 Subject: [PATCH 35/90] use dataclass for is_default. --- scos_actions/calibration/calibration.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/scos_actions/calibration/calibration.py b/scos_actions/calibration/calibration.py index 3fd47e31..eb96e400 100644 --- a/scos_actions/calibration/calibration.py +++ b/scos_actions/calibration/calibration.py @@ -15,9 +15,7 @@ class Calibration: calibration_parameters: List[str] calibration_data: dict clock_rate_lookup_by_sample_rate: List[Dict[str, float]] - - def __init__(self): - self._is_default = False + is_default: bool def __post_init__(self): # Convert key names in calibration_data to strings @@ -25,14 +23,6 @@ def __post_init__(self): # native types provided in Python and data loaded from JSON self.calibration_data = json.loads(json.dumps(self.calibration_data)) - @property - def is_default(self) -> bool: - return self._is_default - - @is_default.setter - def is_default(self, val: bool): - self._is_default = val - def get_clock_rate(self, sample_rate: Union[float, int]) -> Union[float, int]: """Find the clock rate (Hz) using the given sample_rate (samples per second)""" for mapping in self.clock_rate_lookup_by_sample_rate: From 7184c4b86d1e82897427e5c5a2f4a138977a6fce Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 19:13:15 -0700 Subject: [PATCH 36/90] set is_default in Calibration creation. --- scos_actions/calibration/calibration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scos_actions/calibration/calibration.py b/scos_actions/calibration/calibration.py index eb96e400..666cdb36 100644 --- a/scos_actions/calibration/calibration.py +++ b/scos_actions/calibration/calibration.py @@ -121,7 +121,7 @@ def update( outfile.write(json.dumps(cal_dict)) -def load_from_json(fname: Path) -> Calibration: +def load_from_json(fname: Path, is_default: bool) -> Calibration: """ Load a calibration from a JSON file. @@ -158,6 +158,7 @@ def load_from_json(fname: Path) -> Calibration: calibration["calibration_parameters"], calibration["calibration_data"], calibration["clock_rate_lookup_by_sample_rate"], + is_default, ) From 103034439b629dcd4decd160012dfdcbd05194fc Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 21:27:14 -0700 Subject: [PATCH 37/90] correct sensor_definition reference in Sensor. --- scos_actions/hardware/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index 739e9b58..c1067bdb 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -86,8 +86,8 @@ def has_configurable_preselector(self): else: sensor_definition = self._capabilities["sensor"] if ( - "preselector" in self.sensor_definition - and "rf_paths" in self.sensor_definition["preselector"] + "preselector" in sensor_definition + and "rf_paths" in sensor_definition["preselector"] ): return True else: From 58d7fff81b10cf381b7df3a3c0e0dd75c8071c32 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 21:36:39 -0700 Subject: [PATCH 38/90] correct refs to signal_analyzer in actions. --- .../actions/acquire_sea_data_product.py | 31 ++++++++++++------- scos_actions/actions/calibrate_y_factor.py | 4 +-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 2fb5daf0..7b8d65a4 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -627,10 +627,14 @@ def capture_iq(self, params: dict) -> dict: nskip = utils.get_parameter(NUM_SKIP, params) num_samples = int(params[SAMPLE_RATE] * duration_ms * 1e-3) # Collect IQ data - measurement_result = self.sigan.acquire_time_domain_samples(num_samples, nskip) + measurement_result = self.sensor.signal_analyzer.acquire_time_domain_samples( + num_samples, nskip + ) # Store some metadata with the IQ measurement_result.update(params) - measurement_result["sensor_cal"] = self.sigan.sensor_calibration_data + measurement_result[ + "sensor_cal" + ] = self.sensor.signal_analyzer.sensor_calibration_data toc = perf_counter() logger.debug( f"IQ Capture ({duration_ms} ms @ {(params[FREQUENCY]/1e6):.1f} MHz) completed in {toc-tic:.2f} s." @@ -779,11 +783,11 @@ def capture_diagnostics( "scos_sensor_version": SCOS_SENSOR_GIT_TAG, "scos_actions_version": SCOS_ACTIONS_VERSION, "scos_sigan_plugin": ntia_diagnostics.ScosPlugin( - name="scos_tekrsa", version=self.sigan.plugin_version + name="scos_tekrsa", version=self.sensor.signal_analyzer.plugin_version ), "preselector_api_version": PRESELECTOR_API_VERSION, - "sigan_firmware_version": self.sigan.firmware_version, - "sigan_api_version": self.sigan.api_version, + "sigan_firmware_version": self.sensor.signal_analyzer.firmware_version, + "sigan_api_version": self.sensor.signal_analyzer.api_version, } toc = perf_counter() @@ -832,7 +836,10 @@ def add_temperature_and_humidity_sensors( logger.warning("No internal_temp found in switch status.") try: switch_diag["temperature_sensors"].append( - {"name": "sigan_internal_temp", "value": self.sigan.temperature} + { + "name": "sigan_internal_temp", + "value": self.sensor.signal_analyzer.temperature, + } ) except: logger.warning("Unable to read internal sigan temperature") @@ -960,11 +967,11 @@ def create_global_sensor_metadata(self, sensor: Sensor): def test_required_components(self): """Fail acquisition if a required component is not available.""" - if not self.sigan.is_available: + if not self.sensor.signal_analyzer.is_available: msg = "Acquisition failed: signal analyzer is not available" trigger_api_restart.send(sender=self.__class__) raise RuntimeError(msg) - if not self.sigan.healthy(): + if not self.sensor.signal_analyzer.healthy(): trigger_api_restart.send(sender=self.__class__) return None @@ -1111,7 +1118,7 @@ def create_capture_segment( capture_segment = CaptureSegment( sample_start=channel_idx * self.total_channel_data_length, - frequency=self.sigan.frequency, + frequency=self.sensor.signal_analyzer.frequency, datetime=measurement_result["capture_time"], duration=measurement_result[DURATION_MS], overload=measurement_result["overload"], @@ -1123,9 +1130,9 @@ def create_capture_segment( reference=DATA_REFERENCE_POINT, ), sigan_settings=ntia_sensor.SiganSettings( - reference_level=self.sigan.reference_level, - attenuation=self.sigan.attenuation, - preamp_enable=self.sigan.preamp_enable, + reference_level=self.sensor.signal_analyzer.reference_level, + attenuation=self.sensor.signal_analyzer.attenuation, + preamp_enable=self.sensor.signal_analyzer.preamp_enable, ), ) self.sigmf_builder.add_capture(capture_segment) diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index e1e989f2..3f592475 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -379,9 +379,9 @@ def description(self): def test_required_components(self): """Fail acquisition if a required component is not available.""" - if not self.sigan.is_available: + if not self.sensor.signal_analyzer.is_available: msg = "acquisition failed: signal analyzer required but not available" trigger_api_restart.send(sender=self.__class__) raise RuntimeError(msg) - if not self.sigan.healthy(): + if not self.sensor.signal_analyzer.healthy(): trigger_api_restart.send(sender=self.__class__) From 8f17a92b3ef48018ce0a4cc82af568a2a6c1afce Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 21:50:30 -0700 Subject: [PATCH 39/90] add sensor in call to configure preselector. --- scos_actions/actions/acquire_sea_data_product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 7b8d65a4..76436f11 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -511,7 +511,7 @@ def __call__(self, sensor, schedule_entry, task_id): _ = psutil.cpu_percent(interval=None) # Initialize CPU usage monitor self.test_required_components() - self.configure_preselector(self.rf_path) + self.configure_preselector(self.sensor, self.rf_path) # Initialize metadata object self.get_sigmf_builder( From d23ea9f06cc200b28a6cb499c6f688414b173a1e Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 22:13:39 -0700 Subject: [PATCH 40/90] use sensor capabilities to set location in data product action. --- scos_actions/actions/acquire_sea_data_product.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 76436f11..f07064bc 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -55,6 +55,7 @@ ntia_sensor, ) from scos_actions.metadata.structs.capture import CaptureSegment +from scos_actions.metadata.utils import construct_geojson_point from scos_actions.settings import SCOS_SENSOR_GIT_TAG from scos_actions.signal_processing.apd import get_apd from scos_actions.signal_processing.fft import ( @@ -1165,13 +1166,16 @@ def get_sigmf_builder( # Do not include lengthy description ) sigmf_builder.set_action(action_obj) - - if SENSOR_LOCATION is not None: - sigmf_builder.set_geolocation(SENSOR_LOCATION) + location = self.sensor.capabilities["sensor"]["location"] + if location is not None: + location = construct_geojson_point( + location["x"], + location["y"], + sensor_loc["z"] if "z" in location else None, + ) + sigmf_builder.set_geolocation(location) else: - logger.error("Set the sensor location in the SCOS admin web UI") - raise RuntimeError - + raise Exception("Sensor does not have a location defined.") sigmf_builder.set_data_type(self.is_complex(), bit_width=16, endianness="") sigmf_builder.set_sample_rate(sample_rate_Hz) sigmf_builder.set_num_channels(len(iter_params)) From 4d4fb43a81510aa94ca75902791ac1d2f049543f Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sat, 13 Jan 2024 22:23:21 -0700 Subject: [PATCH 41/90] correct ref to switches and location. --- scos_actions/actions/acquire_sea_data_product.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index f07064bc..dc7e655e 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -708,7 +708,7 @@ def capture_diagnostics( switch_diag = {} all_switch_status = {} # Add status for any switch - for switch in switches.values(): + for switch in self.sensor.switches.values(): switch_status = switch.get_status() del switch_status["name"] del switch_status["healthy"] @@ -1169,9 +1169,7 @@ def get_sigmf_builder( location = self.sensor.capabilities["sensor"]["location"] if location is not None: location = construct_geojson_point( - location["x"], - location["y"], - sensor_loc["z"] if "z" in location else None, + location["x"], location["y"], location["z"] if "z" in location else None ) sigmf_builder.set_geolocation(location) else: From 4abce51a2bf88f1c8a75974518cd1ec4e495762a Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sun, 14 Jan 2024 11:33:50 -0700 Subject: [PATCH 42/90] Add register sensor signal. Use sensor.location in sigmf metadata in sea data product action. --- scos_actions/actions/acquire_sea_data_product.py | 8 ++------ scos_actions/signals.py | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index dc7e655e..1c9a3ac5 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -1166,12 +1166,8 @@ def get_sigmf_builder( # Do not include lengthy description ) sigmf_builder.set_action(action_obj) - location = self.sensor.capabilities["sensor"]["location"] - if location is not None: - location = construct_geojson_point( - location["x"], location["y"], location["z"] if "z" in location else None - ) - sigmf_builder.set_geolocation(location) + if self.sensor.location is not None: + sigmf_builder.set_geolocation(self.sensor.location) else: raise Exception("Sensor does not have a location defined.") sigmf_builder.set_data_type(self.is_complex(), bit_width=16, endianness="") diff --git a/scos_actions/signals.py b/scos_actions/signals.py index 30c40845..ee7c913e 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -11,4 +11,8 @@ # Provides argument: 'component' register_component_with_status = Signal() +# Provides argument: signal_analyzer register_signal_analyzer = Signal() + +# Provides argument: sensor +register_sensor = Signal() From fae07d4fdeeb40ede0095d8a7c990a6c8af23caa Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sun, 14 Jan 2024 12:02:57 -0700 Subject: [PATCH 43/90] pass sensor to global sensor metadata method. --- scos_actions/actions/acquire_sea_data_product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 1c9a3ac5..b80004a7 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -522,7 +522,7 @@ def __call__(self, sensor, schedule_entry, task_id): schedule_entry, self.iteration_params, ) - self.create_global_sensor_metadata() + self.create_global_sensor_metadata(self.sensor) self.create_global_data_product_metadata() # Initialize remote supervisor actors for IQ processing From 0dba7218471540c491ff8dc48768ecbadd9935ed Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sun, 14 Jan 2024 12:09:36 -0700 Subject: [PATCH 44/90] pass sensor to capture_diagnostics --- scos_actions/actions/acquire_sea_data_product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index b80004a7..fec488ed 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -608,7 +608,7 @@ def __call__(self, sensor, schedule_entry, task_id): self.sigmf_builder.set_median_channel_powers(median_ch_pwrs) # Get diagnostics last to record action runtime self.capture_diagnostics( - action_start_tic, cpu_speed + self.sensor, action_start_tic, cpu_speed ) # Add diagnostics to metadata measurement_action_completed.send( From a9bb6b355f21167b9e38af285ff929dd1fd24b69 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sun, 14 Jan 2024 14:00:48 -0700 Subject: [PATCH 45/90] add type hints to Sensor --- scos_actions/hardware/sensor.py | 42 ++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index c1067bdb..5a41c99d 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -1,5 +1,9 @@ import hashlib +from its_preselector.preselector import Preselector +from its_preselector.web_relay import WebRelay + +from .gps_iface import GPSInterface from .mocks.mock_gps import MockGPS from .mocks.mock_sigan import MockSignalAnalyzer from .sigan_iface import SignalAnalyzerInterface @@ -8,12 +12,12 @@ class Sensor: def __init__( self, - signal_analyzer=MockSignalAnalyzer, - gps=MockGPS(), - preselector=None, - switches={}, - location=None, - capabilities=None, + signal_analyzer: SignalAnalyzerInterface = MockSignalAnalyzer, + gps: GPSInterface = MockGPS(), + preselector: Preselector = None, + switches: dict[str, WebRelay] = {}, + location: dict = None, + capabilities: dict = None, ): self._signal_analyzer = signal_analyzer self._gps = gps @@ -23,51 +27,51 @@ def __init__( self.capabilities = capabilities @property - def signal_analyzer(self): + def signal_analyzer(self) -> SignalAnalyzerInterface: return self._signal_analyzer @signal_analyzer.setter - def signal_analyzer(self, sigan): + def signal_analyzer(self, sigan: SignalAnalyzerInterface): self._signal_analyzer = sigan @property - def gps(self): + def gps(self) -> GPSInterface: return self._gps @gps.setter - def gps(self, gps): + def gps(self, gps: GPSInterface): self._gps = gps @property - def preselector(self): + def preselector(self) -> Preselector: return self._preselector @preselector.setter - def preselector(self, preselector): + def preselector(self, preselector: Preselector): self._preselector = preselector @property - def switches(self): + def switches(self) -> dict[str, WebRelay]: return self._switches @switches.setter - def switches(self, switches): + def switches(self, switches: dict[str, WebRelay]): self._switches = switches @property - def location(self): + def location(self) -> dict: return self._location @location.setter - def location(self, loc): + def location(self, loc: dict): self._location = loc @property - def capabilities(self): + def capabilities(self) -> dict: return self._capabilities @capabilities.setter - def capabilities(self, capabilities): + def capabilities(self, capabilities: dict): if capabilities is not None: if "sensor_sha512" not in capabilities["sensor"]: sensor_def = json.dumps(capabilities["sensor"], sort_keys=True) @@ -80,7 +84,7 @@ def capabilities(self, capabilities): self._capabilities = None @property - def has_configurable_preselector(self): + def has_configurable_preselector(self) -> bool: if self._capabilities is None: return False else: From 946a14aa5841312c22b3442c681a6f54fafdea05 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sun, 14 Jan 2024 14:02:37 -0700 Subject: [PATCH 46/90] add type hints to SignalAnalyzerInterface --- scos_actions/hardware/sigan_iface.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index e8932ff5..a2e185b1 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -146,29 +146,29 @@ def recompute_sigan_calibration_data(self, cal_args: list) -> None: else: logger.warning("Sigan calibration does not exist.") - def get_status(self): + def get_status(self) -> dict: return {"model": self._model, "healthy": self.healthy()} @property - def model(self): + def model(self) -> str: return self._model @model.setter - def model(self, value): + def model(self, value: str): self._model = value @property - def sensor_calibration(self): + def sensor_calibration(self) -> Calibration: return self._sensor_calibration @sensor_calibration.setter - def sensor_calibration(self, cal): + def sensor_calibration(self, cal: Calibration): self._sensor_calibration = cal @property - def sigan_calibration(self): + def sigan_calibration(self) -> Calibration: return self._sigan_calibration @sigan_calibration.setter - def sigan_calibration(self, cal): + def sigan_calibration(self, cal: Calibration): self._sigan_calibration = cal From 8410505a5f6e5408ed8e11999aff95e20128900a Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sun, 14 Jan 2024 14:20:15 -0700 Subject: [PATCH 47/90] fix dict type hints and update tests. --- scos_actions/calibration/tests/test_calibration.py | 7 +++++-- scos_actions/hardware/sensor.py | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/scos_actions/calibration/tests/test_calibration.py b/scos_actions/calibration/tests/test_calibration.py index 30878d5f..f81de685 100644 --- a/scos_actions/calibration/tests/test_calibration.py +++ b/scos_actions/calibration/tests/test_calibration.py @@ -180,7 +180,7 @@ def setup_calibration_file(self, tmpdir): json.dump(cal_data, file, indent=4) # Load the data back in - self.sample_cal = load_from_json(self.calibration_file) + self.sample_cal = load_from_json(self.calibration_file, False) # Create a list of previous points to ensure that we don't repeat self.pytest_points = [] @@ -234,6 +234,7 @@ def test_get_calibration_dict_exact_match_lookup(self): calibration_params, calibration_data, clock_rate_lookup_by_sample_rate, + False, ) cal_data = cal.get_calibration_dict([100.0, 200.0]) assert cal_data["NF"] == "NF at 100, 200" @@ -251,6 +252,7 @@ def test_get_calibration_dict_within_range(self): calibration_params, calibration_data, clock_rate_lookup_by_sample_rate, + False, ) with pytest.raises(CalibrationException) as e_info: cal_data = cal.get_calibration_dict([100.0, 250.0]) @@ -290,12 +292,13 @@ def test_update(self): calibration_params, calibration_data, clock_rate_lookup_by_sample_rate, + False, ) action_params = {"sample_rate": 100.0, "frequency": 200.0} update_time = get_datetime_str_now() test_cal_path = Path("test_calibration.json") cal.update(action_params, update_time, 30.0, 5.0, 21, test_cal_path) - cal_from_file = load_from_json(test_cal_path) + cal_from_file = load_from_json(test_cal_path, False) test_cal_path.unlink() file_utc_time = parse_datetime_iso_format_str(cal.last_calibration_datetime) cal_time_utc = parse_datetime_iso_format_str(update_time) diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index 5a41c99d..d8f7fbb0 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -1,4 +1,5 @@ import hashlib +from typing import Dict from its_preselector.preselector import Preselector from its_preselector.web_relay import WebRelay @@ -15,7 +16,7 @@ def __init__( signal_analyzer: SignalAnalyzerInterface = MockSignalAnalyzer, gps: GPSInterface = MockGPS(), preselector: Preselector = None, - switches: dict[str, WebRelay] = {}, + switches: Dict[str, WebRelay] = {}, location: dict = None, capabilities: dict = None, ): @@ -51,11 +52,11 @@ def preselector(self, preselector: Preselector): self._preselector = preselector @property - def switches(self) -> dict[str, WebRelay]: + def switches(self) -> Dict[str, WebRelay]: return self._switches @switches.setter - def switches(self, switches: dict[str, WebRelay]): + def switches(self, switches: Dict[str, WebRelay]): self._switches = switches @property From a2cf95c6b693b25e024478577d924ecb1901d606 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 11:51:36 -0700 Subject: [PATCH 48/90] Update readme and add type hint for Sensor to the __call__ method. --- README.md | 34 ++++++++++------------- scos_actions/actions/interfaces/action.py | 3 +- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 80292eb0..1aaac687 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,9 @@ architecture. - `scos_actions/configs/actions`: This folder contains the YAML files with the parameters used to initialize the actions described above. - `scos_actions/discover`: This includes the code to read YAML files and make actions - available to scos-sensor. + available to scos-sensor. The [scos-sensor actions module](https://github.com/NTIA/scos-sensor/blob/master/src/actions/__init__.py) + looks for any other modules that begin with scos_. For any scos module, scos-sensor + imports its discover module to load any actions or actions_types. - `scos_actions/hardware`: This includes the signal analyzer interface and GPS interface used by the actions and the mock signal analyzer. The signal analyzer interface is intended to represent universal functionality that is common across all signal @@ -181,9 +183,6 @@ yaml_actions, yaml_test_actions = init(yaml_dir=ACTION_DEFINITIONS_DIR) actions.update(yaml_actions) ``` -Pass the implementation of the signal analyzer interface and the directory where the -YAML files are located to the `init` method. - If no existing action class meets your needs, see [Writing Custom Actions]( #writing-custom-actions). @@ -304,7 +303,9 @@ scos-sensor. Start by looking at the [`Action` base class](scos_actions/actions/interfaces/action.py). It includes some logic to parse a description and summary out of the action class's -docstring, and a `__call__` method that must be overridden. +docstring, and a `__call__` method that must be overridden. Actions are only instantiated +with parameters. The signal analyzer implementation will be passed to the action at +execution time through the __call__ method's Sensor object. A new custom action can inherit from the existing action classes to reuse and build upon existing functionality. A [`MeasurementAction` base class](scos_actions/actions/interfaces/measurement_action.py), @@ -318,7 +319,7 @@ enables SCOS Sensor to do something with the results of the action. This could r from storing measurement data to recycling a Docker container or to fixing an unhealthy connection to the signal analyzer. You can see the available signals in [`scos_actions/signals.py`](scos_actions/signals.py). -The following signals are currently offered: +The following signals are currently offered for actions: - `measurement_action_completed` - signal expects task_id, data, and metadata - `location_action_completed` - signal expects latitude and longitude @@ -332,11 +333,7 @@ scos-sensor to receive the signals and process the results. A custom action meant to be re-used by other plugins can live in SCOS Actions. It can be instantiated using a YAML file, or directly in the `actions` dictionary in the -`discover/__init__.py` module. This can be done in SCOS Actions with a mock signal -analyzer. Plugins supporting other hardware would need to import the action from -SCOS Actions. Then it can be instantiated in that plugin’s actions dictionary in its -discover module, or in a YAML file living in that plugin (as long as its discover -module includes the required code to parse the YAML files). +`discover/__init__.py` module. ##### Adding system or hardware specific custom action @@ -372,16 +369,14 @@ another signal analyzer with a Python API. custom actions that are unique to the hardware. See [Adding Actions](#adding-actions) subsection above. - In the new repository, add a `discover/__init__.py` file. This should contain a - dictionary called `actions` with a key of action name and a value of action object. + dictionary called `actions` with keys of action names and values of action instances. + If the repository also includes new action implementations, it should also expose a + dictionary named `action_types` with keys of actions names and values of action classes. You can use the [init()](scos_actions/discover/__init__.py) and/or the [load_from_yaml()](scos_actions/discover/yaml.py) methods provided in this repository - to look for YAML files and initialize actions. These methods allow you to pass your - new signal analyzer object to the action's constructor. You can use the existing + to look for YAML files and initialize actions. You can use the existing action classes [defined in this repository](scos_actions/actions/__init__.py) or - [create custom actions](#writing-custom-actions). If the signal analyzer supports - calibration, you should also add a `get_last_calibration_time()` method to - `discover/__init__.py` to enable the status endpoint to report the last calibration - time. + [create custom actions](#writing-custom-actions). If your signal analyzer doesn't have a Python API, you'll need a Python wrapper that calls out to your signal analyzer's available API and reads the samples back into @@ -389,7 +384,8 @@ Python. Libraries such as [SWIG](http://www.swig.org/) can automatically generat Python wrappers for programs written in C/C++. The next step in supporting a different signal analyzer is to create a class that -inherits from the [GPSInterface](scos_actions/hardware/gps_iface.py) abstract class. +inherits from the [GPSInterface](scos_actions/hardware/gps_iface.py) abstract class if +the signal analyzer includes GPS capabilities. Then add the `sync_gps` and `monitor_sigan` actions to your `actions` dictionary, passing the gps object to the `SyncGps` constructor, and the signal analyzer object to the `MonitorSignalAnalyzer` constructor. See the example in the [Adding Actions diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index b60cf4ea..92ee124b 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -2,7 +2,6 @@ from abc import ABC, abstractmethod from copy import deepcopy -from scos_actions.hardware.gps_iface import GPSInterface from scos_actions.hardware.sensor import Sensor from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS from scos_actions.metadata.sigmf_builder import SigMFBuilder @@ -128,5 +127,5 @@ def name(self): return get_parameter("name", self.parameters) @abstractmethod - def __call__(self, sensor=None, schedule_entry=None, task_id=None): + def __call__(self, sensor: Sensor = None, schedule_entry=None, task_id=None): pass From a8107f4561e1b27b4783b371905275c68b29d0b5 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 12:14:34 -0700 Subject: [PATCH 49/90] Used passed in sensor in create_global_sensor_metadata --- scos_actions/actions/acquire_sea_data_product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index fec488ed..8108f51e 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -960,7 +960,7 @@ def create_global_sensor_metadata(self, sensor: Sensor): self.sigmf_builder.set_sensor( ntia_sensor.Sensor( sensor_spec=ntia_core.HardwareSpec( - id=self.sensor.capabilities["sensor"]["sensor_spec"]["id"], + id=sensor.capabilities["sensor"]["sensor_spec"]["id"], ), sensor_sha512=sensor.capabilities["sensor"]["sensor_sha512"], ) From b3961b97ae8b4bece2dbfeaee3216a840ca6b0ea Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 12:34:32 -0700 Subject: [PATCH 50/90] update calibration action for sensor changes. --- scos_actions/actions/calibrate_y_factor.py | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 3f592475..2b22ea9c 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -78,7 +78,6 @@ from scos_actions import utils from scos_actions.actions.interfaces.action import Action -from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS from scos_actions.settings import SENSOR_CALIBRATION_FILE from scos_actions.signal_processing.calibration import ( @@ -224,25 +223,29 @@ def calibrate(self, params): # Set noise diode on logger.debug("Setting noise diode on") - self.configure_preselector({RF_PATH: nd_on_state}) + self.configure_preselector(sensor=self.sensor, params={RF_PATH: nd_on_state}) time.sleep(0.25) # Get noise diode on IQ logger.debug("Acquiring IQ samples with noise diode ON") - noise_on_measurement_result = self.sigan.acquire_time_domain_samples( - num_samples, num_samples_skip=nskip, cal_adjust=False + noise_on_measurement_result = ( + self.sensor.signal_analyzer.acquire_time_domain_samples( + num_samples, num_samples_skip=nskip, cal_adjust=False + ) ) sample_rate = noise_on_measurement_result["sample_rate"] # Set noise diode off logger.debug("Setting noise diode off") - self.configure_preselector({RF_PATH: nd_off_state}) + self.configure_preselector(sensor=self.sensor, params={RF_PATH: nd_off_state}) time.sleep(0.25) # Get noise diode off IQ logger.debug("Acquiring IQ samples with noise diode OFF") - noise_off_measurement_result = self.sigan.acquire_time_domain_samples( - num_samples, num_samples_skip=nskip, cal_adjust=False + noise_off_measurement_result = ( + self.sensor.signal_analyzer.acquire_time_domain_samples( + num_samples, num_samples_skip=nskip, cal_adjust=False + ) ) assert ( sample_rate == noise_off_measurement_result["sample_rate"] @@ -272,8 +275,8 @@ def calibrate(self, params): sigan_params[k] for k in self.sensor.signal_analyzer.sensor_calibration.calibration_parameters ] - self.sigan.recompute_sensor_calibration_data(cal_args) - enbw_hz = self.sigan.sensor_calibration_data["enbw"] + self.sensor.signal_analyzer.recompute_sensor_calibration_data(cal_args) + enbw_hz = self.sensor.signal_analyzer.sensor_calibration_data["enbw"] noise_on_data = noise_on_measurement_result["data"] noise_off_data = noise_off_measurement_result["data"] @@ -289,7 +292,7 @@ def calibrate(self, params): ) # Update sensor calibration with results - sensor_calibration.update( + self.sensor.signal_analyzer.sensor_calibration.update( sigan_params, utils.get_datetime_str_now(), gain, From d569dd9569581a4045607c135c75033b336c5bc8 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 12:48:26 -0700 Subject: [PATCH 51/90] pass preselector to get_temperature and get_linear_enr. Update type hints and docstring params in signal_processing/calibration.py. --- scos_actions/actions/calibrate_y_factor.py | 6 ++++-- scos_actions/signal_processing/calibration.py | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 2b22ea9c..15357b6b 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -285,8 +285,10 @@ def calibrate(self, params): pwr_off_watts = calculate_power_watts(noise_off_data) / 2.0 # Y-Factor - enr_linear = get_linear_enr(cal_source_idx) - temp_k, temp_c, _ = get_temperature(temp_sensor_idx) + enr_linear = get_linear_enr( + preselector=self.sensor.preselector, cal_source_idx=cal_source_idx + ) + temp_k, temp_c, _ = get_temperature(self.sensor.preselector, temp_sensor_idx) noise_figure, gain = y_factor( pwr_on_watts, pwr_off_watts, enr_linear, enbw_hz, temp_k ) diff --git a/scos_actions/signal_processing/calibration.py b/scos_actions/signal_processing/calibration.py index 06b6504d..2e41a279 100644 --- a/scos_actions/signal_processing/calibration.py +++ b/scos_actions/signal_processing/calibration.py @@ -2,6 +2,7 @@ from typing import Tuple import numpy as np +from its_preselector.preselector import Preselector from scipy.constants import Boltzmann from scos_actions.signal_processing.unit_conversion import ( @@ -64,7 +65,7 @@ def y_factor( return noise_figure_dB, gain_dB -def get_linear_enr(preselector, cal_source_idx: int = None) -> float: +def get_linear_enr(preselector: Preselector, cal_source_idx: int = None) -> float: """ Get the excess noise ratio of a calibration source. @@ -74,6 +75,7 @@ def get_linear_enr(preselector, cal_source_idx: int = None) -> float: The preselector is loaded from `scos_actions.hardware`. + :param preselector: The sensor preselector :param cal_source_idx: The index of the specified calibration source in `preselector.cal_sources`. :return: The excess noise ratio of the specified @@ -105,15 +107,16 @@ def get_linear_enr(preselector, cal_source_idx: int = None) -> float: return enr_linear -def get_temperature(preselector, sensor_idx: int = None) -> Tuple[float, float, float]: +def get_temperature( + preselector: Preselector, sensor_idx: int = None +) -> Tuple[float, float, float]: """ Get the temperature from a preselector sensor. The preselector is expected to be configured to return the temperature in degrees Celsius. - The preselector is loaded from `scos_actions.hardware`. - + :param preselector: The sensor preselector. :param sensor_idx: The index of the desired temperature sensor in the preselector. :raises CalibrationException: If no sensor index is provided, or From a42c536741cfa3d1dce0eae158b6f07fcf3d2bef Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 13:21:41 -0700 Subject: [PATCH 52/90] debugging --- scos_actions/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index 0906f525..8ea77915 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -27,7 +27,9 @@ logger.debug(f"SCOS_ACTIONS: SIGAN_CALIBRATION_FILE: {SIGAN_CALIBRATION_FILE}") if not settings.configured or not hasattr(settings, "SENSOR_CALIBRATION_FILE"): - logger.warning("Sensor calibration file is not defined.") + logger.warning( + f"Sensor calibration file is not defined. Settings configured: {settings.configured}" + ) SENSOR_CALIBRATION_FILE = "" sensor_calibration = None else: From 35380519f25b2401cc1de6ce50f0e09da26ca2f3 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 13:33:43 -0700 Subject: [PATCH 53/90] store calibration file path in Calibration. --- scos_actions/actions/acquire_sea_data_product.py | 1 - scos_actions/actions/calibrate_y_factor.py | 7 +------ scos_actions/calibration/calibration.py | 5 +++-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 8108f51e..9b4660bb 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -55,7 +55,6 @@ ntia_sensor, ) from scos_actions.metadata.structs.capture import CaptureSegment -from scos_actions.metadata.utils import construct_geojson_point from scos_actions.settings import SCOS_SENSOR_GIT_TAG from scos_actions.signal_processing.apd import get_apd from scos_actions.signal_processing.fft import ( diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 15357b6b..17b2f51d 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -295,12 +295,7 @@ def calibrate(self, params): # Update sensor calibration with results self.sensor.signal_analyzer.sensor_calibration.update( - sigan_params, - utils.get_datetime_str_now(), - gain, - noise_figure, - temp_c, - SENSOR_CALIBRATION_FILE, + sigan_params, utils.get_datetime_str_now(), gain, noise_figure, temp_c ) # Debugging diff --git a/scos_actions/calibration/calibration.py b/scos_actions/calibration/calibration.py index 666cdb36..7cd514e9 100644 --- a/scos_actions/calibration/calibration.py +++ b/scos_actions/calibration/calibration.py @@ -16,6 +16,7 @@ class Calibration: calibration_data: dict clock_rate_lookup_by_sample_rate: List[Dict[str, float]] is_default: bool + file_path: Path def __post_init__(self): # Convert key names in calibration_data to strings @@ -56,7 +57,6 @@ def update( gain_dB: float, noise_figure_dB: float, temp_degC: float, - file_path: Path, ) -> None: """ Update the calibration data by overwriting or adding an entry. @@ -117,7 +117,7 @@ def update( "clock_rate_lookup_by_sample_rate": self.clock_rate_lookup_by_sample_rate, "calibration_data": self.calibration_data, } - with open(file_path, "w") as outfile: + with open(self.file_path, "w") as outfile: outfile.write(json.dumps(cal_dict)) @@ -159,6 +159,7 @@ def load_from_json(fname: Path, is_default: bool) -> Calibration: calibration["calibration_data"], calibration["clock_rate_lookup_by_sample_rate"], is_default, + fname, ) From 3c020d6ebf2b9fad4e225757d6492576c3ca2792 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 14:49:58 -0700 Subject: [PATCH 54/90] Use SCOS_SENSOR_GIT_TAG from environment. --- scos_actions/settings.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index 8ea77915..21f7dd71 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -37,6 +37,7 @@ logger.debug(f"SCOS_ACTIONS: SENSOR_CALIBRATION_FILE: {SENSOR_CALIBRATION_FILE}") SWITCH_CONFIGS_DIR = env("SWITCH_CONFIGS_DIR", default=None) +SCOS_SENSOR_GIT_TAG = env("SCOS_SENSOR_GIT_TAG", default="unknown") if not settings.configured: PRESELECTOR_CONFIG_FILE = None SENSOR_DEFINITION_FILE = None @@ -46,25 +47,12 @@ SIGAN_POWER_CYCLE_STATES = env("SIGAN_POWER_CYCLE_STATES", default=None) SIGAN_POWER_SWITCH = env("SIGAN_POWER_SWITCH", default=None) MOCK_SIGAN = env("MOCK_SIGAN", default=None) - SCOS_SENSOR_GIT_TAG = env("SCOS_SENSOR_GIT_TAG", default="unknown") + else: MOCK_SIGAN = settings.MOCK_SIGAN RUNNING_TESTS = settings.RUNNING_TESTS SENSOR_DEFINITION_FILE = Path(settings.SENSOR_DEFINITION_FILE) FQDN = settings.FQDN - SCOS_SENSOR_GIT_TAG = settings.SCOS_SENSOR_GIT_TAG - # if settings.PRESELECTOR_CONFIG: - # PRESELECTOR_CONFIG_FILE = settings.PRESELECTOR_CONFIG - # else: - # PRESELECTOR_CONFIG_FILE = None - # - # if settings.PRESELECTOR_MODULE and settings.PRESELECTOR_CLASS: - # PRESELECTOR_MODULE = settings.PRESELECTOR_MODULE - # PRESELECTOR_CLASS = settings.PRESELECTOR_CLASS - # else: - # PRESELECTOR_MODULE = "its_preselector.web_relay_preselector" - # PRESELECTOR_CLASS = "WebRelayPreselector" - SIGAN_POWER_SWITCH = None SIGAN_POWER_CYCLE_STATES = None if hasattr(settings, "SIGAN_POWER_SWITCH"): From d15a7efb552872c49f6c243f4a0f4f343970f80b Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 15:10:37 -0700 Subject: [PATCH 55/90] set sigan and sensor calibration files from env. --- scos_actions/settings.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index 21f7dd71..3721ce91 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -17,25 +17,8 @@ else: DEFAULT_CALIBRATION_FILE = settings.DEFAULT_CALIBRATION_FILE -# set sigan_calibration file and sensor_calibration_file -if not settings.configured or not hasattr(settings, "SIGAN_CALIBRATION_FILE"): - logger.warning("Sigan calibration file is not defined.") - SIGAN_CALIBRATION_FILE = "" - sigan_calibration = None -else: - SIGAN_CALIBRATION_FILE = settings.SIGAN_CALIBRATION_FILE - logger.debug(f"SCOS_ACTIONS: SIGAN_CALIBRATION_FILE: {SIGAN_CALIBRATION_FILE}") - -if not settings.configured or not hasattr(settings, "SENSOR_CALIBRATION_FILE"): - logger.warning( - f"Sensor calibration file is not defined. Settings configured: {settings.configured}" - ) - SENSOR_CALIBRATION_FILE = "" - sensor_calibration = None -else: - SENSOR_CALIBRATION_FILE = settings.SENSOR_CALIBRATION_FILE - logger.debug(f"SCOS_ACTIONS: SENSOR_CALIBRATION_FILE: {SENSOR_CALIBRATION_FILE}") - +SIGAN_CALIBRATION_FILE = env("SIGAN_CALIBRATION_FILE", None) +SENSOR_CALIBRATION_FILE = env("SENSOR_CALIBRATION_FILE", None) SWITCH_CONFIGS_DIR = env("SWITCH_CONFIGS_DIR", default=None) SCOS_SENSOR_GIT_TAG = env("SCOS_SENSOR_GIT_TAG", default="unknown") if not settings.configured: From 3128ba8af06145fa7e9e89cdf4f217f955e2f48f Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 16:03:00 -0700 Subject: [PATCH 56/90] Don't use django settings. --- scos_actions/settings.py | 52 +++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index 3721ce91..a1a6e5cb 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -2,7 +2,6 @@ from os import path from pathlib import Path -from django.conf import settings from environs import Env logger = logging.getLogger(__name__) @@ -10,35 +9,32 @@ logger.debug("Initializing scos-actions settings") CONFIG_DIR = Path(__file__).parent.resolve() / "configs" +logger.debug(f"scos-actions: CONFIG_DIR:{CONFIG_DIR}") ACTION_DEFINITIONS_DIR = CONFIG_DIR / "actions" - -if not settings.configured or not hasattr(settings, "DEFAULT_CALIBRATION_FILE"): - DEFAULT_CALIBRATION_FILE = path.join(CONFIG_DIR, "default_calibration.json") -else: - DEFAULT_CALIBRATION_FILE = settings.DEFAULT_CALIBRATION_FILE - +logger.debug(f"scos-actions: ACTION_DEFINITIONS_DIR:{ACTION_DEFINITIONS_DIR}") +DEFAULT_CALIBRATION_FILE = path.join(CONFIG_DIR, "default_calibration.json") +logger.debug(f"scos-actions: DEFAULT_CALIBRATION_FILE:{DEFAULT_CALIBRATION_FILE}") SIGAN_CALIBRATION_FILE = env("SIGAN_CALIBRATION_FILE", None) +logger.debug(f"scos-actions: SIGAN_CALIBRATION_FILE:{SIGAN_CALIBRATION_FILE}") SENSOR_CALIBRATION_FILE = env("SENSOR_CALIBRATION_FILE", None) +logger.debug(f"scos-actions: SENSOR_CALIBRATION_FILE:{SENSOR_CALIBRATION_FILE}") SWITCH_CONFIGS_DIR = env("SWITCH_CONFIGS_DIR", default=None) +logger.debug(f"scos-actions: SWITCH_CONFIGS_DIR:{SWITCH_CONFIGS_DIR}") SCOS_SENSOR_GIT_TAG = env("SCOS_SENSOR_GIT_TAG", default="unknown") -if not settings.configured: - PRESELECTOR_CONFIG_FILE = None - SENSOR_DEFINITION_FILE = None - FQDN = None - PRESELECTOR_MODULE = env("PRESELECTOR_MODULE", default=None) - PRESELECTOR_CLASS = env("PRESELECTOR_CLASS", default=None) - SIGAN_POWER_CYCLE_STATES = env("SIGAN_POWER_CYCLE_STATES", default=None) - SIGAN_POWER_SWITCH = env("SIGAN_POWER_SWITCH", default=None) - MOCK_SIGAN = env("MOCK_SIGAN", default=None) - -else: - MOCK_SIGAN = settings.MOCK_SIGAN - RUNNING_TESTS = settings.RUNNING_TESTS - SENSOR_DEFINITION_FILE = Path(settings.SENSOR_DEFINITION_FILE) - FQDN = settings.FQDN - SIGAN_POWER_SWITCH = None - SIGAN_POWER_CYCLE_STATES = None - if hasattr(settings, "SIGAN_POWER_SWITCH"): - SIGAN_POWER_SWITCH = settings.SIGAN_POWER_SWITCH - if hasattr(settings, "SIGAN_POWER_CYCLE_STATES"): - SIGAN_POWER_CYCLE_STATES = settings.SIGAN_POWER_CYCLE_STATES +logger.debug(f"scos-actions: SCOS_SENSOR_GIT_TAG:{SCOS_SENSOR_GIT_TAG}") +MOCK_SIGAN = env.bool("MOCK_SIGAN", True) +logger.debug(f"scos-actions: MOCK_SIGAN:{MOCK_SIGAN}") +RUNNING_TESTS = env.bool("RUNNING_TESTS", False) +logger.debug(f"scos-actions: RUNNING_TESTS:{RUNNING_TESTS}") +SENSOR_DEFINITION_FILE = env("SENSOR_DEFINITION_FILE", None) +logger.debug(f"scos-actions: RUNNING_TESTS:{RUNNING_TESTS}") +FQDN = env("FQDN", None) +logger.debug(f"scos-actions: FQDN:{FQDN}") +SIGAN_POWER_SWITCH = env("SIGAN_POWER_SWITCH", default=None) +logger.debug(f"scos-actions: SIGAN_POWER_SWITCH:{SIGAN_POWER_SWITCH}") +SIGAN_POWER_CYCLE_STATES = env("SIGAN_POWER_CYCLE_STATES", default=None) +logger.debug(f"scos-actions: SIGAN_POWER_CYCLE_STATES:{SIGAN_POWER_CYCLE_STATES}") +PRESELECTOR_MODULE = env("PRESELECTOR_MODULE", default=None) +logger.debug(f"scos-actions: PRESELECTOR_MODULE:{PRESELECTOR_MODULE}") +PRESELECTOR_CLASS = env("PRESELECTOR_CLASS", default=None) +logger.debug(f"scos-actions: PRESELECTOR_CLASS:{PRESELECTOR_CLASS}") From 5f4b3cc5c0af464ca1c9ebf4a94bbde9ad35aaa6 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 16:13:45 -0700 Subject: [PATCH 57/90] fix tests. --- scos_actions/calibration/tests/test_calibration.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scos_actions/calibration/tests/test_calibration.py b/scos_actions/calibration/tests/test_calibration.py index f81de685..9e503acd 100644 --- a/scos_actions/calibration/tests/test_calibration.py +++ b/scos_actions/calibration/tests/test_calibration.py @@ -235,6 +235,7 @@ def test_get_calibration_dict_exact_match_lookup(self): calibration_data, clock_rate_lookup_by_sample_rate, False, + Path(""), ) cal_data = cal.get_calibration_dict([100.0, 200.0]) assert cal_data["NF"] == "NF at 100, 200" @@ -247,12 +248,14 @@ def test_get_calibration_dict_within_range(self): 200.0: {100.0: {"NF": "NF at 200, 100"}}, } clock_rate_lookup_by_sample_rate = {} + test_cal_path = Path("test_calibration.json") cal = Calibration( calibration_datetime, calibration_params, calibration_data, clock_rate_lookup_by_sample_rate, False, + test_cal_path, ) with pytest.raises(CalibrationException) as e_info: cal_data = cal.get_calibration_dict([100.0, 250.0]) @@ -287,17 +290,18 @@ def test_update(self): calibration_params = ["sample_rate", "frequency"] calibration_data = {100.0: {200.0: {"noise_figure": 0, "gain": 0}}} clock_rate_lookup_by_sample_rate = {} + test_cal_path = Path("test_calibration.json") cal = Calibration( calibration_datetime, calibration_params, calibration_data, clock_rate_lookup_by_sample_rate, False, + test_cal_path, ) action_params = {"sample_rate": 100.0, "frequency": 200.0} update_time = get_datetime_str_now() - test_cal_path = Path("test_calibration.json") - cal.update(action_params, update_time, 30.0, 5.0, 21, test_cal_path) + cal.update(action_params, update_time, 30.0, 5.0, 21) cal_from_file = load_from_json(test_cal_path, False) test_cal_path.unlink() file_utc_time = parse_datetime_iso_format_str(cal.last_calibration_datetime) From 25ce95d7bbf57bed2136273b52f2f4ab1e2a336f Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 16:36:57 -0700 Subject: [PATCH 58/90] Remove SENSOR_CALIBRATION_FILE And SIGAN_CALIBRATION_FILE --- scos_actions/actions/calibrate_y_factor.py | 1 - scos_actions/settings.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 17b2f51d..839afc43 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -79,7 +79,6 @@ from scos_actions import utils from scos_actions.actions.interfaces.action import Action from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS -from scos_actions.settings import SENSOR_CALIBRATION_FILE from scos_actions.signal_processing.calibration import ( get_linear_enr, get_temperature, diff --git a/scos_actions/settings.py b/scos_actions/settings.py index a1a6e5cb..b81f9320 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -10,15 +10,13 @@ logger.debug("Initializing scos-actions settings") CONFIG_DIR = Path(__file__).parent.resolve() / "configs" logger.debug(f"scos-actions: CONFIG_DIR:{CONFIG_DIR}") -ACTION_DEFINITIONS_DIR = CONFIG_DIR / "actions" +ACTION_DEFINITIONS_DIR = Path(CONFIG_DIR / "actions") logger.debug(f"scos-actions: ACTION_DEFINITIONS_DIR:{ACTION_DEFINITIONS_DIR}") DEFAULT_CALIBRATION_FILE = path.join(CONFIG_DIR, "default_calibration.json") logger.debug(f"scos-actions: DEFAULT_CALIBRATION_FILE:{DEFAULT_CALIBRATION_FILE}") -SIGAN_CALIBRATION_FILE = env("SIGAN_CALIBRATION_FILE", None) -logger.debug(f"scos-actions: SIGAN_CALIBRATION_FILE:{SIGAN_CALIBRATION_FILE}") -SENSOR_CALIBRATION_FILE = env("SENSOR_CALIBRATION_FILE", None) -logger.debug(f"scos-actions: SENSOR_CALIBRATION_FILE:{SENSOR_CALIBRATION_FILE}") SWITCH_CONFIGS_DIR = env("SWITCH_CONFIGS_DIR", default=None) +if SWITCH_CONFIGS_DIR: + SWITCH_CONFIGS_DIR = Path(SWITCH_CONFIGS_DIR) logger.debug(f"scos-actions: SWITCH_CONFIGS_DIR:{SWITCH_CONFIGS_DIR}") SCOS_SENSOR_GIT_TAG = env("SCOS_SENSOR_GIT_TAG", default="unknown") logger.debug(f"scos-actions: SCOS_SENSOR_GIT_TAG:{SCOS_SENSOR_GIT_TAG}") @@ -27,6 +25,8 @@ RUNNING_TESTS = env.bool("RUNNING_TESTS", False) logger.debug(f"scos-actions: RUNNING_TESTS:{RUNNING_TESTS}") SENSOR_DEFINITION_FILE = env("SENSOR_DEFINITION_FILE", None) +if SENSOR_DEFINITION_FILE: + SENSOR_DEFINITION_FILE = Path(SENSOR_DEFINITION_FILE) logger.debug(f"scos-actions: RUNNING_TESTS:{RUNNING_TESTS}") FQDN = env("FQDN", None) logger.debug(f"scos-actions: FQDN:{FQDN}") From 26613137435c34ff091c4fbcb0961d6dd5850ceb Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 15 Jan 2024 20:16:00 -0700 Subject: [PATCH 59/90] add MOCK_SIGAN_RANDOM setting. --- scos_actions/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index b81f9320..3b966527 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -22,6 +22,8 @@ logger.debug(f"scos-actions: SCOS_SENSOR_GIT_TAG:{SCOS_SENSOR_GIT_TAG}") MOCK_SIGAN = env.bool("MOCK_SIGAN", True) logger.debug(f"scos-actions: MOCK_SIGAN:{MOCK_SIGAN}") +MOCK_SIGAN_RANDOM = env.bool("MOCK_SIGAN_RANDOM", default=False) +logger.debug(f"scos-actions: MOCK_SIGAN_RANDOM:{MOCK_SIGAN_RANDOM}") RUNNING_TESTS = env.bool("RUNNING_TESTS", False) logger.debug(f"scos-actions: RUNNING_TESTS:{RUNNING_TESTS}") SENSOR_DEFINITION_FILE = env("SENSOR_DEFINITION_FILE", None) From 80784aa47e51eb6bc7d4e67f941a217715b8fcaa Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 11:19:36 -0500 Subject: [PATCH 60/90] Consistent naming and README updates - Update repo structure section to include all subdirectories of `scos_actions` - Use "SCOS Sensor" consistently instead of "scos-sensor" - Change `action_types` in scos_actions.discover to `action_classes` to be consistent with README definitions --- README.md | 34 ++++++++++++++++--------------- scos_actions/discover/__init__.py | 1 - 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 1aaac687..c468763a 100644 --- a/README.md +++ b/README.md @@ -36,30 +36,32 @@ architecture. ## Overview of Repo Structure -- `scos_actions/actions`: This includes the base Action class, signals, and the following +- `scos_actions/actions`: This includes base Action classes and the following common action classes: - `acquire_single_freq_fft`: performs FFTs and calculates mean, median, min, max, and sample statistics at a single center frequency. - `acquire_single_freq_tdomain_iq`: acquires IQ data at a single center frequency. - `acquire_stepped_freq_tdomain_iq`: acquires IQ data at multiple center frequencies. - `calibrate_y_factor`: performs calibration using the Y-Factor method. - - `sync_gps`: gets GPS location and syncs the host to GPS time - `monitor_sigan`: ensures a signal analyzer is available and is able to maintain a connection to the computer. + - `sync_gps`: gets GPS location and syncs the host to GPS time +- `scos_actions/calibration`: This includes an interface for sensor calibration data - `scos_actions/configs/actions`: This folder contains the YAML files with the parameters used to initialize the actions described above. - `scos_actions/discover`: This includes the code to read YAML files and make actions - available to scos-sensor. The [scos-sensor actions module](https://github.com/NTIA/scos-sensor/blob/master/src/actions/__init__.py) - looks for any other modules that begin with scos_. For any scos module, scos-sensor - imports its discover module to load any actions or actions_types. -- `scos_actions/hardware`: This includes the signal analyzer interface and GPS interface - used by the actions and the mock signal analyzer. The signal analyzer interface is - intended to represent universal functionality that is common across all signal - analyzers. The specific implementations of the signal analyzer interface for - particular signal analyzers are provided in separate repositories like + available to SCOS Sensor. +- `scos_actions/hardware`: This includes the signal analyzer and GPS interfaces used by + actions and the mock signal analyzer. The signal analyzer interface represents functionality + common to all signal analyzers. Specific implementations of the signal analyzer interface + for particular signal analyzers are provided in separate repositories like [scos-usrp](https://github.com/NTIA/scos-usrp). +- `scos_actions/metadata`: This includes the `SigMFBuilder` class and related metadata + structures used to generate [SigMF](https://github.com/SigMF/SigMF)-compliant metadata. - `scos_actions/signal_processing`: This contains various common signal processing routines which are used in actions. +- `scos_actions/status`: This provides a class to register objects with the SCOS Sensor + status endpoint. ## Running in SCOS Sensor @@ -299,7 +301,7 @@ You're done. sensor owner wants the sensor to be able to *do*. At a lower level, they are simply Python classes with a special method `__call__`. Actions use [Django Signals]( ) to provide data and results to -scos-sensor. +SCOS Sensor. Start by looking at the [`Action` base class](scos_actions/actions/interfaces/action.py). It includes some logic to parse a description and summary out of the action class's @@ -324,10 +326,10 @@ The following signals are currently offered for actions: - `measurement_action_completed` - signal expects task_id, data, and metadata - `location_action_completed` - signal expects latitude and longitude - `trigger_api_restart` - triggers a restart of the API docker container (where -scos-sensor runs) +SCOS Sensor runs) New signals can be added. However, corresponding signal handlers must be added to -scos-sensor to receive the signals and process the results. +SCOS Sensor to receive the signals and process the results. ##### Adding custom action to SCOS Actions @@ -346,7 +348,7 @@ above. ### Supporting a Different Signal Analyzer [scos_usrp](https://github.com/NTIA/scos-usrp) adds support for the Ettus B2xx line of -signal analyzers to `scos-sensor`. Follow these instructions to add support for +signal analyzers to SCOS Sensor. Follow these instructions to add support for another signal analyzer with a Python API. - Create a new repository called `scos-[signal analyzer name]`. @@ -371,7 +373,7 @@ another signal analyzer with a Python API. - In the new repository, add a `discover/__init__.py` file. This should contain a dictionary called `actions` with keys of action names and values of action instances. If the repository also includes new action implementations, it should also expose a - dictionary named `action_types` with keys of actions names and values of action classes. + dictionary named `action_classes` with keys of actions names and values of action classes. You can use the [init()](scos_actions/discover/__init__.py) and/or the [load_from_yaml()](scos_actions/discover/yaml.py) methods provided in this repository to look for YAML files and initialize actions. You can use the existing @@ -403,7 +405,7 @@ specific drivers are required for your signal analyzer, you can attempt to link within the package or create a docker image with the necessary files. You can host the docker image as a [GitHub package]( -). Then, when running scos-sensor, set the environment variable +). Then, when running SCOS Sensor, set the environment variable `BASE_IMAGE=`. ## License diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index 0ad100ee..8650c423 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -40,4 +40,3 @@ def init( yaml_actions, yaml_test_actions = init() actions.update(yaml_actions) test_actions.update(yaml_test_actions) -action_types = action_classes From 165efe57fdd3a7659c987201a190da8944f1af06 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 11:39:10 -0500 Subject: [PATCH 61/90] add and update action base class type hints --- scos_actions/actions/interfaces/action.py | 12 +++++++++--- .../actions/interfaces/measurement_action.py | 14 +++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index 92ee124b..a102a48c 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -1,6 +1,7 @@ import logging from abc import ABC, abstractmethod from copy import deepcopy +from typing import Optional from scos_actions.hardware.sensor import Sensor from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS @@ -32,7 +33,7 @@ class Action(ABC): PRESELECTOR_PATH_KEY = "rf_path" - def __init__(self, parameters): + def __init__(self, parameters: dict): self._sensor = None self.parameters = deepcopy(parameters) self.sigmf_builder = None @@ -46,7 +47,7 @@ def sensor(self): return self._sensor @sensor.setter - def sensor(self, value): + def sensor(self, value: Sensor): self._sensor = value def configure_sigan(self, params: dict): @@ -127,5 +128,10 @@ def name(self): return get_parameter("name", self.parameters) @abstractmethod - def __call__(self, sensor: Sensor = None, schedule_entry=None, task_id=None): + def __call__( + self, + sensor: Sensor = None, + schedule_entry: Optional[dict] = None, + task_id: Optional[int] = None, + ): pass diff --git a/scos_actions/actions/interfaces/measurement_action.py b/scos_actions/actions/interfaces/measurement_action.py index d568faab..8e732ca6 100644 --- a/scos_actions/actions/interfaces/measurement_action.py +++ b/scos_actions/actions/interfaces/measurement_action.py @@ -1,10 +1,10 @@ import logging from abc import abstractmethod -from typing import Union +from typing import Optional import numpy as np - from scos_actions.actions.interfaces.action import Action +from scos_actions.hardware.sensor import Sensor from scos_actions.metadata.structs import ntia_sensor from scos_actions.metadata.structs.capture import CaptureSegment from scos_actions.signals import measurement_action_completed @@ -20,11 +20,11 @@ class MeasurementAction(Action): """ - def __init__(self, parameters): + def __init__(self, parameters: dict): super().__init__(parameters) self.received_samples = 0 - def __call__(self, sensor, schedule_entry: dict, task_id: int): + def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): self._sensor = sensor self.get_sigmf_builder(sensor, schedule_entry) self.test_required_components() @@ -41,7 +41,7 @@ def create_capture_segment( center_frequency_Hz: float, duration_ms: int, overload: bool, - sigan_settings: Union[ntia_sensor.SiganSettings, None], + sigan_settings: Optional[ntia_sensor.SiganSettings], ) -> CaptureSegment: capture_segment = CaptureSegment( sample_start=sample_start, @@ -70,7 +70,7 @@ def create_capture_segment( def create_metadata( self, measurement_result: dict, - recording: int = None, + recording: Optional[int] = None, ) -> None: """Add SigMF metadata to the `sigmf_builder` from the `measurement_result`.""" # Set the received_samples instance variable @@ -126,7 +126,7 @@ def create_metadata( def get_sigan_settings( self, measurement_result: dict - ) -> Union[ntia_sensor.SiganSettings, None]: + ) -> Optional[ntia_sensor.SiganSettings]: """ Retrieve any sigan settings from the measurement result dict, and return a `ntia-sensor` `SiganSettings` object. Values are pulled from the From 07d02017c148c3fb8bce97d29193be27d3776aab Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 12:06:42 -0500 Subject: [PATCH 62/90] remove unused testing code --- .../actions/tests/test_acquire_single_freq_fft.py | 2 +- scos_actions/actions/tests/utils.py | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/scos_actions/actions/tests/test_acquire_single_freq_fft.py b/scos_actions/actions/tests/test_acquire_single_freq_fft.py index 3c5d6432..a172f36f 100644 --- a/scos_actions/actions/tests/test_acquire_single_freq_fft.py +++ b/scos_actions/actions/tests/test_acquire_single_freq_fft.py @@ -1,4 +1,4 @@ -from scos_actions.actions.tests.utils import SENSOR_DEFINITION, check_metadata_fields +from scos_actions.actions.tests.utils import check_metadata_fields from scos_actions.discover import test_actions as actions from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.hardware.sensor import Sensor diff --git a/scos_actions/actions/tests/utils.py b/scos_actions/actions/tests/utils.py index da758289..9b8f0c61 100644 --- a/scos_actions/actions/tests/utils.py +++ b/scos_actions/actions/tests/utils.py @@ -1,13 +1,5 @@ from sigmf.validate import validate as sigmf_validate -SENSOR_DEFINITION = { - "id": "", - "sensor_spec": {"id": "", "model": "greyhound"}, - "antenna": {"antenna_spec": {"id": "", "model": "L-com HG3512UP-NF"}}, - "signal_analyzer": {"sigan_spec": {"id": "", "model": "Ettus USRP B210"}}, - "computer_spec": {"id": "", "model": "Intel NUC"}, -} - def check_metadata_fields( metadata, action, entry_name, action_name, task_id, recording=None From acd3e68320b3c73b3d742aa3a74ae47acb5abfcf Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 12:07:49 -0500 Subject: [PATCH 63/90] add missing json import --- scos_actions/hardware/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index d8f7fbb0..c6458a20 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -1,4 +1,5 @@ import hashlib +import json from typing import Dict from its_preselector.preselector import Preselector From d529c645d40e7ef5d5cc2f3dc5617f79e3dd43e5 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 12:11:53 -0500 Subject: [PATCH 64/90] remove unused SENSOR_DEFINITION_FILE setting --- scos_actions/settings.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index 3b966527..bebd0082 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -26,9 +26,6 @@ logger.debug(f"scos-actions: MOCK_SIGAN_RANDOM:{MOCK_SIGAN_RANDOM}") RUNNING_TESTS = env.bool("RUNNING_TESTS", False) logger.debug(f"scos-actions: RUNNING_TESTS:{RUNNING_TESTS}") -SENSOR_DEFINITION_FILE = env("SENSOR_DEFINITION_FILE", None) -if SENSOR_DEFINITION_FILE: - SENSOR_DEFINITION_FILE = Path(SENSOR_DEFINITION_FILE) logger.debug(f"scos-actions: RUNNING_TESTS:{RUNNING_TESTS}") FQDN = env("FQDN", None) logger.debug(f"scos-actions: FQDN:{FQDN}") From 54567d370d9455adf727e851e4dfc3cdb41c58c3 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 12:19:42 -0500 Subject: [PATCH 65/90] add and update action class type hints and imports --- scos_actions/actions/acquire_sea_data_product.py | 10 +++++----- scos_actions/actions/acquire_single_freq_fft.py | 3 +-- .../actions/acquire_single_freq_tdomain_iq.py | 6 +++--- .../actions/acquire_stepped_freq_tdomain_iq.py | 10 +++++----- scos_actions/actions/calibrate_y_factor.py | 11 ++++++----- scos_actions/actions/logger.py | 4 +++- scos_actions/actions/monitor_sigan.py | 8 +++++++- scos_actions/actions/sync_gps.py | 5 +++-- 8 files changed, 33 insertions(+), 24 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 9b4660bb..2c906623 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -34,9 +34,6 @@ from environs import Env from its_preselector import __version__ as PRESELECTOR_API_VERSION from scipy.signal import sos2tf, sosfilt - -from scos_actions import __version__ as SCOS_ACTIONS_VERSION -from scos_actions import utils from scos_actions.actions.interfaces.action import Action from scos_actions.hardware.sensor import Sensor from scos_actions.hardware.utils import ( @@ -77,6 +74,9 @@ from scos_actions.status import start_time from scos_actions.utils import convert_datetime_to_millisecond_iso_format, get_days_up +from scos_actions import __version__ as SCOS_ACTIONS_VERSION +from scos_actions import utils + env = Env() logger = logging.getLogger(__name__) @@ -450,7 +450,7 @@ class NasctnSeaDataProduct(Action): :param sigan: Instance of SignalAnalyzerInterface. """ - def __init__(self, parameters): + def __init__(self, parameters: dict): super().__init__(parameters) # Assume preselector is present rf_path_name = utils.get_parameter(RF_PATH, self.parameters) @@ -504,7 +504,7 @@ def __init__(self, parameters): # Get iterable parameter list self.iteration_params = utils.get_iterable_parameters(self.parameters) - def __call__(self, sensor, schedule_entry, task_id): + def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" self._sensor = sensor action_start_tic = perf_counter() diff --git a/scos_actions/actions/acquire_single_freq_fft.py b/scos_actions/actions/acquire_single_freq_fft.py index 7f4385e0..aa6f16ba 100644 --- a/scos_actions/actions/acquire_single_freq_fft.py +++ b/scos_actions/actions/acquire_single_freq_fft.py @@ -89,7 +89,6 @@ import logging from numpy import float32, ndarray - from scos_actions.actions.interfaces.measurement_action import MeasurementAction from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.metadata.structs import ntia_algorithm @@ -143,7 +142,7 @@ class SingleFrequencyFftAcquisition(MeasurementAction): :param sigan: Instance of SignalAnalyzerInterface. """ - def __init__(self, parameters): + def __init__(self, parameters: dict): super().__init__(parameters) # Pull parameters from action config self.fft_size = get_parameter(FFT_SIZE, self.parameters) diff --git a/scos_actions/actions/acquire_single_freq_tdomain_iq.py b/scos_actions/actions/acquire_single_freq_tdomain_iq.py index e41700e5..0eb55655 100644 --- a/scos_actions/actions/acquire_single_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_single_freq_tdomain_iq.py @@ -34,12 +34,12 @@ import logging from numpy import complex64 - -from scos_actions import utils from scos_actions.actions.interfaces.measurement_action import MeasurementAction from scos_actions.hardware.mocks.mock_gps import MockGPS from scos_actions.utils import get_parameter +from scos_actions import utils + logger = logging.getLogger(__name__) # Define parameter keys @@ -71,7 +71,7 @@ class SingleFrequencyTimeDomainIqAcquisition(MeasurementAction): :param sigan: instance of SignalAnalyzerInterface. """ - def __init__(self, parameters): + def __init__(self, parameters: dict): super().__init__(parameters=parameters) # Pull parameters from action config self.nskip = get_parameter(NUM_SKIP, self.parameters) diff --git a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py index 721372eb..35cf741c 100644 --- a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py @@ -38,8 +38,6 @@ import logging import numpy as np - -from scos_actions import utils from scos_actions.actions.acquire_single_freq_tdomain_iq import ( CAL_ADJUST, DURATION_MS, @@ -47,12 +45,14 @@ NUM_SKIP, SingleFrequencyTimeDomainIqAcquisition, ) -from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.sensor import Sensor from scos_actions.metadata.structs import ntia_sensor from scos_actions.metadata.structs.capture import CaptureSegment from scos_actions.signals import measurement_action_completed from scos_actions.utils import get_parameter +from scos_actions import utils + logger = logging.getLogger(__name__) @@ -77,7 +77,7 @@ class SteppedFrequencyTimeDomainIqAcquisition(SingleFrequencyTimeDomainIqAcquisi :param sigan: instance of SignalAnalyzerInterface """ - def __init__(self, parameters): + def __init__(self, parameters: dict): super().__init__(parameters=parameters) num_center_frequencies = len(parameters[FREQUENCY]) @@ -87,7 +87,7 @@ def __init__(self, parameters): self.sensor = None self.num_center_frequencies = num_center_frequencies - def __call__(self, sensor, schedule_entry: dict, task_id: int): + def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" self._sensor = sensor self.test_required_components() diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 839afc43..2be35eac 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -75,9 +75,8 @@ import numpy as np from scipy.constants import Boltzmann from scipy.signal import sosfilt - -from scos_actions import utils from scos_actions.actions.interfaces.action import Action +from scos_actions.hardware.sensor import Sensor from scos_actions.hardware.sigan_iface import SIGAN_SETTINGS_KEYS from scos_actions.signal_processing.calibration import ( get_linear_enr, @@ -93,6 +92,8 @@ from scos_actions.signals import trigger_api_restart from scos_actions.utils import ParameterException, get_parameter +from scos_actions import utils + logger = logging.getLogger(__name__) # Define parameter keys @@ -134,7 +135,7 @@ class YFactorCalibration(Action): :param sigan: instance of SignalAnalyzerInterface. """ - def __init__(self, parameters): + def __init__(self, parameters: dict): logger.debug("Initializing calibration action") super().__init__(parameters) self.iteration_params = utils.get_iterable_parameters(parameters) @@ -192,7 +193,7 @@ def __init__(self, parameters): "Only one set of IIR filter parameters may be specified (including sample rate)." ) - def __call__(self, sensor, schedule_entry: dict, task_id: int): + def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): """This is the entrypoint function called by the scheduler.""" self.sensor = sensor self.test_required_components() @@ -206,7 +207,7 @@ def __call__(self, sensor, schedule_entry: dict, task_id: int): detail += os.linesep + self.calibrate(p) return detail - def calibrate(self, params): + def calibrate(self, params: dict): # Configure signal analyzer self.configure_sigan(params) diff --git a/scos_actions/actions/logger.py b/scos_actions/actions/logger.py index 52d47d91..ae2c6e60 100644 --- a/scos_actions/actions/logger.py +++ b/scos_actions/actions/logger.py @@ -1,8 +1,10 @@ """A simple example action that logs a message.""" import logging +from typing import Optional from scos_actions.actions.interfaces.action import Action +from scos_actions.hardware.sensor import Sensor logger = logging.getLogger(__name__) @@ -24,7 +26,7 @@ def __init__(self, loglvl=LOGLVL_INFO): super().__init__(parameters={"name": "logger"}) self.loglvl = loglvl - def __call__(self, sensor, schedule_entry, task_id): + def __call__(self, sensor: Optional[Sensor], schedule_entry: dict, task_id: int): msg = "running test {name}/{tid}" schedule_entry_name = schedule_entry["name"] logger.log( diff --git a/scos_actions/actions/monitor_sigan.py b/scos_actions/actions/monitor_sigan.py index f81ed716..dbc838cd 100644 --- a/scos_actions/actions/monitor_sigan.py +++ b/scos_actions/actions/monitor_sigan.py @@ -1,6 +1,7 @@ """Monitor the signal analyzer.""" import logging +from typing import Optional from scos_actions.actions.interfaces.action import Action from scos_actions.hardware.sensor import Sensor @@ -15,7 +16,12 @@ class MonitorSignalAnalyzer(Action): def __init__(self, parameters={"name": "monitor_sigan"}, gps=None): super().__init__(parameters=parameters) - def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): + def __call__( + self, + sensor: Sensor, + schedule_entry: Optional[dict] = None, + task_id: Optional[int] = None, + ): logger.debug("Performing signal analyzer health check") self._sensor = sensor healthy = self.sensor.signal_analyzer.healthy() diff --git a/scos_actions/actions/sync_gps.py b/scos_actions/actions/sync_gps.py index 872d0a28..b0f88543 100644 --- a/scos_actions/actions/sync_gps.py +++ b/scos_actions/actions/sync_gps.py @@ -4,6 +4,7 @@ import subprocess from scos_actions.actions.interfaces.action import Action +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import location_action_completed logger = logging.getLogger(__name__) @@ -12,10 +13,10 @@ class SyncGps(Action): """Query the GPS and synchronize time and location.""" - def __init__(self, parameters): + def __init__(self, parameters: dict): super().__init__(parameters=parameters) - def __call__(self, sensor, schedule_entry: dict, task_id: int): + def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): logger.debug("Syncing to GPS") self.sensor = sensor dt = self.sensor.gps.get_gps_time() From 719d1b1d9b06c969f144d5955cb165dc74dda038 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 12:59:10 -0500 Subject: [PATCH 66/90] Update sample_debug.py --- sample_debug.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sample_debug.py b/sample_debug.py index 8ff6d5b9..f00a661b 100644 --- a/sample_debug.py +++ b/sample_debug.py @@ -6,6 +6,7 @@ from scos_actions.actions.acquire_single_freq_fft import SingleFrequencyFftAcquisition from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.hardware.sensor import Sensor from scos_actions.signals import measurement_action_completed parameters = { @@ -17,6 +18,7 @@ "nffts": 300, "nskip": 0, "classification": "UNCLASSIFIED", + "calibration_adjust": False, } schedule_entry_json = { "name": "test_m4s_multi_1", @@ -50,10 +52,9 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) -action = SingleFrequencyFftAcquisition( - parameters=parameters, sigan=MockSignalAnalyzer(randomize_values=True) -) -action(schedule_entry_json, 1) +action = SingleFrequencyFftAcquisition(parameters) +sensor = Sensor(signal_analyzer=MockSignalAnalyzer(randomize_values=True)) +action(sensor, schedule_entry_json, 1) print("metadata:") print(json.dumps(_metadata, indent=4)) print("finished") From ecd054cbee3690a3e6ad24cdc0bd3714ff836583 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 13:06:34 -0500 Subject: [PATCH 67/90] add missing parameter to docstring --- scos_actions/calibration/calibration.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scos_actions/calibration/calibration.py b/scos_actions/calibration/calibration.py index 7cd514e9..2506d825 100644 --- a/scos_actions/calibration/calibration.py +++ b/scos_actions/calibration/calibration.py @@ -132,6 +132,8 @@ def load_from_json(fname: Path, is_default: bool) -> Calibration: ``clock_rate_lookup_by_sample_rate`` :param fname: The ``Path`` to the JSON calibration file. + :param is_default: If True, the loaded calibration file + is treated as the default calibration file. :raises Exception: If the provided file does not include the required keys. :return: The ``Calibration`` object generated from the file. From df4589bf748fb380fcb3e20ad090535f5b8527a5 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 13:17:24 -0500 Subject: [PATCH 68/90] remove unused gps parameter --- scos_actions/discover/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index 8650c423..f02962c3 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -24,7 +24,6 @@ def init( action_classes=action_classes, - gps=mock_gps, yaml_dir=ACTION_DEFINITIONS_DIR, ): yaml_actions = {} From 71a2007d8cc031fca4d0be05650289eebb8518e1 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 13:30:57 -0500 Subject: [PATCH 69/90] update type hints and run isort --- scos_actions/hardware/mocks/mock_sigan.py | 11 ++++++----- scos_actions/hardware/sigan_iface.py | 9 +++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/scos_actions/hardware/mocks/mock_sigan.py b/scos_actions/hardware/mocks/mock_sigan.py index 791500b0..0b5705c9 100644 --- a/scos_actions/hardware/mocks/mock_sigan.py +++ b/scos_actions/hardware/mocks/mock_sigan.py @@ -1,14 +1,15 @@ """Mock a signal analyzer for testing.""" import logging from collections import namedtuple +from typing import Optional import numpy as np - -from scos_actions import __version__ as SCOS_ACTIONS_VERSION from scos_actions.calibration.calibration import Calibration from scos_actions.hardware.sigan_iface import SignalAnalyzerInterface from scos_actions.utils import get_datetime_str_now +from scos_actions import __version__ as SCOS_ACTIONS_VERSION + logger = logging.getLogger(__name__) tune_result_params = ["actual_dsp_freq", "actual_rf_freq"] @@ -27,9 +28,9 @@ class MockSignalAnalyzer(SignalAnalyzerInterface): def __init__( self, - sensor_cal: Calibration = None, - sigan_cal: Calibration = None, - randomize_values=False, + sensor_cal: Optional[Calibration] = None, + sigan_cal: Optional[Calibration] = None, + randomize_values: bool = False, ): super().__init__(sensor_cal, sigan_cal) # Define the default calibration dicts diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index a2e185b1..86a08d01 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -1,6 +1,7 @@ import logging import time from abc import ABC, abstractmethod +from typing import Optional from scos_actions.calibration.calibration import Calibration from scos_actions.hardware.utils import power_cycle_sigan @@ -21,7 +22,11 @@ class SignalAnalyzerInterface(ABC): - def __init__(self, sensor_cal: Calibration = None, sigan_cal: Calibration = None): + def __init__( + self, + sensor_cal: Optional[Calibration] = None, + sigan_cal: Optional[Calibration] = None, + ): self.sensor_calibration_data = {} self.sigan_calibration_data = {} self._sensor_calibration = sensor_cal @@ -83,7 +88,7 @@ def connect(self) -> None: """ pass - def healthy(self, num_samples=56000): + def healthy(self, num_samples: int = 56000) -> bool: """Perform health check by collecting IQ samples.""" logger.debug("Performing health check.") if not self.is_available: From 62ed6a2b86806c8890a645f176e1775c09364c1c Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 13:39:39 -0500 Subject: [PATCH 70/90] more specific type hint for power_cycle_sigan --- scos_actions/hardware/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scos_actions/hardware/utils.py b/scos_actions/hardware/utils.py index 08f97125..b479f2a5 100644 --- a/scos_actions/hardware/utils.py +++ b/scos_actions/hardware/utils.py @@ -1,8 +1,9 @@ import logging import subprocess +from typing import Dict import psutil - +from its_preselector.web_relay import WebRelay from scos_actions.hardware.hardware_configuration_exception import ( HardwareConfigurationException, ) @@ -125,7 +126,7 @@ def get_max_cpu_temperature(fahrenheit: bool = False) -> float: raise e -def power_cycle_sigan(switches: dict): +def power_cycle_sigan(switches: Dict[str, WebRelay]): """ Performs a hard power cycle of the signal analyzer. This method requires power to the signal analyzer is controlled by a Web_Relay (see https://www.github.com/ntia/Preselector) and that the switch id of that From 489f829a04461995bab1fa85ec9ca1b624164dc6 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 13:48:01 -0500 Subject: [PATCH 71/90] remove redundant call to Path() --- scos_actions/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index bebd0082..3e97b302 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -10,7 +10,7 @@ logger.debug("Initializing scos-actions settings") CONFIG_DIR = Path(__file__).parent.resolve() / "configs" logger.debug(f"scos-actions: CONFIG_DIR:{CONFIG_DIR}") -ACTION_DEFINITIONS_DIR = Path(CONFIG_DIR / "actions") +ACTION_DEFINITIONS_DIR = CONFIG_DIR / "actions" logger.debug(f"scos-actions: ACTION_DEFINITIONS_DIR:{ACTION_DEFINITIONS_DIR}") DEFAULT_CALIBRATION_FILE = path.join(CONFIG_DIR, "default_calibration.json") logger.debug(f"scos-actions: DEFAULT_CALIBRATION_FILE:{DEFAULT_CALIBRATION_FILE}") From fb3e3103b62d419ece1dbb0821fbfd6b54990437 Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 13:50:12 -0500 Subject: [PATCH 72/90] update type hints --- scos_actions/signal_processing/calibration.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scos_actions/signal_processing/calibration.py b/scos_actions/signal_processing/calibration.py index 2e41a279..41ef5d71 100644 --- a/scos_actions/signal_processing/calibration.py +++ b/scos_actions/signal_processing/calibration.py @@ -1,10 +1,9 @@ import logging -from typing import Tuple +from typing import Optional, Tuple import numpy as np from its_preselector.preselector import Preselector from scipy.constants import Boltzmann - from scos_actions.signal_processing.unit_conversion import ( convert_celsius_to_fahrenheit, convert_celsius_to_kelvins, @@ -65,7 +64,9 @@ def y_factor( return noise_figure_dB, gain_dB -def get_linear_enr(preselector: Preselector, cal_source_idx: int = None) -> float: +def get_linear_enr( + preselector: Preselector, cal_source_idx: Optional[int] = None +) -> float: """ Get the excess noise ratio of a calibration source. @@ -108,7 +109,7 @@ def get_linear_enr(preselector: Preselector, cal_source_idx: int = None) -> floa def get_temperature( - preselector: Preselector, sensor_idx: int = None + preselector: Preselector, sensor_idx: Optional[int] = None ) -> Tuple[float, float, float]: """ Get the temperature from a preselector sensor. From 5d327218bc4c74a411388b8165ee8f1c31044f52 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Tue, 16 Jan 2024 12:12:43 -0700 Subject: [PATCH 73/90] Remove sensor parameter from configure_preselector and get_sigmf_builder. --- scos_actions/actions/acquire_sea_data_product.py | 2 +- scos_actions/actions/calibrate_y_factor.py | 4 ++-- scos_actions/actions/interfaces/action.py | 14 +++++++------- .../actions/interfaces/measurement_action.py | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 2c906623..2a6872c2 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -511,7 +511,7 @@ def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): _ = psutil.cpu_percent(interval=None) # Initialize CPU usage monitor self.test_required_components() - self.configure_preselector(self.sensor, self.rf_path) + self.configure_preselector(self.rf_path) # Initialize metadata object self.get_sigmf_builder( diff --git a/scos_actions/actions/calibrate_y_factor.py b/scos_actions/actions/calibrate_y_factor.py index 2be35eac..d26bb743 100644 --- a/scos_actions/actions/calibrate_y_factor.py +++ b/scos_actions/actions/calibrate_y_factor.py @@ -223,7 +223,7 @@ def calibrate(self, params: dict): # Set noise diode on logger.debug("Setting noise diode on") - self.configure_preselector(sensor=self.sensor, params={RF_PATH: nd_on_state}) + self.configure_preselector(params={RF_PATH: nd_on_state}) time.sleep(0.25) # Get noise diode on IQ @@ -237,7 +237,7 @@ def calibrate(self, params: dict): # Set noise diode off logger.debug("Setting noise diode off") - self.configure_preselector(sensor=self.sensor, params={RF_PATH: nd_off_state}) + self.configure_preselector(params={RF_PATH: nd_off_state}) time.sleep(0.25) # Get noise diode off IQ diff --git a/scos_actions/actions/interfaces/action.py b/scos_actions/actions/interfaces/action.py index a102a48c..496723dd 100644 --- a/scos_actions/actions/interfaces/action.py +++ b/scos_actions/actions/interfaces/action.py @@ -40,7 +40,7 @@ def __init__(self, parameters: dict): def configure(self, params: dict): self.configure_sigan(params) - self.configure_preselector(self.sensor, params) + self.configure_preselector(params) @property def sensor(self): @@ -59,13 +59,13 @@ def configure_sigan(self, params: dict): else: logger.warning(f"Sigan does not have attribute {key}") - def configure_preselector(self, sensor: Sensor, params: dict): - preselector = sensor.preselector + def configure_preselector(self, params: dict): + preselector = self.sensor.preselector if self.PRESELECTOR_PATH_KEY in params: path = params[self.PRESELECTOR_PATH_KEY] logger.debug(f"Setting preselector RF path: {path}") preselector.set_state(path) - elif sensor.has_configurable_preselector: + elif self.sensor.has_configurable_preselector: # Require the RF path to be specified if the sensor has a preselector. raise ParameterException( f"No {self.PRESELECTOR_PATH_KEY} value specified in the YAML config." @@ -74,7 +74,7 @@ def configure_preselector(self, sensor: Sensor, params: dict): # No preselector in use, so do not require an RF path pass - def get_sigmf_builder(self, sensor: Sensor, schedule_entry: dict) -> None: + def get_sigmf_builder(self, schedule_entry: dict) -> None: """ Set the `sigmf_builder` instance variable to an initialized SigMFBuilder. @@ -101,8 +101,8 @@ def get_sigmf_builder(self, sensor: Sensor, schedule_entry: dict) -> None: ) sigmf_builder.set_action(action_obj) - if sensor.location is not None: - sigmf_builder.set_geolocation(sensor.location) + if self.sensor.location is not None: + sigmf_builder.set_geolocation(self.sensor.location) if self.sensor.capabilities is not None and hasattr( self.sensor.capabilities, "sensor" ): diff --git a/scos_actions/actions/interfaces/measurement_action.py b/scos_actions/actions/interfaces/measurement_action.py index 8e732ca6..f0b06e54 100644 --- a/scos_actions/actions/interfaces/measurement_action.py +++ b/scos_actions/actions/interfaces/measurement_action.py @@ -26,7 +26,7 @@ def __init__(self, parameters: dict): def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): self._sensor = sensor - self.get_sigmf_builder(sensor, schedule_entry) + self.get_sigmf_builder(schedule_entry) self.test_required_components() self.configure(self.parameters) measurement_result = self.execute(schedule_entry, task_id) From a56291574b281e4d9d03355b553db4f092d1d132 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Tue, 16 Jan 2024 12:19:38 -0700 Subject: [PATCH 74/90] remove dead code. --- scos_actions/actions/acquire_stepped_freq_tdomain_iq.py | 7 ++----- scos_actions/actions/monitor_sigan.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py index 35cf741c..114d22c2 100644 --- a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py @@ -38,6 +38,8 @@ import logging import numpy as np + +from scos_actions import utils from scos_actions.actions.acquire_single_freq_tdomain_iq import ( CAL_ADJUST, DURATION_MS, @@ -51,8 +53,6 @@ from scos_actions.signals import measurement_action_completed from scos_actions.utils import get_parameter -from scos_actions import utils - logger = logging.getLogger(__name__) @@ -80,11 +80,8 @@ class SteppedFrequencyTimeDomainIqAcquisition(SingleFrequencyTimeDomainIqAcquisi def __init__(self, parameters: dict): super().__init__(parameters=parameters) num_center_frequencies = len(parameters[FREQUENCY]) - # Create iterable parameter set self.iterable_params = utils.get_iterable_parameters(parameters) - - self.sensor = None self.num_center_frequencies = num_center_frequencies def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): diff --git a/scos_actions/actions/monitor_sigan.py b/scos_actions/actions/monitor_sigan.py index dbc838cd..5153fd8f 100644 --- a/scos_actions/actions/monitor_sigan.py +++ b/scos_actions/actions/monitor_sigan.py @@ -13,7 +13,7 @@ class MonitorSignalAnalyzer(Action): """Monitor signal analyzer connection and restart container if unreachable.""" - def __init__(self, parameters={"name": "monitor_sigan"}, gps=None): + def __init__(self, parameters={"name": "monitor_sigan"}): super().__init__(parameters=parameters) def __call__( From cd1a20581fdbca79aee1b07b304b7c1c3a0223cd Mon Sep 17 00:00:00 2001 From: Anthony Romaniello Date: Tue, 16 Jan 2024 14:22:46 -0500 Subject: [PATCH 75/90] fix get_sigmf_builder call in test action no longer requires `sensor` parameter --- scos_actions/actions/acquire_stepped_freq_tdomain_iq.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py index 114d22c2..495be7e2 100644 --- a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py @@ -38,8 +38,6 @@ import logging import numpy as np - -from scos_actions import utils from scos_actions.actions.acquire_single_freq_tdomain_iq import ( CAL_ADJUST, DURATION_MS, @@ -53,6 +51,8 @@ from scos_actions.signals import measurement_action_completed from scos_actions.utils import get_parameter +from scos_actions import utils + logger = logging.getLogger(__name__) @@ -93,7 +93,7 @@ def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): for recording_id, measurement_params in enumerate( self.iterable_params, start=1 ): - self.get_sigmf_builder(sensor, schedule_entry) + self.get_sigmf_builder(schedule_entry) self.configure(measurement_params) duration_ms = get_parameter(DURATION_MS, measurement_params) nskip = get_parameter(NUM_SKIP, measurement_params) From cd7457756d184751f660222f3b14f34004e18dd4 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Tue, 16 Jan 2024 12:23:33 -0700 Subject: [PATCH 76/90] Remove sensor from call to get_sigmf_builder. --- scos_actions/actions/acquire_stepped_freq_tdomain_iq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py index 114d22c2..39575f8b 100644 --- a/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/acquire_stepped_freq_tdomain_iq.py @@ -93,7 +93,7 @@ def __call__(self, sensor: Sensor, schedule_entry: dict, task_id: int): for recording_id, measurement_params in enumerate( self.iterable_params, start=1 ): - self.get_sigmf_builder(sensor, schedule_entry) + self.get_sigmf_builder(schedule_entry) self.configure(measurement_params) duration_ms = get_parameter(DURATION_MS, measurement_params) nskip = get_parameter(NUM_SKIP, measurement_params) From 8a4f69eefd044d445128554a0ddc1be43f886cb9 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Wed, 17 Jan 2024 08:54:28 -0700 Subject: [PATCH 77/90] Remove StatusMonitor (functionality replaced with singleton in scos-sensor). Remove registration signals to prevent plugins from registering things. --- scos_actions/signals.py | 9 --------- scos_actions/status/__init__.py | 2 -- scos_actions/status/status_monitor.py | 20 -------------------- 3 files changed, 31 deletions(-) delete mode 100644 scos_actions/status/status_monitor.py diff --git a/scos_actions/signals.py b/scos_actions/signals.py index ee7c913e..01a351f6 100644 --- a/scos_actions/signals.py +++ b/scos_actions/signals.py @@ -7,12 +7,3 @@ location_action_completed = Signal() trigger_api_restart = Signal() - -# Provides argument: 'component' -register_component_with_status = Signal() - -# Provides argument: signal_analyzer -register_signal_analyzer = Signal() - -# Provides argument: sensor -register_sensor = Signal() diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py index c6d73ec9..a30e8b0a 100644 --- a/scos_actions/status/__init__.py +++ b/scos_actions/status/__init__.py @@ -1,5 +1,3 @@ import datetime -from scos_actions.status.status_monitor import StatusMonitor - start_time = datetime.datetime.utcnow() diff --git a/scos_actions/status/status_monitor.py b/scos_actions/status/status_monitor.py deleted file mode 100644 index c55bda84..00000000 --- a/scos_actions/status/status_monitor.py +++ /dev/null @@ -1,20 +0,0 @@ -import logging - -logger = logging.getLogger(__name__) - - -class StatusMonitor: - def __init__(self): - logger.debug("Initializing StatusMonitor") - self.status_components = [] - - def add_component(self, component): - """ - Allows objects to be registered to provide status. Any object registered will - be included in scos-sensors status endpoint. All objects registered must - implement a get_status() method that returns a dictionary. - - :param component: the object to add to the list of status providing objects. - """ - if hasattr(component, "get_status"): - self.status_components.append(component) From e35274f38a91ee79cf4f6bc20969571b2432959a Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Wed, 17 Jan 2024 09:07:46 -0700 Subject: [PATCH 78/90] Remove status package and move start_time into Sensor. --- scos_actions/actions/acquire_sea_data_product.py | 3 +-- scos_actions/hardware/sensor.py | 6 ++++++ scos_actions/status/__init__.py | 3 --- 3 files changed, 7 insertions(+), 5 deletions(-) delete mode 100644 scos_actions/status/__init__.py diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 2a6872c2..69d464fe 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -71,7 +71,6 @@ create_statistical_detector, ) from scos_actions.signals import measurement_action_completed, trigger_api_restart -from scos_actions.status import start_time from scos_actions.utils import convert_datetime_to_millisecond_iso_format, get_days_up from scos_actions import __version__ as SCOS_ACTIONS_VERSION @@ -762,7 +761,7 @@ def capture_diagnostics( logger.warning("Failed to get CPU overheating status") try: # SCOS start time cpu_diag["software_start"] = convert_datetime_to_millisecond_iso_format( - start_time + self.sensor.start_time ) except: logger.warning("Failed to get SCOS start time") diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index c6458a20..36fe0ca6 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -1,5 +1,6 @@ import hashlib import json +from datetime import datetime from typing import Dict from its_preselector.preselector import Preselector @@ -27,6 +28,7 @@ def __init__( self._switches = switches self._location = location self.capabilities = capabilities + self.start_time = datetime.datetime.utcnow() @property def signal_analyzer(self) -> SignalAnalyzerInterface: @@ -98,3 +100,7 @@ def has_configurable_preselector(self) -> bool: return True else: return False + + @property + def start_time(self): + return self.start_time diff --git a/scos_actions/status/__init__.py b/scos_actions/status/__init__.py deleted file mode 100644 index a30e8b0a..00000000 --- a/scos_actions/status/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import datetime - -start_time = datetime.datetime.utcnow() From e5f5ac25d3ba5e6cbf01e66ce8f7ac6a006d7446 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Wed, 17 Jan 2024 09:16:24 -0700 Subject: [PATCH 79/90] Fix tests. --- scos_actions/actions/acquire_sea_data_product.py | 8 ++++---- scos_actions/discover/__init__.py | 9 +-------- scos_actions/hardware/sensor.py | 6 +++--- scos_actions/utils.py | 4 +--- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/scos_actions/actions/acquire_sea_data_product.py b/scos_actions/actions/acquire_sea_data_product.py index 69d464fe..3409699a 100644 --- a/scos_actions/actions/acquire_sea_data_product.py +++ b/scos_actions/actions/acquire_sea_data_product.py @@ -34,6 +34,9 @@ from environs import Env from its_preselector import __version__ as PRESELECTOR_API_VERSION from scipy.signal import sos2tf, sosfilt + +from scos_actions import __version__ as SCOS_ACTIONS_VERSION +from scos_actions import utils from scos_actions.actions.interfaces.action import Action from scos_actions.hardware.sensor import Sensor from scos_actions.hardware.utils import ( @@ -73,9 +76,6 @@ from scos_actions.signals import measurement_action_completed, trigger_api_restart from scos_actions.utils import convert_datetime_to_millisecond_iso_format, get_days_up -from scos_actions import __version__ as SCOS_ACTIONS_VERSION -from scos_actions import utils - env = Env() logger = logging.getLogger(__name__) @@ -766,7 +766,7 @@ def capture_diagnostics( except: logger.warning("Failed to get SCOS start time") try: # SCOS uptime - cpu_diag["software_uptime"] = get_days_up() + cpu_diag["software_uptime"] = get_days_up(self.sensor.start_time) except: logger.warning("Failed to get SCOS uptime") try: # SSD SMART data diff --git a/scos_actions/discover/__init__.py b/scos_actions/discover/__init__.py index f02962c3..84ba8e6e 100644 --- a/scos_actions/discover/__init__.py +++ b/scos_actions/discover/__init__.py @@ -3,15 +3,8 @@ from scos_actions.actions.monitor_sigan import MonitorSignalAnalyzer from scos_actions.actions.sync_gps import SyncGps from scos_actions.discover.yaml import load_from_yaml -from scos_actions.hardware.mocks.mock_gps import MockGPS -from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer -from scos_actions.settings import ACTION_DEFINITIONS_DIR, MOCK_SIGAN -from scos_actions.signals import register_component_with_status +from scos_actions.settings import ACTION_DEFINITIONS_DIR -mock_sigan = MockSignalAnalyzer(randomize_values=True) -mock_gps = MockGPS() -if MOCK_SIGAN: - register_component_with_status.send(mock_sigan.__class__, component=mock_sigan) actions = {"logger": Logger()} test_actions = { "test_sync_gps": SyncGps(parameters={"name": "test_sync_gps"}), diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index 36fe0ca6..c5adb9bb 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -1,6 +1,6 @@ import hashlib import json -from datetime import datetime +import datetime from typing import Dict from its_preselector.preselector import Preselector @@ -28,7 +28,7 @@ def __init__( self._switches = switches self._location = location self.capabilities = capabilities - self.start_time = datetime.datetime.utcnow() + self_start_time = datetime.datetime.utcnow() @property def signal_analyzer(self) -> SignalAnalyzerInterface: @@ -103,4 +103,4 @@ def has_configurable_preselector(self) -> bool: @property def start_time(self): - return self.start_time + return self._start_time diff --git a/scos_actions/utils.py b/scos_actions/utils.py index 0fc5a73e..e70a4324 100644 --- a/scos_actions/utils.py +++ b/scos_actions/utils.py @@ -5,8 +5,6 @@ from dateutil import parser -from scos_actions.status import start_time - logger = logging.getLogger(__name__) @@ -121,7 +119,7 @@ def get_parameter(p: str, params: dict): return params[p] -def get_days_up(): +def get_days_up(start_time): elapsed = datetime.utcnow() - start_time days = elapsed.days fractional_day = elapsed.seconds / (60 * 60 * 24) From 83f7dc98e603c1bdeea7855bd29fc9013528a384 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Wed, 17 Jan 2024 09:27:38 -0700 Subject: [PATCH 80/90] Use property setters in Sensor init. --- scos_actions/hardware/sensor.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index c5adb9bb..74cbf479 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -7,7 +7,6 @@ from its_preselector.web_relay import WebRelay from .gps_iface import GPSInterface -from .mocks.mock_gps import MockGPS from .mocks.mock_sigan import MockSignalAnalyzer from .sigan_iface import SignalAnalyzerInterface @@ -16,19 +15,20 @@ class Sensor: def __init__( self, signal_analyzer: SignalAnalyzerInterface = MockSignalAnalyzer, - gps: GPSInterface = MockGPS(), + gps: GPSInterface = None, preselector: Preselector = None, switches: Dict[str, WebRelay] = {}, location: dict = None, capabilities: dict = None, ): - self._signal_analyzer = signal_analyzer - self._gps = gps - self._preselector = preselector - self._switches = switches - self._location = location + self.signal_analyzer = signal_analyzer + self.gps = gps + self.preselector = preselector + self.switches = switches + self.location = location self.capabilities = capabilities - self_start_time = datetime.datetime.utcnow() + # There is no setter for start_time property + self._start_time = datetime.datetime.utcnow() @property def signal_analyzer(self) -> SignalAnalyzerInterface: From d8d45e470a165df55649a30badeba5c1294c668b Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Wed, 17 Jan 2024 09:31:35 -0700 Subject: [PATCH 81/90] Add test_sensor. Remove dead code. --- scos_actions/hardware/tests/test_sensor.py | 10 ++++++++++ scos_actions/hardware/tests/test_sigan.py | 2 -- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 scos_actions/hardware/tests/test_sensor.py diff --git a/scos_actions/hardware/tests/test_sensor.py b/scos_actions/hardware/tests/test_sensor.py new file mode 100644 index 00000000..1d86de81 --- /dev/null +++ b/scos_actions/hardware/tests/test_sensor.py @@ -0,0 +1,10 @@ +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.sensor import Sensor + + +def test_sensor(): + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), gps=MockGPS()) + assert sensor is not None + assert sensor.signal_analyzer is not None + assert sensor.gps is not None diff --git a/scos_actions/hardware/tests/test_sigan.py b/scos_actions/hardware/tests/test_sigan.py index df980ecf..c82ffeec 100644 --- a/scos_actions/hardware/tests/test_sigan.py +++ b/scos_actions/hardware/tests/test_sigan.py @@ -1,5 +1,3 @@ -from environs import Env - from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer From ee5ebb46e145a6efcd99701b3681e859eca79eee Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Wed, 17 Jan 2024 09:40:09 -0700 Subject: [PATCH 82/90] Revert unnecessary change in MockSignalAnalyzer recompute_sensor_calibration_data --- scos_actions/hardware/mocks/mock_sigan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/hardware/mocks/mock_sigan.py b/scos_actions/hardware/mocks/mock_sigan.py index 0b5705c9..1876851b 100644 --- a/scos_actions/hardware/mocks/mock_sigan.py +++ b/scos_actions/hardware/mocks/mock_sigan.py @@ -209,7 +209,7 @@ def update_calibration(self, params): pass def recompute_sensor_calibration_data(self, cal_args: list) -> None: - if self._sensor_calibration is not None: + if self.sensor_calibration is not None: self.sensor_calibration_data.update( self._sensor_calibration.get_calibration_dict(cal_args) ) From d3c169dc37059b6f0aac9c5d2cbcfdabbbbf45b9 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Wed, 17 Jan 2024 12:10:37 -0700 Subject: [PATCH 83/90] Remove DEFAULT_CALIBRATION_FILE from settings. --- scos_actions/settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scos_actions/settings.py b/scos_actions/settings.py index 3e97b302..fa63aaa1 100644 --- a/scos_actions/settings.py +++ b/scos_actions/settings.py @@ -12,8 +12,6 @@ logger.debug(f"scos-actions: CONFIG_DIR:{CONFIG_DIR}") ACTION_DEFINITIONS_DIR = CONFIG_DIR / "actions" logger.debug(f"scos-actions: ACTION_DEFINITIONS_DIR:{ACTION_DEFINITIONS_DIR}") -DEFAULT_CALIBRATION_FILE = path.join(CONFIG_DIR, "default_calibration.json") -logger.debug(f"scos-actions: DEFAULT_CALIBRATION_FILE:{DEFAULT_CALIBRATION_FILE}") SWITCH_CONFIGS_DIR = env("SWITCH_CONFIGS_DIR", default=None) if SWITCH_CONFIGS_DIR: SWITCH_CONFIGS_DIR = Path(SWITCH_CONFIGS_DIR) From 027fde898fd536e519ce2c3dba0cef38eebec5e9 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 18 Jan 2024 10:54:20 -0700 Subject: [PATCH 84/90] Add optional switches dictionary to SignalAnalyzerInterface --- scos_actions/hardware/sigan_iface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index 86a08d01..0375b6ed 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -26,12 +26,14 @@ def __init__( self, sensor_cal: Optional[Calibration] = None, sigan_cal: Optional[Calibration] = None, + switches: Optional[dict] = None, ): self.sensor_calibration_data = {} self.sigan_calibration_data = {} self._sensor_calibration = sensor_cal self._sigan_calibration = sigan_cal self._model = "Unknown" + self.switches = switches @property def last_calibration_time(self) -> str: From 4d3e5b58e8c201c8d2d56c61234af1a85931991d Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 18 Jan 2024 10:55:59 -0700 Subject: [PATCH 85/90] update type hint in SignalAnalyzerInterface --- scos_actions/hardware/sigan_iface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index 0375b6ed..0037d870 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -1,8 +1,9 @@ import logging import time from abc import ABC, abstractmethod -from typing import Optional +from typing import Dict, Optional +from its_preselector.web_relay import WebRelay from scos_actions.calibration.calibration import Calibration from scos_actions.hardware.utils import power_cycle_sigan from scos_actions.utils import convert_string_to_millisecond_iso_format @@ -26,7 +27,7 @@ def __init__( self, sensor_cal: Optional[Calibration] = None, sigan_cal: Optional[Calibration] = None, - switches: Optional[dict] = None, + switches: Optional[Dict[str, WebRelay]] = None, ): self.sensor_calibration_data = {} self.sigan_calibration_data = {} From acfdb8f585d4a8741d2c90cc4c5e0319e3fcfbaa Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 18 Jan 2024 11:32:18 -0700 Subject: [PATCH 86/90] pass switches to power cycle sigan. --- scos_actions/hardware/sigan_iface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scos_actions/hardware/sigan_iface.py b/scos_actions/hardware/sigan_iface.py index 0037d870..7ed01d45 100644 --- a/scos_actions/hardware/sigan_iface.py +++ b/scos_actions/hardware/sigan_iface.py @@ -119,7 +119,7 @@ def power_cycle_and_connect(self, sleep_time: float = 2.0) -> None: """ logger.info("Attempting to power cycle the signal analyzer and reconnect.") try: - power_cycle_sigan() + power_cycle_sigan(self.switches) except Exception as hce: logger.warning(f"Unable to power cycle sigan: {hce}") return From 46c9f617e099ce1cdeee2c5752105ad61c291a1a Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Thu, 18 Jan 2024 16:32:50 -0700 Subject: [PATCH 87/90] add load_switches --- scos_actions/hardware/utils.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scos_actions/hardware/utils.py b/scos_actions/hardware/utils.py index b479f2a5..1bf0aa14 100644 --- a/scos_actions/hardware/utils.py +++ b/scos_actions/hardware/utils.py @@ -1,9 +1,14 @@ import logging import subprocess +from pathlib import Path from typing import Dict import psutil +from its_preselector.configuration_exception import ConfigurationException +from its_preselector.controlbyweb_web_relay import ControlByWebWebRelay from its_preselector.web_relay import WebRelay + +from scos_actions import utils from scos_actions.hardware.hardware_configuration_exception import ( HardwareConfigurationException, ) @@ -161,3 +166,24 @@ def power_cycle_sigan(switches: Dict[str, WebRelay]): raise HardwareConfigurationException( "Call to power cycle sigan, but no power switch or power cycle states specified " ) + + +def load_switches(switch_dir: Path) -> dict: + logger.debug(f"Loading switches in {switch_dir}") + switch_dict = {} + try: + if switch_dir is not None and switch_dir.is_dir(): + for f in switch_dir.iterdir(): + file_path = f.resolve() + logger.debug(f"loading switch config {file_path}") + conf = utils.load_from_json(file_path) + try: + switch = ControlByWebWebRelay(conf) + logger.debug(f"Adding {switch.id}") + switch_dict[switch.id] = switch + logger.debug(f"Registering switch status for {switch.name}") + except ConfigurationException: + logger.error(f"Unable to configure switch defined in: {file_path}") + except Exception as ex: + logger.error(f"Unable to load switches {ex}") + return switch_dict From 0e8299654a335a82fca83012873c695df3a60418 Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Sun, 21 Jan 2024 16:49:20 -0700 Subject: [PATCH 88/90] Remove load switches. --- scos_actions/hardware/utils.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/scos_actions/hardware/utils.py b/scos_actions/hardware/utils.py index 1bf0aa14..d9f6fbf2 100644 --- a/scos_actions/hardware/utils.py +++ b/scos_actions/hardware/utils.py @@ -166,24 +166,3 @@ def power_cycle_sigan(switches: Dict[str, WebRelay]): raise HardwareConfigurationException( "Call to power cycle sigan, but no power switch or power cycle states specified " ) - - -def load_switches(switch_dir: Path) -> dict: - logger.debug(f"Loading switches in {switch_dir}") - switch_dict = {} - try: - if switch_dir is not None and switch_dir.is_dir(): - for f in switch_dir.iterdir(): - file_path = f.resolve() - logger.debug(f"loading switch config {file_path}") - conf = utils.load_from_json(file_path) - try: - switch = ControlByWebWebRelay(conf) - logger.debug(f"Adding {switch.id}") - switch_dict[switch.id] = switch - logger.debug(f"Registering switch status for {switch.name}") - except ConfigurationException: - logger.error(f"Unable to configure switch defined in: {file_path}") - except Exception as ex: - logger.error(f"Unable to load switches {ex}") - return switch_dict From 0581b9583c984b4df818bec92ff1ebb28dc6af5d Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Mon, 22 Jan 2024 16:35:00 -0700 Subject: [PATCH 89/90] Default signal_analyzer to None and add optional switches to MockSignalAnalyzer. --- scos_actions/hardware/mocks/mock_sigan.py | 1 + scos_actions/hardware/sensor.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scos_actions/hardware/mocks/mock_sigan.py b/scos_actions/hardware/mocks/mock_sigan.py index 1876851b..8367bf0d 100644 --- a/scos_actions/hardware/mocks/mock_sigan.py +++ b/scos_actions/hardware/mocks/mock_sigan.py @@ -30,6 +30,7 @@ def __init__( self, sensor_cal: Optional[Calibration] = None, sigan_cal: Optional[Calibration] = None, + switches: Optional[dict] = None, randomize_values: bool = False, ): super().__init__(sensor_cal, sigan_cal) diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index 74cbf479..c4120a33 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -1,6 +1,6 @@ +import datetime import hashlib import json -import datetime from typing import Dict from its_preselector.preselector import Preselector @@ -14,7 +14,7 @@ class Sensor: def __init__( self, - signal_analyzer: SignalAnalyzerInterface = MockSignalAnalyzer, + signal_analyzer: SignalAnalyzerInterface = None, gps: GPSInterface = None, preselector: Preselector = None, switches: Dict[str, WebRelay] = {}, From 74b3760ffea05e6899649c4f5f55722ba9f261cf Mon Sep 17 00:00:00 2001 From: Doug Boulware Date: Fri, 26 Jan 2024 11:48:07 -0700 Subject: [PATCH 90/90] Require signal analyzer and capabilities in Sensor constructor. Add docstrings and test cases. Modify has_configurable_preselector to return True if the sensor definition includes a preselector with multiple rf_paths or if the preselector has multiple rf_paths. --- sample_debug.py | 4 +- .../tests/test_acquire_single_freq_fft.py | 4 +- .../actions/tests/test_monitor_sigan.py | 6 +- .../tests/test_single_freq_tdomain_iq.py | 6 +- .../tests/test_stepped_freq_tdomain_iq.py | 4 +- scos_actions/actions/tests/test_sync_gps.py | 5 +- scos_actions/hardware/mocks/mock_sigan.py | 6 +- scos_actions/hardware/sensor.py | 119 ++++++++++++++---- scos_actions/hardware/tests/test_sensor.py | 68 +++++++++- scos_actions/hardware/utils.py | 4 - 10 files changed, 181 insertions(+), 45 deletions(-) diff --git a/sample_debug.py b/sample_debug.py index f00a661b..cc04ae29 100644 --- a/sample_debug.py +++ b/sample_debug.py @@ -53,7 +53,9 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = SingleFrequencyFftAcquisition(parameters) -sensor = Sensor(signal_analyzer=MockSignalAnalyzer(randomize_values=True)) +sensor = Sensor( + signal_analyzer=MockSignalAnalyzer(randomize_values=True), capabilities={} +) action(sensor, schedule_entry_json, 1) print("metadata:") print(json.dumps(_metadata, indent=4)) diff --git a/scos_actions/actions/tests/test_acquire_single_freq_fft.py b/scos_actions/actions/tests/test_acquire_single_freq_fft.py index a172f36f..0419d7cc 100644 --- a/scos_actions/actions/tests/test_acquire_single_freq_fft.py +++ b/scos_actions/actions/tests/test_acquire_single_freq_fft.py @@ -29,7 +29,7 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_m4s_action"] assert action.description - sensor = Sensor(signal_analyzer=MockSignalAnalyzer()) + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities={}) action( sensor=sensor, schedule_entry=SINGLE_FREQUENCY_FFT_ACQUISITION, @@ -89,6 +89,6 @@ def callback(sender, **kwargs): def test_num_samples_skip(): action = actions["test_single_frequency_m4s_action"] assert action.description - sensor = Sensor(signal_analyzer=MockSignalAnalyzer()) + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities={}) action(sensor, SINGLE_FREQUENCY_FFT_ACQUISITION, 1) assert action.sensor.signal_analyzer._num_samples_skip == action.parameters["nskip"] diff --git a/scos_actions/actions/tests/test_monitor_sigan.py b/scos_actions/actions/tests/test_monitor_sigan.py index 0767ea84..9a00ea71 100644 --- a/scos_actions/actions/tests/test_monitor_sigan.py +++ b/scos_actions/actions/tests/test_monitor_sigan.py @@ -23,7 +23,7 @@ def callback(sender, **kwargs): action = actions["test_monitor_sigan"] mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = False - sensor = Sensor(signal_analyzer=mock_sigan) + sensor = Sensor(signal_analyzer=mock_sigan, capabilities={}) action(sensor, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == True # signal sent mock_sigan._is_available = True @@ -40,7 +40,7 @@ def callback(sender, **kwargs): action = actions["test_monitor_sigan"] mock_sigan = MockSignalAnalyzer() mock_sigan.times_to_fail_recv = 6 - sensor = Sensor(signal_analyzer=mock_sigan) + sensor = Sensor(signal_analyzer=mock_sigan, capabilities={}) action(sensor, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == True # signal sent @@ -57,6 +57,6 @@ def callback(sender, **kwargs): mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = True mock_sigan.set_times_to_fail_recv(0) - sensor = Sensor(signal_analyzer=mock_sigan) + sensor = Sensor(signal_analyzer=mock_sigan, capabilities={}) action(sensor, MONITOR_SIGAN_SCHEDULE, 1) assert _api_restart_triggered == False # signal not sent diff --git a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py index fa5a1ad4..67d86495 100644 --- a/scos_actions/actions/tests/test_single_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_single_freq_tdomain_iq.py @@ -31,7 +31,7 @@ def callback(sender, **kwargs): measurement_action_completed.connect(callback) action = actions["test_single_frequency_iq_action"] assert action.description - sensor = Sensor(signal_analyzer=MockSignalAnalyzer()) + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities={}) action(sensor, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) assert _data.any() assert _metadata @@ -62,7 +62,7 @@ def test_required_components(): action = actions["test_single_frequency_m4s_action"] mock_sigan = MockSignalAnalyzer() mock_sigan._is_available = False - sensor = Sensor(signal_analyzer=mock_sigan) + sensor = Sensor(signal_analyzer=mock_sigan, capabilities={}) with pytest.raises(RuntimeError): action(sensor, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) mock_sigan._is_available = True @@ -72,6 +72,6 @@ def test_num_samples_skip(): action = actions["test_single_frequency_iq_action"] assert action.description mock_sigan = MockSignalAnalyzer() - sensor = Sensor(signal_analyzer=mock_sigan) + sensor = Sensor(signal_analyzer=mock_sigan, capabilities={}) action(sensor, SINGLE_TIMEDOMAIN_IQ_ACQUISITION, 1) assert action.sensor.signal_analyzer._num_samples_skip == action.parameters["nskip"] diff --git a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py index 40c44141..e06920cc 100644 --- a/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py +++ b/scos_actions/actions/tests/test_stepped_freq_tdomain_iq.py @@ -35,7 +35,7 @@ def callback(sender, **kwargs): action = actions["test_multi_frequency_iq_action"] assert action.description mock_sigan = MockSignalAnalyzer() - sensor = Sensor(signal_analyzer=mock_sigan) + sensor = Sensor(signal_analyzer=mock_sigan, capabilities={}) action(sensor, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) for i in range(_count): assert _datas[i].any() @@ -49,7 +49,7 @@ def test_num_samples_skip(): action = actions["test_multi_frequency_iq_action"] assert action.description mock_sigan = MockSignalAnalyzer() - sensor = Sensor(signal_analyzer=mock_sigan) + sensor = Sensor(signal_analyzer=mock_sigan, capabilities={}) action(sensor, SINGLE_TIMEDOMAIN_IQ_MULTI_RECORDING_ACQUISITION, 1) if isinstance(action.parameters["nskip"], list): assert ( diff --git a/scos_actions/actions/tests/test_sync_gps.py b/scos_actions/actions/tests/test_sync_gps.py index 5b8a9311..ed7b2691 100644 --- a/scos_actions/actions/tests/test_sync_gps.py +++ b/scos_actions/actions/tests/test_sync_gps.py @@ -5,6 +5,7 @@ from scos_actions.discover import test_actions from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.hardware.sensor import Sensor from scos_actions.signals import location_action_completed @@ -29,7 +30,9 @@ def callback(sender, **kwargs): location_action_completed.connect(callback) action = test_actions["test_sync_gps"] - sensor = Sensor(gps=MockGPS()) + sensor = Sensor( + signal_analyzer=MockSignalAnalyzer(), capabilities={}, gps=MockGPS() + ) if sys.platform == "linux": action(sensor, SYNC_GPS, 1) assert _latitude diff --git a/scos_actions/hardware/mocks/mock_sigan.py b/scos_actions/hardware/mocks/mock_sigan.py index 8367bf0d..49687574 100644 --- a/scos_actions/hardware/mocks/mock_sigan.py +++ b/scos_actions/hardware/mocks/mock_sigan.py @@ -4,12 +4,12 @@ from typing import Optional import numpy as np + +from scos_actions import __version__ as SCOS_ACTIONS_VERSION from scos_actions.calibration.calibration import Calibration from scos_actions.hardware.sigan_iface import SignalAnalyzerInterface from scos_actions.utils import get_datetime_str_now -from scos_actions import __version__ as SCOS_ACTIONS_VERSION - logger = logging.getLogger(__name__) tune_result_params = ["actual_dsp_freq", "actual_rf_freq"] @@ -33,7 +33,7 @@ def __init__( switches: Optional[dict] = None, randomize_values: bool = False, ): - super().__init__(sensor_cal, sigan_cal) + super().__init__(sensor_cal, sigan_cal, switches) # Define the default calibration dicts self.DEFAULT_SIGAN_CALIBRATION = { "datetime": get_datetime_str_now(), diff --git a/scos_actions/hardware/sensor.py b/scos_actions/hardware/sensor.py index c4120a33..b9e14806 100644 --- a/scos_actions/hardware/sensor.py +++ b/scos_actions/hardware/sensor.py @@ -1,25 +1,38 @@ import datetime import hashlib import json -from typing import Dict +import logging +from typing import Dict, Optional from its_preselector.preselector import Preselector from its_preselector.web_relay import WebRelay from .gps_iface import GPSInterface -from .mocks.mock_sigan import MockSignalAnalyzer from .sigan_iface import SignalAnalyzerInterface class Sensor: + """ + Software representation of the physical RF sensor. The Sensor may include a GPSInterface, + Preselector, a dictionary of WebRelays, a location specified in GeoJSON, and a dictionary + of the sensor capabilities. The capabilities should include a 'sensor' key that maps to + the metadata definition of the Sensor( + https://github.com/NTIA/sigmf-ns-ntia/blob/master/ntia-sensor.sigmf-ext.md#01-the-sensor-object), + and an 'action' key that maps to a list of ntia-scos action objects + (https://github.com/NTIA/sigmf-ns-ntia/blob/master/ntia-scos.sigmf-ext.md#02-the-action-object) + The Sensor instance is passed into Actions __call__ methods to perform an action. + """ + + logger = logging.getLogger(__name__) + def __init__( self, - signal_analyzer: SignalAnalyzerInterface = None, - gps: GPSInterface = None, - preselector: Preselector = None, - switches: Dict[str, WebRelay] = {}, - location: dict = None, - capabilities: dict = None, + signal_analyzer: SignalAnalyzerInterface, + capabilities: dict, + gps: Optional[GPSInterface] = None, + preselector: Optional[Preselector] = None, + switches: Optional[Dict[str, WebRelay]] = {}, + location: Optional[dict] = None, ): self.signal_analyzer = signal_analyzer self.gps = gps @@ -40,22 +53,38 @@ def signal_analyzer(self, sigan: SignalAnalyzerInterface): @property def gps(self) -> GPSInterface: + """ + The sensor's Global Positioning System. + """ return self._gps @gps.setter def gps(self, gps: GPSInterface): + """ + Set the sensor's Global Positioning System. + """ self._gps = gps @property def preselector(self) -> Preselector: + """ + RF front end that may include calibration sources, filters, and/or amplifiers. + """ return self._preselector @preselector.setter def preselector(self, preselector: Preselector): + """ + Set the RF front end that may include calibration sources, filters, and/or amplifiers. + """ self._preselector = preselector @property def switches(self) -> Dict[str, WebRelay]: + """ + Dictionary of WebRelays, indexed by name. WebRelays may enable/disable other + components within the sensor and/or provide a variety of sensors. + """ return self._switches @switches.setter @@ -64,42 +93,84 @@ def switches(self, switches: Dict[str, WebRelay]): @property def location(self) -> dict: + """ + The GeoJSON dictionary of the sensor's location. + """ return self._location @location.setter def location(self, loc: dict): + """ + Set the GeoJSON location of the sensor. + """ self._location = loc @property def capabilities(self) -> dict: + """ + A dictionary of the sensor's capabilities. The dictionary should + include a 'sensor' key that maps to the ntia-sensor + (https://github.com/NTIA/sigmf-ns-ntia/blob/master/ntia-sensor.sigmf-ext.md) + object and an actions key that maps to a list of ntia-scos action objects + (https://github.com/NTIA/sigmf-ns-ntia/blob/master/ntia-scos.sigmf-ext.md#02-the-action-object) + """ return self._capabilities @capabilities.setter def capabilities(self, capabilities: dict): + """ + Set the dictionary of the sensor's capabilities. The dictionary should + include a 'sensor' key that links to the ntia-sensor + (https://github.com/NTIA/sigmf-ns-ntia/blob/master/ntia-sensor.sigmf-ext.md) + object and an actions key that links to a list of ntia-scos action objects + (https://github.com/NTIA/sigmf-ns-ntia/blob/master/ntia-scos.sigmf-ext.md#02-the-action-object) + """ if capabilities is not None: - if "sensor_sha512" not in capabilities["sensor"]: + if ( + "sensor" in capabilities + and "sensor_sha512" not in capabilities["sensor"] + ): sensor_def = json.dumps(capabilities["sensor"], sort_keys=True) - SENSOR_DEFINITION_HASH = hashlib.sha512( + sensor_definition_hash = hashlib.sha512( sensor_def.encode("UTF-8") ).hexdigest() - capabilities["sensor"]["sensor_sha512"] = SENSOR_DEFINITION_HASH - self._capabilities = capabilities - else: - self._capabilities = None + capabilities["sensor"]["sensor_sha512"] = sensor_definition_hash + self._capabilities = capabilities @property def has_configurable_preselector(self) -> bool: - if self._capabilities is None: - return False + """ + Checks if the preselector has multiple rf paths. + Returns: True if either the Preselector object or the sensor definition contain multiple rf_paths, False + otherwise. + """ + if ( + self.preselector is not None + and self.preselector.rf_paths is not None + and len(self.preselector.rf_paths) > 0 + ): + self.logger.debug( + "Preselector is configurable: found multiple rf_paths in preselector object." + ) + return True + elif ( + self.capabilities + and len( + self.capabilities.get("sensor", {}) + .get("preselector", {}) + .get("rf_paths", []) + ) + > 1 + ): + self.logger.debug( + "Preselector is configurable: found multiple rf_paths in sensor definition." + ) + return True else: - sensor_definition = self._capabilities["sensor"] - if ( - "preselector" in sensor_definition - and "rf_paths" in sensor_definition["preselector"] - ): - return True - else: - return False + self.logger.debug( + "Preselector is not configurable: Neither sensor definition or preselector object contained multiple rf_paths." + ) + return False @property def start_time(self): diff --git a/scos_actions/hardware/tests/test_sensor.py b/scos_actions/hardware/tests/test_sensor.py index 1d86de81..216d626a 100644 --- a/scos_actions/hardware/tests/test_sensor.py +++ b/scos_actions/hardware/tests/test_sensor.py @@ -1,10 +1,74 @@ -from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer +from its_preselector.controlbyweb_web_relay import ControlByWebWebRelay +from its_preselector.web_relay_preselector import WebRelayPreselector + from scos_actions.hardware.mocks.mock_gps import MockGPS +from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer from scos_actions.hardware.sensor import Sensor def test_sensor(): - sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), gps=MockGPS()) + sensor = Sensor( + signal_analyzer=MockSignalAnalyzer(), capabilities={}, gps=MockGPS() + ) assert sensor is not None assert sensor.signal_analyzer is not None assert sensor.gps is not None + + +def test_set_get_preselector(): + preselector = WebRelayPreselector({}, {"name": "preselector", "base_url": "url"}) + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities={}) + sensor.preselector = preselector + assert sensor.preselector == preselector + + +def test_set_get_gps(): + gps = MockGPS() + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities={}) + sensor.gps = gps + assert sensor.gps == gps + + +def test_set_get_switches(): + switches = {"spu": ControlByWebWebRelay({"name": "spu", "base_url": "url"})} + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities={}) + sensor.switches = switches + assert sensor.switches == switches + + +def test_has_configurable_preselector_in_capabilities(): + capabilities = { + "sensor": { + "preselector": {"rf_paths": [{"name": "antenna"}, {"name": "noise_diode"}]} + } + } + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities=capabilities) + assert sensor.has_configurable_preselector == True + + +def test_has_configurable_preselector_in_preselector(): + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities={}) + sensor.preselector = WebRelayPreselector( + {}, {"name": "preselector", "base_url": "url"} + ) + sensor.preselector.rf_paths = [{"name": "antenna"}, {"name": "noise_diode"}] + assert sensor.has_configurable_preselector == True + + +def test_has_configurable_preselector_not_configurable(): + capabilities = {"sensor": {"preselector": {"rf_paths": [{"name": "antenna"}]}}} + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities=capabilities) + assert sensor.has_configurable_preselector == False + + +def test_hash_set_when_not_present(): + capabilities = {"sensor": {"preselector": {"rf_paths": [{"name": "antenna"}]}}} + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities=capabilities) + assert "sensor_sha512" in sensor.capabilities["sensor"] + assert sensor.capabilities["sensor"]["sensor_sha512"] is not None + + +def test_hash_not_overwritten(): + capabilities = {"sensor": {"sensor_sha512": "some hash"}} + sensor = Sensor(signal_analyzer=MockSignalAnalyzer(), capabilities=capabilities) + assert sensor.capabilities["sensor"]["sensor_sha512"] == "some hash" diff --git a/scos_actions/hardware/utils.py b/scos_actions/hardware/utils.py index d9f6fbf2..49642da7 100644 --- a/scos_actions/hardware/utils.py +++ b/scos_actions/hardware/utils.py @@ -1,14 +1,10 @@ import logging import subprocess -from pathlib import Path from typing import Dict import psutil -from its_preselector.configuration_exception import ConfigurationException -from its_preselector.controlbyweb_web_relay import ControlByWebWebRelay from its_preselector.web_relay import WebRelay -from scos_actions import utils from scos_actions.hardware.hardware_configuration_exception import ( HardwareConfigurationException, )