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`:

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

## Quick Start

### Basic Connection and Data Reading

```rust
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

```rust
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

```rust
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

```rust
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

```rust
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:

```rust
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:

```bash
cargo test
```

Run tests with output:

```bash
cargo test -- --nocapture
```

Run clippy for additional code quality checks:

```bash
cargo clippy
```

Format code:

```bash
cargo fmt
```

## Examples

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