Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support multiple web relays, provide dynamic status, and enable signal analyzer power cycling. #38

Merged
merged 75 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
3746ad4
Add support for multiple switches. Adds register_component_with_statu…
dboulware Aug 9, 2022
41ba257
fix check for switch configuration directory.
dboulware Aug 9, 2022
e7fc17a
add name and get_status to sigan_iface and send register_component_wi…
dboulware Aug 10, 2022
e110cbe
Change name property in sigan_iface to always use Signal Analyzer and…
dboulware Aug 10, 2022
a800212
Register mock sigan as status provider.
dboulware Aug 10, 2022
b0e2171
fix send of status registration signal
dboulware Aug 10, 2022
07fd8e1
only set SWITCH_CONFIGS_DIR if not set already.
dboulware Aug 11, 2022
d508278
Register sigan status in discover.init
dboulware Aug 11, 2022
87cf68c
Return type to name property and cast __class__ to string.
dboulware Aug 11, 2022
752667d
Use sensor definition in sigan get_status to specify signal analyzer …
dboulware Aug 11, 2022
dd8151b
Use model instead of type in sigan get_status
dboulware Aug 11, 2022
96be859
string cast in preselector and switch status registration.
dboulware Aug 11, 2022
e7040b6
WebRelayAndStatus preslector branch.
dboulware Aug 11, 2022
64dc6af
fix path to switch files.
dboulware Aug 11, 2022
e381d74
log loading switch configs.
dboulware Aug 11, 2022
af06448
Move status monitoring into scos_actions.
dboulware Aug 11, 2022
210508d
fix StatusMonitor import
dboulware Aug 11, 2022
eb01959
fix import
dboulware Aug 11, 2022
916919c
Move status classes into their own package.
dboulware Aug 11, 2022
5ceb846
add missing init file.
dboulware Aug 11, 2022
155716a
Initialize status_registrar in status package.
dboulware Aug 11, 2022
030977e
Fix import.
dboulware Aug 11, 2022
a4ea200
remove dead code.
dboulware Aug 11, 2022
7387038
fix register_component_with_status send calls.
dboulware Aug 11, 2022
a91437c
debug
dboulware Aug 11, 2022
0fdbb4b
log switch registration.
dboulware Aug 12, 2022
ca9610d
log preselector registration.
dboulware Aug 12, 2022
66708b2
log status registration handler connection.
dboulware Aug 12, 2022
f98fcc5
Move status handler signal connection to hardware to ensure it happen…
dboulware Aug 12, 2022
c4d863b
typo fix
dboulware Aug 12, 2022
0f36c55
Correct reference to sensor definition in get_status
dboulware Aug 12, 2022
7433414
Correct reference to sensor definition in get_status
dboulware Aug 12, 2022
0ed974c
Check if switch directory exists before attempting to load configs.
dboulware Aug 12, 2022
dc1752f
Merge branch 'master' of https://github.com/NTIA/scos-actions
dboulware Aug 23, 2022
b630586
Merge branch 'master' of https://github.com/NTIA/scos-actions
dboulware Aug 24, 2022
7194899
set start date in SignalAnalyzerInterface.
dboulware Aug 24, 2022
f26bcdf
Add started to sigan status.
dboulware Aug 24, 2022
abc9ca0
Update preselector branch
dboulware Aug 24, 2022
5e9788a
Remove start_date from sign.
dboulware Aug 25, 2022
8e1aea2
correected load_preselector
dboulware Aug 25, 2022
26a23bb
catch exceptions when loading preselector and switches.
dboulware Aug 25, 2022
54bb8ab
Remove sigan name.
dboulware Aug 26, 2022
9707703
Redeleted providing_args in signals.
dboulware Aug 26, 2022
bcf15de
refrence preselector 2.0.0
dboulware Aug 26, 2022
788b479
Refactored load_preselector to enable testing. Moved preselector test…
dboulware Aug 29, 2022
8179b4d
Merge branch 'master' of https://www.github.com/ntia/scos-actions int…
dboulware Aug 29, 2022
ec67f73
Merge branch 'fix-tox' of https://www.github.com/ntia/scos-actions in…
dboulware Aug 29, 2022
48fad1f
Removed action to set switch states.
dboulware Aug 29, 2022
59a0cb9
Add power_cycle method to SignalAnalyzerInterface and settings to sup…
dboulware Sep 1, 2022
268fb13
add ScosSensorException
dboulware Sep 1, 2022
29a1151
Removed power_cycle from sigan and moved it to power_cycle_sigan in h…
dboulware Sep 1, 2022
6389b07
Removed self from power_cycle_sigan method.
dboulware Sep 1, 2022
79cdba9
Fix call to load_preselector
dboulware Sep 1, 2022
3c1a5a7
Fix call to load_preselector
dboulware Sep 1, 2022
0a85675
Only create MockSignalAnalyzer if MOCK_SIGAN enabled
dboulware Sep 1, 2022
296378d
Only register MockSignalAnalyzer for status if MOCK_SIGAN enabled
dboulware Sep 1, 2022
824c311
enable env variable for SIGAN_POWER_SWITCH
dboulware Sep 1, 2022
a610c57
Log call to power cycle without configuration.
dboulware Sep 1, 2022
a1aee41
use dictionary to access power switch
dboulware Sep 2, 2022
64dca1f
logging
dboulware Sep 2, 2022
adc76d0
fix log statement
dboulware Sep 2, 2022
4777001
fix log statement
dboulware Sep 2, 2022
61d2d93
debugging
dboulware Sep 2, 2022
3c657b6
debugging
dboulware Sep 2, 2022
42e8c9f
Move calibration into calibratio package.
dboulware Sep 4, 2022
67aa068
Don't default to using MockSignalAnalyzer in actions. Move creation o…
dboulware Sep 4, 2022
2f1372d
Removed debug statement.
dboulware Sep 6, 2022
e663079
increment version
dboulware Sep 6, 2022
0a899d4
pre-commit run --all-files
aromanielloNTIA Sep 6, 2022
4c3c132
Update string formatting to f-string syntax
aromanielloNTIA Sep 6, 2022
9adcbf0
Update string formatting to f-string syntax
aromanielloNTIA Sep 6, 2022
04b966f
simplify sigan get_status per pr feedback
dboulware Sep 6, 2022
dae6039
Rename ScosSensorException to HardwareConfigurationException and rais…
dboulware Sep 6, 2022
e6f419b
Merge branch 'MultiSwitchStatus' of https://www.github.com/ntia/scos-…
dboulware Sep 6, 2022
2c2b43f
Update power_cycle_sigan docstring and when to raise exception.
dboulware Sep 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ classifiers = [
]

dependencies = [
"environs>=9.5.0",
"django>=3.2.15,<4.0",
"its_preselector @ git+https://github.com/NTIA/Preselector@1.0.0",
"its_preselector @ git+https://github.com/NTIA/Preselector@2.0.0",
"numexpr>=2.8.3",
"numpy>=1.22.0",
"python-dateutil>=2.0",
Expand Down
2 changes: 1 addition & 1 deletion scos_actions/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.0.1"
__version__ = "4.0.0"
3 changes: 2 additions & 1 deletion scos_actions/actions/calibrate_y_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@

from scos_actions import utils
from scos_actions.actions.interfaces.action import Action
from scos_actions.calibration import sensor_calibration
from scos_actions.hardware import gps as mock_gps
from scos_actions.settings import SENSOR_CALIBRATION_FILE, sensor_calibration
from scos_actions.settings import SENSOR_CALIBRATION_FILE
from scos_actions.signal_processing.calibration import (
get_linear_enr,
get_temperature,
Expand Down
3 changes: 1 addition & 2 deletions scos_actions/actions/interfaces/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from scos_actions.capabilities import capabilities
from scos_actions.hardware import gps as mock_gps
from scos_actions.hardware import preselector
from scos_actions.hardware import sigan as mock_sigan
from scos_actions.utils import get_parameter

logger = logging.getLogger(__name__)
Expand All @@ -32,7 +31,7 @@ class Action(ABC):

PRESELECTOR_PATH_KEY = "rf_path"

def __init__(self, parameters, sigan=mock_sigan, gps=mock_gps):
def __init__(self, parameters, sigan, gps=mock_gps):
self.parameters = deepcopy(parameters)
self.sigan = sigan
self.gps = gps
Expand Down
3 changes: 1 addition & 2 deletions scos_actions/actions/interfaces/measurement_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from scos_actions.actions.interfaces.action import Action
from scos_actions.actions.interfaces.signals import measurement_action_completed
from scos_actions.hardware import gps as mock_gps
from scos_actions.hardware import sigan as mock_sigan
from scos_actions.metadata.annotations.calibration_annotation import (
CalibrationAnnotation,
)
Expand All @@ -23,7 +22,7 @@ class MeasurementAction(Action):

"""

def __init__(self, parameters, sigan=mock_sigan, gps=mock_gps):
def __init__(self, parameters, sigan, gps=mock_gps):
super().__init__(parameters, sigan, gps)
self.received_samples = 0

Expand Down
3 changes: 3 additions & 0 deletions scos_actions/actions/interfaces/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@

# Provides arguments: 'sigan_healthy'
monitor_action_completed = Signal()

# Provides argument: 'component'
register_component_with_status = Signal()
3 changes: 1 addition & 2 deletions scos_actions/actions/sync_gps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

from scos_actions.actions.interfaces.action import Action
from scos_actions.actions.interfaces.signals import location_action_completed
from scos_actions.hardware import sigan as mock_sigan

logger = logging.getLogger(__name__)


class SyncGps(Action):
"""Query the GPS and synchronize time and location."""

def __init__(self, gps, parameters={}, sigan=mock_sigan):
def __init__(self, gps, parameters, sigan):
super().__init__(parameters=parameters, sigan=sigan, gps=gps)

def __call__(self, schedule_entry_json, task_id):
Expand Down
5 changes: 0 additions & 5 deletions scos_actions/actions/tests/test_preselector.py

This file was deleted.

36 changes: 36 additions & 0 deletions scos_actions/calibration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import logging

from scos_actions.calibration.calibration import load_from_json
from scos_actions.settings import SENSOR_CALIBRATION_FILE, SIGAN_CALIBRATION_FILE

logger = logging.getLogger(__name__)


def get_sigan_calibration(sigan_cal_file):
try:
sigan_cal = load_from_json(sigan_cal_file)
except Exception as err:
logger.error("Unable to load sigan calibration data, reverting to none")
logger.exception(err)
sigan_cal = None

return sigan_cal


def get_sensor_calibration(sensor_cal_file):
"""Get calibration data from sensor_cal_file and sigan_cal_file."""
try:
sensor_cal = load_from_json(sensor_cal_file)
except Exception as err:
logger.error("Unable to load sensor calibration data, reverting to none")
logger.exception(err)
sensor_cal = None
return sensor_cal


logger.info(f"Loading sensor cal file: {SENSOR_CALIBRATION_FILE}")
sensor_calibration = get_sensor_calibration(SENSOR_CALIBRATION_FILE)
logger.info(f"Loading sigan cal file: {SIGAN_CALIBRATION_FILE}")
sigan_calibration = get_sigan_calibration(SIGAN_CALIBRATION_FILE)
if sensor_calibration:
logger.info(f"last sensor cal: {sensor_calibration.calibration_datetime}")
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def get_calibration_dict(self, args):
cal_data = filter_by_parameter(cal_data, setting, setting_value)
if "calibration_datetime" not in cal_data:
cal_data["calibration_datetime"] = self.calibration_datetime
logger.info("Cal Data: " + str(cal_data))
logger.info(f"Cal Data: {cal_data}")
return cal_data

def update(self, params, calibration_datetime, gain, noise_figure, temp, file_path):
Expand Down Expand Up @@ -135,7 +135,7 @@ def filter_by_parameter(calibrations, parameter, value):

def check_floor_of_parameter(calibrations, parameter, value):
value = math.floor(value)
logger.debug("Checking floor value of: " + str(value))
logger.debug(f"Checking floor value of: {value}")
if value in calibrations:
return calibrations[value]
else:
Expand All @@ -144,7 +144,7 @@ def check_floor_of_parameter(calibrations, parameter, value):

def check_ceiling_of_parameter(calibrations, parameter, value):
value = math.ceil(value)
logger.debug("Checking ceiling at: " + str(value))
logger.debug(f"Checking ceiling at: {value}")
if value in calibrations:
return calibrations[value]
else:
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
import pytz
from pytz import timezone

from scos_actions import calibration, utils
from scos_actions.calibration import Calibration
from scos_actions.settings import sensor_calibration, sigan_calibration
from scos_actions.calibration import sensor_calibration, sigan_calibration
from scos_actions.calibration.calibration import (
Calibration,
convert_keys,
filter_by_parameter,
load_from_json,
)
from scos_actions.tests.resources.utils import easy_gain
from scos_actions.utils import get_datetime_str_now


class TestCalibrationFile:
Expand Down Expand Up @@ -180,7 +185,7 @@ def setup_calibration_file(self, tmpdir):
json.dump(cal_data, file, indent=4)

# Load the data back in
self.sample_cal = calibration.load_from_json(self.calibration_file)
self.sample_cal = load_from_json(self.calibration_file)

# Create a list of previous points to ensure that we don't repeat
self.pytest_points = []
Expand All @@ -200,7 +205,7 @@ def setup_calibration_file(self, tmpdir):
def test_filter_by_parameter_out_of_range(self):
calibrations = {200.0: {"some_cal_data"}, 300.0: {"more cal data"}}
with pytest.raises(Exception) as e_info:
cal = calibration.filter_by_parameter(calibrations, "frequency", 400.0)
cal = filter_by_parameter(calibrations, "frequency", 400.0)
assert (
e_info.value.args[0]
== "No calibration was performed with frequency at 400.0"
Expand All @@ -212,7 +217,7 @@ def test_filter_by_parameter_in_range_requires_match(self):
300.0: {"Gain": "Gain at 300.0"},
}
with pytest.raises(Exception) as e_info:
cal = calibration.filter_by_parameter(calibrations, "frequency", 150.0)
cal = filter_by_parameter(calibrations, "frequency", 150.0)
assert (
e_info.value.args[0]
== "No calibration was performed with frequency at 150.0"
Expand Down Expand Up @@ -259,7 +264,7 @@ def test_convert_keys(self):
"100": {"2000": {"40": {"cal_data": 5}}},
"200": {"2000": {"40": {"cal_data": 6}}},
}
converted_cal = calibration.convert_keys(test_cal)
converted_cal = convert_keys(test_cal)
keys = list(test_cal.keys())
assert keys[0] == 100.0
second_level_keys = list(converted_cal[100.0].keys())
Expand Down Expand Up @@ -301,9 +306,9 @@ def test_update(self):
clock_rate_lookup_by_sample_rate,
)
action_params = {"sample_rate": 100.0, "frequency": 200.0}
update_time = utils.get_datetime_str_now()
update_time = get_datetime_str_now()
cal.update(action_params, update_time, 30.0, 5.0, 21, "test_calibration.json")
cal_from_file = calibration.load_from_json("test_calibration.json")
cal_from_file = load_from_json("test_calibration.json")
os.remove("test_calibration.json")
local = timezone("US/Mountain")
local_cal_time = local.localize(cal.calibration_datetime)
Expand Down Expand Up @@ -331,10 +336,10 @@ def test_default_sigan_cal_location(self):

def test_filter_by_paramter_floor(self):
calibrations = {200.0: {"some_cal_data"}, 300.0: {"more cal data"}}
filtered_data = calibration.filter_by_parameter(calibrations, "", 200.1234567)
filtered_data = filter_by_parameter(calibrations, "", 200.1234567)
assert filtered_data is not None

def test_filter_by_parameter_ceil(self):
calibrations = {700.5e6: {"some_cal_data"}, 300.0: {"more cal data"}}
filtered_data = calibration.filter_by_parameter(calibrations, "", 700499999.999)
filtered_data = filter_by_parameter(calibrations, "", 700499999.999)
assert filtered_data is not None
21 changes: 12 additions & 9 deletions scos_actions/discover/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
# from scos_actions.actions import action_classes
from scos_actions.actions import action_classes
from scos_actions.actions.interfaces.signals import register_component_with_status
from scos_actions.actions.logger import Logger
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 import gps as mock_gps
from scos_actions.hardware import sigan as mock_sigan
from scos_actions.settings import ACTION_DEFINITIONS_DIR
from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer
from scos_actions.settings import ACTION_DEFINITIONS_DIR, MOCK_SIGAN

mock_sigan = MockSignalAnalyzer(randomize_values=True)
if MOCK_SIGAN:
register_component_with_status.send(mock_sigan.__class__, component=mock_sigan)
actions = {"logger": Logger()}
test_actions = {
"test_sync_gps": SyncGps(gps=mock_gps),
"test_monitor_sigan": MonitorSignalAnalyzer(sigan=mock_sigan),
"test_sync_gps": SyncGps(
parameters={"name": "test_sync_gps"}, sigan=mock_sigan, gps=mock_gps
),
"test_monitor_sigan": MonitorSignalAnalyzer(
parameters={"name": "test_monitor_sigan"}, sigan=mock_sigan
),
"logger": Logger(),
}

Expand All @@ -37,7 +44,3 @@ def init(
yaml_actions, yaml_test_actions = init()
actions.update(yaml_actions)
test_actions.update(yaml_test_actions)


def get_last_calibration_time():
return None
5 changes: 4 additions & 1 deletion scos_actions/discover/tests/test_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from scos_actions import actions
from scos_actions.discover.yaml import load_from_yaml
from scos_actions.hardware import gps, sigan
from scos_actions.hardware import gps
from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer

INVALID_YAML = b"""\
single_frequency_fft:
Expand All @@ -22,6 +23,8 @@
name: test_expected_failure
"""

sigan = MockSignalAnalyzer()


def test_load_from_yaml_existing():
"""Any existing action definitions should be valid yaml."""
Expand Down
69 changes: 59 additions & 10 deletions scos_actions/hardware/__init__.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,78 @@
import importlib
import logging
import os

from its_preselector.configuration_exception import ConfigurationException
from its_preselector.controlbyweb_web_relay import ControlByWebWebRelay

from scos_actions import utils
from scos_actions.actions.interfaces.signals import register_component_with_status
from scos_actions.capabilities import capabilities
from scos_actions.hardware.mocks.mock_gps import MockGPS
from scos_actions.hardware.mocks.mock_sigan import MockSignalAnalyzer
from scos_actions.settings import (
PRESELECTOR_CLASS,
PRESELECTOR_CONFIG_FILE,
PRESELECTOR_MODULE,
SWITCH_CONFIGS_DIR,
)
from scos_actions.status.status_registration_handler import status_registration_handler

logger = logging.getLogger(__name__)


def load_switches(switch_dir) -> dict:
switch_dict = {}
if switch_dir is not None and os.path.isdir(switch_dir):
files = os.listdir(switch_dir)
for f in files:
file_path = os.path.join(switch_dir, f)
logger.info(f"loading switch config {file_path}")
conf = utils.load_from_json(file_path)
try:
switch = ControlByWebWebRelay(conf)
logger.info(f"Adding {switch.id}")

switch_dict[switch.id] = switch
logger.info(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}")

def load_preselector(preselector_config_file):
return switch_dict


def load_preslector_from_file(preselector_config_file):
if preselector_config_file is None:
preselector_config = {}
return None
else:
preselector_config = utils.load_from_json(preselector_config_file)
try:
preselector_config = utils.load_from_json(preselector_config_file)
return load_preselector(
preselector_config, PRESELECTOR_MODULE, PRESELECTOR_CLASS
)
except ConfigurationException:
logger.error(
f"Unable to create preselector defined in: {preselector_config_file}"
)
return None

preselector_module = importlib.import_module(PRESELECTOR_MODULE)
preselector_class = getattr(preselector_module, PRESELECTOR_CLASS)
preselector = preselector_class(capabilities["sensor"], preselector_config)

return preselector
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.info(f"Registering {ps.name} as status provider")
register_component_with_status.send(__name__, component=ps)
else:
ps = None
return ps


sigan = MockSignalAnalyzer(randomize_values=True)
register_component_with_status.connect(status_registration_handler)
logger.info("Connected status registration handler")
gps = MockGPS()
preselector = load_preselector(PRESELECTOR_CONFIG_FILE)
preselector = load_preslector_from_file(PRESELECTOR_CONFIG_FILE)
switches = load_switches(SWITCH_CONFIGS_DIR)
logger.info("Loaded {switch_count} switches.".format(switch_count=(len(switches))))
3 changes: 3 additions & 0 deletions scos_actions/hardware/hardware_configuration_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class HardwareConfigurationException(Exception):
def __init__(self, message: str):
super().__init__(message)
Loading