spectre-core 0.0.25__tar.gz → 0.0.26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spectre_core-0.0.25 → spectre_core-0.0.26}/PKG-INFO +1 -1
- {spectre_core-0.0.25 → spectre_core-0.0.26}/pyproject.toml +1 -1
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/__init__.py +1 -1
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_capture_config.py +2 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/plotting/_panel_stack.py +11 -4
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/receivers/__init__.py +11 -8
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/receivers/_factory.py +16 -11
- spectre_core-0.0.26/src/spectre_core/receivers/_receiver.py +246 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/receivers/_register.py +3 -3
- spectre_core-0.0.25/src/spectre_core/receivers/_spec_names.py → spectre_core-0.0.26/src/spectre_core/receivers/_specs.py +42 -7
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/_usrp.py → spectre_core-0.0.26/src/spectre_core/receivers/plugins/_b200mini.py +87 -44
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/gr/_usrp.py → spectre_core-0.0.26/src/spectre_core/receivers/plugins/_b200mini_gr.py +38 -61
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_custom.py +20 -0
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/gr/_base.py → spectre_core-0.0.26/src/spectre_core/receivers/plugins/_gr.py +1 -1
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/receivers/plugins/_receiver_names.py +5 -3
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_rsp1a.py +64 -0
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_rsp1a_gr.py +112 -0
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_rspduo.py +76 -0
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_rspduo_gr.py +165 -0
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_sdrplay_receiver.py +262 -0
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_signal_generator.py +225 -0
- spectre_core-0.0.26/src/spectre_core/receivers/plugins/_signal_generator_gr.py +77 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/spectrograms/_analytical.py +18 -18
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core.egg-info/PKG-INFO +1 -1
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core.egg-info/SOURCES.txt +9 -10
- spectre_core-0.0.26/tests/test_receivers.py +160 -0
- spectre_core-0.0.25/src/spectre_core/receivers/_base.py +0 -242
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/_b200mini.py +0 -71
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/_rsp1a.py +0 -69
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/_rspduo.py +0 -86
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/_sdrplay_receiver.py +0 -158
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/_test.py +0 -225
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/gr/__init__.py +0 -3
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/gr/_rsp1a.py +0 -127
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/gr/_rspduo.py +0 -202
- spectre_core-0.0.25/src/spectre_core/receivers/plugins/gr/_test.py +0 -117
- spectre_core-0.0.25/tests/test_receivers.py +0 -3
- {spectre_core-0.0.25 → spectre_core-0.0.26}/LICENSE +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/README.md +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/setup.cfg +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/_file_io/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/_file_io/file_handlers.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/_base.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/_batches.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/_factory.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/_register.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/plugins/_batch_keys.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/plugins/_callisto.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/batches/plugins/_iq_stream.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_capture_modes.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_capture_templates.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_parameters.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_pconstraints.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_pnames.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_ptemplates.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_pvalidators.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/config/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/config/_paths.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/config/_time_formats.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/exceptions.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/jobs/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/jobs/_duration.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/jobs/_jobs.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/jobs/_workers.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/logs/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/logs/_configure.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/logs/_decorators.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/logs/_logs.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/logs/_process_types.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/plotting/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/plotting/_base.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/plotting/_format.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/plotting/_panel_names.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/plotting/_panels.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/_base.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/_factory.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/_post_processor.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/_register.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/plugins/_event_handler_keys.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/plugins/_fixed_center_frequency.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/post_processing/plugins/_swept_center_frequency.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/py.typed +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/receivers/plugins/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/spectrograms/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/spectrograms/_array_operations.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/spectrograms/_spectrogram.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/spectrograms/_transform.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/wgetting/__init__.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/wgetting/_callisto.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core.egg-info/dependency_links.txt +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core.egg-info/requires.txt +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core.egg-info/top_level.txt +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_batches.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_capture_configs.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_config.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_jobs.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_logs.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_plotting.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_post_processing.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_spectrograms.py +0 -0
- {spectre_core-0.0.25 → spectre_core-0.0.26}/tests/test_wgetting.py +0 -0
{spectre_core-0.0.25 → spectre_core-0.0.26}/src/spectre_core/capture_configs/_capture_config.py
RENAMED
@@ -49,6 +49,8 @@ class CaptureConfig(JsonHandler):
|
|
49
49
|
|
50
50
|
Some substrings are reserved for third-party spectrogram data.
|
51
51
|
"""
|
52
|
+
if "_" in tag:
|
53
|
+
raise ValueError("An underscore is not allowed in a capture config tag.")
|
52
54
|
if "callisto" in tag:
|
53
55
|
raise ValueError(
|
54
56
|
f"The substring `callisto` is reserved, and is not allowed in a capture config tag."
|
@@ -10,6 +10,7 @@ from matplotlib.figure import Figure
|
|
10
10
|
from matplotlib.axes import Axes
|
11
11
|
from matplotlib import use
|
12
12
|
from datetime import datetime
|
13
|
+
import gc
|
13
14
|
|
14
15
|
from spectre_core.spectrograms import TimeType
|
15
16
|
from spectre_core.config import TimeFormat, get_batches_dir_path
|
@@ -281,12 +282,19 @@ class PanelStack:
|
|
281
282
|
|
282
283
|
self._overlay_superimposed_panels()
|
283
284
|
|
285
|
+
def _close(self) -> None:
|
286
|
+
"""Prevent memory leaks once a figure has been created, and successfully visualised."""
|
287
|
+
self._get_fig().clear()
|
288
|
+
plt.close(self._fig)
|
289
|
+
# Garbage collection seems to be required to prevent the memory leak.
|
290
|
+
# See https://github.com/jcfitzpatrick12/spectre/issues/128
|
291
|
+
gc.collect()
|
292
|
+
|
284
293
|
def show(self) -> None:
|
285
294
|
"""Display the panel stack figure."""
|
286
295
|
self._make_figure()
|
287
296
|
self._get_fig().show()
|
288
|
-
self.
|
289
|
-
plt.close(self._fig)
|
297
|
+
self._close()
|
290
298
|
|
291
299
|
def save(
|
292
300
|
self,
|
@@ -312,6 +320,5 @@ class PanelStack:
|
|
312
320
|
# If the parent directory does not exist, create it.
|
313
321
|
os.makedirs(os.path.dirname(batch_file_path), exist_ok=True)
|
314
322
|
self._get_fig().savefig(batch_file_path)
|
315
|
-
self.
|
316
|
-
plt.close(self._fig)
|
323
|
+
self._close()
|
317
324
|
return batch_file_path
|
@@ -2,27 +2,30 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
"""A vendor-neutral interface for
|
5
|
+
"""A vendor-neutral interface for capturing data from SDRs."""
|
6
6
|
|
7
7
|
from .plugins._receiver_names import ReceiverName
|
8
|
-
from .plugins.
|
8
|
+
from .plugins._signal_generator import SignalGenerator
|
9
9
|
from .plugins._rsp1a import RSP1A
|
10
10
|
from .plugins._rspduo import RSPduo
|
11
11
|
from .plugins._b200mini import B200mini
|
12
12
|
|
13
|
-
from .
|
13
|
+
from ._receiver import Receiver, ReceiverComponents
|
14
|
+
from ._specs import SpecName, Specs
|
14
15
|
from ._factory import get_receiver
|
15
16
|
from ._register import get_registered_receivers
|
16
|
-
from ._spec_names import SpecName
|
17
17
|
|
18
18
|
__all__ = [
|
19
|
-
"
|
19
|
+
"Receiver",
|
20
|
+
"ReceiverComponents",
|
21
|
+
"Specs",
|
22
|
+
"SpecName",
|
23
|
+
"ReceiverName",
|
24
|
+
"SignalGenerator",
|
20
25
|
"RSP1A",
|
21
26
|
"RSPduo",
|
22
27
|
"B200mini",
|
23
|
-
"
|
28
|
+
"Custom",
|
24
29
|
"get_receiver",
|
25
30
|
"get_registered_receivers",
|
26
|
-
"SpecName",
|
27
|
-
"ReceiverName",
|
28
31
|
]
|
@@ -2,16 +2,23 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
from typing import Optional, overload, Literal
|
5
|
+
from typing import Optional, overload, Literal
|
6
6
|
|
7
7
|
from spectre_core.exceptions import ReceiverNotFoundError
|
8
8
|
from ._register import receivers
|
9
|
-
from .
|
9
|
+
from ._receiver import Receiver
|
10
10
|
from .plugins._receiver_names import ReceiverName
|
11
|
+
from .plugins._signal_generator import SignalGenerator
|
11
12
|
from .plugins._rsp1a import RSP1A
|
12
13
|
from .plugins._rspduo import RSPduo
|
13
|
-
from .plugins._test import Test
|
14
14
|
from .plugins._b200mini import B200mini
|
15
|
+
from .plugins._custom import CustomReceiver
|
16
|
+
|
17
|
+
|
18
|
+
@overload
|
19
|
+
def get_receiver(
|
20
|
+
receiver_name: Literal[ReceiverName.SIGNAL_GENERATOR], mode: Optional[str] = None
|
21
|
+
) -> SignalGenerator: ...
|
15
22
|
|
16
23
|
|
17
24
|
@overload
|
@@ -28,25 +35,23 @@ def get_receiver(
|
|
28
35
|
|
29
36
|
@overload
|
30
37
|
def get_receiver(
|
31
|
-
receiver_name: Literal[ReceiverName.
|
32
|
-
) ->
|
38
|
+
receiver_name: Literal[ReceiverName.B200MINI], mode: Optional[str] = None
|
39
|
+
) -> B200mini: ...
|
33
40
|
|
34
41
|
|
35
42
|
@overload
|
36
43
|
def get_receiver(
|
37
|
-
receiver_name: Literal[ReceiverName.
|
38
|
-
) ->
|
44
|
+
receiver_name: Literal[ReceiverName.CUSTOM], mode: Optional[str] = None
|
45
|
+
) -> CustomReceiver: ...
|
39
46
|
|
40
47
|
|
41
48
|
@overload
|
42
49
|
def get_receiver(
|
43
50
|
receiver_name: ReceiverName, mode: Optional[str] = None
|
44
|
-
) ->
|
51
|
+
) -> Receiver: ...
|
45
52
|
|
46
53
|
|
47
|
-
def get_receiver(
|
48
|
-
receiver_name: ReceiverName, mode: Optional[str] = None
|
49
|
-
) -> BaseReceiver:
|
54
|
+
def get_receiver(receiver_name: ReceiverName, mode: Optional[str] = None) -> Receiver:
|
50
55
|
"""Get a registered receiver.
|
51
56
|
|
52
57
|
:param receiver_name: The name of the receiver.
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024-2025 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
from typing import Callable, Optional, TypeVar, Generic, Any
|
6
|
+
|
7
|
+
from spectre_core.exceptions import ModeNotFoundError
|
8
|
+
from spectre_core.capture_configs import CaptureTemplate, Parameters, CaptureConfig
|
9
|
+
from .plugins._receiver_names import ReceiverName
|
10
|
+
from ._specs import Specs, SpecName
|
11
|
+
|
12
|
+
T = TypeVar("T")
|
13
|
+
|
14
|
+
|
15
|
+
class ReceiverComponents(Generic[T]):
|
16
|
+
"""Base class for managing receiver components per operating mode."""
|
17
|
+
|
18
|
+
def __init__(self) -> None:
|
19
|
+
"""Initialise an instance of `ReceiverComponents`."""
|
20
|
+
self._components: dict[str, T] = {}
|
21
|
+
|
22
|
+
@property
|
23
|
+
def modes(self) -> list[str]:
|
24
|
+
"""Get all the added operating modes."""
|
25
|
+
return list(self._components.keys())
|
26
|
+
|
27
|
+
def add(self, mode: str, component: T) -> None:
|
28
|
+
"""Add a component for a particular operating mode.
|
29
|
+
|
30
|
+
:param mode: The operating mode for the receiver.
|
31
|
+
:param component: The component associated with this mode.
|
32
|
+
"""
|
33
|
+
self._components[mode] = component
|
34
|
+
|
35
|
+
def get(self, mode: str) -> T:
|
36
|
+
"""Retrieve the component for a particular operating mode.
|
37
|
+
|
38
|
+
:param mode: The operating mode for the receiver.
|
39
|
+
:return: The component associated with this mode.
|
40
|
+
:raises ModeNotFoundError: If the mode is not found.
|
41
|
+
"""
|
42
|
+
if mode not in self._components:
|
43
|
+
raise ModeNotFoundError(
|
44
|
+
f"Mode `{mode}` not found. Expected one of {self._components}"
|
45
|
+
)
|
46
|
+
return self._components[mode]
|
47
|
+
|
48
|
+
|
49
|
+
class CaptureMethods(ReceiverComponents[Callable[[str, Parameters], None]]):
|
50
|
+
"""Per operating mode, define how the receiver captures data."""
|
51
|
+
|
52
|
+
|
53
|
+
class CaptureTemplates(ReceiverComponents[CaptureTemplate]):
|
54
|
+
"""Per operating mode, define what parameters are required in a capture config, and the values each parameter can take."""
|
55
|
+
|
56
|
+
|
57
|
+
class PValidators(ReceiverComponents[Callable[[Parameters], None]]):
|
58
|
+
"""Validate capture config parameters en groupe, per operating mode."""
|
59
|
+
|
60
|
+
|
61
|
+
def default_pvalidator(parameters: Parameters) -> None:
|
62
|
+
"""A noop parameter validator. Doesn't check anything at all."""
|
63
|
+
|
64
|
+
|
65
|
+
class Receiver:
|
66
|
+
"""An abstraction layer for software-defined radio receivers."""
|
67
|
+
|
68
|
+
def __init__(
|
69
|
+
self,
|
70
|
+
name: ReceiverName,
|
71
|
+
mode: Optional[str] = None,
|
72
|
+
specs: Optional[Specs] = None,
|
73
|
+
capture_methods: Optional[CaptureMethods] = None,
|
74
|
+
capture_templates: Optional[CaptureTemplates] = None,
|
75
|
+
pvalidators: Optional[PValidators] = None,
|
76
|
+
) -> None:
|
77
|
+
"""Initialise a receiver instance.
|
78
|
+
|
79
|
+
:param name: The name of the receiver.
|
80
|
+
:param capture_methods: Defines how the receiver captures data per mode.
|
81
|
+
:param capture_templates: Defines required parameters per mode.
|
82
|
+
:param pvalidators: Defines parameter validation functions per mode.
|
83
|
+
:param specs: Hardware specifications for the receiver.
|
84
|
+
:param mode: The initial active operating mode. Defaults to None.
|
85
|
+
"""
|
86
|
+
self._name = name
|
87
|
+
self._mode = mode
|
88
|
+
self._specs = specs or Specs()
|
89
|
+
self._capture_methods = capture_methods or CaptureMethods()
|
90
|
+
self._capture_templates = capture_templates or CaptureTemplates()
|
91
|
+
self._pvalidators = pvalidators or PValidators()
|
92
|
+
|
93
|
+
@property
|
94
|
+
def name(self) -> ReceiverName:
|
95
|
+
"""Retrieve the receiver's name."""
|
96
|
+
return self._name
|
97
|
+
|
98
|
+
@property
|
99
|
+
def mode(self) -> Optional[str]:
|
100
|
+
"""Retrieve the operating mode."""
|
101
|
+
return self._mode
|
102
|
+
|
103
|
+
@mode.setter
|
104
|
+
def mode(self, value: str) -> None:
|
105
|
+
"""Set the operating mode.
|
106
|
+
|
107
|
+
:param value: The new operating mode of the receiver. Use `None` to unset the mode.
|
108
|
+
"""
|
109
|
+
if value not in self.modes:
|
110
|
+
raise ModeNotFoundError(
|
111
|
+
f"Mode `{value}` not found. Expected one of {self.modes}"
|
112
|
+
)
|
113
|
+
self._mode = value
|
114
|
+
|
115
|
+
@property
|
116
|
+
def modes(self) -> list[str]:
|
117
|
+
"""The operating modes for the receiver.
|
118
|
+
|
119
|
+
:raises ValueError: If the modes are inconsistent.
|
120
|
+
"""
|
121
|
+
if (
|
122
|
+
not self._capture_methods.modes
|
123
|
+
== self._pvalidators.modes
|
124
|
+
== self._capture_templates.modes
|
125
|
+
):
|
126
|
+
raise ValueError(f"Modes are inconsistent for the receiver {self.name}.")
|
127
|
+
return self._capture_methods.modes
|
128
|
+
|
129
|
+
@property
|
130
|
+
def active_mode(self) -> str:
|
131
|
+
"""Retrieve the active operating mode, raising an error if not set.
|
132
|
+
|
133
|
+
:raises ValueError: If no mode is currently set.
|
134
|
+
:return: The active operating mode.
|
135
|
+
"""
|
136
|
+
if self._mode is None:
|
137
|
+
raise ValueError(
|
138
|
+
f"An active mode is not set for the receiver `{self.name.value}`. Currently, the mode is {self._mode}"
|
139
|
+
)
|
140
|
+
return self._mode
|
141
|
+
|
142
|
+
@property
|
143
|
+
def capture_method(self) -> Callable[[str, Parameters], None]:
|
144
|
+
"""Retrieve the capture method for the active operating mode."""
|
145
|
+
return self._capture_methods.get(self.active_mode)
|
146
|
+
|
147
|
+
@property
|
148
|
+
def capture_template(self) -> CaptureTemplate:
|
149
|
+
"""Retrieve the capture template for the active operating mode."""
|
150
|
+
return self._capture_templates.get(self.active_mode)
|
151
|
+
|
152
|
+
@property
|
153
|
+
def pvalidator(self) -> Callable[[Parameters], None]:
|
154
|
+
"""Retrieve the parameter validator for the active operating mode."""
|
155
|
+
return self._pvalidators.get(self.active_mode)
|
156
|
+
|
157
|
+
@property
|
158
|
+
def specs(self) -> dict[SpecName, Any]:
|
159
|
+
"""Retrieve all hardware specifications.
|
160
|
+
|
161
|
+
:return: A dictionary of all specifications.
|
162
|
+
"""
|
163
|
+
return self._specs.all()
|
164
|
+
|
165
|
+
def start_capture(self, tag: str) -> None:
|
166
|
+
"""Start capturing data using the active operating mode.
|
167
|
+
|
168
|
+
:param tag: The tag identifying the capture config.
|
169
|
+
:raises ValueError: If no mode is currently set.
|
170
|
+
"""
|
171
|
+
self.capture_method(tag, self.load_parameters(tag))
|
172
|
+
|
173
|
+
def save_parameters(
|
174
|
+
self,
|
175
|
+
tag: str,
|
176
|
+
parameters: Parameters,
|
177
|
+
force: bool = False,
|
178
|
+
validate: bool = True,
|
179
|
+
) -> None:
|
180
|
+
"""Save parameters to a capture config.
|
181
|
+
|
182
|
+
:param tag: The tag identifying the capture config.
|
183
|
+
:param parameters: The parameters to save.
|
184
|
+
:param force: If True, overwrite existing configuration if it exists.
|
185
|
+
:param validate: If True, apply the capture template and pvalidator.
|
186
|
+
:raises ValueError: If no mode is currently set.
|
187
|
+
"""
|
188
|
+
if validate:
|
189
|
+
parameters = self.capture_template.apply_template(parameters)
|
190
|
+
self.pvalidator(parameters)
|
191
|
+
|
192
|
+
capture_config = CaptureConfig(tag)
|
193
|
+
capture_config.save_parameters(
|
194
|
+
self.name.value, self.active_mode, parameters, force
|
195
|
+
)
|
196
|
+
|
197
|
+
def load_parameters(self, tag: str, validate: bool = True) -> Parameters:
|
198
|
+
"""Load parameters from a capture config.
|
199
|
+
|
200
|
+
:param tag: The tag identifying the capture config.
|
201
|
+
:param validate: If True, apply the capture template and pvalidator.
|
202
|
+
:raises ValueError: If no mode is currently set.
|
203
|
+
:return: The validated parameters stored in the configuration.
|
204
|
+
"""
|
205
|
+
capture_config = CaptureConfig(tag)
|
206
|
+
|
207
|
+
if validate:
|
208
|
+
parameters = self.capture_template.apply_template(capture_config.parameters)
|
209
|
+
self.pvalidator(parameters)
|
210
|
+
return parameters
|
211
|
+
else:
|
212
|
+
return capture_config.parameters
|
213
|
+
|
214
|
+
def add_mode(
|
215
|
+
self,
|
216
|
+
mode: str,
|
217
|
+
capture_method: Callable[[str, Parameters], None],
|
218
|
+
capture_template: CaptureTemplate,
|
219
|
+
pvalidator: Callable[[Parameters], None] = default_pvalidator,
|
220
|
+
) -> None:
|
221
|
+
"""Add a new mode to the receiver.
|
222
|
+
|
223
|
+
:param mode: The name of the new mode.
|
224
|
+
:param capture_method: Define how data is captured in this mode.
|
225
|
+
:param capture_template: Define what parameters are required in a capture config, and the values each parameter can take.
|
226
|
+
:param pvalidator: The function to validate parameters for this mode, as a group.
|
227
|
+
"""
|
228
|
+
self._capture_methods.add(mode, capture_method)
|
229
|
+
self._capture_templates.add(mode, capture_template)
|
230
|
+
self._pvalidators.add(mode, pvalidator)
|
231
|
+
|
232
|
+
def add_spec(self, name: SpecName, value: Any) -> None:
|
233
|
+
"""Add a hardware specification.
|
234
|
+
|
235
|
+
:param name: The specification's name.
|
236
|
+
:param value: The specification's value.
|
237
|
+
"""
|
238
|
+
self._specs.add(name, value)
|
239
|
+
|
240
|
+
def get_spec(self, name: SpecName) -> Any:
|
241
|
+
"""Retrieve a specific hardware specification.
|
242
|
+
|
243
|
+
:param name: The specification's name.
|
244
|
+
:return: The specification's value.
|
245
|
+
"""
|
246
|
+
return self._specs.get(name)
|
@@ -5,12 +5,12 @@
|
|
5
5
|
from typing import TypeVar, Callable, Type
|
6
6
|
|
7
7
|
from .plugins._receiver_names import ReceiverName
|
8
|
-
from .
|
8
|
+
from ._receiver import Receiver
|
9
9
|
|
10
10
|
# map populated at runtime via the `register_receiver` decorator.
|
11
|
-
receivers: dict[ReceiverName, Type[
|
11
|
+
receivers: dict[ReceiverName, Type[Receiver]] = {}
|
12
12
|
|
13
|
-
T = TypeVar("T", bound=
|
13
|
+
T = TypeVar("T", bound=Receiver)
|
14
14
|
|
15
15
|
|
16
16
|
def register_receiver(
|
@@ -2,11 +2,12 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
+
from typing import Any
|
5
6
|
from enum import Enum
|
6
7
|
|
7
8
|
|
8
9
|
class SpecName(Enum):
|
9
|
-
"""
|
10
|
+
"""Hardware specification names for SDR receivers.
|
10
11
|
|
11
12
|
:ivar FREQUENCY_LOWER_BOUND: The lower bound for the center frequency, in Hz.
|
12
13
|
:ivar FREQUENCY_UPPER_BOUND: The upper bound for the center frequency, in Hz.
|
@@ -16,15 +17,14 @@ class SpecName(Enum):
|
|
16
17
|
:ivar BANDWIDTH_UPPER_BOUND: The upper bound for the bandwidth, in Hz.
|
17
18
|
:ivar BANDWIDTH_OPTIONS: The permitted bandwidths for the receiver, in Hz.
|
18
19
|
:ivar IF_GAIN_UPPER_BOUND: The upper bound for the intermediate frequency gain, in dB.
|
19
|
-
|
20
|
+
:ivar IF_GAIN_LOWER_BOUND: The lower bound for the intermediate frequency gain, in dB.
|
20
21
|
:ivar RF_GAIN_UPPER_BOUND: The upper bound for the radio frequency gain, in dB.
|
21
|
-
|
22
|
+
:ivar RF_GAIN_LOWER_BOUND: The lower bound for the radio frequency gain, in dB.
|
22
23
|
:ivar GAIN_UPPER_BOUND: The upper bound for the gain, in dB.
|
23
24
|
:ivar WIRE_FORMATS: Supported data types transferred over the bus/network.
|
24
|
-
:ivar MASTER_CLOCK_RATE_LOWER_BOUND:
|
25
|
-
:ivar MASTER_CLOCK_RATE_UPPER_BOUND:
|
26
|
-
:ivar API_RETUNING_LATENCY:
|
27
|
-
for a receiver to retune its center frequency and the actual physical update of the center frequency.
|
25
|
+
:ivar MASTER_CLOCK_RATE_LOWER_BOUND: The lower bound for the SDR reference clock rate, in Hz.
|
26
|
+
:ivar MASTER_CLOCK_RATE_UPPER_BOUND: The upper bound for the SDR reference clock rate, in Hz.
|
27
|
+
:ivar API_RETUNING_LATENCY: Estimated delay between issuing a retune command and the actual center frequency update.
|
28
28
|
"""
|
29
29
|
|
30
30
|
FREQUENCY_LOWER_BOUND = "frequency_lower_bound"
|
@@ -35,9 +35,44 @@ class SpecName(Enum):
|
|
35
35
|
BANDWIDTH_UPPER_BOUND = "bandwidth_upper_bound"
|
36
36
|
BANDWIDTH_OPTIONS = "bandwidth_options"
|
37
37
|
IF_GAIN_UPPER_BOUND = "if_gain_upper_bound"
|
38
|
+
IF_GAIN_LOWER_BOUND = "if_gain_lower_bound"
|
38
39
|
RF_GAIN_UPPER_BOUND = "rf_gain_upper_bound"
|
40
|
+
RF_GAIN_LOWER_BOUND = "rf_gain_lower_bound"
|
39
41
|
GAIN_UPPER_BOUND = "gain_upper_bound"
|
40
42
|
WIRE_FORMATS = "wire_formats"
|
41
43
|
MASTER_CLOCK_RATE_LOWER_BOUND = "master_clock_rate_lower_bound"
|
42
44
|
MASTER_CLOCK_RATE_UPPER_BOUND = "master_clock_rate_upper_bound"
|
43
45
|
API_RETUNING_LATENCY = "api_retuning_latency"
|
46
|
+
|
47
|
+
|
48
|
+
class Specs:
|
49
|
+
"""Define hardware specifications."""
|
50
|
+
|
51
|
+
def __init__(self) -> None:
|
52
|
+
"""Initialise an instance of `Specs`."""
|
53
|
+
self._specs: dict[SpecName, Any] = {}
|
54
|
+
|
55
|
+
def add(self, name: SpecName, value: Any) -> None:
|
56
|
+
"""Add a hardware specification.
|
57
|
+
|
58
|
+
:param name: The specification's name.
|
59
|
+
:param value: The specification's value.
|
60
|
+
"""
|
61
|
+
self._specs[name] = value
|
62
|
+
|
63
|
+
def get(self, name: SpecName) -> Any:
|
64
|
+
"""Get a hardware specification.
|
65
|
+
|
66
|
+
:param name: The specification's name.
|
67
|
+
:return: The specification's value.
|
68
|
+
:raises KeyError: If the specification is not found.
|
69
|
+
"""
|
70
|
+
if name not in self._specs:
|
71
|
+
raise KeyError(
|
72
|
+
f"Specification `{name}` not found. Expected one of {list(self._specs.keys())}"
|
73
|
+
)
|
74
|
+
return self._specs[name]
|
75
|
+
|
76
|
+
def all(self) -> dict[SpecName, Any]:
|
77
|
+
"""Retrieve all hardware specifications."""
|
78
|
+
return self._specs
|