1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//! Tektronix TekHSI gRPC client with strict validation and waveform decoding.
//!
//! This crate connects to HSI-compatible Tektronix oscilloscopes, validates waveform headers, and decodes
//! analog/digital/IQ data into typed waveforms.
//!
//! - Tektronix TekHSI gRPC client for HSI-compatible oscilloscopes
//! - Strict waveform header validation that rejects invalid/unsupported data
//! - High-throughput acquisition with parallel download and decode loops
//! - Decodes analog (I8/I16/F32/F64), digital (I8/I16), and IQ waveforms
//! - FFT utilities with calibrated output for frequency analysis (requires `fft` feature)
//! - Streaming acquisitions via broadcast channels
//!
//! # Creating a Client
//!
//! Connect to a TekHSI server using [`TekHsiClient::connect`] with the scope's address.
//!
//! ```no_run
//! # use tekhsi_rs::TekHsiClient;
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! let client = TekHsiClient::connect("tek-scope-address:5000").await?;
//! # Ok(())
//! # }
//! ```
//!
//! - Establishes a gRPC channel to the specified address
//! - Performs a handshake with the scope's TekHSI service
//! - All Tektronix scopes tested allow a **single connection** at a time
//! - Attempting to create a second connection to the same scope returns [`errors::ConnectionError::ScopeBusy`] error
//!
//! # Listing Available Symbols
//!
//! Query the scope for available channels using [`TekHsiClient::list_available_symbols`].
//!
//! ```no_run
//! # use tekhsi_rs::TekHsiClient;
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! # let client = TekHsiClient::connect("tek-scope-address:5000").await?;
//! let symbols = client.list_available_symbols().await?;
//! println!("Available channels: {:?}", symbols);
//! # Ok(())
//! # }
//! ```
//!
//! - Returns symbols normalized to lowercase (e.g., "ch1", "ch2_iq", "ch3_dall")
//! - List of available symbols depends on which channels and modes are currently enabled on the scope
//!
//! # Subscribing
//!
//! After connecting, start streaming data with [`TekHsiClient::subscribe`].
//!
//! ```no_run
//! # use tekhsi_rs::{SubscribeOptions, TekHsiClient};
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! # let client = TekHsiClient::connect("tek-scope-address:5000").await?;
//! let symbols = client.list_available_symbols().await?;
//! let mut rx = client.subscribe(symbols, SubscribeOptions::default())?;
//! # Ok(())
//! # }
//! ```
//!
//! - Only **one subscription** can be active per client
//! - Acquisitions are delivered via an async broadcast channel
//! - Must provide at least one symbol (returns [`errors::SubscriptionUpdateError::EmptySymbols`] if empty)
//! - Attempting to start a second subscription returns [`errors::SubscriptionError::AlreadyActive`]
//!
//! # Updating Subscriptions
//!
//! Dynamically change the active symbols without reconnecting by calling [`TekHsiClient::update_symbols`].
//!
//! ```no_run
//! # use tekhsi_rs::{SubscribeOptions, TekHsiClient};
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! # let client = TekHsiClient::connect("tek-scope-address:5000").await?;
//! let symbols = vec!["ch1".to_string()];
//! let mut rx = client.subscribe(symbols, SubscribeOptions::default())?;
//! // ... receive ch1 acquisitions from `rx` ...
//! let new_symbols = vec!["ch2".to_string(), "ch3".to_string()];
//! client.update_symbols(new_symbols)?; // Update to different symbols
//! // ... receive ch2 and ch3 acquisitions from `rx` ...
//!
//! # Ok(())
//! # }
//! ```
//!
//! - Must provide at least one symbol (returns [`errors::SubscriptionUpdateError::EmptySymbols`] if empty)
//! - Requires an active subscription (returns [`errors::SubscriptionUpdateError::NotActive`] if no subscription exists)
//! - There might still be acquisitions in the channel from the previous subscription
//!
//! # Acquisitions
//!
//! Each [`data::Acquisition`] represents a single acquisition across all subscribed symbols.
//! It is recommended that you access a channel's waveform by symbol name using [`data::Acquisition::get_by_symbol`] for example:
//! ```no_run
//! # use tekhsi_rs::{SubscribeOptions, TekHsiClient};
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! # let client = TekHsiClient::connect("tek-scope-address:5000").await?;
//! let mut rx = client.subscribe(vec!["ch1".to_string()], SubscribeOptions::default())?;
//! while let Ok(acquisition) = rx.recv().await {
//! let ch1_waveform = acquisition.get_by_symbol("ch1").unwrap();
//! // ... use the ChannelData in ch1_waveform ...
//! }
//! # Ok(())
//! # }
//! ```
//!
//! [`data::ChannelData`] variants:
//! - [`data::ChannelData::Waveform`]: Successfully decoded waveform with acquisition ID, symbol, header, and waveform data
//! - [`data::ChannelData::DecodeError`]: Failed decoding with symbol, header, and error details
//! - [`data::ChannelData::AcquisitionError`]: Failed acquisition with symbol and error details
//!
//! [`data::Waveform`] variants:
//! - [`data::Waveform::Analog`]: Analog voltage samples (I8/I16/F32/F64) from oscilloscope analog channels
//! - [`data::Waveform::Digital`]: Digital bit samples (I8/I16) from digital inputs or logic probes
//! - [`data::Waveform::Iq`]: Complex IQ samples (I8/I16/I32) from spectrum or RF analysis
//!
//! Reading acquisition data:
//! - Each waveform type provides iterator-based `iter_normalized_*` helpers for calibrated values
//! - I highly recommend using these functions unless you have a very good reason not to
//! - There is a `as_scope_digital8()` helper for [`data::Waveform::Digital`] that returns an 8-channel logic probe bit mapping (tested with a TLP058)
//!
//! # Disconnecting
//!
//! Explicitly disconnect to stop acquisition and clean up resources:
//!
//! ```no_run
//! # use tekhsi_rs::TekHsiClient;
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! let client = TekHsiClient::connect("tek-scope-address:5000").await?;
//! // ... use client ...
//! client.disconnect().await?;
//! # Ok(())
//! # }
//! ```
//!
//! - Stops all active acquisition loops via cancellation tokens
//! - Waits 500ms for the scope to exit acquisition mode
//! - Sends a disconnect request to the scope
//!
//! The client also implements [`Drop`] for automatic cleanup
//!
//! # FFT Analysis
//!
//! The [`fft::FftWaveform`] trait is implemented on [`data::Waveform::Analog`] and [`data::Waveform::Iq`]
//! for convenience.
//!
//! Use `fft_real` or `fft_complex` to compute FFT bins and then
//! convert to dBm:
//!
//! ```no_run
//! # #[cfg(feature = "fft")]
//! # mod ensure_fft {
//! # use tracing::warn;
//! # use tekhsi_rs::{FftWaveform, SubscribeOptions, TekHsiClient, data::Waveform, data::ChannelData};
//! # use tekhsi_rs::fft::{ComplexFftWaveform, FftResult, FftWindow};
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! # let client = TekHsiClient::connect("tek-scope-address:5000").await?;
//! # let symbols = client.list_available_symbols().await?;
//! let mut rx = client.subscribe(symbols, SubscribeOptions::default())?;
//! while let Ok(acquisition) = rx.recv().await {
//! for channel in acquisition.data.iter() {
//! match channel {
//! ChannelData::Waveform { waveform, .. } => match waveform {
//! Waveform::Analog(analog) => {
//! let mut fft = analog.fft_mag(FftWindow::Rectangular, None);
//! let _fft_dbm = fft.as_dbm_single_sided(Some(50.0));
//! }
//! Waveform::Iq(iq) => {
//! let mut fft = iq.fft_complex(FftWindow::Rectangular, None);
//! let _fft_dbm = fft.as_dbm(Some(50.0));
//! }
//! Waveform::Digital(_digital) => {}
//! },
//! _ => { warn!("Acquisition or decode failed!") }
//! }
//! }
//! }
//! # client.disconnect().await?;
//! # Ok(())
//! # }
//! # }
//! ```
//!
//! Notes:
//! - FFT functionality **requires enabling** the `fft` feature flag.
//! - If you are repeatedly computing FFTs the consider the `_cached` variants
//! - Uses the `rustfft` package for CPU-bound FFT computations (no GPU acceleration)
//! - Calibrated output requires optional impedance parameter (typically 50Ω)
//! - Provided as a convenience helper for common frequency analysis tasks
pub use ;
pub use ;
pub use SmolStr;
pub use WaveformHeader;
pub use cratedecode_waveform_chunks;
pub use crateConnectClient;
pub use crate;