1818# Author(s): <you>
1919##--------------------------------------------------------------------\
2020
21+ from __future__ import annotations
22+
2123import os
24+ from typing import TYPE_CHECKING , Any , Callable , Generator
25+
26+ import numpy as np
2227
2328from .. import constants as C
29+ from .._host import HostOps
2430from ..sigmf import write_sigmf_meta
2531
32+ if TYPE_CHECKING :
33+ from .._receiver import PersistentReceiver
34+ from .._stream_ctx import StreamCtx
35+
2636
27- class CaptureMixin :
28- def capture (self , freq , sample_rate , * , out = "capture.iq" ,
29- num_samples = None , duration = None ,
30- lna = 16 , vga = 20 , amp = False , bias_tee = False ,
31- baseband_bw = None , to_stdout = False , sigmf = True ,
32- segment_secs = None , print_cmd = False ):
37+ class CaptureMixin (HostOps ):
38+ def capture (self , freq : float , sample_rate : float , * ,
39+ out : str = "capture.iq" , num_samples : int | None = None ,
40+ duration : float | None = None , lna : int = 16 , vga : int = 20 ,
41+ amp : bool = False , bias_tee : bool = False ,
42+ baseband_bw : float | None = None , to_stdout : bool = False ,
43+ sigmf : bool = True , segment_secs : float | None = None ,
44+ print_cmd : bool = False ) -> Any :
3345 self .require_mode (C .MODE_RX )
3446 freq , sample_rate , lna , vga = self .validate_rx (freq , sample_rate , lna , vga )
3547 bw = self ._auto_baseband (sample_rate , baseband_bw )
@@ -57,7 +69,7 @@ def capture(self, freq, sample_rate, *, out="capture.iq",
5769 # The inner stream generator is closed EXPLICITLY on the way out;
5870 # leaving it to GC can orphan a receiving hackrf_transfer after
5971 # the caller breaks out of the loop.
60- def _gen ():
72+ def _gen () -> Generator [ np . ndarray , None , None ]: # decoded, not raw
6173 tail = b""
6274 inner = self ._run (argv , mode = "stream" )
6375 try :
@@ -97,17 +109,21 @@ def _gen():
97109 return res
98110
99111 # ---- aliases ----
100- def rx (self , * a , ** k ) :
112+ def rx (self , * a : Any , ** k : Any ) -> Any :
101113 return self .capture (* a , ** k )
102114
103- def capture_samples (self , freq , sample_rate , num_samples , ** k ):
115+ def capture_samples (self , freq : float , sample_rate : float ,
116+ num_samples : int , ** k : Any ) -> Any :
104117 return self .capture (freq , sample_rate , num_samples = num_samples , ** k )
105118
106- def capture_seconds (self , freq , sample_rate , duration , ** k ):
119+ def capture_seconds (self , freq : float , sample_rate : float ,
120+ duration : float , ** k : Any ) -> Any :
107121 return self .capture (freq , sample_rate , duration = duration , ** k )
108122
109- def scan_frequencies (self , freqs , sample_rate , num_samples , * ,
110- on_capture = None , ** k ):
123+ def scan_frequencies (self , freqs : list [float ], sample_rate : float ,
124+ num_samples : int , * ,
125+ on_capture : Callable [..., Any ] | None = None ,
126+ ** k : Any ) -> dict [float , np .ndarray ] | None :
111127 # Sequentially capture a fixed sample count at each frequency in
112128 # `freqs`, retuning between them. This does NOT close the gapless-
113129 # retune gap (each retune is a fresh hackrf_transfer with a short
@@ -130,8 +146,8 @@ def scan_frequencies(self, freqs, sample_rate, num_samples, *,
130146 return results if on_capture is None else None
131147
132148 # ---- in-memory + context-managed entry points ----
133- def capture_array (self , freq , sample_rate , num_samples , * ,
134- return_params = False , ** k ) :
149+ def capture_array (self , freq : float , sample_rate : float , num_samples : int ,
150+ * , return_params : bool = False , ** k : Any ) -> Any :
135151 # Scripting entry point: return EXACTLY num_samples complex64 samples
136152 # in RAM, no file. Built on the stdout-stream path so it shares the
137153 # odd-byte carry + clean-reap logic. The stream is closed as soon as
@@ -164,8 +180,10 @@ def capture_array(self, freq, sample_rate, num_samples, *,
164180 return iq , self .last_params
165181 return iq
166182
167- def open_receiver (self , freq , sample_rate , * , lna = 16 , vga = 20 , amp = False ,
168- baseband_bw = None , read_samples = 131072 ):
183+ def open_receiver (self , freq : float , sample_rate : float , * , lna : int = 16 ,
184+ vga : int = 20 , amp : bool = False ,
185+ baseband_bw : float | None = None ,
186+ read_samples : int = 131072 ) -> PersistentReceiver :
169187 # Open a PERSISTENT fixed-frequency receiver: one long-lived
170188 # hackrf_transfer you drain in segments over time, so you don't pay the
171189 # ~1-2 s process spin-up per capture. Use as a context manager:
@@ -184,7 +202,8 @@ def open_receiver(self, freq, sample_rate, *, lna=16, vga=20, amp=False,
184202 amp = amp , baseband_bw = baseband_bw ,
185203 read_samples = read_samples )
186204
187- def capture_stream (self , freq , sample_rate , ** k ):
205+ def capture_stream (self , freq : float , sample_rate : float ,
206+ ** k : Any ) -> StreamCtx :
188207 # Context manager wrapping the live stdout stream so the receiving
189208 # hackrf_transfer is ALWAYS reaped on exit, even on exception:
190209 # with h.capture_stream(433.92e6, 8e6) as blocks:
@@ -194,8 +213,10 @@ def capture_stream(self, freq, sample_rate, **k):
194213 gen = self .capture (freq , sample_rate , to_stdout = True , ** k )
195214 return StreamCtx (gen )
196215
197- def capture_callback (self , freq , sample_rate , on_block , * ,
198- max_samples = None , max_blocks = None , ** k ):
216+ def capture_callback (self , freq : float , sample_rate : float ,
217+ on_block : Callable [..., Any ], * ,
218+ max_samples : int | None = None ,
219+ max_blocks : int | None = None , ** k : Any ) -> int :
199220 # Binder-style ergonomics over the subprocess stream: instead of the
200221 # caller writing the receive loop, register a callback that fires with
201222 # each decoded complex64 block as it arrives. This is the usability
@@ -228,8 +249,9 @@ def capture_callback(self, freq, sample_rate, on_block, *,
228249 return total
229250
230251 # ---- internals ----
231- def _rx_argv (self , freq , sample_rate , target , lna , vga , amp , bias_tee ,
232- bw , num_samples ):
252+ def _rx_argv (self , freq : float , sample_rate : float , target : str , lna : int ,
253+ vga : int , amp : bool , bias_tee : bool , bw : float ,
254+ num_samples : int | None ) -> list [Any ]:
233255 argv = ["transfer" , "-r" , target ,
234256 "-f" , int (freq ), "-s" , int (sample_rate ),
235257 "-l" , lna , "-g" , vga ,
@@ -241,8 +263,10 @@ def _rx_argv(self, freq, sample_rate, target, lna, vga, amp, bias_tee,
241263 argv += ["-n" , int (num_samples )]
242264 return argv
243265
244- def _capture_segmented (self , freq , sample_rate , out , segment_secs , lna ,
245- vga , amp , bias_tee , bw , sigmf , print_cmd ):
266+ def _capture_segmented (self , freq : float , sample_rate : float , out : str ,
267+ segment_secs : float , lna : int , vga : int , amp : bool ,
268+ bias_tee : bool , bw : float , sigmf : bool ,
269+ print_cmd : bool ) -> list [str ]:
246270 # Rolling files out_000.iq, out_001.iq, ... each `segment_secs` long.
247271 # Each segment is a bounded blocking capture, so files are whole.
248272 base , ext = os .path .splitext (out )
0 commit comments