spectre-core 0.0.12__py3-none-any.whl → 0.0.13__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.
Files changed (88) hide show
  1. spectre_core/_file_io/__init__.py +1 -3
  2. spectre_core/_file_io/file_handlers.py +163 -58
  3. spectre_core/batches/__init__.py +10 -11
  4. spectre_core/batches/_base.py +170 -78
  5. spectre_core/batches/_batches.py +149 -99
  6. spectre_core/batches/_factory.py +56 -14
  7. spectre_core/batches/_register.py +23 -8
  8. spectre_core/batches/plugins/_batch_keys.py +16 -0
  9. spectre_core/batches/plugins/_callisto.py +183 -0
  10. spectre_core/batches/plugins/_iq_stream.py +354 -0
  11. spectre_core/capture_configs/__init__.py +17 -13
  12. spectre_core/capture_configs/_capture_config.py +93 -34
  13. spectre_core/capture_configs/_capture_modes.py +22 -0
  14. spectre_core/capture_configs/_capture_templates.py +207 -122
  15. spectre_core/capture_configs/_parameters.py +115 -42
  16. spectre_core/capture_configs/_pconstraints.py +86 -35
  17. spectre_core/capture_configs/_pnames.py +49 -0
  18. spectre_core/capture_configs/_ptemplates.py +389 -346
  19. spectre_core/capture_configs/_pvalidators.py +117 -73
  20. spectre_core/config/__init__.py +6 -8
  21. spectre_core/config/_paths.py +65 -25
  22. spectre_core/config/_time_formats.py +15 -10
  23. spectre_core/exceptions.py +2 -4
  24. spectre_core/jobs/__init__.py +14 -0
  25. spectre_core/jobs/_jobs.py +111 -0
  26. spectre_core/jobs/_workers.py +171 -0
  27. spectre_core/logs/__init__.py +17 -0
  28. spectre_core/logs/_configure.py +67 -0
  29. spectre_core/logs/_decorators.py +33 -0
  30. spectre_core/logs/_logs.py +228 -0
  31. spectre_core/logs/_process_types.py +14 -0
  32. spectre_core/plotting/__init__.py +4 -2
  33. spectre_core/plotting/_base.py +204 -102
  34. spectre_core/plotting/_format.py +17 -4
  35. spectre_core/plotting/_panel_names.py +18 -0
  36. spectre_core/plotting/_panel_stack.py +167 -53
  37. spectre_core/plotting/_panels.py +341 -141
  38. spectre_core/post_processing/__init__.py +8 -6
  39. spectre_core/post_processing/_base.py +70 -44
  40. spectre_core/post_processing/_factory.py +42 -12
  41. spectre_core/post_processing/_post_processor.py +24 -26
  42. spectre_core/post_processing/_register.py +22 -6
  43. spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
  44. spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
  45. spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
  46. spectre_core/py.typed +0 -0
  47. spectre_core/receivers/__init__.py +10 -7
  48. spectre_core/receivers/_base.py +220 -69
  49. spectre_core/receivers/_factory.py +53 -7
  50. spectre_core/receivers/_register.py +30 -9
  51. spectre_core/receivers/_spec_names.py +26 -15
  52. spectre_core/receivers/plugins/__init__.py +0 -0
  53. spectre_core/receivers/plugins/_receiver_names.py +16 -0
  54. spectre_core/receivers/plugins/_rsp1a.py +59 -0
  55. spectre_core/receivers/plugins/_rspduo.py +67 -0
  56. spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
  57. spectre_core/receivers/plugins/_test.py +218 -0
  58. spectre_core/receivers/plugins/gr/_base.py +80 -0
  59. spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
  60. spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
  61. spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
  62. spectre_core/spectrograms/__init__.py +5 -3
  63. spectre_core/spectrograms/_analytical.py +121 -66
  64. spectre_core/spectrograms/_array_operations.py +103 -36
  65. spectre_core/spectrograms/_spectrogram.py +380 -207
  66. spectre_core/spectrograms/_transform.py +197 -169
  67. spectre_core/wgetting/__init__.py +4 -2
  68. spectre_core/wgetting/_callisto.py +173 -118
  69. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
  70. spectre_core-0.0.13.dist-info/RECORD +75 -0
  71. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
  72. spectre_core/batches/library/_callisto.py +0 -96
  73. spectre_core/batches/library/_fixed_center_frequency.py +0 -133
  74. spectre_core/batches/library/_swept_center_frequency.py +0 -105
  75. spectre_core/logging/__init__.py +0 -11
  76. spectre_core/logging/_configure.py +0 -35
  77. spectre_core/logging/_decorators.py +0 -19
  78. spectre_core/logging/_log_handlers.py +0 -176
  79. spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
  80. spectre_core/receivers/gr/_base.py +0 -33
  81. spectre_core/receivers/library/_rsp1a.py +0 -61
  82. spectre_core/receivers/library/_rspduo.py +0 -69
  83. spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  84. spectre_core/receivers/library/_test.py +0 -221
  85. spectre_core-0.0.12.dist-info/RECORD +0 -64
  86. /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
  87. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
  88. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -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, CaptureModes, Parameters, Bound, PValidators, PNames,
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
- def __init__(self,
18
- name: str,
19
- mode: Optional[str] = None):
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[str, Number | list[Number]] = {}
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.mode = mode
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(self) -> None:
39
- pass
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(self) -> None:
44
- pass
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 _add_pvalidators(self) -> None:
49
- pass
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 _add_capture_templates(self) -> None:
54
- pass
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(self) -> str:
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(self) -> dict[str, Callable]:
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 pvalidators(self) -> dict[str, Callable]:
69
- return self._pvalidators
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 capture_templates(self) -> dict[str, CaptureTemplate]:
74
- return self._capture_templates
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(self) -> dict[str, Number]:
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(self) -> list[str]:
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(self) -> str:
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(self, value: Optional[str]) -> None:
101
- if (value is not None) and value not in self.modes:
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(self) -> Callable:
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(self) -> Callable:
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(self) -> CaptureTemplate:
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(self,
123
- mode: str,
124
- capture_method: Callable) -> None:
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(self,
129
- mode: str,
130
- pvalidator: Callable) -> None:
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(self,
135
- mode: str,
136
- capture_template: CaptureTemplate) -> CaptureTemplate:
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(self,
141
- name: str,
142
- value: Number) -> None:
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
- def get_spec(self,
147
- spec_name: str) -> Number | list[Number]:
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(self,
155
- tag: str) -> None:
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(self,
160
- tag: str,
161
- parameters: Parameters,
162
- force: bool = False) -> None:
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=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
- def load_parameters(self,
174
- tag: str) -> Parameters:
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
- # used to fetch an instance of the receiver class
12
- def get_receiver(receiver_name: str, mode: Optional[str] = None) -> BaseReceiver:
13
- Receiver = receivers.get(receiver_name)
14
- if Receiver is None:
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 Receiver(receiver_name,
19
- mode = mode)
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
- # Global dictionaries to hold the mappings
6
- receivers = {}
5
+ from typing import TypeVar, Callable, Type
7
6
 
8
- # classes decorated with @register_receiver("<receiver_name>")
9
- # will be added to the global map of receivers with key "receiver_name"
10
- def register_receiver(receiver_name: str):
11
- def decorator(cls):
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
- # return a list of all receiver names
17
- def list_all_receiver_names() -> list[str]:
18
- return list(receivers.keys())
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 dataclasses import dataclass
5
+ from enum import Enum
6
6
 
7
- @dataclass(frozen=True)
8
- class SpecNames:
9
- """A centralised store of specification names"""
10
- FREQUENCY_LOWER_BOUND : str = "frequency_lower_bound"
11
- FREQUENCY_UPPER_BOUND : str = "frequency_upper_bound"
12
- SAMPLE_RATE_LOWER_BOUND : str = "sample_rate_lower_bound"
13
- SAMPLE_RATE_UPPER_BOUND : str = "sample_rate_upper_bound"
14
- BANDWIDTH_LOWER_BOUND : str = "bandwidth_lower_bound"
15
- BANDWIDTH_UPPER_BOUND : str = "bandwidth_upper_bound"
16
- BANDWIDTH_OPTIONS : str = "bandwidth_options"
17
- DEFINED_BANDWIDTHS : str = "defined_bandwidths"
18
- IF_GAIN_UPPER_BOUND : str = "if_gain_upper_bound"
19
- RF_GAIN_UPPER_BOUND : str = "rf_gain_upper_bound"
20
- API_RETUNING_LATENCY : str = "api_retuning_latency"
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())