1use std::future::{Future, IntoFuture, poll_fn};
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use std::time::Duration;
7
8use nusb::MaybeFuture;
9
10use crate::Complex32;
11use crate::config::{
12 Config, ConfigBuilder, validate_frequency, validate_lna_gain, validate_sample_rate,
13 validate_tx_vga_gain, validate_vga_gain,
14};
15use crate::device::{HackRf, shutdown_hardware};
16use crate::errors::{Error, Result};
17use crate::maybe_future::{MaybeFutureExt, ready};
18use crate::radio::{
19 Direction, ExecutionStyle, LifecycleController, StreamClaim, TxStreamingStats, TxTransport,
20};
21use crate::streaming::{AsyncDirectRxStream, AsyncStreamingBackend, StreamingStats};
22#[cfg(not(target_arch = "wasm32"))]
23use crate::streaming::{DirectRxStream, StreamingBackend};
24use crate::types::DeviceInfo;
25use crate::usb::{ControlBackend, NusbControl};
26
27#[cfg(not(target_arch = "wasm32"))]
28type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
29#[cfg(target_arch = "wasm32")]
30type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
31
32#[derive(Debug)]
33struct DeviceInner {
34 direct: HackRf<NusbControl>,
35 info: DeviceInfo,
36 #[cfg(not(target_arch = "wasm32"))]
37 shutdown_on_drop: bool,
38}
39
40struct OpeningCleanup {
47 direct: Option<HackRf<NusbControl>>,
48}
49
50impl OpeningCleanup {
51 fn new(direct: HackRf<NusbControl>) -> Self {
52 Self {
53 direct: Some(direct),
54 }
55 }
56
57 fn disarm(mut self) {
58 self.direct = None;
59 }
60}
61
62#[cfg(not(target_arch = "wasm32"))]
63impl Drop for OpeningCleanup {
64 fn drop(&mut self) {
65 if let Some(direct) = self.direct.as_ref() {
66 let _ = shutdown_hardware(direct).wait();
67 }
68 }
69}
70
71#[cfg(target_arch = "wasm32")]
72impl Drop for OpeningCleanup {
73 fn drop(&mut self) {
74 if let Some(direct) = self.direct.take() {
75 wasm_bindgen_futures::spawn_local(async move {
76 let _ = shutdown_hardware(&direct).await;
77 });
78 }
79 }
80}
81
82#[cfg(not(target_arch = "wasm32"))]
83impl Drop for DeviceInner {
84 fn drop(&mut self) {
85 if self.shutdown_on_drop {
86 let _ = shutdown_hardware(&self.direct).wait();
87 }
88 }
89}
90
91#[derive(Debug)]
93pub struct Device {
94 inner: DeviceInner,
95 config: Config,
96 radio: std::sync::Arc<LifecycleController>,
97 #[cfg(target_arch = "wasm32")]
98 shutdown_complete: bool,
99}
100
101#[derive(Clone, Debug)]
102enum ConfigOperationKind {
103 Configure(Config),
104 Frequency(u64),
105 SampleRate(u32),
106 LnaGain(u8),
107 VgaGain(u8),
108 TxVgaGain(u8),
109 Amp(bool),
110 BiasTee(bool),
111}
112
113struct ConfigOperation<'a> {
114 device: &'a mut Device,
115 kind: ConfigOperationKind,
116}
117
118impl ConfigOperation<'_> {
119 fn begin(&self, style: ExecutionStyle) -> Result<crate::radio::ReconfigureGuard> {
120 match self.kind {
121 ConfigOperationKind::Configure(_)
122 | ConfigOperationKind::Amp(_)
123 | ConfigOperationKind::BiasTee(_) => {}
124 ConfigOperationKind::Frequency(value) => validate_frequency(value)?,
125 ConfigOperationKind::SampleRate(value) => validate_sample_rate(value)?,
126 ConfigOperationKind::LnaGain(value) => validate_lna_gain(value)?,
127 ConfigOperationKind::VgaGain(value) => validate_vga_gain(value)?,
128 ConfigOperationKind::TxVgaGain(value) => validate_tx_vga_gain(value)?,
129 }
130 self.device.radio.begin_reconfigure(style)
131 }
132
133 fn record_success(&mut self) {
134 match &self.kind {
135 ConfigOperationKind::Configure(config) => {
136 self.device.config = config.clone();
137 self.device
138 .radio
139 .set_bias_tee(self.device.config.bias_tee_enabled());
140 }
141 ConfigOperationKind::Frequency(value) => {
142 self.device.config.set_frequency_hz_internal(*value)
143 }
144 ConfigOperationKind::SampleRate(value) => {
145 self.device.config.set_sample_rate_hz_internal(*value)
146 }
147 ConfigOperationKind::LnaGain(value) => {
148 self.device.config.set_lna_gain_db_internal(*value)
149 }
150 ConfigOperationKind::VgaGain(value) => {
151 self.device.config.set_vga_gain_db_internal(*value)
152 }
153 ConfigOperationKind::TxVgaGain(value) => {
154 self.device.config.set_tx_vga_gain_db_internal(*value)
155 }
156 ConfigOperationKind::Amp(value) => self.device.config.set_amp_enabled_internal(*value),
157 ConfigOperationKind::BiasTee(value) => {
158 self.device.config.set_bias_tee_enabled_internal(*value);
159 self.device.radio.set_bias_tee(*value);
160 }
161 }
162 }
163
164 #[cfg(not(target_arch = "wasm32"))]
165 fn wait(mut self) -> Result<()> {
166 let _guard = self.begin(ExecutionStyle::Blocking)?;
167 let result = match &self.kind {
168 ConfigOperationKind::Configure(config) => {
169 self.device.inner.direct.configure(config).wait()
170 }
171 ConfigOperationKind::Frequency(value) => {
172 self.device.inner.direct.set_frequency(*value).wait()
173 }
174 ConfigOperationKind::SampleRate(value) => {
175 self.device.inner.direct.set_sample_rate(*value).wait()
176 }
177 ConfigOperationKind::LnaGain(value) => {
178 self.device.inner.direct.set_lna_gain(*value).wait()
179 }
180 ConfigOperationKind::VgaGain(value) => {
181 self.device.inner.direct.set_vga_gain(*value).wait()
182 }
183 ConfigOperationKind::TxVgaGain(value) => {
184 self.device.inner.direct.set_tx_vga_gain(*value).wait()
185 }
186 ConfigOperationKind::Amp(value) => self.device.inner.direct.set_amp(*value).wait(),
187 ConfigOperationKind::BiasTee(value) => {
188 self.device.inner.direct.set_bias_tee(*value).wait()
189 }
190 };
191 if result.is_ok() {
192 self.record_success();
193 }
194 result
195 }
196
197 async fn run_async(mut self) -> Result<()> {
198 let _guard = self.begin(ExecutionStyle::Async)?;
199 let result = match &self.kind {
200 ConfigOperationKind::Configure(config) => {
201 self.device.inner.direct.configure(config).await
202 }
203 ConfigOperationKind::Frequency(value) => {
204 self.device.inner.direct.set_frequency(*value).await
205 }
206 ConfigOperationKind::SampleRate(value) => {
207 self.device.inner.direct.set_sample_rate(*value).await
208 }
209 ConfigOperationKind::LnaGain(value) => {
210 self.device.inner.direct.set_lna_gain(*value).await
211 }
212 ConfigOperationKind::VgaGain(value) => {
213 self.device.inner.direct.set_vga_gain(*value).await
214 }
215 ConfigOperationKind::TxVgaGain(value) => {
216 self.device.inner.direct.set_tx_vga_gain(*value).await
217 }
218 ConfigOperationKind::Amp(value) => self.device.inner.direct.set_amp(*value).await,
219 ConfigOperationKind::BiasTee(value) => {
220 self.device.inner.direct.set_bias_tee(*value).await
221 }
222 };
223 if result.is_ok() {
224 self.record_success();
225 }
226 result
227 }
228}
229
230impl<'a> IntoFuture for ConfigOperation<'a> {
231 type Output = Result<()>;
232 type IntoFuture = BoxFuture<'a, Result<()>>;
233
234 fn into_future(self) -> Self::IntoFuture {
235 Box::pin(async move { self.run_async().await })
236 }
237}
238
239impl MaybeFuture for ConfigOperation<'_> {
240 #[cfg(not(target_arch = "wasm32"))]
241 fn wait(self) -> Self::Output {
242 self.wait()
243 }
244}
245
246impl Device {
247 pub fn list() -> impl MaybeFuture<Output = Result<Vec<crate::DeviceDescriptor>>> {
249 crate::discovery::list_devices()
250 }
251
252 pub fn builder() -> DeviceBuilder {
254 DeviceBuilder::default()
255 }
256
257 pub fn open() -> impl MaybeFuture<Output = Result<Self>> {
259 Self::builder().open()
260 }
261
262 pub fn open_serial(serial: u128) -> impl MaybeFuture<Output = Result<Self>> {
264 Self::builder().serial(serial).open()
265 }
266
267 #[cfg(target_arch = "wasm32")]
271 pub async fn request_permission() -> Result<()> {
272 Self::builder().request_permission().await
273 }
274
275 pub fn info(&self) -> &DeviceInfo {
277 &self.inner.info
278 }
279
280 pub fn config(&self) -> &Config {
282 &self.config
283 }
284
285 pub fn configure(&mut self, config: &Config) -> impl MaybeFuture<Output = Result<()>> + '_ {
289 ConfigOperation {
290 device: self,
291 kind: ConfigOperationKind::Configure(config.clone()),
292 }
293 }
294
295 pub fn set_frequency_hz(
299 &mut self,
300 frequency_hz: u64,
301 ) -> impl MaybeFuture<Output = Result<()>> + '_ {
302 ConfigOperation {
303 device: self,
304 kind: ConfigOperationKind::Frequency(frequency_hz),
305 }
306 }
307
308 pub fn set_sample_rate_hz(
313 &mut self,
314 sample_rate_hz: u32,
315 ) -> impl MaybeFuture<Output = Result<()>> + '_ {
316 ConfigOperation {
317 device: self,
318 kind: ConfigOperationKind::SampleRate(sample_rate_hz),
319 }
320 }
321
322 pub fn set_lna_gain_db(&mut self, gain_db: u8) -> impl MaybeFuture<Output = Result<()>> + '_ {
326 ConfigOperation {
327 device: self,
328 kind: ConfigOperationKind::LnaGain(gain_db),
329 }
330 }
331
332 pub fn set_vga_gain_db(&mut self, gain_db: u8) -> impl MaybeFuture<Output = Result<()>> + '_ {
336 ConfigOperation {
337 device: self,
338 kind: ConfigOperationKind::VgaGain(gain_db),
339 }
340 }
341
342 pub fn set_tx_vga_gain_db(
346 &mut self,
347 gain_db: u8,
348 ) -> impl MaybeFuture<Output = Result<()>> + '_ {
349 ConfigOperation {
350 device: self,
351 kind: ConfigOperationKind::TxVgaGain(gain_db),
352 }
353 }
354
355 pub fn set_amp_enable(&mut self, enabled: bool) -> impl MaybeFuture<Output = Result<()>> + '_ {
359 ConfigOperation {
360 device: self,
361 kind: ConfigOperationKind::Amp(enabled),
362 }
363 }
364
365 pub fn set_bias_tee(&mut self, enabled: bool) -> impl MaybeFuture<Output = Result<()>> + '_ {
369 ConfigOperation {
370 device: self,
371 kind: ConfigOperationKind::BiasTee(enabled),
372 }
373 }
374
375 pub fn rx_stream(&self) -> Result<RxStream> {
380 let claim = self.radio.claim_stream(Direction::Rx)?;
381 Ok(RxStream::new(
382 self.inner.direct.stream_handle(),
383 std::sync::Arc::clone(&self.radio),
384 claim,
385 ))
386 }
387
388 pub fn tx_stream(&self) -> Result<TxStream> {
393 let claim = self.radio.claim_stream(Direction::Tx)?;
394 Ok(TxStream::new(std::sync::Arc::clone(&self.radio), claim))
395 }
396
397 #[must_use = "shutdown must be waited or awaited to observe hardware cleanup"]
405 pub fn shutdown(&mut self) -> impl MaybeFuture<Output = Result<()>> + '_ {
406 DeviceShutdownOperation { device: self }
407 }
408
409 #[cfg(not(target_arch = "wasm32"))]
410 fn shutdown_blocking(&mut self) -> Result<()> {
411 self.radio.shutdown_blocking()?;
412 self.inner.shutdown_on_drop = false;
413 Ok(())
414 }
415
416 async fn shutdown_async(&mut self) -> Result<()> {
417 self.radio.shutdown_async().await?;
418 #[cfg(not(target_arch = "wasm32"))]
419 {
420 self.inner.shutdown_on_drop = false;
421 }
422 #[cfg(target_arch = "wasm32")]
423 {
424 self.shutdown_complete = true;
425 }
426 Ok(())
427 }
428}
429
430impl Drop for Device {
431 fn drop(&mut self) {
432 self.radio.close_on_drop();
433 #[cfg(not(target_arch = "wasm32"))]
434 {
435 }
438 #[cfg(target_arch = "wasm32")]
439 if !self.shutdown_complete {
440 let direct = self.inner.direct.stream_handle();
441 wasm_bindgen_futures::spawn_local(async move {
442 let _ = shutdown_hardware(&direct).await;
443 });
444 }
445 }
446}
447
448#[derive(Clone, Debug, Default)]
450pub struct DeviceBuilder {
451 serial: Option<u128>,
452 config: ConfigBuilder,
453}
454
455impl DeviceBuilder {
456 pub fn serial(mut self, serial: u128) -> Self {
458 self.serial = Some(serial);
459 self
460 }
461
462 pub fn frequency_hz(mut self, value: u64) -> Self {
464 self.config = self.config.frequency_hz(value);
465 self
466 }
467
468 pub fn sample_rate_hz(mut self, value: u32) -> Self {
470 self.config = self.config.sample_rate_hz(value);
471 self
472 }
473
474 pub fn lna_gain_db(mut self, value: u8) -> Self {
476 self.config = self.config.lna_gain_db(value);
477 self
478 }
479
480 pub fn vga_gain_db(mut self, value: u8) -> Self {
482 self.config = self.config.vga_gain_db(value);
483 self
484 }
485
486 pub fn tx_vga_gain_db(mut self, value: u8) -> Self {
488 self.config = self.config.tx_vga_gain_db(value);
489 self
490 }
491
492 pub fn amp_enable(mut self, value: bool) -> Self {
494 self.config = self.config.amp_enable(value);
495 self
496 }
497
498 pub fn bias_tee(mut self, value: bool) -> Self {
500 self.config = self.config.bias_tee(value);
501 self
502 }
503
504 pub fn open(self) -> impl MaybeFuture<Output = Result<Device>> {
506 let config = self.config.build();
507 let serial = self.serial;
508 ready(config).and_then(move |config| {
509 HackRf::open(serial).and_then(move |(direct, usb_api_version)| {
510 let cleanup = OpeningCleanup::new(direct.stream_handle());
511 let info = direct.fetch_device_info(usb_api_version);
512 info.and_then(move |info| {
513 let flush_size = direct.tx_flush_size(info.usb_api_version);
514 direct
515 .configure(&config)
516 .and_then(move |()| flush_size)
517 .map(move |flush_size| {
518 let flush_size = flush_size?;
519 let tx_endpoint = direct.bulk_out()?;
520 cleanup.disarm();
521 Ok(Device {
522 inner: DeviceInner {
523 direct: direct.stream_handle(),
524 info,
525 #[cfg(not(target_arch = "wasm32"))]
526 shutdown_on_drop: true,
527 },
528 radio: LifecycleController::new(
529 direct,
530 tx_endpoint,
531 flush_size,
532 config.bias_tee_enabled(),
533 ),
534 config,
535 #[cfg(target_arch = "wasm32")]
536 shutdown_complete: false,
537 })
538 })
539 })
540 })
541 })
542 }
543
544 #[cfg(target_arch = "wasm32")]
546 pub async fn request_permission(&self) -> Result<()> {
547 crate::discovery::request_device_permission(self.serial).await
548 }
549}
550
551#[derive(Clone, Copy, Debug, Eq, PartialEq)]
552enum ReceiverState {
553 Stopped,
554 CleanupRequired,
555 Paused,
556 Running,
557}
558
559#[cfg(not(target_arch = "wasm32"))]
560struct BlockingRxInner<C: ControlBackend + StreamingBackend> {
561 direct: HackRf<C>,
562 stream: Option<DirectRxStream<C::BulkIn>>,
563 stats: StreamingStats,
564 state: ReceiverState,
565}
566
567#[cfg(not(target_arch = "wasm32"))]
568impl<C> BlockingRxInner<C>
569where
570 C: ControlBackend + StreamingBackend,
571{
572 fn new(direct: HackRf<C>) -> Self {
573 Self {
574 direct,
575 stream: None,
576 stats: StreamingStats::default(),
577 state: ReceiverState::Stopped,
578 }
579 }
580
581 fn open_queue(&mut self) -> Result<()> {
582 match self.state {
583 ReceiverState::Running => return Ok(()),
584 ReceiverState::CleanupRequired => return Err(Error::Busy),
585 ReceiverState::Paused => {
586 self.stats = StreamingStats::default();
587 self.stream
588 .as_mut()
589 .ok_or(Error::stream_closed("RX stream has no USB queue"))?
590 .reset_stats();
591 return Ok(());
592 }
593 ReceiverState::Stopped => {}
594 }
595 self.stats = StreamingStats::default();
596 self.state = ReceiverState::CleanupRequired;
597 self.stream = Some(self.direct.start_rx_blocking()?);
598 Ok(())
599 }
600
601 fn commit_start(&mut self) {
602 debug_assert!(self.stream.is_some());
603 self.state = ReceiverState::Running;
604 }
605
606 fn read(&mut self, out: &mut [Complex32], timeout: Duration) -> Result<usize> {
607 if self.state != ReceiverState::Running {
608 return Err(Error::stream_closed("RX stream is stopped"));
609 }
610 let result = self
611 .stream
612 .as_mut()
613 .ok_or(Error::stream_closed("RX stream has no USB queue"))?
614 .read_complex(out, timeout);
615 if result.is_err() {
616 self.state = ReceiverState::CleanupRequired;
617 }
618 result
619 }
620
621 fn close_queue(&mut self) -> StreamingStats {
622 if self.state == ReceiverState::Stopped {
623 return self.stats;
624 }
625 if let Some(mut stream) = self.stream.take() {
626 self.stats = stream.close();
627 }
628 self.state = ReceiverState::Stopped;
629 self.stats
630 }
631
632 fn pause_queue(&mut self) -> StreamingStats {
633 if self.state == ReceiverState::Running {
634 if let Some(stream) = self.stream.as_mut() {
635 self.stats = stream.stats();
636 }
637 self.state = ReceiverState::Paused;
638 }
639 self.stats
640 }
641
642 fn stop(&mut self) -> StreamingStats {
643 if self.state == ReceiverState::CleanupRequired {
644 self.close_queue()
645 } else {
646 self.pause_queue()
647 }
648 }
649}
650
651struct AsyncRxInner<C: ControlBackend + AsyncStreamingBackend> {
652 direct: HackRf<C>,
653 stream: Option<AsyncDirectRxStream<C::BulkIn>>,
654 stats: StreamingStats,
655 state: ReceiverState,
656}
657
658impl<C> AsyncRxInner<C>
659where
660 C: ControlBackend + AsyncStreamingBackend,
661{
662 fn new(direct: HackRf<C>) -> Self {
663 Self {
664 direct,
665 stream: None,
666 stats: StreamingStats::default(),
667 state: ReceiverState::Stopped,
668 }
669 }
670
671 async fn open_queue(&mut self) -> Result<()> {
672 match self.state {
673 ReceiverState::Running => return Ok(()),
674 ReceiverState::CleanupRequired => return Err(Error::Busy),
675 ReceiverState::Paused => {
676 self.stats = StreamingStats::default();
677 self.stream
678 .as_mut()
679 .ok_or(Error::stream_closed("async RX stream has no USB queue"))?
680 .reset_stats();
681 return Ok(());
682 }
683 ReceiverState::Stopped => {}
684 }
685 self.stats = StreamingStats::default();
686 self.state = ReceiverState::CleanupRequired;
687 self.stream = Some(self.direct.start_rx_async().await?);
688 Ok(())
689 }
690
691 fn commit_start(&mut self) {
692 debug_assert!(self.stream.is_some());
693 self.state = ReceiverState::Running;
694 }
695
696 fn poll_read(&mut self, out: &mut [Complex32], cx: &mut Context<'_>) -> Poll<Result<usize>> {
697 if self.state != ReceiverState::Running {
698 return Poll::Ready(Err(Error::stream_closed("async RX stream is stopped")));
699 }
700 let result = match self.stream.as_mut() {
701 Some(stream) => stream.poll_read_complex(out, cx),
702 None => Poll::Ready(Err(Error::stream_closed(
703 "async RX stream has no USB queue",
704 ))),
705 };
706 if matches!(result, Poll::Ready(Err(_))) {
707 self.state = ReceiverState::CleanupRequired;
708 }
709 result
710 }
711
712 fn close_queue(&mut self) -> StreamingStats {
713 if self.state == ReceiverState::Stopped {
714 return self.stats;
715 }
716 if let Some(mut stream) = self.stream.take() {
717 self.stats = stream.close();
718 }
719 self.state = ReceiverState::Stopped;
720 self.stats
721 }
722
723 fn pause_queue(&mut self) -> StreamingStats {
724 if self.state == ReceiverState::Running {
725 if let Some(stream) = self.stream.as_mut() {
726 self.stats = stream.stats();
727 }
728 self.state = ReceiverState::Paused;
729 }
730 self.stats
731 }
732
733 fn stop(&mut self) -> StreamingStats {
734 if self.state == ReceiverState::CleanupRequired {
735 self.close_queue()
736 } else {
737 self.pause_queue()
738 }
739 }
740}
741
742enum RxStreamState {
743 Dormant(HackRf<NusbControl>),
744 Poisoned,
745 #[cfg(not(target_arch = "wasm32"))]
746 Blocking(BlockingRxInner<NusbControl>),
747 Async(AsyncRxInner<NusbControl>),
748}
749
750#[must_use = "RX streams retain the device's exclusive RX stream claim until dropped"]
755pub struct RxStream {
756 state: RxStreamState,
757 radio: std::sync::Arc<LifecycleController>,
758 failed: bool,
759 #[cfg(target_arch = "wasm32")]
760 queue_started: bool,
761 _claim: StreamClaim,
762}
763
764impl RxStream {
765 fn new(
766 direct: HackRf<NusbControl>,
767 radio: std::sync::Arc<LifecycleController>,
768 claim: StreamClaim,
769 ) -> Self {
770 Self {
771 state: RxStreamState::Dormant(direct),
772 radio,
773 failed: false,
774 #[cfg(target_arch = "wasm32")]
775 queue_started: false,
776 _claim: claim,
777 }
778 }
779
780 pub fn start(&mut self) -> impl MaybeFuture<Output = Result<()>> + '_ {
786 RxStartOperation { stream: self }
787 }
788
789 pub fn read<'a>(
795 &'a mut self,
796 out: &'a mut [Complex32],
797 timeout: Option<Duration>,
798 ) -> impl MaybeFuture<Output = Result<usize>> + 'a {
799 ReadOperation {
800 stream: self,
801 out,
802 timeout,
803 }
804 }
805
806 pub fn stop(&mut self) -> impl MaybeFuture<Output = Result<StreamingStats>> + '_ {
816 RxStopOperation { stream: self }
817 }
818
819 fn receiver_state(&self) -> ReceiverState {
820 match &self.state {
821 RxStreamState::Dormant(_) => ReceiverState::Stopped,
822 RxStreamState::Poisoned => ReceiverState::CleanupRequired,
823 #[cfg(not(target_arch = "wasm32"))]
824 RxStreamState::Blocking(stream) => stream.state,
825 RxStreamState::Async(stream) => stream.state,
826 }
827 }
828
829 #[cfg(not(target_arch = "wasm32"))]
830 fn initialize_blocking(&mut self) -> Result<()> {
831 if matches!(self.state, RxStreamState::Blocking(_)) {
832 return Ok(());
833 }
834 if matches!(self.state, RxStreamState::Async(_)) {
835 return Err(Error::Busy);
836 }
837 let state = core::mem::replace(&mut self.state, RxStreamState::Poisoned);
838 match state {
839 RxStreamState::Dormant(direct) => {
840 self.state = RxStreamState::Blocking(BlockingRxInner::new(direct));
841 Ok(())
842 }
843 other => {
844 self.state = other;
845 Err(Error::stream_closed("RX stream has no device"))
846 }
847 }
848 }
849
850 fn initialize_async(&mut self) -> Result<()> {
851 if matches!(self.state, RxStreamState::Async(_)) {
852 return Ok(());
853 }
854 #[cfg(not(target_arch = "wasm32"))]
855 if matches!(self.state, RxStreamState::Blocking(_)) {
856 return Err(Error::Busy);
857 }
858 let state = core::mem::replace(&mut self.state, RxStreamState::Poisoned);
859 match state {
860 RxStreamState::Dormant(direct) => {
861 self.state = RxStreamState::Async(AsyncRxInner::new(direct));
862 Ok(())
863 }
864 other => {
865 self.state = other;
866 Err(Error::stream_closed("RX stream has no device"))
867 }
868 }
869 }
870
871 #[cfg(not(target_arch = "wasm32"))]
872 fn start_blocking(&mut self) -> Result<()> {
873 if self.failed {
874 return Err(self.failed_error());
875 }
876 match self.receiver_state() {
877 ReceiverState::Running => return Ok(()),
878 ReceiverState::CleanupRequired => return Err(Error::Busy),
879 ReceiverState::Stopped | ReceiverState::Paused => {}
880 }
881 let mut transition = self.radio.begin_rx_start_blocking()?;
882 self.initialize_blocking()?;
883 match &mut self.state {
884 RxStreamState::Blocking(stream) => stream.open_queue()?,
885 _ => return Err(Error::Busy),
886 }
887 transition.finish_blocking()?;
888 if let RxStreamState::Blocking(stream) = &mut self.state {
889 stream.commit_start();
890 }
891 Ok(())
892 }
893
894 async fn start_async(&mut self) -> Result<()> {
895 if self.failed {
896 return Err(self.failed_error());
897 }
898 match self.receiver_state() {
899 ReceiverState::Running => return Ok(()),
900 ReceiverState::CleanupRequired => return Err(Error::Busy),
901 ReceiverState::Stopped | ReceiverState::Paused => {}
902 }
903 let mut transition = self.radio.begin_rx_start_async().await?;
904 self.initialize_async()?;
905 match &mut self.state {
906 RxStreamState::Async(stream) => stream.open_queue().await?,
907 _ => return Err(Error::Busy),
908 }
909 #[cfg(target_arch = "wasm32")]
910 {
911 self.queue_started = true;
915 }
916 transition.finish_async().await?;
917 if let RxStreamState::Async(stream) = &mut self.state {
918 stream.commit_start();
919 }
920 Ok(())
921 }
922
923 #[cfg(not(target_arch = "wasm32"))]
924 fn read_blocking(&mut self, out: &mut [Complex32], timeout: Duration) -> Result<usize> {
925 if self.receiver_state() != ReceiverState::Running {
926 return Err(Error::stream_closed("RX stream is stopped"));
927 }
928 match &mut self.state {
929 RxStreamState::Blocking(stream) => {
930 let result = stream.read(out, timeout);
931 if let Err(error) = result {
932 self.failed = true;
933 self.radio.mark_stream_failed(Direction::Rx);
934 return match self.stop_blocking() {
935 Ok(_) => Err(error),
936 Err(cleanup_error) => {
937 Err(cleanup_error.at("turn radio off after RX failure"))
938 }
939 };
940 }
941 result
942 }
943 RxStreamState::Async(_) => Err(Error::Busy),
944 RxStreamState::Dormant(_) => Err(Error::stream_closed("RX stream is not started")),
945 RxStreamState::Poisoned => Err(Error::stream_closed("RX stream requires stop")),
946 }
947 }
948
949 async fn read_async(&mut self, out: &mut [Complex32]) -> Result<usize> {
950 if self.receiver_state() != ReceiverState::Running {
951 return Err(Error::stream_closed("RX stream is stopped"));
952 }
953 match &mut self.state {
954 RxStreamState::Async(stream) => {
955 let result = poll_fn(|cx| stream.poll_read(out, cx)).await;
956 if let Err(error) = result {
957 self.failed = true;
958 self.radio.mark_stream_failed(Direction::Rx);
959 return match self.stop_async().await {
960 Ok(_) => Err(error),
961 Err(cleanup_error) => {
962 Err(cleanup_error.at("turn radio off after RX failure"))
963 }
964 };
965 }
966 result
967 }
968 #[cfg(not(target_arch = "wasm32"))]
969 RxStreamState::Blocking(_) => Err(Error::Busy),
970 RxStreamState::Dormant(_) => Err(Error::stream_closed("RX stream is not started")),
971 RxStreamState::Poisoned => Err(Error::stream_closed("RX stream requires stop")),
972 }
973 }
974
975 #[cfg(not(target_arch = "wasm32"))]
976 fn stop_blocking(&mut self) -> Result<StreamingStats> {
977 if matches!(self.state, RxStreamState::Async(_)) {
978 return Err(Error::Busy);
979 }
980 let transition = self.radio.begin_stop_blocking(Direction::Rx)?;
981 let stats = match &mut self.state {
982 RxStreamState::Dormant(_) | RxStreamState::Poisoned => StreamingStats::default(),
983 RxStreamState::Blocking(stream) => stream.stop(),
984 RxStreamState::Async(_) => return Err(Error::Busy),
985 };
986 if let Some(mut transition) = transition {
987 transition.finish_blocking()?;
988 }
989 Ok(stats)
990 }
991
992 async fn stop_async(&mut self) -> Result<StreamingStats> {
993 #[cfg(not(target_arch = "wasm32"))]
994 if matches!(self.state, RxStreamState::Blocking(_)) {
995 return Err(Error::Busy);
996 }
997 let transition = self.radio.begin_stop_async(Direction::Rx).await?;
998 #[cfg(target_arch = "wasm32")]
999 if self.receiver_state() == ReceiverState::CleanupRequired {
1000 self.abandon_webusb_queue();
1004 }
1005 let stats = match &mut self.state {
1006 RxStreamState::Dormant(_) | RxStreamState::Poisoned => StreamingStats::default(),
1007 RxStreamState::Async(stream) => stream.stop(),
1008 #[cfg(not(target_arch = "wasm32"))]
1009 RxStreamState::Blocking(_) => return Err(Error::Busy),
1010 };
1011 if let Some(mut transition) = transition {
1012 transition.finish_async().await?;
1013 }
1014 Ok(stats)
1015 }
1016
1017 fn failed_error(&self) -> Error {
1018 #[cfg(not(target_arch = "wasm32"))]
1019 {
1020 Error::stream_closed("RX stream failed; drop it and create a new RX stream")
1021 }
1022 #[cfg(target_arch = "wasm32")]
1023 {
1024 Error::stream_closed("RX stream failed; drop and reopen the WebUSB device")
1025 }
1026 }
1027
1028 #[cfg(target_arch = "wasm32")]
1029 fn abandon_webusb_queue(&mut self) {
1030 if self.queue_started {
1031 self.failed = true;
1032 self.radio.mark_rx_queue_abandoned();
1033 }
1034 }
1035
1036 #[cfg(not(target_arch = "wasm32"))]
1037 fn close_queue_on_drop(&mut self) {
1038 match &mut self.state {
1039 RxStreamState::Dormant(_) | RxStreamState::Poisoned => {}
1040 RxStreamState::Blocking(stream) => {
1041 stream.close_queue();
1042 }
1043 RxStreamState::Async(stream) => {
1044 stream.close_queue();
1045 }
1046 }
1047 }
1048}
1049
1050impl Drop for RxStream {
1051 fn drop(&mut self) {
1052 #[cfg(not(target_arch = "wasm32"))]
1053 if let Ok(Some(mut transition)) = self.radio.begin_stop_on_drop_blocking(Direction::Rx) {
1054 self.close_queue_on_drop();
1055 let _ = transition.finish_blocking();
1056 }
1057 #[cfg(target_arch = "wasm32")]
1058 {
1059 self.abandon_webusb_queue();
1060 let radio = std::sync::Arc::clone(&self.radio);
1061 wasm_bindgen_futures::spawn_local(async move {
1062 if let Ok(Some(mut transition)) = radio.begin_stop_async(Direction::Rx).await {
1063 let _ = transition.finish_async().await;
1064 }
1065 });
1066 }
1067 }
1068}
1069
1070#[must_use = "TX streams retain the device's exclusive TX stream claim until dropped"]
1075pub struct TxStream {
1076 radio: std::sync::Arc<LifecycleController>,
1077 transport: Option<TxTransport>,
1078 style: Option<ExecutionStyle>,
1079 state: TxStreamState,
1080 failed: bool,
1081 _claim: StreamClaim,
1082}
1083
1084#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1085enum TxStreamState {
1086 Dormant,
1087 Active,
1088 Recovery,
1089}
1090
1091impl TxStream {
1092 fn new(radio: std::sync::Arc<LifecycleController>, claim: StreamClaim) -> Self {
1093 Self {
1094 radio,
1095 transport: None,
1096 style: None,
1097 state: TxStreamState::Dormant,
1098 failed: false,
1099 _claim: claim,
1100 }
1101 }
1102
1103 pub fn start(&mut self) -> impl MaybeFuture<Output = Result<()>> + '_ {
1109 TxStartOperation { stream: self }
1110 }
1111
1112 pub fn write<'a>(
1119 &'a mut self,
1120 samples: &'a [Complex32],
1121 timeout: Option<Duration>,
1122 end_burst: bool,
1123 ) -> impl MaybeFuture<Output = Result<usize>> + 'a {
1124 TxWriteOperation {
1125 stream: self,
1126 samples,
1127 timeout,
1128 end_burst,
1129 }
1130 }
1131
1132 pub fn stop(&mut self) -> impl MaybeFuture<Output = Result<TxStreamingStats>> + '_ {
1144 TxStopOperation { stream: self }
1145 }
1146
1147 #[cfg(not(target_arch = "wasm32"))]
1148 fn start_blocking(&mut self) -> Result<()> {
1149 if self.failed {
1150 return Err(Error::stream_closed(
1151 "TX stream failed; drop it and create a new TX stream",
1152 ));
1153 }
1154 match self.state {
1155 TxStreamState::Active => return Ok(()),
1156 TxStreamState::Recovery => return Err(Error::Busy),
1157 TxStreamState::Dormant => {}
1158 }
1159 let mut transition = self.radio.begin_tx_start_blocking()?;
1160 let transport = transition.finish_blocking()?;
1161 self.transport = Some(transport);
1162 self.style = Some(ExecutionStyle::Blocking);
1163 self.state = TxStreamState::Active;
1164 Ok(())
1165 }
1166
1167 async fn start_async(&mut self) -> Result<()> {
1168 if self.failed {
1169 return Err(Error::stream_closed(
1170 "TX stream failed; drop it and create a new TX stream",
1171 ));
1172 }
1173 match self.state {
1174 TxStreamState::Active => return Ok(()),
1175 TxStreamState::Recovery => return Err(Error::Busy),
1176 TxStreamState::Dormant => {}
1177 }
1178 let mut transition = self.radio.begin_tx_start_async().await?;
1179 let transport = transition.finish_async().await?;
1180 self.transport = Some(transport);
1181 self.style = Some(ExecutionStyle::Async);
1182 self.state = TxStreamState::Active;
1183 Ok(())
1184 }
1185
1186 #[cfg(not(target_arch = "wasm32"))]
1187 fn write_blocking(
1188 &mut self,
1189 samples: &[Complex32],
1190 timeout: Duration,
1191 end_burst: bool,
1192 ) -> Result<usize> {
1193 match self.state {
1194 TxStreamState::Active => {}
1195 TxStreamState::Dormant | TxStreamState::Recovery => {
1196 return Err(Error::stream_closed("TX stream is not started"));
1197 }
1198 }
1199 if self.style != Some(ExecutionStyle::Blocking) {
1200 return Err(Error::Busy);
1201 }
1202 let result = self
1203 .transport
1204 .as_mut()
1205 .ok_or(Error::stream_closed("TX stream has no transport"))?
1206 .write_blocking(samples, timeout, end_burst);
1207 if let Err(error) = result {
1208 self.failed = true;
1209 self.state = TxStreamState::Recovery;
1210 self.radio.mark_stream_failed(Direction::Tx);
1211 return match self.stop_blocking() {
1212 Ok(_) => Err(error),
1213 Err(cleanup_error) => Err(cleanup_error.at("turn radio off after TX failure")),
1214 };
1215 }
1216 result
1217 }
1218
1219 async fn write_async(&mut self, samples: &[Complex32], end_burst: bool) -> Result<usize> {
1220 match self.state {
1221 TxStreamState::Active => {}
1222 TxStreamState::Dormant | TxStreamState::Recovery => {
1223 return Err(Error::stream_closed("TX stream is not started"));
1224 }
1225 }
1226 if self.style != Some(ExecutionStyle::Async) {
1227 return Err(Error::Busy);
1228 }
1229 let result = match self.transport.as_mut() {
1230 Some(transport) => transport.write_async(samples, end_burst).await,
1231 None => Err(Error::stream_closed("TX stream has no transport")),
1232 };
1233 if let Err(error) = result {
1234 self.failed = true;
1235 self.state = TxStreamState::Recovery;
1236 self.radio.mark_stream_failed(Direction::Tx);
1237 return match self.stop_async().await {
1238 Ok(_) => Err(error),
1239 Err(cleanup_error) => Err(cleanup_error.at("turn radio off after TX failure")),
1240 };
1241 }
1242 result
1243 }
1244
1245 #[cfg(not(target_arch = "wasm32"))]
1246 fn stop_blocking(&mut self) -> Result<TxStreamingStats> {
1247 let transition = self.radio.begin_stop_blocking(Direction::Tx)?;
1248 self.state = TxStreamState::Recovery;
1249 let (stats, drain_error) = if let Some(transport) = self.transport.as_mut() {
1250 match transport.flush_and_drain_blocking() {
1251 Ok(()) => (transport.stats(), None),
1252 Err(error) => {
1253 self.failed = true;
1254 transport.mark_unusable();
1255 (transport.stats(), Some(error))
1256 }
1257 }
1258 } else {
1259 (TxStreamingStats::default(), None)
1260 };
1261 if let Some(mut transition) = transition
1262 && let Err(error) = transition.finish_blocking()
1263 {
1264 self.state = TxStreamState::Recovery;
1265 return Err(match drain_error {
1266 Some(_) => error.at("turn radio off after TX drain failure"),
1267 None => error,
1268 });
1269 }
1270 if let Some(transport) = self.transport.take()
1271 && transport.is_reusable()
1272 {
1273 self.radio.return_tx_transport(transport);
1274 }
1275 self.state = TxStreamState::Dormant;
1276 drain_error.map_or(Ok(stats), Err)
1277 }
1278
1279 async fn stop_async(&mut self) -> Result<TxStreamingStats> {
1280 let transition = self.radio.begin_stop_async(Direction::Tx).await?;
1281 self.state = TxStreamState::Recovery;
1282 let (stats, drain_error) = if let Some(transport) = self.transport.as_mut() {
1283 match transport.flush_and_drain_async().await {
1284 Ok(()) => (transport.stats(), None),
1285 Err(error) => {
1286 self.failed = true;
1287 transport.mark_unusable();
1288 (transport.stats(), Some(error))
1289 }
1290 }
1291 } else {
1292 (TxStreamingStats::default(), None)
1293 };
1294 if let Some(mut transition) = transition
1295 && let Err(error) = transition.finish_async().await
1296 {
1297 self.state = TxStreamState::Recovery;
1298 return Err(match drain_error {
1299 Some(_) => error.at("turn radio off after TX drain failure"),
1300 None => error,
1301 });
1302 }
1303 if let Some(transport) = self.transport.take()
1304 && transport.is_reusable()
1305 {
1306 self.radio.return_tx_transport(transport);
1307 }
1308 self.state = TxStreamState::Dormant;
1309 drain_error.map_or(Ok(stats), Err)
1310 }
1311
1312 #[cfg(not(target_arch = "wasm32"))]
1313 fn stop_on_drop_blocking(&mut self) {
1314 let Ok(Some(mut transition)) = self.radio.begin_stop_on_drop_blocking(Direction::Tx) else {
1315 return;
1316 };
1317 let drain_succeeded = self
1318 .transport
1319 .as_mut()
1320 .map(|transport| transport.flush_and_drain_blocking().is_ok())
1321 .unwrap_or(true);
1322 if transition.finish_blocking().is_ok()
1323 && drain_succeeded
1324 && let Some(transport) = self.transport.take()
1325 && transport.is_reusable()
1326 {
1327 self.radio.return_tx_transport(transport);
1328 }
1329 }
1330}
1331
1332impl Drop for TxStream {
1333 fn drop(&mut self) {
1334 #[cfg(not(target_arch = "wasm32"))]
1335 self.stop_on_drop_blocking();
1336 #[cfg(target_arch = "wasm32")]
1337 {
1338 let radio = std::sync::Arc::clone(&self.radio);
1339 let mut transport = self.transport.take();
1340 wasm_bindgen_futures::spawn_local(async move {
1341 if let Ok(Some(mut transition)) = radio.begin_stop_async(Direction::Tx).await {
1342 let drain_succeeded = match transport.as_mut() {
1343 Some(transport) => transport.flush_and_drain_async().await.is_ok(),
1344 None => true,
1345 };
1346 if transition.finish_async().await.is_ok()
1347 && drain_succeeded
1348 && let Some(transport) = transport.take()
1349 && transport.is_reusable()
1350 {
1351 radio.return_tx_transport(transport);
1352 }
1353 }
1354 });
1355 }
1356 }
1357}
1358
1359struct RxStartOperation<'a> {
1360 stream: &'a mut RxStream,
1361}
1362
1363impl<'a> IntoFuture for RxStartOperation<'a> {
1364 type Output = Result<()>;
1365 type IntoFuture = BoxFuture<'a, Result<()>>;
1366
1367 fn into_future(self) -> Self::IntoFuture {
1368 Box::pin(async move { self.stream.start_async().await })
1369 }
1370}
1371
1372impl MaybeFuture for RxStartOperation<'_> {
1373 #[cfg(not(target_arch = "wasm32"))]
1374 fn wait(self) -> Self::Output {
1375 self.stream.start_blocking()
1376 }
1377}
1378
1379struct RxStopOperation<'a> {
1380 stream: &'a mut RxStream,
1381}
1382
1383impl<'a> IntoFuture for RxStopOperation<'a> {
1384 type Output = Result<StreamingStats>;
1385 type IntoFuture = BoxFuture<'a, Result<StreamingStats>>;
1386
1387 fn into_future(self) -> Self::IntoFuture {
1388 Box::pin(async move { self.stream.stop_async().await })
1389 }
1390}
1391
1392impl MaybeFuture for RxStopOperation<'_> {
1393 #[cfg(not(target_arch = "wasm32"))]
1394 fn wait(self) -> Self::Output {
1395 self.stream.stop_blocking()
1396 }
1397}
1398
1399struct TxStartOperation<'a> {
1400 stream: &'a mut TxStream,
1401}
1402
1403impl<'a> IntoFuture for TxStartOperation<'a> {
1404 type Output = Result<()>;
1405 type IntoFuture = BoxFuture<'a, Result<()>>;
1406
1407 fn into_future(self) -> Self::IntoFuture {
1408 Box::pin(async move { self.stream.start_async().await })
1409 }
1410}
1411
1412impl MaybeFuture for TxStartOperation<'_> {
1413 #[cfg(not(target_arch = "wasm32"))]
1414 fn wait(self) -> Self::Output {
1415 self.stream.start_blocking()
1416 }
1417}
1418
1419struct TxWriteOperation<'a> {
1420 stream: &'a mut TxStream,
1421 samples: &'a [Complex32],
1422 timeout: Option<Duration>,
1423 end_burst: bool,
1424}
1425
1426impl<'a> IntoFuture for TxWriteOperation<'a> {
1427 type Output = Result<usize>;
1428 type IntoFuture = BoxFuture<'a, Result<usize>>;
1429
1430 fn into_future(self) -> Self::IntoFuture {
1431 let _ = self.timeout;
1432 Box::pin(async move { self.stream.write_async(self.samples, self.end_burst).await })
1433 }
1434}
1435
1436impl MaybeFuture for TxWriteOperation<'_> {
1437 #[cfg(not(target_arch = "wasm32"))]
1438 fn wait(self) -> Self::Output {
1439 self.stream.write_blocking(
1440 self.samples,
1441 self.timeout.unwrap_or(Duration::MAX),
1442 self.end_burst,
1443 )
1444 }
1445}
1446
1447struct TxStopOperation<'a> {
1448 stream: &'a mut TxStream,
1449}
1450
1451impl<'a> IntoFuture for TxStopOperation<'a> {
1452 type Output = Result<TxStreamingStats>;
1453 type IntoFuture = BoxFuture<'a, Result<TxStreamingStats>>;
1454
1455 fn into_future(self) -> Self::IntoFuture {
1456 Box::pin(async move { self.stream.stop_async().await })
1457 }
1458}
1459
1460impl MaybeFuture for TxStopOperation<'_> {
1461 #[cfg(not(target_arch = "wasm32"))]
1462 fn wait(self) -> Self::Output {
1463 self.stream.stop_blocking()
1464 }
1465}
1466
1467struct ReadOperation<'a> {
1468 stream: &'a mut RxStream,
1469 out: &'a mut [Complex32],
1470 timeout: Option<Duration>,
1471}
1472
1473impl<'a> IntoFuture for ReadOperation<'a> {
1474 type Output = Result<usize>;
1475 type IntoFuture = BoxFuture<'a, Result<usize>>;
1476
1477 fn into_future(self) -> Self::IntoFuture {
1478 let _ = self.timeout;
1479 Box::pin(async move { self.stream.read_async(self.out).await })
1480 }
1481}
1482
1483impl MaybeFuture for ReadOperation<'_> {
1484 #[cfg(not(target_arch = "wasm32"))]
1485 fn wait(self) -> Self::Output {
1486 self.stream
1487 .read_blocking(self.out, self.timeout.unwrap_or(Duration::MAX))
1488 }
1489}
1490
1491struct DeviceShutdownOperation<'a> {
1492 device: &'a mut Device,
1493}
1494
1495impl<'a> IntoFuture for DeviceShutdownOperation<'a> {
1496 type Output = Result<()>;
1497 type IntoFuture = BoxFuture<'a, Result<()>>;
1498
1499 fn into_future(self) -> Self::IntoFuture {
1500 Box::pin(async move { self.device.shutdown_async().await })
1501 }
1502}
1503
1504impl MaybeFuture for DeviceShutdownOperation<'_> {
1505 #[cfg(not(target_arch = "wasm32"))]
1506 fn wait(self) -> Self::Output {
1507 self.device.shutdown_blocking()
1508 }
1509}