fleascope-rs 0.2.0

Library to interact with a Fleascope
Documentation

FleaScope RS

A Rust library for configuring triggers and communicating with FleaScope oscilloscope devices.

This library is a complete port of the Python pyFleaScope library to Rust, providing idiomatic Rust APIs for device control, data acquisition, and calibration management.

Features

  • Cross-platform device discovery: Uses serialport for finding FleaScope devices across Windows, Linux, and macOS
  • Trigger configuration: Digital and analog triggers with builder patterns and type safety
  • Data acquisition: Raw oscilloscope data reading with automatic time indexing
  • Probe calibration: Automated zero and 3.3V calibration procedures
  • Calibration management: Read/write probe calibrations from/to device flash memory
  • DataFrame output: Uses polars for efficient data handling (replacing pandas)
  • Type safety: Strong typing and comprehensive error handling throughout
  • Memory efficiency: Iterator-based device discovery for reduced memory usage
  • Comprehensive testing: Full test coverage with both unit tests and documentation tests

Installation

Add this to your Cargo.toml:

[dependencies]
fleascope-rs = { path = "path/to/fleascope-rs" }

Quick Start

Basic Connection and Data Reading

use fleascope_rs::{FleaScope, Waveform};
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to any available FleaScope device
    let mut scope = FleaScope::connect(None, None, true)?;
    
    // Set up signal generator
    scope.set_waveform(Waveform::Sine, 1000)?; // 1kHz sine wave
    
    // Read data using the 1x probe with default auto trigger
    let data = scope.read_x1(Duration::from_millis(10), None, None)?;
    println!("Captured {} samples", data.height());
    
    Ok(())
}

Device Discovery

use fleascope_rs::FleaConnector;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // List available devices (memory efficient iterator)
    let devices = FleaConnector::get_available_devices(None)?;
    for device in devices.take(3) { // Only process first 3 devices
        println!("Found device: {} at {}", device.name, device.port);
    }
    
    // Or get all devices as a Vec (if you need to access multiple times)
    let devices_vec = FleaConnector::get_available_devices_vec(None)?;
    println!("Total devices: {}", devices_vec.len());
    
    Ok(())
}

Digital Triggers

use fleascope_rs::{FleaScope, DigitalTrigger, BitState};
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut scope = FleaScope::connect(None, None, true)?;
    
    // Create a digital trigger that starts capturing when bit0 is high and bit1 is low
    let trigger = DigitalTrigger::start_capturing_when()
        .bit0(BitState::High)
        .bit1(BitState::Low)
        .bit2(BitState::DontCare)  // Ignore bit2
        .starts_matching();        // Start capturing when pattern matches
    
    // Read data with digital trigger
    let data = scope.read_x1_digital(
        Duration::from_millis(5), 
        Some(trigger),
        None
    )?;
    
    println!("Captured {} samples with digital trigger", data.height());
    Ok(())
}

Analog Triggers

use fleascope_rs::{FleaScope, AnalogTrigger};
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut scope = FleaScope::connect(None, None, true)?;
    
    // Create an analog trigger that starts on rising edge at 1.5V
    let trigger = AnalogTrigger::start_capturing_when()
        .rising_edge(1.5);
    
    // Read data with analog trigger using 10x probe
    let data = scope.read_x10_analog(
        Duration::from_millis(5),
        Some(trigger),
        Some(Duration::from_micros(100)) // 100μs delay
    )?;
    
    println!("Captured {} samples with analog trigger", data.height());
    Ok(())
}

Calibration

use fleascope_rs::FleaScope;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut scope = FleaScope::connect(None, None, true)?;
    
    // Calibrate the 1x probe
    println!("Connect 1x probe to ground and press Enter...");
    std::io::stdin().read_line(&mut String::new())?;
    let zero_value = scope.calibrate_x1_zero()?;
    println!("Zero calibration: {}", zero_value);
    
    println!("Connect 1x probe to 3.3V and press Enter...");
    std::io::stdin().read_line(&mut String::new())?;
    let full_scale = scope.calibrate_x1_3v3()?;
    println!("Full scale calibration: {}", full_scale);
    
    // Save calibration to device flash
    scope.write_x1_calibration_to_flash()?;
    println!("Calibration saved to flash");
    
    Ok(())
}

API Reference

Core Types

  • FleaScope: Main oscilloscope control interface
  • FleaConnector: Device discovery and connection management
  • FleaTerminal: Low-level serial communication
  • FleaProbe: Probe calibration and voltage conversion
  • DigitalTrigger: Digital pattern triggers
  • AnalogTrigger: Analog level/edge triggers

Data Acquisition Methods

  • read_x1(): Read with 1x probe and auto trigger
  • read_x10(): Read with 10x probe and auto trigger
  • read_x1_digital(): Read with 1x probe and digital trigger
  • read_x10_digital(): Read with 10x probe and digital trigger
  • read_x1_analog(): Read with 1x probe and analog trigger
  • read_x10_analog(): Read with 10x probe and analog trigger

Trigger Configuration

Digital triggers support 9 bits (bit0-bit8) with states:

  • BitState::High: Bit must be high
  • BitState::Low: Bit must be low
  • BitState::DontCare: Ignore this bit

Trigger behaviors:

  • starts_matching(): Start when pattern first matches
  • stops_matching(): Start when pattern stops matching
  • is_matching(): Continuously capture while matching

Analog triggers support:

  • rising_edge(voltage): Trigger on rising edge
  • falling_edge(voltage): Trigger on falling edge
  • level(voltage): Trigger on level crossing

Error Handling

The library uses thiserror for comprehensive error handling:

use fleascope_rs::{FleaScope, FleaScopeError};

match FleaScope::connect(None, None, true) {
    Ok(scope) => println!("Connected successfully"),
    Err(FleaScopeError::Connector(e)) => println!("Connection failed: {}", e),
    Err(FleaScopeError::SerialTerminal(e)) => println!("Serial error: {}", e),
    Err(e) => println!("Other error: {}", e),
}

Data Output

All data acquisition methods return a polars::DataFrame with columns:

  • time: Time in seconds from trigger
  • bnc: Voltage values (converted from raw ADC values)
  • bitmap: Raw digital bit values (hex string)

For digital data analysis, use FleaScope::extract_bits() to convert the bitmap column into individual bit columns (bit_0, bit_1, etc.).

Platform Support

This library supports Windows, Linux, and macOS through the serialport crate. Device discovery automatically handles platform-specific USB device enumeration.

Dependencies

  • serialport: Cross-platform serial port communication
  • polars: High-performance DataFrame library
  • csv: CSV parsing for data acquisition
  • chrono: Time and duration handling
  • thiserror: Error handling
  • log: Logging support

Differences from Python Version

  • Memory efficiency: Iterator-based device discovery
  • Type safety: Strong typing prevents many runtime errors
  • Performance: Rust's zero-cost abstractions and polars for data handling
  • Error handling: Comprehensive error types with context
  • Cross-platform: Better cross-platform device discovery
  • API consistency: More consistent method naming and parameter ordering

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Run cargo test and cargo clippy
  6. Submit a pull request

License

This project maintains the same license as the original Python version.

Testing

Run the full test suite:

cargo test

Run tests with output:

cargo test -- --nocapture

Run clippy for additional code quality checks:

cargo clippy

Format code:

cargo fmt

Examples

See the examples/ directory for more comprehensive usage examples and real-world scenarios.