1#![forbid(unsafe_code)]
2
3use crossbeam::channel::{bounded, Sender};
52use events::StreamingEvents;
53pub use events::{NewDataHandler, RawChannelDataBlock, StreamingEvent};
54use parking_lot::RwLock;
55use pico_common::{
56 ChannelConfig, PicoChannel, PicoCoupling, PicoRange, PicoResult, PicoStatus, SampleConfig,
57};
58use pico_device::PicoDevice;
59use std::{
60 collections::HashMap,
61 fmt,
62 sync::Arc,
63 thread::{self, JoinHandle},
64 time::Duration,
65};
66use tracing::*;
67
68mod events;
69
70#[cfg_attr(feature = "serde", derive(serde::Serialize))]
71#[derive(Debug, Clone, Copy)]
72enum Target {
73 Closed,
74 Open,
75 Streaming { requested_sample_rate: u32 },
76}
77
78#[cfg_attr(feature = "serde", derive(serde::Serialize))]
79#[derive(Clone)]
80struct LockedTarget(Arc<RwLock<Target>>);
81
82impl LockedTarget {
83 pub fn new(target: Target) -> Self {
84 LockedTarget(Arc::new(RwLock::new(target)))
85 }
86
87 pub fn set(&self, new: Target) {
88 *self.0.write() = new;
89 }
90
91 pub fn get(&self) -> Target {
92 *self.0.read()
93 }
94}
95
96impl fmt::Debug for LockedTarget {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.write_fmt(format_args!("{:?}", self.0.try_read()))
99 }
100}
101
102type BufferMap = HashMap<PicoChannel, Arc<RwLock<Vec<i16>>>>;
103
104#[cfg_attr(feature = "serde", derive(serde::Serialize))]
105#[derive(Clone)]
106enum State {
107 Closed,
108 Open {
109 handle: i16,
110 },
111 Streaming {
112 handle: i16,
113 actual_sample_rate: u32,
114 #[cfg_attr(feature = "serde", serde(skip))]
115 buffers: BufferMap,
116 },
117}
118
119impl PartialEq for State {
120 fn eq(&self, other: &Self) -> bool {
121 matches!(
122 (self, other),
123 (State::Closed, State::Closed)
124 | (State::Open { .. }, State::Open { .. })
125 | (State::Streaming { .. }, State::Streaming { .. })
126 )
127 }
128}
129
130impl fmt::Debug for State {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 State::Closed => f.debug_struct("Closed").finish(),
134 State::Open { handle } => f.debug_struct("Open").field("handle", handle).finish(),
135 State::Streaming {
136 handle,
137 actual_sample_rate,
138 ..
139 } => f
140 .debug_struct("Streaming")
141 .field("handle", handle)
142 .field("actual_sample_rate", actual_sample_rate)
143 .finish(),
144 }
145 }
146}
147
148#[cfg_attr(feature = "serde", derive(serde::Serialize))]
153#[derive(Clone)]
154pub struct PicoStreamingDevice {
155 device: PicoDevice,
156 target_state: LockedTarget,
157 current_state: Arc<RwLock<State>>,
158 enabled_channels: Arc<RwLock<HashMap<PicoChannel, ChannelConfig>>>,
159 #[cfg_attr(feature = "serde", serde(skip))]
160 background_handle: Option<Arc<BackgroundThreadHandle>>,
161 #[cfg_attr(feature = "serde", serde(skip))]
162 pub new_data: StreamingEvents,
163}
164
165impl fmt::Debug for PicoStreamingDevice {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 f.debug_struct("PicoStreamingDevice")
168 .field("device", &self.device)
169 .field("target_state", &self.target_state)
170 .field("current_state", &self.current_state.try_read())
171 .finish()
172 }
173}
174
175impl PartialEq for PicoStreamingDevice {
176 fn eq(&self, other: &Self) -> bool {
177 self.get_serial() == other.get_serial()
178 }
179}
180
181impl Eq for PicoStreamingDevice {}
182
183impl std::hash::Hash for PicoStreamingDevice {
184 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
185 self.get_serial().hash(state);
186 }
187}
188
189impl From<PicoDevice> for PicoStreamingDevice {
190 fn from(d: PicoDevice) -> Self {
191 PicoStreamingDevice::new(d)
192 }
193}
194
195impl PicoStreamingDevice {
196 fn new(device: PicoDevice) -> Self {
197 let (current_state, target_state) = match device.handle.lock().take() {
198 Some(handle) => (State::Open { handle }, Target::Open),
199 None => (State::Closed, Target::Closed),
200 };
201
202 let mut device = PicoStreamingDevice {
203 device,
204 target_state: LockedTarget::new(target_state),
205 current_state: Arc::new(RwLock::new(current_state)),
206 new_data: Default::default(),
207 enabled_channels: Default::default(),
208 background_handle: Default::default(),
209 };
210
211 device.start_background_thread();
212
213 device
214 }
215
216 pub fn get_serial(&self) -> String {
217 self.device.serial.to_string()
218 }
219
220 pub fn get_variant(&self) -> String {
221 self.device.variant.to_string()
222 }
223
224 pub fn enable_channel(&self, channel: PicoChannel, range: PicoRange, coupling: PicoCoupling) {
225 self.enabled_channels.write().insert(
226 channel,
227 ChannelConfig {
228 range,
229 coupling,
230 offset: 0.0,
231 },
232 );
233 }
234
235 pub fn disable_channel(&self, channel: PicoChannel) {
236 self.enabled_channels.write().remove(&channel);
237 }
238
239 pub fn get_channels(&self) -> Vec<PicoChannel> {
240 self.device.get_channels()
241 }
242
243 pub fn get_valid_ranges(&self, channel: PicoChannel) -> Option<Vec<PicoRange>> {
244 self.device.channel_ranges.get(&channel).cloned()
245 }
246
247 pub fn get_channel_config(&self, channel: PicoChannel) -> Option<ChannelConfig> {
248 self.enabled_channels.read().get(&channel).cloned()
249 }
250
251 #[tracing::instrument(level = "info")]
253 pub fn start(&self, requested_sample_rate: u32) -> PicoResult<u32> {
254 self.target_state.set(Target::Streaming {
257 requested_sample_rate,
258 });
259
260 let mut count = 0;
262 loop {
263 if let Err(e) = self.run_state() {
264 self.target_state.set(Target::Open);
265 return Err(e);
266 }
267
268 let current = self.current_state.read();
269 if let State::Streaming {
270 actual_sample_rate, ..
271 } = *current
272 {
273 return Ok(actual_sample_rate);
274 }
275
276 count += 1;
277
278 if count > 5 {
279 return Err(PicoStatus::TIMEOUT.into());
280 }
281 }
282 }
283
284 #[tracing::instrument(level = "info")]
286 pub fn stop(&self) {
287 self.target_state.set(Target::Open);
288 }
289
290 #[tracing::instrument(level = "info")]
292 pub fn close(&self) {
293 self.target_state.set(Target::Closed);
294 }
295
296 fn start_background_thread(&mut self) {
297 let (tx_terminate, rx_terminate) = bounded::<()>(0);
298
299 let handle = thread::Builder::new()
300 .name("Streaming background task".to_string())
301 .spawn({
302 let device = self.clone();
303 let mut wait_for_closed = false;
304
305 move || loop {
306 let next_wait = device
307 .run_state()
308 .unwrap_or_else(|_| Duration::from_millis(500));
309
310 if !wait_for_closed && rx_terminate.recv_timeout(next_wait).is_ok() {
311 device.close();
312 wait_for_closed = true;
313 }
314
315 if wait_for_closed {
316 if let State::Closed = *device.current_state.read() {
317 return;
318 }
319 }
320 }
321 })
322 .expect("Could not start thread");
323
324 self.background_handle = Some(BackgroundThreadHandle::new(tx_terminate, handle));
325 }
326
327 #[tracing::instrument(skip(self), level = "debug", err(Display))]
328 fn run_state(&self) -> PicoResult<Duration> {
329 let mut current_state = self.current_state.write();
330 let initial_state = current_state.clone();
331
332 let target = self.target_state.get();
333
334 let (next_state, next_duration) = match current_state.clone() {
335 State::Closed => match target {
336 Target::Closed => (State::Closed, Duration::from_millis(500)),
337 Target::Open | Target::Streaming { .. } => {
338 let handle = self.device.driver.open_unit(Some(&self.device.serial))?;
339 (State::Open { handle }, Duration::from_millis(1))
340 }
341 },
342 State::Open { handle } => match target {
343 Target::Closed => {
344 self.device.driver.close(handle)?;
345 (State::Closed, Duration::from_millis(500))
346 }
347 Target::Open => self.ping(handle),
348 Target::Streaming {
349 requested_sample_rate,
350 } => self.configure_and_start(handle, requested_sample_rate)?,
351 },
352 State::Streaming {
353 handle,
354 actual_sample_rate,
355 buffers,
356 } => match target {
357 Target::Closed | Target::Open => {
358 self.device.driver.stop(handle)?;
359 (State::Open { handle }, Duration::from_millis(1))
360 }
361 Target::Streaming { .. } => self.stream(handle, buffers, actual_sample_rate),
362 },
363 };
364
365 if initial_state != next_state {
366 info!("State changed '{:?}' > '{:?}'", initial_state, next_state);
367 }
368
369 *current_state = next_state;
370
371 Ok(next_duration)
372 }
373
374 fn ping(&self, handle: i16) -> (State, Duration) {
375 if self.device.driver.ping_unit(handle).is_err() {
376 let _ = self.device.driver.stop(handle);
377 let _ = self.device.driver.close(handle);
378
379 (State::Closed, Duration::from_millis(500))
380 } else {
381 (State::Open { handle }, Duration::from_millis(500))
382 }
383 }
384
385 #[tracing::instrument(skip(self), level = "debug")]
386 fn configure_and_start(
387 &self,
388 handle: i16,
389 samples_per_second: u32,
390 ) -> PicoResult<(State, Duration)> {
391 let mut buffers = HashMap::new();
392
393 let enabled_channels = self.enabled_channels.read();
394
395 let mut enabled_channel_count = 0;
396
397 for (channel, ranges) in &self.device.channel_ranges {
398 if ranges.is_empty() {
400 continue;
401 }
402
403 if let Some(config) = enabled_channels.get(channel) {
405 let buffer_size = samples_per_second as usize;
406
407 self.device
408 .driver
409 .enable_channel(handle, *channel, config)?;
410
411 let ch_buf = buffers
412 .entry(*channel)
413 .or_insert_with(|| Arc::new(RwLock::new(vec![0i16; buffer_size])));
414
415 self.device.driver.set_data_buffer(
416 handle,
417 *channel,
418 ch_buf.clone(),
419 buffer_size,
420 )?;
421
422 enabled_channel_count += 1;
423 } else {
424 self.device.driver.disable_channel(handle, *channel)?;
425 }
426 }
427
428 let target_config = SampleConfig::from_samples_per_second(samples_per_second);
429 let actual_sample_rate = self
430 .device
431 .driver
432 .start_streaming(handle, &target_config, enabled_channel_count)
433 .map(|sc| sc.samples_per_second())?;
434
435 Ok((
436 State::Streaming {
437 handle,
438 actual_sample_rate,
439 buffers,
440 },
441 Duration::from_millis(100),
442 ))
443 }
444
445 #[tracing::instrument(skip(self, buffers), level = "trace")]
446 fn stream(
447 &self,
448 handle: i16,
449 buffers: BufferMap,
450 actual_sample_rate: u32,
451 ) -> (State, Duration) {
452 let callback = |start_index, sample_count| {
453 let channels = self.enabled_channels.read();
454
455 let channels = channels
456 .iter()
457 .map(|(ch, config)| {
458 let ch_buf = buffers
459 .get(ch)
460 .expect("Channel is enabled but has no buffer")
461 .read();
462
463 (
464 *ch,
465 RawChannelDataBlock {
466 multiplier: config.range.get_max_scaled_value()
467 / self.device.max_adc_value as f64,
468 samples: ch_buf[start_index..(start_index + sample_count)].to_vec(),
469 },
470 )
471 })
472 .collect::<HashMap<_, _>>();
473
474 self.new_data.emit(StreamingEvent {
475 samples_per_second: actual_sample_rate,
476 length: sample_count,
477 channels,
478 });
479 };
480
481 let channels = buffers.keys().copied().collect::<Vec<_>>();
482
483 if let Err(error) =
484 self.device
485 .driver
486 .get_latest_streaming_values(handle, &channels, Box::new(callback))
487 {
488 if error.status == PicoStatus::WAITING_FOR_DATA_BUFFERS {
489 for (channel, buffer) in &buffers {
490 let len = { buffer.read().len() };
491 self.device
492 .driver
493 .set_data_buffer(handle, *channel, buffer.clone(), len)
494 .unwrap();
495 }
496
497 (
498 State::Streaming {
499 handle,
500 buffers,
501 actual_sample_rate,
502 },
503 Duration::from_millis(5),
504 )
505 } else {
506 warn!("Streaming stopped: '{:?}'", error);
507
508 let _ = self.device.driver.stop(handle);
509 let _ = self.device.driver.close(handle);
510
511 (State::Closed, Duration::from_millis(200))
512 }
513 } else {
514 (
515 State::Streaming {
516 handle,
517 actual_sample_rate,
518 buffers,
519 },
520 Duration::from_millis(50),
521 )
522 }
523 }
524}
525
526pub trait ToStreamDevice {
528 fn into_streaming_device(self) -> PicoStreamingDevice;
529}
530
531impl ToStreamDevice for PicoDevice {
532 fn into_streaming_device(self) -> PicoStreamingDevice {
533 PicoStreamingDevice::new(self)
534 }
535}
536
537pub struct BackgroundThreadHandle {
538 tx_terminate: Sender<()>,
539 handle: Option<JoinHandle<()>>,
540}
541
542impl BackgroundThreadHandle {
543 pub fn new(tx_terminate: Sender<()>, handle: JoinHandle<()>) -> Arc<Self> {
544 Arc::new(BackgroundThreadHandle {
545 tx_terminate,
546 handle: Some(handle),
547 })
548 }
549}
550
551impl Drop for BackgroundThreadHandle {
552 #[tracing::instrument(skip(self), level = "debug")]
553 fn drop(&mut self) {
554 self.tx_terminate.send(()).unwrap();
555
556 self.handle.take().unwrap().join().unwrap();
557 }
558}