spectre-core 0.0.24__py3-none-any.whl → 0.0.26__py3-none-any.whl
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/__init__.py +1 -1
- spectre_core/capture_configs/_capture_config.py +2 -0
- spectre_core/plotting/_panel_stack.py +11 -0
- spectre_core/receivers/__init__.py +11 -8
- spectre_core/receivers/_factory.py +16 -11
- spectre_core/receivers/_receiver.py +246 -0
- spectre_core/receivers/_register.py +3 -3
- spectre_core/receivers/{_spec_names.py → _specs.py} +42 -7
- spectre_core/receivers/plugins/_b200mini.py +219 -34
- spectre_core/receivers/plugins/{gr/_usrp.py → _b200mini_gr.py} +38 -61
- spectre_core/receivers/plugins/_custom.py +20 -0
- spectre_core/receivers/plugins/{gr/_base.py → _gr.py} +1 -1
- spectre_core/receivers/plugins/_receiver_names.py +5 -3
- spectre_core/receivers/plugins/_rsp1a.py +38 -43
- spectre_core/receivers/plugins/_rsp1a_gr.py +112 -0
- spectre_core/receivers/plugins/_rspduo.py +47 -57
- spectre_core/receivers/plugins/_rspduo_gr.py +165 -0
- spectre_core/receivers/plugins/_sdrplay_receiver.py +146 -42
- spectre_core/receivers/plugins/_signal_generator.py +225 -0
- spectre_core/receivers/plugins/_signal_generator_gr.py +77 -0
- spectre_core/spectrograms/_analytical.py +18 -18
- {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/METADATA +1 -1
- {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/RECORD +26 -27
- spectre_core/receivers/_base.py +0 -242
- spectre_core/receivers/plugins/_test.py +0 -225
- spectre_core/receivers/plugins/_usrp.py +0 -213
- spectre_core/receivers/plugins/gr/__init__.py +0 -3
- spectre_core/receivers/plugins/gr/_rsp1a.py +0 -127
- spectre_core/receivers/plugins/gr/_rspduo.py +0 -202
- spectre_core/receivers/plugins/gr/_test.py +0 -117
- {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/licenses/LICENSE +0 -0
- {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/top_level.txt +0 -0
@@ -52,14 +52,14 @@ class _AnalyticalFactory:
|
|
52
52
|
def __init__(self) -> None:
|
53
53
|
"""Initialises an instance of the `_AnalyticalFactory` class."""
|
54
54
|
self._builders: dict[str, Callable[[int, CaptureConfig], Spectrogram]] = {
|
55
|
-
"
|
56
|
-
"
|
55
|
+
"cosine_wave": self._cosine_wave,
|
56
|
+
"constant_staircase": self._constant_staircase,
|
57
57
|
}
|
58
58
|
|
59
59
|
@property
|
60
60
|
def builders(self) -> dict[str, Callable[[int, CaptureConfig], Spectrogram]]:
|
61
61
|
"""
|
62
|
-
Provides a mapping from each `
|
62
|
+
Provides a mapping from each `SignalGenerator` receiver mode to its corresponding builder method.
|
63
63
|
|
64
64
|
Each builder method generates the expected spectrograms for a session run in the associated mode.
|
65
65
|
"""
|
@@ -67,23 +67,23 @@ class _AnalyticalFactory:
|
|
67
67
|
|
68
68
|
@property
|
69
69
|
def test_modes(self) -> list[str]:
|
70
|
-
"""Returns the available modes for the `
|
70
|
+
"""Returns the available modes for the `SignalGenerator` receiver."""
|
71
71
|
return list(self.builders.keys())
|
72
72
|
|
73
73
|
def get_spectrogram(
|
74
74
|
self, num_spectrums: int, capture_config: CaptureConfig
|
75
75
|
) -> Spectrogram:
|
76
76
|
"""
|
77
|
-
Generates an analytical spectrogram based on the capture config for a `
|
77
|
+
Generates an analytical spectrogram based on the capture config for a `SignalGenerator` receiver.
|
78
78
|
|
79
79
|
:param num_spectrums: The number of spectrums to include in the output spectrogram.
|
80
|
-
:param capture_config: The capture config used for `
|
81
|
-
:raises ValueError: Raised if the capture config is not associated with a `
|
82
|
-
:raises ModeNotFoundError: Raised if the specified `
|
80
|
+
:param capture_config: The capture config used for `SignalGenerator` receiver data capture.
|
81
|
+
:raises ValueError: Raised if the capture config is not associated with a `SignalGenerator` receiver.
|
82
|
+
:raises ModeNotFoundError: Raised if the specified `SignalGenerator` mode in the capture config lacks
|
83
83
|
a corresponding builder method.
|
84
|
-
:return: The expected spectrogram for running a session with the `
|
84
|
+
:return: The expected spectrogram for running a session with the `SignalGenerator` receiver in the specified mode.
|
85
85
|
"""
|
86
|
-
if capture_config.receiver_name != "
|
86
|
+
if capture_config.receiver_name != "signal_generator":
|
87
87
|
raise ValueError(
|
88
88
|
f"Input capture config must correspond to the test receiver"
|
89
89
|
)
|
@@ -95,10 +95,10 @@ class _AnalyticalFactory:
|
|
95
95
|
)
|
96
96
|
return builder_method(num_spectrums, capture_config)
|
97
97
|
|
98
|
-
def
|
98
|
+
def _cosine_wave(
|
99
99
|
self, num_spectrums: int, capture_config: CaptureConfig
|
100
100
|
) -> Spectrogram:
|
101
|
-
"""Creates the expected spectrogram for the `
|
101
|
+
"""Creates the expected spectrogram for the `SignalGenerator` receiver operating in the mode `cosine_wave`."""
|
102
102
|
# Extract necessary parameters from the capture config.
|
103
103
|
window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
|
104
104
|
sample_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
|
@@ -146,10 +146,10 @@ class _AnalyticalFactory:
|
|
146
146
|
SpectrumUnit.AMPLITUDE,
|
147
147
|
)
|
148
148
|
|
149
|
-
def
|
149
|
+
def _constant_staircase(
|
150
150
|
self, num_spectrums: int, capture_config: CaptureConfig
|
151
151
|
) -> Spectrogram:
|
152
|
-
"""Creates the expected spectrogram for the `
|
152
|
+
"""Creates the expected spectrogram for the `SignalGenerator` receiver operating in the mode `constant_staircase`."""
|
153
153
|
# Extract necessary parameters from the capture config.
|
154
154
|
window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
|
155
155
|
min_samples_per_step = cast(
|
@@ -217,15 +217,15 @@ class _AnalyticalFactory:
|
|
217
217
|
def get_analytical_spectrogram(
|
218
218
|
num_spectrums: int, capture_config: CaptureConfig
|
219
219
|
) -> Spectrogram:
|
220
|
-
"""Each mode of the `
|
220
|
+
"""Each mode of the `SignalGenerator` receiver generates a known synthetic signal. Based on this, we can
|
221
221
|
derive an analytical solution that predicts the expected spectrogram for a session in that mode.
|
222
222
|
|
223
|
-
This function constructs the analytical spectrogram using the capture config for a `
|
223
|
+
This function constructs the analytical spectrogram using the capture config for a `SignalGenerator`
|
224
224
|
receiver operating in a specific mode.
|
225
225
|
|
226
226
|
:param num_spectrums: The number of spectrums in the output spectrogram.
|
227
227
|
:param capture_config: The capture config used for data capture.
|
228
|
-
:return: The expected, analytically derived spectrogram for the specified mode of the `
|
228
|
+
:return: The expected, analytically derived spectrogram for the specified mode of the `SignalGenerator` receiver.
|
229
229
|
"""
|
230
230
|
factory = _AnalyticalFactory()
|
231
231
|
return factory.get_spectrogram(num_spectrums, capture_config)
|
@@ -234,7 +234,7 @@ def get_analytical_spectrogram(
|
|
234
234
|
def validate_analytically(
|
235
235
|
spectrogram: Spectrogram, capture_config: CaptureConfig, absolute_tolerance: float
|
236
236
|
) -> TestResults:
|
237
|
-
"""Validate a spectrogram generated during sessions with a `
|
237
|
+
"""Validate a spectrogram generated during sessions with a `SignalGenerator` receiver operating
|
238
238
|
in a particular mode.
|
239
239
|
|
240
240
|
:param spectrogram: The spectrogram to be validated.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
spectre_core/__init__.py,sha256=
|
1
|
+
spectre_core/__init__.py,sha256=886XLvO2Z9Dnk4FKUsWgHA6m-yd4ulobVDdKN0_MczE,184
|
2
2
|
spectre_core/exceptions.py,sha256=MJSUkuH8BYFt9zfv9m4dNISELEit6Sv_M4ifXHmeBGU,1167
|
3
3
|
spectre_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
spectre_core/_file_io/__init__.py,sha256=H_-6xE-T3yUyywTe4hQn-oU5ACUumY1O-7Sp80EnKfQ,358
|
@@ -12,7 +12,7 @@ spectre_core/batches/plugins/_batch_keys.py,sha256=KbtKIMCQIay6ulvMtKE6cc_nwXeTy
|
|
12
12
|
spectre_core/batches/plugins/_callisto.py,sha256=Y03rDG2mc9sboYMeB9WxXWgsp0MXKb1ZMUKKJbEJ26Q,5871
|
13
13
|
spectre_core/batches/plugins/_iq_stream.py,sha256=exyROl9HKudLga6Ziz0QAe8ipPm6kj0wXgA8DevjeF4,11804
|
14
14
|
spectre_core/capture_configs/__init__.py,sha256=IAdhXKUY8KDwl4xBj2NRtAhfgLPdMcSDdZysUIC_HYw,1859
|
15
|
-
spectre_core/capture_configs/_capture_config.py,sha256=
|
15
|
+
spectre_core/capture_configs/_capture_config.py,sha256=dRQxFutgvQ8GXmT7IMlIgi4rk05y7c_ECjdH9J_-MPg,4333
|
16
16
|
spectre_core/capture_configs/_capture_modes.py,sha256=ESk-wb-yqUYgRSfl9HYJg01w03UwsS6kSSrkFKW9fNw,889
|
17
17
|
spectre_core/capture_configs/_capture_templates.py,sha256=Y8i8_9voMTq0IbQVUt3wdhzXdr9p5naLMLPU0LNefwQ,9867
|
18
18
|
spectre_core/capture_configs/_parameters.py,sha256=8SfNrfQD0JWpEqSJFNSQF4MFpzbPIvtfQXZgt_Sekgw,5318
|
@@ -36,7 +36,7 @@ spectre_core/plotting/__init__.py,sha256=AGu9ooOhVmzeLY7BDN7yj1W6ej1G6VBliIga0WR
|
|
36
36
|
spectre_core/plotting/_base.py,sha256=0lm_KhnexgXqrx7TfskHJiut-2KTyZ-CmBfujc2Yl4I,8783
|
37
37
|
spectre_core/plotting/_format.py,sha256=ACpwfQw5raBAQLRvLBfwQto-Hqg9JsmrZ5eLqU-51Kg,1352
|
38
38
|
spectre_core/plotting/_panel_names.py,sha256=XxWIOfQRs5hhas1X6q9vRarH06SWMC_Kv96Hy4r6jYY,799
|
39
|
-
spectre_core/plotting/_panel_stack.py,sha256
|
39
|
+
spectre_core/plotting/_panel_stack.py,sha256=-YvLkF6ykJqLvtnHzguDr_jt3Teavw341bok9b4yJBI,12028
|
40
40
|
spectre_core/plotting/_panels.py,sha256=9tk_ebpIN4ADx6vcauy-reUWg5gR_FYK9d_--_mG598,15982
|
41
41
|
spectre_core/post_processing/__init__.py,sha256=jMIfEpEi1cH61nyZXXtUxYXxN1hinvWml1tdhhZrUEo,642
|
42
42
|
spectre_core/post_processing/_base.py,sha256=cS7f0TRAbd_6vVG4dxngzdsftyq3py4PRXhEQWby-UE,6473
|
@@ -46,34 +46,33 @@ spectre_core/post_processing/_register.py,sha256=A0GkFiUVmD8DkqblxEiywMiDq00vPpI
|
|
46
46
|
spectre_core/post_processing/plugins/_event_handler_keys.py,sha256=4Gs7xcr_TqefMbL_DDij1GhUtAdCoMmmGaonzftcAiM,610
|
47
47
|
spectre_core/post_processing/plugins/_fixed_center_frequency.py,sha256=Bsq8uxEEQqxvR_Iot6rhr7qjz-I0Eb6sQ4WSiZvOYKI,4951
|
48
48
|
spectre_core/post_processing/plugins/_swept_center_frequency.py,sha256=wxiKYWgD6vQMNbNtIDb9iMf8uXauoDTyOZcAhax0me4,19937
|
49
|
-
spectre_core/receivers/__init__.py,sha256=
|
50
|
-
spectre_core/receivers/
|
51
|
-
spectre_core/receivers/
|
52
|
-
spectre_core/receivers/_register.py,sha256=
|
53
|
-
spectre_core/receivers/
|
49
|
+
spectre_core/receivers/__init__.py,sha256=91JLTWPAe-UvXh4zpN2dw3p0TspL5w2IlU1OU-BRAdc,850
|
50
|
+
spectre_core/receivers/_factory.py,sha256=CFyy2roBIpZEeeKuf6f1owVE_3IAmhuk2bYBNoFEgUE,2131
|
51
|
+
spectre_core/receivers/_receiver.py,sha256=WJfTNdgLU_pplq98xxAtpFSvGJzjHo7Gtx4LloCR8mo,8936
|
52
|
+
spectre_core/receivers/_register.py,sha256=j20Gb0CddcPlRSN5a74cwVpEdwqu9KMH-_p21pHDjWQ,1348
|
53
|
+
spectre_core/receivers/_specs.py,sha256=iOYWRwDf8cH-z5TM7yhZtIVFRZPioUtn2DCVfW20nMo,3482
|
54
54
|
spectre_core/receivers/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
-
spectre_core/receivers/plugins/_b200mini.py,sha256=
|
56
|
-
spectre_core/receivers/plugins/
|
57
|
-
spectre_core/receivers/plugins/
|
58
|
-
spectre_core/receivers/plugins/
|
59
|
-
spectre_core/receivers/plugins/
|
60
|
-
spectre_core/receivers/plugins/
|
61
|
-
spectre_core/receivers/plugins/
|
62
|
-
spectre_core/receivers/plugins/
|
63
|
-
spectre_core/receivers/plugins/
|
64
|
-
spectre_core/receivers/plugins/
|
65
|
-
spectre_core/receivers/plugins/
|
66
|
-
spectre_core/receivers/plugins/
|
67
|
-
spectre_core/receivers/plugins/gr/_usrp.py,sha256=0pyyW-Fn6rO1NlJXzXQswdJLdgbQxjD5z1PHhbhXxnk,5687
|
55
|
+
spectre_core/receivers/plugins/_b200mini.py,sha256=ob1aPIaQyNo6GFI6_fKuxDPfv9YAC1ImpDHZCLqTJwQ,8745
|
56
|
+
spectre_core/receivers/plugins/_b200mini_gr.py,sha256=TR2quhuYFMsbpOM6ahMPhTLvWJc-Te3j_GZfOybwpkQ,4926
|
57
|
+
spectre_core/receivers/plugins/_custom.py,sha256=XO_J0HBIBKfzpa1zm6qtDbqrbiH6cLUJhPT0CBsMg-0,623
|
58
|
+
spectre_core/receivers/plugins/_gr.py,sha256=g0pLqCsiW1muaF17A9jAC8hhp6VR1m89NYCIOgwDvxc,2452
|
59
|
+
spectre_core/receivers/plugins/_receiver_names.py,sha256=2grQdv76fdOGVQhJhtQOXhC22_Vu2zJ3EaSXTrmsit0,626
|
60
|
+
spectre_core/receivers/plugins/_rsp1a.py,sha256=6vx4uFUxkC4-7kC7qxL7WFLW2v1cUsvHX6pNsWNMsqA,2340
|
61
|
+
spectre_core/receivers/plugins/_rsp1a_gr.py,sha256=kI_Z8IoSdNP3AoQQGQrbcpeJctmA8WemWsQnm7oKEEk,4939
|
62
|
+
spectre_core/receivers/plugins/_rspduo.py,sha256=BjpODVms24qyMDJ_N5sYH7oKzsaa2hdF8JixRSLDq5Q,2766
|
63
|
+
spectre_core/receivers/plugins/_rspduo_gr.py,sha256=u-DSa_7wtCNEQkSqLMwC9d89C-rYOMImoH8xGGpCN-o,7517
|
64
|
+
spectre_core/receivers/plugins/_sdrplay_receiver.py,sha256=OqjILmsC0JmT5n_XlF5TMjmtsGWSb7l9E1NndAuNiHg,9868
|
65
|
+
spectre_core/receivers/plugins/_signal_generator.py,sha256=7-Vp60fqsfApWEvjcOqKpYAb-mAMaWPP4EGV3DUqnKE,7408
|
66
|
+
spectre_core/receivers/plugins/_signal_generator_gr.py,sha256=e6gPj1KJJqMmIZJkvT7n5XaoU7TTBeL5TKJXf9N99zo,3358
|
68
67
|
spectre_core/spectrograms/__init__.py,sha256=olHW9pFhYg6-yCcLL6sP8C4noLwzgBGDOO9_RDyP6Cw,803
|
69
|
-
spectre_core/spectrograms/_analytical.py,sha256=
|
68
|
+
spectre_core/spectrograms/_analytical.py,sha256=YspKx8GfS8aFcCRhyLTTyCVHfM1WA7te2JwP8wN9Gq8,11166
|
70
69
|
spectre_core/spectrograms/_array_operations.py,sha256=AsBDmxFMnC3hYRP6QCFN65Fh0OLUAY7CeCx8kqPG2ks,7518
|
71
70
|
spectre_core/spectrograms/_spectrogram.py,sha256=k0Azaz1NRV-ls1NOrwb0TpKwwcpGzjxv-MFj-MTyXsE,27192
|
72
71
|
spectre_core/spectrograms/_transform.py,sha256=S9V0dROKv3L8dg9EiPxR90XNeKaNmvbfZh66sFjETRo,11749
|
73
72
|
spectre_core/wgetting/__init__.py,sha256=9QD-8DjYq0hbU7f-NQ_Cg-TLKNbq0E0PI3yWJXJoC4k,341
|
74
73
|
spectre_core/wgetting/_callisto.py,sha256=eZyTG07WQic-w9STbx91bOYWhWFof85TX41WY5WrJg4,7148
|
75
|
-
spectre_core-0.0.
|
76
|
-
spectre_core-0.0.
|
77
|
-
spectre_core-0.0.
|
78
|
-
spectre_core-0.0.
|
79
|
-
spectre_core-0.0.
|
74
|
+
spectre_core-0.0.26.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
75
|
+
spectre_core-0.0.26.dist-info/METADATA,sha256=ETJz8h_5EtRNtcCV22cS6AOv6J9FXh4ikHXe5ollR7g,41951
|
76
|
+
spectre_core-0.0.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
77
|
+
spectre_core-0.0.26.dist-info/top_level.txt,sha256=-UsyjpFohXgZpgcZ9QbVeXhsIyF3Am8RxNFNDV_Ta2Y,13
|
78
|
+
spectre_core-0.0.26.dist-info/RECORD,,
|
spectre_core/receivers/_base.py
DELETED
@@ -1,242 +0,0 @@
|
|
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 abc import ABC, abstractmethod
|
6
|
-
from typing import Callable, Optional, Literal, overload, Any
|
7
|
-
|
8
|
-
from spectre_core.exceptions import ModeNotFoundError
|
9
|
-
from spectre_core.capture_configs import CaptureTemplate, Parameters, CaptureConfig
|
10
|
-
from .plugins._receiver_names import ReceiverName
|
11
|
-
from ._spec_names import SpecName
|
12
|
-
|
13
|
-
|
14
|
-
class BaseReceiver(ABC):
|
15
|
-
"""Abstract base class for software-defined radio receivers."""
|
16
|
-
|
17
|
-
def __init__(self, name: ReceiverName, mode: Optional[str] = None) -> None:
|
18
|
-
"""Initialise an instance of `BaseReceiver`.
|
19
|
-
|
20
|
-
:param name: The name of the receiver.
|
21
|
-
:param mode: The initial active operating mode. Defaults to None.
|
22
|
-
"""
|
23
|
-
self._name = name
|
24
|
-
|
25
|
-
self._specs: dict[SpecName, float | int | list[float | int]] = {}
|
26
|
-
self._add_specs()
|
27
|
-
|
28
|
-
self._capture_methods: dict[str, Callable[[str, Parameters], None]] = {}
|
29
|
-
self._add_capture_methods()
|
30
|
-
|
31
|
-
self._capture_templates: dict[str, CaptureTemplate] = {}
|
32
|
-
self._add_capture_templates()
|
33
|
-
|
34
|
-
self._pvalidators: dict[str, Callable[[Parameters], None]] = {}
|
35
|
-
self._add_pvalidators()
|
36
|
-
|
37
|
-
self._mode: Optional[str] = mode
|
38
|
-
|
39
|
-
@abstractmethod
|
40
|
-
def _add_specs(self) -> None:
|
41
|
-
"""Subclasses must use `add_spec` to add hardware specifications."""
|
42
|
-
|
43
|
-
@abstractmethod
|
44
|
-
def _add_capture_methods(self) -> None:
|
45
|
-
"""Subclasses must use `add_capture_method` to specify which method is called to capture
|
46
|
-
data, for each operating mode."""
|
47
|
-
|
48
|
-
@abstractmethod
|
49
|
-
def _add_capture_templates(self) -> None:
|
50
|
-
"""Subclasses must use `add_capture_template` to define a `CaptureTemplate` for each operating mode."""
|
51
|
-
|
52
|
-
@abstractmethod
|
53
|
-
def _add_pvalidators(self) -> None:
|
54
|
-
"""Subclasses must use `add_pvalidator` to add a parameter validation function (pvalidator)
|
55
|
-
for each operating mode."""
|
56
|
-
|
57
|
-
@property
|
58
|
-
def name(self) -> ReceiverName:
|
59
|
-
"""The name of the receiver."""
|
60
|
-
return self._name
|
61
|
-
|
62
|
-
@property
|
63
|
-
def capture_methods(self) -> dict[str, Callable[[str, Parameters], None]]:
|
64
|
-
"""For each operating mode, the method which is called to capture data."""
|
65
|
-
return self._capture_methods
|
66
|
-
|
67
|
-
@property
|
68
|
-
def capture_templates(self) -> dict[str, CaptureTemplate]:
|
69
|
-
"""For each operating mode, the corresponding `CaptureTemplate`."""
|
70
|
-
return self._capture_templates
|
71
|
-
|
72
|
-
@property
|
73
|
-
def pvalidators(self) -> dict[str, Callable[[Parameters], None]]:
|
74
|
-
"""For each operating mode, the corresponding parameter validation function (pvalidator)."""
|
75
|
-
return self._pvalidators
|
76
|
-
|
77
|
-
@property
|
78
|
-
def specs(self) -> dict[SpecName, float | int | list[float | int]]:
|
79
|
-
"""The hardware specifications."""
|
80
|
-
return self._specs
|
81
|
-
|
82
|
-
@property
|
83
|
-
def modes(self) -> list[str]:
|
84
|
-
"""The operating modes for the receiver.
|
85
|
-
|
86
|
-
:raises ValueError: If the modes are inconsistent between `capture_methods`,
|
87
|
-
`pvalidators` and `capture_templates`.
|
88
|
-
"""
|
89
|
-
capture_method_modes = list(self.capture_methods.keys())
|
90
|
-
pvalidator_modes = list(self.pvalidators.keys())
|
91
|
-
capture_template_modes = list(self.capture_templates.keys())
|
92
|
-
|
93
|
-
if not capture_method_modes == pvalidator_modes == capture_template_modes:
|
94
|
-
raise ValueError(f"Mode mismatch for the receiver {self.name}.")
|
95
|
-
return capture_method_modes
|
96
|
-
|
97
|
-
@property
|
98
|
-
def mode(self) -> Optional[str]:
|
99
|
-
"""The active operating mode for the receiver, if set."""
|
100
|
-
return self._mode
|
101
|
-
|
102
|
-
@mode.setter
|
103
|
-
def mode(self, value: str) -> None:
|
104
|
-
"""Set the active operating mode.
|
105
|
-
|
106
|
-
:param value: The new operating mode to activate.
|
107
|
-
:raises ModeNotFoundError: If the specified mode is not defined in `modes`.
|
108
|
-
"""
|
109
|
-
if value not in self.modes:
|
110
|
-
raise ModeNotFoundError(
|
111
|
-
f"{value} is not a defined mode for the receiver {self.name}. "
|
112
|
-
f"Expected one of {self.modes}"
|
113
|
-
)
|
114
|
-
self._mode = value
|
115
|
-
|
116
|
-
def _get_active_mode(self) -> str:
|
117
|
-
"""Get the current mode, raising an error if none is set.
|
118
|
-
|
119
|
-
:raises ValueError: If no mode is currently set
|
120
|
-
:return: The current mode
|
121
|
-
"""
|
122
|
-
if self._mode is None:
|
123
|
-
raise ValueError(
|
124
|
-
f"This operation requires an active mode for receiver `{self.name.value}`"
|
125
|
-
)
|
126
|
-
return self._mode
|
127
|
-
|
128
|
-
@property
|
129
|
-
def capture_method(self) -> Callable[[str, Parameters], None]:
|
130
|
-
"""Start capturing data under the active operating mode."""
|
131
|
-
return self.capture_methods[self._get_active_mode()]
|
132
|
-
|
133
|
-
@property
|
134
|
-
def pvalidator(self) -> Callable[[Parameters], None]:
|
135
|
-
"""The parameter validation function for the active operating mode."""
|
136
|
-
return self.pvalidators[self._get_active_mode()]
|
137
|
-
|
138
|
-
@property
|
139
|
-
def capture_template(self) -> CaptureTemplate:
|
140
|
-
"""The `CaptureTemplate` for the active operating mode."""
|
141
|
-
return self._capture_templates[self._get_active_mode()]
|
142
|
-
|
143
|
-
def add_capture_method(
|
144
|
-
self, mode: str, capture_method: Callable[[str, Parameters], None]
|
145
|
-
) -> None:
|
146
|
-
"""
|
147
|
-
Add a capture method for a specific operating mode.
|
148
|
-
|
149
|
-
:param mode: The operating mode.
|
150
|
-
:param capture_method: The function which captures data.
|
151
|
-
"""
|
152
|
-
self._capture_methods[mode] = capture_method
|
153
|
-
|
154
|
-
def add_pvalidator(
|
155
|
-
self, mode: str, pvalidator: Callable[[Parameters], None]
|
156
|
-
) -> None:
|
157
|
-
"""
|
158
|
-
Add a parameter validation function for a specific operating mode.
|
159
|
-
|
160
|
-
:param mode: The operating mode.
|
161
|
-
:param pvalidator: The validation function.
|
162
|
-
"""
|
163
|
-
self._pvalidators[mode] = pvalidator
|
164
|
-
|
165
|
-
def add_capture_template(
|
166
|
-
self, mode: str, capture_template: CaptureTemplate
|
167
|
-
) -> None:
|
168
|
-
"""
|
169
|
-
Add a `CaptureTemplate` for a specific operating mode.
|
170
|
-
|
171
|
-
:param mode: The operating mode.
|
172
|
-
:param capture_template: The capture template, defining parameter requirements.
|
173
|
-
"""
|
174
|
-
self._capture_templates[mode] = capture_template
|
175
|
-
|
176
|
-
def add_spec(self, name: SpecName, value: Any) -> None:
|
177
|
-
"""
|
178
|
-
Add a hardware specification.
|
179
|
-
|
180
|
-
:param name: The specification's name.
|
181
|
-
:param value: The specification's value.
|
182
|
-
"""
|
183
|
-
self.specs[name] = value
|
184
|
-
|
185
|
-
def get_spec(self, spec_name: SpecName) -> Any:
|
186
|
-
"""
|
187
|
-
Retrieve a hardware specification.
|
188
|
-
|
189
|
-
:param spec_name: The name of the specification.
|
190
|
-
:raises KeyError: If the specification is not found.
|
191
|
-
:return: The specification's value.
|
192
|
-
"""
|
193
|
-
if spec_name not in self.specs:
|
194
|
-
raise KeyError(
|
195
|
-
f"Spec not found with name '{spec_name}' "
|
196
|
-
f"for the receiver '{self.name}'"
|
197
|
-
)
|
198
|
-
return self.specs[spec_name]
|
199
|
-
|
200
|
-
def start_capture(self, tag: str) -> None:
|
201
|
-
"""Start capturing data in the active operating mode.
|
202
|
-
|
203
|
-
:param tag: The tag of the capture config to load.
|
204
|
-
:raises ValueError: If no mode is currently set
|
205
|
-
"""
|
206
|
-
active_mode = self._get_active_mode()
|
207
|
-
self.capture_methods[active_mode](tag, self.load_parameters(tag))
|
208
|
-
|
209
|
-
def save_parameters(
|
210
|
-
self, tag: str, parameters: Parameters, force: bool = False
|
211
|
-
) -> None:
|
212
|
-
"""Create a capture config according to the active operating mode and save the
|
213
|
-
input parameters.
|
214
|
-
|
215
|
-
:param tag: The tag identifying the capture config.
|
216
|
-
:param parameters: The parameters to save in the capture config.
|
217
|
-
:param force: If True, overwrites existing file if it exists.
|
218
|
-
:raises ValueError: If no mode is currently set
|
219
|
-
"""
|
220
|
-
active_mode = self._get_active_mode()
|
221
|
-
parameters = self.capture_templates[active_mode].apply_template(parameters)
|
222
|
-
self.pvalidators[active_mode](parameters)
|
223
|
-
|
224
|
-
capture_config = CaptureConfig(tag)
|
225
|
-
capture_config.save_parameters(self.name.value, active_mode, parameters, force)
|
226
|
-
|
227
|
-
def load_parameters(self, tag: str) -> Parameters:
|
228
|
-
"""Load a capture config, and return the parameters it stores.
|
229
|
-
|
230
|
-
:param tag: The tag identifying the capture config.
|
231
|
-
:raises ValueError: If no mode is currently set
|
232
|
-
:return: The validated parameters stored in the capture config.
|
233
|
-
"""
|
234
|
-
active_mode = self._get_active_mode()
|
235
|
-
capture_config = CaptureConfig(tag)
|
236
|
-
|
237
|
-
parameters = self.capture_templates[active_mode].apply_template(
|
238
|
-
capture_config.parameters
|
239
|
-
)
|
240
|
-
self.pvalidators[active_mode](parameters)
|
241
|
-
|
242
|
-
return parameters
|
@@ -1,225 +0,0 @@
|
|
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 dataclasses import dataclass
|
6
|
-
from typing import Callable, cast
|
7
|
-
|
8
|
-
from spectre_core.capture_configs import (
|
9
|
-
CaptureTemplate,
|
10
|
-
CaptureMode,
|
11
|
-
Parameters,
|
12
|
-
Bound,
|
13
|
-
PName,
|
14
|
-
get_base_capture_template,
|
15
|
-
make_base_capture_template,
|
16
|
-
get_base_ptemplate,
|
17
|
-
validate_window,
|
18
|
-
)
|
19
|
-
from .gr._test import CaptureMethod
|
20
|
-
from ._receiver_names import ReceiverName
|
21
|
-
from .._spec_names import SpecName
|
22
|
-
from .._base import BaseReceiver
|
23
|
-
from .._register import register_receiver
|
24
|
-
|
25
|
-
|
26
|
-
@dataclass(frozen=True)
|
27
|
-
class Mode:
|
28
|
-
"""An operating mode for the `Test` receiver."""
|
29
|
-
|
30
|
-
COSINE_SIGNAL_1 = "cosine_signal_1"
|
31
|
-
TAGGED_STAIRCASE = "tagged_staircase"
|
32
|
-
|
33
|
-
|
34
|
-
@register_receiver(ReceiverName.TEST)
|
35
|
-
class Test(BaseReceiver):
|
36
|
-
"""An entirely software-defined receiver, which generates synthetic signals."""
|
37
|
-
|
38
|
-
def _add_specs(self) -> None:
|
39
|
-
self.add_spec(SpecName.SAMPLE_RATE_LOWER_BOUND, 64000)
|
40
|
-
self.add_spec(SpecName.SAMPLE_RATE_UPPER_BOUND, 640000)
|
41
|
-
self.add_spec(SpecName.FREQUENCY_LOWER_BOUND, 16000)
|
42
|
-
self.add_spec(SpecName.FREQUENCY_UPPER_BOUND, 160000)
|
43
|
-
|
44
|
-
def _add_capture_methods(self) -> None:
|
45
|
-
self.add_capture_method(Mode.COSINE_SIGNAL_1, CaptureMethod.cosine_signal_1)
|
46
|
-
self.add_capture_method(Mode.TAGGED_STAIRCASE, CaptureMethod.tagged_staircase)
|
47
|
-
|
48
|
-
def _add_pvalidators(self) -> None:
|
49
|
-
self.add_pvalidator(
|
50
|
-
Mode.COSINE_SIGNAL_1, self.__get_pvalidator_cosine_signal_1()
|
51
|
-
)
|
52
|
-
self.add_pvalidator(
|
53
|
-
Mode.TAGGED_STAIRCASE, self.__get_pvalidator_tagged_staircase()
|
54
|
-
)
|
55
|
-
|
56
|
-
def _add_capture_templates(self) -> None:
|
57
|
-
self.add_capture_template(
|
58
|
-
Mode.COSINE_SIGNAL_1, self.__get_capture_template_cosine_signal_1()
|
59
|
-
)
|
60
|
-
self.add_capture_template(
|
61
|
-
Mode.TAGGED_STAIRCASE, self.__get_capture_template_tagged_staircase()
|
62
|
-
)
|
63
|
-
|
64
|
-
def __get_capture_template_cosine_signal_1(self) -> CaptureTemplate:
|
65
|
-
capture_template = get_base_capture_template(CaptureMode.FIXED_CENTER_FREQUENCY)
|
66
|
-
capture_template.add_ptemplate(get_base_ptemplate(PName.AMPLITUDE))
|
67
|
-
capture_template.add_ptemplate(get_base_ptemplate(PName.FREQUENCY))
|
68
|
-
|
69
|
-
capture_template.set_defaults(
|
70
|
-
(PName.BATCH_SIZE, 3.0),
|
71
|
-
(PName.CENTER_FREQUENCY, 16000),
|
72
|
-
(PName.AMPLITUDE, 2.0),
|
73
|
-
(PName.FREQUENCY, 32000),
|
74
|
-
(PName.SAMPLE_RATE, 128000),
|
75
|
-
(PName.WINDOW_HOP, 512),
|
76
|
-
(PName.WINDOW_SIZE, 512),
|
77
|
-
(PName.WINDOW_TYPE, "boxcar"),
|
78
|
-
)
|
79
|
-
|
80
|
-
capture_template.enforce_defaults(
|
81
|
-
PName.TIME_RESOLUTION,
|
82
|
-
PName.TIME_RANGE,
|
83
|
-
PName.FREQUENCY_RESOLUTION,
|
84
|
-
PName.WINDOW_TYPE,
|
85
|
-
)
|
86
|
-
|
87
|
-
capture_template.add_pconstraint(
|
88
|
-
PName.SAMPLE_RATE,
|
89
|
-
[
|
90
|
-
Bound(
|
91
|
-
lower_bound=self.get_spec(SpecName.SAMPLE_RATE_LOWER_BOUND),
|
92
|
-
upper_bound=self.get_spec(SpecName.SAMPLE_RATE_UPPER_BOUND),
|
93
|
-
)
|
94
|
-
],
|
95
|
-
)
|
96
|
-
capture_template.add_pconstraint(
|
97
|
-
PName.FREQUENCY,
|
98
|
-
[
|
99
|
-
Bound(
|
100
|
-
lower_bound=self.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
|
101
|
-
upper_bound=self.get_spec(SpecName.FREQUENCY_UPPER_BOUND),
|
102
|
-
)
|
103
|
-
],
|
104
|
-
)
|
105
|
-
return capture_template
|
106
|
-
|
107
|
-
def __get_capture_template_tagged_staircase(self) -> CaptureTemplate:
|
108
|
-
capture_template = make_base_capture_template(
|
109
|
-
PName.TIME_RESOLUTION,
|
110
|
-
PName.FREQUENCY_RESOLUTION,
|
111
|
-
PName.TIME_RANGE,
|
112
|
-
PName.SAMPLE_RATE,
|
113
|
-
PName.BATCH_SIZE,
|
114
|
-
PName.WINDOW_TYPE,
|
115
|
-
PName.WINDOW_HOP,
|
116
|
-
PName.WINDOW_SIZE,
|
117
|
-
PName.EVENT_HANDLER_KEY,
|
118
|
-
PName.BATCH_KEY,
|
119
|
-
PName.WATCH_EXTENSION,
|
120
|
-
PName.MIN_SAMPLES_PER_STEP,
|
121
|
-
PName.MAX_SAMPLES_PER_STEP,
|
122
|
-
PName.FREQUENCY_STEP,
|
123
|
-
PName.STEP_INCREMENT,
|
124
|
-
PName.OBS_ALT,
|
125
|
-
PName.OBS_LAT,
|
126
|
-
PName.OBS_LON,
|
127
|
-
PName.OBJECT,
|
128
|
-
PName.ORIGIN,
|
129
|
-
PName.TELESCOPE,
|
130
|
-
PName.INSTRUMENT,
|
131
|
-
)
|
132
|
-
|
133
|
-
capture_template.set_defaults(
|
134
|
-
(PName.BATCH_SIZE, 3.0),
|
135
|
-
(PName.FREQUENCY_STEP, 128000),
|
136
|
-
(PName.MAX_SAMPLES_PER_STEP, 5000),
|
137
|
-
(PName.MIN_SAMPLES_PER_STEP, 4000),
|
138
|
-
(PName.SAMPLE_RATE, 128000),
|
139
|
-
(PName.STEP_INCREMENT, 200),
|
140
|
-
(PName.WINDOW_HOP, 512),
|
141
|
-
(PName.WINDOW_SIZE, 512),
|
142
|
-
(PName.WINDOW_TYPE, "boxcar"),
|
143
|
-
(PName.EVENT_HANDLER_KEY, "swept_center_frequency"),
|
144
|
-
(PName.BATCH_KEY, "iq_stream"),
|
145
|
-
(PName.WATCH_EXTENSION, "bin"),
|
146
|
-
)
|
147
|
-
|
148
|
-
capture_template.enforce_defaults(
|
149
|
-
PName.TIME_RESOLUTION,
|
150
|
-
PName.TIME_RANGE,
|
151
|
-
PName.FREQUENCY_RESOLUTION,
|
152
|
-
PName.WINDOW_TYPE,
|
153
|
-
PName.EVENT_HANDLER_KEY,
|
154
|
-
PName.BATCH_KEY,
|
155
|
-
PName.WATCH_EXTENSION,
|
156
|
-
)
|
157
|
-
|
158
|
-
return capture_template
|
159
|
-
|
160
|
-
def __get_pvalidator_cosine_signal_1(self) -> Callable[[Parameters], None]:
|
161
|
-
def pvalidator(parameters: Parameters) -> None:
|
162
|
-
validate_window(parameters)
|
163
|
-
|
164
|
-
sample_rate = cast(int, parameters.get_parameter_value(PName.SAMPLE_RATE))
|
165
|
-
window_size = cast(int, parameters.get_parameter_value(PName.WINDOW_SIZE))
|
166
|
-
frequency = cast(float, parameters.get_parameter_value(PName.FREQUENCY))
|
167
|
-
|
168
|
-
# check that the sample rate is an integer multiple of the underlying signal frequency
|
169
|
-
if sample_rate % frequency != 0:
|
170
|
-
raise ValueError(
|
171
|
-
"The sampling rate must be some integer multiple of frequency"
|
172
|
-
)
|
173
|
-
|
174
|
-
a = sample_rate / frequency
|
175
|
-
if a < 2:
|
176
|
-
raise ValueError(
|
177
|
-
(
|
178
|
-
f"The ratio of sampling rate over frequency must be greater than two. "
|
179
|
-
f"Got {a}"
|
180
|
-
)
|
181
|
-
)
|
182
|
-
|
183
|
-
# analytical requirement
|
184
|
-
# if p is the number of sampled cycles, we can find that p = window_size / a
|
185
|
-
# the number of sampled cycles must be a positive natural number.
|
186
|
-
p = window_size / a
|
187
|
-
if window_size % a != 0:
|
188
|
-
raise ValueError(
|
189
|
-
(
|
190
|
-
f"The number of sampled cycles must be a positive natural number. "
|
191
|
-
f"Computed that p={p}"
|
192
|
-
)
|
193
|
-
)
|
194
|
-
|
195
|
-
return pvalidator
|
196
|
-
|
197
|
-
def __get_pvalidator_tagged_staircase(self) -> Callable[[Parameters], None]:
|
198
|
-
def pvalidator(parameters: Parameters) -> None:
|
199
|
-
validate_window(parameters)
|
200
|
-
|
201
|
-
freq_step = cast(
|
202
|
-
float, parameters.get_parameter_value(PName.FREQUENCY_STEP)
|
203
|
-
)
|
204
|
-
sample_rate = cast(int, parameters.get_parameter_value(PName.SAMPLE_RATE))
|
205
|
-
min_samples_per_step = cast(
|
206
|
-
int, parameters.get_parameter_value(PName.MIN_SAMPLES_PER_STEP)
|
207
|
-
)
|
208
|
-
max_samples_per_step = cast(
|
209
|
-
int, parameters.get_parameter_value(PName.MAX_SAMPLES_PER_STEP)
|
210
|
-
)
|
211
|
-
|
212
|
-
if freq_step != sample_rate:
|
213
|
-
raise ValueError(
|
214
|
-
f"The frequency step must be equal to the sampling rate"
|
215
|
-
)
|
216
|
-
|
217
|
-
if min_samples_per_step > max_samples_per_step:
|
218
|
-
raise ValueError(
|
219
|
-
(
|
220
|
-
f"Minimum samples per step cannot be greater than the maximum samples per step. "
|
221
|
-
f"Got {min_samples_per_step}, which is greater than {max_samples_per_step}"
|
222
|
-
)
|
223
|
-
)
|
224
|
-
|
225
|
-
return pvalidator
|