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
@@ -0,0 +1,67 @@
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._rspduo import CaptureMethod
9
+ from .._spec_names import SpecName
10
+ from ._sdrplay_receiver import SDRPlayReceiver
11
+ from .._register import register_receiver
12
+
13
+
14
+ @dataclass
15
+ class Mode:
16
+ """An operating mode for the `RSPduo` receiver."""
17
+ TUNER_1_FIXED_CENTER_FREQUENCY = f"tuner_1_fixed_center_frequency"
18
+ TUNER_2_FIXED_CENTER_FREQUENCY = f"tuner_2_fixed_center_frequency"
19
+ TUNER_1_SWEPT_CENTER_FREQUENCY = f"tuner_1_swept_center_frequency"
20
+
21
+
22
+ @register_receiver(ReceiverName.RSPDUO)
23
+ class RSPduo(SDRPlayReceiver):
24
+ """Receiver implementation for the SDRPlay RSPduo (https://www.sdrplay.com/rspduo/)"""
25
+ def _add_specs(self) -> None:
26
+ self.add_spec( SpecName.SAMPLE_RATE_LOWER_BOUND, 200e3 )
27
+ self.add_spec( SpecName.SAMPLE_RATE_UPPER_BOUND, 10e6 )
28
+ self.add_spec( SpecName.FREQUENCY_LOWER_BOUND , 1e3 )
29
+ self.add_spec( SpecName.FREQUENCY_UPPER_BOUND , 2e9 )
30
+ self.add_spec( SpecName.IF_GAIN_UPPER_BOUND , -20 )
31
+ self.add_spec( SpecName.RF_GAIN_UPPER_BOUND , 0 )
32
+ self.add_spec( SpecName.API_RETUNING_LATENCY , 50 * 1e-3 )
33
+ self.add_spec( SpecName.BANDWIDTH_OPTIONS,
34
+ [200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000])
35
+
36
+
37
+ def _add_capture_methods(
38
+ self
39
+ ) -> None:
40
+ self.add_capture_method(Mode.TUNER_1_FIXED_CENTER_FREQUENCY,
41
+ CaptureMethod.tuner_1_fixed_center_frequency)
42
+ self.add_capture_method(Mode.TUNER_2_FIXED_CENTER_FREQUENCY,
43
+ CaptureMethod.tuner_2_fixed_center_frequency)
44
+ self.add_capture_method(Mode.TUNER_1_SWEPT_CENTER_FREQUENCY,
45
+ CaptureMethod.tuner_1_swept_center_frequency)
46
+
47
+
48
+ def _add_capture_templates(
49
+ self
50
+ ) -> None:
51
+ self.add_capture_template(Mode.TUNER_1_FIXED_CENTER_FREQUENCY,
52
+ self._get_capture_template_fixed_center_frequency())
53
+ self.add_capture_template(Mode.TUNER_2_FIXED_CENTER_FREQUENCY,
54
+ self._get_capture_template_fixed_center_frequency())
55
+ self.add_capture_template(Mode.TUNER_1_SWEPT_CENTER_FREQUENCY,
56
+ self._get_capture_template_swept_center_frequency())
57
+
58
+
59
+ def _add_pvalidators(
60
+ self
61
+ ) -> None:
62
+ self.add_pvalidator(Mode.TUNER_1_FIXED_CENTER_FREQUENCY,
63
+ self._get_pvalidator_fixed_center_frequency())
64
+ self.add_pvalidator(Mode.TUNER_2_FIXED_CENTER_FREQUENCY,
65
+ self._get_pvalidator_fixed_center_frequency())
66
+ self.add_pvalidator(Mode.TUNER_1_SWEPT_CENTER_FREQUENCY,
67
+ self._get_pvalidator_swept_center_frequency())
@@ -0,0 +1,190 @@
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 typing import Callable, overload
6
+
7
+ from spectre_core.capture_configs import (
8
+ CaptureTemplate, CaptureMode, Parameters, Bound, PName,
9
+ get_base_capture_template, get_base_ptemplate, OneOf,
10
+ validate_fixed_center_frequency, validate_swept_center_frequency
11
+ )
12
+ from .._base import BaseReceiver
13
+ from .._spec_names import SpecName
14
+
15
+ class SDRPlayReceiver(BaseReceiver):
16
+ """An abstract base class for SDRPlay receivers.
17
+
18
+ Includes ready-to-go pvalidators and capture templates which are shared by all subclasses.
19
+ Each subclasses must define the following hardware specifications:
20
+
21
+ .. code-block:: python
22
+ def _add_specs(self) -> None:
23
+ self.add_spec( SpecName.SAMPLE_RATE_LOWER_BOUND, <TBD> )
24
+ self.add_spec( SpecName.SAMPLE_RATE_UPPER_BOUND, <TBD> )
25
+ self.add_spec( SpecName.FREQUENCY_LOWER_BOUND , <TBD> )
26
+ self.add_spec( SpecName.FREQUENCY_UPPER_BOUND , <TBD> )
27
+ self.add_spec( SpecName.IF_GAIN_UPPER_BOUND , <TBD> )
28
+ self.add_spec( SpecName.RF_GAIN_UPPER_BOUND , <TBD> )
29
+ self.add_spec( SpecName.API_RETUNING_LATENCY , <TBD> )
30
+ self.add_spec( SpecName.BANDWIDTH_OPTIONS , <TBD> )
31
+ """
32
+ def _get_pvalidator_fixed_center_frequency(
33
+ self
34
+ ) -> Callable[[Parameters], None]:
35
+ def pvalidator(parameters: Parameters):
36
+ validate_fixed_center_frequency(parameters)
37
+ return pvalidator
38
+
39
+
40
+ def _get_pvalidator_swept_center_frequency(
41
+ self
42
+ ) -> Callable[[Parameters], None]:
43
+ def pvalidator(parameters: Parameters):
44
+ validate_swept_center_frequency(parameters,
45
+ self.get_spec(SpecName.API_RETUNING_LATENCY))
46
+ return pvalidator
47
+
48
+
49
+ def _get_capture_template_fixed_center_frequency(
50
+ self
51
+ ) -> CaptureTemplate:
52
+
53
+ capture_template = get_base_capture_template( CaptureMode.FIXED_CENTER_FREQUENCY )
54
+ capture_template.add_ptemplate( get_base_ptemplate(PName.BANDWIDTH) )
55
+ capture_template.add_ptemplate( get_base_ptemplate(PName.IF_GAIN) )
56
+ capture_template.add_ptemplate( get_base_ptemplate(PName.RF_GAIN) )
57
+
58
+ capture_template.set_defaults(
59
+ (PName.BATCH_SIZE, 3.0),
60
+ (PName.CENTER_FREQUENCY, 95800000),
61
+ (PName.SAMPLE_RATE, 600000),
62
+ (PName.BANDWIDTH, 600000),
63
+ (PName.WINDOW_HOP, 512),
64
+ (PName.WINDOW_SIZE, 1024),
65
+ (PName.WINDOW_TYPE, "blackman"),
66
+ (PName.RF_GAIN, -30),
67
+ (PName.IF_GAIN, -30)
68
+ )
69
+
70
+ capture_template.add_pconstraint(
71
+ PName.CENTER_FREQUENCY,
72
+ [
73
+ Bound(
74
+ lower_bound=self.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
75
+ upper_bound=self.get_spec(SpecName.FREQUENCY_UPPER_BOUND)
76
+ )
77
+ ]
78
+ )
79
+ capture_template.add_pconstraint(
80
+ PName.SAMPLE_RATE,
81
+ [
82
+ Bound(
83
+ lower_bound=self.get_spec(SpecName.SAMPLE_RATE_LOWER_BOUND),
84
+ upper_bound=self.get_spec(SpecName.SAMPLE_RATE_UPPER_BOUND)
85
+ )
86
+ ]
87
+ )
88
+ capture_template.add_pconstraint(
89
+ PName.BANDWIDTH,
90
+ [
91
+ OneOf(
92
+ self.get_spec( SpecName.BANDWIDTH_OPTIONS )
93
+ )
94
+ ]
95
+ )
96
+ capture_template.add_pconstraint(
97
+ PName.IF_GAIN,
98
+ [
99
+ Bound(
100
+ upper_bound=self.get_spec(SpecName.IF_GAIN_UPPER_BOUND)
101
+ )
102
+ ]
103
+ )
104
+ capture_template.add_pconstraint(
105
+ PName.RF_GAIN,
106
+ [
107
+ Bound(
108
+ upper_bound=self.get_spec(SpecName.RF_GAIN_UPPER_BOUND)
109
+ )
110
+ ]
111
+ )
112
+ return capture_template
113
+
114
+
115
+ def _get_capture_template_swept_center_frequency(
116
+ self
117
+ ) -> CaptureTemplate:
118
+
119
+ capture_template = get_base_capture_template( CaptureMode.SWEPT_CENTER_FREQUENCY )
120
+ capture_template.add_ptemplate( get_base_ptemplate(PName.BANDWIDTH) )
121
+ capture_template.add_ptemplate( get_base_ptemplate(PName.IF_GAIN) )
122
+ capture_template.add_ptemplate( get_base_ptemplate(PName.RF_GAIN) )
123
+
124
+ capture_template.set_defaults(
125
+ (PName.BATCH_SIZE, 4.0),
126
+ (PName.MIN_FREQUENCY, 95000000),
127
+ (PName.MAX_FREQUENCY, 100000000),
128
+ (PName.SAMPLES_PER_STEP, 80000),
129
+ (PName.FREQUENCY_STEP, 1536000),
130
+ (PName.SAMPLE_RATE, 1536000),
131
+ (PName.BANDWIDTH, 1536000),
132
+ (PName.WINDOW_HOP, 512),
133
+ (PName.WINDOW_SIZE, 1024),
134
+ (PName.WINDOW_TYPE, "blackman"),
135
+ (PName.RF_GAIN, -30),
136
+ (PName.IF_GAIN, -30)
137
+ )
138
+
139
+ capture_template.add_pconstraint(
140
+ PName.MIN_FREQUENCY,
141
+ [
142
+ Bound(
143
+ lower_bound=self.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
144
+ upper_bound=self.get_spec(SpecName.FREQUENCY_UPPER_BOUND)
145
+ )
146
+ ]
147
+ )
148
+ capture_template.add_pconstraint(
149
+ PName.MAX_FREQUENCY,
150
+ [
151
+ Bound(
152
+ lower_bound=self.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
153
+ upper_bound=self.get_spec(SpecName.FREQUENCY_UPPER_BOUND)
154
+ )
155
+ ]
156
+ )
157
+ capture_template.add_pconstraint(
158
+ PName.SAMPLE_RATE,
159
+ [
160
+ Bound(
161
+ lower_bound=self.get_spec(SpecName.SAMPLE_RATE_LOWER_BOUND),
162
+ upper_bound=self.get_spec(SpecName.SAMPLE_RATE_UPPER_BOUND)
163
+ )
164
+ ]
165
+ )
166
+ capture_template.add_pconstraint(
167
+ PName.BANDWIDTH,
168
+ [
169
+ OneOf(
170
+ self.get_spec( SpecName.BANDWIDTH_OPTIONS )
171
+ )
172
+ ]
173
+ )
174
+ capture_template.add_pconstraint(
175
+ PName.IF_GAIN,
176
+ [
177
+ Bound(
178
+ upper_bound=self.get_spec(SpecName.IF_GAIN_UPPER_BOUND)
179
+ )
180
+ ]
181
+ )
182
+ capture_template.add_pconstraint(
183
+ PName.RF_GAIN,
184
+ [
185
+ Bound(
186
+ upper_bound=self.get_spec(SpecName.RF_GAIN_UPPER_BOUND)
187
+ )
188
+ ]
189
+ )
190
+ return capture_template
@@ -0,0 +1,218 @@
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
+ from typing import Callable, cast
7
+
8
+ from spectre_core.capture_configs import (
9
+ CaptureTemplate, CaptureMode, Parameters, Bound, PName,
10
+ get_base_capture_template, make_base_capture_template, get_base_ptemplate,
11
+ validate_window
12
+ )
13
+ from .gr._test import CaptureMethod
14
+ from ._receiver_names import ReceiverName
15
+ from .._spec_names import SpecName
16
+ from .._base import BaseReceiver
17
+ from .._register import register_receiver
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class Mode:
22
+ """An operating mode for the `Test` receiver."""
23
+ COSINE_SIGNAL_1 = "cosine_signal_1"
24
+ TAGGED_STAIRCASE = "tagged_staircase"
25
+
26
+
27
+ @register_receiver(ReceiverName.TEST)
28
+ class Test(BaseReceiver):
29
+ """An entirely software-defined receiver, which generates synthetic signals."""
30
+ def _add_specs(
31
+ self
32
+ ) -> None:
33
+ self.add_spec( SpecName.SAMPLE_RATE_LOWER_BOUND, 64000 )
34
+ self.add_spec( SpecName.SAMPLE_RATE_UPPER_BOUND, 640000 )
35
+ self.add_spec( SpecName.FREQUENCY_LOWER_BOUND , 16000 )
36
+ self.add_spec( SpecName.FREQUENCY_UPPER_BOUND , 160000 )
37
+
38
+
39
+ def _add_capture_methods(
40
+ self
41
+ ) -> None:
42
+ self.add_capture_method(Mode.COSINE_SIGNAL_1 ,
43
+ CaptureMethod.cosine_signal_1 )
44
+ self.add_capture_method(Mode.TAGGED_STAIRCASE,
45
+ CaptureMethod.tagged_staircase)
46
+
47
+
48
+ def _add_pvalidators(
49
+ self
50
+ ) -> None:
51
+ self.add_pvalidator(Mode.COSINE_SIGNAL_1 ,
52
+ self.__get_pvalidator_cosine_signal_1() )
53
+ self.add_pvalidator(Mode.TAGGED_STAIRCASE,
54
+ self.__get_pvalidator_tagged_staircase() )
55
+
56
+
57
+ def _add_capture_templates(
58
+ self
59
+ ) -> None:
60
+ self.add_capture_template(Mode.COSINE_SIGNAL_1 ,
61
+ self.__get_capture_template_cosine_signal_1() )
62
+ self.add_capture_template(Mode.TAGGED_STAIRCASE,
63
+ self.__get_capture_template_tagged_staircase() )
64
+
65
+
66
+ def __get_capture_template_cosine_signal_1(
67
+ self
68
+ ) -> CaptureTemplate:
69
+ capture_template = get_base_capture_template( CaptureMode.FIXED_CENTER_FREQUENCY )
70
+ capture_template.add_ptemplate( get_base_ptemplate(PName.AMPLITUDE) )
71
+ capture_template.add_ptemplate( get_base_ptemplate(PName.FREQUENCY) )
72
+
73
+ capture_template.set_defaults(
74
+ (PName.BATCH_SIZE, 3.0),
75
+ (PName.CENTER_FREQUENCY, 16000),
76
+ (PName.AMPLITUDE, 2.0),
77
+ (PName.FREQUENCY, 32000),
78
+ (PName.SAMPLE_RATE, 128000),
79
+ (PName.WINDOW_HOP, 512),
80
+ (PName.WINDOW_SIZE, 512),
81
+ (PName.WINDOW_TYPE, "boxcar")
82
+ )
83
+
84
+ capture_template.enforce_defaults(
85
+ PName.TIME_RESOLUTION,
86
+ PName.TIME_RANGE,
87
+ PName.FREQUENCY_RESOLUTION,
88
+ PName.WINDOW_TYPE
89
+ )
90
+
91
+
92
+ capture_template.add_pconstraint(
93
+ PName.SAMPLE_RATE,
94
+ [
95
+ Bound(
96
+ lower_bound=self.get_spec(SpecName.SAMPLE_RATE_LOWER_BOUND),
97
+ upper_bound=self.get_spec(SpecName.SAMPLE_RATE_UPPER_BOUND)
98
+ )
99
+ ]
100
+ )
101
+ capture_template.add_pconstraint(
102
+ PName.FREQUENCY,
103
+ [
104
+ Bound(
105
+ lower_bound=self.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
106
+ upper_bound=self.get_spec(SpecName.FREQUENCY_UPPER_BOUND)
107
+ )
108
+ ]
109
+ )
110
+ return capture_template
111
+
112
+
113
+ def __get_capture_template_tagged_staircase(
114
+ self
115
+ ) -> CaptureTemplate:
116
+ capture_template = make_base_capture_template(
117
+ PName.TIME_RESOLUTION,
118
+ PName.FREQUENCY_RESOLUTION,
119
+ PName.TIME_RANGE,
120
+ PName.SAMPLE_RATE,
121
+ PName.BATCH_SIZE,
122
+ PName.WINDOW_TYPE,
123
+ PName.WINDOW_HOP,
124
+ PName.WINDOW_SIZE,
125
+ PName.EVENT_HANDLER_KEY,
126
+ PName.BATCH_KEY,
127
+ PName.WATCH_EXTENSION,
128
+ PName.MIN_SAMPLES_PER_STEP,
129
+ PName.MAX_SAMPLES_PER_STEP,
130
+ PName.FREQUENCY_STEP,
131
+ PName.STEP_INCREMENT,
132
+ PName.OBS_ALT,
133
+ PName.OBS_LAT,
134
+ PName.OBS_LON,
135
+ PName.OBJECT,
136
+ PName.ORIGIN,
137
+ PName.TELESCOPE,
138
+ PName.INSTRUMENT
139
+ )
140
+
141
+ capture_template.set_defaults(
142
+ (PName.BATCH_SIZE, 3.0),
143
+ (PName.FREQUENCY_STEP, 128000),
144
+ (PName.MAX_SAMPLES_PER_STEP, 5000),
145
+ (PName.MIN_SAMPLES_PER_STEP, 4000),
146
+ (PName.SAMPLE_RATE, 128000),
147
+ (PName.STEP_INCREMENT, 200),
148
+ (PName.WINDOW_HOP, 512),
149
+ (PName.WINDOW_SIZE, 512),
150
+ (PName.WINDOW_TYPE, "boxcar"),
151
+ (PName.EVENT_HANDLER_KEY, "swept_center_frequency"),
152
+ (PName.BATCH_KEY, "iq_stream"),
153
+ (PName.WATCH_EXTENSION, "bin")
154
+ )
155
+
156
+ capture_template.enforce_defaults(
157
+ PName.TIME_RESOLUTION,
158
+ PName.TIME_RANGE,
159
+ PName.FREQUENCY_RESOLUTION,
160
+ PName.WINDOW_TYPE,
161
+ PName.EVENT_HANDLER_KEY,
162
+ PName.BATCH_KEY,
163
+ PName.WATCH_EXTENSION
164
+ )
165
+
166
+ return capture_template
167
+
168
+
169
+ def __get_pvalidator_cosine_signal_1(
170
+ self
171
+ ) -> Callable[[Parameters], None]:
172
+ def pvalidator(parameters: Parameters) -> None:
173
+ validate_window(parameters)
174
+
175
+ sample_rate = cast(int, parameters.get_parameter_value(PName.SAMPLE_RATE))
176
+ window_size = cast(int, parameters.get_parameter_value(PName.WINDOW_SIZE))
177
+ frequency = cast(float, parameters.get_parameter_value(PName.FREQUENCY))
178
+
179
+ # check that the sample rate is an integer multiple of the underlying signal frequency
180
+ if sample_rate % frequency != 0:
181
+ raise ValueError("The sampling rate must be some integer multiple of frequency")
182
+
183
+ a = sample_rate/frequency
184
+ if a < 2:
185
+ raise ValueError((f"The ratio of sampling rate over frequency must be greater than two. "
186
+ f"Got {a}"))
187
+
188
+ # analytical requirement
189
+ # if p is the number of sampled cycles, we can find that p = window_size / a
190
+ # the number of sampled cycles must be a positive natural number.
191
+ p = window_size / a
192
+ if window_size % a != 0:
193
+ raise ValueError((f"The number of sampled cycles must be a positive natural number. "
194
+ f"Computed that p={p}"))
195
+ return pvalidator
196
+
197
+
198
+ def __get_pvalidator_tagged_staircase(
199
+ self
200
+ ) -> Callable[[Parameters], None]:
201
+ def pvalidator(parameters: Parameters) -> None:
202
+ validate_window(parameters)
203
+
204
+ freq_step = cast(float, parameters.get_parameter_value(PName.FREQUENCY_STEP))
205
+ sample_rate = cast(int, parameters.get_parameter_value(PName.SAMPLE_RATE))
206
+ min_samples_per_step = cast(int, parameters.get_parameter_value(PName.MIN_SAMPLES_PER_STEP))
207
+ max_samples_per_step = cast(int, parameters.get_parameter_value(PName.MAX_SAMPLES_PER_STEP))
208
+
209
+ if freq_step != sample_rate:
210
+ raise ValueError(f"The frequency step must be equal to the sampling rate")
211
+
212
+
213
+ if min_samples_per_step > max_samples_per_step:
214
+ raise ValueError((f"Minimum samples per step cannot be greater than the maximum samples per step. "
215
+ f"Got {min_samples_per_step}, which is greater than {max_samples_per_step}"))
216
+
217
+ return pvalidator
218
+
@@ -0,0 +1,80 @@
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
+ import sys
6
+ import signal
7
+ from typing import Type, TypeVar
8
+
9
+ from gnuradio import gr
10
+
11
+ from spectre_core.capture_configs import Parameters
12
+
13
+ class spectre_top_block(gr.top_block):
14
+ """A thin wrapper around GNU Radio's `gr.top_block` class.
15
+
16
+ Propagate through the input tag and parameters to `flowgraph` in the constructor,
17
+ to make the GNU Radio flow graphs easily configurable.
18
+ """
19
+ def flowgraph(
20
+ self,
21
+ tag: str,
22
+ parameters: Parameters
23
+ ) -> None:
24
+ """Define the flowgraph for the block.
25
+
26
+ This method uses inline imports to allow the `spectre_core.receivers`
27
+ module to work without requiring all Out-Of-Tree (OOT) modules to be installed.
28
+ Only import the necessary OOT modules when implementing this method.
29
+ """
30
+ raise NotImplementedError()
31
+
32
+
33
+ def __init__(
34
+ self,
35
+ tag: str,
36
+ parameters: Parameters
37
+ ) -> None:
38
+ """Create an instance of `spectre_top_block`.
39
+
40
+ :param tag: The capture config tag.
41
+ :param parameters: The capture config parameters
42
+ """
43
+ gr.top_block.__init__(self)
44
+ self.flowgraph(tag, parameters)
45
+
46
+
47
+ def capture(
48
+ tag: str,
49
+ parameters: Parameters,
50
+ top_block_cls: Type[spectre_top_block],
51
+ max_noutput_items: int = 10000000
52
+ ) -> None:
53
+ """
54
+ Run a GNU Radio flowgraph with the given number of output items.
55
+
56
+ Typically, this should be used with `partial` from `functools` to create
57
+ a capture method. For example,
58
+
59
+ .. code-block:: python
60
+ capture_method = partial(capture, top_block_cls=<your GNU Radio top block>)
61
+
62
+ :param tag: The capture config tag
63
+ :param parameters: The parameters stored in the capture config
64
+ :param top_block_cls: The subclass of `spectre_top_block`, defining the GNURadio flowgraph.
65
+ :param max_noutput_items: The number of max `noutput_items` in the flowgraph. This controls
66
+ the maximum number of output items any block will handle during a call to work. Defaults to 10000000
67
+ (which has been chosen as per GNU Radio source code).
68
+ """
69
+ tb = top_block_cls(tag,
70
+ parameters)
71
+
72
+ def sig_handler(sig=None, frame=None):
73
+ tb.stop()
74
+ tb.wait()
75
+ sys.exit(0)
76
+
77
+ signal.signal(signal.SIGINT, sig_handler)
78
+ signal.signal(signal.SIGTERM, sig_handler)
79
+
80
+ tb.run(max_noutput_items)
@@ -19,34 +19,30 @@
19
19
  from functools import partial
20
20
  from dataclasses import dataclass
21
21
 
22
- from gnuradio import gr
23
- from gnuradio import spectre
24
- from gnuradio import sdrplay3
25
-
26
- from spectre_core.capture_configs import Parameters, PNames
22
+ from spectre_core.capture_configs import Parameters, PName
27
23
  from spectre_core.config import get_batches_dir_path
28
- from ._base import capture
29
-
30
- class _fixed_center_frequency(gr.top_block):
31
- def __init__(self,
32
- tag: str,
33
- parameters: Parameters):
34
- gr.top_block.__init__(self, catch_exceptions=True)
35
-
36
- ##################################################
37
- # Unpack capture config
38
- ##################################################
39
- sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
40
- batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
41
- center_freq = parameters.get_parameter_value(PNames.CENTER_FREQUENCY)
42
- bandwidth = parameters.get_parameter_value(PNames.BANDWIDTH)
43
- if_gain = parameters.get_parameter_value(PNames.IF_GAIN)
44
- rf_gain = parameters.get_parameter_value(PNames.RF_GAIN)
24
+ from ._base import capture, spectre_top_block
25
+
26
+ class _fixed_center_frequency(spectre_top_block):
27
+ def flowgraph(
28
+ self,
29
+ tag: str,
30
+ parameters: Parameters
31
+ ) -> None:
32
+ # OOT Module inline imports
33
+ from gnuradio import spectre
34
+ from gnuradio import sdrplay3
35
+
36
+ # Unpack the capture config parameters
37
+ sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
38
+ batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
39
+ center_freq = parameters.get_parameter_value(PName.CENTER_FREQUENCY)
40
+ bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
41
+ if_gain = parameters.get_parameter_value(PName.IF_GAIN)
42
+ rf_gain = parameters.get_parameter_value(PName.RF_GAIN)
45
43
 
46
44
 
47
- ##################################################
48
45
  # Blocks
49
- ##################################################
50
46
  self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_batches_dir_path(),
51
47
  tag,
52
48
  batch_size,
@@ -75,36 +71,33 @@ class _fixed_center_frequency(gr.top_block):
75
71
  self.sdrplay3_rsp1a_0.set_sample_sequence_gaps_check(False)
76
72
  self.sdrplay3_rsp1a_0.set_show_gain_changes(False)
77
73
 
78
-
79
- ##################################################
80
74
  # Connections
81
- ##################################################
82
75
  self.connect((self.sdrplay3_rsp1a_0, 0), (self.spectre_batched_file_sink_0, 0))
83
76
 
84
77
 
85
- class _swept_center_frequency(gr.top_block):
86
- def __init__(self,
87
- tag: str,
88
- parameters: Parameters):
89
- gr.top_block.__init__(self, catch_exceptions=True)
78
+ class _swept_center_frequency(spectre_top_block):
79
+ def flowgraph(
80
+ self,
81
+ tag: str,
82
+ parameters: Parameters
83
+ ) -> None:
84
+ # OOT Module inline imports
85
+ from gnuradio import spectre
86
+ from gnuradio import sdrplay3
87
+
88
+ # Unpack the capture config parameters
89
+ sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
90
+ bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
91
+ min_frequency = parameters.get_parameter_value(PName.MIN_FREQUENCY)
92
+ max_frequency = parameters.get_parameter_value(PName.MAX_FREQUENCY)
93
+ frequency_step = parameters.get_parameter_value(PName.FREQUENCY_STEP)
94
+ samples_per_step = parameters.get_parameter_value(PName.SAMPLES_PER_STEP)
95
+ if_gain = parameters.get_parameter_value(PName.IF_GAIN)
96
+ rf_gain = parameters.get_parameter_value(PName.RF_GAIN)
97
+ batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
90
98
 
91
- ##################################################
92
- # Unpack capture config
93
- ##################################################
94
- sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
95
- bandwidth = parameters.get_parameter_value(PNames.BANDWIDTH)
96
- min_frequency = parameters.get_parameter_value(PNames.MIN_FREQUENCY)
97
- max_frequency = parameters.get_parameter_value(PNames.MAX_FREQUENCY)
98
- frequency_step = parameters.get_parameter_value(PNames.FREQUENCY_STEP)
99
- samples_per_step = parameters.get_parameter_value(PNames.SAMPLES_PER_STEP)
100
- if_gain = parameters.get_parameter_value(PNames.IF_GAIN)
101
- rf_gain = parameters.get_parameter_value(PNames.RF_GAIN)
102
- batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
103
99
 
104
-
105
- ##################################################
106
100
  # Blocks
107
- ##################################################
108
101
  self.spectre_sweep_driver_0 = spectre.sweep_driver(min_frequency,
109
102
  max_frequency,
110
103
  frequency_step,
@@ -143,16 +136,13 @@ class _swept_center_frequency(gr.top_block):
143
136
  self.sdrplay3_rsp1a_0.set_sample_sequence_gaps_check(False)
144
137
  self.sdrplay3_rsp1a_0.set_show_gain_changes(False)
145
138
 
146
-
147
- ##################################################
148
139
  # Connections
149
- ##################################################
150
140
  self.msg_connect((self.spectre_sweep_driver_0, 'freq'), (self.sdrplay3_rsp1a_0, 'freq'))
151
141
  self.connect((self.sdrplay3_rsp1a_0, 0), (self.spectre_batched_file_sink_0, 0))
152
142
  self.connect((self.sdrplay3_rsp1a_0, 0), (self.spectre_sweep_driver_0, 0))
153
143
 
154
144
 
155
145
  @dataclass(frozen=True)
156
- class CaptureMethods:
146
+ class CaptureMethod:
157
147
  fixed_center_frequency = partial(capture, top_block_cls=_fixed_center_frequency)
158
- swept_center_frequency = partial(capture, top_block_cls=_swept_center_frequency)
148
+ swept_center_frequency = partial(capture, top_block_cls=_swept_center_frequency, max_noutput_items=1024)