---
jupytext:
  formats: md:myst
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.11.5
kernelspec:
  display_name: Python 3
  language: python
  name: python3
---

# Capture Configuration

The `capture_config` module provides classes for configuring capture operations on the Saleae Logic MSO device.

A `CaptureConfig` object must be built and passed to `MSO.capture()` to initiate an actual capture on the hardware. There are
a lot of different knobs that control a capture, and the class `CaptureConfig` handles most of the validation of those various
settings.

When a capture happens, sometimes values on the `CaptureConfig` object change slightly, i.e. to reflect the actual sample rate that
was used during the capture (not all sample rates are supported, some interpolation happens, especially at high sample rates). A `Capture`
object always contains the `CaptureConfig` objects that best represents the capture settings along with it.

Not all settings need to be set explicitly to get started, many will get sensible defaults (or device defaults) when a capture is taken.

Note: capture durations are not exact. The Logic MSO will always give you at least the requested number of samples, but may give up to 255 more samples than requested due to it's truncation logic.

## Overview

This module defines the configuration objects that control:
- Which channels to capture (analog and digital)
- Sample rates and other analog settings
- Capture duration and trigger settings


## Classes

### `CaptureConfig`

```python
@dataclass
class CaptureConfig:
    enabled_channels: list[ChannelConfig]
    analog_settings: Optional[AnalogSettings] = None
    capture_settings: Optional[CaptureSettings] = None
```

The main configuration object for captures. A capture must have at least one enabled channel, and
should include either `analog_settings` or `capture_settings` if a capture other than the defaults
below is desired.

**Parameters:**
- `enabled_channels`: List of analog and digital channels to capture
- `analog_settings`: Optional analog capture settings (defaults to device maximum sample rate and DROP mode)
- `capture_settings`: Optional capture settings (defaults to 1ms timed capture)

**Methods:**

You likely won't need to use these, they are used behind the scenes to persist `CaptureConfig` objects
in human-readable JSON in capture directories.

- `to_dict`: Converts configuration to device format that the capture binary understands
  ```python
  def to_dict(self, output_dir: Path, mso_part_number: MsoPodPartNumber) -> Dict[str, Any]:
  ```

- `to_dir`: Writes a full configuration to several files in a given directory
  ```python
  def to_dir(self, output_dir: Path, mso_part_number: MsoPodPartNumber) -> Path:
  ```

- `from_dir`: Reads a configuration from a given directory
  ```python
  @classmethod
  def from_dir(cls, dir_path: Path) -> "CaptureConfig":
  ```

### `AnalogChannel`

```python
@dataclass
class AnalogChannel:
    channel: int
    name: Optional[str] = None
    center_voltage: float = 0.0
    voltage_range: float = 10.0
    probe_attenuation: ProbeAttenuation = ProbeAttenuation.PROBE_10X  # Default to the 10x probe included with the MSO
    coupling: Coupling = Coupling.DC
    bandwidth_mhz: BandwidthLimit = BandwidthLimit.MHz_350
```

Represents an analog channel configuration.

**Parameters:**
- `channel`: The channel number (0-based index)
- `name`: Optional descriptive name for the channel (defaults to "CH<n>_volts")
- `center_voltage`: Center voltage for the channel (default: 0.0V)
- `voltage_range`: Voltage range for the channel (default: 10.0V)
- `probe_attenuation`: Probe attenuation setting (default: 1x)
- `coupling`: Coupling type (default: DC)
- `bandwidth_mhz`: Bandwidth limit (default: 350MHz)

### `DigitalChannel`

```python
@dataclass
class DigitalChannel(ChannelConfig):
    channel: int
    name: Optional[str] = None
    port: Optional[int] = None
    threshold_volts: Optional[float] = None
    minimum_pulse_width_samples: Optional[int] = None
```

Represents a digital channel configuration.

**Parameters:**
- `channel`: The channel number (0-based index)
- `name`: Optional descriptive name for the channel (defaults to "P<port>_CH<channel>_logic")
- `port`: The port number for the digital channel (required)
- `threshold_volts`: Voltage threshold for digital signal detection (default: None, uses global threshold)
- `minimum_pulse_width_samples`: Minimum pulse width in samples to filter out noise (default: None)

**Note:** Digital channels require a port number to be specified. All digital channels on the same port share the same voltage threshold, so setting `threshold_volts` on one channel will apply to all channels on that port.

### `AnalogSettings`

```python
@dataclass
class AnalogSettings:
    downsample_mode: DownsampleMode = DownsampleMode.AVERAGE
    sample_rate: float = 1_600_000_000  # Defaults to 1.6 GSPS
```

Configures analog capture settings.

**Parameters:**
- `downsample_mode`: How to handle downsampling (default: AVERAGE)
- `sample_rate`: The sample rate in Hz (default: 1.6GS/s)

### `TimedCapture`

```python
@dataclass
class TimedCapture(CaptureSettings):
    capture_length_seconds: float
```

Configures a capture based on duration.

**Parameters:**
- `capture_length_seconds`: How long to capture in seconds

### `AnalogTriggered`

```python
@dataclass
class AnalogTriggered(CaptureSettings):
    trigger: EdgeTrigger
    pre_trigger_seconds: float = 0.0005
    post_trigger_seconds: float = 0.0005
    timeout_seconds: float = 1.0
```

Configures a capture triggered by an analog signal reaching a specific threshold. See the [trigger documentation](trigger.md)
for more information on configuring a trigger.

**Parameters:**
- `trigger`: The trigger settings
- `pre_trigger_seconds`: Time before trigger to capture (default: 0.5ms)
- `post_trigger_seconds`: Time after trigger to capture (default: 0.5ms)
- `timeout_seconds`: Maximum time to wait for trigger before throwing a timeout error (default: 1.0s)

## Enums

### `ProbeAttenuation`

If you are using the probes included with your Logic MSO, leave this attenuation setting at `Probe10x` which is
the default. Those probes attenuate the signal by 10x inherently. If you are connecting a signal directly to the
input of your Logic MSO with e.g. the BNC adapter and coaxial cable, then use the `Probe1x` setting to inform the
MSO it will be receiving more signal per ADC count.

```python
class ProbeAttenuation(Enum):
    PROBE_10X = "Probe10x"  # default, use this when using the probes included with MSO
    PROBE_1X = "Probe1x"
```

### `DownsampleMode`

```python
class DownsampleMode(Enum):
    DROP = "Drop"
    AVERAGE = "Average"
```

## Example Capture Configurations

```{code-cell} python
from pathlib import Path
from saleae import mso_api
from saleae.mso_api.part_number import MsoPodPartNumber as PN


# Capture 100ms of data in a timed capture on 2 analog channels and 4 digital ones
config1 = mso_api.CaptureConfig(
    enabled_channels=[
        mso_api.AnalogChannel(channel=0, name="clock", center_voltage=2.0, voltage_range=5.0),
        mso_api.AnalogChannel(channel=1, name="data", center_voltage=2.0, voltage_range=5.0),
        mso_api.DigitalChannel(port=0, channel=0, name="MISO", threshold_volts=1.5),
        mso_api.DigitalChannel(port=0, channel=1, name="MOSI"),
        mso_api.DigitalChannel(port=0, channel=2, name="CLK"),
        mso_api.DigitalChannel(port=0, channel=3, name="CS")
    ],
    analog_settings=mso_api.AnalogSettings(
        downsample_mode=mso_api.DownsampleMode.AVERAGE,
        sample_rate=100e6
    ),
    capture_settings=mso_api.TimedCapture(capture_length_seconds=0.1)
)

# Trigger a capture on the rising edge of an analog channel at full sample rate (default)
config2 = mso_api.CaptureConfig(
    enabled_channels=[
        # signals directly connected via coax must be set to 1x attenuation mode
        mso_api.AnalogChannel(
            channel=0, name="clock",
            probe_attenuation=mso_api.ProbeAttenuation.PROBE_1X
        ),
        mso_api.AnalogChannel(
            channel=1, name="signal",
            probe_attenuation=mso_api.ProbeAttenuation.PROBE_1X
        ),
    ],
    capture_settings=mso_api.AnalogTriggered(
        # specify by channel_name, or channel_index, both work
        trigger=mso_api.EdgeTrigger(
            channel_name="clock", threshold_volts=1.6,
            direction=mso_api.EdgeTriggerDirection.RISING
        ),
        # capture 1/2 millisecond of data on either side of the trigger
        pre_trigger_seconds=0.5e-3,
        post_trigger_seconds=0.5e-3,
    )
)

# running config.to_dict() or mso.capture(config, ...) will throw
# errors if the configuration is invalid
for n, config in enumerate([config1, config2], start=1):
    _ = config.to_dict(Path("my-capture-save-dir"), PN.MsoPod_4ch_200Mhz_12b_1000Ms)
    print(f"configuration #{n} is valid")
```
