spectre-core 0.0.12__py3-none-any.whl → 0.0.14__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/_file_io/__init__.py +1 -3
- spectre_core/_file_io/file_handlers.py +163 -58
- spectre_core/batches/__init__.py +10 -11
- spectre_core/batches/_base.py +170 -78
- spectre_core/batches/_batches.py +149 -99
- spectre_core/batches/_factory.py +56 -14
- spectre_core/batches/_register.py +23 -8
- spectre_core/batches/plugins/_batch_keys.py +16 -0
- spectre_core/batches/plugins/_callisto.py +183 -0
- spectre_core/batches/plugins/_iq_stream.py +354 -0
- spectre_core/capture_configs/__init__.py +17 -13
- spectre_core/capture_configs/_capture_config.py +93 -34
- spectre_core/capture_configs/_capture_modes.py +22 -0
- spectre_core/capture_configs/_capture_templates.py +207 -122
- spectre_core/capture_configs/_parameters.py +115 -42
- spectre_core/capture_configs/_pconstraints.py +86 -35
- spectre_core/capture_configs/_pnames.py +49 -0
- spectre_core/capture_configs/_ptemplates.py +389 -346
- spectre_core/capture_configs/_pvalidators.py +117 -73
- spectre_core/config/__init__.py +6 -8
- spectre_core/config/_paths.py +65 -25
- spectre_core/config/_time_formats.py +15 -10
- spectre_core/exceptions.py +2 -4
- spectre_core/jobs/__init__.py +14 -0
- spectre_core/jobs/_jobs.py +111 -0
- spectre_core/jobs/_workers.py +171 -0
- spectre_core/logs/__init__.py +17 -0
- spectre_core/logs/_configure.py +67 -0
- spectre_core/logs/_decorators.py +33 -0
- spectre_core/logs/_logs.py +228 -0
- spectre_core/logs/_process_types.py +14 -0
- spectre_core/plotting/__init__.py +4 -2
- spectre_core/plotting/_base.py +204 -102
- spectre_core/plotting/_format.py +17 -4
- spectre_core/plotting/_panel_names.py +18 -0
- spectre_core/plotting/_panel_stack.py +167 -53
- spectre_core/plotting/_panels.py +341 -141
- spectre_core/post_processing/__init__.py +8 -6
- spectre_core/post_processing/_base.py +70 -44
- spectre_core/post_processing/_factory.py +42 -12
- spectre_core/post_processing/_post_processor.py +24 -26
- spectre_core/post_processing/_register.py +22 -6
- spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
- spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
- spectre_core/py.typed +0 -0
- spectre_core/receivers/__init__.py +10 -7
- spectre_core/receivers/_base.py +220 -69
- spectre_core/receivers/_factory.py +53 -7
- spectre_core/receivers/_register.py +30 -9
- spectre_core/receivers/_spec_names.py +26 -15
- spectre_core/receivers/plugins/__init__.py +0 -0
- spectre_core/receivers/plugins/_receiver_names.py +16 -0
- spectre_core/receivers/plugins/_rsp1a.py +59 -0
- spectre_core/receivers/plugins/_rspduo.py +67 -0
- spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
- spectre_core/receivers/plugins/_test.py +218 -0
- spectre_core/receivers/plugins/gr/_base.py +80 -0
- spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
- spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
- spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
- spectre_core/spectrograms/__init__.py +5 -3
- spectre_core/spectrograms/_analytical.py +121 -66
- spectre_core/spectrograms/_array_operations.py +103 -36
- spectre_core/spectrograms/_spectrogram.py +380 -207
- spectre_core/spectrograms/_transform.py +197 -169
- spectre_core/wgetting/__init__.py +4 -2
- spectre_core/wgetting/_callisto.py +173 -118
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/METADATA +14 -7
- spectre_core-0.0.14.dist-info/RECORD +75 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/WHEEL +1 -1
- spectre_core/batches/library/_callisto.py +0 -96
- spectre_core/batches/library/_fixed_center_frequency.py +0 -133
- spectre_core/batches/library/_swept_center_frequency.py +0 -105
- spectre_core/logging/__init__.py +0 -11
- spectre_core/logging/_configure.py +0 -35
- spectre_core/logging/_decorators.py +0 -19
- spectre_core/logging/_log_handlers.py +0 -176
- spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
- spectre_core/receivers/gr/_base.py +0 -33
- spectre_core/receivers/library/_rsp1a.py +0 -61
- spectre_core/receivers/library/_rspduo.py +0 -69
- spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
- spectre_core/receivers/library/_test.py +0 -221
- spectre_core-0.0.12.dist-info/RECORD +0 -64
- /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/top_level.txt +0 -0
spectre_core/receivers/_base.py
CHANGED
@@ -3,175 +3,326 @@
|
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
5
|
from abc import ABC, abstractmethod
|
6
|
-
from typing import Callable, Optional
|
7
|
-
from numbers import Number
|
6
|
+
from typing import Callable, Optional, Literal, overload
|
8
7
|
|
9
8
|
from spectre_core.exceptions import ModeNotFoundError
|
10
|
-
from ._spec_names import SpecNames
|
11
9
|
from spectre_core.capture_configs import (
|
12
|
-
CaptureTemplate,
|
13
|
-
get_base_capture_template, get_base_ptemplate, OneOf, CaptureConfig
|
10
|
+
CaptureTemplate, Parameters, CaptureConfig
|
14
11
|
)
|
12
|
+
from .plugins._receiver_names import ReceiverName
|
13
|
+
from ._spec_names import SpecName
|
15
14
|
|
16
15
|
class BaseReceiver(ABC):
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
"""Abstract base class for software-defined radio receivers."""
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
name: ReceiverName,
|
20
|
+
mode: Optional[str] = None
|
21
|
+
) -> None:
|
22
|
+
"""Initialise an instance of `BaseReceiver`.
|
23
|
+
|
24
|
+
:param name: The name of the receiver.
|
25
|
+
:param mode: The initial active operating mode. Defaults to None.
|
26
|
+
"""
|
20
27
|
self._name = name
|
21
28
|
|
22
|
-
self._specs: dict[
|
29
|
+
self._specs: dict[SpecName, float|int|list[float|int]] = {}
|
23
30
|
self._add_specs()
|
24
31
|
|
25
|
-
self._capture_methods: dict[str, Callable] = {}
|
32
|
+
self._capture_methods: dict[str, Callable[[str, Parameters], None]] = {}
|
26
33
|
self._add_capture_methods()
|
27
|
-
|
28
|
-
self._pvalidators: dict[str, Callable] = {}
|
29
|
-
self._add_pvalidators()
|
30
|
-
|
34
|
+
|
31
35
|
self._capture_templates: dict[str, CaptureTemplate] = {}
|
32
36
|
self._add_capture_templates()
|
37
|
+
|
38
|
+
self._pvalidators: dict[str, Callable[[Parameters], None]] = {}
|
39
|
+
self._add_pvalidators()
|
33
40
|
|
34
|
-
self.
|
41
|
+
self._mode = None
|
42
|
+
if mode is not None:
|
43
|
+
self.mode = mode
|
35
44
|
|
36
45
|
|
37
46
|
@abstractmethod
|
38
|
-
def _add_specs(
|
39
|
-
|
47
|
+
def _add_specs(
|
48
|
+
self
|
49
|
+
) -> None:
|
50
|
+
"""Subclasses must use `add_spec` to add hardware specifications."""
|
40
51
|
|
41
52
|
|
42
53
|
@abstractmethod
|
43
|
-
def _add_capture_methods(
|
44
|
-
|
45
|
-
|
54
|
+
def _add_capture_methods(
|
55
|
+
self
|
56
|
+
) -> None:
|
57
|
+
"""Subclasses must use `add_capture_method` to specify which method is called to capture
|
58
|
+
data, for each operating mode."""
|
59
|
+
|
46
60
|
|
47
61
|
@abstractmethod
|
48
|
-
def
|
49
|
-
|
62
|
+
def _add_capture_templates(
|
63
|
+
self
|
64
|
+
) -> None:
|
65
|
+
"""Subclasses must use `add_capture_template` to define a `CaptureTemplate` for each operating mode."""
|
50
66
|
|
51
67
|
|
52
68
|
@abstractmethod
|
53
|
-
def
|
54
|
-
|
69
|
+
def _add_pvalidators(
|
70
|
+
self
|
71
|
+
) -> None:
|
72
|
+
"""Subclasses must use `add_pvalidator` to add a parameter validation function (pvalidator)
|
73
|
+
for each operating mode."""
|
55
74
|
|
56
75
|
|
57
76
|
@property
|
58
|
-
def name(
|
77
|
+
def name(
|
78
|
+
self
|
79
|
+
) -> ReceiverName:
|
80
|
+
"""The name of the receiver."""
|
59
81
|
return self._name
|
60
82
|
|
61
83
|
|
62
84
|
@property
|
63
|
-
def capture_methods(
|
85
|
+
def capture_methods(
|
86
|
+
self
|
87
|
+
) -> dict[str, Callable[[str, Parameters], None]]:
|
88
|
+
"""For each operating mode, the method which is called to capture data."""
|
64
89
|
return self._capture_methods
|
65
|
-
|
66
|
-
|
90
|
+
|
91
|
+
|
67
92
|
@property
|
68
|
-
def
|
69
|
-
|
93
|
+
def capture_templates(
|
94
|
+
self
|
95
|
+
) -> dict[str, CaptureTemplate]:
|
96
|
+
"""For each operating mode, the corresponding `CaptureTemplate`."""
|
97
|
+
return self._capture_templates
|
70
98
|
|
71
99
|
|
72
100
|
@property
|
73
|
-
def
|
74
|
-
|
101
|
+
def pvalidators(
|
102
|
+
self
|
103
|
+
) -> dict[str, Callable[[Parameters], None]]:
|
104
|
+
"""For each operating mode, the corresponding parameter validation function (pvalidator)."""
|
105
|
+
return self._pvalidators
|
75
106
|
|
76
107
|
|
77
108
|
@property
|
78
|
-
def specs(
|
109
|
+
def specs(
|
110
|
+
self
|
111
|
+
) -> dict[SpecName, float|int|list[float|int]]:
|
112
|
+
"""The hardware specifications."""
|
79
113
|
return self._specs
|
80
114
|
|
81
115
|
|
82
116
|
@property
|
83
|
-
def modes(
|
117
|
+
def modes(
|
118
|
+
self
|
119
|
+
) -> list[str]:
|
120
|
+
"""The operating modes for the receiver.
|
121
|
+
|
122
|
+
:raises ValueError: If the modes are inconsistent between `capture_methods`,
|
123
|
+
`pvalidators` and `capture_templates`.
|
124
|
+
"""
|
84
125
|
capture_method_modes = list(self.capture_methods.keys())
|
85
126
|
pvalidator_modes = list(self.pvalidators.keys())
|
86
127
|
capture_template_modes = list(self.capture_templates.keys())
|
87
|
-
|
88
|
-
if capture_method_modes == pvalidator_modes == capture_template_modes:
|
89
|
-
return capture_method_modes
|
90
|
-
else:
|
128
|
+
|
129
|
+
if not capture_method_modes == pvalidator_modes == capture_template_modes:
|
91
130
|
raise ValueError(f"Mode mismatch for the receiver {self.name}.")
|
131
|
+
return capture_method_modes
|
92
132
|
|
93
133
|
|
94
134
|
@property
|
95
|
-
def mode(
|
135
|
+
def mode(
|
136
|
+
self
|
137
|
+
) -> str:
|
138
|
+
"""The active operating mode for the receiver."""
|
139
|
+
if self._mode is None:
|
140
|
+
raise ValueError(f"The operating mode for the receiver `{self.name.value}` is not set.")
|
96
141
|
return self._mode
|
97
|
-
|
142
|
+
|
98
143
|
|
99
144
|
@mode.setter
|
100
|
-
def mode(
|
101
|
-
|
145
|
+
def mode(
|
146
|
+
self,
|
147
|
+
value: str,
|
148
|
+
) -> None:
|
149
|
+
"""Set the active operating mode.
|
150
|
+
|
151
|
+
:param value: The new operating mode to activate.
|
152
|
+
:raises ModeNotFoundError: If the specified mode is not defined in `modes`.
|
153
|
+
"""
|
154
|
+
if (value not in self.modes):
|
102
155
|
raise ModeNotFoundError((f"{value} is not a defined mode for the receiver {self.name}. "
|
103
156
|
f"Expected one of {self.modes}"))
|
104
157
|
self._mode = value
|
105
158
|
|
106
159
|
|
107
160
|
@property
|
108
|
-
def capture_method(
|
161
|
+
def capture_method(
|
162
|
+
self
|
163
|
+
) -> Callable[[str, Parameters], None]:
|
164
|
+
"""Start capturing data under the active operating mode."""
|
109
165
|
return self.capture_methods[self.mode]
|
110
166
|
|
111
167
|
|
112
168
|
@property
|
113
|
-
def pvalidator(
|
169
|
+
def pvalidator(
|
170
|
+
self
|
171
|
+
) -> Callable[[Parameters], None]:
|
172
|
+
"""The parameter validation function for the active operating mode."""
|
114
173
|
return self.pvalidators[self.mode]
|
115
174
|
|
116
175
|
|
117
176
|
@property
|
118
|
-
def capture_template(
|
177
|
+
def capture_template(
|
178
|
+
self
|
179
|
+
) -> CaptureTemplate:
|
180
|
+
"""The `CaptureTemplate` for the active operating mode."""
|
119
181
|
return self._capture_templates[self.mode]
|
120
182
|
|
121
183
|
|
122
|
-
def add_capture_method(
|
123
|
-
|
124
|
-
|
184
|
+
def add_capture_method(
|
185
|
+
self,
|
186
|
+
mode: str,
|
187
|
+
capture_method: Callable[[str, Parameters], None]
|
188
|
+
) -> None:
|
189
|
+
"""
|
190
|
+
Add a capture method for a specific operating mode.
|
191
|
+
|
192
|
+
:param mode: The operating mode.
|
193
|
+
:param capture_method: The function which captures data.
|
194
|
+
"""
|
125
195
|
self._capture_methods[mode] = capture_method
|
126
196
|
|
127
197
|
|
128
|
-
def add_pvalidator(
|
129
|
-
|
130
|
-
|
198
|
+
def add_pvalidator(
|
199
|
+
self,
|
200
|
+
mode: str,
|
201
|
+
pvalidator: Callable[[Parameters], None]
|
202
|
+
) -> None:
|
203
|
+
"""
|
204
|
+
Add a parameter validation function for a specific operating mode.
|
205
|
+
|
206
|
+
:param mode: The operating mode.
|
207
|
+
:param pvalidator: The validation function.
|
208
|
+
"""
|
131
209
|
self._pvalidators[mode] = pvalidator
|
132
210
|
|
133
211
|
|
134
|
-
def add_capture_template(
|
135
|
-
|
136
|
-
|
212
|
+
def add_capture_template(
|
213
|
+
self,
|
214
|
+
mode: str,
|
215
|
+
capture_template: CaptureTemplate
|
216
|
+
) -> None:
|
217
|
+
"""
|
218
|
+
Add a `CaptureTemplate` for a specific operating mode.
|
219
|
+
|
220
|
+
:param mode: The operating mode.
|
221
|
+
:param capture_template: The capture template, defining parameter requirements.
|
222
|
+
"""
|
137
223
|
self._capture_templates[mode] = capture_template
|
138
224
|
|
139
225
|
|
140
|
-
def add_spec(
|
141
|
-
|
142
|
-
|
226
|
+
def add_spec(
|
227
|
+
self,
|
228
|
+
name: SpecName,
|
229
|
+
value: float|int|list[float|int]
|
230
|
+
) -> None:
|
231
|
+
"""
|
232
|
+
Add a hardware specification.
|
233
|
+
|
234
|
+
:param name: The specification's name.
|
235
|
+
:param value: The specification's value.
|
236
|
+
"""
|
143
237
|
self.specs[name] = value
|
144
238
|
|
145
239
|
|
146
|
-
|
147
|
-
|
240
|
+
# tell static type checkers the type of specification
|
241
|
+
@overload
|
242
|
+
def get_spec(self, spec_name: Literal[SpecName.API_RETUNING_LATENCY]) -> float: ...
|
243
|
+
@overload
|
244
|
+
def get_spec(self, spec_name: Literal[SpecName.FREQUENCY_LOWER_BOUND]) -> float: ...
|
245
|
+
@overload
|
246
|
+
def get_spec(self, spec_name: Literal[SpecName.FREQUENCY_UPPER_BOUND]) -> float: ...
|
247
|
+
@overload
|
248
|
+
def get_spec(self, spec_name: Literal[SpecName.SAMPLE_RATE_LOWER_BOUND]) -> int: ...
|
249
|
+
@overload
|
250
|
+
def get_spec(self, spec_name: Literal[SpecName.SAMPLE_RATE_UPPER_BOUND]) -> int: ...
|
251
|
+
@overload
|
252
|
+
def get_spec(self, spec_name: Literal[SpecName.BANDWIDTH_LOWER_BOUND]) -> float: ...
|
253
|
+
@overload
|
254
|
+
def get_spec(self, spec_name: Literal[SpecName.BANDWIDTH_UPPER_BOUND]) -> float: ...
|
255
|
+
@overload
|
256
|
+
def get_spec(self, spec_name: Literal[SpecName.RF_GAIN_UPPER_BOUND]) -> int: ...
|
257
|
+
@overload
|
258
|
+
def get_spec(self, spec_name: Literal[SpecName.IF_GAIN_UPPER_BOUND]) -> int: ...
|
259
|
+
@overload
|
260
|
+
def get_spec(self, spec_name: Literal[SpecName.BANDWIDTH_OPTIONS]) -> list[float]: ...
|
261
|
+
|
262
|
+
|
263
|
+
def get_spec(
|
264
|
+
self,
|
265
|
+
spec_name: SpecName
|
266
|
+
) -> float|int|list[float|int]:
|
267
|
+
"""
|
268
|
+
Retrieve a hardware specification.
|
269
|
+
|
270
|
+
:param spec_name: The name of the specification.
|
271
|
+
:raises KeyError: If the specification is not found.
|
272
|
+
:return: The specification's value.
|
273
|
+
"""
|
148
274
|
if spec_name not in self.specs:
|
149
275
|
raise KeyError(f"Spec not found with name '{spec_name}' "
|
150
276
|
f"for the receiver '{self.name}'")
|
151
277
|
return self.specs[spec_name]
|
152
278
|
|
153
279
|
|
154
|
-
def start_capture(
|
155
|
-
|
280
|
+
def start_capture(
|
281
|
+
self,
|
282
|
+
tag: str
|
283
|
+
) -> None:
|
284
|
+
"""Start capturing data in the active operating mode.
|
285
|
+
|
286
|
+
:param tag: The tag of the capture config to load.
|
287
|
+
"""
|
156
288
|
self.capture_method( tag, self.load_parameters(tag) )
|
157
289
|
|
158
290
|
|
159
|
-
def save_parameters(
|
160
|
-
|
161
|
-
|
162
|
-
|
291
|
+
def save_parameters(
|
292
|
+
self,
|
293
|
+
tag: str,
|
294
|
+
parameters: Parameters,
|
295
|
+
force: bool = False
|
296
|
+
) -> None:
|
297
|
+
"""Create a capture config according to the active operating mode and save the
|
298
|
+
input parameters.
|
299
|
+
|
300
|
+
The input parameters are validated before being written to file.
|
163
301
|
|
302
|
+
:param tag: The tag identifying the capture config.
|
303
|
+
:param parameters: The parameters to save in the capture config.
|
304
|
+
:param force: If True, overwrites the existing file if it already exists. Defaults to False.
|
305
|
+
"""
|
164
306
|
parameters = self.capture_template.apply_template(parameters)
|
165
307
|
self.pvalidator(parameters)
|
166
308
|
|
167
309
|
capture_config = CaptureConfig(tag)
|
168
|
-
capture_config.save_parameters(self.name,
|
310
|
+
capture_config.save_parameters(self.name.value,
|
169
311
|
self.mode,
|
170
312
|
parameters,
|
171
|
-
force
|
313
|
+
force)
|
314
|
+
|
315
|
+
def load_parameters(
|
316
|
+
self,
|
317
|
+
tag: str
|
318
|
+
) -> Parameters:
|
319
|
+
"""Load a capture config, and return the parameters it stores.
|
320
|
+
|
321
|
+
The parameters are validated before being returned.
|
172
322
|
|
173
|
-
|
174
|
-
|
323
|
+
:param tag: The tag identifying the capture config.
|
324
|
+
:return: The validated parameters stored in the capture config.
|
325
|
+
"""
|
175
326
|
capture_config = CaptureConfig(tag)
|
176
327
|
|
177
328
|
parameters = self.capture_template.apply_template(capture_config.parameters)
|
@@ -2,18 +2,64 @@
|
|
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
|
5
|
+
from typing import Optional, overload, Literal, TypeVar
|
6
6
|
|
7
7
|
from spectre_core.exceptions import ReceiverNotFoundError
|
8
8
|
from ._register import receivers
|
9
9
|
from ._base import BaseReceiver
|
10
|
+
from .plugins._receiver_names import ReceiverName
|
11
|
+
from .plugins._rsp1a import RSP1A
|
12
|
+
from .plugins._rspduo import RSPduo
|
13
|
+
from .plugins._test import Test
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
|
16
|
+
@overload
|
17
|
+
def get_receiver(
|
18
|
+
receiver_name: Literal[ReceiverName.RSP1A],
|
19
|
+
mode: Optional[str] = None
|
20
|
+
) -> RSP1A:
|
21
|
+
...
|
22
|
+
|
23
|
+
|
24
|
+
@overload
|
25
|
+
def get_receiver(
|
26
|
+
receiver_name: Literal[ReceiverName.RSPDUO],
|
27
|
+
mode: Optional[str] = None
|
28
|
+
) -> RSPduo:
|
29
|
+
...
|
30
|
+
|
31
|
+
|
32
|
+
@overload
|
33
|
+
def get_receiver(
|
34
|
+
receiver_name: Literal[ReceiverName.TEST],
|
35
|
+
mode: Optional[str] = None
|
36
|
+
) -> Test:
|
37
|
+
...
|
38
|
+
|
39
|
+
|
40
|
+
@overload
|
41
|
+
def get_receiver(
|
42
|
+
receiver_name: ReceiverName,
|
43
|
+
mode: Optional[str] = None
|
44
|
+
) -> BaseReceiver:
|
45
|
+
...
|
46
|
+
|
47
|
+
|
48
|
+
def get_receiver(
|
49
|
+
receiver_name: ReceiverName,
|
50
|
+
mode: Optional[str] = None
|
51
|
+
) -> BaseReceiver:
|
52
|
+
"""Get a registered receiver.
|
53
|
+
|
54
|
+
:param receiver_name: The name of the receiver.
|
55
|
+
:param mode: The initial operating mode for the receiver, defaults to None
|
56
|
+
:raises ReceiverNotFoundError: If the receiver name is not registered.
|
57
|
+
:return: An instance of the receiver class registered under `receiver_name`.
|
58
|
+
"""
|
59
|
+
receiver_cls = receivers.get(receiver_name)
|
60
|
+
if receiver_cls is None:
|
15
61
|
valid_receivers = list(receivers.keys())
|
16
62
|
raise ReceiverNotFoundError(f"No class found for the receiver: {receiver_name}. "
|
17
63
|
f"Please specify one of the following receivers {valid_receivers}")
|
18
|
-
return
|
19
|
-
|
64
|
+
return receiver_cls(receiver_name,
|
65
|
+
mode = mode)
|
@@ -2,20 +2,41 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
|
6
|
-
receivers = {}
|
5
|
+
from typing import TypeVar, Callable, Type
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
from .plugins._receiver_names import ReceiverName
|
8
|
+
from ._base import BaseReceiver
|
9
|
+
|
10
|
+
# map populated at runtime via the `register_receiver` decorator.
|
11
|
+
receivers: dict[ReceiverName, Type[BaseReceiver]] = {}
|
12
|
+
|
13
|
+
T = TypeVar('T', bound=BaseReceiver)
|
14
|
+
def register_receiver(
|
15
|
+
receiver_name: ReceiverName,
|
16
|
+
) -> Callable[[Type[T]], Type[T]]:
|
17
|
+
"""Decorator to register a fully implemented `BaseReceiver` subclass under a specified `receiver_name`.
|
18
|
+
|
19
|
+
:param receiver_name: The name of the receiver.
|
20
|
+
:raises ValueError: If the provided `receiver_name` is already registered.
|
21
|
+
:return: A decorator that registers the `BaseReceiver` subclass under the given `receiver_name`.
|
22
|
+
"""
|
23
|
+
def decorator(
|
24
|
+
cls: Type[T]
|
25
|
+
) -> Type[T]:
|
26
|
+
if receiver_name in receivers:
|
27
|
+
raise ValueError(f"The receiver '{receiver_name}' is already registered!")
|
12
28
|
receivers[receiver_name] = cls
|
13
29
|
return cls
|
14
30
|
return decorator
|
15
31
|
|
16
|
-
|
17
|
-
def
|
18
|
-
|
32
|
+
|
33
|
+
def get_registered_receivers(
|
34
|
+
) -> list[str]:
|
35
|
+
"""List all registered receivers.
|
36
|
+
|
37
|
+
:return: The string values of all registered `ReceiverName` enum keys.
|
38
|
+
"""
|
39
|
+
return [k.value for k in receivers.keys()]
|
19
40
|
|
20
41
|
|
21
42
|
|
@@ -2,19 +2,30 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
from
|
5
|
+
from enum import Enum
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
FREQUENCY_LOWER_BOUND
|
11
|
-
FREQUENCY_UPPER_BOUND
|
12
|
-
SAMPLE_RATE_LOWER_BOUND
|
13
|
-
SAMPLE_RATE_UPPER_BOUND
|
14
|
-
BANDWIDTH_LOWER_BOUND
|
15
|
-
BANDWIDTH_UPPER_BOUND
|
16
|
-
BANDWIDTH_OPTIONS
|
17
|
-
|
18
|
-
|
19
|
-
RF_GAIN_UPPER_BOUND
|
20
|
-
|
7
|
+
class SpecName(Enum):
|
8
|
+
"""A hardware specification name.
|
9
|
+
|
10
|
+
:ivar FREQUENCY_LOWER_BOUND: The lower bound for the center frequency, in Hz.
|
11
|
+
:ivar FREQUENCY_UPPER_BOUND: The upper bound for the center frequency, in Hz.
|
12
|
+
:ivar SAMPLE_RATE_LOWER_BOUND: The lower bound for the sampling rate, in Hz.
|
13
|
+
:ivar SAMPLE_RATE_UPPER_BOUND: The upper bound for the sampling rate, in Hz.
|
14
|
+
:ivar BANDWIDTH_LOWER_BOUND: The lower bound for the bandwidth, in Hz.
|
15
|
+
:ivar BANDWIDTH_UPPER_BOUND: The upper bound for the bandwidth, in Hz.
|
16
|
+
:ivar BANDWIDTH_OPTIONS: The permitted bandwidths for the receiver, in Hz.
|
17
|
+
:ivar IF_GAIN_UPPER_BOUND: The upper bound for the intermediate frequency gain, in dB.
|
18
|
+
Negative values indicate attenuation.
|
19
|
+
:ivar RF_GAIN_UPPER_BOUND: The upper bound for the radio frequency gain, in dB.
|
20
|
+
Negative values indicate attenuation.
|
21
|
+
"""
|
22
|
+
FREQUENCY_LOWER_BOUND = "frequency_lower_bound"
|
23
|
+
FREQUENCY_UPPER_BOUND = "frequency_upper_bound"
|
24
|
+
SAMPLE_RATE_LOWER_BOUND = "sample_rate_lower_bound"
|
25
|
+
SAMPLE_RATE_UPPER_BOUND = "sample_rate_upper_bound"
|
26
|
+
BANDWIDTH_LOWER_BOUND = "bandwidth_lower_bound"
|
27
|
+
BANDWIDTH_UPPER_BOUND = "bandwidth_upper_bound"
|
28
|
+
BANDWIDTH_OPTIONS = "bandwidth_options"
|
29
|
+
IF_GAIN_UPPER_BOUND = "if_gain_upper_bound"
|
30
|
+
RF_GAIN_UPPER_BOUND = "rf_gain_upper_bound"
|
31
|
+
API_RETUNING_LATENCY = "api_retuning_latency"
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
|
7
|
+
class ReceiverName(Enum):
|
8
|
+
"""A `spectre` supported receiver.
|
9
|
+
|
10
|
+
:ivar RSP1A: SDRPlay RSP1A
|
11
|
+
:ivar RSPDUO: SDRPlay RSPduo
|
12
|
+
:ivar TEST: `spectre` test receiver.
|
13
|
+
"""
|
14
|
+
RSP1A = "rsp1a"
|
15
|
+
RSPDUO = "rspduo"
|
16
|
+
TEST = "test"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 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
|
+
|
7
|
+
from ._receiver_names import ReceiverName
|
8
|
+
from .gr._rsp1a import CaptureMethod
|
9
|
+
from .._spec_names import SpecName
|
10
|
+
from ._sdrplay_receiver import SDRPlayReceiver
|
11
|
+
from .._register import register_receiver
|
12
|
+
|
13
|
+
@dataclass(frozen=True)
|
14
|
+
class Mode:
|
15
|
+
"""An operating mode for the `RSP1A` receiver."""
|
16
|
+
FIXED_CENTER_FREQUENCY = "fixed_center_frequency"
|
17
|
+
SWEPT_CENTER_FREQUENCY = "swept_center_frequency"
|
18
|
+
|
19
|
+
|
20
|
+
@register_receiver(ReceiverName.RSP1A)
|
21
|
+
class RSP1A(SDRPlayReceiver):
|
22
|
+
"""Receiver implementation for the SDRPlay RSP1A (https://www.sdrplay.com/rsp1a/)"""
|
23
|
+
def _add_specs(self) -> None:
|
24
|
+
self.add_spec( SpecName.SAMPLE_RATE_LOWER_BOUND, 200e3 )
|
25
|
+
self.add_spec( SpecName.SAMPLE_RATE_UPPER_BOUND, 10e6 )
|
26
|
+
self.add_spec( SpecName.FREQUENCY_LOWER_BOUND , 1e3 )
|
27
|
+
self.add_spec( SpecName.FREQUENCY_UPPER_BOUND , 2e9 )
|
28
|
+
self.add_spec( SpecName.IF_GAIN_UPPER_BOUND , -20 )
|
29
|
+
self.add_spec( SpecName.RF_GAIN_UPPER_BOUND , 0 )
|
30
|
+
self.add_spec( SpecName.API_RETUNING_LATENCY , 50 * 1e-3 )
|
31
|
+
self.add_spec( SpecName.BANDWIDTH_OPTIONS,
|
32
|
+
[200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000])
|
33
|
+
|
34
|
+
|
35
|
+
def _add_capture_methods(
|
36
|
+
self
|
37
|
+
) -> None:
|
38
|
+
self.add_capture_method(Mode.FIXED_CENTER_FREQUENCY,
|
39
|
+
CaptureMethod.fixed_center_frequency)
|
40
|
+
self.add_capture_method(Mode.SWEPT_CENTER_FREQUENCY,
|
41
|
+
CaptureMethod.swept_center_frequency)
|
42
|
+
|
43
|
+
|
44
|
+
def _add_capture_templates(
|
45
|
+
self
|
46
|
+
) -> None:
|
47
|
+
self.add_capture_template(Mode.FIXED_CENTER_FREQUENCY,
|
48
|
+
self._get_capture_template_fixed_center_frequency())
|
49
|
+
self.add_capture_template(Mode.SWEPT_CENTER_FREQUENCY,
|
50
|
+
self._get_capture_template_swept_center_frequency())
|
51
|
+
|
52
|
+
|
53
|
+
def _add_pvalidators(
|
54
|
+
self
|
55
|
+
) -> None:
|
56
|
+
self.add_pvalidator(Mode.FIXED_CENTER_FREQUENCY,
|
57
|
+
self._get_pvalidator_fixed_center_frequency())
|
58
|
+
self.add_pvalidator(Mode.SWEPT_CENTER_FREQUENCY,
|
59
|
+
self._get_pvalidator_swept_center_frequency())
|